The forge-made merge's branch is read from the pull request's head label, not its head ref; the indexer checks the answer's number (issue 1966)
mesh/delivery merged: source.main moved to novox/mesh-delivery@596cb653fc1b
mesh/delivery-group group fix/1966-forge-made-merge merged: every member is merged
mesh/check checking c71d38d073a0 on 596cb653fc1b | novox/mesh-delivery#185 in group fix/1966-forge-made-merge

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
2026-11-09 09:23:07 +01:00
parent 5d1b07bd3e
commit c71d38d073
3 changed files with 12 additions and 5 deletions
@@ -182,6 +182,8 @@ func TestTheBridgeToolsReadTheGroupsAndAskTheEndAgain(t *testing.T) {
api: forgeAnswers(t, "/repos/novox/a/pulls/9", false, "fix/x", h2, "", "main"), fail: "not merged"}, api: forgeAnswers(t, "/repos/novox/a/pulls/9", false, "fix/x", h2, "", "main"), fail: "not merged"},
{name: "a forge pull request merged into another branch", raws: map[string][]byte{}, repository: "novox/a", merge: c2, number: 9, {name: "a forge pull request merged into another branch", raws: map[string][]byte{}, repository: "novox/a", merge: c2, number: 9,
api: forgeAnswers(t, "/repos/novox/a/pulls/9", true, "fix/x", h2, c2, "dev"), fail: "not the trunk"}, api: forgeAnswers(t, "/repos/novox/a/pulls/9", true, "fix/x", h2, c2, "dev"), fail: "not the trunk"},
{name: "a forge pull request with no head branch", raws: map[string][]byte{}, repository: "novox/a", merge: c2, number: 9,
api: forgeAnswers(t, "/repos/novox/a/pulls/9", true, "refs/pull/9/head", h2, c2, "main"), fail: "no head branch"},
{name: "a forge that does not answer", raws: map[string][]byte{}, repository: "novox/a", merge: c2, {name: "a forge that does not answer", raws: map[string][]byte{}, repository: "novox/a", merge: c2,
api: func(string) (json.RawMessage, error) { return nil, errors.New("the forge is down") }, fail: "the forge is down"}, api: func(string) (json.RawMessage, error) { return nil, errors.New("the forge is down") }, fail: "the forge is down"},
} { } {
@@ -214,7 +216,7 @@ func TestTheBridgeToolsReadTheGroupsAndAskTheEndAgain(t *testing.T) {
func forgeAnswers(t *testing.T, path string, merged bool, branch, head, merge, base string) bridge.API { func forgeAnswers(t *testing.T, path string, merged bool, branch, head, merge, base string) bridge.API {
t.Helper() t.Helper()
body, _ := json.Marshal(map[string]any{"number": 9, "merged": merged, "merge_commit_sha": merge, body, _ := json.Marshal(map[string]any{"number": 9, "merged": merged, "merge_commit_sha": merge,
"head": map[string]any{"ref": branch, "sha": head}, "base": map[string]any{"ref": base}}) "head": map[string]any{"ref": "refs/pull/9/head", "label": branch, "sha": head}, "base": map[string]any{"ref": base}})
return func(p string) (json.RawMessage, error) { return func(p string) (json.RawMessage, error) {
if p != path { if p != path {
return nil, errors.New("the forge has no " + p) return nil, errors.New("the forge has no " + p)
@@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/url" "net/url"
"strings"
) )
// ForgePull is a pull request as the forge itself says it: what the bridge needs of a merge made on the forge, not // ForgePull is a pull request as the forge itself says it: what the bridge needs of a merge made on the forge, not
@@ -33,8 +34,8 @@ func ForgeMade(api API, owner, repo string, number int, merge string) (ForgePull
Merged bool `json:"merged"` Merged bool `json:"merged"`
MergeCommitSHA string `json:"merge_commit_sha"` MergeCommitSHA string `json:"merge_commit_sha"`
Head struct { Head struct {
Ref string `json:"ref"` Label string `json:"label"`
SHA string `json:"sha"` SHA string `json:"sha"`
} `json:"head"` } `json:"head"`
Base struct { Base struct {
Ref string `json:"ref"` Ref string `json:"ref"`
@@ -51,5 +52,9 @@ func ForgeMade(api API, owner, repo string, number int, merge string) (ForgePull
case p.Base.Ref != Trunk: case p.Base.Ref != Trunk:
return ForgePull{}, fmt.Errorf("the forge says %s/%s#%d was merged into %s, not the trunk %s", owner, repo, p.Number, p.Base.Ref, Trunk) return ForgePull{}, fmt.Errorf("the forge says %s/%s#%d was merged into %s, not the trunk %s", owner, repo, p.Number, p.Base.Ref, Trunk)
} }
return ForgePull{Number: p.Number, Branch: p.Head.Ref, Head: p.Head.SHA, Merge: p.MergeCommitSHA}, nil // For a merged pull request the forge's head.ref is "refs/pull/N/head"; the branch is in head.label (same-repository pull requests).
if p.Head.Label == "" || strings.HasPrefix(p.Head.Label, "refs/") {
return ForgePull{}, fmt.Errorf("the forge gives no head branch for %s/%s#%d (label %q)", owner, repo, p.Number, p.Head.Label)
}
return ForgePull{Number: p.Number, Branch: p.Head.Label, Head: p.Head.SHA, Merge: p.MergeCommitSHA}, nil
} }
@@ -233,7 +233,7 @@ func (ix *Indexer) load(ctx context.Context, e PendingEntry) (PendingEntry, erro
if err != nil { if err != nil {
return e, fmt.Errorf("the forge bridge's answer for %s is not a forge message: %w", e.Key, err) return e, fmt.Errorf("the forge bridge's answer for %s is not a forge message: %w", e.Key, err)
} }
if f.Kind != forgemsg.ForgeMerged || f.Repository != e.Forge.Repository || f.Merge != e.Forge.Merge { if f.Kind != forgemsg.ForgeMerged || f.Repository != e.Forge.Repository || f.Merge != e.Forge.Merge || (e.Forge.Number > 0 && f.Number != e.Forge.Number) {
return e, fmt.Errorf("the forge bridge's answer is not the merge %s#%d at %s that %s refers to", e.Forge.Repository, e.Forge.Number, e.Forge.Merge, e.Key) return e, fmt.Errorf("the forge bridge's answer is not the merge %s#%d at %s that %s refers to", e.Forge.Repository, e.Forge.Number, e.Forge.Merge, e.Key)
} }
e.Forge, e.Light = f, false e.Forge, e.Light = f, false