diff --git a/cmd/mesh-controller/batches.go b/cmd/mesh-controller/batches.go index 81a52afb..165bdb42 100644 --- a/cmd/mesh-controller/batches.go +++ b/cmd/mesh-controller/batches.go @@ -177,7 +177,7 @@ func (f following) hearMerge(ctx context.Context, m link.SourceMoved, now time.T repository, m.Base, m.Commit, already) return nil } - fmt.Printf("%s merged into %s (%.8s); nothing the mesh holds reads it\n", repository, m.Base, m.Commit) + fmt.Print(nothingReadsIt(repository, m)) return nil } // **Its delivery group's word, before the plans are held** (ADR 0306 decision 2): a slow answer holds up no walk. diff --git a/cmd/mesh-controller/non_trunk_merge_test.go b/cmd/mesh-controller/non_trunk_merge_test.go new file mode 100644 index 00000000..3e7c190d --- /dev/null +++ b/cmd/mesh-controller/non_trunk_merge_test.go @@ -0,0 +1,53 @@ +package main + +import ( + "strings" + "testing" + + "github.com/novox/mesh-controller/internal/catalogue" + "github.com/novox/mesh-controller/internal/inventory" +) + +// A merge into a branch other than the trunk starts no delivery (novox/hq ADR 0401 clause 3, issue 889): a +// module recorded with no ref follows the trunk, main, and no other branch. +func TestAMergeIntoANonTrunkBranchStartsNoDelivery(t *testing.T) { + for ref, name := range map[string]string{"": "noref", "main": "mainref"} { + t.Run(name, func(t *testing.T) { + open := windowed(t) + asksWithPaths(t) + if err := open.inventory.RegisterModule(t.Context(), catalogue.Manifest{Module: "free", Version: "1"}, + inventory.Source{Repository: "novox/free", Seat: "git", Ref: ref, BuiltFrom: "c0", Head: "c0"}); err != nil { + t.Fatal(err) + } + feature := lone(repoMerge("free", "f1", t0)) + feature.Base = "feat/57-config-and-provisioning" + said := captured(t, func() error { return (following{open: open}).hearMerge(t.Context(), feature, t0) }) + if ws, bs := walks(t, open); len(ws) != 0 || len(bs) != 0 { + t.Fatalf("a merge into a feature branch: %d walk(s), %d batch(es)", len(ws), len(bs)) + } + if !strings.Contains(said, feature.Base) || !strings.Contains(said, "starts no delivery") { + t.Fatalf("the merge was not said to start no delivery: %q", said) + } + hear(t, open, lone(repoMerge("free", "m1", t0)), t0) + if ws, bs := walks(t, open); len(ws)+len(bs) != 1 { + t.Fatalf("a merge into main: %d walk(s), %d batch(es)", len(ws), len(bs)) + } + }) + } +} + +// A module recorded with an explicit branch keeps following that branch (the lead's settlement of issue 977 Q5). +func TestAModuleRecordedWithABranchStillFollowsIt(t *testing.T) { + open := windowed(t) + asksWithPaths(t) + if err := open.inventory.RegisterModule(t.Context(), catalogue.Manifest{Module: "pinned", Version: "1"}, + inventory.Source{Repository: "novox/pinned", Seat: "git", Ref: "feat/x", BuiltFrom: "c0", Head: "c0"}); err != nil { + t.Fatal(err) + } + m := lone(repoMerge("pinned", "p1", t0)) + m.Base = "feat/x" + hear(t, open, m, t0) + if ws, bs := walks(t, open); len(ws)+len(bs) != 1 { + t.Fatalf("a merge into the branch a module follows: %d walk(s), %d batch(es)", len(ws), len(bs)) + } +} diff --git a/cmd/mesh-controller/upgrades.go b/cmd/mesh-controller/upgrades.go index 31626b80..ea9ec498 100644 --- a/cmd/mesh-controller/upgrades.go +++ b/cmd/mesh-controller/upgrades.go @@ -326,8 +326,7 @@ func movesOfMerge(ctx context.Context, inv *inventory.Inventory, m link.SourceMo "from it\n", m.Owner, m.Repo, m.Base, m.Commit, already) return nil, nil } - fmt.Printf("%s/%s merged into %s (%.8s); nothing the mesh holds reads it\n", - m.Owner, m.Repo, m.Base, m.Commit) + fmt.Print(nothingReadsIt(m.Owner+"/"+m.Repo, m)) return nil, nil } // Said, never silent (novox/hq 04-ISSUES/215): a module built from this repository that follows @@ -531,7 +530,7 @@ func whyMoved(e inventory.Entry, read []inventory.ReadRepository, m link.SourceM return "read whole: " + whole } for _, r := range read { - if r.Own || !sameRepository(r.Repository, m) || (r.Ref != "" && r.Ref != m.Base) { + if r.Own || !sameRepository(r.Repository, m) || !followsBase(r.Ref, m.Base) { continue } if len(r.Paths) > 0 { @@ -615,6 +614,17 @@ func retireDeleted(ctx context.Context, inv *inventory.Inventory, e inventory.En } } +// nothingReadsIt is what is said of a merge no module the mesh holds reads. **A merge into a branch other than the +// trunk is said to start no delivery** (novox/hq ADR 0401 clause 3, issue 889): every delivery is cut from a +// repository's trunk, and whoever merged into a feature branch should not look for a broken trigger. +func nothingReadsIt(repository string, m link.SourceMoved) string { + if m.Base != "" && m.Base != "main" { + return fmt.Sprintf("%s merged into %s (%.8s), not the trunk of %s; a merge into any other branch starts no "+ + "delivery\n", repository, m.Base, m.Commit, repository) + } + return fmt.Sprintf("%s merged into %s (%.8s); nothing the mesh holds reads it\n", repository, m.Base, m.Commit) +} + // sourceIs is whether a recorded source is the repository and branch a merge announced. A source on // the git seat is recorded as its path on the forge; one elsewhere as the URL it was cloned from. // An empty recorded ref is the repository's default branch, which is what a merge into the base @@ -623,8 +633,18 @@ func sourceIs(s inventory.Source, m link.SourceMoved) bool { if !sameRepository(s.Repository, m) { return false } - ref := followedBranch(s.Ref) - return ref == "" || ref == m.Base + return followsBase(s.Ref, m.Base) +} + +// followsBase is whether a recorded ref follows the branch a merge went into. **An empty ref is the +// trunk, main, and nothing else** (novox/hq ADR 0401 clause 3, issue 889): it used to match a merge +// into any branch, so a merge into a feature branch started a delivery. A ref recorded as a branch +// keeps following that branch. +func followsBase(ref, base string) bool { + if ref = strings.TrimSpace(followedBranch(ref)); ref == "" { + ref = "main" + } + return ref == base } // commitRef is a ref that names a commit rather than a branch: what `build --ref ` asks for. @@ -663,7 +683,7 @@ func sameRepository(repository string, m link.SourceMoved) bool { // read, as before. func readsFrom(read []inventory.ReadRepository, m link.SourceMoved) bool { for _, r := range read { - if r.Own || !sameRepository(r.Repository, m) || (r.Ref != "" && r.Ref != m.Base) { + if r.Own || !sameRepository(r.Repository, m) || !followsBase(r.Ref, m.Base) { continue } if len(r.Paths) == 0 || len(m.Paths) == 0 || m.PathsTruncated { @@ -757,7 +777,7 @@ func unchangedOfMerge(m link.SourceMoved, entries []inventory.Entry, // branch, as a context beside its own. func packagesSaid(read []inventory.ReadRepository, m link.SourceMoved) bool { for _, r := range read { - if !r.Own && sameRepository(r.Repository, m) && (r.Ref == "" || r.Ref == m.Base) && len(r.Paths) > 0 { + if !r.Own && sameRepository(r.Repository, m) && followsBase(r.Ref, m.Base) && len(r.Paths) > 0 { return true } }