next-issue 2.4.8 → 2.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -125,6 +125,8 @@ pull request yet, when:
125
125
  ping-pong between the fixer and the reviewer, or a finding that the fixer
126
126
  disputed and the reviewer holds to. The person then decides;
127
127
  - the reviewer gives no verdict, or the checks do not finish in time;
128
+ - someone pushes to the pull request while the harness waits for the checks,
129
+ so the pull request no longer shows the commit that the harness pushed;
128
130
  - with `--merge`, the merge of an approved pull request fails, for example
129
131
  because branch protection asks for a human approval;
130
132
  - with `--merge`, a queued merge does not finish in `checkTimeoutMinutes`, or
package/dist/git.mjs CHANGED
@@ -160,6 +160,10 @@ export async function resetToBase(cwd, base, remote) {
160
160
  export async function revision(cwd) {
161
161
  return must('git', ['rev-parse', 'HEAD'], { cwd });
162
162
  }
163
+ export async function isAncestor(cwd, ancestor, descendant) {
164
+ const result = await run('git', ['merge-base', '--is-ancestor', ancestor, descendant], { cwd });
165
+ return result.code === 0;
166
+ }
163
167
  export async function deleteBranch(repo, branch) {
164
168
  const result = await run('git', ['branch', '-D', branch], { cwd: repo.root });
165
169
  return result.code === 0;
package/dist/github.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import { must, run } from './exec.mjs';
2
+ import { isAncestor } from './git.mjs';
2
3
  const CHECK_PENDING_CODE = 8;
3
4
  const READ_TRIES = 3;
4
5
  const NETWORK_TRIES = 3;
@@ -214,6 +215,9 @@ export async function pollChecks(probe, options, clock = REAL_CLOCK) {
214
215
  return 'timeout';
215
216
  }
216
217
  const state = await read(probe, options.head, rest(), () => (shown ??= clock.now()));
218
+ if (state === 'behind' && shown !== undefined) {
219
+ return 'moved';
220
+ }
217
221
  if (state instanceof Error) {
218
222
  failures += 1;
219
223
  if (failures >= READ_TRIES) {
@@ -234,14 +238,21 @@ export async function pollChecks(probe, options, clock = REAL_CLOCK) {
234
238
  }
235
239
  async function read(probe, head, timeoutMs, shown) {
236
240
  try {
237
- if (head !== undefined && (await probe.head()) !== head) {
238
- return 'behind';
241
+ if (head !== undefined) {
242
+ const current = await probe.head();
243
+ if (current !== head) {
244
+ return (await probe.lags(current)) ? 'behind' : 'moved';
245
+ }
239
246
  }
240
247
  shown();
241
248
  if ((await probe.list()) === 'none') {
242
249
  return 'none';
243
250
  }
244
- return await probe.watch(timeoutMs);
251
+ const state = await probe.watch(timeoutMs);
252
+ if (head !== undefined && (await probe.head()) !== head) {
253
+ return 'moved';
254
+ }
255
+ return state;
245
256
  }
246
257
  catch (error) {
247
258
  return error instanceof Error ? error : new Error(String(error));
@@ -250,6 +261,9 @@ async function read(probe, head, timeoutMs, shown) {
250
261
  export function waitForChecks(repo, pr, options) {
251
262
  return pollChecks({
252
263
  head: () => prHead(repo, pr),
264
+ lags: (head) => options.head === undefined
265
+ ? Promise.resolve(true)
266
+ : isAncestor(repo.root, head, options.head),
253
267
  list: async () => ((await readChecks(repo, pr)) === 'none' ? 'none' : 'some'),
254
268
  watch: (timeoutMs) => watchOnce(repo, pr, options.intervalSeconds, timeoutMs, options.show),
255
269
  }, options);
@@ -287,6 +301,7 @@ function baseState(runs) {
287
301
  export function waitForBaseChecks(repo, sha, options) {
288
302
  return pollChecks({
289
303
  head: async () => sha,
304
+ lags: async () => true,
290
305
  list: async () => ((await baseRuns(repo, sha)).length === 0 ? 'none' : 'some'),
291
306
  watch: async () => baseState(await baseRuns(repo, sha)),
292
307
  }, options);
package/dist/pipeline.mjs CHANGED
@@ -302,6 +302,10 @@ async function checkGate(job) {
302
302
  await escalate(job, 'The pull request conflicts with its base, so no checks ran.');
303
303
  return { done: true, report: await finish(job, 'needs-human', 'merge conflict') };
304
304
  }
305
+ if (checks === 'moved') {
306
+ await escalate(job, 'Someone pushed to the pull request while the harness waited for the checks.');
307
+ return { done: true, report: await finish(job, 'needs-human', 'pull request moved') };
308
+ }
305
309
  if (checks !== 'fail') {
306
310
  return { done: false, fixed: false };
307
311
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-issue",
3
- "version": "2.4.8",
3
+ "version": "2.4.9",
4
4
  "description": "Autonomous issue harness driven by the pi SDK",
5
5
  "keywords": [
6
6
  "github",