A merge of a base rebuilds what stands on it #193
@@ -0,0 +1,52 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/novox/mesh-controller/internal/catalogue"
|
||||
"github.com/novox/mesh-controller/internal/inventory"
|
||||
)
|
||||
|
||||
// A merge that rebuilds a base rebuilds what stands on it, through every layer, and nothing else
|
||||
// (novox/hq issue 186): the runtime image moving means every module built on it moves too, and a
|
||||
// module built on one of those moves as well.
|
||||
func TestAMergeOfABaseTakesWhatStandsOnItAlong(t *testing.T) {
|
||||
entry := func(name string) inventory.Entry {
|
||||
return inventory.Entry{Manifest: catalogue.Manifest{Module: name}}
|
||||
}
|
||||
entries := []inventory.Entry{entry("mesh-tools"), entry("shop"), entry("shop-plugin"), entry("postgres"), entry("unrelated")}
|
||||
against := map[string][]string{
|
||||
"shop": {catalogue.ArtifactStoreScheme + "mesh-tools/runtime@sha256:a"},
|
||||
"shop-plugin": {catalogue.ArtifactStoreScheme + "shop/runtime@sha256:b"},
|
||||
"postgres": {catalogue.ArtifactStoreScheme + "mesh-tools/runtime@sha256:a"},
|
||||
"unrelated": {catalogue.ArtifactStoreScheme + "alpine/base@sha256:c"},
|
||||
}
|
||||
got := dependentsOf([]inventory.Entry{entry("mesh-tools")}, entries, against)
|
||||
var names []string
|
||||
for _, e := range got {
|
||||
names = append(names, e.Manifest.Module)
|
||||
}
|
||||
want := map[string]bool{"shop": true, "shop-plugin": true, "postgres": true}
|
||||
if len(names) != len(want) {
|
||||
t.Fatalf("rebuilt %v; wanted exactly the three that stand on the runtime, directly or through shop", names)
|
||||
}
|
||||
for _, n := range names {
|
||||
if !want[n] {
|
||||
t.Fatalf("%s was rebuilt and stands on nothing that moved (%v)", n, names)
|
||||
}
|
||||
}
|
||||
// The dependents come in base order when the merge orders them: the runtime, then shop, then
|
||||
// the plugin that stands on shop.
|
||||
ordered := orderByBases(append([]inventory.Entry{entry("mesh-tools")}, got...), against)
|
||||
pos := map[string]int{}
|
||||
for i, e := range ordered {
|
||||
pos[e.Manifest.Module] = i
|
||||
}
|
||||
if !(pos["mesh-tools"] < pos["shop"] && pos["shop"] < pos["shop-plugin"]) {
|
||||
t.Fatalf("not in base order: %v", ordered)
|
||||
}
|
||||
// Nothing moved: nothing follows.
|
||||
if more := dependentsOf(nil, entries, against); len(more) != 0 {
|
||||
t.Fatalf("with nothing moved, %d module(s) were rebuilt", len(more))
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user