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]>
156 lines
3.6 KiB
Go
156 lines
3.6 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"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/hanzoai/git/modules/httplib"
|
|
"github.com/hanzoai/git/modules/private"
|
|
"github.com/hanzoai/git/modules/setting"
|
|
"github.com/hanzoai/git/modules/util"
|
|
|
|
"github.com/charmbracelet/git-lfs-transfer/transfer"
|
|
)
|
|
|
|
// internalRepoRoutePath is the repo branch of the internal API, relative to
|
|
// setting.LocalURL — derived from private.RoutePrefix so a mount change reaches
|
|
// both the URL builder and the matcher below.
|
|
var internalRepoRoutePath = private.RoutePrefix[1:] + "/repo/"
|
|
|
|
// HTTP headers
|
|
const (
|
|
headerAccept = "Accept"
|
|
headerAuthorization = "Authorization"
|
|
headerInternalAuth = "X-Internal-Auth"
|
|
headerContentType = "Content-Type"
|
|
headerContentLength = "Content-Length"
|
|
)
|
|
|
|
// MIME types
|
|
const (
|
|
mimeGitLFS = "application/vnd.git-lfs+json"
|
|
mimeOctetStream = "application/octet-stream"
|
|
)
|
|
|
|
// SSH protocol action keys
|
|
const (
|
|
actionDownload = "download"
|
|
actionUpload = "upload"
|
|
actionVerify = "verify"
|
|
)
|
|
|
|
// SSH protocol argument keys
|
|
const (
|
|
argCursor = "cursor"
|
|
argExpiresAt = "expires-at"
|
|
argID = "id"
|
|
argLimit = "limit"
|
|
argPath = "path"
|
|
argRefname = "refname"
|
|
argToken = "token"
|
|
argTransfer = "transfer"
|
|
)
|
|
|
|
// Default username constants
|
|
const (
|
|
userSelf = "(self)"
|
|
userUnknown = "(unknown)"
|
|
)
|
|
|
|
// Operations enum
|
|
const (
|
|
opDownload = iota + 1
|
|
opUpload
|
|
)
|
|
|
|
var opMap = map[string]int{
|
|
"download": opDownload,
|
|
"upload": opUpload,
|
|
}
|
|
|
|
var ErrMissingID = fmt.Errorf("%w: missing id arg", transfer.ErrMissingData)
|
|
|
|
func statusCodeToErr(code int) error {
|
|
switch code {
|
|
case http.StatusBadRequest:
|
|
return transfer.ErrParseError
|
|
case http.StatusConflict:
|
|
return transfer.ErrConflict
|
|
case http.StatusForbidden:
|
|
return transfer.ErrForbidden
|
|
case http.StatusNotFound:
|
|
return transfer.ErrNotFound
|
|
case http.StatusUnauthorized:
|
|
return transfer.ErrUnauthorized
|
|
default:
|
|
return fmt.Errorf("server returned status %v: %v", code, http.StatusText(code))
|
|
}
|
|
}
|
|
|
|
func toInternalLFSURL(s string) string {
|
|
pos1 := strings.Index(s, "://")
|
|
if pos1 == -1 {
|
|
return ""
|
|
}
|
|
appSubURLWithSlash := setting.AppSubURL + "/"
|
|
pos2 := strings.Index(s[pos1+3:], appSubURLWithSlash)
|
|
if pos2 == -1 {
|
|
return ""
|
|
}
|
|
routePath := s[pos1+3+pos2+len(appSubURLWithSlash):]
|
|
fields := strings.SplitN(routePath, "/", 3)
|
|
if len(fields) < 3 || !strings.HasPrefix(fields[2], "info/lfs") {
|
|
return ""
|
|
}
|
|
return setting.LocalURL + internalRepoRoutePath + routePath
|
|
}
|
|
|
|
func isInternalLFSURL(s string) bool {
|
|
if !strings.HasPrefix(s, setting.LocalURL) {
|
|
return false
|
|
}
|
|
u, err := url.Parse(s)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
routePath := util.PathJoinRelX(u.Path)
|
|
subRoutePath, cut := strings.CutPrefix(routePath, internalRepoRoutePath)
|
|
if !cut {
|
|
return false
|
|
}
|
|
fields := strings.SplitN(subRoutePath, "/", 3)
|
|
if len(fields) < 3 || !strings.HasPrefix(fields[2], "info/lfs") {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func newInternalRequestLFS(ctx context.Context, internalURL, method string, headers map[string]string, body any) *httplib.Request {
|
|
if !isInternalLFSURL(internalURL) {
|
|
return nil
|
|
}
|
|
req := private.NewInternalRequest(ctx, internalURL, method)
|
|
req.SetReadWriteTimeout(0)
|
|
for k, v := range headers {
|
|
req.Header(k, v)
|
|
}
|
|
switch body := body.(type) {
|
|
case nil: // do nothing
|
|
case []byte:
|
|
req.Body(body) // []byte
|
|
case io.Reader:
|
|
req.Body(body) // io.Reader or io.ReadCloser
|
|
default:
|
|
panic(fmt.Sprintf("unsupported request body type %T", body))
|
|
}
|
|
return req
|
|
}
|