@n-seiji/nuthatch 0.1.5 → 0.2.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.
@@ -0,0 +1,103 @@
1
+ import {
2
+ messageOf
3
+ } from "./cli-ph0gp56k.js";
4
+ import {
5
+ withRepoLock
6
+ } from "./cli-wcnyhk4w.js";
7
+ import {
8
+ loadRepoContext,
9
+ nestedWorktrees,
10
+ worktreeDirtyState
11
+ } from "./cli-fqz7emfm.js";
12
+ import {
13
+ EXIT_GENERAL_ERROR,
14
+ EXIT_USAGE_ERROR,
15
+ EXIT_SAFE_REJECTION,
16
+ ok,
17
+ fail,
18
+ pass,
19
+ reject
20
+ } from "./cli-77yanq37.js";
21
+
22
+ // src/commands/rm.ts
23
+ var lockedRejection = (branch, lockReason) => fail(EXIT_SAFE_REJECTION, `Worktree for "${branch}" is locked by git${lockReason === null ? "" : ` (${lockReason})`}. hop never unlocks worktrees automatically — run "git worktree unlock" yourself first if you're sure.`);
24
+ var turnedPrunableRejection = (branch) => fail(EXIT_SAFE_REJECTION, `The selected worktree for "${branch}" changed before removal (git now reports it prunable). Refresh the picker and retry.`);
25
+ var stalePrunableWarning = ({ path, prunableReason }) => `Worktree at ${path} no longer exists${prunableReason === null ? "" : ` (${prunableReason})`}; removed its stale registration — if it had been moved by hand, the moved copy is no longer linked to this repository.`;
26
+ var resolveTarget = (worktrees, branch, expectedPath) => {
27
+ const matches = worktrees.filter((wt) => wt.branch === branch);
28
+ if (expectedPath !== undefined) {
29
+ const target = matches.find((wt) => wt.path === expectedPath);
30
+ return target === undefined ? reject(fail(EXIT_SAFE_REJECTION, `The selected worktree for "${branch}" changed before removal. Refresh the picker and retry.`)) : pass(target);
31
+ }
32
+ if (matches.length > 1) {
33
+ return reject(fail(EXIT_SAFE_REJECTION, `Branch "${branch}" is checked out at ${matches.length} worktrees (${matches.map((wt) => wt.path).join(", ")}). Refusing to guess which one to remove.`));
34
+ }
35
+ const [target] = matches;
36
+ return target === undefined ? reject(fail(EXIT_GENERAL_ERROR, `No worktree found for branch "${branch}".`)) : pass(target);
37
+ };
38
+ var nestedWorktreeRejection = (branch, targetPath, worktrees) => {
39
+ const nested = nestedWorktrees(worktrees, targetPath);
40
+ if (nested.length === 0) {
41
+ return null;
42
+ }
43
+ const listing = nested.map((wt) => ` ${wt.path}${wt.branch === null ? "" : ` (${wt.branch})`}`).join(`
44
+ `);
45
+ return fail(EXIT_SAFE_REJECTION, `Worktree for "${branch}" still contains ${nested.length} registered worktree(s) — refusing to remove it, since that would destroy their files too. Remove these first, then retry:
46
+ ${listing}`);
47
+ };
48
+ var targetSafetyRejection = async ({ git, fs, context, options }, target) => {
49
+ const nestedRejection = nestedWorktreeRejection(options.branch, target.path, context.worktrees);
50
+ if (nestedRejection !== null) {
51
+ return nestedRejection;
52
+ }
53
+ if (target.locked) {
54
+ return lockedRejection(options.branch, target.lockReason);
55
+ }
56
+ if (target.prunable && options.allowPrunable === false) {
57
+ return turnedPrunableRejection(options.branch);
58
+ }
59
+ if (!options.force && await worktreeDirtyState(git, fs, context.worktrees, target) === true) {
60
+ return fail(EXIT_SAFE_REJECTION, `Worktree for "${options.branch}" has uncommitted or untracked changes. Use --force to remove anyway.`);
61
+ }
62
+ return null;
63
+ };
64
+ var validateRemoval = async (check) => {
65
+ const { context, options } = check;
66
+ const resolved = resolveTarget(context.worktrees, options.branch, options.expectedPath);
67
+ if (!resolved.ok) {
68
+ return resolved;
69
+ }
70
+ const target = resolved.value;
71
+ if (target.kind === "root") {
72
+ return reject(fail(EXIT_USAGE_ERROR, "Cannot remove the root clone."));
73
+ }
74
+ const rejection = await targetSafetyRejection(check, target);
75
+ return rejection === null ? resolved : reject(rejection);
76
+ };
77
+ var removeWorktree = async (git, fs, options) => {
78
+ const context = await loadRepoContext(git, fs, options.cwd);
79
+ const validation = await validateRemoval({ git, fs, context, options });
80
+ if (!validation.ok) {
81
+ return validation.rejection;
82
+ }
83
+ return withRepoLock(context.commonDir, async () => {
84
+ try {
85
+ const fresh = await loadRepoContext(git, fs, options.cwd);
86
+ const freshValidation = await validateRemoval({ git, fs, context: fresh, options });
87
+ if (!freshValidation.ok) {
88
+ return freshValidation.rejection;
89
+ }
90
+ const target = freshValidation.value;
91
+ await git.removeWorktree(context.rootPath, target.path, options.force);
92
+ return ok({
93
+ data: { branch: options.branch, path: target.path },
94
+ ...target.prunable ? { warnings: [stalePrunableWarning(target)] } : {}
95
+ });
96
+ } catch (error) {
97
+ return fail(EXIT_SAFE_REJECTION, `Failed to remove worktree: ${messageOf(error)}`);
98
+ }
99
+ });
100
+ };
101
+ var rm2 = (git, fs, options) => removeWorktree(git, fs, options);
102
+
103
+ export { rm2 };
@@ -0,0 +1,25 @@
1
+ // src/domain/posix-path.ts
2
+ var withoutTrailingSlash = (path) => path.length > 1 && path.endsWith("/") ? path.slice(0, -1) : path;
3
+ var shortenHome = (path, home) => {
4
+ if (home === "" || home === "/") {
5
+ return path;
6
+ }
7
+ if (path === home) {
8
+ return "~";
9
+ }
10
+ return path.startsWith(`${home}/`) ? `~${path.slice(home.length)}` : path;
11
+ };
12
+ var parentDir = (path) => {
13
+ const trimmed = withoutTrailingSlash(path);
14
+ const index = trimmed.lastIndexOf("/");
15
+ if (index === -1) {
16
+ return ".";
17
+ }
18
+ return index === 0 ? "/" : trimmed.slice(0, index);
19
+ };
20
+ var absoluteCandidates = (dirs, name) => [
21
+ ...new Set(dirs.map((entry) => entry.trim()).filter((entry) => entry.startsWith("/")).map((dir) => `${dir.endsWith("/") ? dir.slice(0, -1) : dir}/${name}`))
22
+ ];
23
+ var splitPathEnv = (pathEnv) => (pathEnv ?? "").split(":");
24
+
25
+ export { shortenHome, parentDir, absoluteCandidates, splitPathEnv };