pr-shepherd 0.46.7 → 0.47.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/.claude-plugin/plugin.json +1 -1
- package/bin/api.d.mts +1 -1
- package/bin/commands/check-fingerprint.d.mts +8 -0
- package/bin/commands/check-fingerprint.mjs +69 -0
- package/bin/commands/check.d.mts +1 -0
- package/bin/commands/check.mjs +23 -3
- package/bin/commands/iterate/base.mjs +1 -0
- package/bin/commands/iterate/mark-ready.mjs +8 -0
- package/bin/commands/poll-progress.d.mts +11 -0
- package/bin/commands/poll-progress.mjs +52 -0
- package/bin/commands/poll-quota.d.mts +9 -0
- package/bin/commands/poll-quota.mjs +36 -0
- package/bin/commands/poll.mjs +64 -65
- package/bin/config/load.mjs +11 -1
- package/bin/github/batch-raw-rules.d.mts +4 -1
- package/bin/github/batch-raw-types.d.mts +14 -0
- package/bin/github/batch.d.mts +2 -0
- package/bin/github/batch.mjs +2 -0
- package/bin/github/fingerprint-fields.d.mts +58 -0
- package/bin/github/fingerprint-fields.mjs +39 -0
- package/bin/github/fingerprint.d.mts +35 -0
- package/bin/github/fingerprint.mjs +67 -0
- package/bin/github/gql/batch-pr.gql +25 -120
- package/bin/github/gql/commit-check-suites.gql +19 -0
- package/bin/github/gql/pr-fingerprint.gql +75 -0
- package/bin/github/gql/pr-merge-policy.gql +49 -0
- package/bin/github/merge-queue-checks.mjs +57 -30
- package/bin/github/queries.d.mts +2 -0
- package/bin/github/queries.mjs +4 -1
- package/bin/merge-status/requirements-format.mjs +1 -1
- package/bin/state/pr-fingerprint.d.mts +20 -0
- package/bin/state/pr-fingerprint.mjs +90 -0
- package/bin/types/iterate.d.mts +9 -0
- package/bin/types/report.d.mts +2 -0
- package/package.json +2 -2
- package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
- package/plugins/pr-shepherd/.codex.mcp.json +1 -1
- package/plugins/pr-shepherd/.mcp.json +1 -1
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { RawBaseRef } from "./batch-raw-rules.mts";
|
|
2
|
+
export interface FingerprintSuites {
|
|
3
|
+
pageInfo?: {
|
|
4
|
+
hasNextPage: boolean;
|
|
5
|
+
};
|
|
6
|
+
nodes?: Array<{
|
|
7
|
+
id?: string;
|
|
8
|
+
conclusion: string | null;
|
|
9
|
+
workflowRun?: {
|
|
10
|
+
databaseId: number | null;
|
|
11
|
+
} | null;
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
14
|
+
export interface FingerprintComment {
|
|
15
|
+
id: string;
|
|
16
|
+
updatedAt?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function suiteFingerprint(suites: FingerprintSuites | undefined): {
|
|
19
|
+
checkSuiteConclusions: string;
|
|
20
|
+
checkSuitesComplete: boolean;
|
|
21
|
+
};
|
|
22
|
+
export declare function mergePolicyFingerprint(raw: {
|
|
23
|
+
isMergeQueueEnabled?: boolean;
|
|
24
|
+
baseRef?: RawBaseRef | null;
|
|
25
|
+
}): string;
|
|
26
|
+
export declare function commentRevisions(nodes: FingerprintComment[]): string;
|
|
27
|
+
export declare function threadCommentRevisions(nodes: Array<{
|
|
28
|
+
id: string;
|
|
29
|
+
comments?: {
|
|
30
|
+
totalCount?: number;
|
|
31
|
+
nodes: Array<{
|
|
32
|
+
id: string;
|
|
33
|
+
updatedAt?: string;
|
|
34
|
+
}>;
|
|
35
|
+
};
|
|
36
|
+
}>): string;
|
|
37
|
+
export declare function hasMultiCommentThreads(nodes: Array<{
|
|
38
|
+
comments?: {
|
|
39
|
+
totalCount?: number;
|
|
40
|
+
};
|
|
41
|
+
}>): boolean;
|
|
42
|
+
export declare function rulesComplete(baseRef?: {
|
|
43
|
+
rules?: {
|
|
44
|
+
pageInfo?: {
|
|
45
|
+
hasNextPage: boolean;
|
|
46
|
+
};
|
|
47
|
+
} | null;
|
|
48
|
+
} | null): boolean;
|
|
49
|
+
export declare function stackKey(raw: {
|
|
50
|
+
stack?: {
|
|
51
|
+
number: number;
|
|
52
|
+
size: number;
|
|
53
|
+
baseRefName: string;
|
|
54
|
+
} | null;
|
|
55
|
+
stackEntry?: {
|
|
56
|
+
position: number;
|
|
57
|
+
} | null;
|
|
58
|
+
}): string;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { parseBranchRules } from "./batch-parsers-rules.mjs";
|
|
2
|
+
export function suiteFingerprint(suites) {
|
|
3
|
+
if (suites === undefined)
|
|
4
|
+
return { checkSuiteConclusions: "", checkSuitesComplete: false };
|
|
5
|
+
return {
|
|
6
|
+
checkSuiteConclusions: (suites.nodes ?? [])
|
|
7
|
+
.map((node) => `${node.id ?? node.workflowRun?.databaseId ?? ""}:${node.conclusion ?? ""}`)
|
|
8
|
+
.join(","),
|
|
9
|
+
checkSuitesComplete: suites.pageInfo?.hasNextPage === false,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export function mergePolicyFingerprint(raw) {
|
|
13
|
+
return JSON.stringify({
|
|
14
|
+
isMergeQueueEnabled: Boolean(raw.isMergeQueueEnabled),
|
|
15
|
+
rules: parseBranchRules(raw.baseRef),
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
export function commentRevisions(nodes) {
|
|
19
|
+
return nodes.map((node) => `${node.id}:${node.updatedAt ?? ""}`).join(",");
|
|
20
|
+
}
|
|
21
|
+
export function threadCommentRevisions(nodes) {
|
|
22
|
+
return nodes
|
|
23
|
+
.map((thread) => {
|
|
24
|
+
const last = thread.comments?.nodes.at(-1);
|
|
25
|
+
return `${thread.id}:${last?.id ?? ""}:${last?.updatedAt ?? ""}`;
|
|
26
|
+
})
|
|
27
|
+
.join(",");
|
|
28
|
+
}
|
|
29
|
+
export function hasMultiCommentThreads(nodes) {
|
|
30
|
+
return nodes.some((thread) => (thread.comments?.totalCount ?? 0) > 1);
|
|
31
|
+
}
|
|
32
|
+
export function rulesComplete(baseRef) {
|
|
33
|
+
return baseRef?.rules?.pageInfo?.hasNextPage !== true;
|
|
34
|
+
}
|
|
35
|
+
export function stackKey(raw) {
|
|
36
|
+
if (!raw.stack)
|
|
37
|
+
return "";
|
|
38
|
+
return `${raw.stack.number}:${raw.stack.size}:${raw.stackEntry?.position ?? 0}:${raw.stack.baseRefName}`;
|
|
39
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { RepoInfo } from "./client.mts";
|
|
2
|
+
import type { RawPr } from "./batch-raw-types.mts";
|
|
3
|
+
export interface PrFingerprint {
|
|
4
|
+
headRefOid: string;
|
|
5
|
+
updatedAt: string;
|
|
6
|
+
state: string;
|
|
7
|
+
isDraft: boolean;
|
|
8
|
+
mergeable: string;
|
|
9
|
+
mergeStateStatus: string;
|
|
10
|
+
reviewDecision: string | null;
|
|
11
|
+
isInMergeQueue: boolean;
|
|
12
|
+
isMergeQueueEnabled: boolean;
|
|
13
|
+
mergePolicy: string;
|
|
14
|
+
commentCount: number;
|
|
15
|
+
commentRevisions: string;
|
|
16
|
+
threadCount: number;
|
|
17
|
+
reviewCount: number;
|
|
18
|
+
reviewRevisions: string;
|
|
19
|
+
latestCommentId: string | null;
|
|
20
|
+
latestThreadId: string | null;
|
|
21
|
+
latestReviewId: string | null;
|
|
22
|
+
checkRollupState: string | null;
|
|
23
|
+
checkSuiteConclusions: string;
|
|
24
|
+
checkSuitesComplete: boolean;
|
|
25
|
+
viewerCanUpdate: boolean;
|
|
26
|
+
viewerPermission: string | null;
|
|
27
|
+
viewerLogin: string | null;
|
|
28
|
+
stackKey: string;
|
|
29
|
+
threadCommentRevisions: string;
|
|
30
|
+
rulesComplete: boolean;
|
|
31
|
+
hasMultiCommentThreads: boolean;
|
|
32
|
+
}
|
|
33
|
+
export declare function fingerprintFromRaw(raw: RawPr, viewerPermission?: string | null, viewerLogin?: string | null): PrFingerprint;
|
|
34
|
+
export declare function fingerprintsEqual(left: PrFingerprint, right: PrFingerprint): boolean;
|
|
35
|
+
export declare function fetchPrFingerprint(pr: number, repo: RepoInfo): Promise<PrFingerprint>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { graphqlWithRateLimit } from "./client.mjs";
|
|
2
|
+
import { PR_FINGERPRINT_QUERY } from "./queries.mjs";
|
|
3
|
+
import { GitHubRequestError } from "./errors.mjs";
|
|
4
|
+
import { EXIT, ShepherdError } from "../exit-codes.mjs";
|
|
5
|
+
import { commentRevisions, mergePolicyFingerprint, rulesComplete, stackKey, suiteFingerprint, hasMultiCommentThreads, threadCommentRevisions, } from "./fingerprint-fields.mjs";
|
|
6
|
+
function coreFingerprint(raw, counts, viewer) {
|
|
7
|
+
return {
|
|
8
|
+
headRefOid: raw.headRefOid,
|
|
9
|
+
updatedAt: raw.updatedAt ?? "",
|
|
10
|
+
state: raw.state,
|
|
11
|
+
isDraft: raw.isDraft,
|
|
12
|
+
mergeable: raw.mergeable,
|
|
13
|
+
mergeStateStatus: raw.mergeStateStatus,
|
|
14
|
+
reviewDecision: raw.reviewDecision,
|
|
15
|
+
isInMergeQueue: Boolean(raw.isInMergeQueue),
|
|
16
|
+
isMergeQueueEnabled: Boolean(raw.isMergeQueueEnabled),
|
|
17
|
+
mergePolicy: mergePolicyFingerprint(raw),
|
|
18
|
+
commentCount: raw.comments.totalCount ?? raw.comments.nodes.length,
|
|
19
|
+
commentRevisions: commentRevisions(raw.comments.nodes),
|
|
20
|
+
threadCount: raw.reviewThreads.totalCount ?? raw.reviewThreads.nodes.length,
|
|
21
|
+
reviewCount: counts.reviewCount,
|
|
22
|
+
reviewRevisions: counts.reviewRevisions,
|
|
23
|
+
latestCommentId: raw.comments.nodes.at(-1)?.id ?? null,
|
|
24
|
+
latestThreadId: raw.reviewThreads.nodes.at(-1)?.id ?? null,
|
|
25
|
+
latestReviewId: counts.latestReviewId,
|
|
26
|
+
checkRollupState: raw.commits.nodes[0]?.commit.statusCheckRollup?.state ?? null,
|
|
27
|
+
...suiteFingerprint(raw.commits.nodes[0]?.commit.checkSuites),
|
|
28
|
+
viewerCanUpdate: raw.viewerCanUpdate === true,
|
|
29
|
+
viewerPermission: viewer.permission,
|
|
30
|
+
viewerLogin: viewer.login,
|
|
31
|
+
stackKey: stackKey(raw),
|
|
32
|
+
threadCommentRevisions: threadCommentRevisions(raw.reviewThreads.nodes),
|
|
33
|
+
rulesComplete: rulesComplete(raw.baseRef),
|
|
34
|
+
hasMultiCommentThreads: hasMultiCommentThreads(raw.reviewThreads.nodes),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export function fingerprintFromRaw(raw, viewerPermission = null, viewerLogin = null) {
|
|
38
|
+
return coreFingerprint(raw, {
|
|
39
|
+
reviewCount: raw.allReviews?.totalCount ?? 0,
|
|
40
|
+
latestReviewId: raw.allReviews?.nodes?.at(-1)?.id ?? null,
|
|
41
|
+
reviewRevisions: commentRevisions(raw.allReviews?.nodes ?? []),
|
|
42
|
+
}, { permission: viewerPermission, login: viewerLogin });
|
|
43
|
+
}
|
|
44
|
+
export function fingerprintsEqual(left, right) {
|
|
45
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
46
|
+
}
|
|
47
|
+
export async function fetchPrFingerprint(pr, repo) {
|
|
48
|
+
const result = await graphqlWithRateLimit(PR_FINGERPRINT_QUERY, {
|
|
49
|
+
owner: repo.owner,
|
|
50
|
+
repo: repo.name,
|
|
51
|
+
pr,
|
|
52
|
+
});
|
|
53
|
+
if (!result.data.repository) {
|
|
54
|
+
throw new GitHubRequestError(`GitHub GraphQL response did not include repository ${repo.owner}/${repo.name} (not found or access denied)`, { status: 200 });
|
|
55
|
+
}
|
|
56
|
+
const raw = result.data.repository.pullRequest;
|
|
57
|
+
if (!raw)
|
|
58
|
+
throw new ShepherdError(`PR #${pr} not found`, EXIT.UNAVAILABLE);
|
|
59
|
+
return coreFingerprint(raw, {
|
|
60
|
+
reviewCount: raw.reviews.totalCount,
|
|
61
|
+
latestReviewId: raw.reviews.nodes.at(-1)?.id ?? null,
|
|
62
|
+
reviewRevisions: commentRevisions(raw.reviews.nodes),
|
|
63
|
+
}, {
|
|
64
|
+
permission: result.data.repository.viewerPermission,
|
|
65
|
+
login: result.data.viewer?.login ?? null,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
@@ -9,12 +9,16 @@ query BatchPr($owner: String!, $repo: String!, $pr: Int!) {
|
|
|
9
9
|
resetAt
|
|
10
10
|
used
|
|
11
11
|
}
|
|
12
|
+
viewer {
|
|
13
|
+
login
|
|
14
|
+
}
|
|
12
15
|
repository(owner: $owner, name: $repo) {
|
|
13
16
|
viewerPermission
|
|
14
17
|
viewerCanAdminister
|
|
15
18
|
pullRequest(number: $pr) {
|
|
16
19
|
id
|
|
17
20
|
number
|
|
21
|
+
updatedAt
|
|
18
22
|
state
|
|
19
23
|
isDraft
|
|
20
24
|
viewerDidAuthor
|
|
@@ -32,7 +36,7 @@ query BatchPr($owner: String!, $repo: String!, $pr: Int!) {
|
|
|
32
36
|
}
|
|
33
37
|
baseRefName
|
|
34
38
|
isInMergeQueue
|
|
35
|
-
|
|
39
|
+
...PrMergePolicy
|
|
36
40
|
autoMergeRequest {
|
|
37
41
|
enabledAt
|
|
38
42
|
mergeMethod
|
|
@@ -49,7 +53,8 @@ query BatchPr($owner: String!, $repo: String!, $pr: Int!) {
|
|
|
49
53
|
login
|
|
50
54
|
}
|
|
51
55
|
headCommit {
|
|
52
|
-
|
|
56
|
+
oid
|
|
57
|
+
committedDate
|
|
53
58
|
}
|
|
54
59
|
}
|
|
55
60
|
mergeQueueAdditions: timelineItems(last: 1, itemTypes: [ADDED_TO_MERGE_QUEUE_EVENT]) {
|
|
@@ -68,7 +73,13 @@ query BatchPr($owner: String!, $repo: String!, $pr: Int!) {
|
|
|
68
73
|
login
|
|
69
74
|
}
|
|
70
75
|
beforeCommit {
|
|
71
|
-
|
|
76
|
+
oid
|
|
77
|
+
committedDate
|
|
78
|
+
parents(first: 100) {
|
|
79
|
+
nodes {
|
|
80
|
+
oid
|
|
81
|
+
}
|
|
82
|
+
}
|
|
72
83
|
}
|
|
73
84
|
}
|
|
74
85
|
}
|
|
@@ -81,49 +92,6 @@ query BatchPr($owner: String!, $repo: String!, $pr: Int!) {
|
|
|
81
92
|
stackEntry {
|
|
82
93
|
position
|
|
83
94
|
}
|
|
84
|
-
baseRef {
|
|
85
|
-
branchProtectionRule {
|
|
86
|
-
requiresApprovingReviews
|
|
87
|
-
requiredApprovingReviewCount
|
|
88
|
-
requiresConversationResolution
|
|
89
|
-
requiresCodeOwnerReviews
|
|
90
|
-
requireLastPushApproval
|
|
91
|
-
requiresCommitSignatures
|
|
92
|
-
requiresLinearHistory
|
|
93
|
-
requiresStatusChecks
|
|
94
|
-
requiredStatusCheckContexts
|
|
95
|
-
requiresStrictStatusChecks
|
|
96
|
-
requiresDeployments
|
|
97
|
-
requiredDeploymentEnvironments
|
|
98
|
-
}
|
|
99
|
-
rules(first: 100) {
|
|
100
|
-
nodes {
|
|
101
|
-
type
|
|
102
|
-
parameters {
|
|
103
|
-
... on PullRequestParameters {
|
|
104
|
-
requiredApprovingReviewCount
|
|
105
|
-
requiredReviewThreadResolution
|
|
106
|
-
requireCodeOwnerReview
|
|
107
|
-
requireLastPushApproval
|
|
108
|
-
}
|
|
109
|
-
... on RequiredStatusChecksParameters {
|
|
110
|
-
strictRequiredStatusChecksPolicy
|
|
111
|
-
requiredStatusChecks {
|
|
112
|
-
context
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
... on RequiredDeploymentsParameters {
|
|
116
|
-
requiredDeploymentEnvironments
|
|
117
|
-
}
|
|
118
|
-
... on CodeScanningParameters {
|
|
119
|
-
codeScanningTools {
|
|
120
|
-
tool
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
95
|
# Not paginated — capped at 50; PRs with more pending reviewers truncate silently.
|
|
128
96
|
reviewRequests(last: 50) {
|
|
129
97
|
nodes {
|
|
@@ -153,6 +121,7 @@ query BatchPr($owner: String!, $repo: String!, $pr: Int!) {
|
|
|
153
121
|
}
|
|
154
122
|
}
|
|
155
123
|
reviewThreads(last: 100) {
|
|
124
|
+
totalCount
|
|
156
125
|
pageInfo {
|
|
157
126
|
hasPreviousPage
|
|
158
127
|
startCursor
|
|
@@ -189,11 +158,13 @@ query BatchPr($owner: String!, $repo: String!, $pr: Int!) {
|
|
|
189
158
|
line
|
|
190
159
|
startLine
|
|
191
160
|
createdAt
|
|
161
|
+
updatedAt
|
|
192
162
|
}
|
|
193
163
|
}
|
|
194
164
|
}
|
|
195
165
|
}
|
|
196
166
|
comments(last: 100) {
|
|
167
|
+
totalCount
|
|
197
168
|
pageInfo {
|
|
198
169
|
hasPreviousPage
|
|
199
170
|
startCursor
|
|
@@ -210,6 +181,7 @@ query BatchPr($owner: String!, $repo: String!, $pr: Int!) {
|
|
|
210
181
|
}
|
|
211
182
|
body
|
|
212
183
|
createdAt
|
|
184
|
+
updatedAt
|
|
213
185
|
}
|
|
214
186
|
}
|
|
215
187
|
changesRequestedReviews: reviews(states: CHANGES_REQUESTED, last: 50) {
|
|
@@ -249,8 +221,12 @@ query BatchPr($owner: String!, $repo: String!, $pr: Int!) {
|
|
|
249
221
|
createdAt
|
|
250
222
|
}
|
|
251
223
|
}
|
|
252
|
-
allReviews: reviews(last:
|
|
224
|
+
allReviews: reviews(last: 100) {
|
|
253
225
|
totalCount
|
|
226
|
+
nodes {
|
|
227
|
+
id
|
|
228
|
+
updatedAt
|
|
229
|
+
}
|
|
254
230
|
}
|
|
255
231
|
approvedReviews: reviews(states: APPROVED, last: 50) {
|
|
256
232
|
pageInfo {
|
|
@@ -276,23 +252,9 @@ query BatchPr($owner: String!, $repo: String!, $pr: Int!) {
|
|
|
276
252
|
commit {
|
|
277
253
|
oid
|
|
278
254
|
committedDate
|
|
279
|
-
|
|
280
|
-
pageInfo {
|
|
281
|
-
hasNextPage
|
|
282
|
-
}
|
|
283
|
-
nodes {
|
|
284
|
-
conclusion
|
|
285
|
-
workflowRun {
|
|
286
|
-
databaseId
|
|
287
|
-
event
|
|
288
|
-
url
|
|
289
|
-
workflow {
|
|
290
|
-
name
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
}
|
|
255
|
+
...CommitCheckSuites
|
|
295
256
|
statusCheckRollup {
|
|
257
|
+
state
|
|
296
258
|
contexts(first: 100) {
|
|
297
259
|
pageInfo {
|
|
298
260
|
hasNextPage
|
|
@@ -345,60 +307,3 @@ query BatchPr($owner: String!, $repo: String!, $pr: Int!) {
|
|
|
345
307
|
}
|
|
346
308
|
}
|
|
347
309
|
}
|
|
348
|
-
|
|
349
|
-
fragment QueueCheckCommit on Commit {
|
|
350
|
-
oid
|
|
351
|
-
committedDate
|
|
352
|
-
parents(first: 100) {
|
|
353
|
-
nodes {
|
|
354
|
-
oid
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
statusCheckRollup {
|
|
358
|
-
contexts(first: 100) {
|
|
359
|
-
pageInfo {
|
|
360
|
-
hasNextPage
|
|
361
|
-
endCursor
|
|
362
|
-
}
|
|
363
|
-
nodes {
|
|
364
|
-
__typename
|
|
365
|
-
... on CheckRun {
|
|
366
|
-
id
|
|
367
|
-
name
|
|
368
|
-
status
|
|
369
|
-
conclusion
|
|
370
|
-
detailsUrl
|
|
371
|
-
completedAt
|
|
372
|
-
startedAt
|
|
373
|
-
title
|
|
374
|
-
summary
|
|
375
|
-
annotations(first: 1) {
|
|
376
|
-
nodes {
|
|
377
|
-
message
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
checkSuite {
|
|
381
|
-
createdAt
|
|
382
|
-
updatedAt
|
|
383
|
-
workflowRun {
|
|
384
|
-
event
|
|
385
|
-
createdAt
|
|
386
|
-
updatedAt
|
|
387
|
-
workflow {
|
|
388
|
-
name
|
|
389
|
-
databaseId
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
... on StatusContext {
|
|
395
|
-
context
|
|
396
|
-
state
|
|
397
|
-
createdAt
|
|
398
|
-
targetUrl
|
|
399
|
-
description
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Cheap preflight. Cost is 1. Used to skip BatchPr when GitHub state is unchanged.
|
|
2
|
+
query PrFingerprint($owner: String!, $repo: String!, $pr: Int!) {
|
|
3
|
+
_shepherdRateLimit: rateLimit {
|
|
4
|
+
cost
|
|
5
|
+
limit
|
|
6
|
+
nodeCount
|
|
7
|
+
remaining
|
|
8
|
+
resetAt
|
|
9
|
+
used
|
|
10
|
+
}
|
|
11
|
+
viewer {
|
|
12
|
+
login
|
|
13
|
+
}
|
|
14
|
+
repository(owner: $owner, name: $repo) {
|
|
15
|
+
viewerPermission
|
|
16
|
+
pullRequest(number: $pr) {
|
|
17
|
+
updatedAt
|
|
18
|
+
state
|
|
19
|
+
isDraft
|
|
20
|
+
viewerCanUpdate
|
|
21
|
+
headRefOid
|
|
22
|
+
mergeable
|
|
23
|
+
mergeStateStatus
|
|
24
|
+
reviewDecision
|
|
25
|
+
isInMergeQueue
|
|
26
|
+
stack {
|
|
27
|
+
number
|
|
28
|
+
size
|
|
29
|
+
baseRefName
|
|
30
|
+
}
|
|
31
|
+
stackEntry {
|
|
32
|
+
position
|
|
33
|
+
}
|
|
34
|
+
...PrMergePolicy
|
|
35
|
+
comments(last: 100) {
|
|
36
|
+
totalCount
|
|
37
|
+
nodes {
|
|
38
|
+
id
|
|
39
|
+
updatedAt
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
reviewThreads(last: 20) {
|
|
43
|
+
totalCount
|
|
44
|
+
nodes {
|
|
45
|
+
id
|
|
46
|
+
comments(last: 1) {
|
|
47
|
+
totalCount
|
|
48
|
+
nodes {
|
|
49
|
+
id
|
|
50
|
+
updatedAt
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
reviews(last: 100) {
|
|
56
|
+
totalCount
|
|
57
|
+
nodes {
|
|
58
|
+
id
|
|
59
|
+
updatedAt
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
commits(last: 1) {
|
|
63
|
+
nodes {
|
|
64
|
+
commit {
|
|
65
|
+
oid
|
|
66
|
+
statusCheckRollup {
|
|
67
|
+
state
|
|
68
|
+
}
|
|
69
|
+
...CommitCheckSuites
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
fragment PrMergePolicy on PullRequest {
|
|
2
|
+
isMergeQueueEnabled
|
|
3
|
+
baseRef {
|
|
4
|
+
branchProtectionRule {
|
|
5
|
+
requiresApprovingReviews
|
|
6
|
+
requiredApprovingReviewCount
|
|
7
|
+
requiresConversationResolution
|
|
8
|
+
requiresCodeOwnerReviews
|
|
9
|
+
requireLastPushApproval
|
|
10
|
+
requiresCommitSignatures
|
|
11
|
+
requiresLinearHistory
|
|
12
|
+
requiresStatusChecks
|
|
13
|
+
requiredStatusCheckContexts
|
|
14
|
+
requiresStrictStatusChecks
|
|
15
|
+
requiresDeployments
|
|
16
|
+
requiredDeploymentEnvironments
|
|
17
|
+
}
|
|
18
|
+
rules(first: 100) {
|
|
19
|
+
pageInfo {
|
|
20
|
+
hasNextPage
|
|
21
|
+
}
|
|
22
|
+
nodes {
|
|
23
|
+
type
|
|
24
|
+
parameters {
|
|
25
|
+
... on PullRequestParameters {
|
|
26
|
+
requiredApprovingReviewCount
|
|
27
|
+
requiredReviewThreadResolution
|
|
28
|
+
requireCodeOwnerReview
|
|
29
|
+
requireLastPushApproval
|
|
30
|
+
}
|
|
31
|
+
... on RequiredStatusChecksParameters {
|
|
32
|
+
strictRequiredStatusChecksPolicy
|
|
33
|
+
requiredStatusChecks {
|
|
34
|
+
context
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
... on RequiredDeploymentsParameters {
|
|
38
|
+
requiredDeploymentEnvironments
|
|
39
|
+
}
|
|
40
|
+
... on CodeScanningParameters {
|
|
41
|
+
codeScanningTools {
|
|
42
|
+
tool
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -1,46 +1,73 @@
|
|
|
1
1
|
import { graphql } from "./client.mjs";
|
|
2
2
|
import { requireContextNodes } from "./batch-response.mjs";
|
|
3
3
|
import { COMMIT_CHECK_CONTEXTS_QUERY } from "./queries.mjs";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
function omittedCursorError(oid) {
|
|
5
|
+
return new Error(`Merge queue check pagination interrupted: GitHub omitted the next cursor for ${oid}. Retry.`);
|
|
6
|
+
}
|
|
7
|
+
function nextPageCursor(contexts, oid) {
|
|
8
|
+
if (!contexts.pageInfo.hasNextPage)
|
|
9
|
+
return undefined;
|
|
10
|
+
if (!contexts.pageInfo.endCursor)
|
|
11
|
+
throw omittedCursorError(oid);
|
|
12
|
+
return contexts.pageInfo.endCursor;
|
|
13
|
+
}
|
|
14
|
+
function initialQueueCursor(existing, oid) {
|
|
15
|
+
if (!existing)
|
|
16
|
+
return null;
|
|
17
|
+
return nextPageCursor(existing, oid);
|
|
18
|
+
}
|
|
19
|
+
async function fetchQueuePage(oid, repo, cursor) {
|
|
20
|
+
const result = await graphql(COMMIT_CHECK_CONTEXTS_QUERY, {
|
|
21
|
+
owner: repo.owner,
|
|
22
|
+
repo: repo.name,
|
|
23
|
+
oid,
|
|
24
|
+
...(cursor !== null && { cursor }),
|
|
25
|
+
});
|
|
26
|
+
const object = result.data.repository?.object;
|
|
27
|
+
if (object?.__typename !== "Commit" || object.oid !== oid) {
|
|
28
|
+
if (cursor === null)
|
|
29
|
+
return null;
|
|
30
|
+
throw new Error(`Merge queue check pagination interrupted: commit ${oid} disappeared or changed. Retry.`);
|
|
11
31
|
}
|
|
12
|
-
|
|
13
|
-
|
|
32
|
+
return object.statusCheckRollup?.contexts ?? null;
|
|
33
|
+
}
|
|
34
|
+
async function hydrateCommitContexts(commit, repo) {
|
|
35
|
+
const existing = commit.statusCheckRollup?.contexts;
|
|
36
|
+
const nodes = existing ? [...requireContextNodes(existing.nodes)] : [];
|
|
37
|
+
let cursor = initialQueueCursor(existing, commit.oid);
|
|
38
|
+
while (cursor !== undefined) {
|
|
14
39
|
// eslint-disable-next-line no-await-in-loop
|
|
15
|
-
const
|
|
16
|
-
owner: repo.owner,
|
|
17
|
-
repo: repo.name,
|
|
18
|
-
oid: commit.oid,
|
|
19
|
-
cursor,
|
|
20
|
-
});
|
|
21
|
-
const object = result.data.repository?.object;
|
|
22
|
-
if (object?.__typename !== "Commit" || object.oid !== commit.oid) {
|
|
23
|
-
throw new Error(`Merge queue check pagination interrupted: commit ${commit.oid} disappeared or changed. Retry.`);
|
|
24
|
-
}
|
|
25
|
-
const next = object.statusCheckRollup?.contexts;
|
|
40
|
+
const next = await fetchQueuePage(commit.oid, repo, cursor);
|
|
26
41
|
if (!next) {
|
|
42
|
+
if (cursor === null) {
|
|
43
|
+
cursor = undefined;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
27
46
|
throw new Error(`Merge queue check pagination interrupted: statusCheckRollup disappeared for ${commit.oid}. Retry.`);
|
|
28
47
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
cursor = next.pageInfo.hasNextPage ? next.pageInfo.endCursor : null;
|
|
32
|
-
if (next.pageInfo.hasNextPage && !cursor) {
|
|
33
|
-
throw new Error(`Merge queue check pagination interrupted: GitHub omitted the next cursor for ${commit.oid}. Retry.`);
|
|
34
|
-
}
|
|
48
|
+
nodes.push(...requireContextNodes(next.nodes));
|
|
49
|
+
cursor = nextPageCursor(next, commit.oid);
|
|
35
50
|
}
|
|
51
|
+
commit.statusCheckRollup = {
|
|
52
|
+
contexts: { pageInfo: { hasNextPage: false, endCursor: null }, nodes },
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function currentRemovalCommit(raw) {
|
|
56
|
+
const removal = raw.mergeQueueRemovals?.nodes[0];
|
|
57
|
+
const addition = raw.mergeQueueAdditions?.nodes[0];
|
|
58
|
+
if (!removal?.beforeCommit)
|
|
59
|
+
return undefined;
|
|
60
|
+
if (addition && Date.parse(removal.createdAt) < Date.parse(addition.createdAt))
|
|
61
|
+
return undefined;
|
|
62
|
+
const parentOids = removal.beforeCommit.parents?.nodes.map((node) => node.oid);
|
|
63
|
+
if (!parentOids?.includes(raw.headRefOid))
|
|
64
|
+
return undefined;
|
|
65
|
+
return removal.beforeCommit;
|
|
36
66
|
}
|
|
37
67
|
/** Hydrate all status contexts for the active or most recently removed queue commit. */
|
|
38
68
|
export async function hydrateMergeQueueChecks(raw, repo) {
|
|
39
69
|
const active = raw.mergeQueueEntry?.headCommit;
|
|
40
|
-
const
|
|
41
|
-
const addition = raw.mergeQueueAdditions?.nodes[0];
|
|
42
|
-
const removalIsCurrent = Boolean(removal && (!addition || Date.parse(removal.createdAt) >= Date.parse(addition.createdAt)));
|
|
43
|
-
const removed = removalIsCurrent ? removal?.beforeCommit : undefined;
|
|
70
|
+
const removed = currentRemovalCommit(raw);
|
|
44
71
|
if (active)
|
|
45
72
|
await hydrateCommitContexts(active, repo);
|
|
46
73
|
if (removed && removed.oid !== active?.oid)
|
package/bin/github/queries.d.mts
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
export declare const BATCH_PR_QUERY: string;
|
|
11
11
|
/** Slim @include follow-up for outstanding batch-query connections. */
|
|
12
12
|
export declare const BATCH_PR_PAGE_QUERY: string;
|
|
13
|
+
/** Cheap PR fingerprint used to skip an unchanged BatchPr snapshot. */
|
|
14
|
+
export declare const PR_FINGERPRINT_QUERY: string;
|
|
13
15
|
/** PR head fields plus a single review thread for `commit-suggestion`. */
|
|
14
16
|
export declare const SUGGESTION_THREADS_QUERY: string;
|
|
15
17
|
/** Fetches additional comments for a single review thread when its nested connection paginates. */
|