pr-report-core 0.1.0 → 0.2.1
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/dist/checks.d.mts +61 -62
- package/dist/checks.d.mts.map +1 -1
- package/dist/checks.mjs +8 -9
- package/dist/checks.mjs.map +1 -1
- package/dist/claim.d.mts +13 -14
- package/dist/claim.d.mts.map +1 -1
- package/dist/claim.mjs +0 -1
- package/dist/claim.mjs.map +1 -1
- package/dist/closing-issues.d.mts +18 -19
- package/dist/closing-issues.d.mts.map +1 -1
- package/dist/closing-issues.mjs +0 -1
- package/dist/closing-issues.mjs.map +1 -1
- package/dist/code-owners.d.mts +48 -49
- package/dist/code-owners.d.mts.map +1 -1
- package/dist/code-owners.mjs +41 -43
- package/dist/code-owners.mjs.map +1 -1
- package/dist/labels.d.mts +38 -40
- package/dist/labels.d.mts.map +1 -1
- package/dist/labels.mjs +0 -2
- package/dist/labels.mjs.map +1 -1
- package/dist/merge-after.d.mts +22 -16
- package/dist/merge-after.d.mts.map +1 -1
- package/dist/merge-after.mjs +97 -34
- package/dist/merge-after.mjs.map +1 -1
- package/dist/queue-comments.d.mts +2 -2
- package/dist/queue-order.d.mts +43 -30
- package/dist/queue-order.d.mts.map +1 -1
- package/dist/queue-order.mjs +33 -18
- package/dist/queue-order.mjs.map +1 -1
- package/dist/queue-owner.d.mts +30 -30
- package/dist/queue-owner.d.mts.map +1 -1
- package/dist/queue-owner.mjs +51 -51
- package/dist/queue-owner.mjs.map +1 -1
- package/dist/report.d.mts +9 -9
- package/dist/report.d.mts.map +1 -1
- package/dist/report.mjs +23 -23
- package/dist/report.mjs.map +1 -1
- package/dist/review-hold.d.mts +14 -14
- package/dist/ruleset.d.mts +10 -11
- package/dist/ruleset.d.mts.map +1 -1
- package/dist/ruleset.mjs +0 -1
- package/dist/ruleset.mjs.map +1 -1
- package/dist/set-aside.d.mts +28 -28
- package/dist/stack.d.mts +14 -15
- package/dist/stack.d.mts.map +1 -1
- package/dist/stack.mjs +0 -1
- package/dist/stack.mjs.map +1 -1
- package/dist/start-after.d.mts +20 -21
- package/dist/start-after.d.mts.map +1 -1
- package/dist/start-after.mjs +0 -1
- package/dist/start-after.mjs.map +1 -1
- package/dist/summarize.d.mts +2 -2
- package/dist/timestamp.d.mts +10 -10
- package/dist/tree.d.mts +5 -5
- package/dist/types.d.mts +32 -33
- package/dist/types.d.mts.map +1 -1
- package/package.json +8 -8
- package/src/checks.mts +74 -75
- package/src/checks.test.mts +30 -31
- package/src/claim.mts +13 -14
- package/src/closing-issues.mts +18 -19
- package/src/closing-issues.test.mts +2 -2
- package/src/code-owners.mts +107 -109
- package/src/labels.mts +38 -40
- package/src/merge-after.mts +164 -71
- package/src/merge-after.test.mts +80 -3
- package/src/queue-comments.mts +2 -2
- package/src/queue-order.mts +103 -83
- package/src/queue-order.test.mts +41 -4
- package/src/queue-owner.mts +103 -103
- package/src/queue-owner.test.mts +2 -2
- package/src/report.mts +49 -49
- package/src/review-hold.mts +14 -14
- package/src/review-hold.test.mts +3 -3
- package/src/ruleset.mts +10 -11
- package/src/set-aside.mts +28 -28
- package/src/stack.mts +14 -15
- package/src/stack.test.mts +4 -4
- package/src/start-after.mts +20 -21
- package/src/summarize.mts +2 -2
- package/src/timestamp.mts +14 -14
- package/src/tree.mts +5 -5
- package/src/types.mts +32 -33
package/src/queue-order.mts
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
* Where each pull request stands in `unblock-prs`'s queue, and the order the
|
|
3
3
|
* worker picks the ones it may pick.
|
|
4
4
|
*
|
|
5
|
-
* The worker decides on someone's machine, from what `gh` and `git` return,
|
|
6
|
-
*
|
|
5
|
+
* The worker decides on someone's machine, from what `gh` and `git` return, and
|
|
6
|
+
* the Pull Requests Manager page draws the same queue from what GraphQL
|
|
7
7
|
* returns. So the deciding is here, once, over facts either side can supply,
|
|
8
8
|
* and each side does only its own reading: the worker reads some facts only
|
|
9
9
|
* when the answer needs them — the checks of a `BLOCKED` pull request, the
|
|
10
|
-
* native stack of one without auto-merge — which is why
|
|
11
|
-
*
|
|
10
|
+
* native stack of one without auto-merge — which is why {@link queueStanding}
|
|
11
|
+
* asks for them rather than taking them all up front.
|
|
12
12
|
*
|
|
13
13
|
* What the worker alone knows is left out: which pull requests one run has
|
|
14
14
|
* demoted (`demotions.mts` in `unblock-prs`) lives in that run's memory, so
|
|
@@ -39,8 +39,8 @@ export type QueueFacts = Readonly<{
|
|
|
39
39
|
headRef: string;
|
|
40
40
|
baseRef: string;
|
|
41
41
|
/**
|
|
42
|
-
* GitHub's `mergeStateStatus` — `BEHIND`, `DIRTY`, `CLEAN`, `BLOCKED` and
|
|
43
|
-
*
|
|
42
|
+
* GitHub's `mergeStateStatus` — `BEHIND`, `DIRTY`, `CLEAN`, `BLOCKED` and so
|
|
43
|
+
* on — or as near to it as the reader can tell.
|
|
44
44
|
*/
|
|
45
45
|
mergeState: string;
|
|
46
46
|
autoMerge: boolean;
|
|
@@ -57,8 +57,8 @@ export type QueueGraphMember = Readonly<{
|
|
|
57
57
|
baseRef: string;
|
|
58
58
|
fromFork: boolean;
|
|
59
59
|
/**
|
|
60
|
-
* The open issues it closes, whose `Start-After:` it inherits. Absent for
|
|
61
|
-
*
|
|
60
|
+
* The open issues it closes, whose `Start-After:` it inherits. Absent for a
|
|
61
|
+
* reader that did not read them: nothing is inherited.
|
|
62
62
|
*/
|
|
63
63
|
closes?: readonly number[];
|
|
64
64
|
}>;
|
|
@@ -68,8 +68,8 @@ export type QueueGraph = Readonly<{
|
|
|
68
68
|
/** Which open pull request each stacked one is on: child → parent. */
|
|
69
69
|
stackParents: ReadonlyMap<number, number>;
|
|
70
70
|
/**
|
|
71
|
-
* What each pull request must merge after: what it declared, the one it
|
|
72
|
-
*
|
|
71
|
+
* What each pull request must merge after: what it declared, the one it is
|
|
72
|
+
* stacked on, and what it inherits.
|
|
73
73
|
*/
|
|
74
74
|
dependencies: ReadonlyMap<number, readonly number[]>;
|
|
75
75
|
/**
|
|
@@ -165,9 +165,9 @@ export const versionBranchName = (defaultBranch: string): string =>
|
|
|
165
165
|
`changeset-release/${defaultBranch}` as const;
|
|
166
166
|
|
|
167
167
|
/**
|
|
168
|
-
* Whether a pull request from `headRef` is the version pull request. The
|
|
169
|
-
*
|
|
170
|
-
*
|
|
168
|
+
* Whether a pull request from `headRef` is the version pull request. The branch
|
|
169
|
+
* is the only stable handle on it: the title and body are rewritten on every
|
|
170
|
+
* run, and the number changes with every release.
|
|
171
171
|
*/
|
|
172
172
|
export const isVersionBranch = (
|
|
173
173
|
headRef: string,
|
|
@@ -191,8 +191,8 @@ export const isMergeableState = (mergeState: string): boolean =>
|
|
|
191
191
|
|
|
192
192
|
/**
|
|
193
193
|
* The stacks, the declared order and its cycles, and what holds the release,
|
|
194
|
-
* read off the open pull requests. `startAfterOf` is what the issues they
|
|
195
|
-
*
|
|
194
|
+
* read off the open pull requests. `startAfterOf` is what the issues they close
|
|
195
|
+
* declare, each issue's `Start-After:` by its number.
|
|
196
196
|
*/
|
|
197
197
|
export const queueGraph = (
|
|
198
198
|
pulls: readonly QueueGraphMember[],
|
|
@@ -219,12 +219,12 @@ export const queueGraph = (
|
|
|
219
219
|
|
|
220
220
|
return [
|
|
221
221
|
pr.number,
|
|
222
|
-
// The version pull request declares nothing: its body is rewritten
|
|
223
|
-
//
|
|
224
|
-
//
|
|
225
|
-
//
|
|
226
|
-
//
|
|
227
|
-
//
|
|
222
|
+
// The version pull request declares nothing: its body is rewritten on
|
|
223
|
+
// every push to the base, so a trailer written there would hold for a
|
|
224
|
+
// cycle or two and then silently stop. `blocks-release`, on the pull
|
|
225
|
+
// requests it is waiting for, is how an order is declared on it. Other
|
|
226
|
+
// pull requests may still name *it*. A stacked pull request waits for
|
|
227
|
+
// the layer below it whether or not it says so.
|
|
228
228
|
isVersionBranch(pr.headRef, defaultBranch)
|
|
229
229
|
? []
|
|
230
230
|
: Arr.uniq([
|
|
@@ -241,8 +241,8 @@ export const queueGraph = (
|
|
|
241
241
|
|
|
242
242
|
return [
|
|
243
243
|
number,
|
|
244
|
-
// The version pull request closes nothing it means to, and is held
|
|
245
|
-
//
|
|
244
|
+
// The version pull request closes nothing it means to, and is held by
|
|
245
|
+
// `blocks-release` alone.
|
|
246
246
|
isVersionBranch(headRef, defaultBranch)
|
|
247
247
|
? []
|
|
248
248
|
: (inheritedOrders.get(number)?.mergeAfter ?? []).filter(
|
|
@@ -288,15 +288,15 @@ export const queueStanding = (
|
|
|
288
288
|
if (outOfScope !== undefined) {
|
|
289
289
|
// `merge-queued` is an explicit request, so anything that answers it with
|
|
290
290
|
// "no" is worth saying out loud — a queued pull request left as a draft
|
|
291
|
-
// would otherwise sit in the queue forever with nothing saying why. A
|
|
292
|
-
//
|
|
291
|
+
// would otherwise sit in the queue forever with nothing saying why. A pull
|
|
292
|
+
// request that never asked is passed over in silence.
|
|
293
293
|
return hasLabel(pr, MERGE_QUEUED_LABEL)
|
|
294
294
|
? { kind: 'unactionable', reason: outOfScope }
|
|
295
295
|
: { kind: 'ignore' };
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
-
// A pull request set aside, or claimed, is a skill's or a person's until
|
|
299
|
-
//
|
|
298
|
+
// A pull request set aside, or claimed, is a skill's or a person's until its
|
|
299
|
+
// comment is resolved (`queue-owner.mts`).
|
|
300
300
|
const owner = context.owners.get(pr.number);
|
|
301
301
|
|
|
302
302
|
if (owner !== undefined && owner.owner !== 'worker') {
|
|
@@ -327,9 +327,9 @@ export const queueStanding = (
|
|
|
327
327
|
}
|
|
328
328
|
}
|
|
329
329
|
|
|
330
|
-
// The version pull request is rebuilt from the tip of the base by the
|
|
331
|
-
//
|
|
332
|
-
//
|
|
330
|
+
// The version pull request is rebuilt from the tip of the base by the release
|
|
331
|
+
// workflow, never rebased here; taking `skip-ci` off is the whole of what it
|
|
332
|
+
// can be given.
|
|
333
333
|
if (isVersionBranch(pr.headRef, context.defaultBranch)) {
|
|
334
334
|
return versionStanding(pr, context, reads);
|
|
335
335
|
}
|
|
@@ -342,9 +342,9 @@ export const queueStanding = (
|
|
|
342
342
|
}
|
|
343
343
|
|
|
344
344
|
switch (pr.mergeState) {
|
|
345
|
-
// `DIRTY` is a candidate too: it answers whether *merging* conflicts,
|
|
346
|
-
//
|
|
347
|
-
//
|
|
345
|
+
// `DIRTY` is a candidate too: it answers whether *merging* conflicts, from
|
|
346
|
+
// a cached computation, where the worker *rebases*, and the rebase is what
|
|
347
|
+
// says whether the conflict is real.
|
|
348
348
|
case 'BEHIND':
|
|
349
349
|
case 'DIRTY':
|
|
350
350
|
return waitingOn(pr, context) ?? { kind: 'candidate' };
|
|
@@ -363,10 +363,10 @@ export const queueStanding = (
|
|
|
363
363
|
};
|
|
364
364
|
|
|
365
365
|
/**
|
|
366
|
-
* Whether what {@link queueStanding} decided is worth asking the review
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
*
|
|
366
|
+
* Whether what {@link queueStanding} decided is worth asking the review about:
|
|
367
|
+
* one about to be picked, or one about to be watched while GitHub still calls
|
|
368
|
+
* it `BLOCKED`. A merge state GitHub already calls mergeable is its own answer
|
|
369
|
+
* that nothing but the checks is left.
|
|
370
370
|
*/
|
|
371
371
|
export const asksReview = (
|
|
372
372
|
standing: QueueStanding,
|
|
@@ -377,8 +377,8 @@ export const asksReview = (
|
|
|
377
377
|
|
|
378
378
|
/**
|
|
379
379
|
* The standing once the review is known: a hold that waits for someone holds
|
|
380
|
-
* it; one that only a bypass merges, and none at all, change nothing here —
|
|
381
|
-
*
|
|
380
|
+
* it; one that only a bypass merges, and none at all, change nothing here — the
|
|
381
|
+
* worker leaves the first unarmed, which is not a question of order.
|
|
382
382
|
*/
|
|
383
383
|
export const withReviewHold = (
|
|
384
384
|
standing: QueueStanding,
|
|
@@ -392,9 +392,9 @@ export const withReviewHold = (
|
|
|
392
392
|
export type QueuePriority = 'high' | 'low' | 'none' | 'both';
|
|
393
393
|
|
|
394
394
|
/**
|
|
395
|
-
* Which priority label a pull request carries: `both` is the two at once,
|
|
396
|
-
*
|
|
397
|
-
*
|
|
395
|
+
* Which priority label a pull request carries: `both` is the two at once, which
|
|
396
|
+
* contradict each other and so count as neither. `priority:mid` is the same as
|
|
397
|
+
* neither here.
|
|
398
398
|
*/
|
|
399
399
|
export const priorityOfLabels = (labels: readonly string[]): QueuePriority => {
|
|
400
400
|
const high = labels.includes(PRIORITY_HIGH_LABEL);
|
|
@@ -423,16 +423,38 @@ export const priorityRankOfLabels = (labels: readonly string[]): 0 | 1 | 2 => {
|
|
|
423
423
|
};
|
|
424
424
|
|
|
425
425
|
/**
|
|
426
|
-
*
|
|
427
|
-
*
|
|
428
|
-
*
|
|
426
|
+
* {@link priorityRankOfLabels}, for a pull request that `bypass` says only a
|
|
427
|
+
* ruleset bypass merges ({@link ReviewHold}): that one ranks with
|
|
428
|
+
* `priority:low` unless labelled `priority:high` — alone, since the two
|
|
429
|
+
* together count as neither. Picked, it is released, goes green and is set
|
|
430
|
+
* aside for a person, so the cycle spent on it lands nothing; picked last, it
|
|
431
|
+
* still has its checks run. `priority:high` is a person saying they want that
|
|
432
|
+
* verdict now, and is the one way to ask for it, so it still counts.
|
|
433
|
+
*/
|
|
434
|
+
export const pickPriorityRank = (
|
|
435
|
+
labels: readonly string[],
|
|
436
|
+
bypass: boolean,
|
|
437
|
+
): 0 | 1 | 2 => {
|
|
438
|
+
const rank = priorityRankOfLabels(labels);
|
|
439
|
+
|
|
440
|
+
return bypass && rank !== 0
|
|
441
|
+
? priorityRankOfLabels([PRIORITY_LOW_LABEL])
|
|
442
|
+
: rank;
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* The order the worker picks candidates in. The declared order has already been
|
|
447
|
+
* applied, as a gate rather than a sort, so this is lowest number first within
|
|
448
|
+
* these ranks, outermost first:
|
|
429
449
|
*
|
|
430
450
|
* 1. What a watch has seen green and still open (`demoted`) goes after
|
|
431
451
|
* everything else, the release and `priority:high` included: everything
|
|
432
452
|
* else is not known to sit without merging, and this is.
|
|
433
453
|
* 2. `priority:high` before the rest, `priority:low` after them. It is a
|
|
434
454
|
* person's declaration, so it outranks the two guesses below — and the
|
|
435
|
-
* version pull request reads it too.
|
|
455
|
+
* version pull request reads it too. One only a ruleset bypass merges
|
|
456
|
+
* counts as `priority:low` unless labelled `priority:high`
|
|
457
|
+
* ({@link pickPriorityRank}).
|
|
436
458
|
* 3. What GitHub only *thinks* conflicts goes after the rest: a rebase the
|
|
437
459
|
* worker is sure of is worth spending the cycle on before one it is
|
|
438
460
|
* guessing at.
|
|
@@ -441,8 +463,8 @@ export const priorityRankOfLabels = (labels: readonly string[]): 0 | 1 | 2 => {
|
|
|
441
463
|
* release: it only says that when a queued change and the release are
|
|
442
464
|
* both ready, the change goes first.
|
|
443
465
|
*
|
|
444
|
-
* `toSorted` is stable, so each sort keeps the order of the one before it
|
|
445
|
-
*
|
|
466
|
+
* `toSorted` is stable, so each sort keeps the order of the one before it among
|
|
467
|
+
* equals, and the last one is the outermost.
|
|
446
468
|
*/
|
|
447
469
|
export const orderCandidates = <P,>(
|
|
448
470
|
candidates: readonly P[],
|
|
@@ -450,6 +472,7 @@ export const orderCandidates = <P,>(
|
|
|
450
472
|
context: Readonly<{
|
|
451
473
|
defaultBranch: string;
|
|
452
474
|
demoted: (candidate: P) => boolean;
|
|
475
|
+
bypass: (candidate: P) => boolean;
|
|
453
476
|
}>,
|
|
454
477
|
): readonly P[] => {
|
|
455
478
|
const rank = (candidate: P): number => {
|
|
@@ -462,19 +485,27 @@ export const orderCandidates = <P,>(
|
|
|
462
485
|
: 0;
|
|
463
486
|
};
|
|
464
487
|
|
|
488
|
+
const priorityRank = (candidate: P): number =>
|
|
489
|
+
pickPriorityRank(factsOf(candidate).labels, context.bypass(candidate));
|
|
490
|
+
|
|
465
491
|
return candidates
|
|
466
492
|
.toSorted((a, b) => factsOf(a).number - factsOf(b).number)
|
|
467
493
|
.toSorted((a, b) => rank(a) - rank(b))
|
|
468
|
-
.toSorted(
|
|
469
|
-
(a, b) =>
|
|
470
|
-
priorityRankOfLabels(factsOf(a).labels) -
|
|
471
|
-
priorityRankOfLabels(factsOf(b).labels),
|
|
472
|
-
)
|
|
494
|
+
.toSorted((a, b) => priorityRank(a) - priorityRank(b))
|
|
473
495
|
.toSorted(
|
|
474
496
|
(a, b) => Number(context.demoted(a)) - Number(context.demoted(b)),
|
|
475
497
|
);
|
|
476
498
|
};
|
|
477
499
|
|
|
500
|
+
/**
|
|
501
|
+
* Whether `pr` carries the label `name`. A function rather than `includes` in
|
|
502
|
+
* place, which narrows the label constant itself to `never` where it says no.
|
|
503
|
+
*/
|
|
504
|
+
const hasLabel = (
|
|
505
|
+
pr: Readonly<{ labels: readonly string[] }>,
|
|
506
|
+
name: string,
|
|
507
|
+
): boolean => pr.labels.includes(name);
|
|
508
|
+
|
|
478
509
|
/** Why a pull request is none of the queue's business, if it is not. */
|
|
479
510
|
const outOfScopeReason = (
|
|
480
511
|
pr: QueueFacts,
|
|
@@ -490,10 +521,9 @@ const outOfScopeReason = (
|
|
|
490
521
|
return `not labelled ${MERGE_QUEUED_LABEL}`;
|
|
491
522
|
}
|
|
492
523
|
|
|
493
|
-
// A base that is neither the default branch nor another open pull
|
|
494
|
-
//
|
|
495
|
-
//
|
|
496
|
-
// moves it.
|
|
524
|
+
// A base that is neither the default branch nor another open pull request's
|
|
525
|
+
// branch is one nothing here knows how to land on. A stacked one whose parent
|
|
526
|
+
// has just merged reads this way for the moment before GitHub moves it.
|
|
497
527
|
return pr.isDraft
|
|
498
528
|
? 'draft'
|
|
499
529
|
: pr.baseRef !== context.defaultBranch &&
|
|
@@ -522,17 +552,28 @@ const waitingOn = (
|
|
|
522
552
|
};
|
|
523
553
|
|
|
524
554
|
/**
|
|
525
|
-
*
|
|
526
|
-
*
|
|
527
|
-
|
|
555
|
+
* Pending — including a required context that has not reported at all — or
|
|
556
|
+
* green and about to merge is in flight; a failed required check is failing.
|
|
557
|
+
*/
|
|
558
|
+
const byChecks = (reads: QueueReads): QueueStanding =>
|
|
559
|
+
reads.checksFailed === undefined
|
|
560
|
+
? ({ kind: 'needs', read: 'checksFailed' } as const)
|
|
561
|
+
: reads.checksFailed
|
|
562
|
+
? ({ kind: 'failing' } as const)
|
|
563
|
+
: ({ kind: 'in-flight' } as const);
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* The version pull request: held while anything labelled `blocks-release` is
|
|
567
|
+
* open, and while the branch was built before the tip of the base, then a
|
|
568
|
+
* candidate if paused and otherwise judged by its checks.
|
|
528
569
|
*/
|
|
529
570
|
const versionStanding = (
|
|
530
571
|
pr: QueueFacts,
|
|
531
572
|
context: QueueContext,
|
|
532
573
|
reads: QueueReads,
|
|
533
574
|
): QueueStanding => {
|
|
534
|
-
// Never one of its own blockers: the label on it would otherwise hold it
|
|
535
|
-
//
|
|
575
|
+
// Never one of its own blockers: the label on it would otherwise hold it for
|
|
576
|
+
// as long as it existed.
|
|
536
577
|
const by = context.releaseBlockers.filter(
|
|
537
578
|
(blocker) => blocker.number !== pr.number,
|
|
538
579
|
);
|
|
@@ -547,24 +588,3 @@ const versionStanding = (
|
|
|
547
588
|
? { kind: 'candidate' }
|
|
548
589
|
: byChecks(reads);
|
|
549
590
|
};
|
|
550
|
-
|
|
551
|
-
/**
|
|
552
|
-
* Pending — including a required context that has not reported at all — or
|
|
553
|
-
* green and about to merge is in flight; a failed required check is failing.
|
|
554
|
-
*/
|
|
555
|
-
const byChecks = (reads: QueueReads): QueueStanding =>
|
|
556
|
-
reads.checksFailed === undefined
|
|
557
|
-
? ({ kind: 'needs', read: 'checksFailed' } as const)
|
|
558
|
-
: reads.checksFailed
|
|
559
|
-
? ({ kind: 'failing' } as const)
|
|
560
|
-
: ({ kind: 'in-flight' } as const);
|
|
561
|
-
|
|
562
|
-
/**
|
|
563
|
-
* Whether `pr` carries the label `name`. A function rather than `includes`
|
|
564
|
-
* in place, which narrows the label constant itself to `never` where it says
|
|
565
|
-
* no.
|
|
566
|
-
*/
|
|
567
|
-
const hasLabel = (
|
|
568
|
-
pr: Readonly<{ labels: readonly string[] }>,
|
|
569
|
-
name: string,
|
|
570
|
-
): boolean => pr.labels.includes(name);
|
package/src/queue-order.test.mts
CHANGED
|
@@ -253,8 +253,8 @@ describe(queueStanding, () => {
|
|
|
253
253
|
);
|
|
254
254
|
});
|
|
255
255
|
|
|
256
|
-
// Otherwise it would hold itself for as long as it existed, with nothing
|
|
257
|
-
//
|
|
256
|
+
// Otherwise it would hold itself for as long as it existed, with nothing able
|
|
257
|
+
// to take the label off — a `Merge-After` cycle by another name.
|
|
258
258
|
test('never holds the release by its own label', () => {
|
|
259
259
|
const release = facts({
|
|
260
260
|
number: 1,
|
|
@@ -338,8 +338,8 @@ describe(orderCandidates, () => {
|
|
|
338
338
|
});
|
|
339
339
|
|
|
340
340
|
// Priority comes before every rank but the demotion, the release included:
|
|
341
|
-
// a release can be declared urgent, and a change declared able to wait
|
|
342
|
-
//
|
|
341
|
+
// a release can be declared urgent, and a change declared able to wait goes
|
|
342
|
+
// out in the release after.
|
|
343
343
|
test('takes priority:high first and priority:low last, before what sat green', () => {
|
|
344
344
|
assert.deepStrictEqual(
|
|
345
345
|
order(
|
|
@@ -383,15 +383,52 @@ describe(orderCandidates, () => {
|
|
|
383
383
|
[2, 3, 1],
|
|
384
384
|
);
|
|
385
385
|
});
|
|
386
|
+
|
|
387
|
+
// Picked early, it is released, goes green and is set aside for a person, and
|
|
388
|
+
// the cycle it took went to nothing that could merge on its own.
|
|
389
|
+
test('puts one only a bypass merges with priority:low, unless labelled priority:high', () => {
|
|
390
|
+
assert.deepStrictEqual(
|
|
391
|
+
order(
|
|
392
|
+
[
|
|
393
|
+
facts({ number: 1 }),
|
|
394
|
+
facts({
|
|
395
|
+
number: 2,
|
|
396
|
+
labels: [
|
|
397
|
+
MERGE_QUEUED_LABEL,
|
|
398
|
+
PRIORITY_HIGH_LABEL,
|
|
399
|
+
PRIORITY_LOW_LABEL,
|
|
400
|
+
],
|
|
401
|
+
}),
|
|
402
|
+
facts({
|
|
403
|
+
number: 3,
|
|
404
|
+
labels: [MERGE_QUEUED_LABEL, PRIORITY_LOW_LABEL],
|
|
405
|
+
}),
|
|
406
|
+
facts({ number: 4, headRef: 'changeset-release/main' }),
|
|
407
|
+
facts({ number: 5, mergeState: 'DIRTY' }),
|
|
408
|
+
facts({ number: 6 }),
|
|
409
|
+
facts({ number: 7 }),
|
|
410
|
+
facts({
|
|
411
|
+
number: 8,
|
|
412
|
+
labels: [MERGE_QUEUED_LABEL, PRIORITY_HIGH_LABEL],
|
|
413
|
+
}),
|
|
414
|
+
],
|
|
415
|
+
[7],
|
|
416
|
+
[1, 2, 7, 8],
|
|
417
|
+
),
|
|
418
|
+
[8, 6, 5, 4, 1, 2, 3, 7],
|
|
419
|
+
);
|
|
420
|
+
});
|
|
386
421
|
});
|
|
387
422
|
|
|
388
423
|
const order = (
|
|
389
424
|
candidates: readonly QueueFacts[],
|
|
390
425
|
demoted: readonly number[] = [],
|
|
426
|
+
bypass: readonly number[] = [],
|
|
391
427
|
): readonly number[] =>
|
|
392
428
|
orderCandidates(candidates, (candidate) => candidate, {
|
|
393
429
|
defaultBranch: 'main',
|
|
394
430
|
demoted: (candidate) => demoted.includes(candidate.number),
|
|
431
|
+
bypass: (candidate) => bypass.includes(candidate.number),
|
|
395
432
|
}).map(({ number }) => number);
|
|
396
433
|
|
|
397
434
|
const facts = (
|