Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

A free and open-source forge that lives in your Git repo.

Phase 4: a rename carries the queue, instead of stranding it

72797dcd4606a81d35fff03124fc1ba627cc1d60 · Keenan Nemetz · 17d ago · parent d798eaac

Phase 4: a rename carries the queue, instead of stranding it

Queue rows are keyed by repository name rather than by the repository's
stable id, so a rename left every queued job under a name no runner is
ever told about — the lease filters by the names an account may run, and
the old name no longer exists — and left the ref cursors behind, so the
next reconciliation queued the current tip a second time.

RenameRepo moves the runs, the cursors and the presence rows in one
transaction, and `vole repo rename` calls it. A rename refuses a name that
is already taken, so the unique index on (repo, ref, sha, workflow, job)
cannot collide.

internal/server/reconcile_test.go +40

@@ -209,6 +209,46 @@ func TestANewerTipCancelsTheQueuedBuildOfTheOlderOne(t *testing.T) {
209 209 }
210 210 }
211 211
212 // A rename must carry the queue with it. Rows are keyed by repository name, so a
213 // stranded job is one no runner is ever told about — the lease filters by the names an
214 // account may run, and the old name no longer exists — and a stranded cursor makes the
215 // next reconciliation build the current tip a second time.
216 func TestRenamingARepositoryCarriesItsQueue(t *testing.T) {
217 h := newReconcileHarness(t)
218 h.commit(t, "refs/heads/main", testWorkflow)
219 h.s.reconcileRefs()
220
221 if err := h.s.Repos.Rename("demo", "renamed"); err != nil {
222 t.Fatal(err)
223 }
224 if err := h.runs.RenameRepo("demo", "renamed"); err != nil {
225 t.Fatal(err)
226 }
227 if old, err := h.runs.Recent("demo", 10); err != nil {
228 t.Fatal(err)
229 } else if len(old) != 0 {
230 t.Fatalf("%d run(s) stayed under the old name", len(old))
231 }
232 kept, err := h.runs.Recent("renamed", 10)
233 if err != nil {
234 t.Fatal(err)
235 }
236 if len(kept) != 1 {
237 t.Fatalf("%d run(s) under the new name, want the queued one to have moved", len(kept))
238 }
239
240 // The cursor came along, so the sweep has nothing new to queue.
241 h.s.reconcileRefs()
242 after, err := h.runs.Recent("renamed", 10)
243 if err != nil {
244 t.Fatal(err)
245 }
246 if len(after) != len(kept) {
247 t.Fatalf("the sweep queued %d more run(s) after the rename: the cursor did not follow",
248 len(after)-len(kept))
249 }
250 }
251
212 252 // A deleted branch drops its cursor, so the branch coming back at a new tip is work
213 253 // owed rather than work suppressed by a note about a branch that no longer exists.
214 254 func TestReconcileDropsTheCursorOfADeletedBranch(t *testing.T) {