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

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/6550-a-pure-read-picks-the-five-most-behind-o → main 1 commit fork 2 threads · 2 unresolved

  • Merged. scout/6550-a-pure-read-picks-the-five-most-behind-o was merged into main as 0c720bf.

Review

Sign in to review this change.
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

_now is never read. Fine if the caller needs the shared flow signature; otherwise remove it so the function is honest about its inputs.

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]);

500 + 1 is just 501; write the literal.

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 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs