The gate reads what the forge would merge now from the seat git's pull, so it names no forge tool (milestone 47/G, W11.18 follow-up)
The base's protection and required contexts, the forge's conflict word and the merging account's write access come from the pull answer's merging (issues 1275, 1945; ADR 0441 §1); a required context not a success at the commit blocks. Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ (Joined from 13e12d2c.) Builds: design 47 G §252, §310, §312 (GATE14) Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
@@ -363,7 +363,8 @@ func (r *runtime) answer(m rpcMessage) {
|
||||
for _, p := range pulls {
|
||||
if p.Owner+"/"+p.Repo == repository && p.Number == int(number) {
|
||||
return gitseat.PullAnswer{Pull: &gitseat.Pull{Repository: repository, Number: int64(p.Number), Base: p.Base,
|
||||
Head: p.HeadSHA, State: gitseat.PullStateOpen}}, nil
|
||||
Head: p.HeadSHA, State: gitseat.PullStateOpen}, Merging: &gitseat.Merging{Mergeable: true, Protected: true,
|
||||
Required: []string{"mesh/check"}, Writes: true}}, nil
|
||||
}
|
||||
}
|
||||
return gitseat.PullAnswer{Error: "no such pull request"}, nil
|
||||
@@ -371,16 +372,6 @@ func (r *runtime) answer(m rpcMessage) {
|
||||
// Green: mesh/check a success at the head asked, as the seat answers.
|
||||
commit, _ := a.Body["commit"].(string)
|
||||
return gitseat.StatusesAnswer{Commit: commit, Statuses: []gitseat.Status{{Context: "mesh/check", State: "success"}}}, nil
|
||||
case mergeStateTool:
|
||||
// Green: mesh/check a success at the head asked, as the gitea module answers.
|
||||
sha, _ := a.Body["sha"].(string)
|
||||
if sha == "" {
|
||||
sha = "0000000"
|
||||
}
|
||||
return toolAnswer(map[string]any{"commit": sha, "statuses": []any{
|
||||
map[string]any{"context": "mesh/check", "state": "success"}},
|
||||
"merge": map[string]any{"branch": "main", "protected": true, "required": []string{"mesh/check"},
|
||||
"blocking": []any{}, "mergeable": true, "says": "main requires mesh/check"}}), nil
|
||||
case "gitea.gitea_note_append", "gitea.gitea_delivery_view", "gitea.gitea_commit_status", "gitea.gitea_merge_pull_request",
|
||||
"gitea.gitea_add_comment":
|
||||
return toolAnswer(map[string]any{}), nil
|
||||
|
||||
@@ -20,10 +20,10 @@ func TestTheGateReadsAndHearsTheForgeThroughTheSeatGit(t *testing.T) {
|
||||
"seat:git.open-pulls": gitseat.OpenPullsAnswer{Pulls: []gitseat.OpenPull{{Pull: pull, Required: []string{"mesh/check"},
|
||||
Checks: map[string]gitseat.Status{"mesh/check": {Context: "mesh/check", State: "pending"}}}}},
|
||||
"seat:git.contains": gitseat.ContainsAnswer{Contains: true},
|
||||
"seat:git.pull": gitseat.PullAnswer{Pull: &pull},
|
||||
"seat:git.pull": gitseat.PullAnswer{Pull: &pull, Merging: &gitseat.Merging{Mergeable: true, Protected: true,
|
||||
Required: []string{"mesh/check"}, Writes: true}},
|
||||
"seat:git.statuses": gitseat.StatusesAnswer{Commit: head, Combined: "pending",
|
||||
Statuses: []gitseat.Status{{Context: "mesh/check", State: "pending", By: "mesh"}}},
|
||||
mergeStateTool: map[string]any{"commit": head, "merge": map[string]any{"branch": "main", "mergeable": true}},
|
||||
}
|
||||
for _, c := range []struct {
|
||||
name string
|
||||
@@ -48,10 +48,23 @@ func TestTheGateReadsAndHearsTheForgeThroughTheSeatGit(t *testing.T) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []any{c.Commit, *c.Pull, c.Statuses, *c.Merge.Mergeable}, nil
|
||||
p := *c.Pull
|
||||
conflict := *p.Mergeable
|
||||
p.Mergeable = nil
|
||||
return []any{c.Commit, p, c.Statuses, conflict, *c.Merge.Mergeable, c.Merge.Blocking[0].Context + " " +
|
||||
c.Merge.Blocking[0].State, c.Merge.Says, *c.Merge.Writes}, nil
|
||||
}, want: []any{head, ForgePull{Number: 7, State: "closed", Merged: true, Base: "main", Head: "feat/x", HeadSHA: head,
|
||||
MergeCommit: pull.MergeCommit, HTMLURL: pull.URL}, []ForgeStatus{{Context: "mesh/check", State: "pending", Creator: "mesh"}}, true},
|
||||
asked: []string{"seat:git.pull", "seat:git.statuses", mergeStateTool}},
|
||||
MergeCommit: pull.MergeCommit, HTMLURL: pull.URL}, []ForgeStatus{{Context: "mesh/check", State: "pending", Creator: "mesh"}},
|
||||
true, false, "mesh/check pending", "main requires mesh/check", true},
|
||||
asked: []string{"seat:git.pull", "seat:git.statuses"}},
|
||||
// What the forge would merge now comes from the pull answer's merging alone: none for a commit no pull request names.
|
||||
{name: "no merge state for a commit alone", read: func(f seatForge) (any, error) {
|
||||
c, err := f.Statuses("novox", "app", 0, head, "main")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []any{c.Merge.Mergeable == nil, c.Merge.Writes == nil}, nil
|
||||
}, want: []any{true, true}, asked: []string{"seat:git.statuses"}},
|
||||
{name: "a pull request not read", failed: "seat:git.pull",
|
||||
read: func(f seatForge) (any, error) { return f.Statuses("novox", "app", 7, "", "") },
|
||||
asked: []string{"seat:git.pull"}},
|
||||
|
||||
@@ -115,7 +115,7 @@ func TestItClaimsTheDeliverySeatAndFollowsWhatItHandles(t *testing.T) {
|
||||
// merge state through the one statuses tool the seat does not carry yet (seatForge.mergeState); the forge's writes are the
|
||||
// bridge's (ADR 0437 §8). It uses operator-channel only to say its conditions (ADR 0437 §9, issue 1275): the teller asks
|
||||
// (ADR 0437 §6).
|
||||
want := []string{"seat:git.pull", "seat:git.statuses", "seat:git.open-pulls", "seat:git.contains", mergeStateTool, "seat:mesh-hand-acts.hand-act"}
|
||||
want := []string{"seat:git.pull", "seat:git.statuses", "seat:git.open-pulls", "seat:git.contains", "seat:mesh-hand-acts.hand-act"}
|
||||
if !reflect.DeepEqual(m.Uses, []string{"operator-channel"}) {
|
||||
t.Fatalf("uses %v: operator-channel, for its conditions alone", m.Uses)
|
||||
}
|
||||
|
||||
@@ -163,11 +163,13 @@ func gitAsk[A any](f seatForge, verb string, in any, errOf func(*A) string) (*A,
|
||||
}
|
||||
|
||||
// Statuses reads a pull request by its number through `pull` (its head when no commit is named) and the commit's statuses
|
||||
// through `statuses`. What the forge would merge now (the base's protection, its conflict word, whether the merging
|
||||
// account writes) is read by mergeState.
|
||||
// through `statuses`. What the forge would merge now (the base's protection and its required contexts, the forge's
|
||||
// conflict word, whether the merging account writes) is the pull answer's `merging` (mergeState); a commit no pull
|
||||
// request names has none.
|
||||
func (f seatForge) Statuses(owner, repo string, number int, commit, base string) (*ForgeChecks, error) {
|
||||
repository := owner + "/" + repo
|
||||
c := &ForgeChecks{Commit: commit}
|
||||
var merging *gitseat.Merging
|
||||
if number > 0 {
|
||||
a, err := gitAsk(f, gitseat.VerbPull, gitseat.PullInput{Repository: repository, Number: int64(number)},
|
||||
func(a *gitseat.PullAnswer) string { return a.Error })
|
||||
@@ -177,7 +179,7 @@ func (f seatForge) Statuses(owner, repo string, number int, commit, base string)
|
||||
if a.Pull == nil {
|
||||
return nil, fmt.Errorf("the seat git's pull named no pull request for %s#%d", repository, number)
|
||||
}
|
||||
c.Pull = forgePullOf(*a.Pull)
|
||||
c.Pull, merging = forgePullOf(*a.Pull), a.Merging
|
||||
if c.Commit == "" {
|
||||
c.Commit = a.Pull.Head
|
||||
}
|
||||
@@ -197,50 +199,58 @@ func (f seatForge) Statuses(owner, repo string, number int, commit, base string)
|
||||
for _, st := range s.Statuses {
|
||||
c.Statuses = append(c.Statuses, forgeStatusOf(st))
|
||||
}
|
||||
merge, mergeable, err := f.mergeState(owner, repo, number, c.Commit, base)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Merge = merge
|
||||
if c.Pull != nil {
|
||||
c.Pull.Mergeable = mergeable
|
||||
c.Merge = mergeState(repository, base, merging, c.Statuses)
|
||||
if c.Pull != nil && merging != nil {
|
||||
mergeable := merging.Mergeable
|
||||
c.Pull.Mergeable = &mergeable
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// mergeState is what the forge would merge now: the base's protection and its required statuses, the forge's own word on
|
||||
// a conflict, and whether the merging account writes the repository. The seat `git` names none of them yet (dev-G-record's
|
||||
// finding on W11.18): until it does, they are read through the forge holder's statuses tool, the one read left there.
|
||||
func (f seatForge) mergeState(owner, repo string, number int, commit, base string) (ForgeMerge, *bool, error) {
|
||||
args := map[string]any{"owner": owner, "repo": repo, "sha": commit}
|
||||
if number > 0 {
|
||||
args["number"] = number
|
||||
// mergeState is what the forge would merge now, from the seat git's `merging` (issues 1275, 1945; ADR 0441 §1): the base's
|
||||
// protection and its required contexts, each required context not a success at the commit blocking, and whether the
|
||||
// merging account writes. Without `merging` (a commit no pull request names, or a pull request merged or closed) whether
|
||||
// it merges is not known.
|
||||
func mergeState(repository, base string, m *gitseat.Merging, statuses []ForgeStatus) ForgeMerge {
|
||||
out := ForgeMerge{Branch: base, Required: []string{}}
|
||||
if m == nil {
|
||||
out.Says = "the seat git says what merges only for an open pull request"
|
||||
return out
|
||||
}
|
||||
if base != "" {
|
||||
args["base"] = base
|
||||
out.Protected, out.Required = m.Protected, append([]string{}, m.Required...)
|
||||
newest := map[string]string{}
|
||||
for _, s := range statuses {
|
||||
if _, seen := newest[s.Context]; !seen {
|
||||
newest[s.Context] = s.State
|
||||
}
|
||||
}
|
||||
raw, err := f.ask(mergeStateTool, args)
|
||||
if err != nil {
|
||||
return ForgeMerge{}, nil, err
|
||||
for _, context := range out.Required {
|
||||
if state := newest[context]; state != "success" {
|
||||
if state == "" {
|
||||
state = "missing"
|
||||
}
|
||||
out.Blocking = append(out.Blocking, struct {
|
||||
Context string `json:"context"`
|
||||
State string `json:"state"`
|
||||
}{context, state})
|
||||
}
|
||||
}
|
||||
answer, output, ok := answerOf(raw)
|
||||
if !ok {
|
||||
return ForgeMerge{}, nil, fmt.Errorf("the forge refused its merge state: %s", lastLine(output))
|
||||
mergeable := len(out.Blocking) == 0
|
||||
out.Mergeable = &mergeable
|
||||
switch {
|
||||
case len(out.Required) == 0:
|
||||
out.Says = base + " requires no status"
|
||||
default:
|
||||
out.Says = base + " requires " + strings.Join(out.Required, ", ")
|
||||
}
|
||||
var c ForgeChecks
|
||||
if err := json.Unmarshal(answer, &c); err != nil || c.Commit == "" {
|
||||
return ForgeMerge{}, nil, fmt.Errorf("the forge's merge state is not readable: %s", clip(string(answer), 200))
|
||||
writes := m.Writes
|
||||
out.Writes = &writes
|
||||
if !writes {
|
||||
out.CannotWrite = "the forge's merging account may not write " + repository + ": its statuses and its merge are refused"
|
||||
}
|
||||
var mergeable *bool
|
||||
if c.Pull != nil {
|
||||
mergeable = c.Pull.Mergeable
|
||||
}
|
||||
return c.Merge, mergeable, nil
|
||||
return out
|
||||
}
|
||||
|
||||
// mergeStateTool is the one forge read the seat `git` does not carry yet (see mergeState).
|
||||
const mergeStateTool = "gitea.gitea_commit_statuses"
|
||||
|
||||
// OpenPulls reads every open pull request through `open-pulls`.
|
||||
func (f seatForge) OpenPulls() ([]OpenPull, error) {
|
||||
a, err := gitAsk(f, gitseat.VerbOpenPulls, gitseat.OpenPullsInput{}, func(a *gitseat.OpenPullsAnswer) string { return a.Error })
|
||||
|
||||
@@ -257,7 +257,6 @@
|
||||
"seat:git.statuses",
|
||||
"seat:git.open-pulls",
|
||||
"seat:git.contains",
|
||||
"gitea.gitea_commit_statuses",
|
||||
"seat:mesh-hand-acts.hand-act"
|
||||
],
|
||||
"state": [
|
||||
|
||||
Reference in New Issue
Block a user