The forge's contents and diffpatch endpoints take a branch, never a commit to
build on, so the parent is read from the commit the forge made, and a branch
that moved under the commit is said. Blobs are read at the commit read first,
not at the branch name. Protection rules match as Gitea 1.27 matches them
([...], {a,b}, escapes, a plain name in any case), in the Go bundle and in
guard.ts alike. One commit carries at most 4 MiB per file or patch and 16 MiB
together; code search stops at its deadline and says so. The HTTP client the
two Go bundles copied is one package, internal/forgehttp.
117 lines
3.4 KiB
Go
117 lines
3.4 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/url"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"gitea/internal/forgehttp"
|
|
)
|
|
|
|
// The forge as this bundle reaches it: its own HTTP interface on the machine, as the forge's admin account
|
|
// (internal/forgehttp, shared with the npm-registry bundle beside it).
|
|
|
|
// Forge is the forge's HTTP interface.
|
|
type Forge struct {
|
|
forgehttp.Client
|
|
}
|
|
|
|
// ForgeFromEnv is the forge as the manifest hands it to this bundle.
|
|
func ForgeFromEnv() (*Forge, error) {
|
|
c, missing := forgehttp.FromEnv()
|
|
if len(missing) > 0 {
|
|
return nil, fmt.Errorf("the forge cannot be reached: %s not set by the manifest", strings.Join(missing, ", "))
|
|
}
|
|
c.Timeout = deadline
|
|
return &Forge{Client: c}, nil
|
|
}
|
|
|
|
// errNotFound is the forge answering 404.
|
|
var errNotFound = forgehttp.ErrNotFound
|
|
|
|
// maxBody is the largest JSON answer read from the forge; maxArchive the largest archive code search reads.
|
|
const (
|
|
maxBody = forgehttp.MaxBody
|
|
maxArchive = 64 << 20
|
|
)
|
|
|
|
// Refused is a refusal of policy or of an argument, in the words the caller sees.
|
|
type Refused struct{ msg string }
|
|
|
|
func (r *Refused) Error() string { return r.msg }
|
|
|
|
func refuse(format string, a ...any) error { return &Refused{fmt.Sprintf(format, a...)} }
|
|
|
|
var nameShape = regexp.MustCompile(`^[A-Za-z0-9_][A-Za-z0-9_.-]*$`)
|
|
|
|
// repoPath is /api/v1/repos/{owner}/{repo} for names that cannot become another path.
|
|
func repoPath(owner, repo string) (string, error) {
|
|
for _, v := range [][2]string{{"owner", owner}, {"name", repo}} {
|
|
if !nameShape.MatchString(v[1]) || v[1] == "." || v[1] == ".." {
|
|
return "", refuse("%q is not a repository %s", v[1], v[0])
|
|
}
|
|
}
|
|
return "/api/v1/repos/" + owner + "/" + repo, nil
|
|
}
|
|
|
|
// segments encodes a value that is several path segments — a branch like fix/1-x, a file's path — refusing
|
|
// any segment that could make the request another path: empty, `.` or `..`, or holding `?`, `#`, `%`, `\`
|
|
// or a control character (the same rule as the TypeScript bundle's pathSegments, guard.ts).
|
|
func segments(what, value string) (string, error) {
|
|
parts := strings.Split(value, "/")
|
|
for i, p := range parts {
|
|
if p == "" || p == "." || p == ".." || strings.ContainsAny(p, "?#%\\") || strings.IndexFunc(p, func(r rune) bool { return r < 0x20 || r == 0x7f }) >= 0 {
|
|
return "", refuse("%q is not a %s this tool puts in a path. Nothing was sent", value, what)
|
|
}
|
|
parts[i] = url.PathEscape(p)
|
|
}
|
|
return strings.Join(parts, "/"), nil
|
|
}
|
|
|
|
// Repo is what the tools read of a repository.
|
|
type Repo struct {
|
|
FullName string `json:"full_name"`
|
|
DefaultBranch string `json:"default_branch"`
|
|
Empty bool `json:"empty"`
|
|
}
|
|
|
|
// Protection is one branch protection rule, as far as covering a branch goes.
|
|
type Protection struct {
|
|
RuleName string `json:"rule_name"`
|
|
BranchName string `json:"branch_name"`
|
|
}
|
|
|
|
func (p Protection) name() string {
|
|
if p.RuleName != "" {
|
|
return p.RuleName
|
|
}
|
|
return p.BranchName
|
|
}
|
|
|
|
// Branch is one branch with the commit it points at.
|
|
type Branch struct {
|
|
Name string `json:"name"`
|
|
Commit struct {
|
|
ID string `json:"id"`
|
|
} `json:"commit"`
|
|
}
|
|
|
|
// branch is the named branch, nil when the forge has none of that name.
|
|
func (f *Forge) branch(ctx context.Context, repo, name string) (*Branch, error) {
|
|
seg, err := segments("branch", name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var b Branch
|
|
if err := f.GetJSON(ctx, repo+"/branches/"+seg, &b); err != nil {
|
|
if errors.Is(err, errNotFound) {
|
|
return nil, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
return &b, nil
|
|
}
|