pr-merge-queue 0.0.0-stage → 0.0.0
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/LICENSE +201 -0
- package/README.md +125 -2
- package/dist/claim-pr/claim-pr.d.mts +45 -0
- package/dist/claim-pr/claim-pr.d.mts.map +1 -0
- package/dist/claim-pr/claim-pr.mjs +217 -0
- package/dist/claim-pr/claim-pr.mjs.map +1 -0
- package/dist/claim-pr/index.d.mts +2 -0
- package/dist/claim-pr/index.d.mts.map +1 -0
- package/dist/claim-pr/index.mjs +2 -0
- package/dist/claim-pr/index.mjs.map +1 -0
- package/dist/cmd/claim-pr.d.mts +3 -0
- package/dist/cmd/claim-pr.d.mts.map +1 -0
- package/dist/cmd/claim-pr.mjs +20 -0
- package/dist/cmd/claim-pr.mjs.map +1 -0
- package/dist/cmd/open-pr.d.mts +3 -0
- package/dist/cmd/open-pr.d.mts.map +1 -0
- package/dist/cmd/open-pr.mjs +20 -0
- package/dist/cmd/open-pr.mjs.map +1 -0
- package/dist/cmd/pr-report.d.mts +3 -0
- package/dist/cmd/pr-report.d.mts.map +1 -0
- package/dist/cmd/pr-report.mjs +20 -0
- package/dist/cmd/pr-report.mjs.map +1 -0
- package/dist/cmd/push-fixed-pr.d.mts +3 -0
- package/dist/cmd/push-fixed-pr.d.mts.map +1 -0
- package/dist/cmd/push-fixed-pr.mjs +20 -0
- package/dist/cmd/push-fixed-pr.mjs.map +1 -0
- package/dist/cmd/report-unowned-prs.d.mts +3 -0
- package/dist/cmd/report-unowned-prs.d.mts.map +1 -0
- package/dist/cmd/report-unowned-prs.mjs +20 -0
- package/dist/cmd/report-unowned-prs.mjs.map +1 -0
- package/dist/cmd/unblock-prs.d.mts +3 -0
- package/dist/cmd/unblock-prs.d.mts.map +1 -0
- package/dist/cmd/unblock-prs.mjs +19 -0
- package/dist/cmd/unblock-prs.mjs.map +1 -0
- package/dist/entry-point.d.mts +3 -0
- package/dist/entry-point.d.mts.map +1 -0
- package/dist/entry-point.mjs +3 -0
- package/dist/entry-point.mjs.map +1 -0
- package/dist/lib/cli-args.d.mts +27 -0
- package/dist/lib/cli-args.d.mts.map +1 -0
- package/dist/lib/cli-args.mjs +55 -0
- package/dist/lib/cli-args.mjs.map +1 -0
- package/dist/lib/github-api.d.mts +153 -0
- package/dist/lib/github-api.d.mts.map +1 -0
- package/dist/lib/github-api.mjs +291 -0
- package/dist/lib/github-api.mjs.map +1 -0
- package/dist/lib/index.d.mts +7 -0
- package/dist/lib/index.d.mts.map +1 -0
- package/dist/lib/index.mjs +7 -0
- package/dist/lib/index.mjs.map +1 -0
- package/dist/lib/json.d.mts +12 -0
- package/dist/lib/json.d.mts.map +1 -0
- package/dist/lib/json.mjs +28 -0
- package/dist/lib/json.mjs.map +1 -0
- package/dist/lib/log.d.mts +10 -0
- package/dist/lib/log.d.mts.map +1 -0
- package/dist/lib/log.mjs +13 -0
- package/dist/lib/log.mjs.map +1 -0
- package/dist/lib/shell.d.mts +18 -0
- package/dist/lib/shell.d.mts.map +1 -0
- package/dist/lib/shell.mjs +42 -0
- package/dist/lib/shell.mjs.map +1 -0
- package/dist/lib/time.d.mts +17 -0
- package/dist/lib/time.d.mts.map +1 -0
- package/dist/lib/time.mjs +35 -0
- package/dist/lib/time.mjs.map +1 -0
- package/dist/open-pr/github.d.mts +72 -0
- package/dist/open-pr/github.d.mts.map +1 -0
- package/dist/open-pr/github.mjs +249 -0
- package/dist/open-pr/github.mjs.map +1 -0
- package/dist/open-pr/index.d.mts +6 -0
- package/dist/open-pr/index.d.mts.map +1 -0
- package/dist/open-pr/index.mjs +6 -0
- package/dist/open-pr/index.mjs.map +1 -0
- package/dist/open-pr/main.d.mts +36 -0
- package/dist/open-pr/main.d.mts.map +1 -0
- package/dist/open-pr/main.mjs +204 -0
- package/dist/open-pr/main.mjs.map +1 -0
- package/dist/open-pr/options.d.mts +20 -0
- package/dist/open-pr/options.d.mts.map +1 -0
- package/dist/open-pr/options.mjs +66 -0
- package/dist/open-pr/options.mjs.map +1 -0
- package/dist/open-pr/steps.d.mts +23 -0
- package/dist/open-pr/steps.d.mts.map +1 -0
- package/dist/open-pr/steps.mjs +38 -0
- package/dist/open-pr/steps.mjs.map +1 -0
- package/dist/open-pr/types.d.mts +179 -0
- package/dist/open-pr/types.d.mts.map +1 -0
- package/dist/open-pr/types.mjs +20 -0
- package/dist/open-pr/types.mjs.map +1 -0
- package/dist/pr-report/github.d.mts +59 -0
- package/dist/pr-report/github.d.mts.map +1 -0
- package/dist/pr-report/github.mjs +561 -0
- package/dist/pr-report/github.mjs.map +1 -0
- package/dist/pr-report/index.d.mts +8 -0
- package/dist/pr-report/index.d.mts.map +1 -0
- package/dist/pr-report/index.mjs +8 -0
- package/dist/pr-report/index.mjs.map +1 -0
- package/dist/pr-report/linked-issues.d.mts +15 -0
- package/dist/pr-report/linked-issues.d.mts.map +1 -0
- package/dist/pr-report/linked-issues.mjs +39 -0
- package/dist/pr-report/linked-issues.mjs.map +1 -0
- package/dist/pr-report/main.d.mts +42 -0
- package/dist/pr-report/main.d.mts.map +1 -0
- package/dist/pr-report/main.mjs +163 -0
- package/dist/pr-report/main.mjs.map +1 -0
- package/dist/pr-report/options.d.mts +36 -0
- package/dist/pr-report/options.d.mts.map +1 -0
- package/dist/pr-report/options.mjs +90 -0
- package/dist/pr-report/options.mjs.map +1 -0
- package/dist/pr-report/queue.d.mts +28 -0
- package/dist/pr-report/queue.d.mts.map +1 -0
- package/dist/pr-report/queue.mjs +74 -0
- package/dist/pr-report/queue.mjs.map +1 -0
- package/dist/pr-report/render.d.mts +14 -0
- package/dist/pr-report/render.d.mts.map +1 -0
- package/dist/pr-report/render.mjs +318 -0
- package/dist/pr-report/render.mjs.map +1 -0
- package/dist/pr-report/repo-settings.d.mts +12 -0
- package/dist/pr-report/repo-settings.d.mts.map +1 -0
- package/dist/pr-report/repo-settings.mjs +22 -0
- package/dist/pr-report/repo-settings.mjs.map +1 -0
- package/dist/push-fixed-pr/index.d.mts +2 -0
- package/dist/push-fixed-pr/index.d.mts.map +1 -0
- package/dist/push-fixed-pr/index.mjs +2 -0
- package/dist/push-fixed-pr/index.mjs.map +1 -0
- package/dist/push-fixed-pr/push-fixed-pr.d.mts +54 -0
- package/dist/push-fixed-pr/push-fixed-pr.d.mts.map +1 -0
- package/dist/push-fixed-pr/push-fixed-pr.mjs +172 -0
- package/dist/push-fixed-pr/push-fixed-pr.mjs.map +1 -0
- package/dist/report-unowned-prs/index.d.mts +2 -0
- package/dist/report-unowned-prs/index.d.mts.map +1 -0
- package/dist/report-unowned-prs/index.mjs +2 -0
- package/dist/report-unowned-prs/index.mjs.map +1 -0
- package/dist/report-unowned-prs/report-unowned-prs.d.mts +49 -0
- package/dist/report-unowned-prs/report-unowned-prs.d.mts.map +1 -0
- package/dist/report-unowned-prs/report-unowned-prs.mjs +127 -0
- package/dist/report-unowned-prs/report-unowned-prs.mjs.map +1 -0
- package/dist/tsconfig.json +1 -0
- package/dist/unblock-prs/auto-fix.d.mts +45 -0
- package/dist/unblock-prs/auto-fix.d.mts.map +1 -0
- package/dist/unblock-prs/auto-fix.mjs +207 -0
- package/dist/unblock-prs/auto-fix.mjs.map +1 -0
- package/dist/unblock-prs/checks.d.mts +307 -0
- package/dist/unblock-prs/checks.d.mts.map +1 -0
- package/dist/unblock-prs/checks.mjs +372 -0
- package/dist/unblock-prs/checks.mjs.map +1 -0
- package/dist/unblock-prs/claims.d.mts +21 -0
- package/dist/unblock-prs/claims.d.mts.map +1 -0
- package/dist/unblock-prs/claims.mjs +23 -0
- package/dist/unblock-prs/claims.mjs.map +1 -0
- package/dist/unblock-prs/config.d.mts +487 -0
- package/dist/unblock-prs/config.d.mts.map +1 -0
- package/dist/unblock-prs/config.mjs +119 -0
- package/dist/unblock-prs/config.mjs.map +1 -0
- package/dist/unblock-prs/constants.d.mts +28 -0
- package/dist/unblock-prs/constants.d.mts.map +1 -0
- package/dist/unblock-prs/constants.mjs +28 -0
- package/dist/unblock-prs/constants.mjs.map +1 -0
- package/dist/unblock-prs/demotions.d.mts +42 -0
- package/dist/unblock-prs/demotions.d.mts.map +1 -0
- package/dist/unblock-prs/demotions.mjs +50 -0
- package/dist/unblock-prs/demotions.mjs.map +1 -0
- package/dist/unblock-prs/github.d.mts +739 -0
- package/dist/unblock-prs/github.d.mts.map +1 -0
- package/dist/unblock-prs/github.mjs +626 -0
- package/dist/unblock-prs/github.mjs.map +1 -0
- package/dist/unblock-prs/index.d.mts +25 -0
- package/dist/unblock-prs/index.d.mts.map +1 -0
- package/dist/unblock-prs/index.mjs +25 -0
- package/dist/unblock-prs/index.mjs.map +1 -0
- package/dist/unblock-prs/labels.d.mts +49 -0
- package/dist/unblock-prs/labels.d.mts.map +1 -0
- package/dist/unblock-prs/labels.mjs +49 -0
- package/dist/unblock-prs/labels.mjs.map +1 -0
- package/dist/unblock-prs/main.d.mts +179 -0
- package/dist/unblock-prs/main.d.mts.map +1 -0
- package/dist/unblock-prs/main.mjs +640 -0
- package/dist/unblock-prs/main.mjs.map +1 -0
- package/dist/unblock-prs/merge-after.d.mts +46 -0
- package/dist/unblock-prs/merge-after.d.mts.map +1 -0
- package/dist/unblock-prs/merge-after.mjs +65 -0
- package/dist/unblock-prs/merge-after.mjs.map +1 -0
- package/dist/unblock-prs/options.d.mts +35 -0
- package/dist/unblock-prs/options.d.mts.map +1 -0
- package/dist/unblock-prs/options.mjs +88 -0
- package/dist/unblock-prs/options.mjs.map +1 -0
- package/dist/unblock-prs/owner.d.mts +32 -0
- package/dist/unblock-prs/owner.d.mts.map +1 -0
- package/dist/unblock-prs/owner.mjs +63 -0
- package/dist/unblock-prs/owner.mjs.map +1 -0
- package/dist/unblock-prs/quiet.d.mts +21 -0
- package/dist/unblock-prs/quiet.d.mts.map +1 -0
- package/dist/unblock-prs/quiet.mjs +42 -0
- package/dist/unblock-prs/quiet.mjs.map +1 -0
- package/dist/unblock-prs/rebase.d.mts +120 -0
- package/dist/unblock-prs/rebase.d.mts.map +1 -0
- package/dist/unblock-prs/rebase.mjs +576 -0
- package/dist/unblock-prs/rebase.mjs.map +1 -0
- package/dist/unblock-prs/release.d.mts +62 -0
- package/dist/unblock-prs/release.d.mts.map +1 -0
- package/dist/unblock-prs/release.mjs +94 -0
- package/dist/unblock-prs/release.mjs.map +1 -0
- package/dist/unblock-prs/set-aside-comment.d.mts +20 -0
- package/dist/unblock-prs/set-aside-comment.d.mts.map +1 -0
- package/dist/unblock-prs/set-aside-comment.mjs +93 -0
- package/dist/unblock-prs/set-aside-comment.mjs.map +1 -0
- package/dist/unblock-prs/set-aside-detail.d.mts +41 -0
- package/dist/unblock-prs/set-aside-detail.d.mts.map +1 -0
- package/dist/unblock-prs/set-aside-detail.mjs +56 -0
- package/dist/unblock-prs/set-aside-detail.mjs.map +1 -0
- package/dist/unblock-prs/skips.d.mts +85 -0
- package/dist/unblock-prs/skips.d.mts.map +1 -0
- package/dist/unblock-prs/skips.mjs +141 -0
- package/dist/unblock-prs/skips.mjs.map +1 -0
- package/dist/unblock-prs/stack.d.mts +98 -0
- package/dist/unblock-prs/stack.d.mts.map +1 -0
- package/dist/unblock-prs/stack.mjs +141 -0
- package/dist/unblock-prs/stack.mjs.map +1 -0
- package/dist/unblock-prs/triage.d.mts +28 -0
- package/dist/unblock-prs/triage.d.mts.map +1 -0
- package/dist/unblock-prs/triage.mjs +396 -0
- package/dist/unblock-prs/triage.mjs.map +1 -0
- package/dist/unblock-prs/types.d.mts +793 -0
- package/dist/unblock-prs/types.d.mts.map +1 -0
- package/dist/unblock-prs/types.mjs +26 -0
- package/dist/unblock-prs/types.mjs.map +1 -0
- package/dist/unblock-prs/util.d.mts +23 -0
- package/dist/unblock-prs/util.d.mts.map +1 -0
- package/dist/unblock-prs/util.mjs +60 -0
- package/dist/unblock-prs/util.mjs.map +1 -0
- package/dist/unblock-prs/version-pr.d.mts +57 -0
- package/dist/unblock-prs/version-pr.d.mts.map +1 -0
- package/dist/unblock-prs/version-pr.mjs +79 -0
- package/dist/unblock-prs/version-pr.mjs.map +1 -0
- package/dist/unblock-prs/watch.d.mts +17 -0
- package/dist/unblock-prs/watch.d.mts.map +1 -0
- package/dist/unblock-prs/watch.mjs +128 -0
- package/dist/unblock-prs/watch.mjs.map +1 -0
- package/package.json +118 -3
- package/src/claim-pr/claim-pr.mts +380 -0
- package/src/claim-pr/claim-pr.test.mts +176 -0
- package/src/claim-pr/index.mts +1 -0
- package/src/cmd/claim-pr.mts +32 -0
- package/src/cmd/open-pr.mts +28 -0
- package/src/cmd/pr-report.mts +28 -0
- package/src/cmd/push-fixed-pr.mts +36 -0
- package/src/cmd/report-unowned-prs.mts +37 -0
- package/src/cmd/unblock-prs.mts +24 -0
- package/src/entry-point.mts +3 -0
- package/src/lib/cli-args.mts +86 -0
- package/src/lib/cli-args.test.mts +92 -0
- package/src/lib/github-api.mts +463 -0
- package/src/lib/github-api.test.mts +40 -0
- package/src/lib/index.mts +6 -0
- package/src/lib/json.mts +43 -0
- package/src/lib/json.test.mts +34 -0
- package/src/lib/log.mts +19 -0
- package/src/lib/log.test.mts +45 -0
- package/src/lib/shell.mts +52 -0
- package/src/lib/time.mts +49 -0
- package/src/open-pr/README.md +237 -0
- package/src/open-pr/github.mts +444 -0
- package/src/open-pr/index.mts +5 -0
- package/src/open-pr/main.mts +327 -0
- package/src/open-pr/options.mts +101 -0
- package/src/open-pr/options.test.mts +67 -0
- package/src/open-pr/steps.mts +57 -0
- package/src/open-pr/steps.test.mts +59 -0
- package/src/open-pr/types.mts +42 -0
- package/src/pr-report/README.md +244 -0
- package/src/pr-report/github.mts +911 -0
- package/src/pr-report/index.mts +7 -0
- package/src/pr-report/linked-issues.mts +51 -0
- package/src/pr-report/linked-issues.test.mts +65 -0
- package/src/pr-report/main.mts +257 -0
- package/src/pr-report/options.mts +140 -0
- package/src/pr-report/queue.mts +125 -0
- package/src/pr-report/render.mts +428 -0
- package/src/pr-report/render.test.mts +379 -0
- package/src/pr-report/repo-settings.mts +34 -0
- package/src/push-fixed-pr/index.mts +1 -0
- package/src/push-fixed-pr/push-fixed-pr.mts +276 -0
- package/src/push-fixed-pr/push-fixed-pr.test.mts +228 -0
- package/src/report-unowned-prs/index.mts +1 -0
- package/src/report-unowned-prs/report-unowned-prs.mts +190 -0
- package/src/report-unowned-prs/report-unowned-prs.test.mts +87 -0
- package/src/unblock-prs/README.md +1616 -0
- package/src/unblock-prs/auto-fix.mts +334 -0
- package/src/unblock-prs/auto-fix.test.mts +159 -0
- package/src/unblock-prs/checks.mts +538 -0
- package/src/unblock-prs/checks.test.mts +507 -0
- package/src/unblock-prs/claims.mts +33 -0
- package/src/unblock-prs/claims.test.mts +64 -0
- package/src/unblock-prs/config.mts +187 -0
- package/src/unblock-prs/config.schema.json +71 -0
- package/src/unblock-prs/config.test.mts +139 -0
- package/src/unblock-prs/constants.mts +33 -0
- package/src/unblock-prs/demotions.mts +73 -0
- package/src/unblock-prs/demotions.test.mts +100 -0
- package/src/unblock-prs/github.mts +1042 -0
- package/src/unblock-prs/github.test.mts +84 -0
- package/src/unblock-prs/index.mts +24 -0
- package/src/unblock-prs/labels.mts +70 -0
- package/src/unblock-prs/main.mts +1051 -0
- package/src/unblock-prs/merge-after.mts +117 -0
- package/src/unblock-prs/merge-after.test.mts +120 -0
- package/src/unblock-prs/options.mts +157 -0
- package/src/unblock-prs/owner.mts +91 -0
- package/src/unblock-prs/owner.test.mts +114 -0
- package/src/unblock-prs/quiet.mts +62 -0
- package/src/unblock-prs/quiet.test.mts +175 -0
- package/src/unblock-prs/rebase.mts +962 -0
- package/src/unblock-prs/release.mts +149 -0
- package/src/unblock-prs/release.test.mts +209 -0
- package/src/unblock-prs/set-aside-comment.mts +143 -0
- package/src/unblock-prs/set-aside-comment.test.mts +148 -0
- package/src/unblock-prs/set-aside-detail.mts +82 -0
- package/src/unblock-prs/set-aside-detail.test.mts +131 -0
- package/src/unblock-prs/skips.mts +215 -0
- package/src/unblock-prs/skips.test.mts +280 -0
- package/src/unblock-prs/stack.mts +220 -0
- package/src/unblock-prs/stack.test.mts +449 -0
- package/src/unblock-prs/triage.mts +669 -0
- package/src/unblock-prs/triage.test.mts +100 -0
- package/src/unblock-prs/types.mts +358 -0
- package/src/unblock-prs/util.mts +78 -0
- package/src/unblock-prs/util.test.mts +26 -0
- package/src/unblock-prs/version-pr.mts +112 -0
- package/src/unblock-prs/version-pr.test.mts +78 -0
- package/src/unblock-prs/watch.mts +219 -0
|
@@ -0,0 +1,1051 @@
|
|
|
1
|
+
// cspell:ignore retargeted
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
MERGE_QUEUED_LABEL,
|
|
5
|
+
SKIP_CI_LABEL,
|
|
6
|
+
stackDescendants,
|
|
7
|
+
type Claim,
|
|
8
|
+
} from 'pr-report-core';
|
|
9
|
+
import { Arr, Result } from 'ts-data-forge';
|
|
10
|
+
import { currentEpochMs, log, type ApiContext } from '../lib/index.mjs';
|
|
11
|
+
import { autoFix } from './auto-fix.mjs';
|
|
12
|
+
import { standingClaims } from './claims.mjs';
|
|
13
|
+
import { CONFIG_PATH, readConfig, type AutoFixConfig } from './config.mjs';
|
|
14
|
+
import { STALE_STATE_PAUSE_MS } from './constants.mjs';
|
|
15
|
+
import { afterWatch, followHead, pruneDemotions } from './demotions.mjs';
|
|
16
|
+
import {
|
|
17
|
+
checkPreflight,
|
|
18
|
+
readIssueOrder,
|
|
19
|
+
readOwnComments,
|
|
20
|
+
readSetAsideComments,
|
|
21
|
+
viewPullRequest,
|
|
22
|
+
writeSetAsideCommentOn,
|
|
23
|
+
} from './github.mjs';
|
|
24
|
+
import { isSkipCiLabelled, wantsAutoRebase } from './labels.mjs';
|
|
25
|
+
import type { Options } from './options.mjs';
|
|
26
|
+
import {
|
|
27
|
+
idleWaitSec,
|
|
28
|
+
initialQuiet,
|
|
29
|
+
observeSurvey,
|
|
30
|
+
surveyFingerprint,
|
|
31
|
+
} from './quiet.mjs';
|
|
32
|
+
import {
|
|
33
|
+
advance,
|
|
34
|
+
armOnPick,
|
|
35
|
+
restackBehind,
|
|
36
|
+
restackRetargeted,
|
|
37
|
+
} from './rebase.mjs';
|
|
38
|
+
import { firstInReleaseOrder, settleAfterRelease } from './release.mjs';
|
|
39
|
+
import {
|
|
40
|
+
describeResolvedBy,
|
|
41
|
+
resolvedCommentBody,
|
|
42
|
+
setAsideCommentBody,
|
|
43
|
+
} from './set-aside-comment.mjs';
|
|
44
|
+
import { describeFailedChecks } from './set-aside-detail.mjs';
|
|
45
|
+
import {
|
|
46
|
+
newSkips,
|
|
47
|
+
pruneSkips,
|
|
48
|
+
settleSkips,
|
|
49
|
+
unannouncedSkips,
|
|
50
|
+
withSkip,
|
|
51
|
+
} from './skips.mjs';
|
|
52
|
+
import { stackedOnAfter, stackParentsOf } from './stack.mjs';
|
|
53
|
+
import {
|
|
54
|
+
bypassPhrase,
|
|
55
|
+
describeAction,
|
|
56
|
+
reportTriage,
|
|
57
|
+
survey,
|
|
58
|
+
triage,
|
|
59
|
+
} from './triage.mjs';
|
|
60
|
+
import type {
|
|
61
|
+
CycleResult,
|
|
62
|
+
Demotions,
|
|
63
|
+
IssueOrder,
|
|
64
|
+
LoopState,
|
|
65
|
+
PullRequest,
|
|
66
|
+
SkipRecord,
|
|
67
|
+
SkipRecords,
|
|
68
|
+
Watched,
|
|
69
|
+
WatchOutcome,
|
|
70
|
+
} from './types.mjs';
|
|
71
|
+
import { installStopHandlers, pause, stopRequested } from './util.mjs';
|
|
72
|
+
import { watch } from './watch.mjs';
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Keeps the pull requests labelled `merge-queued` moving, one at a time, by
|
|
76
|
+
* rebasing the one that is out-of-date with the default branch, arming its
|
|
77
|
+
* auto-merge, and waiting for GitHub to merge it.
|
|
78
|
+
*
|
|
79
|
+
* The `/unblock-prs` skill fixes what this script sets aside — a failing check
|
|
80
|
+
* that needs reading, a conflict — after claiming it, so that this script
|
|
81
|
+
* leaves it alone meanwhile (`claims.mts`). This script fixes only the one
|
|
82
|
+
* kind that needs no reading — a fixer's diff, see step 4 — and it never
|
|
83
|
+
* merges anything: auto-merge does that once the checks are green.
|
|
84
|
+
*
|
|
85
|
+
* One cycle:
|
|
86
|
+
*
|
|
87
|
+
* 1. List the open pull requests labelled `merge-queued` whose base is the
|
|
88
|
+
* default branch. Everything else is not this script's business: an
|
|
89
|
+
* unlabelled pull request is passed over in silence, and a labelled one
|
|
90
|
+
* that cannot be acted on — a draft, a base that is neither the default
|
|
91
|
+
* branch nor another open pull request's — is reported, because the label
|
|
92
|
+
* asked for something and the answer is no. A stacked one waits for the
|
|
93
|
+
* layer below it (`stack.mts`). Auto-merge is this script's to arm, when
|
|
94
|
+
* it picks a pull request (`armOnPick` in `rebase.mts`), whether or not
|
|
95
|
+
* it was armed before: the label is the only permission read. Drafts and
|
|
96
|
+
* anything a previous cycle gave up on are set aside, and so is one whose
|
|
97
|
+
* review holds its merge — a code owner has not approved it, or a
|
|
98
|
+
* conversation is unresolved — because no check reports that, and
|
|
99
|
+
* releasing it would only run a matrix to sit green. One whose waiting
|
|
100
|
+
* paths only its author owns is the exception, since no approval will
|
|
101
|
+
* ever come: it is released and watched with auto-merge left off, and
|
|
102
|
+
* once its checks are green it is set aside `awaiting-bypass` for a
|
|
103
|
+
* person to merge by a ruleset bypass (`pr-report-core`'s
|
|
104
|
+
* `review-hold.mts`). The one thing done without the label comes first:
|
|
105
|
+
* a layer GitHub moved onto the default branch when the one below it
|
|
106
|
+
* merged, still carrying that one's commits, is rebased off them and the
|
|
107
|
+
* list read again (`stack.mts`).
|
|
108
|
+
* 2. If one of them is already up to date and its checks are running, or it
|
|
109
|
+
* is clean and about to merge, watch that one instead of rebasing another:
|
|
110
|
+
* the merge will move `main` and put every other branch back to `BEHIND`,
|
|
111
|
+
* so a second rebase now would only run a CI matrix to throw it away.
|
|
112
|
+
* 3. Otherwise take the lowest-numbered pull request that is `BEHIND` —
|
|
113
|
+
* or, once those are exhausted, one GitHub calls `DIRTY`, then the version
|
|
114
|
+
* pull request, then one a watch saw sit green — rebase it onto
|
|
115
|
+
* `origin/<default branch>` in a throwaway worktree, and push with
|
|
116
|
+
* `--force-with-lease` against the head the survey saw, carrying the
|
|
117
|
+
* layers stacked on it along. A rebase that conflicts, or a push that is
|
|
118
|
+
* refused, drops that pull request for as long as its head and the base
|
|
119
|
+
* stay where they are, and the next
|
|
120
|
+
* candidate is tried in the same cycle. A branch that moved under the
|
|
121
|
+
* rebase is not one of those: someone else — the skill, or a person — is
|
|
122
|
+
* moving it, so nothing is recorded and the cycle surveys again rather
|
|
123
|
+
* than start a second matrix on the next candidate.
|
|
124
|
+
* When there is no candidate and nothing in flight, a pull request paused
|
|
125
|
+
* by `skip-ci` and labelled `auto-rebase` or `merge-queued` that is behind
|
|
126
|
+
* is rebased onto the tip instead, with its layers — under the label the
|
|
127
|
+
* push runs no checks, and it keeps the diff under review its own. Anything
|
|
128
|
+
* that moved sends the cycle back to the survey.
|
|
129
|
+
* 4. Poll the rebased pull request until it merges, or until something says
|
|
130
|
+
* it will not: a required check failed, auto-merge was switched off, the
|
|
131
|
+
* `skip-ci` label went on, the branch was pushed by someone else, or every
|
|
132
|
+
* required context reported
|
|
133
|
+
* green and it still did not merge. "Green" means the whole list the
|
|
134
|
+
* ruleset requires, not the part of it that has reported — a required
|
|
135
|
+
* context with no check run on the head commit is absent from the
|
|
136
|
+
* head's checks rather than pending in them, so reading its absence as
|
|
137
|
+
* success is what used to write a pull request off three minutes into a
|
|
138
|
+
* twenty-five minute matrix.
|
|
139
|
+
* When a required check failed and every job that failed under it is a
|
|
140
|
+
* `fix:` or `gen:` entry of the check matrices, run those commands, amend
|
|
141
|
+
* what they wrote onto the branch's one commit, push, and watch the new
|
|
142
|
+
* head instead — once, and only for the pull request this cycle is
|
|
143
|
+
* watching, never the others that are failing.
|
|
144
|
+
* 5. Remember the verdict against the head *and* the base it was reached on,
|
|
145
|
+
* so the pull request is left alone until someone pushes to it or the
|
|
146
|
+
* base moves, and say so in its one set-aside comment: a person ticks the
|
|
147
|
+
* retry box there to have it tried anyway, the comment is rewritten as
|
|
148
|
+
* resolved once the verdict ends, and a later run takes up a verdict it
|
|
149
|
+
* finds still standing (`settleSkips`). A pull request that sat green
|
|
150
|
+
* without merging is also picked last from then on, until someone else
|
|
151
|
+
* pushes to it: the base moving makes it `BEHIND` and a candidate again,
|
|
152
|
+
* but whatever held it is something triage could not read, and it would
|
|
153
|
+
* otherwise go first each time anything else merged.
|
|
154
|
+
* 6. Survey again, saying what became of the pull requests this run has
|
|
155
|
+
* touched. One that merges after the watch gave up simply stops appearing
|
|
156
|
+
* in the list, and this is the only place that gets recorded. When there
|
|
157
|
+
* is nothing to do the script sleeps before looking again —
|
|
158
|
+
* `--active-interval` seconds while the list keeps changing, so a pull
|
|
159
|
+
* request queued a moment ago is not left for minutes, and
|
|
160
|
+
* `--idle-interval` once `--idle-after` surveys in a row have seen the
|
|
161
|
+
* same list — and keeps going until it is interrupted or `--once` was
|
|
162
|
+
* given.
|
|
163
|
+
*
|
|
164
|
+
* The rebase happens in a `git worktree` under the OS temp directory, so the
|
|
165
|
+
* checkout this runs from is never touched — its working tree may be dirty,
|
|
166
|
+
* and the pull request's branch may even be checked out somewhere else.
|
|
167
|
+
*
|
|
168
|
+
* ## The order, and `skip-ci`
|
|
169
|
+
*
|
|
170
|
+
* Five things the pull requests themselves declare shape that loop.
|
|
171
|
+
*
|
|
172
|
+
* - **A base that is another open pull request's branch** stacks the pull
|
|
173
|
+
* request on that one: it is not picked while that one is open, exactly as
|
|
174
|
+
* if it had declared `Merge-After:` on it. `stack.mts` says what happens
|
|
175
|
+
* when the layer below moves, and when it merges.
|
|
176
|
+
* - **`Merge-After: #1234` in the body** adds an ordering constraint: the
|
|
177
|
+
* pull request is not *picked* while any pull request it names is still
|
|
178
|
+
* open. It constrains picking and nothing else — one that is already up to
|
|
179
|
+
* date and merging is watched as ever, because auto-merge is going to merge
|
|
180
|
+
* it whatever this script thinks, and passing it over would only send the
|
|
181
|
+
* cycle off to rebase a branch that merge is about to invalidate. Several
|
|
182
|
+
* numbers may be named, on one line or several, so the declarations form a
|
|
183
|
+
* graph rather than a chain; a cycle in it is reported by name and
|
|
184
|
+
* everything caught in it is left alone, because nothing here can move it.
|
|
185
|
+
* - **The `skip-ci` label** pauses a queued pull request rather than removing
|
|
186
|
+
* it: every check workflow skips while it is on and `no-skip-ci-label`
|
|
187
|
+
* holds the merge, so taking it off is the action, and it is taken off one
|
|
188
|
+
* pull request at a time. The rebase comes *first* and the label removal
|
|
189
|
+
* *second*: while the label is on, the push fires a `synchronize` whose
|
|
190
|
+
* every check is skipped, and taking the label off then fires `unlabeled`,
|
|
191
|
+
* which runs the matrix once, on the head that will actually be merged. The
|
|
192
|
+
* other order starts a full matrix on the pre-rebase head and has the push
|
|
193
|
+
* cancel it.
|
|
194
|
+
* - **The `blocks-release` label** says the next release must contain this
|
|
195
|
+
* pull request: while it is open the version pull request — the one
|
|
196
|
+
* `changesets/action` opens from `changeset-release/<base>` — is not
|
|
197
|
+
* picked. It exists because that is the one pull request whose body cannot
|
|
198
|
+
* declare anything: its title and body are overwritten on every push to the
|
|
199
|
+
* base, so a `Merge-After:` written there is wiped exactly while several
|
|
200
|
+
* queued pull requests made it matter. The same branch is rebuilt from the
|
|
201
|
+
* tip and force-pushed by the same workflow, so nothing here rebases it
|
|
202
|
+
* either: taking `skip-ci` off, once the release workflow has rebuilt it on
|
|
203
|
+
* the current tip, is all it is ever given, and it is picked last so that a
|
|
204
|
+
* queued change goes into the release rather than after it.
|
|
205
|
+
* - **The `auto-rebase` label** asks for a pull request paused by `skip-ci`
|
|
206
|
+
* to be kept on the tip while it waits, as a queued one is (step 3). It is
|
|
207
|
+
* opt-in so that a branch left to sleep is never moved for want of a label.
|
|
208
|
+
*
|
|
209
|
+
* Releasing one is as far as it goes. From there it is an ordinary queued
|
|
210
|
+
* pull request, and if its checks fail it is set aside like any other — with
|
|
211
|
+
* its `skip-ci` off. Everything that declared `Merge-After` on it then waits,
|
|
212
|
+
* because it has not merged, which is what a declared order is for.
|
|
213
|
+
*
|
|
214
|
+
* **One queued pull request is released at a time, from this side only.**
|
|
215
|
+
* Nothing is picked while one is in flight, and after releasing it looks
|
|
216
|
+
* again: when another the worker owns was released in the same moment and
|
|
217
|
+
* goes first, it gives back the `skip-ci` it has just taken off its own. It
|
|
218
|
+
* puts the label on no other pull request (`release.mts`).
|
|
219
|
+
*
|
|
220
|
+
* ## Where the rest of it is
|
|
221
|
+
*
|
|
222
|
+
* This file is the loop and the command line. Reading order, roughly outside
|
|
223
|
+
* in:
|
|
224
|
+
*
|
|
225
|
+
* - `triage.mts` — what one survey says about each pull request, and why.
|
|
226
|
+
* - `pr-report-core`'s `review-hold.mts` — whether a pull request's own review
|
|
227
|
+
* holds its merge.
|
|
228
|
+
* - `merge-after.mts` — the declared order: the trailer parser and the cycle
|
|
229
|
+
* detection.
|
|
230
|
+
* - `stack.mts` — stacked pull requests: when they wait, when they are armed,
|
|
231
|
+
* and what moves with them.
|
|
232
|
+
* - `version-pr.mts` — the version pull request, and what holds it back.
|
|
233
|
+
* - `rebase.mts` — moving a branch: the rebase in a throwaway worktree, and
|
|
234
|
+
* taking `skip-ci` off.
|
|
235
|
+
* - `release.mts` — holding the queue to one released pull request.
|
|
236
|
+
* - `watch.mts` — polling one pull request until it merges, or until it will
|
|
237
|
+
* not.
|
|
238
|
+
* - `auto-fix.mts` — running the fixer a failed check names, and pushing
|
|
239
|
+
* what it wrote.
|
|
240
|
+
* - `quiet.mts` — how long to sleep when there is nothing to do.
|
|
241
|
+
* - `checks.mts` — what the merge is waiting for, judged against the contexts
|
|
242
|
+
* the ruleset requires.
|
|
243
|
+
* - `github.mts` — everything that reads or writes GitHub or `git`, and
|
|
244
|
+
* nothing that decides. The API client, the runner and the quoting are
|
|
245
|
+
* `tools/scripts/lib/`'s.
|
|
246
|
+
* - `labels.mts`, `skips.mts`, `demotions.mts`, `options.mts`, `types.mts`,
|
|
247
|
+
* `constants.mts`, `util.mts` — the vocabulary.
|
|
248
|
+
*/
|
|
249
|
+
export const unblockPrs = async (
|
|
250
|
+
options: Options,
|
|
251
|
+
): Promise<Result<undefined, string>> => {
|
|
252
|
+
const preflight = await checkPreflight(options.repo);
|
|
253
|
+
|
|
254
|
+
if (Result.isErr(preflight)) {
|
|
255
|
+
return preflight;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const { api, defaultBranch } = preflight.value;
|
|
259
|
+
|
|
260
|
+
const config = await readConfig();
|
|
261
|
+
|
|
262
|
+
if (Result.isErr(config)) {
|
|
263
|
+
return config;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const autoFixConfig = options.autoFix ? config.value?.autoFix : undefined;
|
|
267
|
+
|
|
268
|
+
if (autoFixConfig === undefined && options.autoFix) {
|
|
269
|
+
log(
|
|
270
|
+
`No autoFix in ${CONFIG_PATH}: a failed check is reported, never fixed here.`,
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
log(
|
|
275
|
+
`Watching pull requests into ${defaultBranch}${options.dryRun ? ' (dry run)' : ''}.`,
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
installStopHandlers();
|
|
279
|
+
|
|
280
|
+
let mut_state: LoopState = {
|
|
281
|
+
skipped: new Map(),
|
|
282
|
+
demoted: new Map(),
|
|
283
|
+
tracked: new Set(),
|
|
284
|
+
baseSha: undefined,
|
|
285
|
+
quiet: initialQuiet,
|
|
286
|
+
stackedOn: undefined,
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
while (!stopRequested()) {
|
|
290
|
+
const cycle = await runCycle(
|
|
291
|
+
api,
|
|
292
|
+
defaultBranch,
|
|
293
|
+
mut_state,
|
|
294
|
+
options,
|
|
295
|
+
autoFixConfig,
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
if (!options.dryRun) {
|
|
299
|
+
await announceSetAside(
|
|
300
|
+
api,
|
|
301
|
+
newSkips(cycle.settled, cycle.state.skipped),
|
|
302
|
+
defaultBranch,
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
mut_state = cycle.state;
|
|
307
|
+
|
|
308
|
+
if (cycle.next === 'stop' || options.once || options.dryRun) {
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (cycle.next !== 'idle') {
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const waitSec = idleWaitSec(mut_state.quiet, options);
|
|
317
|
+
|
|
318
|
+
log(`Nothing to do. Checking again in ${waitSec}s.`);
|
|
319
|
+
|
|
320
|
+
await pause(waitSec * 1000);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return Result.ok(undefined);
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Says on each newly set-aside pull request why, where GitHub keeps it, so
|
|
328
|
+
* that finding out does not mean finding the terminal this ran in: in its one
|
|
329
|
+
* set-aside comment, written over the one it has or posted as its first. A
|
|
330
|
+
* comment that could not be written is reported and nothing more: the job is
|
|
331
|
+
* to land pull requests, and this is not a reason to stop.
|
|
332
|
+
*/
|
|
333
|
+
const announceSetAside = async (
|
|
334
|
+
api: ApiContext,
|
|
335
|
+
skips: readonly SkipRecord[],
|
|
336
|
+
defaultBranch: string,
|
|
337
|
+
): Promise<void> => {
|
|
338
|
+
for (const skip of skips) {
|
|
339
|
+
const existing = await readSetAsideComments(api, [skip.number]);
|
|
340
|
+
|
|
341
|
+
const written = Result.isErr(existing)
|
|
342
|
+
? existing
|
|
343
|
+
: await writeSetAsideCommentOn(
|
|
344
|
+
api,
|
|
345
|
+
skip.number,
|
|
346
|
+
existing.value.get(skip.number)?.databaseId,
|
|
347
|
+
setAsideCommentBody(skip, defaultBranch),
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
if (Result.isErr(written)) {
|
|
351
|
+
log(
|
|
352
|
+
`#${skip.number}: could not leave a comment saying why: ${written.value}`,
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Reads this account's set-aside comments on the pull requests that are queued or
|
|
360
|
+
* ask for `auto-rebase`, and on
|
|
361
|
+
* any other this run holds a record for, and settles the records with them
|
|
362
|
+
* (`settleSkips`): a comment whose state has ended, or whose retry box a
|
|
363
|
+
* person ticked, is rewritten as resolved — not under `--dry-run` — and one
|
|
364
|
+
* an earlier run left standing becomes a record again. Comments that cannot
|
|
365
|
+
* be read leave the records as they were.
|
|
366
|
+
*
|
|
367
|
+
* The claims sessions wrote on the same pull requests are read with them,
|
|
368
|
+
* and those that still hold are returned beside the records (`claims.mts`).
|
|
369
|
+
* Comments that cannot be read hold nothing: every push is leased anyway.
|
|
370
|
+
*/
|
|
371
|
+
const settleWithComments = async (
|
|
372
|
+
api: ApiContext,
|
|
373
|
+
skipped: SkipRecords,
|
|
374
|
+
pullRequests: readonly PullRequest[],
|
|
375
|
+
baseSha: string,
|
|
376
|
+
defaultBranch: string,
|
|
377
|
+
dryRun: boolean,
|
|
378
|
+
): Promise<
|
|
379
|
+
Readonly<{ skipped: SkipRecords; claimed: ReadonlyMap<number, Claim> }>
|
|
380
|
+
> => {
|
|
381
|
+
const read = await readOwnComments(
|
|
382
|
+
api,
|
|
383
|
+
pullRequests
|
|
384
|
+
.filter((pr) => wantsAutoRebase(pr) || skipped.has(pr.number))
|
|
385
|
+
.map((pr) => pr.number),
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
if (Result.isErr(read)) {
|
|
389
|
+
log(`Cannot read the set-aside comments: ${read.value}`);
|
|
390
|
+
|
|
391
|
+
return { skipped, claimed: new Map() };
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const settled = settleSkips(
|
|
395
|
+
skipped,
|
|
396
|
+
read.value.setAside,
|
|
397
|
+
pullRequests,
|
|
398
|
+
baseSha,
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
for (const { number, databaseId, setAside, resolvedBy } of settled.resolved) {
|
|
402
|
+
log(
|
|
403
|
+
`#${number}: no longer set aside (${setAside.reason}) — ${describeResolvedBy(resolvedBy, defaultBranch)}${resolvedBy === 'retry' ? '; trying it again' : ''}.`,
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
if (dryRun) {
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const written = await writeSetAsideCommentOn(
|
|
411
|
+
api,
|
|
412
|
+
number,
|
|
413
|
+
databaseId,
|
|
414
|
+
resolvedCommentBody(
|
|
415
|
+
setAside,
|
|
416
|
+
resolvedBy,
|
|
417
|
+
defaultBranch,
|
|
418
|
+
currentEpochMs(),
|
|
419
|
+
),
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
if (Result.isErr(written)) {
|
|
423
|
+
log(`#${number}: could not mark its comment resolved: ${written.value}`);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// A record whose comment never got written is one every other reader of
|
|
428
|
+
// the queue takes for the worker's while this run holds it aside.
|
|
429
|
+
const unannounced = unannouncedSkips(settled.skipped, read.value.setAside);
|
|
430
|
+
|
|
431
|
+
if (!dryRun && Arr.isNonEmpty(unannounced)) {
|
|
432
|
+
log(
|
|
433
|
+
`Writing the set-aside comment again on ${unannounced.map(({ number }) => `#${number}`).join(', ')}, which no comment says.`,
|
|
434
|
+
);
|
|
435
|
+
|
|
436
|
+
await announceSetAside(api, unannounced, defaultBranch);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return {
|
|
440
|
+
skipped: settled.skipped,
|
|
441
|
+
claimed: standingClaims(read.value.claims, pullRequests, currentEpochMs()),
|
|
442
|
+
};
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
const runCycle = async (
|
|
446
|
+
api: ApiContext,
|
|
447
|
+
defaultBranch: string,
|
|
448
|
+
before: LoopState,
|
|
449
|
+
options: Options,
|
|
450
|
+
autoFixConfig: AutoFixConfig | undefined,
|
|
451
|
+
): Promise<CycleResult> => {
|
|
452
|
+
const listed = await survey(api, defaultBranch);
|
|
453
|
+
|
|
454
|
+
if (Result.isErr(listed)) {
|
|
455
|
+
log(`Survey failed: ${listed.value}`);
|
|
456
|
+
|
|
457
|
+
return { state: before, next: 'idle', settled: before.skipped };
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// What the next cycle compares its bases with. From this survey rather
|
|
461
|
+
// than the one below, which a layer moving in between could skip.
|
|
462
|
+
const stackedOn = stackedOnAfter(
|
|
463
|
+
before.stackedOn,
|
|
464
|
+
listed.value.pullRequests,
|
|
465
|
+
stackParentsOf(listed.value.pullRequests, defaultBranch),
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
// A layer the one below merged from under goes first, labelled or not, and
|
|
469
|
+
// the heads read before it are then read again. See `stack.mts`.
|
|
470
|
+
const surveyed = (await restackRetargeted(
|
|
471
|
+
api,
|
|
472
|
+
before.stackedOn,
|
|
473
|
+
listed.value.pullRequests,
|
|
474
|
+
defaultBranch,
|
|
475
|
+
options.dryRun,
|
|
476
|
+
))
|
|
477
|
+
? await survey(api, defaultBranch)
|
|
478
|
+
: listed;
|
|
479
|
+
|
|
480
|
+
if (Result.isErr(surveyed)) {
|
|
481
|
+
log(`Survey failed: ${surveyed.value}`);
|
|
482
|
+
|
|
483
|
+
return { state: before, next: 'idle', settled: before.skipped };
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const { pullRequests, baseSha, requiredContexts, reviewRequirements } =
|
|
487
|
+
surveyed.value;
|
|
488
|
+
|
|
489
|
+
if (before.baseSha !== undefined && before.baseSha !== baseSha) {
|
|
490
|
+
log(`${defaultBranch} moved to ${baseSha.slice(0, 10)}.`);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// A pull request this run acted on and can no longer see is one that ended
|
|
494
|
+
// while nothing was watching it — including one the watch had given up on,
|
|
495
|
+
// whose merge would otherwise go unrecorded.
|
|
496
|
+
const tracked = await reportDeparted(api, before.tracked, pullRequests);
|
|
497
|
+
|
|
498
|
+
const { skipped, claimed } = await settleWithComments(
|
|
499
|
+
api,
|
|
500
|
+
pruneSkips(before.skipped, pullRequests, baseSha),
|
|
501
|
+
pullRequests,
|
|
502
|
+
baseSha,
|
|
503
|
+
defaultBranch,
|
|
504
|
+
options.dryRun,
|
|
505
|
+
);
|
|
506
|
+
|
|
507
|
+
const demoted = pruneDemotions(before.demoted, pullRequests);
|
|
508
|
+
|
|
509
|
+
const triaged = await triage(api, pullRequests, {
|
|
510
|
+
defaultBranch,
|
|
511
|
+
baseSha,
|
|
512
|
+
skipped,
|
|
513
|
+
claimed,
|
|
514
|
+
demoted,
|
|
515
|
+
requiredContexts,
|
|
516
|
+
reviewRequirements,
|
|
517
|
+
issueOrder: await issueOrderFor(api, pullRequests),
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
reportTriage(triaged, pullRequests.length, { defaultBranch, demoted });
|
|
521
|
+
|
|
522
|
+
const quiet = observeSurvey(
|
|
523
|
+
before.quiet,
|
|
524
|
+
surveyFingerprint({ pullRequests, baseSha }, triaged.held),
|
|
525
|
+
);
|
|
526
|
+
|
|
527
|
+
const state = (
|
|
528
|
+
nextSkipped: SkipRecords,
|
|
529
|
+
nextDemoted: Demotions = demoted,
|
|
530
|
+
nextTracked: ReadonlySet<number> = tracked,
|
|
531
|
+
): LoopState =>
|
|
532
|
+
({
|
|
533
|
+
skipped: nextSkipped,
|
|
534
|
+
demoted: nextDemoted,
|
|
535
|
+
tracked: nextTracked,
|
|
536
|
+
baseSha,
|
|
537
|
+
quiet,
|
|
538
|
+
stackedOn,
|
|
539
|
+
}) as const;
|
|
540
|
+
|
|
541
|
+
// A pull request that is up to date and already failing gets remembered
|
|
542
|
+
// now, so that it is not rebased the moment `main` moves and put through
|
|
543
|
+
// the same failing matrix again.
|
|
544
|
+
let mut_skipped: SkipRecords = triaged.failing.reduce(
|
|
545
|
+
(acc, { pr, summary }) =>
|
|
546
|
+
withSkip(acc, {
|
|
547
|
+
number: pr.number,
|
|
548
|
+
headSha: pr.headRefOid,
|
|
549
|
+
baseSha,
|
|
550
|
+
reason: 'checks-failed',
|
|
551
|
+
...describeFailedChecks(summary),
|
|
552
|
+
}),
|
|
553
|
+
skipped,
|
|
554
|
+
);
|
|
555
|
+
|
|
556
|
+
if (stopRequested()) {
|
|
557
|
+
return { state: state(mut_skipped), next: 'stop', settled: skipped };
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Normally the only one. Two are what a pull request opened already
|
|
561
|
+
// released looks like; the first is watched, and the other runs as it was
|
|
562
|
+
// released, since nothing here labels a pull request it did not pick.
|
|
563
|
+
const watched = firstInReleaseOrder(triaged.inFlight, defaultBranch);
|
|
564
|
+
|
|
565
|
+
if (watched !== undefined) {
|
|
566
|
+
const arm = triaged.toArm.has(watched.number);
|
|
567
|
+
|
|
568
|
+
const bypassReason = triaged.bypass.get(watched.number);
|
|
569
|
+
|
|
570
|
+
log(
|
|
571
|
+
`#${watched.number} is up to date (${watched.mergeStateStatus}); ${arm ? 'arming auto-merge and ' : ''}watching it rather than rebasing another${bypassPhrase(bypassReason)}.`,
|
|
572
|
+
);
|
|
573
|
+
|
|
574
|
+
if (options.dryRun) {
|
|
575
|
+
return { state: state(mut_skipped), next: 'stop', settled: skipped };
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Before the watch, which reads a pull request without auto-merge as one
|
|
579
|
+
// someone switched it off on.
|
|
580
|
+
if (arm) {
|
|
581
|
+
const armed = await armOnPick(
|
|
582
|
+
api,
|
|
583
|
+
watched,
|
|
584
|
+
watched.headRefOid,
|
|
585
|
+
defaultBranch,
|
|
586
|
+
);
|
|
587
|
+
|
|
588
|
+
if (Result.isErr(armed)) {
|
|
589
|
+
log(`#${watched.number}: ${armed.value.detail}`);
|
|
590
|
+
|
|
591
|
+
return {
|
|
592
|
+
state: state(
|
|
593
|
+
withSkip(mut_skipped, {
|
|
594
|
+
number: watched.number,
|
|
595
|
+
headSha: watched.headRefOid,
|
|
596
|
+
baseSha,
|
|
597
|
+
reason: armed.value.reason,
|
|
598
|
+
detail: armed.value.detail,
|
|
599
|
+
}),
|
|
600
|
+
),
|
|
601
|
+
next: 'survey',
|
|
602
|
+
settled: skipped,
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
if (armed.value === 'moved') {
|
|
607
|
+
log(
|
|
608
|
+
`#${watched.number}: its branch moved before it was armed; surveying again.`,
|
|
609
|
+
);
|
|
610
|
+
|
|
611
|
+
return { state: state(mut_skipped), next: 'survey', settled: skipped };
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
const { ended, head } = await watchAndFix(
|
|
616
|
+
api,
|
|
617
|
+
watched,
|
|
618
|
+
watched.headRefOid,
|
|
619
|
+
{ defaultBranch, requiredContexts, bypassReason, autoFixConfig },
|
|
620
|
+
options,
|
|
621
|
+
);
|
|
622
|
+
|
|
623
|
+
return {
|
|
624
|
+
state: state(
|
|
625
|
+
applyWatchOutcome(
|
|
626
|
+
mut_skipped,
|
|
627
|
+
{ ...watched, headRefOid: head },
|
|
628
|
+
baseSha,
|
|
629
|
+
ended,
|
|
630
|
+
),
|
|
631
|
+
afterWatch(demoted, watched.number, head, ended.outcome),
|
|
632
|
+
trackAfterWatch(tracked, watched.number, ended.outcome),
|
|
633
|
+
),
|
|
634
|
+
next: ended.outcome === 'stopped' ? 'stop' : 'survey',
|
|
635
|
+
settled: skipped,
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Only when the queue has nothing to do: `main` is about to move again
|
|
640
|
+
// otherwise, and a push now would be undone by the next merge.
|
|
641
|
+
if (Arr.isEmpty(triaged.candidates)) {
|
|
642
|
+
const behind = await restackBehind(pullRequests, defaultBranch, {
|
|
643
|
+
baseSha,
|
|
644
|
+
skipped: mut_skipped,
|
|
645
|
+
owners: triaged.owners,
|
|
646
|
+
dryRun: options.dryRun,
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
mut_skipped = behind.skipped;
|
|
650
|
+
|
|
651
|
+
if (behind.moved) {
|
|
652
|
+
return { state: state(mut_skipped), next: 'survey', settled: skipped };
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
if (options.dryRun) {
|
|
657
|
+
if (Arr.isNonEmpty(triaged.candidates)) {
|
|
658
|
+
const first = triaged.candidates[0];
|
|
659
|
+
|
|
660
|
+
log(
|
|
661
|
+
`Would ${describeAction(first, defaultBranch, triaged)} for #${first.number}.`,
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
return { state: state(mut_skipped), next: 'stop', settled: skipped };
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// What a release found afterwards can have happened in the same moment
|
|
669
|
+
// only for these; see `release.mts`.
|
|
670
|
+
const pausedAtSurvey: ReadonlySet<number> = new Set(
|
|
671
|
+
pullRequests.filter(isSkipCiLabelled).map((pr) => pr.number),
|
|
672
|
+
);
|
|
673
|
+
|
|
674
|
+
for (const target of triaged.candidates) {
|
|
675
|
+
if (stopRequested()) {
|
|
676
|
+
return { state: state(mut_skipped), next: 'stop', settled: skipped };
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
log(
|
|
680
|
+
`#${target.number} (${target.headRefName}) is next: ${describeAction(target, defaultBranch, triaged)}.`,
|
|
681
|
+
);
|
|
682
|
+
|
|
683
|
+
const advanced = await advance(api, target, defaultBranch, {
|
|
684
|
+
arm: triaged.toArm.has(target.number),
|
|
685
|
+
descendants: stackDescendants(
|
|
686
|
+
triaged.stackParents,
|
|
687
|
+
target.number,
|
|
688
|
+
).flatMap((number) => pullRequests.filter((pr) => pr.number === number)),
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
if (Result.isErr(advanced)) {
|
|
692
|
+
log(`#${target.number}: ${advanced.value.detail}`);
|
|
693
|
+
|
|
694
|
+
mut_skipped = withSkip(mut_skipped, {
|
|
695
|
+
number: target.number,
|
|
696
|
+
headSha: target.headRefOid,
|
|
697
|
+
baseSha,
|
|
698
|
+
reason: advanced.value.reason,
|
|
699
|
+
detail: advanced.value.detail,
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
if (advanced.value.kind === 'moved') {
|
|
706
|
+
// Someone else is moving this one — the skill, or a person. Whatever
|
|
707
|
+
// they pushed is likely to start a matrix, so rather than start a
|
|
708
|
+
// second on the next candidate, survey again and take what is there.
|
|
709
|
+
log(
|
|
710
|
+
`#${target.number} moved while this run was moving it; leaving it to whoever pushed, and surveying again.`,
|
|
711
|
+
);
|
|
712
|
+
|
|
713
|
+
return { state: state(mut_skipped), next: 'survey', settled: skipped };
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
if (advanced.value.kind === 'stale-merge-state') {
|
|
717
|
+
// GitHub said BEHIND or DIRTY but the rebase changed nothing, and there
|
|
718
|
+
// was no label to take off either — the merge state was stale. The next
|
|
719
|
+
// survey will say what is actually there; wait first, because a state
|
|
720
|
+
// GitHub is still recomputing would otherwise send this straight back
|
|
721
|
+
// here, and each turn costs a fetch and a worktree.
|
|
722
|
+
log(
|
|
723
|
+
`#${target.number} was already on top of ${defaultBranch}; its ${target.mergeStateStatus} was stale.`,
|
|
724
|
+
);
|
|
725
|
+
|
|
726
|
+
await pause(STALE_STATE_PAUSE_MS);
|
|
727
|
+
|
|
728
|
+
return { state: state(mut_skipped), next: 'survey', settled: skipped };
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
const advancedHead = advanced.value.head;
|
|
732
|
+
|
|
733
|
+
// The rebase is this run's, not the author's, so a demotion goes with it.
|
|
734
|
+
const demotedNow = followHead(demoted, target.number, advancedHead);
|
|
735
|
+
|
|
736
|
+
if (
|
|
737
|
+
(await settleAfterRelease(
|
|
738
|
+
api,
|
|
739
|
+
target,
|
|
740
|
+
triaged.owners,
|
|
741
|
+
pausedAtSurvey,
|
|
742
|
+
defaultBranch,
|
|
743
|
+
)) === 'yielded'
|
|
744
|
+
) {
|
|
745
|
+
log(
|
|
746
|
+
`#${target.number} was released at the same moment as another, which goes first; paused it again.`,
|
|
747
|
+
);
|
|
748
|
+
|
|
749
|
+
return {
|
|
750
|
+
state: state(mut_skipped, demotedNow),
|
|
751
|
+
next: 'survey',
|
|
752
|
+
settled: skipped,
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
log(
|
|
757
|
+
`#${target.number} is at ${advancedHead.slice(0, 10)}; waiting for it to merge.`,
|
|
758
|
+
);
|
|
759
|
+
|
|
760
|
+
const { ended, head } = await watchAndFix(
|
|
761
|
+
api,
|
|
762
|
+
target,
|
|
763
|
+
advancedHead,
|
|
764
|
+
{
|
|
765
|
+
defaultBranch,
|
|
766
|
+
requiredContexts,
|
|
767
|
+
bypassReason: triaged.bypass.get(target.number),
|
|
768
|
+
autoFixConfig,
|
|
769
|
+
},
|
|
770
|
+
options,
|
|
771
|
+
);
|
|
772
|
+
|
|
773
|
+
return {
|
|
774
|
+
state: state(
|
|
775
|
+
applyWatchOutcome(
|
|
776
|
+
mut_skipped,
|
|
777
|
+
{ ...target, headRefOid: head },
|
|
778
|
+
baseSha,
|
|
779
|
+
ended,
|
|
780
|
+
),
|
|
781
|
+
afterWatch(demotedNow, target.number, head, ended.outcome),
|
|
782
|
+
trackAfterWatch(tracked, target.number, ended.outcome),
|
|
783
|
+
),
|
|
784
|
+
next: ended.outcome === 'stopped' ? 'stop' : 'survey',
|
|
785
|
+
settled: skipped,
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// Nothing to act on, or every candidate failed to rebase or push.
|
|
790
|
+
return { state: state(mut_skipped), next: 'idle', settled: skipped };
|
|
791
|
+
};
|
|
792
|
+
|
|
793
|
+
/**
|
|
794
|
+
* Watches one pull request, and when its checks fail on nothing but a fixer's
|
|
795
|
+
* diff, has `autoFix` push the fix and watches the new head in its place.
|
|
796
|
+
* Resolves to the outcome and the head it was reached on, which is the one a
|
|
797
|
+
* skip record has to name.
|
|
798
|
+
*
|
|
799
|
+
* Once per watch: a head `autoFix` wrote that fails again is a fixer that
|
|
800
|
+
* does not agree with CI, and running it again would only push the same diff.
|
|
801
|
+
* The pull request is set aside as any other failure is, and nothing else
|
|
802
|
+
* failing in the queue is touched — the one being watched is the one this
|
|
803
|
+
* cycle picked.
|
|
804
|
+
*/
|
|
805
|
+
const watchAndFix = async (
|
|
806
|
+
api: ApiContext,
|
|
807
|
+
pr: PullRequest,
|
|
808
|
+
expectedHead: string,
|
|
809
|
+
context: Readonly<{
|
|
810
|
+
defaultBranch: string;
|
|
811
|
+
requiredContexts: readonly string[];
|
|
812
|
+
/** Set for one only a ruleset bypass merges; see `watch`. */
|
|
813
|
+
bypassReason: string | undefined;
|
|
814
|
+
/** `undefined` under `--no-auto-fix`, or when the repository has none. */
|
|
815
|
+
autoFixConfig: AutoFixConfig | undefined;
|
|
816
|
+
}>,
|
|
817
|
+
options: Options,
|
|
818
|
+
): Promise<Readonly<{ ended: Watched; head: string }>> => {
|
|
819
|
+
const ended = await watch(
|
|
820
|
+
api,
|
|
821
|
+
pr,
|
|
822
|
+
expectedHead,
|
|
823
|
+
context.requiredContexts,
|
|
824
|
+
options,
|
|
825
|
+
context.bypassReason,
|
|
826
|
+
);
|
|
827
|
+
|
|
828
|
+
if (
|
|
829
|
+
ended.outcome !== 'checks-failed' ||
|
|
830
|
+
context.autoFixConfig === undefined ||
|
|
831
|
+
stopRequested()
|
|
832
|
+
) {
|
|
833
|
+
return { ended, head: expectedHead };
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
const fixed = await autoFix(
|
|
837
|
+
api,
|
|
838
|
+
pr,
|
|
839
|
+
expectedHead,
|
|
840
|
+
context.defaultBranch,
|
|
841
|
+
context.autoFixConfig,
|
|
842
|
+
);
|
|
843
|
+
|
|
844
|
+
switch (fixed.kind) {
|
|
845
|
+
case 'declined':
|
|
846
|
+
log(`#${pr.number}: not fixing it here: ${fixed.detail}`);
|
|
847
|
+
|
|
848
|
+
return { ended, head: expectedHead };
|
|
849
|
+
|
|
850
|
+
case 'moved':
|
|
851
|
+
return { ended: { outcome: 'head-moved' }, head: expectedHead };
|
|
852
|
+
|
|
853
|
+
case 'pushed':
|
|
854
|
+
log(
|
|
855
|
+
`#${pr.number}: pushed what ${fixed.commands.join(', ')} wrote as ${fixed.head.slice(0, 10)}; watching it again.`,
|
|
856
|
+
);
|
|
857
|
+
|
|
858
|
+
return {
|
|
859
|
+
ended: await watch(
|
|
860
|
+
api,
|
|
861
|
+
pr,
|
|
862
|
+
fixed.head,
|
|
863
|
+
context.requiredContexts,
|
|
864
|
+
options,
|
|
865
|
+
context.bypassReason,
|
|
866
|
+
),
|
|
867
|
+
head: fixed.head,
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
};
|
|
871
|
+
|
|
872
|
+
/**
|
|
873
|
+
* A pull request the run has acted on is worth remembering while it might
|
|
874
|
+
* still end without anything watching it; once it has ended there is nothing
|
|
875
|
+
* left to report.
|
|
876
|
+
*/
|
|
877
|
+
const trackAfterWatch = (
|
|
878
|
+
tracked: ReadonlySet<number>,
|
|
879
|
+
prNumber: number,
|
|
880
|
+
outcome: WatchOutcome,
|
|
881
|
+
): ReadonlySet<number> =>
|
|
882
|
+
outcome === 'merged' || outcome === 'closed'
|
|
883
|
+
? new Set(Array.from(tracked).filter((number) => number !== prNumber))
|
|
884
|
+
: new Set([...tracked, prNumber]);
|
|
885
|
+
|
|
886
|
+
/**
|
|
887
|
+
* Says what became of the pull requests this run acted on that have left the
|
|
888
|
+
* open list, and returns the ones that are still on it.
|
|
889
|
+
*
|
|
890
|
+
* Nothing else notices: a pull request the watch gave up on merges minutes
|
|
891
|
+
* later, and the only trace is that it stops being listed. Without this the
|
|
892
|
+
* run that pushed the merge commit never mentions it.
|
|
893
|
+
*/
|
|
894
|
+
const reportDeparted = async (
|
|
895
|
+
api: ApiContext,
|
|
896
|
+
tracked: ReadonlySet<number>,
|
|
897
|
+
pullRequests: readonly PullRequest[],
|
|
898
|
+
): Promise<ReadonlySet<number>> => {
|
|
899
|
+
const isOpen = (prNumber: number): boolean =>
|
|
900
|
+
pullRequests.some((pr) => pr.number === prNumber);
|
|
901
|
+
|
|
902
|
+
const departed = Array.from(tracked).filter((prNumber) => !isOpen(prNumber));
|
|
903
|
+
|
|
904
|
+
const viewed = await Promise.all(
|
|
905
|
+
departed.map(async (prNumber) => ({
|
|
906
|
+
prNumber,
|
|
907
|
+
result: await viewPullRequest(api, prNumber),
|
|
908
|
+
})),
|
|
909
|
+
);
|
|
910
|
+
|
|
911
|
+
for (const { prNumber, result } of viewed) {
|
|
912
|
+
if (Result.isErr(result)) {
|
|
913
|
+
const detail =
|
|
914
|
+
`left the open list; cannot read it: ${result.value}` as const;
|
|
915
|
+
|
|
916
|
+
log(`#${prNumber} has ${detail}`);
|
|
917
|
+
} else if (result.value.state === 'MERGED') {
|
|
918
|
+
log(`#${prNumber} merged — ${result.value.title}`);
|
|
919
|
+
} else {
|
|
920
|
+
const detail = `no longer open (${result.value.state})` as const;
|
|
921
|
+
|
|
922
|
+
log(`#${prNumber} is ${detail} — ${result.value.title}`);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
return new Set(Array.from(tracked).filter(isOpen));
|
|
927
|
+
};
|
|
928
|
+
|
|
929
|
+
/**
|
|
930
|
+
* What the issues the open pull requests close add to the merge order. A
|
|
931
|
+
* read that fails holds nothing extra: the declared order and the stacks
|
|
932
|
+
* still stand, and an order inherited from an issue is the one a person can
|
|
933
|
+
* also write into the body.
|
|
934
|
+
*/
|
|
935
|
+
const issueOrderFor = async (
|
|
936
|
+
api: ApiContext,
|
|
937
|
+
pullRequests: readonly PullRequest[],
|
|
938
|
+
): Promise<IssueOrder> => {
|
|
939
|
+
const read = await readIssueOrder(
|
|
940
|
+
api,
|
|
941
|
+
pullRequests.map(({ number }) => number),
|
|
942
|
+
);
|
|
943
|
+
|
|
944
|
+
if (Result.isErr(read)) {
|
|
945
|
+
log(
|
|
946
|
+
`Cannot read the issues the pull requests close; inheriting no Start-After this cycle. (${read.value})`,
|
|
947
|
+
);
|
|
948
|
+
|
|
949
|
+
return { closes: new Map(), startAfter: new Map() };
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
return read.value;
|
|
953
|
+
};
|
|
954
|
+
|
|
955
|
+
const applyWatchOutcome = (
|
|
956
|
+
skipped: SkipRecords,
|
|
957
|
+
pr: PullRequest,
|
|
958
|
+
baseSha: string,
|
|
959
|
+
ended: Watched,
|
|
960
|
+
): SkipRecords => {
|
|
961
|
+
switch (ended.outcome) {
|
|
962
|
+
case 'merged':
|
|
963
|
+
log(`#${pr.number} merged.`);
|
|
964
|
+
|
|
965
|
+
return skipped;
|
|
966
|
+
|
|
967
|
+
case 'closed':
|
|
968
|
+
log(`#${pr.number} was closed without merging.`);
|
|
969
|
+
|
|
970
|
+
return skipped;
|
|
971
|
+
|
|
972
|
+
case 'auto-merge-disabled':
|
|
973
|
+
log(
|
|
974
|
+
`#${pr.number}: auto-merge was switched off; it is armed again when next picked, unless ${MERGE_QUEUED_LABEL} comes off.`,
|
|
975
|
+
);
|
|
976
|
+
|
|
977
|
+
return skipped;
|
|
978
|
+
|
|
979
|
+
case 'skip-ci-labelled':
|
|
980
|
+
log(
|
|
981
|
+
`#${pr.number}: ${SKIP_CI_LABEL} went on while waiting; leaving it alone.`,
|
|
982
|
+
);
|
|
983
|
+
|
|
984
|
+
return skipped;
|
|
985
|
+
|
|
986
|
+
case 'head-moved':
|
|
987
|
+
log(`#${pr.number}: someone else pushed to the branch; surveying again.`);
|
|
988
|
+
|
|
989
|
+
return skipped;
|
|
990
|
+
|
|
991
|
+
case 'behind-again':
|
|
992
|
+
log(`#${pr.number}: the base moved while waiting; surveying again.`);
|
|
993
|
+
|
|
994
|
+
return skipped;
|
|
995
|
+
|
|
996
|
+
case 'checks-failed':
|
|
997
|
+
return withSkip(skipped, {
|
|
998
|
+
number: pr.number,
|
|
999
|
+
headSha: pr.headRefOid,
|
|
1000
|
+
baseSha,
|
|
1001
|
+
reason: 'checks-failed',
|
|
1002
|
+
detail: ended.detail ?? 'a required check failed',
|
|
1003
|
+
failedChecks: ended.failedChecks,
|
|
1004
|
+
});
|
|
1005
|
+
|
|
1006
|
+
case 'awaiting-bypass':
|
|
1007
|
+
log(
|
|
1008
|
+
`#${pr.number}: green, and left for a ruleset bypass merge; nothing here takes it further until the branch is pushed again or a retry is asked for.`,
|
|
1009
|
+
);
|
|
1010
|
+
|
|
1011
|
+
return withSkip(skipped, {
|
|
1012
|
+
number: pr.number,
|
|
1013
|
+
headSha: pr.headRefOid,
|
|
1014
|
+
baseSha,
|
|
1015
|
+
reason: 'awaiting-bypass',
|
|
1016
|
+
detail: ended.detail ?? 'every required check is green',
|
|
1017
|
+
});
|
|
1018
|
+
|
|
1019
|
+
case 'not-merging':
|
|
1020
|
+
log(
|
|
1021
|
+
`#${pr.number}: every required check reported green and it is still open; something other than the checks holds it that its review does not show — auto-merge armed by someone who may not merge, or a rule this script does not read. It goes last from now on, until someone pushes to it.`,
|
|
1022
|
+
);
|
|
1023
|
+
|
|
1024
|
+
return withSkip(skipped, {
|
|
1025
|
+
number: pr.number,
|
|
1026
|
+
headSha: pr.headRefOid,
|
|
1027
|
+
baseSha,
|
|
1028
|
+
reason: 'not-merging',
|
|
1029
|
+
detail: ended.detail ?? 'green but not merged',
|
|
1030
|
+
});
|
|
1031
|
+
|
|
1032
|
+
case 'timeout':
|
|
1033
|
+
log(`#${pr.number}: gave up waiting.`);
|
|
1034
|
+
|
|
1035
|
+
return withSkip(skipped, {
|
|
1036
|
+
number: pr.number,
|
|
1037
|
+
headSha: pr.headRefOid,
|
|
1038
|
+
baseSha,
|
|
1039
|
+
reason: 'watch-timeout',
|
|
1040
|
+
detail: ended.detail ?? 'checks did not finish in time',
|
|
1041
|
+
});
|
|
1042
|
+
|
|
1043
|
+
case 'error':
|
|
1044
|
+
log(`#${pr.number}: polling kept failing; surveying again later.`);
|
|
1045
|
+
|
|
1046
|
+
return skipped;
|
|
1047
|
+
|
|
1048
|
+
case 'stopped':
|
|
1049
|
+
return skipped;
|
|
1050
|
+
}
|
|
1051
|
+
};
|