@brainervirus/workit-core 2.8.0 → 2.9.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/package.json +1 -1
- package/src/forge/pr-ops.ts +13 -1
- package/src/ledger.ts +43 -1
- package/src/stack.ts +1915 -0
package/package.json
CHANGED
package/src/forge/pr-ops.ts
CHANGED
|
@@ -241,6 +241,11 @@ export type MergeInput = {
|
|
|
241
241
|
method: MergeMethod;
|
|
242
242
|
deleteBranch: boolean;
|
|
243
243
|
actor: LedgerActor;
|
|
244
|
+
/**
|
|
245
|
+
* Stack landing (S12): refuse unless the PR still targets this base and
|
|
246
|
+
* comes from this branch, re-checked right before the merge call.
|
|
247
|
+
*/
|
|
248
|
+
expect?: { base: string; branch: string };
|
|
244
249
|
};
|
|
245
250
|
|
|
246
251
|
export type MergeOutcome = {
|
|
@@ -266,7 +271,8 @@ export type MergeRefusal = {
|
|
|
266
271
|
| "head_mismatch"
|
|
267
272
|
| "already_merged"
|
|
268
273
|
| "closed"
|
|
269
|
-
| "protected_branch"
|
|
274
|
+
| "protected_branch"
|
|
275
|
+
| "base_mismatch";
|
|
270
276
|
next?: string;
|
|
271
277
|
blockers?: string[];
|
|
272
278
|
verdict?: Pick<VerdictCheck, "head" | "accepted" | "authors">;
|
|
@@ -340,6 +346,12 @@ export async function mergePullRequest(
|
|
|
340
346
|
{ reason: "not_ready", next: doc.next, blockers: doc.blockers },
|
|
341
347
|
);
|
|
342
348
|
|
|
349
|
+
if (input.expect && (doc.base !== input.expect.base || doc.head.branch !== input.expect.branch))
|
|
350
|
+
return refuse(
|
|
351
|
+
`base_mismatch: ${label} is ${doc.head.branch} -> ${doc.base}, expected ${input.expect.branch} -> ${input.expect.base}`,
|
|
352
|
+
"workit stack sync # retarget it, then land again",
|
|
353
|
+
{ reason: "base_mismatch" },
|
|
354
|
+
);
|
|
343
355
|
const branch = doc.head.branch;
|
|
344
356
|
const head = doc.head.sha;
|
|
345
357
|
// --delete-branch never deletes a protected branch, the base, or the
|
package/src/ledger.ts
CHANGED
|
@@ -847,6 +847,47 @@ function effectiveOf(rows: readonly ReadRow[], authors: Set<string>): ReadRow |
|
|
|
847
847
|
return effective;
|
|
848
848
|
}
|
|
849
849
|
|
|
850
|
+
/**
|
|
851
|
+
* Stacked branches (S12): a verdict keyed against the trunk cannot carry
|
|
852
|
+
* through a restack after its parent was squash-merged, because the
|
|
853
|
+
* trunk-relative diff loses the parent's change. `workit stack sync` observes
|
|
854
|
+
* each restack itself and records `stack.restacked` with `patchEqual` when
|
|
855
|
+
* the change relative to the parent is identical (same patch-id AND same
|
|
856
|
+
* exact diff hash, D18). A verdict carries when such observed rows link its
|
|
857
|
+
* head to the current head.
|
|
858
|
+
*/
|
|
859
|
+
function restackCarries(
|
|
860
|
+
rows: readonly ReadRow[],
|
|
861
|
+
branch: string,
|
|
862
|
+
from: string | null,
|
|
863
|
+
to: string | null,
|
|
864
|
+
): boolean {
|
|
865
|
+
if (!from || !to) return false;
|
|
866
|
+
const edges = new Map<string, string[]>();
|
|
867
|
+
for (const row of rows)
|
|
868
|
+
if (
|
|
869
|
+
row.type === "stack.restacked" &&
|
|
870
|
+
row.observer === "workit_cli" &&
|
|
871
|
+
row.branch === branch &&
|
|
872
|
+
row.patchEqual === true &&
|
|
873
|
+
typeof row.fromHead === "string" &&
|
|
874
|
+
row.head
|
|
875
|
+
)
|
|
876
|
+
edges.set(row.fromHead, [...(edges.get(row.fromHead) ?? []), row.head]);
|
|
877
|
+
const seen = new Set<string>([from]);
|
|
878
|
+
const queue = [from];
|
|
879
|
+
while (queue.length) {
|
|
880
|
+
const next = queue.shift() as string;
|
|
881
|
+
if (next === to) return true;
|
|
882
|
+
for (const target of edges.get(next) ?? [])
|
|
883
|
+
if (!seen.has(target)) {
|
|
884
|
+
seen.add(target);
|
|
885
|
+
queue.push(target);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
return false;
|
|
889
|
+
}
|
|
890
|
+
|
|
850
891
|
/** Current and accepted verdicts for `branch`, after carry-over (D18). */
|
|
851
892
|
export function checkVerdicts(cwd: string, branch: string, rows: readonly ReadRow[]): VerdictCheck {
|
|
852
893
|
const head = branchHead(cwd, branch);
|
|
@@ -872,7 +913,8 @@ export function checkVerdicts(cwd: string, branch: string, rows: readonly ReadRo
|
|
|
872
913
|
for (const [kind, list] of byKind) {
|
|
873
914
|
const row = effectiveOf(list, authors);
|
|
874
915
|
if (!row) continue;
|
|
875
|
-
|
|
916
|
+
let basis = verdictBasis(row, { head, ...keyFor(row.base ?? fallbackBase) });
|
|
917
|
+
if (basis === "stale" && restackCarries(rows, branch, row.head, head)) basis = "carried";
|
|
876
918
|
const current = basis !== "stale";
|
|
877
919
|
const independence = independenceReasons(row, authors);
|
|
878
920
|
const reasons: RejectReason[] = [
|