Files
mesh-controller/cmd/mesh-controller/related.go
T
jschoubben 9460689fff
mesh/merge-gate pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without …
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered
Relate a batch whose moves are not known to every change
The review found that a merge kept without its moves, judged again at
the cut, gave its batch no modules, so it could read as unrelated to a
walk it will build beside. An unknown change is never unrelated.
2026-10-12 02:06:54 +02:00

112 lines
3.7 KiB
Go

package main
import (
"sort"
"strings"
"github.com/novox/mesh-controller/internal/inventory"
)
// What relates two changes (novox/hq ADR 0304 decision 1, as ADR 0306 decision 3 reads it): two changes are
// related when they share a repository, a module, a delivery group's head, or an edge of the module graph
// between their modules. A module is every one the walk builds or sends: the modules its merges moved and the
// modules that depend on them, not only the ones the merges touched. Unrelated changes may walk beside each
// other (ADR 0304 decision 2, ADR 0282 decision 3); nothing walks so yet, and nothing but tests reads this.
// footprint is what one change reaches: its repositories (lower case), its modules, and its delivery group
// heads (the head branch names of its merges). unknown says a merge of it was kept without the modules it
// moves, so what it reaches is not known: it is related to every change, never wrongly to none.
type footprint struct {
repos, modules, heads map[string]bool
unknown string
}
func newFootprint() footprint {
return footprint{repos: map[string]bool{}, modules: map[string]bool{}, heads: map[string]bool{}}
}
// footprintOfWalk is a walk's footprint: the repositories it carries and the modules it plans. A walk keeps no
// head of its merges; kept are the merges the record holds for it, whose announcements say their heads, and
// may be nil.
func footprintOfWalk(p inventory.Plan, kept []inventory.BatchedMerge) footprint {
f := newFootprint()
for _, c := range p.Carried() {
f.repos[strings.ToLower(c.Repository)] = true
}
for name := range p.Modules {
f.modules[name] = true
}
for _, m := range kept {
f.repos[strings.ToLower(m.Repository)] = true
if h := announcedOf(m).Head; h != "" {
f.heads[h] = true
}
}
return f
}
// footprintOfBatch is an assembling batch's footprint: each merge's repository and head, and the modules its
// merges moved when heard, widened to their dependents (planOfMoves). A merge kept with no moves (one owed a
// later look, or kept for its packaging) is judged again at the cut and may move anything then: its batch is
// unknown, related to everything.
func footprintOfBatch(merges []inventory.BatchedMerge, edges []inventory.Edge) footprint {
f := newFootprint()
var moved []string
for _, m := range merges {
k := announcedOf(m)
f.repos[strings.ToLower(m.Repository)] = true
if k.Head != "" {
f.heads[k.Head] = true
}
if len(k.Moves) == 0 && f.unknown == "" {
f.unknown = "the modules a merge of " + m.Repository + " moves are not known until its batch is cut"
}
moved = append(moved, k.Moves...)
}
for _, name := range reachableFrom(moved, edges) {
f.modules[name] = true
}
return f
}
// related says whether two changes are related, and the first reason in plain words; false and empty when
// they are not.
func related(a, b footprint, edges []inventory.Edge) (bool, string) {
if a.unknown != "" {
return true, a.unknown
}
if b.unknown != "" {
return true, b.unknown
}
if r := firstShared(a.repos, b.repos); r != "" {
return true, "both change " + r
}
if m := firstShared(a.modules, b.modules); m != "" {
return true, "both move " + m
}
if h := firstShared(a.heads, b.heads); h != "" {
return true, "both are members of group " + h
}
for _, e := range edges {
if a.modules[e.From] && b.modules[e.To] || b.modules[e.From] && a.modules[e.To] {
return true, e.From + " depends on " + e.To + " (" + e.Kind + ")"
}
}
return false, ""
}
// firstShared is the first key, in order, both sets hold; empty when none.
func firstShared(a, b map[string]bool) string {
var both []string
for k := range a {
if b[k] {
both = append(both, k)
}
}
if len(both) == 0 {
return ""
}
sort.Strings(both)
return both[0]
}