mirror of
https://github.com/hanzoai/forge.git
synced 2026-10-30 15:00:39 +00:00
2978 files. The fork is 2026-07-15 (rebrand: gitea.dev -> github.com/hanzoai/git), so 2026 is the year, and "Copyright 2026 Hanzo AI, Inc." was already the form used by the handful of files that had ours. Shape is now: // Copyright 2026 Hanzo AI, Inc. All rights reserved. // Copyright 2015 The Gogs Authors. All rights reserved. // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT Ours on top, theirs retained verbatim. Retained because MIT requires it, and separately because they are true — those authors did write that code. Not renamed to "The Git Authors": Git is a different project under a different license, and putting its name here would attribute this code to people who have nothing to do with it. Adding our line is the fix; overwriting theirs would have been both a licence violation and a false statement. gofmt still reports import-order drift in some of these files. It predates this change (verified against a stash) and is left alone rather than burying a one-line-per-file legal change under 3000 files of unrelated reordering. Build clean. Co-authored-by: Hanzo Dev <[email protected]>
296 lines
7.4 KiB
Go
296 lines
7.4 KiB
Go
// Copyright 2026 Hanzo AI, Inc. All rights reserved.
|
|
// 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"
|
|
|
|
"github.com/hanzoai/git/modules/json"
|
|
lfslock "github.com/hanzoai/git/modules/structs"
|
|
|
|
"github.com/charmbracelet/git-lfs-transfer/transfer"
|
|
)
|
|
|
|
var _ transfer.LockBackend = &gitLockBackend{}
|
|
|
|
type gitLockBackend struct {
|
|
ctx context.Context
|
|
g *GitBackend
|
|
server *url.URL
|
|
authToken string
|
|
internalAuth string
|
|
logger transfer.Logger
|
|
}
|
|
|
|
func newGitLockBackend(g *GitBackend) transfer.LockBackend {
|
|
server := g.server.JoinPath("locks")
|
|
return &gitLockBackend{ctx: g.ctx, g: g, server: server, authToken: g.authToken, internalAuth: g.internalAuth, logger: g.logger}
|
|
}
|
|
|
|
// Create implements transfer.LockBackend
|
|
func (g *gitLockBackend) 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,
|
|
headerInternalAuth: 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 unmarshal 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 := newGitLock(g, respLock.ID, respLock.Path, respLock.LockedAt, owner)
|
|
return lock, nil
|
|
}
|
|
|
|
// Unlock implements transfer.LockBackend
|
|
func (g *gitLockBackend) 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,
|
|
headerInternalAuth: 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 *gitLockBackend) 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 *gitLockBackend) 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 *gitLockBackend) 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 *gitLockBackend) 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,
|
|
headerInternalAuth: 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 unmarshal 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 := newGitLock(g, respLock.ID, respLock.Path, respLock.LockedAt, owner)
|
|
respLocks = append(respLocks, lock)
|
|
}
|
|
return respLocks, respBody.Next, nil
|
|
}
|
|
|
|
var _ transfer.Lock = &gitLock{}
|
|
|
|
type gitLock struct {
|
|
g *gitLockBackend
|
|
id string
|
|
path string
|
|
lockedAt time.Time
|
|
owner string
|
|
}
|
|
|
|
func newGitLock(g *gitLockBackend, id, path string, lockedAt time.Time, owner string) transfer.Lock {
|
|
return &gitLock{g: g, id: id, path: path, lockedAt: lockedAt, owner: owner}
|
|
}
|
|
|
|
// Unlock implements transfer.Lock
|
|
func (g *gitLock) Unlock() error {
|
|
return g.g.Unlock(g)
|
|
}
|
|
|
|
// ID implements transfer.Lock
|
|
func (g *gitLock) ID() string {
|
|
return g.id
|
|
}
|
|
|
|
// Path implements transfer.Lock
|
|
func (g *gitLock) Path() string {
|
|
return g.path
|
|
}
|
|
|
|
// FormattedTimestamp implements transfer.Lock
|
|
func (g *gitLock) FormattedTimestamp() string {
|
|
return g.lockedAt.UTC().Format(time.RFC3339)
|
|
}
|
|
|
|
// OwnerName implements transfer.Lock
|
|
func (g *gitLock) OwnerName() string {
|
|
return g.owner
|
|
}
|
|
|
|
func (g *gitLock) CurrentUser() (string, error) {
|
|
return userSelf, nil
|
|
}
|
|
|
|
// AsLockSpec implements transfer.Lock
|
|
func (g *gitLock) 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 *gitLock) AsArguments() []string {
|
|
return []string{
|
|
"id=" + g.ID(),
|
|
"path=" + g.Path(),
|
|
"locked-at=" + g.FormattedTimestamp(),
|
|
"ownername=" + g.OwnerName(),
|
|
}
|
|
}
|