diff --git a/modules/mesh-delivery-checker/internal/check/adr548_test.go b/modules/mesh-delivery-checker/internal/check/adr548_test.go index 99e2d95..7bd400c 100644 --- a/modules/mesh-delivery-checker/internal/check/adr548_test.go +++ b/modules/mesh-delivery-checker/internal/check/adr548_test.go @@ -211,6 +211,38 @@ func TestOneCheckPerDelivery(t *testing.T) { t.Errorf("the status after the carry is %q, want success", s) } }, + // §7: a carry whose merge conflicts fails naming the files and asks no check; a later move onto which the head merges + // cleanly asks a whole check, not a carry. + "a carry git refuses fails naming the files, and the next move asks a whole check": func(t *testing.T) { + var kinds []string + var from []map[string]string + j := &judgeFunc{f: func(_ context.Context, c *Case, call int) (Outcome, error) { + kinds, from = append(kinds, c.Kind), append(from, c.CarriedFrom) + if call == 2 { + return Outcome{Verdict: delivery.Verdict{State: delivery.VerdictFail, Failed: delivery.FailedFirst{Machine: "ace", + Words: repoA + "#7 " + ConflictWords + "go.mod"}}}, nil + } + return Outcome{Verdict: passing()}, nil + }} + r := newRig(t, j) + cand := candidateOf(t, "feat/x", pullOf(repoA, 7, head1)) + r.ch.Candidate(ctx, "feat%2Fx", cand) + r.ch.Wait() + r.setTrunks(t, map[string]string{repoA: trunkA2, repoSDK: trunkS}) + r.ch.TrunkMoved(ctx) + r.ch.Wait() + r.ch.Reconsider(ctx) // anything else that looks again asks nothing of a conflict + r.ch.Wait() + if v := r.verdict(t, keyOf(t, "feat/x", cand)); j.count() != 2 || !IsConflictFailure(v) || !strings.Contains(v.Failed.Words, "go.mod") { + t.Fatalf("judged %d times, verdict %+v; want the carry failed naming go.mod and nothing asked after it", j.count(), v.Failed) + } + r.setTrunks(t, map[string]string{repoA: trunkA, repoSDK: trunkS}) + r.ch.TrunkMoved(ctx) + r.ch.Wait() + if j.count() != 3 || kinds[1] != KindTrunk || from[1] == nil || kinds[2] != KindNew || from[2] != nil { + t.Errorf("checks %v carried from %v; want a whole check, a carry, then a whole check after the conflict", kinds, from) + } + }, // §5: a carry that fails names the move it failed on. "a carry that fails names the move": func(t *testing.T) { j := &judgeFunc{f: func(_ context.Context, c *Case, call int) (Outcome, error) { diff --git a/modules/mesh-delivery-checker/internal/check/checker.go b/modules/mesh-delivery-checker/internal/check/checker.go index 8b838b7..4e0f58d 100644 --- a/modules/mesh-delivery-checker/internal/check/checker.go +++ b/modules/mesh-delivery-checker/internal/check/checker.go @@ -247,6 +247,8 @@ func (ch *Checker) prepare(ctx context.Context, cs *Case) { case IsNodesFailure(v): cs.Kind = KindNodes return + case IsConflictFailure(v): + return // a head git refused: its next check is whole (ADR 0548 §7) } cs.Kind = KindTrunk if v.State == delivery.VerdictPass && ch.stale(v, cs) { @@ -440,6 +442,15 @@ func (ch *Checker) Held() map[string]string { return out } +// ConflictWords are what the words of a head that git would not merge onto its trunk say before the files in conflict. +const ConflictWords = "does not merge into the trunk: files in conflict: " + +// IsConflictFailure says whether a verdict failed because its head did not merge onto its trunk (ADR 0548 §7): it has no +// pass to carry, so its next check, at a push or at a later trunk move, is a whole check of its head. +func IsConflictFailure(v delivery.Verdict) bool { + return v.State == delivery.VerdictFail && strings.Contains(v.Failed.Words, ConflictWords) +} + // IsNodesFailure says whether a verdict failed because a part of the mesh the check reads was not heard (UnheardWords): // it is judged again when the nodes change. No check fails for want of a build node any more: its asks wait in the build // seats' streams (ADR 0525 §6, issue 1912). diff --git a/modules/mesh-delivery-checker/internal/check/judge.go b/modules/mesh-delivery-checker/internal/check/judge.go index 38355b0..b136728 100644 --- a/modules/mesh-delivery-checker/internal/check/judge.go +++ b/modules/mesh-delivery-checker/internal/check/judge.go @@ -431,7 +431,7 @@ func (st *state) ask(ctx context.Context, t *target, merged *[]Fetched) *deliver if st.c.Try == nil { st.j.writePreview(ctx, p, append(*merged, t.fetched)) } - return fail(step.Node, fmt.Sprintf("%s#%d does not merge into the trunk: files in conflict: %s", p.Repository, p.Number, strings.Join(t.fetched.Conflicts, ", "))) + return fail(step.Node, fmt.Sprintf("%s#%d %s%s", p.Repository, p.Number, ConflictWords, strings.Join(t.fetched.Conflicts, ", "))) } t.changed = t.fetched.Changed if !changesBuildGo(p.Repository, t.changed) {