canopycms 0.12.0 → 0.13.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/CHANGELOG.md +326 -48
- package/dist/api/admin.d.ts +4 -4
- package/dist/api/branch-review.d.ts +20 -2
- package/dist/api/branch-review.js +153 -32
- package/dist/api/branch-status.d.ts +23 -2
- package/dist/api/branch-status.js +16 -2
- package/dist/api/branch-update-from-base.d.ts +23 -0
- package/dist/api/branch-update-from-base.js +227 -0
- package/dist/api/branch.d.ts +10 -0
- package/dist/api/branch.js +28 -2
- package/dist/api/client.d.ts +15 -2
- package/dist/api/client.js +24 -2
- package/dist/api/comments.d.ts +2 -2
- package/dist/api/content.d.ts +38 -1
- package/dist/api/content.js +41 -5
- package/dist/api/merge-conflicts.d.ts +235 -0
- package/dist/api/merge-conflicts.js +709 -0
- package/dist/api/routes.js +2 -0
- package/dist/api/types.d.ts +8 -3
- package/dist/base-update.d.ts +32 -0
- package/dist/base-update.js +31 -0
- package/dist/branch-metadata-file.d.ts +66 -0
- package/dist/branch-metadata-file.js +16 -1
- package/dist/branch-schema-cache.d.ts +12 -0
- package/dist/branch-schema-cache.js +27 -2
- package/dist/cli/backfill-item-keys.d.ts +77 -0
- package/dist/cli/backfill-item-keys.js +320 -0
- package/dist/cli/cli.d.ts +1 -1
- package/dist/cli/cli.js +9798 -1509
- package/dist/cli/generate-ai-content.js +373 -249
- package/dist/cli/github-app-manifest.d.ts +4 -1
- package/dist/cli/github-app-manifest.js +5 -1
- package/dist/cli/init.js +12 -0
- package/dist/cli/site-config.d.ts +14 -2
- package/dist/cli/site-config.js +20 -4
- package/dist/comment-store.d.ts +8 -0
- package/dist/comment-store.js +28 -0
- package/dist/content-index-generation.d.ts +1 -4
- package/dist/content-index-generation.js +1 -4
- package/dist/content-store.d.ts +39 -3
- package/dist/content-store.js +48 -15
- package/dist/editor/BranchManager.d.ts +4 -1
- package/dist/editor/BranchManager.js +9 -5
- package/dist/editor/Editor.js +462 -62
- package/dist/editor/EntryNavigator.d.ts +10 -2
- package/dist/editor/EntryNavigator.js +12 -4
- package/dist/editor/FormRenderer.d.ts +8 -2
- package/dist/editor/FormRenderer.js +5 -6
- package/dist/editor/admin/ConflictsStatusAlert.d.ts +8 -0
- package/dist/editor/admin/ConflictsStatusAlert.js +22 -0
- package/dist/editor/admin/SystemHealthPanel.js +4 -3
- package/dist/editor/base-update-wait.d.ts +78 -0
- package/dist/editor/base-update-wait.js +98 -0
- package/dist/editor/branch-status.js +1 -1
- package/dist/editor/components/BaseUpdateNotice.d.ts +12 -0
- package/dist/editor/components/BaseUpdateNotice.js +24 -0
- package/dist/editor/components/BranchPill.d.ts +4 -1
- package/dist/editor/components/BranchPill.js +5 -2
- package/dist/editor/components/ConflictsGroup.d.ts +112 -0
- package/dist/editor/components/ConflictsGroup.js +404 -0
- package/dist/editor/components/EditorHeader.d.ts +18 -1
- package/dist/editor/components/EditorHeader.js +99 -33
- package/dist/editor/components/EntryChangesDrawer.d.ts +16 -2
- package/dist/editor/components/EntryChangesDrawer.js +85 -167
- package/dist/editor/components/EntryHeader.d.ts +3 -1
- package/dist/editor/components/EntryHeader.js +2 -2
- package/dist/editor/components/GetLatestChangesButton.d.ts +15 -0
- package/dist/editor/components/GetLatestChangesButton.js +29 -0
- package/dist/editor/components/ReleaseHoldButton.d.ts +20 -0
- package/dist/editor/components/ReleaseHoldButton.js +36 -0
- package/dist/editor/components/RetryToastMessage.js +1 -1
- package/dist/editor/components/change-parts.d.ts +51 -0
- package/dist/editor/components/change-parts.js +171 -0
- package/dist/editor/conflict-cards.d.ts +93 -0
- package/dist/editor/conflict-cards.js +284 -0
- package/dist/editor/conflict-choices.d.ts +60 -0
- package/dist/editor/conflict-choices.js +108 -0
- package/dist/editor/conflict-notice.d.ts +25 -0
- package/dist/editor/conflict-notice.js +39 -0
- package/dist/editor/copy.d.ts +12 -0
- package/dist/editor/copy.js +12 -0
- package/dist/editor/entry-diff.d.ts +2 -1
- package/dist/editor/entry-diff.js +5 -21
- package/dist/editor/hooks/useBaseUpdate.d.ts +38 -0
- package/dist/editor/hooks/useBaseUpdate.js +279 -0
- package/dist/editor/hooks/useBranchChangesData.d.ts +7 -1
- package/dist/editor/hooks/useBranchChangesData.js +13 -0
- package/dist/editor/hooks/useBranchManager.d.ts +8 -1
- package/dist/editor/hooks/useBranchManager.js +108 -13
- package/dist/editor/hooks/useBranchesData.js +3 -0
- package/dist/editor/hooks/useConflictChoices.d.ts +24 -0
- package/dist/editor/hooks/useConflictChoices.js +75 -0
- package/dist/editor/hooks/useConflictsData.d.ts +26 -0
- package/dist/editor/hooks/useConflictsData.js +89 -0
- package/dist/editor/hooks/useDraftManager.d.ts +13 -1
- package/dist/editor/hooks/useDraftManager.js +40 -9
- package/dist/editor/hooks/useEntryManager.d.ts +17 -3
- package/dist/editor/hooks/useEntryManager.js +11 -5
- package/dist/editor/release-hold.d.ts +12 -0
- package/dist/editor/release-hold.js +39 -0
- package/dist/editor/server-clock.d.ts +4 -0
- package/dist/editor/server-clock.js +15 -0
- package/dist/editor/undo-change.d.ts +7 -0
- package/dist/editor/undo-change.js +19 -4
- package/dist/git-manager.d.ts +6 -0
- package/dist/git-manager.js +38 -3
- package/dist/item-keys.d.ts +13 -0
- package/dist/item-keys.js +36 -14
- package/dist/merge/conflict-path.d.ts +40 -0
- package/dist/merge/conflict-path.js +128 -0
- package/dist/merge/diff3.d.ts +27 -0
- package/dist/merge/diff3.js +113 -0
- package/dist/merge/entry-merge.d.ts +94 -0
- package/dist/merge/entry-merge.js +876 -0
- package/dist/merge/list-alignment.d.ts +55 -0
- package/dist/merge/list-alignment.js +490 -0
- package/dist/merge/rekey.d.ts +11 -0
- package/dist/merge/rekey.js +75 -0
- package/dist/merge/text-merge.d.ts +29 -0
- package/dist/merge/text-merge.js +59 -0
- package/dist/merge/value-equality.d.ts +18 -0
- package/dist/merge/value-equality.js +50 -0
- package/dist/merge-conflicts.d.ts +232 -0
- package/dist/merge-conflicts.js +451 -0
- package/dist/operating-mode/client-safe-strategy.d.ts +2 -0
- package/dist/operating-mode/client-safe-strategy.js +6 -0
- package/dist/operating-mode/client-unsafe-strategy.js +6 -0
- package/dist/operating-mode/types.d.ts +13 -0
- package/dist/services.d.ts +24 -1
- package/dist/services.js +138 -22
- package/dist/task-queue/cms-task-queue.d.ts +3 -1
- package/dist/types.d.ts +63 -5
- package/dist/utils/content-serialize.d.ts +18 -1
- package/dist/utils/content-serialize.js +12 -0
- package/dist/utils/content-write-lock.d.ts +3 -3
- package/dist/utils/content-write-lock.js +3 -3
- package/dist/utils/git.d.ts +7 -0
- package/dist/utils/git.js +35 -0
- package/dist/utils/markdown-body-splice.d.ts +1 -1
- package/dist/utils/markdown-body-splice.js +1 -19
- package/dist/utils/markdown-parse.d.ts +9 -0
- package/dist/utils/markdown-parse.js +27 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker/branch-merge.d.ts +80 -40
- package/dist/worker/branch-merge.js +244 -58
- package/dist/worker/branch-policy.d.ts +18 -2
- package/dist/worker/branch-policy.js +36 -0
- package/dist/worker/cms-worker.d.ts +37 -3
- package/dist/worker/cms-worker.js +88 -16
- package/dist/worker/conflicts-status.d.ts +20 -0
- package/dist/worker/conflicts-status.js +204 -0
- package/dist/worker/field-merge.d.ts +80 -0
- package/dist/worker/field-merge.js +729 -0
- package/dist/worker/git-sync.d.ts +3 -2
- package/dist/worker/git-sync.js +16 -6
- package/dist/worker/github-commits.d.ts +42 -10
- package/dist/worker/github-commits.js +444 -52
- package/dist/worker/github-errors.d.ts +15 -0
- package/dist/worker/github-errors.js +47 -0
- package/dist/worker/github-gateway-http.js +24 -9
- package/dist/worker/github-gateway-wire.d.ts +49 -20
- package/dist/worker/github-gateway-wire.js +19 -3
- package/dist/worker/github-gateway.d.ts +27 -1
- package/dist/worker/github-gateway.js +55 -7
- package/dist/worker/github-transfer.d.ts +5 -3
- package/dist/worker/github-transfer.js +8 -5
- package/dist/worker/history-rewrite.d.ts +40 -8
- package/dist/worker/history-rewrite.js +51 -23
- package/dist/worker/publish-decision.d.ts +37 -0
- package/dist/worker/publish-decision.js +16 -0
- package/dist/worker/rebase.d.ts +29 -8
- package/dist/worker/rebase.js +492 -211
- package/dist/worker/task-runner.d.ts +1 -1
- package/dist/worker/task-runner.js +30 -41
- package/dist/worker/worker-context.d.ts +24 -2
- package/package.json +1 -1
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading a GitHub API failure as the gateway passes it on: an Octokit error in-process, or the
|
|
3
|
+
* wire's `GitHubRequestError`, both carrying `status` and the response headers that classify it.
|
|
4
|
+
*/
|
|
5
|
+
/** Extract an HTTP status from an error, if present (Octokit RequestError shape). */
|
|
6
|
+
export function getHttpStatus(err) {
|
|
7
|
+
if (err instanceof Error && 'status' in err) {
|
|
8
|
+
const status = err.status;
|
|
9
|
+
if (typeof status === 'number')
|
|
10
|
+
return status;
|
|
11
|
+
}
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
/** Narrow an unknown value to a response-headers record, if present (Octokit lowercases header names). */
|
|
15
|
+
function getResponseHeaders(err) {
|
|
16
|
+
if (typeof err !== 'object' || err === null || !('response' in err))
|
|
17
|
+
return null;
|
|
18
|
+
const response = err.response;
|
|
19
|
+
if (typeof response !== 'object' || response === null || !('headers' in response))
|
|
20
|
+
return null;
|
|
21
|
+
const headers = response.headers;
|
|
22
|
+
if (typeof headers !== 'object' || headers === null)
|
|
23
|
+
return null;
|
|
24
|
+
return headers;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Whether a 403 is a GitHub rate-limit response rather than a plain permission
|
|
28
|
+
* denial. GitHub signals rate limiting on 403s three ways: the primary limit
|
|
29
|
+
* zeroes `x-ratelimit-remaining`, secondary limits often carry `retry-after`,
|
|
30
|
+
* and both produce a message containing "rate limit".
|
|
31
|
+
*/
|
|
32
|
+
export function isRateLimitSignal403(err) {
|
|
33
|
+
const headers = getResponseHeaders(err);
|
|
34
|
+
if (headers) {
|
|
35
|
+
if (headers['x-ratelimit-remaining'] === '0')
|
|
36
|
+
return true;
|
|
37
|
+
if (typeof headers['retry-after'] === 'string' && headers['retry-after'].length > 0)
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
if (err instanceof Error && /rate limit/i.test(err.message))
|
|
41
|
+
return true;
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
/** A 403 that is GitHub denying the credential the operation, not a rate limit. */
|
|
45
|
+
export function isPermissionDenial(err) {
|
|
46
|
+
return getHttpStatus(err) === 403 && !isRateLimitSignal403(err);
|
|
47
|
+
}
|
|
@@ -219,9 +219,9 @@ class GatewayRequestHandler {
|
|
|
219
219
|
return {};
|
|
220
220
|
},
|
|
221
221
|
getPullRequest: async ({ number }, signal) => {
|
|
222
|
-
const { merged, merged_at, state, node_id, body, mergeable_state, baseRef } = await gateway.getPullRequest(number, signal);
|
|
222
|
+
const { merged, merged_at, state, node_id, body, mergeable_state, baseRef, headSha } = await gateway.getPullRequest(number, signal);
|
|
223
223
|
// A null body is the gateway withholding it (`GitHubPullRequest.body`), and crosses as one.
|
|
224
|
-
return { merged, merged_at, state, node_id, body, mergeable_state, baseRef };
|
|
224
|
+
return { merged, merged_at, state, node_id, body, mergeable_state, baseRef, headSha };
|
|
225
225
|
},
|
|
226
226
|
createOrUpdatePullRequest: async (input, signal) => {
|
|
227
227
|
const { number, url, created } = await gateway.createOrUpdatePullRequest(input, signal);
|
|
@@ -231,6 +231,10 @@ class GatewayRequestHandler {
|
|
|
231
231
|
await gateway.convertPullRequestToDraft(number, signal);
|
|
232
232
|
return {};
|
|
233
233
|
},
|
|
234
|
+
setConflictsStatus: async (status, signal) => {
|
|
235
|
+
await gateway.setConflictsStatus(status, signal);
|
|
236
|
+
return {};
|
|
237
|
+
},
|
|
234
238
|
deleteBranch: async ({ branch }, signal) => {
|
|
235
239
|
await gateway.deleteBranch(branch, signal);
|
|
236
240
|
return {};
|
|
@@ -409,7 +413,7 @@ class GatewayRequestHandler {
|
|
|
409
413
|
}
|
|
410
414
|
async startFetch(req, res) {
|
|
411
415
|
await this.prepared;
|
|
412
|
-
const { have } = parseRequest(fetchRequest, await readJsonBody(req), 'fetch');
|
|
416
|
+
const { have, fresh } = parseRequest(fetchRequest, await readJsonBody(req), 'fetch');
|
|
413
417
|
this.evictJobs();
|
|
414
418
|
const running = [...this.jobs.values()].filter((job) => job.settledAt === null).length;
|
|
415
419
|
if (running >= MAX_RUNNING_FETCH_JOBS) {
|
|
@@ -434,13 +438,19 @@ class GatewayRequestHandler {
|
|
|
434
438
|
progress: 0,
|
|
435
439
|
// No signal: the job outlives the request that started it (see the module doc).
|
|
436
440
|
settled: this.gateway
|
|
437
|
-
.fetch({ have }, undefined, () => {
|
|
441
|
+
.fetch({ have, fresh }, undefined, () => {
|
|
438
442
|
job.progress++;
|
|
439
443
|
})
|
|
440
|
-
.then(({ refs, bundleId }) => {
|
|
444
|
+
.then(({ refs, bundleId, startedAt }) => {
|
|
441
445
|
job.answer = {
|
|
442
446
|
ok: true,
|
|
443
|
-
value: {
|
|
447
|
+
value: {
|
|
448
|
+
state: 'done',
|
|
449
|
+
progress: job.progress,
|
|
450
|
+
refs: [...refs],
|
|
451
|
+
bundleId,
|
|
452
|
+
startedAt,
|
|
453
|
+
},
|
|
444
454
|
};
|
|
445
455
|
}, (err) => {
|
|
446
456
|
job.answer = { ok: false, error: encodeError(err) };
|
|
@@ -703,11 +713,12 @@ class GitHubGatewayClient {
|
|
|
703
713
|
*/
|
|
704
714
|
async fetch(request, signal, onProgress) {
|
|
705
715
|
const have = [...new Set(request.have)];
|
|
716
|
+
const json = { have, fresh: request.fresh === true };
|
|
706
717
|
for (let attempt = 1;; attempt++) {
|
|
707
718
|
const started = await this.send({
|
|
708
719
|
method: 'POST',
|
|
709
720
|
path: FETCH_ROUTE,
|
|
710
|
-
json
|
|
721
|
+
json,
|
|
711
722
|
signal,
|
|
712
723
|
});
|
|
713
724
|
const { job } = await readAnswer(started, fetchStarted, signal);
|
|
@@ -754,8 +765,9 @@ class GitHubGatewayClient {
|
|
|
754
765
|
progress = poll.progress;
|
|
755
766
|
movedAt = performance.now();
|
|
756
767
|
}
|
|
757
|
-
if (poll.state === 'done')
|
|
758
|
-
return { refs: new Map(poll.refs), bundleId: poll.bundleId };
|
|
768
|
+
if (poll.state === 'done') {
|
|
769
|
+
return { refs: new Map(poll.refs), bundleId: poll.bundleId, startedAt: poll.startedAt };
|
|
770
|
+
}
|
|
759
771
|
if (performance.now() - movedAt >= this.fetchInactivityMs) {
|
|
760
772
|
throw new GitHubTransientError('github-fetch-failed', `The GitHub gateway's fetch showed no progress for ${this.fetchInactivityMs}ms; it runs on, ` +
|
|
761
773
|
'and the next fetch joins it');
|
|
@@ -816,6 +828,9 @@ class GitHubGatewayClient {
|
|
|
816
828
|
async convertPullRequestToDraft(number, signal) {
|
|
817
829
|
await this.call('convertPullRequestToDraft', { number }, signal);
|
|
818
830
|
}
|
|
831
|
+
async setConflictsStatus(status, signal) {
|
|
832
|
+
await this.call('setConflictsStatus', status, signal);
|
|
833
|
+
}
|
|
819
834
|
async deleteBranch(branch, signal) {
|
|
820
835
|
await this.call('deleteBranch', { branch }, signal);
|
|
821
836
|
}
|
|
@@ -9,10 +9,13 @@ export declare const MAX_JSON_REQUEST_BYTES: number;
|
|
|
9
9
|
export declare const MAX_JSON_ANSWER_BYTES: number;
|
|
10
10
|
export declare const fetchRequest: z.ZodObject<{
|
|
11
11
|
have: z.ZodArray<z.ZodString, "many">;
|
|
12
|
+
fresh: z.ZodOptional<z.ZodBoolean>;
|
|
12
13
|
}, "strict", z.ZodTypeAny, {
|
|
13
14
|
have: string[];
|
|
15
|
+
fresh?: boolean | undefined;
|
|
14
16
|
}, {
|
|
15
17
|
have: string[];
|
|
18
|
+
fresh?: boolean | undefined;
|
|
16
19
|
}>;
|
|
17
20
|
export declare const pushRequest: z.ZodObject<{
|
|
18
21
|
branch: z.ZodString;
|
|
@@ -56,13 +59,13 @@ export declare const requests: {
|
|
|
56
59
|
}, "strict", z.ZodTypeAny, {
|
|
57
60
|
title: string;
|
|
58
61
|
base: string;
|
|
59
|
-
head: string;
|
|
60
62
|
body: string;
|
|
63
|
+
head: string;
|
|
61
64
|
}, {
|
|
62
65
|
title: string;
|
|
63
66
|
base: string;
|
|
64
|
-
head: string;
|
|
65
67
|
body: string;
|
|
68
|
+
head: string;
|
|
66
69
|
}>;
|
|
67
70
|
readonly updatePullRequest: z.ZodObject<{
|
|
68
71
|
number: z.ZodNumber;
|
|
@@ -97,15 +100,15 @@ export declare const requests: {
|
|
|
97
100
|
}, "strict", z.ZodTypeAny, {
|
|
98
101
|
title: string;
|
|
99
102
|
base: string;
|
|
100
|
-
head: string;
|
|
101
103
|
body: string;
|
|
104
|
+
head: string;
|
|
102
105
|
markReadyIfDraft?: boolean | undefined;
|
|
103
106
|
mergeSectionIntoBody?: boolean | undefined;
|
|
104
107
|
}, {
|
|
105
108
|
title: string;
|
|
106
109
|
base: string;
|
|
107
|
-
head: string;
|
|
108
110
|
body: string;
|
|
111
|
+
head: string;
|
|
109
112
|
markReadyIfDraft?: boolean | undefined;
|
|
110
113
|
mergeSectionIntoBody?: boolean | undefined;
|
|
111
114
|
}>;
|
|
@@ -116,6 +119,25 @@ export declare const requests: {
|
|
|
116
119
|
}, {
|
|
117
120
|
number: number;
|
|
118
121
|
}>;
|
|
122
|
+
readonly setConflictsStatus: z.ZodObject<{
|
|
123
|
+
pullRequest: z.ZodNumber;
|
|
124
|
+
sha: z.ZodString;
|
|
125
|
+
state: z.ZodEnum<["success", "failure"]>;
|
|
126
|
+
description: z.ZodString;
|
|
127
|
+
targetUrl: z.ZodOptional<z.ZodString>;
|
|
128
|
+
}, "strict", z.ZodTypeAny, {
|
|
129
|
+
description: string;
|
|
130
|
+
state: "success" | "failure";
|
|
131
|
+
sha: string;
|
|
132
|
+
pullRequest: number;
|
|
133
|
+
targetUrl?: string | undefined;
|
|
134
|
+
}, {
|
|
135
|
+
description: string;
|
|
136
|
+
state: "success" | "failure";
|
|
137
|
+
sha: string;
|
|
138
|
+
pullRequest: number;
|
|
139
|
+
targetUrl?: string | undefined;
|
|
140
|
+
}>;
|
|
119
141
|
readonly deleteBranch: z.ZodObject<{
|
|
120
142
|
branch: z.ZodString;
|
|
121
143
|
}, "strict", z.ZodTypeAny, {
|
|
@@ -143,15 +165,15 @@ export declare const answers: {
|
|
|
143
165
|
}, "strict", z.ZodTypeAny, {
|
|
144
166
|
settingsBranch: string | null;
|
|
145
167
|
namespace: string;
|
|
146
|
-
legacy: string[];
|
|
147
168
|
configured: string[];
|
|
148
169
|
githubDefault: string | null;
|
|
170
|
+
legacy: string[];
|
|
149
171
|
}, {
|
|
150
172
|
settingsBranch: string | null;
|
|
151
173
|
namespace: string;
|
|
152
|
-
legacy: string[];
|
|
153
174
|
configured: string[];
|
|
154
175
|
githubDefault: string | null;
|
|
176
|
+
legacy: string[];
|
|
155
177
|
}>;
|
|
156
178
|
}, "strict", z.ZodTypeAny, {
|
|
157
179
|
ready: boolean;
|
|
@@ -161,9 +183,9 @@ export declare const answers: {
|
|
|
161
183
|
branchPolicy: {
|
|
162
184
|
settingsBranch: string | null;
|
|
163
185
|
namespace: string;
|
|
164
|
-
legacy: string[];
|
|
165
186
|
configured: string[];
|
|
166
187
|
githubDefault: string | null;
|
|
188
|
+
legacy: string[];
|
|
167
189
|
};
|
|
168
190
|
}, {
|
|
169
191
|
ready: boolean;
|
|
@@ -173,9 +195,9 @@ export declare const answers: {
|
|
|
173
195
|
branchPolicy: {
|
|
174
196
|
settingsBranch: string | null;
|
|
175
197
|
namespace: string;
|
|
176
|
-
legacy: string[];
|
|
177
198
|
configured: string[];
|
|
178
199
|
githubDefault: string | null;
|
|
200
|
+
legacy: string[];
|
|
179
201
|
};
|
|
180
202
|
}>;
|
|
181
203
|
readonly defaultBranch: z.ZodObject<{
|
|
@@ -211,12 +233,14 @@ export declare const answers: {
|
|
|
211
233
|
body: z.ZodNullable<z.ZodString>;
|
|
212
234
|
mergeable_state: z.ZodOptional<z.ZodString>;
|
|
213
235
|
baseRef: z.ZodOptional<z.ZodString>;
|
|
236
|
+
headSha: z.ZodOptional<z.ZodString>;
|
|
214
237
|
}, "strict", z.ZodTypeAny, {
|
|
215
238
|
merged: boolean;
|
|
216
239
|
state: "open" | "closed";
|
|
217
240
|
body: string | null;
|
|
218
241
|
merged_at: string | null;
|
|
219
242
|
node_id: string;
|
|
243
|
+
headSha?: string | undefined;
|
|
220
244
|
mergeable_state?: string | undefined;
|
|
221
245
|
baseRef?: string | undefined;
|
|
222
246
|
}, {
|
|
@@ -225,6 +249,7 @@ export declare const answers: {
|
|
|
225
249
|
body: string | null;
|
|
226
250
|
merged_at: string | null;
|
|
227
251
|
node_id: string;
|
|
252
|
+
headSha?: string | undefined;
|
|
228
253
|
mergeable_state?: string | undefined;
|
|
229
254
|
baseRef?: string | undefined;
|
|
230
255
|
}>;
|
|
@@ -243,6 +268,7 @@ export declare const answers: {
|
|
|
243
268
|
url: string;
|
|
244
269
|
}>;
|
|
245
270
|
readonly convertPullRequestToDraft: z.ZodObject<{}, "strict", z.ZodTypeAny, {}, {}>;
|
|
271
|
+
readonly setConflictsStatus: z.ZodObject<{}, "strict", z.ZodTypeAny, {}, {}>;
|
|
246
272
|
readonly deleteBranch: z.ZodObject<{}, "strict", z.ZodTypeAny, {}, {}>;
|
|
247
273
|
readonly authSnapshot: z.ZodObject<{
|
|
248
274
|
users: z.ZodArray<z.ZodObject<{
|
|
@@ -312,21 +338,21 @@ export declare const answers: {
|
|
|
312
338
|
kind: z.ZodEnum<["auto-scaling", "spot"]>;
|
|
313
339
|
reason: z.ZodString;
|
|
314
340
|
}, "strict", z.ZodTypeAny, {
|
|
315
|
-
kind: "auto-scaling" | "spot";
|
|
316
341
|
reason: string;
|
|
317
|
-
}, {
|
|
318
342
|
kind: "auto-scaling" | "spot";
|
|
343
|
+
}, {
|
|
319
344
|
reason: string;
|
|
345
|
+
kind: "auto-scaling" | "spot";
|
|
320
346
|
}>>;
|
|
321
347
|
}, "strict", z.ZodTypeAny, {
|
|
322
348
|
notice: {
|
|
323
|
-
kind: "auto-scaling" | "spot";
|
|
324
349
|
reason: string;
|
|
350
|
+
kind: "auto-scaling" | "spot";
|
|
325
351
|
} | null;
|
|
326
352
|
}, {
|
|
327
353
|
notice: {
|
|
328
|
-
kind: "auto-scaling" | "spot";
|
|
329
354
|
reason: string;
|
|
355
|
+
kind: "auto-scaling" | "spot";
|
|
330
356
|
} | null;
|
|
331
357
|
}>;
|
|
332
358
|
readonly completeLifecycle: z.ZodObject<{}, "strict", z.ZodTypeAny, {}, {}>;
|
|
@@ -360,15 +386,18 @@ export declare const fetchPoll: z.ZodDiscriminatedUnion<"state", [z.ZodObject<{
|
|
|
360
386
|
/** Every GitHub branch and its tip, as pairs: a `Map` has no JSON form of its own. */
|
|
361
387
|
refs: z.ZodArray<z.ZodTuple<[z.ZodString, z.ZodString], null>, "many">;
|
|
362
388
|
bundleId: z.ZodNullable<z.ZodString>;
|
|
389
|
+
startedAt: z.ZodNumber;
|
|
363
390
|
}, "strict", z.ZodTypeAny, {
|
|
364
391
|
state: "done";
|
|
365
392
|
refs: [string, string][];
|
|
366
393
|
progress: number;
|
|
394
|
+
startedAt: number;
|
|
367
395
|
bundleId: string | null;
|
|
368
396
|
}, {
|
|
369
397
|
state: "done";
|
|
370
398
|
refs: [string, string][];
|
|
371
399
|
progress: number;
|
|
400
|
+
startedAt: number;
|
|
372
401
|
bundleId: string | null;
|
|
373
402
|
}>]>;
|
|
374
403
|
export declare const pushAnswer: z.ZodUnion<[z.ZodObject<{
|
|
@@ -391,15 +420,15 @@ export declare const pushAnswer: z.ZodUnion<[z.ZodObject<{
|
|
|
391
420
|
pastStaleLease: z.ZodBoolean;
|
|
392
421
|
}, "strict", z.ZodTypeAny, {
|
|
393
422
|
kind: "pushed";
|
|
423
|
+
moved: true;
|
|
394
424
|
from: string | null;
|
|
395
425
|
to: string;
|
|
396
|
-
moved: true;
|
|
397
426
|
pastStaleLease: boolean;
|
|
398
427
|
}, {
|
|
399
428
|
kind: "pushed";
|
|
429
|
+
moved: true;
|
|
400
430
|
from: string | null;
|
|
401
431
|
to: string;
|
|
402
|
-
moved: true;
|
|
403
432
|
pastStaleLease: boolean;
|
|
404
433
|
}>, z.ZodObject<{
|
|
405
434
|
kind: z.ZodLiteral<"need-objects">;
|
|
@@ -423,24 +452,24 @@ export declare function readAnswerBody(json: unknown): {
|
|
|
423
452
|
declare const errorEnvelope: z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
|
|
424
453
|
kind: z.ZodLiteral<"refusal">;
|
|
425
454
|
push: z.ZodBoolean;
|
|
426
|
-
operation: z.ZodEnum<["push", "delete", "pull-request"]>;
|
|
455
|
+
operation: z.ZodEnum<["push", "delete", "pull-request", "commit-status"]>;
|
|
427
456
|
branch: z.ZodString;
|
|
428
|
-
reason: z.ZodEnum<["invalid", "protected", "settings", "other-settings", "outside-namespace", "not-a-commit", "too-long"]>;
|
|
457
|
+
reason: z.ZodEnum<["invalid", "protected", "settings", "other-settings", "outside-namespace", "not-a-commit", "too-long", "not-head"]>;
|
|
429
458
|
message: z.ZodString;
|
|
430
459
|
}, "strict", z.ZodTypeAny, {
|
|
431
460
|
branch: string;
|
|
432
461
|
push: boolean;
|
|
433
462
|
message: string;
|
|
463
|
+
reason: "settings" | "protected" | "invalid" | "other-settings" | "outside-namespace" | "not-a-commit" | "too-long" | "not-head";
|
|
434
464
|
kind: "refusal";
|
|
435
|
-
|
|
436
|
-
operation: "push" | "delete" | "pull-request";
|
|
465
|
+
operation: "push" | "delete" | "pull-request" | "commit-status";
|
|
437
466
|
}, {
|
|
438
467
|
branch: string;
|
|
439
468
|
push: boolean;
|
|
440
469
|
message: string;
|
|
470
|
+
reason: "settings" | "protected" | "invalid" | "other-settings" | "outside-namespace" | "not-a-commit" | "too-long" | "not-head";
|
|
441
471
|
kind: "refusal";
|
|
442
|
-
|
|
443
|
-
operation: "push" | "delete" | "pull-request";
|
|
472
|
+
operation: "push" | "delete" | "pull-request" | "commit-status";
|
|
444
473
|
}>, z.ZodObject<{
|
|
445
474
|
kind: z.ZodLiteral<"push">;
|
|
446
475
|
pushKind: z.ZodEnum<["rejected", "rejected-after-stale-lease", "seen-commit-gone"]>;
|
|
@@ -37,7 +37,9 @@ const objectId = z.string().regex(/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/, 'not a full
|
|
|
37
37
|
const name = z.string().max(4096);
|
|
38
38
|
const prNumber = z.number().int().safe();
|
|
39
39
|
const message = z.string().max(MAX_MESSAGE_LENGTH);
|
|
40
|
-
export const fetchRequest = z
|
|
40
|
+
export const fetchRequest = z
|
|
41
|
+
.object({ have: z.array(objectId).max(MAX_FETCH_HAVE), fresh: z.boolean().optional() })
|
|
42
|
+
.strict();
|
|
41
43
|
export const pushRequest = z
|
|
42
44
|
.object({
|
|
43
45
|
branch: name,
|
|
@@ -75,6 +77,15 @@ export const requests = {
|
|
|
75
77
|
})
|
|
76
78
|
.strict(),
|
|
77
79
|
convertPullRequestToDraft: z.object({ number: prNumber }).strict(),
|
|
80
|
+
setConflictsStatus: z
|
|
81
|
+
.object({
|
|
82
|
+
pullRequest: prNumber,
|
|
83
|
+
sha: objectId,
|
|
84
|
+
state: z.enum(['success', 'failure']),
|
|
85
|
+
description: z.string().max(4096),
|
|
86
|
+
targetUrl: z.string().max(4096).optional(),
|
|
87
|
+
})
|
|
88
|
+
.strict(),
|
|
78
89
|
deleteBranch: z.object({ branch: name }).strict(),
|
|
79
90
|
authSnapshot: z.object({}).strict(),
|
|
80
91
|
termination: z.object({}).strict(),
|
|
@@ -114,10 +125,12 @@ export const answers = {
|
|
|
114
125
|
body: pullRequestText.body.nullable(),
|
|
115
126
|
mergeable_state: z.string().max(64).optional(),
|
|
116
127
|
baseRef: z.string().max(256).optional(),
|
|
128
|
+
headSha: objectId.optional(),
|
|
117
129
|
})
|
|
118
130
|
.strict(),
|
|
119
131
|
createOrUpdatePullRequest: pullRequestRef.extend({ created: z.boolean() }).strict(),
|
|
120
132
|
convertPullRequestToDraft: empty,
|
|
133
|
+
setConflictsStatus: empty,
|
|
121
134
|
deleteBranch: empty,
|
|
122
135
|
authSnapshot: z
|
|
123
136
|
.object({
|
|
@@ -165,6 +178,7 @@ export const JSON_ROUTES = {
|
|
|
165
178
|
getPullRequest: `${WIRE_PREFIX}/pull-request/get`,
|
|
166
179
|
createOrUpdatePullRequest: `${WIRE_PREFIX}/pull-request/create-or-update`,
|
|
167
180
|
convertPullRequestToDraft: `${WIRE_PREFIX}/pull-request/convert-to-draft`,
|
|
181
|
+
setConflictsStatus: `${WIRE_PREFIX}/pull-request/conflicts-status`,
|
|
168
182
|
deleteBranch: `${WIRE_PREFIX}/branch/delete`,
|
|
169
183
|
authSnapshot: `${WIRE_PREFIX}/auth-snapshot`,
|
|
170
184
|
termination: `${WIRE_PREFIX}/termination`,
|
|
@@ -187,6 +201,7 @@ export const fetchPoll = z.discriminatedUnion('state', [
|
|
|
187
201
|
/** Every GitHub branch and its tip, as pairs: a `Map` has no JSON form of its own. */
|
|
188
202
|
refs: z.array(z.tuple([name, objectId])).max(1_000_000),
|
|
189
203
|
bundleId: jobId.nullable(),
|
|
204
|
+
startedAt: z.number().int().nonnegative(),
|
|
190
205
|
})
|
|
191
206
|
.strict(),
|
|
192
207
|
]);
|
|
@@ -236,7 +251,7 @@ const errorEnvelope = z.discriminatedUnion('kind', [
|
|
|
236
251
|
.object({
|
|
237
252
|
kind: z.literal('refusal'),
|
|
238
253
|
push: z.boolean(),
|
|
239
|
-
operation: z.enum(['push', 'delete', 'pull-request']),
|
|
254
|
+
operation: z.enum(['push', 'delete', 'pull-request', 'commit-status']),
|
|
240
255
|
branch: name,
|
|
241
256
|
reason: z.enum([
|
|
242
257
|
'invalid',
|
|
@@ -246,6 +261,7 @@ const errorEnvelope = z.discriminatedUnion('kind', [
|
|
|
246
261
|
'outside-namespace',
|
|
247
262
|
'not-a-commit',
|
|
248
263
|
'too-long',
|
|
264
|
+
'not-head',
|
|
249
265
|
]),
|
|
250
266
|
message,
|
|
251
267
|
})
|
|
@@ -457,7 +473,7 @@ export function decodeError(value) {
|
|
|
457
473
|
const { push, operation, branch, reason } = envelope;
|
|
458
474
|
if (!push)
|
|
459
475
|
return new GatewayRefusalError(operation, branch, reason, envelope.message);
|
|
460
|
-
if (operation !== 'push' || reason === 'too-long') {
|
|
476
|
+
if (operation !== 'push' || reason === 'too-long' || reason === 'not-head') {
|
|
461
477
|
return new GitHubGatewayProtocolError(`The GitHub gateway answered with a push refusal for ${operation}, reason ${reason}`);
|
|
462
478
|
}
|
|
463
479
|
const err = new RefusedPushError(branch, reason);
|
|
@@ -15,6 +15,11 @@ export interface GitHubFetchResult {
|
|
|
15
15
|
* request's `have` and their history; null when that leaves nothing to send.
|
|
16
16
|
*/
|
|
17
17
|
bundleId: string | null;
|
|
18
|
+
/**
|
|
19
|
+
* When the GitHub fetch `refs` come from began, by the gateway's clock, which is the worker's:
|
|
20
|
+
* they share the instance. A fetch can be joined or reused, so this can predate the request.
|
|
21
|
+
*/
|
|
22
|
+
startedAt: number;
|
|
18
23
|
}
|
|
19
24
|
/** The most `have` IDs one fetch takes. */
|
|
20
25
|
export declare const MAX_FETCH_HAVE = 10000;
|
|
@@ -81,7 +86,19 @@ export type GitHubPullRequest = Pick<RestEndpointMethodTypes['pulls']['get']['re
|
|
|
81
86
|
mergeable_state?: string;
|
|
82
87
|
/** The branch the pull request merges into on GitHub, which a person can change. */
|
|
83
88
|
baseRef?: string;
|
|
89
|
+
/** The pull request's head commit, read with `mergeable_state`. */
|
|
90
|
+
headSha?: string;
|
|
84
91
|
};
|
|
92
|
+
/** A "CanopyCMS conflicts" status for the head of pull request `pullRequest`. */
|
|
93
|
+
export interface GitHubConflictsStatus {
|
|
94
|
+
pullRequest: number;
|
|
95
|
+
/** The pull request's head commit; the gateway refuses any other. */
|
|
96
|
+
sha: string;
|
|
97
|
+
state: 'success' | 'failure';
|
|
98
|
+
description: string;
|
|
99
|
+
/** The editor, opened on the branch. */
|
|
100
|
+
targetUrl?: string;
|
|
101
|
+
}
|
|
85
102
|
export type GitHubCreateOrUpdatePullRequest = Omit<CreateOrUpdatePullRequestParams, 'octokit' | 'owner' | 'repo' | 'signal'>;
|
|
86
103
|
/**
|
|
87
104
|
* Everything the worker does with GitHub, and the only place the credential and Octokit are used.
|
|
@@ -114,10 +131,12 @@ export interface GitHubGateway {
|
|
|
114
131
|
* Fetch every GitHub branch, and answer with all of them and a bundle of the commits `have`
|
|
115
132
|
* lacks. `have` is the caller's own tips, at most {@link MAX_FETCH_HAVE} object IDs; any the
|
|
116
133
|
* gateway does not hold as a commit is ignored. A new fetch replaces a bundle not yet opened.
|
|
117
|
-
* `
|
|
134
|
+
* `fresh` takes no GitHub fetch begun before the request arrived. `onProgress` is told of each
|
|
135
|
+
* sign of life from the gateway's git while the caller waits.
|
|
118
136
|
*/
|
|
119
137
|
fetch(request: {
|
|
120
138
|
have: readonly string[];
|
|
139
|
+
fresh?: boolean;
|
|
121
140
|
}, signal?: AbortSignal, onProgress?: () => void): Promise<GitHubFetchResult>;
|
|
122
141
|
/**
|
|
123
142
|
* The bundle a fetch answered with, readable once. Throws {@link GitHubTransientError}
|
|
@@ -157,6 +176,13 @@ export interface GitHubGateway {
|
|
|
157
176
|
created: boolean;
|
|
158
177
|
}>;
|
|
159
178
|
convertPullRequestToDraft(number: number, signal?: AbortSignal): Promise<void>;
|
|
179
|
+
/**
|
|
180
|
+
* Set the {@link CONFLICTS_STATUS_CONTEXT} status on a pull request's head. Refused
|
|
181
|
+
* (`GatewayRefusalError`) with reason `not-head` unless `sha` is the head GitHub holds now, as
|
|
182
|
+
* `updatePullRequest` refuses a pull request it would not update, and for a description or link
|
|
183
|
+
* `assertCommitStatusText` refuses (worker/branch-policy.ts).
|
|
184
|
+
*/
|
|
185
|
+
setConflictsStatus(status: GitHubConflictsStatus, signal?: AbortSignal): Promise<void>;
|
|
160
186
|
deleteBranch(branch: string, signal?: AbortSignal): Promise<void>;
|
|
161
187
|
/**
|
|
162
188
|
* The auth provider's users, groups and memberships, which the worker writes to the auth cache:
|
|
@@ -4,8 +4,9 @@ import { createCanopyOctokit, createOrUpdatePullRequest, } from '../github-servi
|
|
|
4
4
|
import { getErrorMessage } from '../utils/error.js';
|
|
5
5
|
import { isStaleLeaseRejection, workflowPushRefusalFile } from '../utils/git.js';
|
|
6
6
|
import { isTransientAuthFailure } from './github-auth.js';
|
|
7
|
+
import { isPermissionDenial } from './github-errors.js';
|
|
7
8
|
import { GitHubMirror, GitHubTransientError, RefusedPushError, assertPlainBranchName, isObjectId, } from './github-mirror.js';
|
|
8
|
-
import { BranchPolicy, GatewayRefusalError, assertPullRequestText, } from './branch-policy.js';
|
|
9
|
+
import { BranchPolicy, GatewayRefusalError, assertCommitStatusText, assertPullRequestText, } from './branch-policy.js';
|
|
9
10
|
import { gatewayLog, gatewayLogError, gatewayLogWarn, redactEgress, redactError, redactValue, registerLiveSecret, } from './gateway-egress.js';
|
|
10
11
|
import { GatewayInstance, untilAborted } from './gateway-instance.js';
|
|
11
12
|
/** The most `have` IDs one fetch takes. */
|
|
@@ -47,6 +48,11 @@ export function describeMove(update) {
|
|
|
47
48
|
const to = update.to.slice(0, 7);
|
|
48
49
|
return update.from === null ? `new branch at ${to}` : `${update.from.slice(0, 7)}..${to}`;
|
|
49
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* The context of the commit status the worker sets on a pull request's head. Fixed by the gateway,
|
|
53
|
+
* so a worker can set no other; adopters name it when they make it a required check.
|
|
54
|
+
*/
|
|
55
|
+
const CONFLICTS_STATUS_CONTEXT = 'CanopyCMS conflicts';
|
|
50
56
|
/** The in-process {@link GitHubGateway}. */
|
|
51
57
|
export function createLocalGitHubGateway(options) {
|
|
52
58
|
return new LocalGitHubGateway(options);
|
|
@@ -197,11 +203,14 @@ class LocalGitHubGateway {
|
|
|
197
203
|
* own, which repairs a corrupt mirror first ({@link GitHubMirror.heal}), and its failure re-reads
|
|
198
204
|
* the credential when it reached GitHub, whoever started it.
|
|
199
205
|
*/
|
|
200
|
-
async joinGitHubFetch(resolve, signal
|
|
206
|
+
async joinGitHubFetch(resolve, signal,
|
|
207
|
+
/** Join or reuse only a fetch that began at or after this time. */
|
|
208
|
+
notBefore) {
|
|
201
209
|
const current = this.githubFetch;
|
|
202
210
|
const window = this.options.fetchCoalesceMs ?? FETCH_COALESCE_MS;
|
|
203
211
|
const reusable = current !== null &&
|
|
204
212
|
!current.failed &&
|
|
213
|
+
(notBefore === undefined || current.startedAt >= notBefore) &&
|
|
205
214
|
(current.completedAt === null ||
|
|
206
215
|
(Date.now() - current.completedAt < window && this.isCurrent(current.snapshot)));
|
|
207
216
|
const job = reusable ? current : this.startGitHubFetch(resolve);
|
|
@@ -223,7 +232,7 @@ class LocalGitHubGateway {
|
|
|
223
232
|
async afterGitHubFetch(join, fn) {
|
|
224
233
|
for (let attempt = 1;; attempt++) {
|
|
225
234
|
const snapshot = await join();
|
|
226
|
-
const result = await this.mirror.exclusive(async (session) => this.isCurrent(snapshot) ? { value: await fn(session) } : null);
|
|
235
|
+
const result = await this.mirror.exclusive(async (session) => this.isCurrent(snapshot) ? { value: await fn(session, snapshot) } : null);
|
|
227
236
|
if (result !== null)
|
|
228
237
|
return result.value;
|
|
229
238
|
if (attempt === 2) {
|
|
@@ -234,6 +243,7 @@ class LocalGitHubGateway {
|
|
|
234
243
|
/** Start a GitHub fetch, taking its place at once, before the credential resolves. */
|
|
235
244
|
startGitHubFetch(resolve) {
|
|
236
245
|
let reachedGitHub = false;
|
|
246
|
+
const startedAt = Date.now();
|
|
237
247
|
const run = async () => {
|
|
238
248
|
const credential = await resolve();
|
|
239
249
|
const own = {
|
|
@@ -248,12 +258,18 @@ class LocalGitHubGateway {
|
|
|
248
258
|
const pushes = this.pushes;
|
|
249
259
|
await this.mirror.heal(mirror, own);
|
|
250
260
|
await this.mirror.fetchFromGitHub(mirror, own);
|
|
251
|
-
return { generation: this.mirror.generation, pushes };
|
|
261
|
+
return { generation: this.mirror.generation, pushes, startedAt };
|
|
252
262
|
});
|
|
253
263
|
await this.mirror.afterFetch();
|
|
254
264
|
return snapshot;
|
|
255
265
|
};
|
|
256
|
-
const job = {
|
|
266
|
+
const job = {
|
|
267
|
+
failed: false,
|
|
268
|
+
completedAt: null,
|
|
269
|
+
snapshot: null,
|
|
270
|
+
startedAt,
|
|
271
|
+
done: run(),
|
|
272
|
+
};
|
|
257
273
|
job.done.then((snapshot) => {
|
|
258
274
|
job.completedAt = Date.now();
|
|
259
275
|
job.snapshot = snapshot;
|
|
@@ -472,19 +488,20 @@ class LocalGitHubGateway {
|
|
|
472
488
|
}
|
|
473
489
|
}
|
|
474
490
|
fetchOnce(request, signal) {
|
|
491
|
+
const notBefore = request.fresh ? Date.now() : undefined;
|
|
475
492
|
const have = [...new Set(request.have)];
|
|
476
493
|
if (have.length > MAX_FETCH_HAVE) {
|
|
477
494
|
throw new Error(`A fetch takes at most ${MAX_FETCH_HAVE} have IDs, not ${have.length}`);
|
|
478
495
|
}
|
|
479
496
|
for (const id of have)
|
|
480
497
|
assertObjectId(id);
|
|
481
|
-
return this.credentialed(signal, (reach) => this.afterGitHubFetch(() => this.joinGitHubFetch(() => this.credential(reach), signal), async (mirror) => {
|
|
498
|
+
return this.credentialed(signal, (reach) => this.afterGitHubFetch(() => this.joinGitHubFetch(() => this.credential(reach), signal, notBefore), async (mirror, snapshot) => {
|
|
482
499
|
this.mirror.assertUsable();
|
|
483
500
|
const refs = await mirror.heads();
|
|
484
501
|
const file = await this.mirror.outgoingBundlePath();
|
|
485
502
|
const created = await mirror.createBundle(have, file, signal);
|
|
486
503
|
const bundleId = await this.mirror.setPendingBundle(created ? file : null);
|
|
487
|
-
return { refs, bundleId };
|
|
504
|
+
return { refs, bundleId, startedAt: snapshot.startedAt };
|
|
488
505
|
}));
|
|
489
506
|
}
|
|
490
507
|
openBundle(bundleId) {
|
|
@@ -688,6 +705,7 @@ class LocalGitHubGateway {
|
|
|
688
705
|
body: ours ? (data.body ?? '') : null,
|
|
689
706
|
mergeable_state: data.mergeable_state,
|
|
690
707
|
baseRef: data.base.ref,
|
|
708
|
+
headSha: data.head.sha,
|
|
691
709
|
};
|
|
692
710
|
});
|
|
693
711
|
}
|
|
@@ -726,6 +744,36 @@ class LocalGitHubGateway {
|
|
|
726
744
|
await this.api(reach, () => this.octokit.graphql(`mutation($id: ID!) { convertPullRequestToDraft(input: { pullRequestId: $id }) { pullRequest { isDraft } } }`, { id: pr.node_id, request: { signal } }));
|
|
727
745
|
});
|
|
728
746
|
}
|
|
747
|
+
setConflictsStatus(status, signal) {
|
|
748
|
+
return this.credentialed(signal, async (reach) => {
|
|
749
|
+
const { pullRequest, sha, state, description, targetUrl } = status;
|
|
750
|
+
const refusing = `Refusing to set a status on pull request #${pullRequest}`;
|
|
751
|
+
const pr = await this.assertOwnPullRequest(reach, pullRequest, refusing, signal);
|
|
752
|
+
assertCommitStatusText(refusing, pr.head.ref, { description, targetUrl });
|
|
753
|
+
if (pr.head.sha !== sha) {
|
|
754
|
+
throw new GatewayRefusalError('commit-status', pr.head.ref, 'not-head', `${refusing}: ${sha.slice(0, 7)} is not its head, ${pr.head.sha.slice(0, 7)}`);
|
|
755
|
+
}
|
|
756
|
+
try {
|
|
757
|
+
await this.api(reach, () => this.octokit.repos.createCommitStatus({
|
|
758
|
+
...this.repo,
|
|
759
|
+
sha,
|
|
760
|
+
state,
|
|
761
|
+
context: CONFLICTS_STATUS_CONTEXT,
|
|
762
|
+
description,
|
|
763
|
+
...(targetUrl !== undefined && { target_url: targetUrl }),
|
|
764
|
+
request: { signal },
|
|
765
|
+
}));
|
|
766
|
+
}
|
|
767
|
+
catch (err) {
|
|
768
|
+
// A credential without the Commit statuses permission, which the worker meets every cycle
|
|
769
|
+
// until it is granted: the `pulls.get` above just proved the credential works, so a
|
|
770
|
+
// re-read would only spend the refresh floor.
|
|
771
|
+
if (isPermissionDenial(err))
|
|
772
|
+
reach.failed = false;
|
|
773
|
+
throw err;
|
|
774
|
+
}
|
|
775
|
+
});
|
|
776
|
+
}
|
|
729
777
|
deleteBranch(branch, signal) {
|
|
730
778
|
return this.credentialed(signal, async (reach) => {
|
|
731
779
|
await assertPlainBranchName(branch, await this.mirror.checkGit(), 'delete');
|
|
@@ -12,11 +12,13 @@ export interface TransferContext {
|
|
|
12
12
|
/**
|
|
13
13
|
* Fetch every GitHub branch into `remote.git`'s tracking namespace (`GITHUB_TRACKING_REF_PREFIX`),
|
|
14
14
|
* which then holds exactly GitHub's branches. `have` is every tip `remote.git` holds, so the
|
|
15
|
-
* bundle carries only what it lacks.
|
|
16
|
-
*
|
|
15
|
+
* bundle carries only what it lacks. `fresh` takes no GitHub fetch the gateway began before this
|
|
16
|
+
* call. Returns GitHub's branches, when the GitHub fetch they come from began, and what an operator
|
|
17
|
+
* must fix in remote.git for the namespace to keep up, if anything (see {@link moveTrackingRefs}).
|
|
17
18
|
*/
|
|
18
|
-
export declare function fetchGitHubIntoRemoteGit(ctx: TransferContext, signal?: AbortSignal): Promise<{
|
|
19
|
+
export declare function fetchGitHubIntoRemoteGit(ctx: TransferContext, signal?: AbortSignal, fresh?: boolean): Promise<{
|
|
19
20
|
refs: ReadonlyMap<string, string>;
|
|
21
|
+
fetchedAt: number;
|
|
20
22
|
needsOperator: string | null;
|
|
21
23
|
}>;
|
|
22
24
|
/**
|