Build for the merge from a copy its tests never touch, and keep or take only the same head's build
mesh/merge-gate head 060f2a9ca5fd merged into trunk 04730b54a8f2 — pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every …
mesh/repo-check head 060f2a9ca5fd merged into trunk 04730b54a8f2 — pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate head 060f2a9ca5fd merged into trunk 04730b54a8f2 — pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every …
mesh/repo-check head 060f2a9ca5fd merged into trunk 04730b54a8f2 — pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request
The gate and the repository's check mount the workspace read-write, so a build from that tree could hold what the tests wrote under a key that describes the merge. A check now clones the merge outside the workspace before anything of the change runs, and builds from that. A check keeps, and a walk takes, only a build an earlier check of the same pull request at the same head made: another pull request's build, or an earlier head's, is never registered. The walk reads the verdicts once (novox/hq issue 703, review of #328).
This commit is contained in:
@@ -688,7 +688,7 @@ func spelledOf(m inventory.BatchedMerge) string {
|
|||||||
func namedOf(m inventory.BatchedMerge, repository string) inventory.PlanMerge {
|
func namedOf(m inventory.BatchedMerge, repository string) inventory.PlanMerge {
|
||||||
k := announcedOf(m)
|
k := announcedOf(m)
|
||||||
return inventory.PlanMerge{Repository: repository, Commit: m.Commit, Number: k.Number, Title: k.Title, Moves: k.Moves,
|
return inventory.PlanMerge{Repository: repository, Commit: m.Commit, Number: k.Number, Title: k.Title, Moves: k.Moves,
|
||||||
Merged: m.Merged, Heard: m.Heard}
|
Head: k.HeadSHA, Merged: m.Merged, Heard: m.Heard}
|
||||||
}
|
}
|
||||||
|
|
||||||
// laterMerge says a was merged after b: by the forge's merge time, then by when each was heard.
|
// laterMerge says a was merged after b: by the forge's merge time, then by when each was heard.
|
||||||
|
|||||||
@@ -190,52 +190,75 @@ func TestAReusedCheckBuildIsRegisteredByItsDigestAndGatedAsABuiltOne(t *testing.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A walk's leave names the builds its own pull requests' verdicts made, and none when they cannot be read: the
|
// A walk's leave names the builds its own pull requests' verdicts made at the head each merge merged — never an
|
||||||
// builder takes nothing a verdict does not name (novox/hq ADR 0350 §2).
|
// earlier head's — read once per walk, and none when they cannot be read: the builder takes nothing a verdict does
|
||||||
|
// not name (novox/hq ADR 0350 §2, issue 703).
|
||||||
func TestAWalksLeaveNamesTheBuildsItsPullRequestsVerdictsMade(t *testing.T) {
|
func TestAWalksLeaveNamesTheBuildsItsPullRequestsVerdictsMade(t *testing.T) {
|
||||||
was := checkedBuildsOf
|
was := checkedBuildsOf
|
||||||
t.Cleanup(func() { checkedBuildsOf = was })
|
forgetWalkVerdicts()
|
||||||
var asked []link.PullRef
|
t.Cleanup(func() { checkedBuildsOf = was; forgetWalkVerdicts() })
|
||||||
named := []link.CheckedBuild{{Pull: link.PullRef{Owner: "novox", Repo: "shop", Number: 7}, Check: "build-1",
|
var asked [][]link.PullRef
|
||||||
Built: link.CheckBuilt{Module: "app", Key: "r1:1"}}}
|
pull := link.PullRef{Owner: "novox", Repo: "shop", Number: 7}
|
||||||
|
atHead := link.CheckedBuild{Pull: pull, Check: "build-2", Head: "bbbbbbbbbbbb", Built: link.CheckBuilt{Module: "app", Key: "r1:2"}}
|
||||||
|
earlier := link.CheckedBuild{Pull: pull, Check: "build-1", Head: "aaaaaaaaaaaa", Built: link.CheckBuilt{Module: "app", Key: "r1:1"}}
|
||||||
checkedBuildsOf = func(_ context.Context, pulls []link.PullRef) ([]link.CheckedBuild, error) {
|
checkedBuildsOf = func(_ context.Context, pulls []link.PullRef) ([]link.CheckedBuild, error) {
|
||||||
asked = pulls
|
asked = append(asked, pulls)
|
||||||
return named, nil
|
return []link.CheckedBuild{atHead, earlier}, nil
|
||||||
}
|
}
|
||||||
pulls := []link.PullRef{{Owner: "novox", Repo: "shop", Number: 7}}
|
pulls := []link.PullRef{pull}
|
||||||
if got := reuseWithVerdicts(t.Context(), &link.ReuseAsk{Pulls: pulls}); !reflect.DeepEqual(got.Builds, named) ||
|
heads := map[link.PullRef]string{pull: "bbbbbbbbbbbb0123456789"}
|
||||||
!reflect.DeepEqual(asked, pulls) {
|
for range 3 {
|
||||||
t.Fatalf("the leave names %+v, read for %+v", got.Builds, asked)
|
if got := reuseWithVerdicts(t.Context(), "plan-1", &link.ReuseAsk{Pulls: pulls}, heads); !reflect.DeepEqual(got.Builds,
|
||||||
|
[]link.CheckedBuild{atHead}) {
|
||||||
|
t.Fatalf("the leave names %+v, not only the merged head's build", got.Builds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(asked, [][]link.PullRef{pulls}) {
|
||||||
|
t.Fatalf("the verdicts were read %d times for one walk: %v", len(asked), asked)
|
||||||
|
}
|
||||||
|
// A merge whose head was not said takes nothing.
|
||||||
|
if got := reuseWithVerdicts(t.Context(), "plan-1", &link.ReuseAsk{Pulls: pulls}, nil); got.Builds != nil {
|
||||||
|
t.Fatalf("a merge of no said head names %+v", got.Builds)
|
||||||
}
|
}
|
||||||
checkedBuildsOf = func(context.Context, []link.PullRef) ([]link.CheckedBuild, error) {
|
checkedBuildsOf = func(context.Context, []link.PullRef) ([]link.CheckedBuild, error) {
|
||||||
return nil, errors.New("no bus")
|
return nil, errors.New("no bus")
|
||||||
}
|
}
|
||||||
if got := reuseWithVerdicts(t.Context(), &link.ReuseAsk{Pulls: pulls}); got.Builds != nil {
|
if got := reuseWithVerdicts(t.Context(), "plan-2", &link.ReuseAsk{Pulls: pulls}, heads); got.Builds != nil {
|
||||||
t.Fatalf("verdicts that could not be read named %+v", got.Builds)
|
t.Fatalf("verdicts that could not be read named %+v", got.Builds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A check is handed the builds the seat's verdicts name for the modules it builds, read once, and none when they
|
// A check is handed only the builds earlier checks of its own pull request at its own head made: another pull
|
||||||
// cannot be read (novox/hq issue 703).
|
// request's build, or an earlier head's, is never kept (novox/hq issue 703). None when they cannot be read.
|
||||||
func TestACheckIsHandedTheBuildsTheVerdictsNameForItsModules(t *testing.T) {
|
func TestACheckIsHandedOnlyItsOwnHeadsBuilds(t *testing.T) {
|
||||||
was := checkedBuildsOfModules
|
was := checkedBuildsOf
|
||||||
t.Cleanup(func() { checkedBuildsOfModules = was })
|
t.Cleanup(func() { checkedBuildsOf = was })
|
||||||
var asked [][]string
|
pull := link.PullRef{Owner: "novox", Repo: "shop", Number: 7}
|
||||||
named := []link.CheckedBuild{{Pull: link.PullRef{Owner: "novox", Repo: "shop", Number: 7}, Check: "build-1",
|
mine := link.CheckedBuild{Pull: pull, Check: "build-1", Head: "bbbbbbbbbbbb", Built: link.CheckBuilt{Module: "app", Key: "r1:1"}}
|
||||||
Built: link.CheckBuilt{Module: "app", Key: "r1:1"}}}
|
earlier := link.CheckedBuild{Pull: pull, Check: "build-0", Head: "aaaaaaaaaaaa", Built: link.CheckBuilt{Module: "app", Key: "r1:1"}}
|
||||||
checkedBuildsOfModules = func(_ context.Context, modules []string) ([]link.CheckedBuild, error) {
|
another := link.CheckedBuild{Pull: link.PullRef{Owner: "novox", Repo: "shop", Number: 8}, Check: "build-9",
|
||||||
asked = append(asked, modules)
|
Head: "bbbbbbbbbbbb", Built: link.CheckBuilt{Module: "app", Key: "r1:1"}}
|
||||||
return named, nil
|
var asked [][]link.PullRef
|
||||||
|
checkedBuildsOf = func(_ context.Context, pulls []link.PullRef) ([]link.CheckedBuild, error) {
|
||||||
|
asked = append(asked, pulls)
|
||||||
|
return []link.CheckedBuild{mine, earlier, another}, nil
|
||||||
}
|
}
|
||||||
builds := []link.CheckBuild{{Module: "app"}, {Module: "web", Skip: "the merge deletes it"}}
|
p := link.PullUpdated{Owner: "novox", Repo: "shop", Number: 7, Commit: "bbbbbbbbbbbb0123456789"}
|
||||||
if got := namedForTheCheck(t.Context(), builds); !reflect.DeepEqual(got, named) ||
|
builds := []link.CheckBuild{{Module: "app"}}
|
||||||
!reflect.DeepEqual(asked, [][]string{{"app"}}) {
|
if got := namedForTheCheck(t.Context(), p, builds); !reflect.DeepEqual(got, []link.CheckedBuild{mine}) ||
|
||||||
t.Fatalf("the check is handed %+v, read %v", got, asked)
|
!reflect.DeepEqual(asked, [][]link.PullRef{{pull}}) {
|
||||||
|
t.Fatalf("the check is handed %+v, read for %v", got, asked)
|
||||||
}
|
}
|
||||||
checkedBuildsOfModules = func(context.Context, []string) ([]link.CheckedBuild, error) {
|
checkedBuildsOf = func(context.Context, []link.PullRef) ([]link.CheckedBuild, error) {
|
||||||
return nil, errors.New("no bus")
|
return nil, errors.New("no bus")
|
||||||
}
|
}
|
||||||
if got := namedForTheCheck(t.Context(), builds); got != nil {
|
if got := namedForTheCheck(t.Context(), p, builds); got != nil {
|
||||||
t.Fatalf("verdicts that could not be read named %+v", got)
|
t.Fatalf("verdicts that could not be read named %+v", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func forgetWalkVerdicts() {
|
||||||
|
walkVerdicts.Lock()
|
||||||
|
defer walkVerdicts.Unlock()
|
||||||
|
walkVerdicts.read = map[string]verdictsRead{}
|
||||||
|
}
|
||||||
|
|||||||
@@ -283,30 +283,32 @@ func checkRequestFor(ctx context.Context, open *stores, p link.PullUpdated, scop
|
|||||||
Source: sourceOnSeat(source),
|
Source: sourceOnSeat(source),
|
||||||
Check: &link.CheckRequest{Owner: p.Owner, Repo: p.Repo, Number: p.Number, Base: p.Base,
|
Check: &link.CheckRequest{Owner: p.Owner, Repo: p.Repo, Number: p.Number, Base: p.Base,
|
||||||
Paths: p.Paths, Beside: beside, Modules: scope.Modules, Dependents: scope.Dependents, New: scope.New,
|
Paths: p.Paths, Beside: beside, Modules: scope.Modules, Dependents: scope.Dependents, New: scope.New,
|
||||||
Manifests: scope.Manifests, Judge: scope.Judge, Builds: builds, Named: namedForTheCheck(ctx, builds)},
|
Manifests: scope.Manifests, Judge: scope.Judge, Builds: builds, Named: namedForTheCheck(ctx, p, builds)},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// namedForTheCheck are the builds the seat's verdicts name for the modules a check builds, read once for the check
|
// namedForTheCheck are the builds earlier checks of the same pull request at the same head named, read once for the
|
||||||
// (novox/hq issue 703): what it may keep instead of building again. None when they cannot be read, and the check
|
// check (novox/hq issue 703): asked again because only the trunk moved, the check keeps what they built of a key
|
||||||
// builds, as before.
|
// still the same. Another pull request's build, or one of an earlier head, is never kept. None when they cannot be
|
||||||
func namedForTheCheck(ctx context.Context, builds []link.CheckBuild) []link.CheckedBuild {
|
// read, and the check builds, as before.
|
||||||
var modules []string
|
func namedForTheCheck(ctx context.Context, p link.PullUpdated, builds []link.CheckBuild) []link.CheckedBuild {
|
||||||
for _, b := range builds {
|
if len(builds) == 0 || p.Commit == "" {
|
||||||
if b.Skip == "" {
|
|
||||||
modules = append(modules, b.Module)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(modules) == 0 {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
named, err := checkedBuildsOfModules(ctx, modules)
|
pull := link.PullRef{Owner: p.Owner, Repo: p.Repo, Number: p.Number}
|
||||||
|
read, err := checkedBuildsOf(ctx, []link.PullRef{pull})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf(" the checks' builds of %d module(s) could not be read, so the check builds them: %v\n",
|
fmt.Printf(" the earlier checks' builds of %s/%s#%d could not be read, so the check builds: %v\n",
|
||||||
len(modules), err)
|
p.Owner, p.Repo, p.Number, err)
|
||||||
// empty-on-error: said above; a check handed no named build builds every module, as before issue 703
|
// empty-on-error: said above; a check handed no named build builds every module, as before issue 703
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
var named []link.CheckedBuild
|
||||||
|
for _, b := range read {
|
||||||
|
if b.Pull == pull && link.SameHead(b.Head, p.Commit) {
|
||||||
|
named = append(named, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
return named
|
return named
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/nats-io/nats.go"
|
"github.com/nats-io/nats.go"
|
||||||
@@ -386,30 +387,73 @@ var checkedBuildsOf = func(ctx context.Context, pulls []link.PullRef) ([]link.Ch
|
|||||||
return link.ReadCheckBuilds(ctx, js, buildSeatHeld(ctx), pulls, time.Now().Add(-builder.CheckBuildsKept))
|
return link.ReadCheckBuilds(ctx, js, buildSeatHeld(ctx), pulls, time.Now().Add(-builder.CheckBuildsKept))
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkedBuildsOfModules are the builds the build seat's checks of any pull request said they made, or kept, of
|
// mergedHeadsOf are the head commits the walk's merges merged, by pull request (novox/hq issue 703): a pull request
|
||||||
// those modules within the week check builds are kept (novox/hq issue 703): what a check may keep instead of
|
// merged twice in one walk, or whose head the announcement did not say, has none, and takes no check's build.
|
||||||
// building. A variable so a test needs no bus.
|
func mergedHeadsOf(p *inventory.Plan) map[link.PullRef]string {
|
||||||
var checkedBuildsOfModules = func(ctx context.Context, modules []string) ([]link.CheckedBuild, error) {
|
heads := map[link.PullRef]string{}
|
||||||
if len(modules) == 0 {
|
if p.Delivery == nil {
|
||||||
return nil, nil
|
return heads
|
||||||
}
|
}
|
||||||
js, err := aBus()
|
seen := map[link.PullRef]bool{}
|
||||||
if err != nil {
|
for _, m := range p.Delivery.Merges {
|
||||||
return nil, err
|
pull, ok := pullOf(m)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
defer js.Close()
|
if seen[pull] && heads[pull] != m.Head {
|
||||||
return link.ReadCheckBuildsOf(ctx, js, buildSeatHeld(ctx), modules, time.Now().Add(-builder.CheckBuildsKept))
|
heads[pull] = ""
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[pull] = true
|
||||||
|
heads[pull] = m.Head
|
||||||
|
}
|
||||||
|
return heads
|
||||||
|
}
|
||||||
|
|
||||||
|
// walkVerdicts are the checks' builds a walk read, by the walk's plan: read once per walk (novox/hq issue 649), not
|
||||||
|
// once per module it asks for. Kept a day; a walk lasts minutes.
|
||||||
|
var walkVerdicts = struct {
|
||||||
|
sync.Mutex
|
||||||
|
read map[string]verdictsRead
|
||||||
|
}{read: map[string]verdictsRead{}}
|
||||||
|
|
||||||
|
type verdictsRead struct {
|
||||||
|
at time.Time
|
||||||
|
builds []link.CheckedBuild
|
||||||
}
|
}
|
||||||
|
|
||||||
// reuseWithVerdicts is a walk's leave with the builds its pull requests' verdicts name: the only builds the builder
|
// reuseWithVerdicts is a walk's leave with the builds its pull requests' verdicts name: the only builds the builder
|
||||||
// may take (ADR 0350 §2). When they cannot be read the leave names none, and the walk builds, saying why.
|
// may take (ADR 0350 §2), and **only those of a check of the head each merge merged** (novox/hq issue 703): a build
|
||||||
func reuseWithVerdicts(ctx context.Context, r *link.ReuseAsk) *link.ReuseAsk {
|
// a check made at an earlier head of the same pull request is never registered. Read once per walk. When they
|
||||||
|
// cannot be read the leave names none, and the walk builds, saying why.
|
||||||
|
func reuseWithVerdicts(ctx context.Context, walk string, r *link.ReuseAsk, heads map[link.PullRef]string) *link.ReuseAsk {
|
||||||
|
walkVerdicts.Lock()
|
||||||
|
read, known := walkVerdicts.read[walk]
|
||||||
|
walkVerdicts.Unlock()
|
||||||
|
if !known || walk == "" {
|
||||||
builds, err := checkedBuildsOf(ctx, r.Pulls)
|
builds, err := checkedBuildsOf(ctx, r.Pulls)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf(" the checks' builds of %d pull request(s) could not be read, so the walk builds: %v\n", len(r.Pulls), err)
|
fmt.Printf(" the checks' builds of %d pull request(s) could not be read, so the walk builds: %v\n", len(r.Pulls), err)
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
r.Builds = builds
|
read = verdictsRead{at: time.Now(), builds: builds}
|
||||||
|
if walk != "" {
|
||||||
|
walkVerdicts.Lock()
|
||||||
|
for id, w := range walkVerdicts.read {
|
||||||
|
if time.Since(w.at) > 24*time.Hour {
|
||||||
|
delete(walkVerdicts.read, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
walkVerdicts.read[walk] = read
|
||||||
|
walkVerdicts.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.Builds = nil
|
||||||
|
for _, b := range read.builds {
|
||||||
|
if link.SameHead(b.Head, heads[b.Pull]) {
|
||||||
|
r.Builds = append(r.Builds, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -452,7 +496,8 @@ func askModule(ctx context.Context, p *inventory.Plan, name string, byName map[s
|
|||||||
}
|
}
|
||||||
// **The walk takes the check's build of the same key** (novox/hq ADR 0350 §2): its ask says so, with the pull
|
// **The walk takes the check's build of the same key** (novox/hq ADR 0350 §2): its ask says so, with the pull
|
||||||
// requests it answers, whose checks name the part of a differing key.
|
// requests it answers, whose checks name the part of a differing key.
|
||||||
id, err := askABuild(withReuse(ctx, reuseWithVerdicts(ctx, reuseAskOf(p))), source, e.Source.Path, ref)
|
id, err := askABuild(withReuse(ctx, reuseWithVerdicts(ctx, p.ID, reuseAskOf(p), mergedHeadsOf(p))), source,
|
||||||
|
e.Source.Path, ref)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
state.State = "failed"
|
state.State = "failed"
|
||||||
state.Why = err.Error()
|
state.Why = err.Error()
|
||||||
|
|||||||
@@ -162,6 +162,10 @@ type Reuse struct {
|
|||||||
// keyFirst is a check's build (§1): its key is taken before anything is built, and a module with none is not
|
// keyFirst is a check's build (§1): its key is taken before anything is built, and a module with none is not
|
||||||
// built at all — nothing could ever take it.
|
// built at all — nothing could ever take it.
|
||||||
keyFirst bool
|
keyFirst bool
|
||||||
|
// pull and head are the check's own pull request and the head it judged (novox/hq issue 703): it keeps only a
|
||||||
|
// build an earlier check of the same pull request at the same head made.
|
||||||
|
pull PullRef
|
||||||
|
head string
|
||||||
}
|
}
|
||||||
|
|
||||||
// errNoKey is a check's build of a module whose source does not pin it: not built (ADR 0350 §1).
|
// errNoKey is a check's build of a module whose source does not pin it: not built (ADR 0350 §1).
|
||||||
@@ -498,11 +502,22 @@ func takeACheckBuild(ctx context.Context, run Runner, publish Publisher, reuse R
|
|||||||
}
|
}
|
||||||
|
|
||||||
// keptCheckBuild is the build a check keeps instead of building (novox/hq issue 703): the one kept under the
|
// keptCheckBuild is the build a check keeps instead of building (novox/hq issue 703): the one kept under the
|
||||||
// module's key, when a verdict of the build seat names its key and every digest — the record alone is never trusted
|
// module's key by an earlier check of the same pull request at the same head — only the trunk moved — when a verdict
|
||||||
// (ADR 0350 §2) — and it holds every artifact the module declares. Nil, and the check builds, otherwise.
|
// of the build seat names its key and every digest — the record alone is never trusted (ADR 0350 §2) — and it holds
|
||||||
|
// every artifact the module declares. Another pull request's build, or an earlier head's, is never kept. Nil, and
|
||||||
|
// the check builds, otherwise.
|
||||||
func keptCheckBuild(ctx context.Context, reuse Reuse, module, key string, artifacts int,
|
func keptCheckBuild(ctx context.Context, reuse Reuse, module, key string, artifacts int,
|
||||||
say func(step, format string, args ...any)) *Reused {
|
say func(step, format string, args ...any)) *Reused {
|
||||||
if reuse.Scratch == nil || len(reuse.Named) == 0 {
|
if reuse.Scratch == nil || reuse.head == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var same []NamedBuild
|
||||||
|
for _, n := range reuse.Named {
|
||||||
|
if n.Pull == reuse.pull {
|
||||||
|
same = append(same, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(same) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
found, ok, err := reuse.Scratch.Find(ctx, module, key)
|
found, ok, err := reuse.Scratch.Find(ctx, module, key)
|
||||||
@@ -512,7 +527,11 @@ func keptCheckBuild(ctx context.Context, reuse Reuse, module, key string, artifa
|
|||||||
return nil
|
return nil
|
||||||
case !ok:
|
case !ok:
|
||||||
return nil
|
return nil
|
||||||
case unnamed(reuse.Named, module, key, found) != "":
|
case found.Pull != reuse.pull || found.Head == "" || twelve(found.Head) != twelve(reuse.head):
|
||||||
|
say("reuse", "the build kept under %s is %s, not this pull request's at this head: built", twelve(key[3:]),
|
||||||
|
found.Said())
|
||||||
|
return nil
|
||||||
|
case unnamed(same, module, key, found) != "":
|
||||||
say("reuse", "a build is kept under %s and no verdict names it with its digests: built", twelve(key[3:]))
|
say("reuse", "a build is kept under %s and no verdict names it with its digests: built", twelve(key[3:]))
|
||||||
return nil
|
return nil
|
||||||
case len(found.Made) != artifacts:
|
case len(found.Made) != artifacts:
|
||||||
|
|||||||
@@ -354,6 +354,22 @@ func Check(ctx context.Context, run Runner, spec CheckSpec, workspace, registry
|
|||||||
v.Head, v.Trunk, v.Merge, v.Beside, v.Members = judged.Head, judged.Trunk, judged.Merge, judged.Beside, judged.Members
|
v.Head, v.Trunk, v.Merge, v.Beside, v.Members = judged.Head, judged.Trunk, judged.Merge, judged.Beside, judged.Members
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
// **What the merge would move is built from a checkout nothing of the check touches** (novox/hq issue 703): the
|
||||||
|
// gate and the repository's own check run in containers that mount the workspace read-write, so they can change
|
||||||
|
// its tree, its objects and its caches. The builds are made from a copy of the merge's objects, cloned before
|
||||||
|
// anything of the change runs, outside the workspace — so what is kept under a key is what the key describes.
|
||||||
|
building, buildingTree := "", ""
|
||||||
|
if len(spec.Builds) > 0 && spec.BuildWith != nil && len(spec.Group) == 0 {
|
||||||
|
at := judged.Merge
|
||||||
|
if at == "" {
|
||||||
|
at = spec.Ref
|
||||||
|
}
|
||||||
|
building, buildingTree, err = checkoutForTheBuilds(ctx, run, tree, at, workspace)
|
||||||
|
if err != nil {
|
||||||
|
return CheckVerdict{}, err
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(building)
|
||||||
|
}
|
||||||
// **The base branch's check judges the change** (novox/hq issue 310), never the one the change brings.
|
// **The base branch's check judges the change** (novox/hq issue 310), never the one the change brings.
|
||||||
judging, err := TheCheckThatJudges(ctx, tree, spec.Base)
|
judging, err := TheCheckThatJudges(ctx, tree, spec.Base)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -610,7 +626,7 @@ func Check(ctx context.Context, run Runner, spec CheckSpec, workspace, registry
|
|||||||
say("check", "%s", why)
|
say("check", "%s", why)
|
||||||
} else {
|
} else {
|
||||||
began := time.Now()
|
began := time.Now()
|
||||||
v.Built = buildForTheMerge(ctx, run, spec, *spec.BuildWith, workspace, tree, judged.Merge, judged, forge,
|
v.Built = buildForTheMerge(ctx, run, spec, *spec.BuildWith, building, buildingTree, judged.Merge, judged, forge,
|
||||||
func(step, message string) {
|
func(step, message string) {
|
||||||
if log != nil {
|
if log != nil {
|
||||||
log(step, redact.redact(message))
|
log(step, redact.redact(message))
|
||||||
@@ -623,6 +639,39 @@ func Check(ctx context.Context, run Runner, spec CheckSpec, workspace, registry
|
|||||||
return named(v), nil
|
return named(v), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkoutForTheBuilds is a clone of the commit a check builds for its merge, made before anything of the check runs,
|
||||||
|
// outside the workspace its containers mount (novox/hq issue 703): the directory to remove, and the clone in it.
|
||||||
|
// Cloned over git's own transport, never by linking the objects: what the check's containers later write into the
|
||||||
|
// workspace's objects cannot reach it.
|
||||||
|
func checkoutForTheBuilds(ctx context.Context, run Runner, tree, at, workspace string) (string, string, error) {
|
||||||
|
dir, err := os.MkdirTemp("", "mesh-check-builds-")
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
if withinDir(workspace, dir) {
|
||||||
|
os.RemoveAll(dir)
|
||||||
|
return "", "", fmt.Errorf("the temporary directory %s is inside the workspace %s, which a check's containers "+
|
||||||
|
"mount: the merge is not built from where its tests could change it", dir, workspace)
|
||||||
|
}
|
||||||
|
commit, err := run(ctx, tree, "git", "rev-parse", "--verify", at+"^{commit}")
|
||||||
|
if err != nil {
|
||||||
|
os.RemoveAll(dir)
|
||||||
|
return "", "", fmt.Errorf("the commit to build for the merge, %s: %w", at, err)
|
||||||
|
}
|
||||||
|
commit = strings.TrimSpace(commit)
|
||||||
|
if _, err := run(ctx, tree, "git", "update-ref", "refs/heads/mesh-check-builds", commit); err != nil {
|
||||||
|
os.RemoveAll(dir)
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
cloned := filepath.Join(dir, "merged")
|
||||||
|
if _, err := run(ctx, dir, "git", "clone", "--quiet", "--no-local", "--no-checkout", "--branch",
|
||||||
|
"mesh-check-builds", tree, cloned); err != nil {
|
||||||
|
os.RemoveAll(dir)
|
||||||
|
return "", "", fmt.Errorf("cannot copy the merge to build it: %w", err)
|
||||||
|
}
|
||||||
|
return dir, cloned, nil
|
||||||
|
}
|
||||||
|
|
||||||
// builtCount is how many of a check's builds for its merge were built.
|
// builtCount is how many of a check's builds for its merge were built.
|
||||||
func builtCount(built []CheckBuilt) int {
|
func builtCount(built []CheckBuilt) int {
|
||||||
n := 0
|
n := 0
|
||||||
|
|||||||
@@ -37,9 +37,10 @@ type CheckBuilding struct {
|
|||||||
Held map[string]string
|
Held map[string]string
|
||||||
Npmrc Npmrc
|
Npmrc Npmrc
|
||||||
Seats map[string]string
|
Seats map[string]string
|
||||||
// Named are the builds the build seat's verdicts name for the modules this check builds, of any pull request,
|
// Named are the builds the build seat's verdicts of earlier checks of this pull request at this head name
|
||||||
// within the week check builds are kept (novox/hq issue 703): a build kept under a module's key is kept again,
|
// (novox/hq issue 703): a build kept under a module's key is kept again, instead of built, only when one of them
|
||||||
// instead of built, only when one of them names its key and every digest. The record alone is never enough.
|
// names its key and every digest, and the build is this pull request's at this head. The record alone is never
|
||||||
|
// enough.
|
||||||
Named []NamedBuild
|
Named []NamedBuild
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +109,8 @@ func buildForTheMerge(ctx context.Context, run Runner, spec CheckSpec, with Chec
|
|||||||
var mirrored []string
|
var mirrored []string
|
||||||
result, err := build(ctx, run, scratchPublisher{to: with.Publish}, repository, b.Path, at,
|
result, err := build(ctx, run, scratchPublisher{to: with.Publish}, repository, b.Path, at,
|
||||||
filepath.Join(workspace, "for-the-merge"), held, with.Npmrc, forge, log, &mirrored,
|
filepath.Join(workspace, "for-the-merge"), held, with.Npmrc, forge, log, &mirrored,
|
||||||
&Reuse{keyFirst: true, Scratch: with.Scratch, Named: with.Named}, with.Seats)
|
&Reuse{keyFirst: true, Scratch: with.Scratch, Named: with.Named,
|
||||||
|
pull: PullRef{Owner: spec.Owner, Repo: spec.Repo, Number: spec.Number}, head: judged.Head}, with.Seats)
|
||||||
built := CheckBuilt{Module: b.Module, Key: result.ReuseKey, Took: time.Since(began)}
|
built := CheckBuilt{Module: b.Module, Key: result.ReuseKey, Took: time.Since(began)}
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, errNoKey):
|
case errors.Is(err, errNoKey):
|
||||||
|
|||||||
@@ -210,3 +210,53 @@ func TestACheckMergeRunsNoHook(t *testing.T) {
|
|||||||
t.Fatalf("a hook ran when the check made its merge: %s", said)
|
t.Fatalf("a hook ran when the check made its merge: %s", said)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// What a check's tests change in the workspace never reaches what it builds for the merge (novox/hq issue 703): the
|
||||||
|
// builds are made from a copy of the merge, outside the workspace, cloned before anything of the change runs.
|
||||||
|
func TestWhatTheTestsChangeInTheWorkspaceNeverReachesTheBuild(t *testing.T) {
|
||||||
|
repo, head, _ := aTrunkAndABranch(t, map[string]string{"main.go": "package main // the merge\n"},
|
||||||
|
map[string]string{"trunk-only": "t"}, map[string]string{"head-only": "h"})
|
||||||
|
workspace := t.TempDir()
|
||||||
|
tree := filepath.Join(workspace, "check", "checked")
|
||||||
|
if out, err := exec.Command("git", "clone", "--quiet", repo, tree).CombinedOutput(); err != nil {
|
||||||
|
t.Fatalf("%v\n%s", err, out)
|
||||||
|
}
|
||||||
|
dir, cloned, err := checkoutForTheBuilds(t.Context(), Command, tree, head, workspace)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
if withinDir(workspace, cloned) {
|
||||||
|
t.Fatalf("the builds' checkout %s is inside the workspace %s the tests mount", cloned, workspace)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The tests rewrite the tree, its branch and every object it holds.
|
||||||
|
if err := os.WriteFile(filepath.Join(tree, "main.go"), []byte("package main // planted\n"), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := filepath.WalkDir(filepath.Join(tree, ".git", "objects"), func(path string, d os.DirEntry, err error) error {
|
||||||
|
if err != nil || d.IsDir() {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_ = os.Chmod(path, 0o644)
|
||||||
|
return os.WriteFile(path, []byte("planted"), 0o644)
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The build clones the merge from its own checkout, as buildForTheMerge does, and reads what the merge holds.
|
||||||
|
build := filepath.Join(t.TempDir(), "source")
|
||||||
|
if out, err := exec.Command("git", "clone", "--quiet", cloned, build).CombinedOutput(); err != nil {
|
||||||
|
t.Fatalf("%v\n%s", err, out)
|
||||||
|
}
|
||||||
|
if out, err := exec.Command("git", "-C", build, "checkout", "--quiet", head).CombinedOutput(); err != nil {
|
||||||
|
t.Fatalf("%v\n%s", err, out)
|
||||||
|
}
|
||||||
|
body, err := os.ReadFile(filepath.Join(build, "main.go"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(body) != "package main // the merge\n" {
|
||||||
|
t.Fatalf("the build read %q: what the tests wrote reached it", body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -630,13 +630,19 @@ func TestABuildTwoChecksShareGoesWithTheLastOfThem(t *testing.T) {
|
|||||||
// aCheckAgain is a later check (build-2) of aPull building the module from a tree, with the builds the build seat's
|
// aCheckAgain is a later check (build-2) of aPull building the module from a tree, with the builds the build seat's
|
||||||
// verdicts name for it (novox/hq issue 703).
|
// verdicts name for it (novox/hq issue 703).
|
||||||
func aCheckAgain(t *testing.T, scratch *aScratch, tree string, leave []NamedBuild) (CheckBuilt, *recorded) {
|
func aCheckAgain(t *testing.T, scratch *aScratch, tree string, leave []NamedBuild) (CheckBuilt, *recorded) {
|
||||||
|
t.Helper()
|
||||||
|
return aCheckOf(t, scratch, aPull, "abcdefabcdef", tree, leave)
|
||||||
|
}
|
||||||
|
|
||||||
|
// aCheckOf is a later check (build-2) of a pull request at a head, the trunk moved since build-1.
|
||||||
|
func aCheckOf(t *testing.T, scratch *aScratch, pull PullRef, head, tree string, leave []NamedBuild) (CheckBuilt, *recorded) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
r, run, workspace := aModuleAt(t, tree)
|
r, run, workspace := aModuleAt(t, tree)
|
||||||
spec := CheckSpec{ID: "build-2", Owner: aPull.Owner, Repo: aPull.Repo, Number: aPull.Number,
|
spec := CheckSpec{ID: "build-2", Owner: pull.Owner, Repo: pull.Repo, Number: pull.Number,
|
||||||
Builds: []CheckBuildSpec{{Module: "meshboard", Repository: "https://forge.invalid/meshboard.git", Path: "mod",
|
Builds: []CheckBuildSpec{{Module: "meshboard", Repository: "https://forge.invalid/meshboard.git", Path: "mod",
|
||||||
Ref: "main"}}}
|
Ref: "main"}}}
|
||||||
built := buildForTheMerge(t.Context(), run, spec, CheckBuilding{Publish: r, Scratch: scratch, Named: leave},
|
built := buildForTheMerge(t.Context(), run, spec, CheckBuilding{Publish: r, Scratch: scratch, Named: leave},
|
||||||
workspace, "", "", CheckVerdict{Head: "fedcbafedcba", Trunk: "654321654321"}, GitCredential{}, nil, logging(nil))
|
workspace, "", "", CheckVerdict{Head: head, Trunk: "654321654321"}, GitCredential{}, nil, logging(nil))
|
||||||
if len(built) != 1 || built[0].NotBuilt != "" || built[0].Key == "" {
|
if len(built) != 1 || built[0].NotBuilt != "" || built[0].Key == "" {
|
||||||
t.Fatalf("the second check did not answer the module for its merge: %+v", built)
|
t.Fatalf("the second check did not answer the module for its merge: %+v", built)
|
||||||
}
|
}
|
||||||
@@ -745,3 +751,28 @@ func TestABuildALaterCheckKeptStaysWhenTheCheckThatBuiltItGoes(t *testing.T) {
|
|||||||
t.Fatalf("the build stayed after the last check naming it went: held %v, record %v", held(), found())
|
t.Fatalf("the build stayed after the last check naming it went: held %v, record %v", held(), found())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only an earlier check of the same pull request at the same head is kept from: another head of it, or another pull
|
||||||
|
// request whose verdict names the same key and digests, builds (novox/hq issue 703).
|
||||||
|
func TestACheckKeepsNoBuildOfAnotherHeadOrPullRequest(t *testing.T) {
|
||||||
|
withVersion(t, "builder-1")
|
||||||
|
other := PullRef{Owner: "novox", Repo: "meshboard", Number: 8}
|
||||||
|
for name, c := range map[string]struct {
|
||||||
|
pull PullRef
|
||||||
|
head string
|
||||||
|
}{
|
||||||
|
"another head": {aPull, "999999999999"},
|
||||||
|
"another pull request": {other, "abcdefabcdef"},
|
||||||
|
} {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
scratch := newScratch()
|
||||||
|
first := aCheckBuilt(t, scratch, "tree-one")
|
||||||
|
leave := named(first)
|
||||||
|
leave[0].Pull = c.pull // as if its own verdict named it
|
||||||
|
again, r := aCheckOf(t, scratch, c.pull, c.head, "tree-one", leave)
|
||||||
|
if !dockerBuilt(r) || again.Kept != "" {
|
||||||
|
t.Fatalf("a build of %s was kept: %+v", name, again)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -104,6 +104,9 @@ type PlanMerge struct {
|
|||||||
Number int `json:"number,omitempty"`
|
Number int `json:"number,omitempty"`
|
||||||
Title string `json:"title,omitempty"`
|
Title string `json:"title,omitempty"`
|
||||||
Moves []string `json:"moves,omitempty"`
|
Moves []string `json:"moves,omitempty"`
|
||||||
|
// Head is the pull request's head commit the forge merged (novox/hq issue 703): the walk takes only a build a
|
||||||
|
// check of that head made. Empty where the announcement said none, and then it takes none.
|
||||||
|
Head string `json:"head,omitempty"`
|
||||||
// Merged is when the forge made the merge and Heard when the controller heard it (novox/hq ADR 0282): where
|
// Merged is when the forge made the merge and Heard when the controller heard it (novox/hq ADR 0282): where
|
||||||
// its delivery time starts. Zero for a merge named before they were kept.
|
// its delivery time starts. Zero for a merge named before they were kept.
|
||||||
Merged time.Time `json:"merged,omitzero"`
|
Merged time.Time `json:"merged,omitzero"`
|
||||||
|
|||||||
+16
-3
@@ -116,9 +116,21 @@ type ReuseAsk struct {
|
|||||||
type CheckedBuild struct {
|
type CheckedBuild struct {
|
||||||
Pull PullRef `json:"pull"`
|
Pull PullRef `json:"pull"`
|
||||||
Check string `json:"check"`
|
Check string `json:"check"`
|
||||||
|
// Head is the pull request's head the check judged, as its verdict says it (twelve characters): a build is
|
||||||
|
// taken or kept only by a check of the same head (novox/hq issue 703).
|
||||||
|
Head string `json:"head,omitempty"`
|
||||||
Built CheckBuilt `json:"built"`
|
Built CheckBuilt `json:"built"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SameHead says two spellings of a commit name the same one: both said, one the other's beginning, at least
|
||||||
|
// twelve characters of it (a verdict says twelve).
|
||||||
|
func SameHead(a, b string) bool {
|
||||||
|
if len(a) > len(b) {
|
||||||
|
a, b = b, a
|
||||||
|
}
|
||||||
|
return len(a) >= 12 && strings.HasPrefix(b, a)
|
||||||
|
}
|
||||||
|
|
||||||
// PullRef names a pull request.
|
// PullRef names a pull request.
|
||||||
type PullRef struct {
|
type PullRef struct {
|
||||||
Owner string `json:"owner"`
|
Owner string `json:"owner"`
|
||||||
@@ -164,9 +176,10 @@ type CheckRequest struct {
|
|||||||
// each, from the merge it judged, into the scratch namespace once it has judged and not failed, for the walk to
|
// each, from the merge it judged, into the scratch namespace once it has judged and not failed, for the walk to
|
||||||
// take. A build seat from before it builds nothing, and the walk builds as before.
|
// take. A build seat from before it builds nothing, and the walk builds as before.
|
||||||
Builds []CheckBuild `json:"builds,omitempty"`
|
Builds []CheckBuild `json:"builds,omitempty"`
|
||||||
// Named are the builds the seat's verdicts name for those modules, of any pull request, within the week check
|
// Named are the builds the seat's verdicts of earlier checks of this pull request at this head name, within the
|
||||||
// builds are kept (novox/hq issue 703): the check keeps a build kept under a module's unchanged key, instead of
|
// week check builds are kept (novox/hq issue 703): asked again because only the trunk moved, the check keeps a
|
||||||
// building it again, only when one of these names its key and every digest. A build seat from before it builds.
|
// build kept under a module's unchanged key, instead of building it again, only when one of these names its key
|
||||||
|
// and every digest. Never another pull request's, nor an earlier head's. A build seat from before it builds.
|
||||||
Named []CheckedBuild `json:"named,omitempty"`
|
Named []CheckedBuild `json:"named,omitempty"`
|
||||||
// Try marks a branch tried before its pull request (novox/hq issue 686): the repository's own check for a
|
// Try marks a branch tried before its pull request (novox/hq issue 686): the repository's own check for a
|
||||||
// pushed branch or commit, asked by a builder through the controller's `try` verb. Its outcome is read from
|
// pushed branch or commit, asked by a builder through the controller's `try` verb. Its outcome is read from
|
||||||
|
|||||||
@@ -38,9 +38,9 @@ func TestNatsTheBuildsAWalkMayTakeAreTheChecksOutcomesOfItsPullRequests(t *testi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A check reads the builds every pull request's checks made of the modules it builds, and a kept build is named as
|
// A build is read with the head its check judged, and a kept build is named as the check's that built it (novox/hq
|
||||||
// the check's that built it (novox/hq issue 703).
|
// issue 703).
|
||||||
func TestNatsACheckReadsTheBuildsOfItsModulesAndAKeptBuildIsTheBuildersCheck(t *testing.T) {
|
func TestNatsAKeptBuildIsTheBuildersCheckAndEveryBuildSaysItsHead(t *testing.T) {
|
||||||
js := aBus(t)
|
js := aBus(t)
|
||||||
since := time.Now().Add(-time.Minute)
|
since := time.Now().Add(-time.Minute)
|
||||||
say := func(r BuildResult) {
|
say := func(r BuildResult) {
|
||||||
@@ -49,21 +49,39 @@ func TestNatsACheckReadsTheBuildsOfItsModulesAndAKeptBuildIsTheBuildersCheck(t *
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mine := &CheckRequest{Owner: "novox", Repo: "shop", Number: 7}
|
||||||
app := CheckBuilt{Module: "app", Key: "r1:1", Made: []MadeArtifact{{Name: "server", Reference: "s/app/server@sha256:a"}}}
|
app := CheckBuilt{Module: "app", Key: "r1:1", Made: []MadeArtifact{{Name: "server", Reference: "s/app/server@sha256:a"}}}
|
||||||
web := CheckBuilt{Module: "web", Key: "r1:2", Made: []MadeArtifact{{Name: "server", Reference: "s/web/server@sha256:b"}}}
|
say(BuildResult{ID: "build-1", Checked: mine, Check: &CheckOutcome{Verdict: "pass", Head: "aaaaaaaaaaaa",
|
||||||
say(BuildResult{ID: "build-1", Checked: &CheckRequest{Owner: "novox", Repo: "shop", Number: 7},
|
Built: []CheckBuilt{app}}})
|
||||||
Check: &CheckOutcome{Verdict: "pass", Built: []CheckBuilt{app, web}}})
|
|
||||||
kept := app
|
kept := app
|
||||||
kept.Kept = "build-1"
|
kept.Kept = "build-1"
|
||||||
say(BuildResult{ID: "build-2", Checked: &CheckRequest{Owner: "novox", Repo: "till", Number: 3},
|
say(BuildResult{ID: "build-2", Checked: mine, Check: &CheckOutcome{Verdict: "pass", Head: "aaaaaaaaaaaa",
|
||||||
Check: &CheckOutcome{Verdict: "pass", Built: []CheckBuilt{kept}}})
|
Built: []CheckBuilt{kept}}})
|
||||||
|
|
||||||
got, err := ReadCheckBuildsOf(t.Context(), js, TheBuildMachine, []string{"app"}, since)
|
got, err := ReadCheckBuilds(t.Context(), js, TheBuildMachine, []PullRef{{Owner: "novox", Repo: "shop", Number: 7}}, since)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if len(got) != 2 || got[0].Check != "build-1" || got[0].Pull.Repo != "till" || got[1].Check != "build-1" ||
|
if len(got) != 2 || got[0].Check != "build-1" || got[0].Built.Kept != "build-1" || got[0].Head != "aaaaaaaaaaaa" ||
|
||||||
got[1].Built.Module != "app" {
|
got[1].Check != "build-1" || got[1].Head != "aaaaaaaaaaaa" {
|
||||||
t.Fatalf("read %+v", got)
|
t.Fatalf("read %+v", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Two spellings of a head are the same head only when both say at least twelve characters of it.
|
||||||
|
func TestSameHeadNeedsTwelveCharactersOfBoth(t *testing.T) {
|
||||||
|
for _, c := range []struct {
|
||||||
|
a, b string
|
||||||
|
same bool
|
||||||
|
}{
|
||||||
|
{"aaaaaaaaaaaa", "aaaaaaaaaaaabbbb", true},
|
||||||
|
{"aaaaaaaaaaaabbbb", "aaaaaaaaaaaa", true},
|
||||||
|
{"aaaaaaaaaaaa", "aaaaaaaaaaab", false},
|
||||||
|
{"", "", false},
|
||||||
|
{"aaaa", "aaaaaaaaaaaa", false},
|
||||||
|
} {
|
||||||
|
if SameHead(c.a, c.b) != c.same {
|
||||||
|
t.Errorf("SameHead(%q, %q) is %v", c.a, c.b, !c.same)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -176,6 +176,9 @@ type SourceMoved struct {
|
|||||||
Repo string `json:"repo"`
|
Repo string `json:"repo"`
|
||||||
Base string `json:"base"`
|
Base string `json:"base"`
|
||||||
Head string `json:"head"`
|
Head string `json:"head"`
|
||||||
|
// HeadSHA is the head commit the forge merged (novox/hq issue 703): a walk takes only a build a check of that
|
||||||
|
// head made. Empty from a forge module from before it, and then the walk takes no check's build.
|
||||||
|
HeadSHA string `json:"head_sha,omitempty"`
|
||||||
Commit string `json:"merge_commit_sha"`
|
Commit string `json:"merge_commit_sha"`
|
||||||
CloneURL string `json:"clone_url"`
|
CloneURL string `json:"clone_url"`
|
||||||
HTMLURL string `json:"html_url"`
|
HTMLURL string `json:"html_url"`
|
||||||
|
|||||||
+1
-14
@@ -378,19 +378,6 @@ func ReadCheckBuilds(ctx context.Context, js *broker.JetStream, seat string, pul
|
|||||||
return readCheckBuilds(ctx, js, seat, since, func(p PullRef, _ CheckBuilt) bool { return wanted[p] })
|
return readCheckBuilds(ctx, js, seat, since, func(p PullRef, _ CheckBuilt) bool { return wanted[p] })
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadCheckBuildsOf is every build the seat's checks of any pull request said they made, or kept, of those modules
|
|
||||||
// since a moment (novox/hq issue 703): what a check may keep instead of building. Newest first.
|
|
||||||
func ReadCheckBuildsOf(ctx context.Context, js *broker.JetStream, seat string, modules []string, since time.Time) ([]CheckedBuild, error) {
|
|
||||||
if len(modules) == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
wanted := map[string]bool{}
|
|
||||||
for _, m := range modules {
|
|
||||||
wanted[m] = true
|
|
||||||
}
|
|
||||||
return readCheckBuilds(ctx, js, seat, since, func(_ PullRef, b CheckBuilt) bool { return wanted[b.Module] })
|
|
||||||
}
|
|
||||||
|
|
||||||
// readCheckBuilds is every build the seat's checks said they made, or kept, that wanted takes. A kept build is named
|
// readCheckBuilds is every build the seat's checks said they made, or kept, that wanted takes. A kept build is named
|
||||||
// as the check's that built it (novox/hq issue 703): the record under its key is that check's.
|
// as the check's that built it (novox/hq issue 703): the record under its key is that check's.
|
||||||
func readCheckBuilds(ctx context.Context, js *broker.JetStream, seat string, since time.Time,
|
func readCheckBuilds(ctx context.Context, js *broker.JetStream, seat string, since time.Time,
|
||||||
@@ -428,7 +415,7 @@ func readCheckBuilds(ctx context.Context, js *broker.JetStream, seat string, sin
|
|||||||
if b.Kept != "" {
|
if b.Kept != "" {
|
||||||
check = b.Kept
|
check = b.Kept
|
||||||
}
|
}
|
||||||
out = append(out, CheckedBuild{Pull: pull, Check: check, Built: b})
|
out = append(out, CheckedBuild{Pull: pull, Check: check, Head: r.Check.Head, Built: b})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user