Commit 47861ecc58

47861ecc5867852c6491b3b272f9a3a373cd19ae

parent: 17f45745ef

Verified · cmc ci/build: success ci/test: success ci/vuln: success

cmc <hello@cleberg.net> · 2026-09-02 04:42 UTC

ci: a commit with a build still queued or running is not queued again

The landing-time skip counted only a success build of the commit, so a
merge made before the branch's build had finished queued the same
commit a second time on main. Three of the jobs after !172 were that.

A pending or running build of the commit for the job now counts too:
it will say what the commit does soon enough, and the commit statuses
it writes land on the sha whatever ref it ran for. Failed, abandoned
and cancelled builds still do not count.

Ref #90

Layout: unified · split

e2e/ci_dedupe_test.go +20 −1
@@ -57,6 +57,25 @@ func TestFastForwardMergeSkipsBuiltCommit(t *testing.T) {
57 t.Fatalf("statuses on the merged commit changed:\n%s", status) 57 t.Fatalf("statuses on the merged commit changed:\n%s", status)
58 } 58 }
59 59
60 // A branch merged before its build has run: the queued build will
61 // say, so the merge queues nothing either.
62 mustGit(t, dir, env, "checkout", "-q", "-b", "early")
63 os.WriteFile(filepath.Join(dir, "g.txt"), []byte("y\n"), 0o644)
64 mustGit(t, dir, env, "add", ".")
65 mustGit(t, dir, env, "commit", "-q", "-m", "early change")
66 mustGit(t, dir, env, "push", "-q", "origin", "early")
67 if _, errOut, code := inst.ssh(t, aliceKey, "", "mr", "create", "alice/app", "--source", "early", "--target", "main", "--title", "early"); code != 0 {
68 t.Fatalf("mr create: %s", errOut)
69 }
70 before = strings.Count(inst.buildList(t, aliceKey), "\n")
71 if _, errOut, code := inst.ssh(t, aliceKey, "", "mr", "merge", "alice/app", "2", "--strategy", "ff"); code != 0 {
72 t.Fatalf("mr merge early: %s", errOut)
73 }
74 if after := strings.Count(inst.buildList(t, aliceKey), "\n"); after != before {
75 t.Fatalf("a merge before the branch build ran queued a second build:\n%s", inst.buildList(t, aliceKey))
76 }
77 inst.runnerOnce(t, runnerKey) // the branch's queued build, now the only one
78
60 // A branch whose build fails is built again when it lands. 79 // A branch whose build fails is built again when it lands.
61 mustGit(t, dir, env, "checkout", "-q", "-b", "bad") 80 mustGit(t, dir, env, "checkout", "-q", "-b", "bad")
62 os.WriteFile(filepath.Join(dir, "fail.txt"), []byte("x\n"), 0o644) 81 os.WriteFile(filepath.Join(dir, "fail.txt"), []byte("x\n"), 0o644)
@@ -72,7 +91,7 @@ func TestFastForwardMergeSkipsBuiltCommit(t *testing.T) {
72 t.Fatalf("mr create: %s", errOut) 91 t.Fatalf("mr create: %s", errOut)
73 } 92 }
74 before = strings.Count(inst.buildList(t, aliceKey), "\n") 93 before = strings.Count(inst.buildList(t, aliceKey), "\n")
75 if _, errOut, code := inst.ssh(t, aliceKey, "", "mr", "merge", "alice/app", "2", "--strategy", "ff"); code != 0 { 94 if _, errOut, code := inst.ssh(t, aliceKey, "", "mr", "merge", "alice/app", "3", "--strategy", "ff"); code != 0 {
76 t.Fatalf("mr merge bad: %s", errOut) 95 t.Fatalf("mr merge bad: %s", errOut)
77 } 96 }
78 if after := strings.Count(inst.buildList(t, aliceKey), "\n"); after != before+1 { 97 if after := strings.Count(inst.buildList(t, aliceKey), "\n"); after != before+1 {
internal/control/build.go +4 −3
@@ -510,8 +510,9 @@ func QueueBranchBuilds(
510 } 510 }
511 // A build is a fact about a commit, not a ref: a job has no branch 511 // A build is a fact about a commit, not a ref: a job has no branch
512 // filter, so a commit that already passed a job on another branch has 512 // filter, so a commit that already passed a job on another branch has
513 // nothing left to prove when a fast-forward lands it here. A failed or 513 // nothing left to prove when a fast-forward lands it here, and one
514 // abandoned build does not count; that commit runs again. 514 // still queued or running there will say soon enough. A failed,
515 // abandoned or cancelled build does not count; that commit runs again.
515 built, err := st.BuildsForCommit(repo.ID, sha) 516 built, err := st.BuildsForCommit(repo.ID, sha)
516 if err != nil { 517 if err != nil {
517 built = nil 518 built = nil
@@ -522,7 +523,7 @@ func QueueBranchBuilds(
522 if j.Tags != "" { 523 if j.Tags != "" {
523 continue 524 continue
524 } 525 }
525 if b, ok := built[j.Name]; ok && b.Status == "success" { 526 if b, ok := built[j.Name]; ok && (b.Status == "success" || b.Status == "pending" || b.Status == "running") {
526 continue 527 continue
527 } 528 }
528 // Scheduled jobs run on their cron, not on push; a default-branch 529 // Scheduled jobs run on their cron, not on push; a default-branch