diff --git a/internal/apply/hold.go b/internal/apply/hold.go index 941fc43..fc82f0f 100644 --- a/internal/apply/hold.go +++ b/internal/apply/hold.go @@ -22,13 +22,19 @@ import ( // first copy is the one that was there before the mesh. type Keep func(path string, content []byte, mode os.FileMode) (string, error) -// KeepIn keeps originals under dir/kept, each named for the path it came from, readable by root -// alone — a predecessor's configuration may carry its credentials. +// KeepIn keeps originals under dir/kept, each named for the path it came from AND for what was in +// it, readable by root alone — a predecessor's configuration may carry its credentials. +// +// **By content as well as path, because a path has more than one original.** A file held, let go +// when its module was unassigned, rewritten by the predecessor and found again is a second +// original; named by path alone the second copy was silently discarded while the report said it +// was kept (novox/hq ADR 0100). The same content at the same path is kept once. func KeepIn(dir string) Keep { return func(path string, content []byte, _ os.FileMode) (string, error) { - sum := sha256.Sum256([]byte(path)) - kept := filepath.Join(dir, "kept", - hex.EncodeToString(sum[:])[:16]+"-"+filepath.Base(path)) + where := sha256.Sum256([]byte(path)) + what := sha256.Sum256(content) + kept := filepath.Join(dir, "kept", hex.EncodeToString(where[:])[:12]+"-"+ + hex.EncodeToString(what[:])[:12]+"-"+filepath.Base(path)) if _, err := os.Lstat(kept); err == nil { return kept, nil } diff --git a/internal/apply/hold_test.go b/internal/apply/hold_test.go index 6b493f6..112f1a7 100644 --- a/internal/apply/hold_test.go +++ b/internal/apply/hold_test.go @@ -382,8 +382,13 @@ func TestAHoldNoLongerDeclaredIsLetGoAndFoundAgainIfItsModuleReturns(t *testing. t.Fatalf("a returning module's found file was not held again: %+v", o) } again, _ := state.HeldAt("hello-web.page") - if kept, _ := os.ReadFile(again.Kept); again.Kept != first.Kept || string(kept) != "the predecessor's page\n" { - t.Errorf("the first kept original was lost: %s %q", again.Kept, kept) + // The predecessor rewrote it while nothing held it, so that is a second original, kept beside + // the first rather than in place of it (novox/hq ADR 0100). + if kept, _ := os.ReadFile(again.Kept); string(kept) != "the predecessor wrote again\n" { + t.Errorf("what is named as kept is %q", kept) + } + if kept, _ := os.ReadFile(first.Kept); string(kept) != "the predecessor's page\n" { + t.Errorf("the first kept original was lost: %q", kept) } if got, _ := os.ReadFile(page); string(got) != "the predecessor wrote again\n" { t.Errorf("the found file was touched: %q", got) @@ -958,3 +963,60 @@ func TestAPackagedUnitTheMachineUsesIsStillHeld(t *testing.T) { t.Errorf("the predecessor's service was restarted: %v", m.asked) } } + +func TestASecondOriginalAtTheSamePathIsKeptToo(t *testing.T) { + // Held, let go when the module was unassigned, rewritten by the predecessor, found again: + // both originals are kept, and the report names the one it kept (novox/hq ADR 0100). + dir := t.TempDir() + page := filepath.Join(dir, "index.html") + keep := KeepIn(dir) + first, err := keep(page, []byte("the predecessor's page\n"), 0o640) + if err != nil { + t.Fatal(err) + } + same, err := keep(page, []byte("the predecessor's page\n"), 0o640) + if err != nil || same != first { + t.Errorf("the same original was kept twice: %s %s %v", first, same, err) + } + second, err := keep(page, []byte("the predecessor wrote again\n"), 0o640) + if err != nil { + t.Fatal(err) + } + if second == first { + t.Fatal("a second original was kept under the first's name") + } + if got, _ := os.ReadFile(first); string(got) != "the predecessor's page\n" { + t.Errorf("the first original is %q", got) + } + if got, _ := os.ReadFile(second); string(got) != "the predecessor wrote again\n" { + t.Errorf("the second original is %q", got) + } +} + +func TestAHoldLetGoAndFoundAgainKeepsBothOriginals(t *testing.T) { + dir, page, m := predecessor(t) + _, state := applyAdopted(t, adopted(t, untaken("hello-web.page", "hello-web.server"), webResources(page)), + store.State{}, m, dir) + first, _ := state.HeldAt("hello-web.page") + + // Unassigned, then the predecessor writes again, then assigned once more. + _, state = applyAdopted(t, adopted(t, `{"taken":[]}`, withConf(dir)), state, m, dir) + if err := os.WriteFile(page, []byte("the predecessor wrote again\n"), 0o640); err != nil { + t.Fatal(err) + } + report, state := applyAdopted(t, adopted(t, untaken("hello-web.page", "hello-web.server"), webResources(page)), + state, m, dir) + again, _ := state.HeldAt("hello-web.page") + if again.Kept == first.Kept { + t.Fatalf("the second original was kept under the first's name: %s", again.Kept) + } + if got, _ := os.ReadFile(again.Kept); string(got) != "the predecessor wrote again\n" { + t.Errorf("what the report names as kept is %q", got) + } + if got, _ := os.ReadFile(first.Kept); string(got) != "the predecessor's page\n" { + t.Errorf("the first original was lost: %q", got) + } + if o := outcomeOf(report, "hello-web.page"); o.Action != "held" { + t.Errorf("the file found again was not held: %+v", o) + } +}