From e17534383716974e201d1f833a531e86a07ec278 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 21 Oct 2026 21:12:48 +0200 Subject: [PATCH] Write the merge preview of each head the checker fetched, for the indexer's fingerprints (issue 1680) --- .../internal/check/examiner_test.go | 50 +++++++++++++ .../internal/check/judge.go | 3 + .../internal/check/preview.go | 70 +++++++++++++++++++ 3 files changed, 123 insertions(+) create mode 100644 modules/mesh-delivery-checker/internal/check/preview.go diff --git a/modules/mesh-delivery-checker/internal/check/examiner_test.go b/modules/mesh-delivery-checker/internal/check/examiner_test.go index 446ee9c..c7aa9a1 100644 --- a/modules/mesh-delivery-checker/internal/check/examiner_test.go +++ b/modules/mesh-delivery-checker/internal/check/examiner_test.go @@ -454,3 +454,53 @@ func TestAGroupIsOneVerdictAndEachMembersTestsReadTheOthersAtTheirHeads(t *testi func TestAnExaminerIsAJudgeOfTheChecker(t *testing.T) { var _ Judge = (*Examiner)(nil) } + +const ( + manA = "0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a" +) + +func TestACleanMergeWritesItsPreviewWithTheTreesManifestsAndLocks(t *testing.T) { // 1445/AC1 + b := newBench(t) + b.fetch.Trees = map[string]MergeTree{ + "modules/avahi": {Tree: "7777777777777777777777777777777777777777", Manifest: manA, Locks: map[string]string{"go.sum": "ff00"}}, + "modules/bare": {Tree: "8888888888888888888888888888888888888888"}, // no manifest digest: left out + } + b.judge(t, caseOf(t, pullOf(repoA, 7, head1))) + key, _ := delivery.PreviewKey(repoA, head1, trunkA) + raw, _, found, _ := b.prev.Get(context.Background(), key) + if !found { + t.Fatalf("no preview %s; the bucket holds %v", key, b.prev.vals) + } + pv, err := delivery.DecodePreview(raw) + if err != nil { + t.Fatal(err) + } + m := pv.Modules["modules/avahi"] + if pv.Merge != b.fetch.Merge || pv.Trunk != trunkA || pv.Head != head1 || len(pv.Modules) != 1 || m.Tree != "7777777777777777777777777777777777777777" || m.Manifest != manA || m.Locks["go.sum"] != "ff00" { + t.Errorf("preview = %+v", pv) + } +} + +func TestAHeadThatDoesNotMergeWritesNoPreview(t *testing.T) { // 1445/AC5 + b := newBench(t) + b.fetch = Fetched{Workspace: "ws1", Commit: head1, Base: trunkA, Conflicts: []string{"go.mod"}} + b.judge(t, caseOf(t, pullOf(repoA, 7, head1))) + if len(b.prev.vals) != 0 { + t.Errorf("previews %v written for a head that does not merge", b.prev.vals) + } +} + +func TestAPreviewIsWrittenOnceAndAnExistingKeyStands(t *testing.T) { + b := newBench(t) + b.judge(t, caseOf(t, pullOf(repoA, 7, head1))) + b.fetch.Merge = "5656565656565656565656565656565656565656" + b.judge(t, caseOf(t, pullOf(repoA, 7, head1))) + key, _ := delivery.PreviewKey(repoA, head1, trunkA) + if b.prev.creates[key] != 1 { + t.Errorf("the preview was created %d times, want once", b.prev.creates[key]) + } + raw, _, _, _ := b.prev.Get(context.Background(), key) + if pv, _ := delivery.DecodePreview(raw); pv.Merge == b.fetch.Merge { + t.Error("the second merge commit replaced the first") + } +} diff --git a/modules/mesh-delivery-checker/internal/check/judge.go b/modules/mesh-delivery-checker/internal/check/judge.go index a6ac48c..6f90d74 100644 --- a/modules/mesh-delivery-checker/internal/check/judge.go +++ b/modules/mesh-delivery-checker/internal/check/judge.go @@ -202,6 +202,7 @@ func (st *state) fetchAll(ctx context.Context) *delivery.Verdict { began := st.j.now() defer func() { st.clone = st.j.now().Sub(began) }() for _, p := range st.c.Pulls { + var merged []Fetched for _, bm := range st.j.Modules.BuildModules(p.Repository) { node, tc, err := st.choose(bm) if err != nil { @@ -226,8 +227,10 @@ func (st *state) fetchAll(ctx context.Context) *delivery.Verdict { return fail(node, fmt.Sprintf("%s#%d does not merge into the trunk: files in conflict: %s", p.Repository, p.Number, strings.Join(t.fetched.Conflicts, ", "))) } t.changed = t.fetched.Changed + merged = append(merged, t.fetched) st.targets = append(st.targets, t) } + st.j.writePreview(ctx, p, merged) } return nil } diff --git a/modules/mesh-delivery-checker/internal/check/preview.go b/modules/mesh-delivery-checker/internal/check/preview.go new file mode 100644 index 0000000..6884a1e --- /dev/null +++ b/modules/mesh-delivery-checker/internal/check/preview.go @@ -0,0 +1,70 @@ +package check + +import ( + "context" + "errors" + "fmt" + "regexp" + + "git.novox.be/novox/mesh-sdk/go/delivery" +) + +var digest64 = regexp.MustCompile(`^[0-9a-f]{64}$`) + +// PreviewOf is what the fetch steps said of a pull request's head merged onto its trunk head, as the value of a previews +// key (ADR 0484 clause 3): the merge commit and, for each module directory the fetches named with a manifest, its tree, the +// digest of its manifest and the hashes of its lock files. The fetches of the repository's build modules merge the same +// trees; the first one's merge commit is kept. A module directory with no tree or no manifest digest is left out, so the +// indexer gives its module no fingerprint and says why. It answers false when no fetch merged. +func PreviewOf(p Pull, fetched []Fetched) (delivery.Preview, bool) { + var merge string + mods := map[string]delivery.PreviewModule{} + for _, f := range fetched { + if f.Merge == "" { + continue + } + if merge == "" { + merge = f.Merge + } + for dir, t := range f.Trees { + if t.Tree == "" || !digest64.MatchString(t.Manifest) { + continue + } + mods[dir] = delivery.PreviewModule{Tree: t.Tree, Manifest: t.Manifest, Locks: t.Locks} + } + } + if merge == "" { + return delivery.Preview{}, false + } + pv := delivery.Preview{Repository: p.Repository, Head: p.Head, Trunk: p.Trunk, Merge: merge} + if len(mods) > 0 { + pv.Modules = mods + } + return pv, true +} + +// writePreview writes the preview of a pull request by a create: one writer per key, and the same head on the same trunk +// head merges to the same trees, so a key that exists stands. A head that did not merge has no preview. A failure is said +// and does not fail the check: the preview is for the indexer's fingerprints, not for the verdict. +func (j *Examiner) writePreview(ctx context.Context, p Pull, fetched []Fetched) { + if j.Previews == nil { + return + } + pv, ok := PreviewOf(p, fetched) + if !ok { + return + } + key, err := delivery.PreviewKey(p.Repository, p.Head, p.Trunk) + if err == nil { + var data []byte + if data, err = delivery.EncodePreview(pv); err == nil { + _, err = j.Previews.Create(ctx, key, data) + if errors.Is(err, ErrConflict) { + return + } + } + } + if err != nil { + j.logf("%s#%d: the preview cannot be written: %v", p.Repository, p.Number, fmt.Errorf("%w", err)) + } +}