| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295 |
- // Copyright 2024 The Gitea Authors. All rights reserved.
- // SPDX-License-Identifier: MIT
-
- package backend
-
- import (
- "context"
- "errors"
- "fmt"
- "io"
- "net/http"
- "net/url"
- "strconv"
- "time"
-
- "code.gitea.io/gitea/modules/json"
- lfslock "code.gitea.io/gitea/modules/structs"
-
- "github.com/charmbracelet/git-lfs-transfer/transfer"
- )
-
- var _ transfer.LockBackend = &giteaLockBackend{}
-
- type giteaLockBackend struct {
- ctx context.Context
- g *GiteaBackend
- server *url.URL
- authToken string
- internalAuth string
- logger transfer.Logger
- }
-
- func newGiteaLockBackend(g *GiteaBackend) transfer.LockBackend {
- server := g.server.JoinPath("locks")
- return &giteaLockBackend{ctx: g.ctx, g: g, server: server, authToken: g.authToken, internalAuth: g.internalAuth, logger: g.logger}
- }
-
- // Create implements transfer.LockBackend
- func (g *giteaLockBackend) Create(path, refname string) (transfer.Lock, error) {
- reqBody := lfslock.LFSLockRequest{Path: path}
-
- bodyBytes, err := json.Marshal(reqBody)
- if err != nil {
- g.logger.Log("json marshal error", err)
- return nil, err
- }
- headers := map[string]string{
- headerAuthorization: g.authToken,
- headerGiteaInternalAuth: g.internalAuth,
- headerAccept: mimeGitLFS,
- headerContentType: mimeGitLFS,
- }
- req := newInternalRequestLFS(g.ctx, g.server.String(), http.MethodPost, headers, bodyBytes)
- resp, err := req.Response()
- if err != nil {
- g.logger.Log("http request error", err)
- return nil, err
- }
- defer resp.Body.Close()
- respBytes, err := io.ReadAll(resp.Body)
- if err != nil {
- g.logger.Log("http read error", err)
- return nil, err
- }
- if resp.StatusCode != http.StatusCreated {
- g.logger.Log("http statuscode error", resp.StatusCode, statusCodeToErr(resp.StatusCode))
- return nil, statusCodeToErr(resp.StatusCode)
- }
- var respBody lfslock.LFSLockResponse
- err = json.Unmarshal(respBytes, &respBody)
- if err != nil {
- g.logger.Log("json umarshal error", err)
- return nil, err
- }
-
- if respBody.Lock == nil {
- g.logger.Log("api returned nil lock")
- return nil, errors.New("api returned nil lock")
- }
- respLock := respBody.Lock
- owner := userUnknown
- if respLock.Owner != nil {
- owner = respLock.Owner.Name
- }
- lock := newGiteaLock(g, respLock.ID, respLock.Path, respLock.LockedAt, owner)
- return lock, nil
- }
-
- // Unlock implements transfer.LockBackend
- func (g *giteaLockBackend) Unlock(lock transfer.Lock) error {
- reqBody := lfslock.LFSLockDeleteRequest{}
-
- bodyBytes, err := json.Marshal(reqBody)
- if err != nil {
- g.logger.Log("json marshal error", err)
- return err
- }
- headers := map[string]string{
- headerAuthorization: g.authToken,
- headerGiteaInternalAuth: g.internalAuth,
- headerAccept: mimeGitLFS,
- headerContentType: mimeGitLFS,
- }
- req := newInternalRequestLFS(g.ctx, g.server.JoinPath(lock.ID(), "unlock").String(), http.MethodPost, headers, bodyBytes)
- resp, err := req.Response()
- if err != nil {
- g.logger.Log("http request error", err)
- return err
- }
- defer resp.Body.Close()
- if resp.StatusCode != http.StatusOK {
- g.logger.Log("http statuscode error", resp.StatusCode, statusCodeToErr(resp.StatusCode))
- return statusCodeToErr(resp.StatusCode)
- }
- // no need to read response
-
- return nil
- }
-
- // FromPath implements transfer.LockBackend
- func (g *giteaLockBackend) FromPath(path string) (transfer.Lock, error) {
- v := url.Values{
- argPath: []string{path},
- }
-
- respLocks, _, err := g.queryLocks(v)
- if err != nil {
- return nil, err
- }
-
- if len(respLocks) == 0 {
- return nil, transfer.ErrNotFound
- }
- return respLocks[0], nil
- }
-
- // FromID implements transfer.LockBackend
- func (g *giteaLockBackend) FromID(id string) (transfer.Lock, error) {
- v := url.Values{
- argID: []string{id},
- }
-
- respLocks, _, err := g.queryLocks(v)
- if err != nil {
- return nil, err
- }
-
- if len(respLocks) == 0 {
- return nil, transfer.ErrNotFound
- }
- return respLocks[0], nil
- }
-
- // Range implements transfer.LockBackend
- func (g *giteaLockBackend) Range(cursor string, limit int, iter func(transfer.Lock) error) (string, error) {
- v := url.Values{
- argLimit: []string{strconv.FormatInt(int64(limit), 10)},
- }
- if cursor != "" {
- v[argCursor] = []string{cursor}
- }
-
- respLocks, cursor, err := g.queryLocks(v)
- if err != nil {
- return "", err
- }
-
- for _, lock := range respLocks {
- err := iter(lock)
- if err != nil {
- return "", err
- }
- }
- return cursor, nil
- }
-
- func (g *giteaLockBackend) queryLocks(v url.Values) ([]transfer.Lock, string, error) {
- serverURLWithQuery := g.server.JoinPath() // get a copy
- serverURLWithQuery.RawQuery = v.Encode()
- headers := map[string]string{
- headerAuthorization: g.authToken,
- headerGiteaInternalAuth: g.internalAuth,
- headerAccept: mimeGitLFS,
- headerContentType: mimeGitLFS,
- }
- req := newInternalRequestLFS(g.ctx, serverURLWithQuery.String(), http.MethodGet, headers, nil)
- resp, err := req.Response()
- if err != nil {
- g.logger.Log("http request error", err)
- return nil, "", err
- }
- defer resp.Body.Close()
- respBytes, err := io.ReadAll(resp.Body)
- if err != nil {
- g.logger.Log("http read error", err)
- return nil, "", err
- }
- if resp.StatusCode != http.StatusOK {
- g.logger.Log("http statuscode error", resp.StatusCode, statusCodeToErr(resp.StatusCode))
- return nil, "", statusCodeToErr(resp.StatusCode)
- }
- var respBody lfslock.LFSLockList
- err = json.Unmarshal(respBytes, &respBody)
- if err != nil {
- g.logger.Log("json umarshal error", err)
- return nil, "", err
- }
-
- respLocks := make([]transfer.Lock, 0, len(respBody.Locks))
- for _, respLock := range respBody.Locks {
- owner := userUnknown
- if respLock.Owner != nil {
- owner = respLock.Owner.Name
- }
- lock := newGiteaLock(g, respLock.ID, respLock.Path, respLock.LockedAt, owner)
- respLocks = append(respLocks, lock)
- }
- return respLocks, respBody.Next, nil
- }
-
- var _ transfer.Lock = &giteaLock{}
-
- type giteaLock struct {
- g *giteaLockBackend
- id string
- path string
- lockedAt time.Time
- owner string
- }
-
- func newGiteaLock(g *giteaLockBackend, id, path string, lockedAt time.Time, owner string) transfer.Lock {
- return &giteaLock{g: g, id: id, path: path, lockedAt: lockedAt, owner: owner}
- }
-
- // Unlock implements transfer.Lock
- func (g *giteaLock) Unlock() error {
- return g.g.Unlock(g)
- }
-
- // ID implements transfer.Lock
- func (g *giteaLock) ID() string {
- return g.id
- }
-
- // Path implements transfer.Lock
- func (g *giteaLock) Path() string {
- return g.path
- }
-
- // FormattedTimestamp implements transfer.Lock
- func (g *giteaLock) FormattedTimestamp() string {
- return g.lockedAt.UTC().Format(time.RFC3339)
- }
-
- // OwnerName implements transfer.Lock
- func (g *giteaLock) OwnerName() string {
- return g.owner
- }
-
- func (g *giteaLock) CurrentUser() (string, error) {
- return userSelf, nil
- }
-
- // AsLockSpec implements transfer.Lock
- func (g *giteaLock) AsLockSpec(ownerID bool) ([]string, error) {
- msgs := []string{
- "lock " + g.ID(),
- fmt.Sprintf("path %s %s", g.ID(), g.Path()),
- fmt.Sprintf("locked-at %s %s", g.ID(), g.FormattedTimestamp()),
- fmt.Sprintf("ownername %s %s", g.ID(), g.OwnerName()),
- }
- if ownerID {
- user, err := g.CurrentUser()
- if err != nil {
- return nil, fmt.Errorf("error getting current user: %w", err)
- }
- who := "theirs"
- if user == g.OwnerName() {
- who = "ours"
- }
- msgs = append(msgs, fmt.Sprintf("owner %s %s", g.ID(), who))
- }
- return msgs, nil
- }
-
- // AsArguments implements transfer.Lock
- func (g *giteaLock) AsArguments() []string {
- return []string{
- "id=" + g.ID(),
- "path=" + g.Path(),
- "locked-at=" + g.FormattedTimestamp(),
- "ownername=" + g.OwnerName(),
- }
- }
|