-
Notifications
You must be signed in to change notification settings - Fork 23
Expand file tree
/
Copy pathremove.ts
More file actions
142 lines (126 loc) · 5.5 KB
/
Copy pathremove.ts
File metadata and controls
142 lines (126 loc) · 5.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import { execa } from "execa";
import chalk from "chalk";
import { stat, rm } from "node:fs/promises";
import { resolve } from "node:path";
import { getWorktrees, findWorktreeByBranch, findWorktreeByPath, WorktreeInfo } from "../utils/git.js";
import { selectWorktree, confirm } from "../utils/tui.js";
import { withSpinner } from "../utils/spinner.js";
import { runCleanupScriptsSecure } from "../utils/setup.js";
export async function removeWorktreeHandler(
pathOrBranch: string = "",
options: { force?: boolean; skipCleanup?: boolean; trust?: boolean }
) {
try {
await execa("git", ["rev-parse", "--is-inside-work-tree"]);
let targetWorktree: WorktreeInfo | null = null;
// Improvement #4: Interactive TUI for missing arguments
if (!pathOrBranch) {
const selected = await selectWorktree({
message: "Select a worktree to remove",
excludeMain: true, // Don't allow removing main worktree
});
if (!selected || Array.isArray(selected)) {
console.log(chalk.yellow("No worktree selected."));
process.exit(0);
}
targetWorktree = selected;
} else {
// Try to find by path first
try {
const stats = await stat(pathOrBranch);
if (stats.isDirectory()) {
targetWorktree = await findWorktreeByPath(pathOrBranch);
}
} catch {
// Not a valid path, try as branch name
}
// If not found by path, try by branch name
if (!targetWorktree) {
targetWorktree = await findWorktreeByBranch(pathOrBranch);
}
if (!targetWorktree) {
console.error(chalk.red(`Could not find a worktree for "${pathOrBranch}".`));
console.error(chalk.yellow("Use 'wt list' to see existing worktrees, or run 'wt remove' without arguments to select interactively."));
process.exit(1);
}
}
const targetPath = targetWorktree.path;
// Prevent removing main worktree
if (targetWorktree.isMain) {
console.error(chalk.red("Cannot remove the main worktree."));
process.exit(1);
}
// Show what will be removed
console.log(chalk.blue(`Worktree to remove:`));
if (targetWorktree.branch) {
console.log(chalk.cyan(` Branch: ${targetWorktree.branch}`));
}
console.log(chalk.cyan(` Path: ${targetPath}`));
// Warn about locked worktrees
if (targetWorktree.locked) {
console.log(chalk.yellow(` Warning: This worktree is locked${targetWorktree.lockReason ? `: ${targetWorktree.lockReason}` : ''}`));
if (!options.force) {
console.error(chalk.red("Use --force to remove a locked worktree."));
process.exit(1);
}
}
// Confirm removal (skip in non-interactive mode or with force flag)
const isNonInteractive = !process.stdin.isTTY;
if (!options.force && !isNonInteractive) {
const confirmed = await confirm("Are you sure you want to remove this worktree?", false);
if (!confirmed) {
console.log(chalk.yellow("Removal cancelled."));
process.exit(0);
}
}
// Execute cleanup scripts if not skipped
if (!options.skipCleanup) {
await runCleanupScriptsSecure(targetPath, { trust: options.trust });
}
// Remove the worktree
try {
await withSpinner(
`Removing worktree: ${targetPath}`,
async () => {
await execa("git", ["worktree", "remove", ...(options.force ? ["--force"] : []), targetPath]);
},
"Git worktree metadata removed."
);
} catch (removeError: any) {
if (removeError.stderr?.includes("modified or untracked files") && !options.force) {
console.log(chalk.yellow("Worktree contains modified or untracked files."));
const forceRemove = await confirm("Do you want to force remove this worktree (this may lose changes)?", false);
if (forceRemove) {
await withSpinner(
`Force removing worktree: ${targetPath}`,
async () => {
await execa("git", ["worktree", "remove", "--force", targetPath]);
},
"Git worktree metadata force removed."
);
} else {
console.log(chalk.yellow("Removal cancelled."));
process.exit(0);
}
} else {
throw removeError;
}
}
// Also remove the physical directory if it still exists
try {
await stat(targetPath);
await rm(targetPath, { recursive: true, force: true });
console.log(chalk.green(`Deleted folder ${targetPath}`));
} catch {
// Directory doesn't exist, which is fine
}
console.log(chalk.green("Worktree removed successfully!"));
} catch (error) {
if (error instanceof Error) {
console.error(chalk.red("Failed to remove worktree:"), error.message);
} else {
console.error(chalk.red("Failed to remove worktree:"), error);
}
process.exit(1);
}
}