diff --git a/internal/inventory/chain_builds.go b/internal/inventory/chain_builds.go index 459ec109..c6db6b75 100644 --- a/internal/inventory/chain_builds.go +++ b/internal/inventory/chain_builds.go @@ -1206,6 +1206,28 @@ func (i *Inventory) layoutBuilds(ctx context.Context, c *chain.Chain, say func(s if r.Records%1000 == 0 { say("laying the builds out: %d of %d records, %d keys written so far", r.Records, len(ids), r.Written) } + // A record already under its new key is moved: a run again (the start before this one laid it out) neither reads its + // manifest nor writes it, and **no pointer is set back**: they are only made where missing. + own := chain.BuildKey(rec.Module, rec.ID) + if rec.State == chain.BuildAsked { + own = chain.RequestKey(rec.ID) + } + if _, there, err := c.Builds.GetRaw(ctx, own); err != nil { + return r, err + } else if there { + r.Present++ + if rec.State != chain.BuildAsked { + if wrote, err := i.pointTo(ctx, c, chain.IDKey(rec.ID), idPointer(rec.Module), false); err != nil { + return r, err + } else if wrote { + r.Written++ + } + } + if rec.State != chain.BuildAsked && rec.Module != "" { + byModule[rec.Module] = append(byModule[rec.Module], rec) + } + continue + } // A manifest already written in this run is not written again. digest := "" if body := manifestBytes([]byte(rec.Manifest)); body != nil && c.Kept { @@ -1265,7 +1287,7 @@ func (i *Inventory) layoutBuilds(ctx context.Context, c *chain.Chain, say func(s return r, err } for _, p := range points { - wrote, err := i.pointTo(ctx, c, p.key, p.value, true) + wrote, err := i.pointTo(ctx, c, p.key, p.value, false) if err != nil { return r, err } diff --git a/internal/inventory/chain_builds_layout_test.go b/internal/inventory/chain_builds_layout_test.go index b1442c0e..26e30f3a 100644 --- a/internal/inventory/chain_builds_layout_test.go +++ b/internal/inventory/chain_builds_layout_test.go @@ -426,3 +426,36 @@ func TestTheChainIsRefusedUntilTheBuildsAreLaidOutAndTheLayoutIsDoneOnce(t *test t.Fatalf("the second layout changed the bucket: %v", err) } } + +// A layout run again — the live start recorded the first in chain_moved, not in the marker — never sets a pointer back to an +// older build, and does not read a manifest of a record already under its new key. +func TestALayoutRunAgainLeavesThePointersOfTheBuildsMadeSince(t *testing.T) { + counting := &readCounting{Backend: chain.NewMemory()} + inv, c := layoutChain(counting) + ctx := context.Background() + for n := 1; n <= 2; n++ { + old := legacyBuild(n, "alpha", fmt.Sprintf("c%d", n)) + if _, err := c.Builds.Create(ctx, old.ID, old); err != nil { + t.Fatal(err) + } + } + if _, err := inv.layoutBuilds(ctx, c, nil); err != nil { + t.Fatal(err) + } + if err := inv.chainRecordBuild(ctx, c, layoutBuild("alpha", 3, "c3", "s3")); err != nil { + t.Fatal(err) + } + counting.gets.Store(0) + r, err := inv.layoutBuilds(ctx, c, nil) + if err != nil || r.Written != 0 { + t.Fatalf("the second layout %+v: %v", r, err) + } + for _, key := range []string{"alpha.newest", "alpha.worked", "alpha.held"} { + if e, _, _ := counting.Get(ctx, key); string(e.Value) != "build-3" { + t.Errorf("%s = %q after the second layout; want build-3", key, e.Value) + } + } + if e, _, _ := counting.Get(ctx, "alpha.commit.c3"); pointedID(e.Value) != "build-3" { + t.Errorf("commit c3 = %s", e.Value) + } +}