Public: anyone can read this; the crew can change it.
A pure read picks the five most-behind open crew pulls to bring up to date first #6553
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/6550-a-pure-read-picks-the-five-most-behind-o → main 1 commit fork 2 threads · 2 unresolved
2 files
changed +68
−0
src/flow/rebase_first.ts added +32
| @@ -0,0 +1,32 @@ | ||
| 1 | /** | |
| 2 | * Which open pulls to bring up to date first (#6550): at most five that are | |
| 3 | * behind main, approved ones before the rest, then the most landings behind, | |
| 4 | * then the oldest base. Held pulls are never picked. A pure read: plain data in, | |
| 5 | * pull numbers out. | |
| 6 | */ | |
| 7 | ||
| 8 | export const REBASE_FIRST = 5; | |
| 9 | ||
| 10 | export interface RebaseDrift { | |
| 11 | readonly number: number; | |
| 12 | /** Landings on main past the pull's base. */ | |
| 13 | readonly behind: number; | |
| 14 | /** When the pull's base was the tip of main, epoch ms. */ | |
| 15 | readonly baseAt: number; | |
| 16 | readonly approved: boolean; | |
| 17 | readonly held: boolean; | |
| 18 | } | |
| 19 | ||
| 20 | export function rebaseFirst(drifts: readonly RebaseDrift[], _now: number): number[] { | |
| 21 | return drifts | |
| 22 | .filter((d) => !d.held && d.behind > 0) | |
| 23 | .sort( | |
| 24 | (a, b) => | |
| 25 | Number(b.approved) - Number(a.approved) || | |
| 26 | b.behind - a.behind || | |
| 27 | a.baseAt - b.baseAt || | |
| 28 | a.number - b.number, | |
| 29 | ) | |
| 30 | .slice(0, REBASE_FIRST) | |
| 31 | .map((d) => d.number); | |
| 32 | } |
test/flow-rebase-first.test.ts added +36
| @@ -0,0 +1,36 @@ | ||
| 1 | import { expect, test } from "bun:test"; | |
| 2 | import { type RebaseDrift, rebaseFirst } from "../src/flow/rebase_first.ts"; | |
| 3 | ||
| 4 | const d = (number: number, o: Partial<RebaseDrift> = {}): RebaseDrift => ({ | |
| 5 | number, | |
| 6 | behind: 1, | |
| 7 | baseAt: 1000, | |
| 8 | approved: false, | |
| 9 | held: false, | |
| 10 | ...o, | |
| 11 | }); | |
| 12 | ||
| 13 | test("approved pulls come before others", () => { | |
| 14 | expect(rebaseFirst([d(1, { behind: 9 }), d(2, { approved: true })], 0)).toEqual([2, 1]); | |
| 15 | }); | |
| 16 | ||
| 17 | test("then the most landings behind", () => { | |
| 18 | expect(rebaseFirst([d(1, { behind: 3 }), d(2, { behind: 7 })], 0)).toEqual([2, 1]); | |
| 19 | }); | |
| 20 | ||
| 21 | test("then the oldest base", () => { | |
| 22 | expect(rebaseFirst([d(1, { baseAt: 500 + 1 }), d(2, { baseAt: 500 })], 0)).toEqual([2, 1]); | |
|
||
| 23 | }); | |
| 24 | ||
| 25 | test("held pulls are never picked", () => { | |
| 26 | expect(rebaseFirst([d(1, { held: true, approved: true, behind: 99 }), d(2)], 0)).toEqual([2]); | |
| 27 | }); | |
| 28 | ||
| 29 | test("pulls 0 behind are never picked", () => { | |
| 30 | expect(rebaseFirst([d(1, { behind: 0, approved: true })], 0)).toEqual([]); | |
| 31 | }); | |
| 32 | ||
| 33 | test("at most five", () => { | |
| 34 | const all = [1, 2, 3, 4, 5, 6, 7].map((n) => d(n, { behind: n })); | |
| 35 | expect(rebaseFirst(all, 0)).toEqual([7, 6, 5, 4, 3]); | |
| 36 | }); |
new above
offline
_nowis never read. Fine if the caller needs the shared flow signature; otherwise remove it so the function is honest about its inputs.