diff --git a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/holder.go b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/holder.go index 957d4be8..c8c3d02a 100644 --- a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/holder.go +++ b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/holder.go @@ -345,8 +345,8 @@ func (h *Holder) pullUpdated(p PullEvent) (isNew bool) { // Announced again: nothing new — but for a delivery whose verdict is out of date, the announcement is its // check asked again against the trunk's head (the merge tool's ask, novox/hq ADR 0323 §5), and its bound // runs from now (§7). The base head it was announced with is kept, for a check this owner asks after it. - if p.BaseSHA != "" && d.BaseSHA != p.BaseSHA { - d.BaseSHA = p.BaseSHA + if p.BaseSHA != "" { + d.BaseSHA, d.BaseSHAAt = p.BaseSHA, now h.keep(d) } // A delivery made from its verdict before its head was heard (issue 1726) holds none of what an announcement @@ -441,7 +441,7 @@ func (h *Holder) fillFromAnnouncement(d *Delivery, p PullEvent) { d.ReviewedBy, d.ReviewedAt = ReviewIn(p.Body) d.PushedBy = PushedIn(p.Body) if p.BaseSHA != "" { - d.BaseSHA = p.BaseSHA + d.BaseSHA, d.BaseSHAAt = p.BaseSHA, h.Now() } d.Number = p.Number h.Logf("[mesh-delivery] %s: its pull request's announcement filled its base (%s), branch and title", d.ID, p.Base) @@ -560,6 +560,9 @@ func (h *Holder) freshAnnouncement(p PullEvent, now time.Time, startedOver strin Title: p.Title, Base: p.Base, Branch: p.Head, HTMLURL: p.HTMLURL, CloneURL: p.CloneURL, Paths: p.Paths, PathsTruncated: p.PathsTruncated, Removed: p.Removed, ModuleDirs: p.ModuleDirs, ModuleDirsSaid: p.ModuleDirsSaid, After: AfterIn(p.Body), Issues: IssuesIn(p.Title + "\n" + p.Body), Created: now, BaseSHA: p.BaseSHA} + if p.BaseSHA != "" { + d.BaseSHAAt = now + } d.ReviewedBy, d.ReviewedAt = ReviewIn(p.Body) d.PushedBy = PushedIn(p.Body) why := fmt.Sprintf("%s#%d's head announced", d.Repository, d.Number) diff --git a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/issue1726_trunk_test.go b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/issue1726_trunk_test.go index 532c1e63..0f3bea30 100644 --- a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/issue1726_trunk_test.go +++ b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/issue1726_trunk_test.go @@ -55,12 +55,12 @@ func TestStartingWritesTheNewestMergeOfEachRepositoryNotYetInSourceMain(t *testi const never, trunkN, trunkM = "novox/never-merged", "eeee000000000000000000000000000000000001", "eeee000000000000000000000000000000000002" s := newWorld(t) s.source.main = &sdk.Source{V: sdk.Version, Group: "before", Heads: map[string]string{i1726Repo: i1726Merge1}} - other := pr("novox/other-base", 6, "ffff000000000000000000000000000000000001", "fix/c", "a.go") + other := pr("novox/other-base", 6, "f1f1000000000000000000000000000000000001", "fix/c", "a.go") other.Base, other.BaseSHA = "nox-mesh", trunkM s.h.PullUpdated(other) - unread := pr("novox/unread", 2, "ffff000000000000000000000000000000000002", "fix/d", "a.go") + unread := pr("novox/unread", 2, "f2f2000000000000000000000000000000000002", "fix/d", "a.go") s.h.PullUpdated(unread) - seed := pr(never, 4, "ffff000000000000000000000000000000000003", "fix/e", "a.go") + seed := pr(never, 4, "f3f3000000000000000000000000000000000003", "fix/e", "a.go") seed.BaseSHA = trunkN s.h.PullUpdated(seed) s.settleAll() @@ -70,7 +70,7 @@ func TestStartingWritesTheNewestMergeOfEachRepositoryNotYetInSourceMain(t *testi if !strings.Contains(s.logs(), "seeded with "+trunkN[:12]) { t.Errorf("the seed was not said: %s", s.logs()) } - again := pr(never, 5, "ffff000000000000000000000000000000000004", "fix/f", "a.go") + again := pr(never, 5, "f4f4000000000000000000000000000000000004", "fix/f", "a.go") again.BaseSHA = trunkM s.h.PullUpdated(again) s.h.SourceCatchUp() @@ -79,6 +79,25 @@ func TestStartingWritesTheNewestMergeOfEachRepositoryNotYetInSourceMain(t *testi t.Fatalf("a seeded head was replaced by %s (%d moves)", got, len(s.source.moves)) } + // The freshest announcement's base head is the seed, not the newest delivery's: #4, made first, is announced again after #5 + // with main's head moved on. + const trunkF = "eeee000000000000000000000000000000000003" + f := newWorld(t) + first := pr(never, 4, "f5f5000000000000000000000000000000000005", "fix/g", "a.go") + first.BaseSHA = trunkN + f.h.PullUpdated(first) + f.later(time.Minute) + second := pr(never, 5, "f6f6000000000000000000000000000000000006", "fix/h", "a.go") + second.BaseSHA = trunkM + f.h.PullUpdated(second) + f.later(time.Minute) + first.BaseSHA = trunkF + f.h.PullUpdated(first) + f.settleAll() + if got := f.source.main.Heads[never]; got != trunkF { + t.Fatalf("seeded with %s; wanted %s, the base head of the latest announcement", got, trunkF) + } + o := newWorld(t) o.source.main = &sdk.Source{V: sdk.Version, Group: "newer", Heads: map[string]string{i1726Repo: i1726Merge2}} o.h.PullUpdated(pr(i1726Repo, 471, head2, "fix/b", "modules/app/b.go")) diff --git a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/merges.go b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/merges.go index f6fec216..ec1b6cec 100644 --- a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/merges.go +++ b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/merges.go @@ -529,8 +529,8 @@ func (h *Holder) settleForgeMerges() []func() { var trunkHead = regexp.MustCompile(`^[0-9a-f]{40}$`) // seedTrunks gives source.main the trunk head of a repository it names none of (issue 1422): one never merged through the -// gate, whose open pull requests the checker could otherwise never judge. The head is the base_sha the forge announced the -// newest open pull request into main with (gitea.pull.updated; ADR 0323), the trunk's head when it was announced. It is +// gate, whose open pull requests the checker could otherwise never judge. The head is the base_sha of the latest announcement of +// any open pull request into main (gitea.pull.updated; ADR 0323; lastAnnounced), the trunk's freshest head the gate heard. It is // added in one compare-and-set write that never replaces a head source.main holds, so a merge always wins; the first merge // through the gate moves it as any other. source.main is per repository and names main's head only (ADR 0484 §11, MergeBranch): // a pull request into another base seeds nothing. A repository is weighed once per start after it was found or seeded. @@ -541,7 +541,7 @@ func (h *Holder) seedTrunks() []func() { if h.seeded[d.Repository] || d.Base != MergeBranch || !trunkHead.MatchString(d.BaseSHA) || d.State.Final() || d.MergedAs != "" { continue } - if o := newest[d.Repository]; o == nil || d.Created.After(o.Created) { + if o := newest[d.Repository]; o == nil || lastAnnounced(d).After(lastAnnounced(o)) { newest[d.Repository] = d } } @@ -585,6 +585,15 @@ func (h *Holder) seedTrunks() []func() { return out } +// lastAnnounced is when the forge last announced a delivery's head with its base head: BaseSHAAt, or when the gate made it +// for one kept before that was written. +func lastAnnounced(d *Delivery) time.Time { + if !d.BaseSHAAt.IsZero() { + return d.BaseSHAAt + } + return d.Created +} + // replayLate is how long after its merge a merge heard only from the forge's word is a replay: the forge announces a merge // within seconds, so one heard this late was replayed from the bus's history, and the source already holds what came after. const replayLate = 15 * time.Minute diff --git a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/model.go b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/model.go index e46c968f..05e86510 100644 --- a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/model.go +++ b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/model.go @@ -66,6 +66,9 @@ type Delivery struct { // BaseSHA is the base branch's head the forge last announced its head with (ADR 0323): sent with a check this // owner asks, for a verdict no build judges to name its trunk commit. BaseSHA string `json:"base_sha,omitempty"` + // BaseSHAAt is when the forge last announced its head with BaseSHA: the freshest of a repository's announcements gives + // source.main its trunk head when it names none (seedTrunks, issue 1422). Zero for one kept before it was written. + BaseSHAAt time.Time `json:"base_sha_at,omitzero"` // AskedAgain is when its head was announced again while its verdict was out of date — the merge tool's ask of // its check against the trunk's head (ADR 0323 §5) — and no verdict has come since: its one-hour bound runs from // then. Nil while nothing is asked, and cleared by a verdict or another move of its trunk.