@solaqua/gji 0.8.0 → 0.8.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/clean.js +34 -19
- package/dist/gji-bundle.mjs +61 -28
- package/dist/remove.js +2 -2
- package/dist/worktree-management.d.ts +2 -1
- package/dist/worktree-management.js +23 -7
- package/man/man1/gji-back.1 +1 -1
- package/man/man1/gji-clean.1 +1 -1
- package/man/man1/gji-completion.1 +1 -1
- package/man/man1/gji-config.1 +1 -1
- package/man/man1/gji-go.1 +1 -1
- package/man/man1/gji-history.1 +1 -1
- package/man/man1/gji-init.1 +1 -1
- package/man/man1/gji-ls.1 +1 -1
- package/man/man1/gji-new.1 +1 -1
- package/man/man1/gji-open.1 +1 -1
- package/man/man1/gji-pr.1 +1 -1
- package/man/man1/gji-remove.1 +1 -1
- package/man/man1/gji-root.1 +1 -1
- package/man/man1/gji-run-hook.1 +1 -1
- package/man/man1/gji-status.1 +1 -1
- package/man/man1/gji-sync-files.1 +1 -1
- package/man/man1/gji-sync.1 +1 -1
- package/man/man1/gji-warp.1 +1 -1
- package/man/man1/gji.1 +1 -1
- package/package.json +1 -1
package/dist/clean.js
CHANGED
|
@@ -3,7 +3,7 @@ import { loadEffectiveConfig } from "./config.js";
|
|
|
3
3
|
import { isBranchMergedInto, readWorktreeHealth, resolveRemoteDefaultBranch, runGit, } from "./git.js";
|
|
4
4
|
import { isHeadless } from "./headless.js";
|
|
5
5
|
import { formatLastCommit, formatUpstreamState, readWorktreeInfos, serializeWorktreeInfo, } from "./worktree-info.js";
|
|
6
|
-
import { deleteBranch, forceDeleteBranch, forceRemoveWorktree, isBranchUnmergedError,
|
|
6
|
+
import { deleteBranch, forceDeleteBranch, forceRemoveWorktree, isBranchUnmergedError, isWorktreeDeletionError, isWorktreeForceRemovalError, loadLinkedWorktrees, removeWorktree, } from "./worktree-management.js";
|
|
7
7
|
import { buildWorktreePromptEntries, promptForMultipleWorktrees, } from "./worktree-picker.js";
|
|
8
8
|
import { defaultConfirmForceDeleteBranch, defaultConfirmForceRemoveWorktree, } from "./worktree-prompts.js";
|
|
9
9
|
export function createCleanCommand(dependencies = {}) {
|
|
@@ -77,8 +77,8 @@ export function createCleanCommand(dependencies = {}) {
|
|
|
77
77
|
}
|
|
78
78
|
return 0;
|
|
79
79
|
}
|
|
80
|
-
const removedPaths = [];
|
|
81
80
|
const removedWorktrees = [];
|
|
81
|
+
const failures = [];
|
|
82
82
|
for (const worktree of selectedWorktrees) {
|
|
83
83
|
if (options.stale &&
|
|
84
84
|
!(await isStaleCleanupCandidate(repository.repoRoot, worktree, staleBaseRef))) {
|
|
@@ -89,31 +89,39 @@ export function createCleanCommand(dependencies = {}) {
|
|
|
89
89
|
await removeWorktree(repository.repoRoot, worktree.path);
|
|
90
90
|
}
|
|
91
91
|
catch (error) {
|
|
92
|
-
if (!
|
|
93
|
-
|
|
92
|
+
if (!isWorktreeForceRemovalError(error)) {
|
|
93
|
+
failures.push({
|
|
94
|
+
branch: worktree.branch,
|
|
95
|
+
message: toMessage(error),
|
|
96
|
+
path: worktree.path,
|
|
97
|
+
});
|
|
98
|
+
continue;
|
|
94
99
|
}
|
|
95
|
-
if (options.stale) {
|
|
100
|
+
if (options.stale && !isWorktreeDeletionError(error)) {
|
|
96
101
|
options.stderr(`Skipped ${worktree.path}: no longer a safe stale cleanup candidate\n`);
|
|
97
102
|
continue;
|
|
98
103
|
}
|
|
99
104
|
if (!options.force &&
|
|
100
105
|
!(await confirmForceRemoveWorktree(worktree.path))) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
106
|
+
failures.push({
|
|
107
|
+
branch: worktree.branch,
|
|
108
|
+
message: "force removal declined",
|
|
109
|
+
path: worktree.path,
|
|
110
|
+
});
|
|
111
|
+
continue;
|
|
104
112
|
}
|
|
105
113
|
try {
|
|
106
114
|
await forceRemoveWorktree(repository.repoRoot, worktree.path);
|
|
107
115
|
}
|
|
108
116
|
catch (forceError) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
117
|
+
failures.push({
|
|
118
|
+
branch: worktree.branch,
|
|
119
|
+
message: toMessage(forceError),
|
|
120
|
+
path: worktree.path,
|
|
121
|
+
});
|
|
122
|
+
continue;
|
|
114
123
|
}
|
|
115
124
|
}
|
|
116
|
-
removedPaths.push(worktree.path);
|
|
117
125
|
removedWorktrees.push(worktree);
|
|
118
126
|
if (worktree.branch) {
|
|
119
127
|
try {
|
|
@@ -145,12 +153,16 @@ export function createCleanCommand(dependencies = {}) {
|
|
|
145
153
|
? { branch: worktree.branch, path: worktree.path }
|
|
146
154
|
: serializeWorktreeInfo(info);
|
|
147
155
|
});
|
|
148
|
-
|
|
156
|
+
const payload = failures.length === 0 ? { removed } : { removed, failed: failures };
|
|
157
|
+
options.stdout(`${JSON.stringify(payload, null, 2)}\n`);
|
|
158
|
+
}
|
|
159
|
+
else if (failures.length > 0) {
|
|
160
|
+
reportCleanFailures(failures, options.stderr);
|
|
149
161
|
}
|
|
150
162
|
else {
|
|
151
163
|
options.stdout(`${repository.repoRoot}\n`);
|
|
152
164
|
}
|
|
153
|
-
return 0;
|
|
165
|
+
return failures.length === 0 ? 0 : 1;
|
|
154
166
|
};
|
|
155
167
|
}
|
|
156
168
|
export const runCleanCommand = createCleanCommand();
|
|
@@ -216,9 +228,12 @@ function resolveSelectedWorktrees(worktrees, selections) {
|
|
|
216
228
|
}
|
|
217
229
|
return selectedWorktrees;
|
|
218
230
|
}
|
|
219
|
-
function
|
|
220
|
-
|
|
221
|
-
|
|
231
|
+
function reportCleanFailures(failures, stderr) {
|
|
232
|
+
const noun = failures.length === 1 ? "worktree" : "worktrees";
|
|
233
|
+
stderr(`Failed to clean ${failures.length} ${noun}:\n`);
|
|
234
|
+
for (const failure of failures) {
|
|
235
|
+
const branch = failure.branch === null ? "detached" : failure.branch;
|
|
236
|
+
stderr(`- ${failure.path} (${branch}): ${failure.message}\n`);
|
|
222
237
|
}
|
|
223
238
|
}
|
|
224
239
|
function formatCleanInfo(info) {
|
package/dist/gji-bundle.mjs
CHANGED
|
@@ -14127,6 +14127,7 @@ function formatRelativeAge(timestampSeconds) {
|
|
|
14127
14127
|
|
|
14128
14128
|
// src/worktree-management.ts
|
|
14129
14129
|
import { execFile as execFile2 } from "node:child_process";
|
|
14130
|
+
import { rm } from "node:fs/promises";
|
|
14130
14131
|
import { promisify as promisify3 } from "node:util";
|
|
14131
14132
|
var execFileAsync2 = promisify3(execFile2);
|
|
14132
14133
|
var GIT_ENV = { ...process.env, LC_ALL: "C" };
|
|
@@ -14147,10 +14148,24 @@ async function removeWorktree(repoRoot, worktreePath) {
|
|
|
14147
14148
|
});
|
|
14148
14149
|
}
|
|
14149
14150
|
async function forceRemoveWorktree(repoRoot, worktreePath) {
|
|
14150
|
-
|
|
14151
|
-
|
|
14152
|
-
|
|
14153
|
-
|
|
14151
|
+
try {
|
|
14152
|
+
await execFileAsync2(
|
|
14153
|
+
"git",
|
|
14154
|
+
["worktree", "remove", "--force", worktreePath],
|
|
14155
|
+
{
|
|
14156
|
+
cwd: repoRoot,
|
|
14157
|
+
env: GIT_ENV
|
|
14158
|
+
}
|
|
14159
|
+
);
|
|
14160
|
+
} catch (error) {
|
|
14161
|
+
const worktreeIsStillRegistered = (await listWorktrees(repoRoot)).some(
|
|
14162
|
+
(worktree) => worktree.path === worktreePath
|
|
14163
|
+
);
|
|
14164
|
+
if (worktreeIsStillRegistered) {
|
|
14165
|
+
throw error;
|
|
14166
|
+
}
|
|
14167
|
+
await rm(worktreePath, { force: true, recursive: true });
|
|
14168
|
+
}
|
|
14154
14169
|
}
|
|
14155
14170
|
async function deleteBranch(repoRoot, branch) {
|
|
14156
14171
|
await execFileAsync2("git", ["branch", "-d", branch], {
|
|
@@ -14164,8 +14179,14 @@ async function forceDeleteBranch(repoRoot, branch) {
|
|
|
14164
14179
|
env: GIT_ENV
|
|
14165
14180
|
});
|
|
14166
14181
|
}
|
|
14167
|
-
function
|
|
14168
|
-
|
|
14182
|
+
function isWorktreeForceRemovalError(error) {
|
|
14183
|
+
if (!hasStderr(error)) {
|
|
14184
|
+
return false;
|
|
14185
|
+
}
|
|
14186
|
+
return error.stderr.includes("contains modified or untracked files") || isWorktreeDeletionError(error);
|
|
14187
|
+
}
|
|
14188
|
+
function isWorktreeDeletionError(error) {
|
|
14189
|
+
return hasStderr(error) && error.stderr.includes("failed to delete");
|
|
14169
14190
|
}
|
|
14170
14191
|
function isBranchUnmergedError(error) {
|
|
14171
14192
|
return hasStderr(error) && error.stderr.includes("is not fully merged");
|
|
@@ -15156,8 +15177,8 @@ function createCleanCommand(dependencies = {}) {
|
|
|
15156
15177
|
}
|
|
15157
15178
|
return 0;
|
|
15158
15179
|
}
|
|
15159
|
-
const removedPaths = [];
|
|
15160
15180
|
const removedWorktrees = [];
|
|
15181
|
+
const failures = [];
|
|
15161
15182
|
for (const worktree of selectedWorktrees) {
|
|
15162
15183
|
if (options.stale && !await isStaleCleanupCandidate(
|
|
15163
15184
|
repository.repoRoot,
|
|
@@ -15173,10 +15194,15 @@ function createCleanCommand(dependencies = {}) {
|
|
|
15173
15194
|
try {
|
|
15174
15195
|
await removeWorktree(repository.repoRoot, worktree.path);
|
|
15175
15196
|
} catch (error) {
|
|
15176
|
-
if (!
|
|
15177
|
-
|
|
15197
|
+
if (!isWorktreeForceRemovalError(error)) {
|
|
15198
|
+
failures.push({
|
|
15199
|
+
branch: worktree.branch,
|
|
15200
|
+
message: toMessage(error),
|
|
15201
|
+
path: worktree.path
|
|
15202
|
+
});
|
|
15203
|
+
continue;
|
|
15178
15204
|
}
|
|
15179
|
-
if (options.stale) {
|
|
15205
|
+
if (options.stale && !isWorktreeDeletionError(error)) {
|
|
15180
15206
|
options.stderr(
|
|
15181
15207
|
`Skipped ${worktree.path}: no longer a safe stale cleanup candidate
|
|
15182
15208
|
`
|
|
@@ -15184,24 +15210,24 @@ function createCleanCommand(dependencies = {}) {
|
|
|
15184
15210
|
continue;
|
|
15185
15211
|
}
|
|
15186
15212
|
if (!options.force && !await confirmForceRemoveWorktree(worktree.path)) {
|
|
15187
|
-
|
|
15188
|
-
|
|
15189
|
-
|
|
15213
|
+
failures.push({
|
|
15214
|
+
branch: worktree.branch,
|
|
15215
|
+
message: "force removal declined",
|
|
15216
|
+
path: worktree.path
|
|
15217
|
+
});
|
|
15218
|
+
continue;
|
|
15190
15219
|
}
|
|
15191
15220
|
try {
|
|
15192
15221
|
await forceRemoveWorktree(repository.repoRoot, worktree.path);
|
|
15193
15222
|
} catch (forceError) {
|
|
15194
|
-
|
|
15195
|
-
|
|
15196
|
-
|
|
15197
|
-
|
|
15198
|
-
|
|
15199
|
-
|
|
15200
|
-
);
|
|
15201
|
-
return 1;
|
|
15223
|
+
failures.push({
|
|
15224
|
+
branch: worktree.branch,
|
|
15225
|
+
message: toMessage(forceError),
|
|
15226
|
+
path: worktree.path
|
|
15227
|
+
});
|
|
15228
|
+
continue;
|
|
15202
15229
|
}
|
|
15203
15230
|
}
|
|
15204
|
-
removedPaths.push(worktree.path);
|
|
15205
15231
|
removedWorktrees.push(worktree);
|
|
15206
15232
|
if (worktree.branch) {
|
|
15207
15233
|
try {
|
|
@@ -15233,13 +15259,16 @@ function createCleanCommand(dependencies = {}) {
|
|
|
15233
15259
|
const info = selectedInfoByPath.get(worktree.path);
|
|
15234
15260
|
return info === void 0 ? { branch: worktree.branch, path: worktree.path } : serializeWorktreeInfo(info);
|
|
15235
15261
|
});
|
|
15236
|
-
|
|
15262
|
+
const payload = failures.length === 0 ? { removed } : { removed, failed: failures };
|
|
15263
|
+
options.stdout(`${JSON.stringify(payload, null, 2)}
|
|
15237
15264
|
`);
|
|
15265
|
+
} else if (failures.length > 0) {
|
|
15266
|
+
reportCleanFailures(failures, options.stderr);
|
|
15238
15267
|
} else {
|
|
15239
15268
|
options.stdout(`${repository.repoRoot}
|
|
15240
15269
|
`);
|
|
15241
15270
|
}
|
|
15242
|
-
return 0;
|
|
15271
|
+
return failures.length === 0 ? 0 : 1;
|
|
15243
15272
|
};
|
|
15244
15273
|
}
|
|
15245
15274
|
var runCleanCommand = createCleanCommand();
|
|
@@ -15316,9 +15345,13 @@ function resolveSelectedWorktrees(worktrees, selections) {
|
|
|
15316
15345
|
}
|
|
15317
15346
|
return selectedWorktrees;
|
|
15318
15347
|
}
|
|
15319
|
-
function
|
|
15320
|
-
|
|
15321
|
-
|
|
15348
|
+
function reportCleanFailures(failures, stderr) {
|
|
15349
|
+
const noun = failures.length === 1 ? "worktree" : "worktrees";
|
|
15350
|
+
stderr(`Failed to clean ${failures.length} ${noun}:
|
|
15351
|
+
`);
|
|
15352
|
+
for (const failure of failures) {
|
|
15353
|
+
const branch = failure.branch === null ? "detached" : failure.branch;
|
|
15354
|
+
stderr(`- ${failure.path} (${branch}): ${failure.message}
|
|
15322
15355
|
`);
|
|
15323
15356
|
}
|
|
15324
15357
|
}
|
|
@@ -17918,7 +17951,7 @@ function createRemoveCommand(dependencies = {}) {
|
|
|
17918
17951
|
try {
|
|
17919
17952
|
await removeWorktree(repository.repoRoot, worktree.path);
|
|
17920
17953
|
} catch (error) {
|
|
17921
|
-
if (!
|
|
17954
|
+
if (!isWorktreeForceRemovalError(error)) {
|
|
17922
17955
|
throw error;
|
|
17923
17956
|
}
|
|
17924
17957
|
if (!options.force && !await confirmForceRemoveWorktree(worktree.path)) {
|
package/dist/remove.js
CHANGED
|
@@ -4,7 +4,7 @@ import { loadEffectiveConfig } from "./config.js";
|
|
|
4
4
|
import { isHeadless } from "./headless.js";
|
|
5
5
|
import { extractHooks, runHook } from "./hooks.js";
|
|
6
6
|
import { writeShellOutput } from "./shell-handoff.js";
|
|
7
|
-
import { deleteBranch, forceDeleteBranch, forceRemoveWorktree, isBranchUnmergedError,
|
|
7
|
+
import { deleteBranch, forceDeleteBranch, forceRemoveWorktree, isBranchUnmergedError, isWorktreeForceRemovalError, loadLinkedWorktrees, removeWorktree, } from "./worktree-management.js";
|
|
8
8
|
import { buildWorktreePromptEntries, promptForSingleWorktree, } from "./worktree-picker.js";
|
|
9
9
|
import { defaultConfirmForceDeleteBranch, defaultConfirmForceRemoveWorktree, } from "./worktree-prompts.js";
|
|
10
10
|
const REMOVE_OUTPUT_FILE_ENV = "GJI_REMOVE_OUTPUT_FILE";
|
|
@@ -83,7 +83,7 @@ export function createRemoveCommand(dependencies = {}) {
|
|
|
83
83
|
await removeWorktree(repository.repoRoot, worktree.path);
|
|
84
84
|
}
|
|
85
85
|
catch (error) {
|
|
86
|
-
if (!
|
|
86
|
+
if (!isWorktreeForceRemovalError(error)) {
|
|
87
87
|
throw error;
|
|
88
88
|
}
|
|
89
89
|
if (!options.force &&
|
|
@@ -8,5 +8,6 @@ export declare function removeWorktree(repoRoot: string, worktreePath: string):
|
|
|
8
8
|
export declare function forceRemoveWorktree(repoRoot: string, worktreePath: string): Promise<void>;
|
|
9
9
|
export declare function deleteBranch(repoRoot: string, branch: string): Promise<void>;
|
|
10
10
|
export declare function forceDeleteBranch(repoRoot: string, branch: string): Promise<void>;
|
|
11
|
-
export declare function
|
|
11
|
+
export declare function isWorktreeForceRemovalError(error: unknown): boolean;
|
|
12
|
+
export declare function isWorktreeDeletionError(error: unknown): boolean;
|
|
12
13
|
export declare function isBranchUnmergedError(error: unknown): boolean;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
|
+
import { rm } from "node:fs/promises";
|
|
2
3
|
import { promisify } from "node:util";
|
|
3
4
|
import { detectRepository, listWorktrees, } from "./repo.js";
|
|
4
5
|
const execFileAsync = promisify(execFile);
|
|
@@ -19,10 +20,19 @@ export async function removeWorktree(repoRoot, worktreePath) {
|
|
|
19
20
|
});
|
|
20
21
|
}
|
|
21
22
|
export async function forceRemoveWorktree(repoRoot, worktreePath) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
try {
|
|
24
|
+
await execFileAsync("git", ["worktree", "remove", "--force", worktreePath], {
|
|
25
|
+
cwd: repoRoot,
|
|
26
|
+
env: GIT_ENV,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
const worktreeIsStillRegistered = (await listWorktrees(repoRoot)).some((worktree) => worktree.path === worktreePath);
|
|
31
|
+
if (worktreeIsStillRegistered) {
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
await rm(worktreePath, { force: true, recursive: true });
|
|
35
|
+
}
|
|
26
36
|
}
|
|
27
37
|
export async function deleteBranch(repoRoot, branch) {
|
|
28
38
|
await execFileAsync("git", ["branch", "-d", branch], {
|
|
@@ -36,9 +46,15 @@ export async function forceDeleteBranch(repoRoot, branch) {
|
|
|
36
46
|
env: GIT_ENV,
|
|
37
47
|
});
|
|
38
48
|
}
|
|
39
|
-
export function
|
|
40
|
-
|
|
41
|
-
|
|
49
|
+
export function isWorktreeForceRemovalError(error) {
|
|
50
|
+
if (!hasStderr(error)) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
return (error.stderr.includes("contains modified or untracked files") ||
|
|
54
|
+
isWorktreeDeletionError(error));
|
|
55
|
+
}
|
|
56
|
+
export function isWorktreeDeletionError(error) {
|
|
57
|
+
return hasStderr(error) && error.stderr.includes("failed to delete");
|
|
42
58
|
}
|
|
43
59
|
export function isBranchUnmergedError(error) {
|
|
44
60
|
return hasStderr(error) && error.stderr.includes("is not fully merged");
|
package/man/man1/gji-back.1
CHANGED
package/man/man1/gji-clean.1
CHANGED
package/man/man1/gji-config.1
CHANGED
package/man/man1/gji-go.1
CHANGED
package/man/man1/gji-history.1
CHANGED
package/man/man1/gji-init.1
CHANGED
package/man/man1/gji-ls.1
CHANGED
package/man/man1/gji-new.1
CHANGED
package/man/man1/gji-open.1
CHANGED
package/man/man1/gji-pr.1
CHANGED
package/man/man1/gji-remove.1
CHANGED
package/man/man1/gji-root.1
CHANGED
package/man/man1/gji-run-hook.1
CHANGED
package/man/man1/gji-status.1
CHANGED
package/man/man1/gji-sync.1
CHANGED
package/man/man1/gji-warp.1
CHANGED
package/man/man1/gji.1
CHANGED