@afokapu/atdd-bun 0.10.105 → 0.10.106
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/integrity.json +2 -2
- package/package.json +1 -1
- package/src/hooks.ts +42 -15
package/integrity.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.10.
|
|
2
|
+
"version": "0.10.106",
|
|
3
3
|
"files": {
|
|
4
4
|
"HOOK_AUDIT.md": "5329d840db37671b1918f688ead26865473b87db73dbc75f7c8b2a8bbe8d6d43",
|
|
5
5
|
"README.md": "8aefceaa24bee498f39081b2aaf4c0d5c54bc1ed9849c20e1d98d1427df56eab",
|
|
@@ -1269,7 +1269,7 @@
|
|
|
1269
1269
|
"src/cli.ts": "395638ab87565bb10c2beb35988ff1acf686924d13515795156a2e8893b3da88",
|
|
1270
1270
|
"src/docs-capability.ts": "bdeeb414649049b1cd4fa8008cada3a576318be51c43bec91bfa46a8e250c270",
|
|
1271
1271
|
"src/enforce.ts": "c4811abe63efe4568f3cf26e21a152d1537e120cb3ce471daca32367cf6e2cda",
|
|
1272
|
-
"src/hooks.ts": "
|
|
1272
|
+
"src/hooks.ts": "7e3dd5518701d2f3399bee919a68e801588822b173a648ed738c05c4a57231f1",
|
|
1273
1273
|
"src/index.ts": "4081f8b08d1d3ee9e15a9964143e53d9cfb94a5616ab3e58a180d24cc16dc381",
|
|
1274
1274
|
"src/integrity.ts": "f1ede7cb04924ae88e11917047277e264af6596b9759e381bd9c497cd34a797d",
|
|
1275
1275
|
"src/journey-docs.ts": "994d229376244c84212cff951032823068a76565dd2fd427b9d44e341bd14853",
|
package/package.json
CHANGED
package/src/hooks.ts
CHANGED
|
@@ -471,7 +471,7 @@ async function createWorkspace(state: Layout, target: { leaf: string; target: st
|
|
|
471
471
|
return bad(bookmark.err || bookmark.out || `bookmark ${branch} was not exported to the colocated Git repository`);
|
|
472
472
|
}
|
|
473
473
|
|
|
474
|
-
async function finishCandidate(state: Layout, cfg: HookPolicy): Promise<{ error: string } | { branch: string; tip: string
|
|
474
|
+
async function finishCandidate(state: Layout, cfg: HookPolicy): Promise<{ error: string } | { branch: string; tip: string }> {
|
|
475
475
|
if (state.current === state.primary || !inside(state.worktreeRoot, state.current)) return { error: `finish must run from a linked workspace beneath ${state.worktreeRoot}` };
|
|
476
476
|
const conflict = await noConflicts(state.current);
|
|
477
477
|
if (conflict.error !== undefined) return conflict;
|
|
@@ -489,23 +489,50 @@ async function finishCandidate(state: Layout, cfg: HookPolicy): Promise<{ error:
|
|
|
489
489
|
}
|
|
490
490
|
const tip = await branchCommit(state.primary, found.branch);
|
|
491
491
|
if (!tip || tip !== delivery) return { error: `bookmark ${found.branch} must point at the delivery revision before finish` };
|
|
492
|
-
|
|
493
|
-
return { branch: found.branch, tip
|
|
492
|
+
const undelivered = await deliveryError(state, found.branch, tip, cfg);
|
|
493
|
+
return undelivered ? { error: undelivered } : { branch: found.branch, tip };
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
async function jjRevision(root: string, commit: string): Promise<{ error: string } | { empty: boolean; conflict: boolean; parents: string[] }> {
|
|
497
|
+
const info = await jj(root, ["log", "-r", commit, "--no-graph", "-T", 'if(empty, "empty", "content") ++ " " ++ if(conflict, "conflict", "clean") ++ " " ++ parents.map(|p| p.commit_id()).join(" ")']);
|
|
498
|
+
if (info.code || !info.out) return { error: `could not inspect Jujutsu revision ${commit}: ${info.err || info.out}` };
|
|
499
|
+
const [emptiness, conflict, ...parents] = info.out.split(" ");
|
|
500
|
+
return { empty: emptiness === "empty", conflict: conflict === "conflict", parents };
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// A tip is delivered when it is an ancestor of the protected branch, or when it
|
|
504
|
+
// reaches such an ancestor only through empty single-parent changes (nothing to
|
|
505
|
+
// deliver). Content, conflicts, or merge shapes outside that chain fail closed.
|
|
506
|
+
async function deliveryError(state: Layout, branch: string, tip: string, cfg: HookPolicy): Promise<string | null> {
|
|
507
|
+
let commit = tip;
|
|
508
|
+
for (;;) {
|
|
509
|
+
const info = await jjRevision(state.primary, commit);
|
|
510
|
+
if ("error" in info) return info.error;
|
|
511
|
+
if (info.conflict) return `bookmark ${branch} has unresolved conflicts`;
|
|
512
|
+
if (!(await git(state.primary, ["merge-base", "--is-ancestor", commit, cfg.worktrees.primary_branch])).code) return null;
|
|
513
|
+
if (!info.empty || info.parents.length !== 1) return `branch ${branch} is not merged into ${cfg.worktrees.primary_branch} in the primary checkout`;
|
|
514
|
+
commit = info.parents[0]!;
|
|
515
|
+
}
|
|
494
516
|
}
|
|
495
517
|
|
|
496
|
-
// The
|
|
497
|
-
//
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
518
|
+
// The workspace name is never derived from the bookmark (bookmarks can be
|
|
519
|
+
// renamed). Resolve it from the JJ registry: exactly one registered workspace
|
|
520
|
+
// must resolve, by real path, to the current directory.
|
|
521
|
+
async function resolveWorkspace(state: Layout): Promise<{ error: string } | { name: string }> {
|
|
522
|
+
const listed = await jj(state.primary, ["workspace", "list", "-T", 'name ++ "\\n"']);
|
|
523
|
+
if (listed.code) return { error: `could not list Jujutsu workspaces: ${listed.err || listed.out}` };
|
|
524
|
+
const matches: string[] = [];
|
|
525
|
+
for (const name of listed.out.split("\n").filter(Boolean)) {
|
|
526
|
+
const recorded = await jj(state.primary, ["workspace", "root", "--name", name]);
|
|
527
|
+
if (!recorded.code && await realpath(recorded.out).catch(() => null) === state.current) matches.push(name);
|
|
528
|
+
}
|
|
529
|
+
return matches.length === 1 ? { name: matches[0]! } : { error: `${state.current} is not registered as exactly one Jujutsu workspace; found ${matches.length}` };
|
|
503
530
|
}
|
|
504
531
|
|
|
505
532
|
async function deleteBookmark(state: Layout, candidate: { branch: string; tip: string }, cfg: HookPolicy) {
|
|
506
533
|
const now = await branchCommit(state.primary, candidate.branch);
|
|
507
|
-
const merged = !(await
|
|
508
|
-
if (
|
|
534
|
+
const merged = now === candidate.tip && !(await deliveryError(state, candidate.branch, candidate.tip, cfg));
|
|
535
|
+
if (!merged) return `workspace removed, but bookmark ${candidate.branch} was not deleted: its tip changed or is no longer merged into ${cfg.worktrees.primary_branch}`;
|
|
509
536
|
const deleted = await jj(state.primary, ["bookmark", "delete", candidate.branch]);
|
|
510
537
|
return deleted.code ? `workspace removed, but bookmark deletion failed: ${deleted.err || deleted.out}` : null;
|
|
511
538
|
}
|
|
@@ -531,9 +558,9 @@ async function finishJjWorktree(root: string, deleteBranch: boolean, cfg: HookPo
|
|
|
531
558
|
if ("error" in state) return bad(state.error);
|
|
532
559
|
const candidate = await finishCandidate(state, cfg);
|
|
533
560
|
if ("error" in candidate) return bad(candidate.error);
|
|
534
|
-
const
|
|
535
|
-
if (
|
|
536
|
-
const removed = await jj(state.primary, ["workspace", "remove",
|
|
561
|
+
const workspace = await resolveWorkspace(state);
|
|
562
|
+
if ("error" in workspace) return bad(workspace.error);
|
|
563
|
+
const removed = await jj(state.primary, ["workspace", "remove", workspace.name]);
|
|
537
564
|
if (removed.code) return bad(removed.err || removed.out);
|
|
538
565
|
if (!deleteBranch) return { ok: true, message: `removed merged Jujutsu workspace ${state.current}` };
|
|
539
566
|
const deletionError = await deleteBookmark(state, candidate, cfg);
|