Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion src/cli/land-fixer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ async function comment(
}

/** Queue eligibility can eject a previously requeued PR before building another batch. */
/** How many one-second pauses to wait for GitHub to show a pushed head. */
const SYNC_PAUSES = 60;

export async function handbackRepeated(
state: string,
plan: Plan,
Expand Down Expand Up @@ -402,7 +405,12 @@ async function collectJob(
}

if (result?.action === "fixed") {
const latest = object(await (await github.request(pullPath)).json());
// GitHub shows a push a moment later. Wait for our own head; only a third head is a real move.
let latest = object(await (await github.request(pullPath)).json());
for (let wait = 0; object(latest.head).sha === entry.head && wait < SYNC_PAUSES; wait++) {
await github.pause(1000);
latest = object(await (await github.request(pullPath)).json());
}
if (object(latest.head).sha !== head) throw new FixerRefusal("head moved before Review update");
await github.request(pullPath, {
method: "PATCH",
Expand Down
20 changes: 20 additions & 0 deletions test/land.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1717,6 +1717,26 @@ describe("asynchronous land fixer", () => {
expect(fixture.pull.autoMerge?.commitBody).toBe(pin(fixture.pull.head!));
expect(fixture.pull.labels).toContain("quest:merge");
});
test("waits for GitHub to show the pushed fix before recording it", async () => {
const fixture = await fixerFixture(fixedScript(), { conflict: true });
await fixture.finished;
const deps = await fixture.forge.deps();
const old = fixture.plan.queue[0].head;
// GitHub's REST view lags the push: the first two reads after it still show the old head.
let stale = 2;
const original = deps.fetch;
deps.fetch = (async (input, init) => {
const response = await original(input, init);
if (!String(input).endsWith("/pulls/1") || (init?.method ?? "GET") !== "GET" || fixture.pull.head === old)
return response;
if (stale-- <= 0) return response;
const body = (await response.json()) as { head: Record<string, unknown> };
return Response.json({ ...body, head: { ...body.head, sha: old } });
}) as typeof fetch;
deps.sleep = async () => {};
expect(await fixture.collect(deps)).toEqual(["requeued 1"]);
expect(fixture.pull.body).toContain(`(head ${fixture.pull.head})`);
});
test.each([
[
"handback",
Expand Down
Loading