A merge of a base rebuilds what stands on it

The controller knew which modules were built against which base artifacts and used it only when asked
(build --on). A merge that rebuilt the runtime image left forty-two modules on the old image until
somebody asked, twice, by hand (hq issue 186). The merge now takes every module standing on what moved,
through every layer, into the same rebuild, in base order — the same rebuild the flag does, asked by
the merge that made it necessary.
This commit is contained in:
2026-10-01 16:09:26 +02:00
parent 84ac840ff4
commit 853c63b181
2 changed files with 100 additions and 0 deletions
+48
View File
@@ -299,6 +299,20 @@ func (f following) SourceMoved(ctx context.Context, m link.SourceMoved) error {
if err != nil {
return notNow(err)
}
// And whatever stands on what moved. A base rebuilt without its dependents is a mesh half on
// the old image until somebody remembers to ask — on 2026-10-01 forty-two modules, twice, by
// hand (novox/hq issue 186). The relation is the one `build --on` reads; this is the same
// rebuild, asked by the merge that made it necessary, in base order.
standing := dependentsOf(moved, entries, against)
if len(standing) > 0 {
var on []string
for _, e := range standing {
on = append(on, e.Manifest.Module)
}
fmt.Printf(" %d module(s) stand on what moved and are rebuilt with it: %s\n",
len(standing), strings.Join(on, ", "))
moved = append(moved, standing...)
}
ordered := orderByBases(moved, against)
names := make([]string, 0, len(ordered))
for _, e := range ordered {
@@ -522,3 +536,37 @@ func isHistory(mergedAt string, seen time.Time) bool {
}
return at.Before(seen)
}
// dependentsOf is every catalogued module that stands on one of the moved modules, directly or
// through another dependent, and is not itself among them — in the catalogue's order, so the
// answer is the same each time. A module standing on nothing that moved is left alone: a merge
// rebuilds what it changed and what is built on top of that, not the catalogue.
func dependentsOf(moved, entries []inventory.Entry, against map[string][]string) []inventory.Entry {
bases := map[string]bool{}
for _, e := range moved {
bases[e.Manifest.Module] = true
}
var out []inventory.Entry
taken := map[string]bool{}
for grew := true; grew; {
grew = false
for _, e := range entries {
name := e.Manifest.Module
if bases[name] || taken[name] {
continue
}
for base := range bases {
if standsOnModule(e, base, against) {
taken[name] = true
out = append(out, e)
grew = true
break
}
}
}
for _, e := range out {
bases[e.Manifest.Module] = true
}
}
return out
}