Feature 57 (config and provisioning): modules declare config and provisions, their files rendered from templates; synced with main (issues of feature 57, 874)
This commit is contained in:
@@ -9,6 +9,8 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"gitea/internal/forgehttp"
|
||||
)
|
||||
|
||||
// gitea_commit (novox/hq issue 375): one commit on a branch of its own, so an agent follows the branch / pull
|
||||
@@ -33,6 +35,11 @@ type CommitAsk struct {
|
||||
Message string
|
||||
Files []FileChange
|
||||
Patch string // a unified diff, as git diff writes it, instead of Files
|
||||
// PatchFile is the name of a file in the spool directory holding the patch (novox/hq issue 852), with the
|
||||
// SHA-256 its caller says it has; commitAsk reads it into Patch.
|
||||
PatchFile string
|
||||
PatchSHA256 string
|
||||
patchFileGiven bool
|
||||
}
|
||||
|
||||
// Committed is what gitea_commit made.
|
||||
@@ -67,6 +74,11 @@ func readCommitAsk(args map[string]any) (CommitAsk, error) {
|
||||
a := CommitAsk{Owner: text(args, "owner"), Repo: text(args, "repo"), Branch: text(args, "branch"),
|
||||
Base: text(args, "base"), Message: strings.TrimSpace(raw(args, "message"))}
|
||||
a.Patch = raw(args, "patch")
|
||||
if v, ok := args["patch_file"]; ok && v != nil {
|
||||
a.patchFileGiven = true
|
||||
a.PatchFile = raw(args, "patch_file")
|
||||
}
|
||||
a.PatchSHA256 = strings.ToLower(strings.TrimSpace(raw(args, "patch_sha256")))
|
||||
if a.Branch == "" {
|
||||
return a, refuse("branch is required: the branch the commit goes on, a branch of its own (created from base when absent). Nothing was sent")
|
||||
}
|
||||
@@ -109,11 +121,23 @@ func readCommitAsk(args map[string]any) (CommitAsk, error) {
|
||||
a.Files = append(a.Files, fc)
|
||||
}
|
||||
}
|
||||
sources := 0
|
||||
for _, g := range []bool{len(a.Files) > 0, strings.TrimSpace(a.Patch) != "", a.patchFileGiven} {
|
||||
if g {
|
||||
sources++
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case len(a.Files) == 0 && strings.TrimSpace(a.Patch) == "":
|
||||
return a, refuse("give files (each {path, content | content_base64 | delete: true}) or patch (a unified diff, as git diff writes it). Nothing was sent")
|
||||
case len(a.Files) > 0 && strings.TrimSpace(a.Patch) != "":
|
||||
return a, refuse("give files or patch, not both: one commit carries one of them. Nothing was sent")
|
||||
case sources == 0:
|
||||
return a, refuse("give files (each {path, content | content_base64 | delete: true}), patch (a unified diff, as git diff writes it) or patch_file (the name of a patch in the spool directory). Nothing was sent")
|
||||
case sources > 1:
|
||||
return a, refuse("give files, patch or patch_file, not more than one: one commit carries one of them. Nothing was sent")
|
||||
case a.PatchSHA256 != "" && !a.patchFileGiven:
|
||||
return a, refuse("patch_sha256 goes with patch_file only. Nothing was sent")
|
||||
case a.patchFileGiven:
|
||||
if err := checkPatchFileAsk(a); err != nil {
|
||||
return a, err
|
||||
}
|
||||
case len(a.Files) > maxFiles:
|
||||
return a, refuse("%d files is more than the %d one commit carries. Nothing was sent", len(a.Files), maxFiles)
|
||||
case len(a.Patch) > maxPatchBytes:
|
||||
@@ -132,13 +156,13 @@ func readCommitAsk(args map[string]any) (CommitAsk, error) {
|
||||
return a, refuse("%s is named twice in files. Nothing was sent", fc.Path)
|
||||
}
|
||||
seen[fc.Path] = true
|
||||
given := 0
|
||||
have := 0
|
||||
for _, g := range []bool{fc.Content != nil, fc.ContentBase64 != nil, fc.Delete} {
|
||||
if g {
|
||||
given++
|
||||
have++
|
||||
}
|
||||
}
|
||||
if given != 1 {
|
||||
if have != 1 {
|
||||
return a, refuse("files[%d] (%s) needs exactly one of content, content_base64 or delete: true. Nothing was sent", i, fc.Path)
|
||||
}
|
||||
if fc.Content != nil && !utf8.ValidString(*fc.Content) {
|
||||
@@ -211,7 +235,32 @@ func commit(ctx context.Context, f *Forge, a CommitAsk) (*Committed, error) {
|
||||
// Read the rules, or refuse: a branch the rules could not be read for is never written (fails closed).
|
||||
var rules []Protection
|
||||
if err := f.GetJSON(ctx, repo+"/branch_protections", &rules); err != nil {
|
||||
return nil, fmt.Errorf("the protection rules of %s/%s could not be read, so the commit was not judged and nothing was sent: %w", a.Owner, a.Repo, err)
|
||||
var status *forgehttp.StatusError
|
||||
if !errors.As(err, &status) || status.Status != http.StatusForbidden {
|
||||
return nil, fmt.Errorf("the protection rules of %s/%s could not be read, so the commit was not judged and nothing was sent: %w", a.Owner, a.Repo, err)
|
||||
}
|
||||
// The rules are a repository administrator's to read, and the tools' account is none (novox/hq ADR 0365
|
||||
// §2). What it may read is the protection the forge applies to an existing branch: one protected is
|
||||
// refused here. A branch not made yet is covered by no rule this account can see, and the forge itself
|
||||
// judges the commit that makes it against every rule, refusing one a rule guards.
|
||||
if err := targetRefused(a.Branch, r.DefaultBranch, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b, err := f.branch(ctx, repo, a.Branch)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("whether a protection rule guards %s of %s/%s could not be read, so nothing was sent: %w", a.Branch, a.Owner, a.Repo, err)
|
||||
}
|
||||
if b != nil && b.Protected {
|
||||
rule := b.Rule
|
||||
if rule == "" {
|
||||
rule = a.Branch
|
||||
}
|
||||
rules = []Protection{{RuleName: rule}}
|
||||
if !ruleCovers(rule, a.Branch) {
|
||||
return nil, refuse("a commit on %s is refused: the protection rule %s guards it (novox/hq ADR 0266). Put it on a "+
|
||||
"branch of its own. Nothing was sent", a.Branch, rule)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := targetRefused(a.Branch, r.DefaultBranch, rules); err != nil {
|
||||
return nil, err
|
||||
|
||||
Reference in New Issue
Block a user