@solaqua/gji 0.13.0 → 0.15.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/dist/application/worktree/go-resolution.js +5 -3
- package/dist/application/worktree/sources.d.ts +13 -4
- package/dist/application/worktree/sources.js +34 -13
- package/dist/cli/commands/clean.d.ts +4 -2
- package/dist/cli/commands/clean.js +338 -65
- package/dist/cli/commands/go.d.ts +2 -1
- package/dist/cli/commands/go.js +17 -13
- package/dist/cli/commands/hub.js +4 -1
- package/dist/cli/commands/warp.js +6 -4
- package/dist/cli/program.js +5 -0
- package/dist/domain/shared/concurrency.d.ts +1 -1
- package/dist/domain/shared/concurrency.js +6 -1
- package/dist/domain/worktree/path-filter.d.ts +13 -0
- package/dist/domain/worktree/path-filter.js +72 -0
- package/dist/domain/worktree/policy.d.ts +1 -0
- package/dist/domain/worktree/policy.js +1 -1
- package/dist/gji-bundle.mjs +746 -368
- package/dist/infrastructure/git/refs.js +3 -11
- package/dist/presentation/shell/completion.js +7 -2
- package/dist/presentation/worktree/picker.d.ts +9 -6
- package/dist/presentation/worktree/picker.js +19 -2
- package/man/man1/gji-back.1 +1 -1
- package/man/man1/gji-clean.1 +4 -1
- package/man/man1/gji-completion.1 +1 -1
- package/man/man1/gji-config.1 +1 -1
- package/man/man1/gji-doctor.1 +1 -1
- package/man/man1/gji-done.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-task.1 +1 -1
- package/man/man1/gji-undo.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/gji-bundle.mjs
CHANGED
|
@@ -5386,7 +5386,7 @@ var require_minimist = __commonJS({
|
|
|
5386
5386
|
var require_rc = __commonJS({
|
|
5387
5387
|
"node_modules/.pnpm/rc@1.2.8/node_modules/rc/index.js"(exports, module) {
|
|
5388
5388
|
var cc = require_utils();
|
|
5389
|
-
var
|
|
5389
|
+
var join17 = __require("path").join;
|
|
5390
5390
|
var deepExtend = require_deep_extend();
|
|
5391
5391
|
var etc = "/etc";
|
|
5392
5392
|
var win = process.platform === "win32";
|
|
@@ -5411,15 +5411,15 @@ var require_rc = __commonJS({
|
|
|
5411
5411
|
}
|
|
5412
5412
|
if (!win)
|
|
5413
5413
|
[
|
|
5414
|
-
|
|
5415
|
-
|
|
5414
|
+
join17(etc, name, "config"),
|
|
5415
|
+
join17(etc, name + "rc")
|
|
5416
5416
|
].forEach(addConfigFile);
|
|
5417
5417
|
if (home)
|
|
5418
5418
|
[
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5419
|
+
join17(home, ".config", name, "config"),
|
|
5420
|
+
join17(home, ".config", name),
|
|
5421
|
+
join17(home, "." + name, "config"),
|
|
5422
|
+
join17(home, "." + name + "rc")
|
|
5423
5423
|
].forEach(addConfigFile);
|
|
5424
5424
|
addConfigFile(cc.find("." + name + "rc"));
|
|
5425
5425
|
if (env5.config) addConfigFile(env5.config);
|
|
@@ -6757,11 +6757,11 @@ var require_util = __commonJS({
|
|
|
6757
6757
|
if (files.includes("node_modules") || files.includes("package.json") || files.includes("package.json5") || files.includes("package.yaml") || files.includes("pnpm-workspace.yaml")) {
|
|
6758
6758
|
return name2;
|
|
6759
6759
|
}
|
|
6760
|
-
const
|
|
6761
|
-
if (
|
|
6760
|
+
const dirname15 = path9.dirname(name2);
|
|
6761
|
+
if (dirname15 === name2) {
|
|
6762
6762
|
return original;
|
|
6763
6763
|
}
|
|
6764
|
-
return find(
|
|
6764
|
+
return find(dirname15, original);
|
|
6765
6765
|
} catch (error) {
|
|
6766
6766
|
if (name2 === original) {
|
|
6767
6767
|
if (error.code === "ENOENT") {
|
|
@@ -9519,7 +9519,7 @@ var retryifyAsync = (fn, options) => {
|
|
|
9519
9519
|
throw error;
|
|
9520
9520
|
const delay2 = Math.round(interval * Math.random());
|
|
9521
9521
|
if (delay2 > 0) {
|
|
9522
|
-
const delayPromise = new Promise((
|
|
9522
|
+
const delayPromise = new Promise((resolve13) => setTimeout(resolve13, delay2));
|
|
9523
9523
|
return delayPromise.then(() => attempt.apply(void 0, args));
|
|
9524
9524
|
} else {
|
|
9525
9525
|
return attempt.apply(void 0, args);
|
|
@@ -9778,14 +9778,14 @@ var Temp = {
|
|
|
9778
9778
|
}
|
|
9779
9779
|
},
|
|
9780
9780
|
truncate: (filePath) => {
|
|
9781
|
-
const
|
|
9782
|
-
if (
|
|
9781
|
+
const basename14 = path2.basename(filePath);
|
|
9782
|
+
if (basename14.length <= LIMIT_BASENAME_LENGTH)
|
|
9783
9783
|
return filePath;
|
|
9784
|
-
const truncable = /^(\.?)(.*?)((?:\.[^.]+)?(?:\.tmp-\d{10}[a-f0-9]{6})?)$/.exec(
|
|
9784
|
+
const truncable = /^(\.?)(.*?)((?:\.[^.]+)?(?:\.tmp-\d{10}[a-f0-9]{6})?)$/.exec(basename14);
|
|
9785
9785
|
if (!truncable)
|
|
9786
9786
|
return filePath;
|
|
9787
|
-
const truncationLength =
|
|
9788
|
-
return `${filePath.slice(0, -
|
|
9787
|
+
const truncationLength = basename14.length - LIMIT_BASENAME_LENGTH;
|
|
9788
|
+
return `${filePath.slice(0, -basename14.length)}${truncable[1]}${truncable[2].slice(0, -truncationLength)}${truncable[3]}`;
|
|
9789
9789
|
}
|
|
9790
9790
|
};
|
|
9791
9791
|
node_default(Temp.purgeSyncAll);
|
|
@@ -11085,14 +11085,14 @@ var TimeoutError = class extends Error {
|
|
|
11085
11085
|
|
|
11086
11086
|
// node_modules/.pnpm/ky@1.14.3/node_modules/ky/distribution/utils/timeout.js
|
|
11087
11087
|
async function timeout(request, init, abortController, options) {
|
|
11088
|
-
return new Promise((
|
|
11088
|
+
return new Promise((resolve13, reject) => {
|
|
11089
11089
|
const timeoutId = setTimeout(() => {
|
|
11090
11090
|
if (abortController) {
|
|
11091
11091
|
abortController.abort();
|
|
11092
11092
|
}
|
|
11093
11093
|
reject(new TimeoutError(request));
|
|
11094
11094
|
}, options.timeout);
|
|
11095
|
-
void options.fetch(request, init).then(
|
|
11095
|
+
void options.fetch(request, init).then(resolve13).catch(reject).then(() => {
|
|
11096
11096
|
clearTimeout(timeoutId);
|
|
11097
11097
|
});
|
|
11098
11098
|
});
|
|
@@ -11100,7 +11100,7 @@ async function timeout(request, init, abortController, options) {
|
|
|
11100
11100
|
|
|
11101
11101
|
// node_modules/.pnpm/ky@1.14.3/node_modules/ky/distribution/utils/delay.js
|
|
11102
11102
|
async function delay(ms, { signal }) {
|
|
11103
|
-
return new Promise((
|
|
11103
|
+
return new Promise((resolve13, reject) => {
|
|
11104
11104
|
if (signal) {
|
|
11105
11105
|
signal.throwIfAborted();
|
|
11106
11106
|
signal.addEventListener("abort", abortHandler, { once: true });
|
|
@@ -11111,7 +11111,7 @@ async function delay(ms, { signal }) {
|
|
|
11111
11111
|
}
|
|
11112
11112
|
const timeoutId = setTimeout(() => {
|
|
11113
11113
|
signal?.removeEventListener("abort", abortHandler);
|
|
11114
|
-
|
|
11114
|
+
resolve13();
|
|
11115
11115
|
}, ms);
|
|
11116
11116
|
});
|
|
11117
11117
|
}
|
|
@@ -12810,7 +12810,7 @@ function updateNotifier(options) {
|
|
|
12810
12810
|
// package.json
|
|
12811
12811
|
var package_default = {
|
|
12812
12812
|
name: "@solaqua/gji",
|
|
12813
|
-
version: "0.
|
|
12813
|
+
version: "0.15.0",
|
|
12814
12814
|
description: "Git worktree CLI for fast context switching.",
|
|
12815
12815
|
license: "MIT",
|
|
12816
12816
|
author: "sjquant",
|
|
@@ -13093,7 +13093,7 @@ async function ensureDestinationDirectory(root, path9) {
|
|
|
13093
13093
|
// src/infrastructure/process/command-runner.ts
|
|
13094
13094
|
import { spawn as spawn2 } from "node:child_process";
|
|
13095
13095
|
var runCommand = async (command, cwd, stderr, stdout3 = (chunk) => process.stdout.write(chunk), options) => {
|
|
13096
|
-
await new Promise((
|
|
13096
|
+
await new Promise((resolve13, reject) => {
|
|
13097
13097
|
const shell = options?.shell ?? true;
|
|
13098
13098
|
const [executable, args] = shell ? [command, []] : splitCommand(command);
|
|
13099
13099
|
const child = spawn2(executable, args, {
|
|
@@ -13112,7 +13112,7 @@ var runCommand = async (command, cwd, stderr, stdout3 = (chunk) => process.stdou
|
|
|
13112
13112
|
if (code !== 0) {
|
|
13113
13113
|
reject(new Error(`exited with code ${code}`));
|
|
13114
13114
|
} else {
|
|
13115
|
-
|
|
13115
|
+
resolve13();
|
|
13116
13116
|
}
|
|
13117
13117
|
});
|
|
13118
13118
|
child.on("error", reject);
|
|
@@ -13931,16 +13931,7 @@ async function resolveCachedRemoteDefaultBranch(cwd, remote) {
|
|
|
13931
13931
|
const prefix = `${remote}/`;
|
|
13932
13932
|
return ref.startsWith(prefix) ? ref.slice(prefix.length) : void 0;
|
|
13933
13933
|
} catch {
|
|
13934
|
-
|
|
13935
|
-
const refs = await runGit(cwd, [
|
|
13936
|
-
"for-each-ref",
|
|
13937
|
-
"--format=%(refname:strip=3)",
|
|
13938
|
-
`refs/remotes/${remote}`
|
|
13939
|
-
]);
|
|
13940
|
-
return refs.split("\n").find((ref) => ref !== "HEAD");
|
|
13941
|
-
} catch {
|
|
13942
|
-
return void 0;
|
|
13943
|
-
}
|
|
13934
|
+
return void 0;
|
|
13944
13935
|
}
|
|
13945
13936
|
}
|
|
13946
13937
|
async function readBranchLastCommitTimestamp(cwd, branch) {
|
|
@@ -14028,9 +14019,9 @@ var EDITORS = [
|
|
|
14028
14019
|
];
|
|
14029
14020
|
async function defaultSpawnEditor(cli, args) {
|
|
14030
14021
|
const child = spawn3(cli, args, { detached: true, stdio: "ignore" });
|
|
14031
|
-
await new Promise((
|
|
14022
|
+
await new Promise((resolve13, reject) => {
|
|
14032
14023
|
child.once("error", reject);
|
|
14033
|
-
child.once("spawn",
|
|
14024
|
+
child.once("spawn", resolve13);
|
|
14034
14025
|
});
|
|
14035
14026
|
child.unref();
|
|
14036
14027
|
}
|
|
@@ -14872,7 +14863,7 @@ async function runArgvHook(hookCmd, cwd, context, stderr, stdout3) {
|
|
|
14872
14863
|
stderr("gji: hook argv command must include a non-empty command\n");
|
|
14873
14864
|
return;
|
|
14874
14865
|
}
|
|
14875
|
-
await new Promise((
|
|
14866
|
+
await new Promise((resolve13) => {
|
|
14876
14867
|
const child = spawn4(command, args, {
|
|
14877
14868
|
cwd,
|
|
14878
14869
|
shell: false,
|
|
@@ -14892,18 +14883,18 @@ async function runArgvHook(hookCmd, cwd, context, stderr, stdout3) {
|
|
|
14892
14883
|
`
|
|
14893
14884
|
);
|
|
14894
14885
|
}
|
|
14895
|
-
|
|
14886
|
+
resolve13();
|
|
14896
14887
|
});
|
|
14897
14888
|
child.on("error", (err) => {
|
|
14898
14889
|
stderr(`gji: hook failed to start: ${err.message}
|
|
14899
14890
|
`);
|
|
14900
|
-
|
|
14891
|
+
resolve13();
|
|
14901
14892
|
});
|
|
14902
14893
|
});
|
|
14903
14894
|
}
|
|
14904
14895
|
async function runShellHook(hookCmd, cwd, context, stderr, stdout3) {
|
|
14905
14896
|
const interpolated = interpolate(hookCmd, context);
|
|
14906
|
-
await new Promise((
|
|
14897
|
+
await new Promise((resolve13) => {
|
|
14907
14898
|
const child = spawn4(interpolated, {
|
|
14908
14899
|
cwd,
|
|
14909
14900
|
shell: true,
|
|
@@ -14921,12 +14912,12 @@ async function runShellHook(hookCmd, cwd, context, stderr, stdout3) {
|
|
|
14921
14912
|
stderr(`gji: hook exited with code ${code}: ${interpolated}
|
|
14922
14913
|
`);
|
|
14923
14914
|
}
|
|
14924
|
-
|
|
14915
|
+
resolve13();
|
|
14925
14916
|
});
|
|
14926
14917
|
child.on("error", (err) => {
|
|
14927
14918
|
stderr(`gji: hook failed to start: ${err.message}
|
|
14928
14919
|
`);
|
|
14929
|
-
|
|
14920
|
+
resolve13();
|
|
14930
14921
|
});
|
|
14931
14922
|
});
|
|
14932
14923
|
}
|
|
@@ -15091,8 +15082,8 @@ async function removeMissingRegistryEntries(paths, home = homedir4()) {
|
|
|
15091
15082
|
async function withRegistryLock(registryPath, operation) {
|
|
15092
15083
|
const previous = registryLocks.get(registryPath);
|
|
15093
15084
|
let release;
|
|
15094
|
-
const current = new Promise((
|
|
15095
|
-
release =
|
|
15085
|
+
const current = new Promise((resolve13) => {
|
|
15086
|
+
release = resolve13;
|
|
15096
15087
|
});
|
|
15097
15088
|
registryLocks.set(registryPath, current);
|
|
15098
15089
|
if (previous) await previous;
|
|
@@ -15294,11 +15285,12 @@ function toErrorMessage2(error) {
|
|
|
15294
15285
|
}
|
|
15295
15286
|
|
|
15296
15287
|
// src/domain/shared/concurrency.ts
|
|
15297
|
-
async function mapWithConcurrency(items, limit, mapper) {
|
|
15288
|
+
async function mapWithConcurrency(items, limit, mapper, signal) {
|
|
15298
15289
|
const results = new Array(items.length);
|
|
15299
15290
|
let nextIndex = 0;
|
|
15300
15291
|
async function readNext() {
|
|
15301
15292
|
for (; ; ) {
|
|
15293
|
+
throwIfAborted(signal);
|
|
15302
15294
|
const index = nextIndex;
|
|
15303
15295
|
nextIndex += 1;
|
|
15304
15296
|
if (index >= items.length) return;
|
|
@@ -15310,6 +15302,9 @@ async function mapWithConcurrency(items, limit, mapper) {
|
|
|
15310
15302
|
);
|
|
15311
15303
|
return results;
|
|
15312
15304
|
}
|
|
15305
|
+
function throwIfAborted(signal) {
|
|
15306
|
+
if (signal?.aborted) throw new Error("Operation cancelled");
|
|
15307
|
+
}
|
|
15313
15308
|
|
|
15314
15309
|
// src/infrastructure/worktree/info.ts
|
|
15315
15310
|
var MAX_WORKTREE_INFO_READ_CONCURRENCY = 8;
|
|
@@ -15695,6 +15690,10 @@ function formatAge(timestamp) {
|
|
|
15695
15690
|
return `${days}d ago`;
|
|
15696
15691
|
}
|
|
15697
15692
|
|
|
15693
|
+
// src/cli/commands/clean.ts
|
|
15694
|
+
import { realpath as realpath5 } from "node:fs/promises";
|
|
15695
|
+
import { basename as basename8, dirname as dirname10, join as join12, resolve as resolve11 } from "node:path";
|
|
15696
|
+
|
|
15698
15697
|
// node_modules/.pnpm/@clack+core@0.5.0/node_modules/@clack/core/dist/index.mjs
|
|
15699
15698
|
var import_sisteransi = __toESM(require_src(), 1);
|
|
15700
15699
|
var import_picocolors = __toESM(require_picocolors(), 1);
|
|
@@ -16401,6 +16400,205 @@ function sortPullRequests2(pullRequests) {
|
|
|
16401
16400
|
);
|
|
16402
16401
|
}
|
|
16403
16402
|
|
|
16403
|
+
// src/application/worktree/sources.ts
|
|
16404
|
+
var MAX_REPOSITORY_DISCOVERY_CONCURRENCY = 4;
|
|
16405
|
+
async function listRegisteredWorktreeSources(options) {
|
|
16406
|
+
const { cwd, repositoryPort, signal } = options;
|
|
16407
|
+
throwIfAborted2(signal);
|
|
16408
|
+
const registry = await repositoryPort.loadRegistry();
|
|
16409
|
+
const currentRoot = await repositoryPort.detectRepository(cwd).then((repository) => repository.currentRoot).catch(() => null);
|
|
16410
|
+
throwIfAborted2(signal);
|
|
16411
|
+
const results = await mapWithConcurrency(
|
|
16412
|
+
registry,
|
|
16413
|
+
MAX_REPOSITORY_DISCOVERY_CONCURRENCY,
|
|
16414
|
+
async (entry) => {
|
|
16415
|
+
throwIfAborted2(signal);
|
|
16416
|
+
try {
|
|
16417
|
+
const worktrees = await repositoryPort.listWorktrees(entry.path);
|
|
16418
|
+
return { entry, skipped: false, worktrees };
|
|
16419
|
+
} catch {
|
|
16420
|
+
return { entry, skipped: true, worktrees: [] };
|
|
16421
|
+
}
|
|
16422
|
+
},
|
|
16423
|
+
signal
|
|
16424
|
+
);
|
|
16425
|
+
const allItems = [];
|
|
16426
|
+
const skipped = [];
|
|
16427
|
+
for (const result of results) {
|
|
16428
|
+
if (result.skipped) {
|
|
16429
|
+
skipped.push(result.entry);
|
|
16430
|
+
continue;
|
|
16431
|
+
}
|
|
16432
|
+
const { entry, worktrees } = result;
|
|
16433
|
+
for (const worktree of worktrees) {
|
|
16434
|
+
allItems.push({
|
|
16435
|
+
repoRoot: entry.path,
|
|
16436
|
+
repoName: entry.name,
|
|
16437
|
+
worktree: {
|
|
16438
|
+
...worktree,
|
|
16439
|
+
isCurrent: currentRoot !== null && worktree.path === currentRoot
|
|
16440
|
+
}
|
|
16441
|
+
});
|
|
16442
|
+
}
|
|
16443
|
+
}
|
|
16444
|
+
return { skipped, sources: allItems };
|
|
16445
|
+
}
|
|
16446
|
+
async function listDiscoverableWorktreeSources(options) {
|
|
16447
|
+
const { cwd, repositoryPort } = options;
|
|
16448
|
+
const currentRepository = await repositoryPort.detectRepository(cwd).catch(() => null);
|
|
16449
|
+
const registered = await listRegisteredWorktreeSources(options);
|
|
16450
|
+
if (currentRepository === null) {
|
|
16451
|
+
return {
|
|
16452
|
+
skipped: registered.skipped,
|
|
16453
|
+
sources: deduplicateWorktreeSources(registered.sources)
|
|
16454
|
+
};
|
|
16455
|
+
}
|
|
16456
|
+
let currentSources = [];
|
|
16457
|
+
try {
|
|
16458
|
+
currentSources = (await repositoryPort.listWorktrees(cwd)).map(
|
|
16459
|
+
(worktree) => ({
|
|
16460
|
+
repoName: currentRepository.repoName,
|
|
16461
|
+
repoRoot: currentRepository.repoRoot,
|
|
16462
|
+
worktree
|
|
16463
|
+
})
|
|
16464
|
+
);
|
|
16465
|
+
} catch {
|
|
16466
|
+
}
|
|
16467
|
+
return {
|
|
16468
|
+
skipped: registered.skipped,
|
|
16469
|
+
sources: deduplicateWorktreeSources([
|
|
16470
|
+
...currentSources,
|
|
16471
|
+
...registered.sources
|
|
16472
|
+
])
|
|
16473
|
+
};
|
|
16474
|
+
}
|
|
16475
|
+
function deduplicateWorktreeSources(sources) {
|
|
16476
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16477
|
+
return sources.filter((source) => {
|
|
16478
|
+
if (seen.has(source.worktree.path)) return false;
|
|
16479
|
+
seen.add(source.worktree.path);
|
|
16480
|
+
return true;
|
|
16481
|
+
});
|
|
16482
|
+
}
|
|
16483
|
+
function throwIfAborted2(signal) {
|
|
16484
|
+
if (signal?.aborted) throw new Error("Operation cancelled");
|
|
16485
|
+
}
|
|
16486
|
+
|
|
16487
|
+
// src/domain/worktree/path-filter.ts
|
|
16488
|
+
function createWorktreePathFilter(patterns) {
|
|
16489
|
+
const matchers = patterns.map(createPathMatcher);
|
|
16490
|
+
if (matchers.length === 0) return () => true;
|
|
16491
|
+
return (worktreePath) => matchers.some((matches) => matches(worktreePath));
|
|
16492
|
+
}
|
|
16493
|
+
function findGlobIndex(pattern) {
|
|
16494
|
+
for (let index = 0; index < pattern.length; index += 1) {
|
|
16495
|
+
const character = pattern[index];
|
|
16496
|
+
if (character === "\\") index += 1;
|
|
16497
|
+
else if (character === "*" || character === "?") return index;
|
|
16498
|
+
}
|
|
16499
|
+
return -1;
|
|
16500
|
+
}
|
|
16501
|
+
function escapePathPattern(path9) {
|
|
16502
|
+
return path9.replace(/[\\*?]/g, "\\$&");
|
|
16503
|
+
}
|
|
16504
|
+
function unescapePathPattern(pattern) {
|
|
16505
|
+
return pattern.replace(/\\(.)/g, "$1");
|
|
16506
|
+
}
|
|
16507
|
+
function createPathMatcher(pattern) {
|
|
16508
|
+
if (findGlobIndex(pattern) !== -1) {
|
|
16509
|
+
const regExp = globToRegExp(pattern);
|
|
16510
|
+
return (path9) => regExp.test(path9);
|
|
16511
|
+
}
|
|
16512
|
+
const literal = unescapePathPattern(pattern);
|
|
16513
|
+
const prefix = literal === "/" ? "/" : `${literal}/`;
|
|
16514
|
+
return (path9) => path9 === literal || path9.startsWith(prefix);
|
|
16515
|
+
}
|
|
16516
|
+
function globToRegExp(pattern) {
|
|
16517
|
+
let source = "";
|
|
16518
|
+
for (let index = 0; index < pattern.length; index += 1) {
|
|
16519
|
+
let character = pattern[index];
|
|
16520
|
+
if (character === "*") {
|
|
16521
|
+
if (pattern[index + 1] === "*") {
|
|
16522
|
+
index += 1;
|
|
16523
|
+
if (pattern[index + 1] === "/") {
|
|
16524
|
+
index += 1;
|
|
16525
|
+
source += "(?:.*/)?";
|
|
16526
|
+
} else {
|
|
16527
|
+
source += ".*";
|
|
16528
|
+
}
|
|
16529
|
+
} else {
|
|
16530
|
+
source += "[^/]*";
|
|
16531
|
+
}
|
|
16532
|
+
} else if (character === "?") {
|
|
16533
|
+
source += "[^/]";
|
|
16534
|
+
} else {
|
|
16535
|
+
if (character === "\\" && index + 1 < pattern.length) {
|
|
16536
|
+
index += 1;
|
|
16537
|
+
character = pattern[index];
|
|
16538
|
+
}
|
|
16539
|
+
source += character.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
16540
|
+
}
|
|
16541
|
+
}
|
|
16542
|
+
return new RegExp(`^${source}$`);
|
|
16543
|
+
}
|
|
16544
|
+
|
|
16545
|
+
// src/domain/worktree/policy.ts
|
|
16546
|
+
import { homedir as homedir5 } from "node:os";
|
|
16547
|
+
import { basename as basename7, dirname as dirname8, join as join10 } from "node:path";
|
|
16548
|
+
function resolveWorktreePath(repoRoot, branch, basePath) {
|
|
16549
|
+
const segments = branch.split("/").filter(Boolean);
|
|
16550
|
+
if (segments.length === 0) {
|
|
16551
|
+
throw new Error("Branch name must not be empty.");
|
|
16552
|
+
}
|
|
16553
|
+
if (segments.some((segment) => segment === "." || segment === "..")) {
|
|
16554
|
+
throw new Error(
|
|
16555
|
+
`Branch name '${branch}' contains an invalid path segment.`
|
|
16556
|
+
);
|
|
16557
|
+
}
|
|
16558
|
+
const base = basePath ? expandTildeInPath(basePath) : join10(dirname8(repoRoot), "worktrees", basename7(repoRoot));
|
|
16559
|
+
return join10(base, ...segments);
|
|
16560
|
+
}
|
|
16561
|
+
function validateBranchName(name) {
|
|
16562
|
+
if (name.length === 0) {
|
|
16563
|
+
return "Branch name must not be empty.";
|
|
16564
|
+
}
|
|
16565
|
+
if (/[\x00-\x1f\x7f ~^:?*[\\\s]/.test(name)) {
|
|
16566
|
+
return `Branch name '${name}' contains an invalid character.`;
|
|
16567
|
+
}
|
|
16568
|
+
if (name.startsWith("-")) {
|
|
16569
|
+
return `Branch name '${name}' must not start with a dash.`;
|
|
16570
|
+
}
|
|
16571
|
+
if (name.startsWith("/") || name.endsWith("/") || name.includes("//")) {
|
|
16572
|
+
return `Branch name '${name}' has invalid slash placement.`;
|
|
16573
|
+
}
|
|
16574
|
+
if (name.includes("..")) {
|
|
16575
|
+
return `Branch name '${name}' must not contain '..'.`;
|
|
16576
|
+
}
|
|
16577
|
+
if (name.endsWith(".")) {
|
|
16578
|
+
return `Branch name '${name}' must not end with '.'.`;
|
|
16579
|
+
}
|
|
16580
|
+
if (name.includes("@{")) {
|
|
16581
|
+
return `Branch name '${name}' must not contain '@{'.`;
|
|
16582
|
+
}
|
|
16583
|
+
if (name === "@") {
|
|
16584
|
+
return "Branch name cannot be '@'.";
|
|
16585
|
+
}
|
|
16586
|
+
for (const segment of name.split("/")) {
|
|
16587
|
+
if (segment.startsWith(".")) {
|
|
16588
|
+
return `Branch name '${name}' contains a path component starting with '.'.`;
|
|
16589
|
+
}
|
|
16590
|
+
if (segment.endsWith(".lock")) {
|
|
16591
|
+
return `Branch name '${name}' contains a path component ending with '.lock'.`;
|
|
16592
|
+
}
|
|
16593
|
+
}
|
|
16594
|
+
return null;
|
|
16595
|
+
}
|
|
16596
|
+
function expandTildeInPath(path9) {
|
|
16597
|
+
if (path9 === "~") return homedir5();
|
|
16598
|
+
if (path9.startsWith("~/")) return join10(homedir5(), path9.slice(2));
|
|
16599
|
+
return path9;
|
|
16600
|
+
}
|
|
16601
|
+
|
|
16404
16602
|
// src/presentation/worktree/picker.ts
|
|
16405
16603
|
import { env as env4, platform as platform2, stdin, stdout } from "node:process";
|
|
16406
16604
|
|
|
@@ -16501,6 +16699,7 @@ async function promptForSingleWorktree(message, worktrees, io = {}) {
|
|
|
16501
16699
|
message,
|
|
16502
16700
|
multiple: false,
|
|
16503
16701
|
output: io.output,
|
|
16702
|
+
catalog: io.catalog,
|
|
16504
16703
|
scope: io.scope
|
|
16505
16704
|
});
|
|
16506
16705
|
return typeof choice === "string" ? choice : null;
|
|
@@ -16511,7 +16710,9 @@ async function promptForMultipleWorktrees(message, worktrees, io = {}) {
|
|
|
16511
16710
|
input: io.input,
|
|
16512
16711
|
message,
|
|
16513
16712
|
multiple: true,
|
|
16514
|
-
output: io.output
|
|
16713
|
+
output: io.output,
|
|
16714
|
+
catalog: io.catalog,
|
|
16715
|
+
scope: io.scope
|
|
16515
16716
|
});
|
|
16516
16717
|
return Array.isArray(choice) ? choice : null;
|
|
16517
16718
|
}
|
|
@@ -16600,6 +16801,7 @@ var SearchablePrompt = class {
|
|
|
16600
16801
|
scope;
|
|
16601
16802
|
scopeTogglePending = false;
|
|
16602
16803
|
promptClosed = false;
|
|
16804
|
+
cancellation = new AbortController();
|
|
16603
16805
|
prompt;
|
|
16604
16806
|
run() {
|
|
16605
16807
|
return this.prompt.prompt();
|
|
@@ -16650,9 +16852,14 @@ var SearchablePrompt = class {
|
|
|
16650
16852
|
this.scopeTogglePending = true;
|
|
16651
16853
|
renderPrompt(prompt);
|
|
16652
16854
|
try {
|
|
16653
|
-
const nextScope = await currentScope.toggle();
|
|
16855
|
+
const nextScope = await currentScope.toggle(this.cancellation.signal);
|
|
16654
16856
|
if (this.promptClosed) return;
|
|
16655
|
-
|
|
16857
|
+
const nextEntries = await buildWorktreePromptEntries(nextScope.sources, {
|
|
16858
|
+
catalog: this.options.catalog,
|
|
16859
|
+
metadata: nextScope.metadata
|
|
16860
|
+
});
|
|
16861
|
+
this.entries = nextEntries.map(buildSearchableWorktreeEntry);
|
|
16862
|
+
this.pruneHiddenSelections();
|
|
16656
16863
|
this.scope = {
|
|
16657
16864
|
label: nextScope.label,
|
|
16658
16865
|
toggle: currentScope.toggle,
|
|
@@ -16683,6 +16890,7 @@ var SearchablePrompt = class {
|
|
|
16683
16890
|
}
|
|
16684
16891
|
cancel(prompt) {
|
|
16685
16892
|
this.promptClosed = true;
|
|
16893
|
+
this.cancellation.abort();
|
|
16686
16894
|
prompt.state = "cancel";
|
|
16687
16895
|
renderPrompt(prompt);
|
|
16688
16896
|
closePrompt(prompt);
|
|
@@ -16773,6 +16981,14 @@ var SearchablePrompt = class {
|
|
|
16773
16981
|
}
|
|
16774
16982
|
this.selected.add(entry.value);
|
|
16775
16983
|
}
|
|
16984
|
+
pruneHiddenSelections() {
|
|
16985
|
+
const visibleValues = new Set(
|
|
16986
|
+
this.entries.filter(isSelectableEntry).map((entry) => entry.value)
|
|
16987
|
+
);
|
|
16988
|
+
for (const value of this.selected) {
|
|
16989
|
+
if (!visibleValues.has(value)) this.selected.delete(value);
|
|
16990
|
+
}
|
|
16991
|
+
}
|
|
16776
16992
|
render(state, error) {
|
|
16777
16993
|
const entries = this.visibleEntries();
|
|
16778
16994
|
const visibleEntries = windowPromptEntries(
|
|
@@ -17392,21 +17608,21 @@ import {
|
|
|
17392
17608
|
unlink as unlink2,
|
|
17393
17609
|
writeFile as writeFile7
|
|
17394
17610
|
} from "node:fs/promises";
|
|
17395
|
-
import { homedir as
|
|
17396
|
-
import { dirname as
|
|
17611
|
+
import { homedir as homedir6 } from "node:os";
|
|
17612
|
+
import { dirname as dirname9, join as join11, resolve as resolve10 } from "node:path";
|
|
17397
17613
|
var MAX_UNDO_RECORDS = 20;
|
|
17398
17614
|
var UNDO_LOCK_STALE_AFTER_MS = 6e4;
|
|
17399
17615
|
var UNDO_LOCK_RETRY_DELAY_MS = 5;
|
|
17400
17616
|
var UNDO_LOCK_WAIT_TIMEOUT_MS = UNDO_LOCK_STALE_AFTER_MS + UNDO_LOCK_RETRY_DELAY_MS;
|
|
17401
17617
|
var undoLogQueues = /* @__PURE__ */ new Map();
|
|
17402
|
-
function undoLogPath(home =
|
|
17618
|
+
function undoLogPath(home = homedir6(), globalConfigDirectory = defaultCliDependencies.configStore.GLOBAL_CONFIG_DIRECTORY) {
|
|
17403
17619
|
const configured = process.env.GJI_CONFIG_DIR;
|
|
17404
|
-
return
|
|
17405
|
-
configured ? resolve10(configured) :
|
|
17620
|
+
return join11(
|
|
17621
|
+
configured ? resolve10(configured) : join11(home, globalConfigDirectory),
|
|
17406
17622
|
"undo-log.json"
|
|
17407
17623
|
);
|
|
17408
17624
|
}
|
|
17409
|
-
async function loadUndoRecords(home =
|
|
17625
|
+
async function loadUndoRecords(home = homedir6(), globalConfigDirectory = defaultCliDependencies.configStore.GLOBAL_CONFIG_DIRECTORY) {
|
|
17410
17626
|
try {
|
|
17411
17627
|
const parsed = JSON.parse(
|
|
17412
17628
|
await readFile7(undoLogPath(home, globalConfigDirectory), "utf8")
|
|
@@ -17416,7 +17632,7 @@ async function loadUndoRecords(home = homedir5(), globalConfigDirectory = defaul
|
|
|
17416
17632
|
return [];
|
|
17417
17633
|
}
|
|
17418
17634
|
}
|
|
17419
|
-
async function recordUndoOperation(op, repoRoot, worktrees, home =
|
|
17635
|
+
async function recordUndoOperation(op, repoRoot, worktrees, home = homedir6(), runtime = defaultCliDependencies) {
|
|
17420
17636
|
const globalConfigDirectory = runtime.configStore.GLOBAL_CONFIG_DIRECTORY;
|
|
17421
17637
|
const { isDirtyWorktree: isDirtyWorktree2, runGit: runGit2 } = runtime.git;
|
|
17422
17638
|
const entries = [];
|
|
@@ -17466,7 +17682,7 @@ async function recordUndoOperation(op, repoRoot, worktrees, home = homedir5(), r
|
|
|
17466
17682
|
globalConfigDirectory
|
|
17467
17683
|
);
|
|
17468
17684
|
}
|
|
17469
|
-
async function finalizeUndoOperation(record, removedWorktrees, home =
|
|
17685
|
+
async function finalizeUndoOperation(record, removedWorktrees, home = homedir6(), globalConfigDirectory = defaultCliDependencies.configStore.GLOBAL_CONFIG_DIRECTORY) {
|
|
17470
17686
|
const removedPaths = new Set(
|
|
17471
17687
|
removedWorktrees.map((worktree) => worktree.path)
|
|
17472
17688
|
);
|
|
@@ -17486,7 +17702,7 @@ async function finalizeUndoOperation(record, removedWorktrees, home = homedir5()
|
|
|
17486
17702
|
globalConfigDirectory
|
|
17487
17703
|
);
|
|
17488
17704
|
}
|
|
17489
|
-
async function restoreUndoRecord(record, home =
|
|
17705
|
+
async function restoreUndoRecord(record, home = homedir6(), runtime = defaultCliDependencies) {
|
|
17490
17706
|
const globalConfigDirectory = runtime.configStore.GLOBAL_CONFIG_DIRECTORY;
|
|
17491
17707
|
const restored = [];
|
|
17492
17708
|
const failed = [];
|
|
@@ -17531,7 +17747,7 @@ async function runUndoCommand(options) {
|
|
|
17531
17747
|
options.stderr(
|
|
17532
17748
|
"Warning: undo journal is invalid; starting with an empty journal\n"
|
|
17533
17749
|
);
|
|
17534
|
-
const records = await loadUndoRecords(
|
|
17750
|
+
const records = await loadUndoRecords(homedir6(), globalConfigDirectory);
|
|
17535
17751
|
if (options.list) {
|
|
17536
17752
|
if (options.json) options.stdout(`${JSON.stringify(records, null, 2)}
|
|
17537
17753
|
`);
|
|
@@ -17578,7 +17794,7 @@ async function runUndoCommand(options) {
|
|
|
17578
17794
|
}
|
|
17579
17795
|
if (!record)
|
|
17580
17796
|
return emitUndoError(options, `undo record not found: ${options.id}`);
|
|
17581
|
-
const result = await restoreUndoRecord(record,
|
|
17797
|
+
const result = await restoreUndoRecord(record, homedir6(), runtime);
|
|
17582
17798
|
if (options.json)
|
|
17583
17799
|
options.stdout(
|
|
17584
17800
|
`${JSON.stringify({ restored: result.restored.map((entry) => ({ branch: entry.branch, path: entry.path })), failed: result.failed.map((entry) => ({ branch: entry.branch, path: entry.path, error: entry.message })) }, null, 2)}
|
|
@@ -17606,7 +17822,7 @@ async function runUndoCommand(options) {
|
|
|
17606
17822
|
async function hasMalformedUndoLog(globalConfigDirectory = defaultCliDependencies.configStore.GLOBAL_CONFIG_DIRECTORY) {
|
|
17607
17823
|
try {
|
|
17608
17824
|
const parsed = JSON.parse(
|
|
17609
|
-
await readFile7(undoLogPath(
|
|
17825
|
+
await readFile7(undoLogPath(homedir6(), globalConfigDirectory), "utf8")
|
|
17610
17826
|
);
|
|
17611
17827
|
return !Array.isArray(parsed) || parsed.some((entry) => !isUndoRecord(entry));
|
|
17612
17828
|
} catch (error) {
|
|
@@ -17698,7 +17914,7 @@ async function assertCommitExists(repoRoot, headSha, runGit2) {
|
|
|
17698
17914
|
}
|
|
17699
17915
|
async function enqueueUndoLogWrite(home, operation, globalConfigDirectory = defaultCliDependencies.configStore.GLOBAL_CONFIG_DIRECTORY) {
|
|
17700
17916
|
const path9 = undoLogPath(home, globalConfigDirectory);
|
|
17701
|
-
await mkdir6(
|
|
17917
|
+
await mkdir6(dirname9(path9), { recursive: true });
|
|
17702
17918
|
const previous = undoLogQueues.get(path9) ?? Promise.resolve();
|
|
17703
17919
|
const runLocked = async () => {
|
|
17704
17920
|
const release = await acquireUndoLogLock(path9);
|
|
@@ -17739,7 +17955,7 @@ async function acquireUndoLogLock(path9) {
|
|
|
17739
17955
|
const remainingMs = deadline - Date.now();
|
|
17740
17956
|
if (remainingMs > 0)
|
|
17741
17957
|
await new Promise(
|
|
17742
|
-
(
|
|
17958
|
+
(resolve13) => setTimeout(resolve13, Math.min(UNDO_LOCK_RETRY_DELAY_MS, remainingMs))
|
|
17743
17959
|
);
|
|
17744
17960
|
}
|
|
17745
17961
|
}
|
|
@@ -17747,7 +17963,7 @@ async function acquireUndoLogLock(path9) {
|
|
|
17747
17963
|
}
|
|
17748
17964
|
async function writeUndoRecords(home, records, globalConfigDirectory = defaultCliDependencies.configStore.GLOBAL_CONFIG_DIRECTORY) {
|
|
17749
17965
|
const path9 = undoLogPath(home, globalConfigDirectory);
|
|
17750
|
-
await mkdir6(
|
|
17966
|
+
await mkdir6(dirname9(path9), { recursive: true });
|
|
17751
17967
|
const temporaryPath = `${path9}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
17752
17968
|
try {
|
|
17753
17969
|
await writeFile7(temporaryPath, `${JSON.stringify(records, null, 2)}
|
|
@@ -17790,6 +18006,15 @@ function emitUndoError(options, message) {
|
|
|
17790
18006
|
}
|
|
17791
18007
|
|
|
17792
18008
|
// src/cli/commands/clean.ts
|
|
18009
|
+
function toPromptSource(candidate) {
|
|
18010
|
+
return {
|
|
18011
|
+
repoName: candidate.repoName,
|
|
18012
|
+
repoRoot: candidate.repoRoot,
|
|
18013
|
+
worktree: candidate.worktree
|
|
18014
|
+
};
|
|
18015
|
+
}
|
|
18016
|
+
var MAX_CLEAN_REPOSITORY_CONCURRENCY = 4;
|
|
18017
|
+
var MAX_CLEAN_WORKTREE_CONCURRENCY = 8;
|
|
17793
18018
|
function createCleanCommand(dependencies = {}) {
|
|
17794
18019
|
const promptForWorktrees = dependencies.promptForWorktrees ?? defaultPromptForWorktrees;
|
|
17795
18020
|
const confirmRemoval = dependencies.confirmRemoval ?? defaultConfirmRemoval;
|
|
@@ -17800,7 +18025,7 @@ function createCleanCommand(dependencies = {}) {
|
|
|
17800
18025
|
const {
|
|
17801
18026
|
readWorktreeHealth: readWorktreeHealth2,
|
|
17802
18027
|
isBranchMergedInto: isBranchMergedInto2,
|
|
17803
|
-
|
|
18028
|
+
resolveRemoteBase: resolveRemoteBase2,
|
|
17804
18029
|
runGit: runGit2
|
|
17805
18030
|
} = runtime.git;
|
|
17806
18031
|
const { loadEffectiveConfig: loadEffectiveConfig2 } = runtime.config;
|
|
@@ -17830,30 +18055,115 @@ function createCleanCommand(dependencies = {}) {
|
|
|
17830
18055
|
options.cwd,
|
|
17831
18056
|
sourceDependencies
|
|
17832
18057
|
);
|
|
17833
|
-
const
|
|
17834
|
-
|
|
18058
|
+
const pathPatterns = await canonicalizePathPatterns(
|
|
18059
|
+
options.cwd,
|
|
18060
|
+
options.paths ?? []
|
|
17835
18061
|
);
|
|
17836
|
-
const
|
|
17837
|
-
|
|
17838
|
-
|
|
17839
|
-
|
|
17840
|
-
|
|
17841
|
-
|
|
17842
|
-
|
|
17843
|
-
|
|
17844
|
-
repository.repoRoot,
|
|
17845
|
-
linkedCleanupCandidates,
|
|
17846
|
-
staleBaseRef,
|
|
17847
|
-
readWorktreeHealth2,
|
|
17848
|
-
isBranchMergedInto2
|
|
17849
|
-
) : linkedCleanupCandidates;
|
|
17850
|
-
if (cleanupCandidates.length === 0) {
|
|
18062
|
+
const matchesPathFilter = createWorktreePathFilter(pathPatterns);
|
|
18063
|
+
const searchAllRepositories = pathPatterns.length > 0;
|
|
18064
|
+
let allSourceCount = 0;
|
|
18065
|
+
const emitNoCandidates = (sourceCount) => {
|
|
18066
|
+
if (pathPatterns.length > 0 && sourceCount === 0) {
|
|
18067
|
+
emitError(options, "No linked worktrees match --path");
|
|
18068
|
+
return 1;
|
|
18069
|
+
}
|
|
17851
18070
|
if (options.stale) {
|
|
17852
18071
|
emitNoStaleCandidates(options);
|
|
17853
18072
|
return 0;
|
|
17854
18073
|
}
|
|
17855
18074
|
emitError(options, "No linked worktrees to clean");
|
|
17856
18075
|
return 1;
|
|
18076
|
+
};
|
|
18077
|
+
const currentSources = linkedWorktrees.filter(
|
|
18078
|
+
(worktree) => worktree.path !== repository.currentRoot && matchesPathFilter(worktree.path)
|
|
18079
|
+
).map((worktree) => ({
|
|
18080
|
+
repoName: repository.repoName,
|
|
18081
|
+
repoRoot: repository.repoRoot,
|
|
18082
|
+
worktree
|
|
18083
|
+
}));
|
|
18084
|
+
const cleanupDependencies = {
|
|
18085
|
+
isBranchMergedInto: isBranchMergedInto2,
|
|
18086
|
+
loadEffectiveConfig: loadEffectiveConfig2,
|
|
18087
|
+
readWorktreeHealth: readWorktreeHealth2,
|
|
18088
|
+
resolveRemoteBase: resolveRemoteBase2,
|
|
18089
|
+
runGit: runGit2
|
|
18090
|
+
};
|
|
18091
|
+
const currentCandidates = await resolveCleanupCandidates(
|
|
18092
|
+
{
|
|
18093
|
+
sources: currentSources,
|
|
18094
|
+
stale: options.stale,
|
|
18095
|
+
stderr: options.stderr
|
|
18096
|
+
},
|
|
18097
|
+
cleanupDependencies
|
|
18098
|
+
);
|
|
18099
|
+
let activeCandidates = currentCandidates;
|
|
18100
|
+
let allCandidates = null;
|
|
18101
|
+
let currentScope = true;
|
|
18102
|
+
const interactiveSelection = !options.force && !options.json && !isHeadless();
|
|
18103
|
+
const skippedRepositories = [];
|
|
18104
|
+
const loadAllCandidates = async (signal) => {
|
|
18105
|
+
throwIfAborted3(signal);
|
|
18106
|
+
if (allCandidates !== null) return allCandidates;
|
|
18107
|
+
const registered = await listRegisteredWorktreeSources({
|
|
18108
|
+
cwd: options.cwd,
|
|
18109
|
+
repositoryPort: sourceDependencies,
|
|
18110
|
+
signal
|
|
18111
|
+
});
|
|
18112
|
+
skippedRepositories.push(...registered.skipped);
|
|
18113
|
+
if (registered.skipped.length > 0) {
|
|
18114
|
+
reportSkippedRepositories(skippedRepositories, options.stderr);
|
|
18115
|
+
}
|
|
18116
|
+
const sources = deduplicateWorktreeSources([
|
|
18117
|
+
...currentSources,
|
|
18118
|
+
...registered.sources
|
|
18119
|
+
]).filter(
|
|
18120
|
+
(source) => source.repoRoot !== void 0 && source.worktree.path !== source.repoRoot && source.worktree.path !== repository.currentRoot && matchesPathFilter(source.worktree.path)
|
|
18121
|
+
);
|
|
18122
|
+
allSourceCount = sources.length;
|
|
18123
|
+
const otherRepositorySources = sources.filter(
|
|
18124
|
+
(source) => source.repoRoot !== repository.repoRoot
|
|
18125
|
+
);
|
|
18126
|
+
const otherRepositoryCandidates = await resolveCleanupCandidates(
|
|
18127
|
+
{
|
|
18128
|
+
sources: otherRepositorySources,
|
|
18129
|
+
stale: options.stale,
|
|
18130
|
+
stderr: options.stderr,
|
|
18131
|
+
signal
|
|
18132
|
+
},
|
|
18133
|
+
cleanupDependencies
|
|
18134
|
+
);
|
|
18135
|
+
allCandidates = [...currentCandidates, ...otherRepositoryCandidates];
|
|
18136
|
+
throwIfAborted3(signal);
|
|
18137
|
+
return allCandidates;
|
|
18138
|
+
};
|
|
18139
|
+
const scope = {
|
|
18140
|
+
label: "current repository",
|
|
18141
|
+
toggleLabel: "all repositories",
|
|
18142
|
+
toggle: async (signal) => {
|
|
18143
|
+
const nextCurrentScope = !currentScope;
|
|
18144
|
+
const nextCandidates = nextCurrentScope ? currentCandidates : await loadAllCandidates(signal);
|
|
18145
|
+
throwIfAborted3(signal);
|
|
18146
|
+
currentScope = nextCurrentScope;
|
|
18147
|
+
activeCandidates = nextCandidates;
|
|
18148
|
+
return {
|
|
18149
|
+
sources: nextCandidates.map(toPromptSource),
|
|
18150
|
+
metadata: nextCurrentScope ? "full" : "fast",
|
|
18151
|
+
label: nextCurrentScope ? "current repository" : "all repositories",
|
|
18152
|
+
toggleLabel: nextCurrentScope ? "all repositories" : "current repository"
|
|
18153
|
+
};
|
|
18154
|
+
}
|
|
18155
|
+
};
|
|
18156
|
+
if (searchAllRepositories || interactiveSelection && currentCandidates.length === 0) {
|
|
18157
|
+
const loadedAllCandidates = await loadAllCandidates();
|
|
18158
|
+
if (loadedAllCandidates.length === 0) {
|
|
18159
|
+
return emitNoCandidates(allSourceCount);
|
|
18160
|
+
}
|
|
18161
|
+
currentScope = false;
|
|
18162
|
+
activeCandidates = loadedAllCandidates;
|
|
18163
|
+
scope.label = "all repositories";
|
|
18164
|
+
scope.toggleLabel = "current repository";
|
|
18165
|
+
} else if (currentCandidates.length === 0) {
|
|
18166
|
+
return emitNoCandidates(currentSources.length);
|
|
17857
18167
|
}
|
|
17858
18168
|
if (!options.dryRun && !options.force && (options.json || isHeadless())) {
|
|
17859
18169
|
const message = "--force is required";
|
|
@@ -17868,27 +18178,29 @@ function createCleanCommand(dependencies = {}) {
|
|
|
17868
18178
|
return 1;
|
|
17869
18179
|
}
|
|
17870
18180
|
const shouldSelectAll = options.force || options.dryRun && (options.stale || options.json || isHeadless());
|
|
17871
|
-
const selections = shouldSelectAll ?
|
|
17872
|
-
await buildWorktreePromptEntries(
|
|
17873
|
-
|
|
17874
|
-
|
|
17875
|
-
|
|
17876
|
-
|
|
17877
|
-
|
|
17878
|
-
)
|
|
18181
|
+
const selections = shouldSelectAll ? activeCandidates.map(({ worktree }) => worktree.path) : await promptForWorktrees(
|
|
18182
|
+
await buildWorktreePromptEntries(activeCandidates, {
|
|
18183
|
+
metadata: currentScope ? "full" : "fast",
|
|
18184
|
+
catalog: runtime.worktreeCatalog
|
|
18185
|
+
}),
|
|
18186
|
+
scope,
|
|
18187
|
+
runtime.worktreeCatalog
|
|
17879
18188
|
);
|
|
17880
18189
|
if (!selections || selections.length === 0) {
|
|
17881
18190
|
options.stderr("Aborted\n");
|
|
17882
18191
|
return 1;
|
|
17883
18192
|
}
|
|
17884
|
-
const
|
|
17885
|
-
|
|
18193
|
+
const selectedCandidates = resolveSelectedCandidates(
|
|
18194
|
+
activeCandidates,
|
|
17886
18195
|
selections
|
|
17887
18196
|
);
|
|
17888
|
-
if (
|
|
18197
|
+
if (selectedCandidates.length !== selections.length) {
|
|
17889
18198
|
options.stderr("Selected worktree no longer exists\n");
|
|
17890
18199
|
return 1;
|
|
17891
18200
|
}
|
|
18201
|
+
const selectedWorktrees = selectedCandidates.map(
|
|
18202
|
+
({ worktree }) => worktree
|
|
18203
|
+
);
|
|
17892
18204
|
const selectedWorktreeInfos = await readWorktreeInfos2(selectedWorktrees);
|
|
17893
18205
|
const selectedInfoByPath = new Map(
|
|
17894
18206
|
selectedWorktreeInfos.map((info) => [info.path, info])
|
|
@@ -17916,38 +18228,48 @@ function createCleanCommand(dependencies = {}) {
|
|
|
17916
18228
|
}
|
|
17917
18229
|
return 0;
|
|
17918
18230
|
}
|
|
17919
|
-
const
|
|
18231
|
+
const candidatesByRepository = groupCandidatesByRepository(selectedCandidates);
|
|
18232
|
+
const journals = /* @__PURE__ */ new Map();
|
|
17920
18233
|
const failures = [];
|
|
17921
|
-
let journal;
|
|
17922
18234
|
try {
|
|
17923
|
-
|
|
17924
|
-
|
|
17925
|
-
|
|
17926
|
-
|
|
17927
|
-
|
|
17928
|
-
|
|
17929
|
-
|
|
18235
|
+
for (const [repoRoot, candidates] of candidatesByRepository) {
|
|
18236
|
+
const journal = await recordUndoOperation(
|
|
18237
|
+
"clean",
|
|
18238
|
+
repoRoot,
|
|
18239
|
+
candidates.map(({ worktree }) => worktree),
|
|
18240
|
+
void 0,
|
|
18241
|
+
runtime
|
|
18242
|
+
);
|
|
18243
|
+
if (!journal) {
|
|
18244
|
+
await discardUndoRecords(
|
|
18245
|
+
journals.values(),
|
|
18246
|
+
runtime.configStore.GLOBAL_CONFIG_DIRECTORY
|
|
18247
|
+
);
|
|
18248
|
+
emitError(
|
|
18249
|
+
options,
|
|
18250
|
+
"could not capture undo state; no worktrees were removed"
|
|
18251
|
+
);
|
|
18252
|
+
return 1;
|
|
18253
|
+
}
|
|
18254
|
+
journals.set(repoRoot, journal);
|
|
18255
|
+
}
|
|
17930
18256
|
} catch (error) {
|
|
17931
|
-
|
|
17932
|
-
|
|
17933
|
-
|
|
18257
|
+
await discardUndoRecords(
|
|
18258
|
+
journals.values(),
|
|
18259
|
+
runtime.configStore.GLOBAL_CONFIG_DIRECTORY
|
|
17934
18260
|
);
|
|
17935
|
-
return 1;
|
|
17936
|
-
}
|
|
17937
|
-
if (!journal) {
|
|
17938
18261
|
emitError(
|
|
17939
18262
|
options,
|
|
17940
|
-
|
|
18263
|
+
`could not write undo journal; no worktrees were removed: ${toMessage2(error)}`
|
|
17941
18264
|
);
|
|
17942
18265
|
return 1;
|
|
17943
18266
|
}
|
|
17944
|
-
|
|
18267
|
+
const removedCandidates = [];
|
|
18268
|
+
for (const candidate of selectedCandidates) {
|
|
18269
|
+
const { repoRoot, staleBaseRef, worktree } = candidate;
|
|
17945
18270
|
if (options.stale && !await isStaleCleanupCandidate(
|
|
17946
|
-
|
|
17947
|
-
|
|
17948
|
-
staleBaseRef,
|
|
17949
|
-
readWorktreeHealth2,
|
|
17950
|
-
isBranchMergedInto2
|
|
18271
|
+
{ repoRoot, worktree, baseBranch: staleBaseRef },
|
|
18272
|
+
cleanupDependencies
|
|
17951
18273
|
)) {
|
|
17952
18274
|
options.stderr(
|
|
17953
18275
|
`Skipped ${worktree.path}: no longer a safe stale cleanup candidate
|
|
@@ -17956,7 +18278,7 @@ function createCleanCommand(dependencies = {}) {
|
|
|
17956
18278
|
continue;
|
|
17957
18279
|
}
|
|
17958
18280
|
try {
|
|
17959
|
-
await removeWorktree2(
|
|
18281
|
+
await removeWorktree2(repoRoot, worktree.path);
|
|
17960
18282
|
} catch (error) {
|
|
17961
18283
|
if (!isWorktreeForceRemovalError2(error)) {
|
|
17962
18284
|
failures.push({
|
|
@@ -17982,7 +18304,7 @@ function createCleanCommand(dependencies = {}) {
|
|
|
17982
18304
|
continue;
|
|
17983
18305
|
}
|
|
17984
18306
|
try {
|
|
17985
|
-
await forceRemoveWorktree2(
|
|
18307
|
+
await forceRemoveWorktree2(repoRoot, worktree.path);
|
|
17986
18308
|
} catch (forceError) {
|
|
17987
18309
|
failures.push({
|
|
17988
18310
|
branch: worktree.branch,
|
|
@@ -17992,10 +18314,10 @@ function createCleanCommand(dependencies = {}) {
|
|
|
17992
18314
|
continue;
|
|
17993
18315
|
}
|
|
17994
18316
|
}
|
|
17995
|
-
|
|
18317
|
+
removedCandidates.push(candidate);
|
|
17996
18318
|
if (worktree.branch) {
|
|
17997
18319
|
try {
|
|
17998
|
-
await deleteBranch2(
|
|
18320
|
+
await deleteBranch2(repoRoot, worktree.branch);
|
|
17999
18321
|
} catch (error) {
|
|
18000
18322
|
if (!isBranchUnmergedError2(error)) {
|
|
18001
18323
|
failures.push({
|
|
@@ -18007,7 +18329,7 @@ function createCleanCommand(dependencies = {}) {
|
|
|
18007
18329
|
}
|
|
18008
18330
|
if (options.force || await confirmForceDeleteBranch(worktree.branch)) {
|
|
18009
18331
|
try {
|
|
18010
|
-
await forceDeleteBranch2(
|
|
18332
|
+
await forceDeleteBranch2(repoRoot, worktree.branch);
|
|
18011
18333
|
} catch (forceError) {
|
|
18012
18334
|
options.stderr(
|
|
18013
18335
|
`Failed to delete branch ${worktree.branch}: ${toMessage2(forceError)}
|
|
@@ -18023,83 +18345,205 @@ function createCleanCommand(dependencies = {}) {
|
|
|
18023
18345
|
}
|
|
18024
18346
|
}
|
|
18025
18347
|
}
|
|
18026
|
-
|
|
18027
|
-
|
|
18028
|
-
|
|
18029
|
-
|
|
18030
|
-
|
|
18348
|
+
for (const [repoRoot, journal] of journals) {
|
|
18349
|
+
await finalizeUndoOperation(
|
|
18350
|
+
journal,
|
|
18351
|
+
removedCandidates.filter((candidate) => candidate.repoRoot === repoRoot).map(({ worktree }) => worktree),
|
|
18352
|
+
void 0,
|
|
18353
|
+
runtime.configStore.GLOBAL_CONFIG_DIRECTORY
|
|
18354
|
+
);
|
|
18355
|
+
}
|
|
18356
|
+
const undoRecords = remainingUndoRecords(
|
|
18357
|
+
journals.values(),
|
|
18358
|
+
removedCandidates
|
|
18031
18359
|
);
|
|
18032
18360
|
await Promise.all(
|
|
18033
|
-
|
|
18361
|
+
removedCandidates.map(
|
|
18362
|
+
({ worktree }) => releaseWorktreeSlot2(worktree.path)
|
|
18363
|
+
)
|
|
18034
18364
|
);
|
|
18035
18365
|
if (options.json) {
|
|
18036
|
-
const removed =
|
|
18366
|
+
const removed = removedCandidates.map(({ worktree }) => {
|
|
18037
18367
|
const info = selectedInfoByPath.get(worktree.path);
|
|
18038
18368
|
return info === void 0 ? { branch: worktree.branch, path: worktree.path } : serializeWorktreeInfo2(info);
|
|
18039
18369
|
});
|
|
18040
|
-
const payload =
|
|
18370
|
+
const payload = {
|
|
18371
|
+
removed,
|
|
18372
|
+
...failures.length === 0 ? {} : { failed: failures },
|
|
18373
|
+
...needsExplicitUndoIds(undoRecords, repository.repoRoot) ? {
|
|
18374
|
+
undo: undoRecords.map(({ id, repoRoot }) => ({ id, repoRoot }))
|
|
18375
|
+
} : {}
|
|
18376
|
+
};
|
|
18041
18377
|
options.stdout(`${JSON.stringify(payload, null, 2)}
|
|
18042
18378
|
`);
|
|
18043
18379
|
} else if (failures.length > 0) {
|
|
18044
18380
|
reportCleanFailures(failures, options.stderr);
|
|
18381
|
+
emitUndoHints(undoRecords, repository.repoRoot, options.stderr);
|
|
18045
18382
|
} else {
|
|
18046
|
-
options.stderr
|
|
18047
|
-
options.stdout(
|
|
18048
|
-
|
|
18383
|
+
emitUndoHints(undoRecords, repository.repoRoot, options.stderr);
|
|
18384
|
+
options.stdout(
|
|
18385
|
+
`${[...new Set(selectedCandidates.map(({ repoRoot }) => repoRoot))].join("\n")}
|
|
18386
|
+
`
|
|
18387
|
+
);
|
|
18049
18388
|
}
|
|
18050
18389
|
return failures.length === 0 ? 0 : 1;
|
|
18051
18390
|
};
|
|
18052
18391
|
}
|
|
18053
18392
|
var runCleanCommand = createCleanCommand();
|
|
18054
|
-
async function
|
|
18055
|
-
|
|
18056
|
-
|
|
18393
|
+
async function canonicalizePathPatterns(cwd, patterns) {
|
|
18394
|
+
return Promise.all(
|
|
18395
|
+
patterns.map(
|
|
18396
|
+
(pattern) => canonicalizeLiteralPrefix(resolve11(cwd, expandTildeInPath(pattern)))
|
|
18397
|
+
)
|
|
18398
|
+
);
|
|
18399
|
+
}
|
|
18400
|
+
async function canonicalizeLiteralPrefix(pattern) {
|
|
18401
|
+
const globIndex = findGlobIndex(pattern);
|
|
18402
|
+
let prefix = globIndex === -1 ? pattern : dirname10(`${pattern.slice(0, globIndex)}x`);
|
|
18403
|
+
let suffix = pattern.slice(prefix.length);
|
|
18404
|
+
for (; ; ) {
|
|
18405
|
+
try {
|
|
18406
|
+
const canonicalPrefix = escapePathPattern(
|
|
18407
|
+
await realpath5(unescapePathPattern(prefix))
|
|
18408
|
+
);
|
|
18409
|
+
return `${canonicalPrefix}${suffix}`.replace(/^\/\//, "/");
|
|
18410
|
+
} catch {
|
|
18411
|
+
const parent = dirname10(prefix);
|
|
18412
|
+
if (parent === prefix) return pattern;
|
|
18413
|
+
suffix = join12("/", basename8(prefix), suffix);
|
|
18414
|
+
prefix = parent;
|
|
18415
|
+
}
|
|
18057
18416
|
}
|
|
18058
|
-
|
|
18059
|
-
|
|
18060
|
-
|
|
18417
|
+
}
|
|
18418
|
+
async function resolveCleanupCandidates(options, dependencies) {
|
|
18419
|
+
const { sources, stale, stderr, signal } = options;
|
|
18420
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
18421
|
+
for (const source of sources) {
|
|
18422
|
+
if (source.repoRoot === void 0) continue;
|
|
18423
|
+
const existing = grouped.get(source.repoRoot);
|
|
18424
|
+
if (existing === void 0) {
|
|
18425
|
+
grouped.set(source.repoRoot, {
|
|
18426
|
+
repoName: source.repoName,
|
|
18427
|
+
worktrees: [source.worktree]
|
|
18428
|
+
});
|
|
18429
|
+
} else {
|
|
18430
|
+
existing.worktrees.push(source.worktree);
|
|
18431
|
+
}
|
|
18432
|
+
}
|
|
18433
|
+
const results = await mapWithConcurrency(
|
|
18434
|
+
[...grouped.entries()],
|
|
18435
|
+
MAX_CLEAN_REPOSITORY_CONCURRENCY,
|
|
18436
|
+
async ([repoRoot, group]) => {
|
|
18437
|
+
throwIfAborted3(signal);
|
|
18438
|
+
const staleBaseRef = stale ? await resolveStaleBaseRef({ repoRoot, stderr }, dependencies) : null;
|
|
18439
|
+
const worktrees = stale ? await filterStaleCleanupCandidates(
|
|
18440
|
+
{
|
|
18441
|
+
repoRoot,
|
|
18442
|
+
worktrees: group.worktrees,
|
|
18443
|
+
baseBranch: staleBaseRef,
|
|
18444
|
+
signal
|
|
18445
|
+
},
|
|
18446
|
+
dependencies
|
|
18447
|
+
) : group.worktrees;
|
|
18448
|
+
return worktrees.map((worktree) => ({
|
|
18449
|
+
repoName: group.repoName,
|
|
18061
18450
|
repoRoot,
|
|
18062
|
-
|
|
18063
|
-
|
|
18064
|
-
|
|
18065
|
-
|
|
18066
|
-
|
|
18451
|
+
staleBaseRef,
|
|
18452
|
+
worktree
|
|
18453
|
+
}));
|
|
18454
|
+
},
|
|
18455
|
+
signal
|
|
18456
|
+
);
|
|
18457
|
+
return results.flat();
|
|
18458
|
+
}
|
|
18459
|
+
function resolveSelectedCandidates(candidates, selections) {
|
|
18460
|
+
const selected = [];
|
|
18461
|
+
const seenPaths = /* @__PURE__ */ new Set();
|
|
18462
|
+
for (const selection of selections) {
|
|
18463
|
+
const candidate = candidates.find(
|
|
18464
|
+
(entry) => entry.worktree.path === selection || entry.worktree.branch === selection
|
|
18465
|
+
);
|
|
18466
|
+
if (candidate === void 0 || seenPaths.has(candidate.worktree.path)) {
|
|
18467
|
+
continue;
|
|
18468
|
+
}
|
|
18469
|
+
selected.push(candidate);
|
|
18470
|
+
seenPaths.add(candidate.worktree.path);
|
|
18471
|
+
}
|
|
18472
|
+
return selected;
|
|
18473
|
+
}
|
|
18474
|
+
function groupCandidatesByRepository(candidates) {
|
|
18475
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
18476
|
+
for (const candidate of candidates) {
|
|
18477
|
+
const existing = grouped.get(candidate.repoRoot);
|
|
18478
|
+
if (existing === void 0) grouped.set(candidate.repoRoot, [candidate]);
|
|
18479
|
+
else existing.push(candidate);
|
|
18480
|
+
}
|
|
18481
|
+
return grouped;
|
|
18482
|
+
}
|
|
18483
|
+
function remainingUndoRecords(records, removedCandidates) {
|
|
18484
|
+
return [...records].filter(
|
|
18485
|
+
(record) => removedCandidates.some(
|
|
18486
|
+
(candidate) => candidate.repoRoot === record.repoRoot && record.entries.some((entry) => entry.path === candidate.worktree.path)
|
|
18487
|
+
)
|
|
18488
|
+
);
|
|
18489
|
+
}
|
|
18490
|
+
async function discardUndoRecords(records, globalConfigDirectory) {
|
|
18491
|
+
await Promise.all(
|
|
18492
|
+
[...records].map(
|
|
18493
|
+
(record) => finalizeUndoOperation(record, [], void 0, globalConfigDirectory)
|
|
18067
18494
|
)
|
|
18068
18495
|
);
|
|
18496
|
+
}
|
|
18497
|
+
async function filterStaleCleanupCandidates(options, dependencies) {
|
|
18498
|
+
const { repoRoot, worktrees, baseBranch, signal } = options;
|
|
18499
|
+
throwIfAborted3(signal);
|
|
18500
|
+
if (baseBranch === null) {
|
|
18501
|
+
return [];
|
|
18502
|
+
}
|
|
18503
|
+
const results = await mapWithConcurrency(
|
|
18504
|
+
worktrees,
|
|
18505
|
+
MAX_CLEAN_WORKTREE_CONCURRENCY,
|
|
18506
|
+
(worktree) => isStaleCleanupCandidate({ repoRoot, worktree, baseBranch }, dependencies),
|
|
18507
|
+
signal
|
|
18508
|
+
);
|
|
18509
|
+
throwIfAborted3(signal);
|
|
18069
18510
|
return worktrees.filter((_3, index) => results[index]);
|
|
18070
18511
|
}
|
|
18071
|
-
async function resolveStaleBaseRef(
|
|
18072
|
-
const
|
|
18512
|
+
async function resolveStaleBaseRef(options, dependencies) {
|
|
18513
|
+
const { repoRoot, stderr } = options;
|
|
18514
|
+
const config = await dependencies.loadEffectiveConfig(
|
|
18515
|
+
repoRoot,
|
|
18516
|
+
void 0,
|
|
18517
|
+
stderr
|
|
18518
|
+
);
|
|
18073
18519
|
const remote = resolveConfiguredString(config.syncRemote) ?? "origin";
|
|
18074
18520
|
const configuredDefaultBranch = resolveConfiguredString(
|
|
18075
18521
|
config.syncDefaultBranch
|
|
18076
18522
|
);
|
|
18077
|
-
if (configuredDefaultBranch) {
|
|
18078
|
-
return await resolveFetchedRemoteRef(
|
|
18079
|
-
repoRoot,
|
|
18080
|
-
remote,
|
|
18081
|
-
configuredDefaultBranch,
|
|
18082
|
-
runGit2
|
|
18083
|
-
);
|
|
18084
|
-
}
|
|
18085
18523
|
try {
|
|
18086
|
-
const
|
|
18087
|
-
repoRoot,
|
|
18088
|
-
remote
|
|
18089
|
-
);
|
|
18090
|
-
return remoteDefaultBranch === null ? null : await resolveFetchedRemoteRef(
|
|
18524
|
+
const remoteBase = await dependencies.resolveRemoteBase(
|
|
18091
18525
|
repoRoot,
|
|
18092
18526
|
remote,
|
|
18093
|
-
|
|
18094
|
-
|
|
18527
|
+
configuredDefaultBranch ?? void 0
|
|
18528
|
+
);
|
|
18529
|
+
return remoteBase === null ? null : resolveFetchedRemoteRef(
|
|
18530
|
+
{ branch: remoteBase.branch, remote, repoRoot },
|
|
18531
|
+
dependencies.runGit
|
|
18095
18532
|
);
|
|
18096
18533
|
} catch {
|
|
18097
18534
|
return null;
|
|
18098
18535
|
}
|
|
18099
18536
|
}
|
|
18100
|
-
async function resolveFetchedRemoteRef(
|
|
18537
|
+
async function resolveFetchedRemoteRef(options, runGit2) {
|
|
18538
|
+
const { repoRoot, remote, branch } = options;
|
|
18101
18539
|
try {
|
|
18102
18540
|
await runGit2(repoRoot, ["fetch", "--prune", remote]);
|
|
18541
|
+
await runGit2(repoRoot, [
|
|
18542
|
+
"rev-parse",
|
|
18543
|
+
"--verify",
|
|
18544
|
+
"--quiet",
|
|
18545
|
+
`${remote}/${branch}`
|
|
18546
|
+
]);
|
|
18103
18547
|
return `${remote}/${branch}`;
|
|
18104
18548
|
} catch {
|
|
18105
18549
|
return null;
|
|
@@ -18108,33 +18552,39 @@ async function resolveFetchedRemoteRef(repoRoot, remote, branch, runGit2) {
|
|
|
18108
18552
|
function resolveConfiguredString(value) {
|
|
18109
18553
|
return typeof value === "string" && value.length > 0 ? value : null;
|
|
18110
18554
|
}
|
|
18111
|
-
async function isStaleCleanupCandidate(
|
|
18555
|
+
async function isStaleCleanupCandidate(options, dependencies) {
|
|
18556
|
+
const { repoRoot, worktree, baseBranch } = options;
|
|
18112
18557
|
if (baseBranch === null) {
|
|
18113
18558
|
return false;
|
|
18114
18559
|
}
|
|
18115
18560
|
if (worktree.branch === null) {
|
|
18116
18561
|
return false;
|
|
18117
18562
|
}
|
|
18118
|
-
const health = await
|
|
18563
|
+
const health = await dependencies.readWorktreeHealth(worktree.path);
|
|
18119
18564
|
if (health.status !== "clean" || !health.upstreamGone) {
|
|
18120
18565
|
return false;
|
|
18121
18566
|
}
|
|
18122
|
-
|
|
18567
|
+
if (await dependencies.isBranchMergedInto(repoRoot, worktree.branch, baseBranch)) {
|
|
18568
|
+
return true;
|
|
18569
|
+
}
|
|
18570
|
+
return isBranchPatchEquivalentInto(
|
|
18571
|
+
{ baseBranch, repoRoot, worktreeBranch: worktree.branch },
|
|
18572
|
+
dependencies.runGit
|
|
18573
|
+
);
|
|
18123
18574
|
}
|
|
18124
|
-
function
|
|
18125
|
-
const
|
|
18126
|
-
|
|
18127
|
-
|
|
18128
|
-
|
|
18129
|
-
|
|
18130
|
-
|
|
18131
|
-
|
|
18132
|
-
|
|
18133
|
-
|
|
18134
|
-
|
|
18135
|
-
|
|
18575
|
+
async function isBranchPatchEquivalentInto(options, runGit2) {
|
|
18576
|
+
const { repoRoot, worktreeBranch, baseBranch } = options;
|
|
18577
|
+
try {
|
|
18578
|
+
const output = await runGit2(repoRoot, [
|
|
18579
|
+
"cherry",
|
|
18580
|
+
baseBranch,
|
|
18581
|
+
worktreeBranch
|
|
18582
|
+
]);
|
|
18583
|
+
const lines = output.split("\n").filter(Boolean);
|
|
18584
|
+
return lines.length > 0 && lines.every((line) => line.startsWith("-"));
|
|
18585
|
+
} catch {
|
|
18586
|
+
return false;
|
|
18136
18587
|
}
|
|
18137
|
-
return selectedWorktrees;
|
|
18138
18588
|
}
|
|
18139
18589
|
function reportCleanFailures(failures, stderr) {
|
|
18140
18590
|
const noun = failures.length === 1 ? "worktree" : "worktrees";
|
|
@@ -18146,6 +18596,37 @@ function reportCleanFailures(failures, stderr) {
|
|
|
18146
18596
|
`);
|
|
18147
18597
|
}
|
|
18148
18598
|
}
|
|
18599
|
+
function emitUndoHints(records, currentRepositoryRoot, stderr) {
|
|
18600
|
+
if (records.length === 0) return;
|
|
18601
|
+
if (!needsExplicitUndoIds(records, currentRepositoryRoot)) {
|
|
18602
|
+
stderr("undo: gji undo\n");
|
|
18603
|
+
return;
|
|
18604
|
+
}
|
|
18605
|
+
const message = records.length === 1 ? "undo: restore with:" : "undo: restore each repository with:";
|
|
18606
|
+
stderr(`${message}
|
|
18607
|
+
`);
|
|
18608
|
+
for (const record of records) {
|
|
18609
|
+
stderr(` gji undo --id ${record.id} (${record.repoRoot})
|
|
18610
|
+
`);
|
|
18611
|
+
}
|
|
18612
|
+
}
|
|
18613
|
+
function needsExplicitUndoIds(records, currentRepositoryRoot) {
|
|
18614
|
+
return records.length > 1 || records.some((record) => record.repoRoot !== currentRepositoryRoot);
|
|
18615
|
+
}
|
|
18616
|
+
function reportSkippedRepositories(repositories, stderr) {
|
|
18617
|
+
const noun = repositories.length === 1 ? "repository" : "repositories";
|
|
18618
|
+
stderr(
|
|
18619
|
+
`Skipped ${repositories.length} registered ${noun} because worktrees could not be listed:
|
|
18620
|
+
`
|
|
18621
|
+
);
|
|
18622
|
+
for (const repository of repositories) {
|
|
18623
|
+
stderr(`- ${repository.name} (${repository.path})
|
|
18624
|
+
`);
|
|
18625
|
+
}
|
|
18626
|
+
}
|
|
18627
|
+
function throwIfAborted3(signal) {
|
|
18628
|
+
if (signal?.aborted) throw new Error("Operation cancelled");
|
|
18629
|
+
}
|
|
18149
18630
|
function formatCleanInfo(info, formatLastCommit3, formatUpstreamState3) {
|
|
18150
18631
|
const branch = info.branch === null ? "detached" : `branch: ${info.branch}`;
|
|
18151
18632
|
const status = `status: ${info.status}`;
|
|
@@ -18174,8 +18655,11 @@ function emitNoStaleCandidates(options) {
|
|
|
18174
18655
|
function toMessage2(error) {
|
|
18175
18656
|
return error instanceof Error ? error.message : String(error);
|
|
18176
18657
|
}
|
|
18177
|
-
async function defaultPromptForWorktrees(worktrees) {
|
|
18178
|
-
return promptForMultipleWorktrees("Choose worktrees to clean", worktrees
|
|
18658
|
+
async function defaultPromptForWorktrees(worktrees, scope, catalog) {
|
|
18659
|
+
return promptForMultipleWorktrees("Choose worktrees to clean", worktrees, {
|
|
18660
|
+
catalog,
|
|
18661
|
+
scope
|
|
18662
|
+
});
|
|
18179
18663
|
}
|
|
18180
18664
|
async function defaultConfirmRemoval(worktrees) {
|
|
18181
18665
|
const branchCount = worktrees.filter(
|
|
@@ -18432,7 +18916,11 @@ _gji_completion() {
|
|
|
18432
18916
|
COMPREPLY=( $(compgen -W "--compact --json --help" -- "$cur") )
|
|
18433
18917
|
;;
|
|
18434
18918
|
clean)
|
|
18435
|
-
|
|
18919
|
+
if [ "\${COMP_WORDS[COMP_CWORD-1]}" = "--path" ]; then
|
|
18920
|
+
COMPREPLY=( $(compgen -d -- "$cur") )
|
|
18921
|
+
else
|
|
18922
|
+
COMPREPLY=( $(compgen -W "-f --force --stale --path --dry-run --json --help" -- "$cur") )
|
|
18923
|
+
fi
|
|
18436
18924
|
;;
|
|
18437
18925
|
remove|rm)
|
|
18438
18926
|
COMPREPLY=( $(compgen -W "$(__gji_worktree_branches) -f --force --dry-run --json --help" -- "$cur") )
|
|
@@ -18675,6 +19163,7 @@ complete -c gji -n '__fish_seen_subcommand_from ls' -l json -d 'print active wor
|
|
|
18675
19163
|
|
|
18676
19164
|
complete -c gji -n '__fish_seen_subcommand_from clean' -s f -l force -d 'bypass prompts, force-remove dirty worktrees, and force-delete unmerged branches'
|
|
18677
19165
|
complete -c gji -n '__fish_seen_subcommand_from clean' -l stale -d 'only target clean worktrees whose upstream is gone and branch is merged into the default branch'
|
|
19166
|
+
complete -c gji -n '__fish_seen_subcommand_from clean' -l path -r -F -d 'only target worktrees under a directory or matching a path glob (repeatable)'
|
|
18678
19167
|
complete -c gji -n '__fish_seen_subcommand_from clean' -l dry-run -d 'show what would be deleted without removing anything'
|
|
18679
19168
|
complete -c gji -n '__fish_seen_subcommand_from clean' -l json -d 'emit JSON on success or error instead of human-readable output'
|
|
18680
19169
|
|
|
@@ -18835,7 +19324,7 @@ case "\${words[2]}" in
|
|
|
18835
19324
|
_arguments '--compact[show only branch and path columns]' '--json[print active worktrees as JSON]'
|
|
18836
19325
|
;;
|
|
18837
19326
|
clean)
|
|
18838
|
-
_arguments '(-f --force)'{-f,--force}'[bypass prompts, force-remove dirty worktrees, and force-delete unmerged branches]' '--stale[only target clean worktrees whose upstream is gone and branch is merged into the default branch]' '--dry-run[show what would be deleted without removing anything]' '--json[emit JSON on success or error instead of human-readable output]'
|
|
19327
|
+
_arguments '(-f --force)'{-f,--force}'[bypass prompts, force-remove dirty worktrees, and force-delete unmerged branches]' '--stale[only target clean worktrees whose upstream is gone and branch is merged into the default branch]' '*--path[only target worktrees under a directory or matching a path glob]:pattern:_files' '--dry-run[show what would be deleted without removing anything]' '--json[emit JSON on success or error instead of human-readable output]'
|
|
18839
19328
|
;;
|
|
18840
19329
|
remove|rm)
|
|
18841
19330
|
_arguments '(-f --force)'{-f,--force}'[bypass prompts, force-remove a dirty worktree, and force-delete an unmerged branch]' '--dry-run[show what would be deleted without removing anything]' '--json[emit JSON on success or error instead of human-readable output]' '2:branch:->worktrees'
|
|
@@ -18972,20 +19461,20 @@ function writeJson(stdout3, value) {
|
|
|
18972
19461
|
import { execFile as execFile8 } from "node:child_process";
|
|
18973
19462
|
import { constants as constants6 } from "node:fs";
|
|
18974
19463
|
import { access as access6, readFile as readFile8 } from "node:fs/promises";
|
|
18975
|
-
import { homedir as
|
|
18976
|
-
import { basename as
|
|
19464
|
+
import { homedir as homedir7 } from "node:os";
|
|
19465
|
+
import { basename as basename9, dirname as dirname11, join as join14 } from "node:path";
|
|
18977
19466
|
import { promisify as promisify9 } from "node:util";
|
|
18978
19467
|
|
|
18979
19468
|
// src/presentation/shell/setup.ts
|
|
18980
19469
|
import { constants as constants5 } from "node:fs";
|
|
18981
19470
|
import { access as access5 } from "node:fs/promises";
|
|
18982
|
-
import { delimiter, join as
|
|
19471
|
+
import { delimiter, join as join13 } from "node:path";
|
|
18983
19472
|
var SHELL_INTEGRATION_START_MARKER = "# >>> gji init >>>";
|
|
18984
19473
|
var SHELL_INTEGRATION_END_MARKER = "# <<< gji init <<<";
|
|
18985
19474
|
var ONBOARDING_SHELL_INTEGRATION_START_MARKER = "# >>> gji shell integration >>>";
|
|
18986
19475
|
var ONBOARDING_SHELL_INTEGRATION_END_MARKER = "# <<< gji shell integration <<<";
|
|
18987
19476
|
async function executableExists(command) {
|
|
18988
|
-
const candidates = command.includes("/") ? [command] : (process.env.PATH ?? "").split(delimiter).filter(Boolean).map((directory) =>
|
|
19477
|
+
const candidates = command.includes("/") ? [command] : (process.env.PATH ?? "").split(delimiter).filter(Boolean).map((directory) => join13(directory, command));
|
|
18989
19478
|
for (const path9 of candidates) {
|
|
18990
19479
|
try {
|
|
18991
19480
|
await access5(path9, constants5.X_OK);
|
|
@@ -18998,17 +19487,17 @@ async function executableExists(command) {
|
|
|
18998
19487
|
function resolveShellConfigPath(shell, home) {
|
|
18999
19488
|
switch (shell) {
|
|
19000
19489
|
case "bash":
|
|
19001
|
-
return
|
|
19490
|
+
return join13(home, ".bashrc");
|
|
19002
19491
|
case "fish":
|
|
19003
|
-
return
|
|
19492
|
+
return join13(home, ".config", "fish", "config.fish");
|
|
19004
19493
|
case "zsh":
|
|
19005
|
-
return
|
|
19494
|
+
return join13(home, ".zshrc");
|
|
19006
19495
|
}
|
|
19007
19496
|
}
|
|
19008
19497
|
function resolveCompletionPath(shell, home) {
|
|
19009
19498
|
switch (shell) {
|
|
19010
19499
|
case "bash":
|
|
19011
|
-
return
|
|
19500
|
+
return join13(
|
|
19012
19501
|
home,
|
|
19013
19502
|
".local",
|
|
19014
19503
|
"share",
|
|
@@ -19017,9 +19506,9 @@ function resolveCompletionPath(shell, home) {
|
|
|
19017
19506
|
"gji"
|
|
19018
19507
|
);
|
|
19019
19508
|
case "fish":
|
|
19020
|
-
return
|
|
19509
|
+
return join13(home, ".config", "fish", "completions", "gji.fish");
|
|
19021
19510
|
case "zsh":
|
|
19022
|
-
return
|
|
19511
|
+
return join13(home, ".zsh", "completions", "_gji");
|
|
19023
19512
|
}
|
|
19024
19513
|
}
|
|
19025
19514
|
function hasShellIntegration(config, shell) {
|
|
@@ -19051,7 +19540,7 @@ async function runDoctorCommand(options) {
|
|
|
19051
19540
|
}
|
|
19052
19541
|
return 1;
|
|
19053
19542
|
}
|
|
19054
|
-
const home = options.home ??
|
|
19543
|
+
const home = options.home ?? homedir7();
|
|
19055
19544
|
const shell = resolveSupportedShell(
|
|
19056
19545
|
void 0,
|
|
19057
19546
|
options.shell ?? process.env.SHELL
|
|
@@ -19116,7 +19605,7 @@ async function collectDoctorInspection(cwd, home, shell, runtime) {
|
|
|
19116
19605
|
KNOWN_GLOBAL_CONFIG_KEYS2
|
|
19117
19606
|
);
|
|
19118
19607
|
const localConfig = repository ? await inspectConfig(
|
|
19119
|
-
|
|
19608
|
+
join14(repository.repoRoot, CONFIG_FILE_NAME2),
|
|
19120
19609
|
"local",
|
|
19121
19610
|
KNOWN_CONFIG_KEYS2
|
|
19122
19611
|
) : null;
|
|
@@ -19295,7 +19784,7 @@ function resolvePerRepoConfig(repos, repoRoot, home) {
|
|
|
19295
19784
|
}
|
|
19296
19785
|
function expandTilde2(path9, home) {
|
|
19297
19786
|
if (path9 === "~") return home;
|
|
19298
|
-
if (path9.startsWith("~/")) return
|
|
19787
|
+
if (path9.startsWith("~/")) return join14(home, path9.slice(2));
|
|
19299
19788
|
return path9;
|
|
19300
19789
|
}
|
|
19301
19790
|
async function checkGitVersion() {
|
|
@@ -19422,7 +19911,7 @@ function resolveWorktreeBase(repoRoot, config, home) {
|
|
|
19422
19911
|
if (typeof configuredPath === "string" && (configuredPath.startsWith("/") || configuredPath.startsWith("~"))) {
|
|
19423
19912
|
return expandTilde2(configuredPath, home);
|
|
19424
19913
|
}
|
|
19425
|
-
return
|
|
19914
|
+
return join14(dirname11(repoRoot), "worktrees", basename9(repoRoot));
|
|
19426
19915
|
}
|
|
19427
19916
|
async function findNearestExistingPath(path9) {
|
|
19428
19917
|
let candidate = path9;
|
|
@@ -19431,7 +19920,7 @@ async function findNearestExistingPath(path9) {
|
|
|
19431
19920
|
await access6(candidate, constants6.F_OK);
|
|
19432
19921
|
return candidate;
|
|
19433
19922
|
} catch {
|
|
19434
|
-
const parent =
|
|
19923
|
+
const parent = dirname11(candidate);
|
|
19435
19924
|
if (parent === candidate) return null;
|
|
19436
19925
|
candidate = parent;
|
|
19437
19926
|
}
|
|
@@ -19582,7 +20071,7 @@ function statusSymbol(status) {
|
|
|
19582
20071
|
}
|
|
19583
20072
|
|
|
19584
20073
|
// src/cli/commands/done.ts
|
|
19585
|
-
import { basename as
|
|
20074
|
+
import { basename as basename10 } from "node:path";
|
|
19586
20075
|
var DONE_OUTPUT_FILE_ENV = "GJI_DONE_OUTPUT_FILE";
|
|
19587
20076
|
async function runDoneCommand(options) {
|
|
19588
20077
|
const runtime = options.runtime ?? defaultCliDependencies;
|
|
@@ -19690,7 +20179,7 @@ async function runDoneCommand(options) {
|
|
|
19690
20179
|
{
|
|
19691
20180
|
branch: target.branch ?? void 0,
|
|
19692
20181
|
path: target.path,
|
|
19693
|
-
repo:
|
|
20182
|
+
repo: basename10(repository.repoRoot)
|
|
19694
20183
|
},
|
|
19695
20184
|
options.stderr
|
|
19696
20185
|
);
|
|
@@ -19881,7 +20370,7 @@ function toMessage3(error) {
|
|
|
19881
20370
|
}
|
|
19882
20371
|
|
|
19883
20372
|
// src/cli/commands/go.ts
|
|
19884
|
-
import { basename as
|
|
20373
|
+
import { basename as basename11 } from "node:path";
|
|
19885
20374
|
|
|
19886
20375
|
// src/application/worktree/context-card.ts
|
|
19887
20376
|
async function loadContextCardModel(worktreePath, dependencies) {
|
|
@@ -19984,71 +20473,6 @@ function parsePrInput(input) {
|
|
|
19984
20473
|
return urlMatch?.[1] ?? null;
|
|
19985
20474
|
}
|
|
19986
20475
|
|
|
19987
|
-
// src/application/worktree/sources.ts
|
|
19988
|
-
var MAX_REPOSITORY_DISCOVERY_CONCURRENCY = 4;
|
|
19989
|
-
async function listRegisteredWorktreeSources(cwd, repositoryPort, onSkipped) {
|
|
19990
|
-
const registry = await repositoryPort.loadRegistry();
|
|
19991
|
-
const currentRoot = await repositoryPort.detectRepository(cwd).then((repository) => repository.currentRoot).catch(() => null);
|
|
19992
|
-
const results = await mapWithConcurrency(
|
|
19993
|
-
registry,
|
|
19994
|
-
MAX_REPOSITORY_DISCOVERY_CONCURRENCY,
|
|
19995
|
-
async (entry) => {
|
|
19996
|
-
try {
|
|
19997
|
-
const worktrees = await repositoryPort.listWorktrees(entry.path);
|
|
19998
|
-
return { entry, worktrees };
|
|
19999
|
-
} catch {
|
|
20000
|
-
onSkipped?.(entry);
|
|
20001
|
-
return null;
|
|
20002
|
-
}
|
|
20003
|
-
}
|
|
20004
|
-
);
|
|
20005
|
-
const allItems = [];
|
|
20006
|
-
for (const result of results) {
|
|
20007
|
-
if (result === null) continue;
|
|
20008
|
-
const { entry, worktrees } = result;
|
|
20009
|
-
for (const worktree of worktrees) {
|
|
20010
|
-
allItems.push({
|
|
20011
|
-
repoRoot: entry.path,
|
|
20012
|
-
repoName: entry.name,
|
|
20013
|
-
worktree: {
|
|
20014
|
-
...worktree,
|
|
20015
|
-
isCurrent: currentRoot !== null && worktree.path === currentRoot
|
|
20016
|
-
}
|
|
20017
|
-
});
|
|
20018
|
-
}
|
|
20019
|
-
}
|
|
20020
|
-
return allItems;
|
|
20021
|
-
}
|
|
20022
|
-
async function listDiscoverableWorktreeSources(cwd, repositoryPort, onSkipped) {
|
|
20023
|
-
const currentRepository = await repositoryPort.detectRepository(cwd).catch(() => null);
|
|
20024
|
-
const registeredSources = await listRegisteredWorktreeSources(
|
|
20025
|
-
cwd,
|
|
20026
|
-
repositoryPort,
|
|
20027
|
-
onSkipped
|
|
20028
|
-
);
|
|
20029
|
-
if (currentRepository === null) return dedupeSources(registeredSources);
|
|
20030
|
-
let currentSources = [];
|
|
20031
|
-
try {
|
|
20032
|
-
currentSources = (await repositoryPort.listWorktrees(cwd)).map(
|
|
20033
|
-
(worktree) => ({
|
|
20034
|
-
repoName: currentRepository.repoName,
|
|
20035
|
-
repoRoot: currentRepository.repoRoot,
|
|
20036
|
-
worktree
|
|
20037
|
-
})
|
|
20038
|
-
);
|
|
20039
|
-
} catch {
|
|
20040
|
-
}
|
|
20041
|
-
return dedupeSources([...currentSources, ...registeredSources]);
|
|
20042
|
-
}
|
|
20043
|
-
function dedupeSources(sources) {
|
|
20044
|
-
const seen = /* @__PURE__ */ new Set();
|
|
20045
|
-
return sources.filter((source) => {
|
|
20046
|
-
if (seen.has(source.worktree.path)) return false;
|
|
20047
|
-
seen.add(source.worktree.path);
|
|
20048
|
-
return true;
|
|
20049
|
-
});
|
|
20050
|
-
}
|
|
20051
|
-
|
|
20052
20476
|
// src/application/worktree/go-resolution.ts
|
|
20053
20477
|
async function resolveGoBranch(options) {
|
|
20054
20478
|
const {
|
|
@@ -20115,14 +20539,12 @@ async function resolveGoBranch(options) {
|
|
|
20115
20539
|
return finish({ kind: "existing", source: pullRequestMatches[0] }, false);
|
|
20116
20540
|
}
|
|
20117
20541
|
}
|
|
20118
|
-
|
|
20119
|
-
const registeredSources = await listRegisteredWorktreeSources(
|
|
20542
|
+
const registered = await listRegisteredWorktreeSources({
|
|
20120
20543
|
cwd,
|
|
20121
|
-
sourceDependencies
|
|
20122
|
-
|
|
20123
|
-
|
|
20124
|
-
|
|
20125
|
-
);
|
|
20544
|
+
repositoryPort: sourceDependencies
|
|
20545
|
+
});
|
|
20546
|
+
const registeredSources = registered.sources;
|
|
20547
|
+
const skippedRegisteredRepos = registered.skipped.length;
|
|
20126
20548
|
const crossRepoSources = registeredSources.filter(
|
|
20127
20549
|
(source) => source.repoRoot !== repository?.repoRoot
|
|
20128
20550
|
);
|
|
@@ -20279,64 +20701,7 @@ function formatLastCommit2(info) {
|
|
|
20279
20701
|
|
|
20280
20702
|
// src/cli/commands/new.ts
|
|
20281
20703
|
import { access as access8, mkdir as mkdir7 } from "node:fs/promises";
|
|
20282
|
-
import { dirname as
|
|
20283
|
-
|
|
20284
|
-
// src/domain/worktree/policy.ts
|
|
20285
|
-
import { homedir as homedir7 } from "node:os";
|
|
20286
|
-
import { basename as basename9, dirname as dirname10, join as join13 } from "node:path";
|
|
20287
|
-
function resolveWorktreePath(repoRoot, branch, basePath) {
|
|
20288
|
-
const segments = branch.split("/").filter(Boolean);
|
|
20289
|
-
if (segments.length === 0) {
|
|
20290
|
-
throw new Error("Branch name must not be empty.");
|
|
20291
|
-
}
|
|
20292
|
-
if (segments.some((segment) => segment === "." || segment === "..")) {
|
|
20293
|
-
throw new Error(
|
|
20294
|
-
`Branch name '${branch}' contains an invalid path segment.`
|
|
20295
|
-
);
|
|
20296
|
-
}
|
|
20297
|
-
const base = basePath ? expandTildeInPath(basePath) : join13(dirname10(repoRoot), "worktrees", basename9(repoRoot));
|
|
20298
|
-
return join13(base, ...segments);
|
|
20299
|
-
}
|
|
20300
|
-
function validateBranchName(name) {
|
|
20301
|
-
if (name.length === 0) {
|
|
20302
|
-
return "Branch name must not be empty.";
|
|
20303
|
-
}
|
|
20304
|
-
if (/[\x00-\x1f\x7f ~^:?*[\\\s]/.test(name)) {
|
|
20305
|
-
return `Branch name '${name}' contains an invalid character.`;
|
|
20306
|
-
}
|
|
20307
|
-
if (name.startsWith("-")) {
|
|
20308
|
-
return `Branch name '${name}' must not start with a dash.`;
|
|
20309
|
-
}
|
|
20310
|
-
if (name.startsWith("/") || name.endsWith("/") || name.includes("//")) {
|
|
20311
|
-
return `Branch name '${name}' has invalid slash placement.`;
|
|
20312
|
-
}
|
|
20313
|
-
if (name.includes("..")) {
|
|
20314
|
-
return `Branch name '${name}' must not contain '..'.`;
|
|
20315
|
-
}
|
|
20316
|
-
if (name.endsWith(".")) {
|
|
20317
|
-
return `Branch name '${name}' must not end with '.'.`;
|
|
20318
|
-
}
|
|
20319
|
-
if (name.includes("@{")) {
|
|
20320
|
-
return `Branch name '${name}' must not contain '@{'.`;
|
|
20321
|
-
}
|
|
20322
|
-
if (name === "@") {
|
|
20323
|
-
return "Branch name cannot be '@'.";
|
|
20324
|
-
}
|
|
20325
|
-
for (const segment of name.split("/")) {
|
|
20326
|
-
if (segment.startsWith(".")) {
|
|
20327
|
-
return `Branch name '${name}' contains a path component starting with '.'.`;
|
|
20328
|
-
}
|
|
20329
|
-
if (segment.endsWith(".lock")) {
|
|
20330
|
-
return `Branch name '${name}' contains a path component ending with '.lock'.`;
|
|
20331
|
-
}
|
|
20332
|
-
}
|
|
20333
|
-
return null;
|
|
20334
|
-
}
|
|
20335
|
-
function expandTildeInPath(path9) {
|
|
20336
|
-
if (path9 === "~") return homedir7();
|
|
20337
|
-
if (path9.startsWith("~/")) return join13(homedir7(), path9.slice(2));
|
|
20338
|
-
return path9;
|
|
20339
|
-
}
|
|
20704
|
+
import { dirname as dirname12, resolve as resolve12 } from "node:path";
|
|
20340
20705
|
|
|
20341
20706
|
// src/presentation/bootstrap/output.ts
|
|
20342
20707
|
function createBootstrapReporter(write, json) {
|
|
@@ -20646,7 +21011,7 @@ ${formatDependencyBootstrapPreview(dryRunDependencyBootstrap)}`
|
|
|
20646
21011
|
}
|
|
20647
21012
|
return 0;
|
|
20648
21013
|
}
|
|
20649
|
-
await mkdir7(
|
|
21014
|
+
await mkdir7(dirname12(worktreePath), { recursive: true });
|
|
20650
21015
|
const startPoint = options.fromCurrent && !options.detached ? await resolveCurrentWorktreeHead(options.cwd, runtime.git.runGit) : void 0;
|
|
20651
21016
|
const takeStartPoint = options.take ? await resolveCurrentWorktreeHead(options.cwd, runtime.git.runGit) : void 0;
|
|
20652
21017
|
const changedTakeFiles = options.take ? await listTakeFiles(options.cwd, runtime.git.runGitRaw) : [];
|
|
@@ -21238,7 +21603,7 @@ async function detectInProgressGitState(cwd, runGit2) {
|
|
|
21238
21603
|
for (const [marker, name] of markers) {
|
|
21239
21604
|
try {
|
|
21240
21605
|
const gitPath = await runGit2(cwd, ["rev-parse", "--git-path", marker]);
|
|
21241
|
-
await access8(
|
|
21606
|
+
await access8(resolve12(cwd, gitPath));
|
|
21242
21607
|
return name;
|
|
21243
21608
|
} catch {
|
|
21244
21609
|
}
|
|
@@ -21390,7 +21755,7 @@ function emitNewError(options, message, details) {
|
|
|
21390
21755
|
|
|
21391
21756
|
// src/cli/commands/pr.ts
|
|
21392
21757
|
import { mkdir as mkdir8 } from "node:fs/promises";
|
|
21393
|
-
import { dirname as
|
|
21758
|
+
import { dirname as dirname13 } from "node:path";
|
|
21394
21759
|
var PR_OUTPUT_FILE_ENV = "GJI_PR_OUTPUT_FILE";
|
|
21395
21760
|
function createPrCommand(dependencies = {}) {
|
|
21396
21761
|
const prompt = dependencies.promptForPathConflict ?? promptForPathConflict;
|
|
@@ -21529,7 +21894,7 @@ ${formatDependencyBootstrapPreview(dryRunDependencyBootstrap)}`
|
|
|
21529
21894
|
}
|
|
21530
21895
|
return 1;
|
|
21531
21896
|
}
|
|
21532
|
-
await mkdir8(
|
|
21897
|
+
await mkdir8(dirname13(worktreePath), { recursive: true });
|
|
21533
21898
|
const branchAlreadyExists = await localBranchExists2(
|
|
21534
21899
|
repository.repoRoot,
|
|
21535
21900
|
branchName,
|
|
@@ -21705,17 +22070,16 @@ function createGoCommand(dependencies = {}) {
|
|
|
21705
22070
|
let promptSources = currentSources;
|
|
21706
22071
|
const loadAllSources = async () => {
|
|
21707
22072
|
if (registeredSources === null) {
|
|
21708
|
-
|
|
21709
|
-
options.cwd,
|
|
21710
|
-
{
|
|
22073
|
+
const registered = await listRegisteredWorktreeSources({
|
|
22074
|
+
cwd: options.cwd,
|
|
22075
|
+
repositoryPort: {
|
|
21711
22076
|
...runtime.repositoryContext,
|
|
21712
22077
|
...runtime.repositoryRegistry,
|
|
21713
22078
|
...runtime.worktrees
|
|
21714
|
-
},
|
|
21715
|
-
() => {
|
|
21716
|
-
skippedRegisteredRepos++;
|
|
21717
22079
|
}
|
|
21718
|
-
);
|
|
22080
|
+
});
|
|
22081
|
+
registeredSources = registered.sources;
|
|
22082
|
+
skippedRegisteredRepos = registered.skipped.length;
|
|
21719
22083
|
}
|
|
21720
22084
|
return deduplicateSources([...currentSources, ...registeredSources]);
|
|
21721
22085
|
};
|
|
@@ -21737,13 +22101,8 @@ function createGoCommand(dependencies = {}) {
|
|
|
21737
22101
|
currentRepositoryScope = nextCurrentRepositoryScope;
|
|
21738
22102
|
promptSources = nextSources;
|
|
21739
22103
|
return {
|
|
21740
|
-
|
|
21741
|
-
|
|
21742
|
-
catalog: withPullRequestQueries(
|
|
21743
|
-
runtime,
|
|
21744
|
-
dependencies.queryPullRequests
|
|
21745
|
-
)
|
|
21746
|
-
}),
|
|
22104
|
+
sources: promptSources,
|
|
22105
|
+
metadata: currentRepositoryScope ? "full" : "fast",
|
|
21747
22106
|
label: currentRepositoryScope ? "current repository" : "all repositories",
|
|
21748
22107
|
toggleLabel: currentRepositoryScope ? "all repositories" : "current repository"
|
|
21749
22108
|
};
|
|
@@ -21757,7 +22116,11 @@ function createGoCommand(dependencies = {}) {
|
|
|
21757
22116
|
dependencies.queryPullRequests
|
|
21758
22117
|
)
|
|
21759
22118
|
});
|
|
21760
|
-
const selectedPath = await prompt(
|
|
22119
|
+
const selectedPath = await prompt(
|
|
22120
|
+
promptEntries,
|
|
22121
|
+
scope,
|
|
22122
|
+
withPullRequestQueries(runtime, dependencies.queryPullRequests)
|
|
22123
|
+
);
|
|
21761
22124
|
if (!selectedPath) {
|
|
21762
22125
|
options.stderr("Aborted\n");
|
|
21763
22126
|
return 1;
|
|
@@ -21940,7 +22303,7 @@ async function navigateToExistingWorktree(options, path9, worktree, runtime) {
|
|
|
21940
22303
|
{
|
|
21941
22304
|
branch: worktree?.branch ?? void 0,
|
|
21942
22305
|
path: path9,
|
|
21943
|
-
repo:
|
|
22306
|
+
repo: basename11(repository.repoRoot),
|
|
21944
22307
|
slot: await runtime.slots.getWorktreeSlot(path9)
|
|
21945
22308
|
},
|
|
21946
22309
|
options.stderr
|
|
@@ -22055,8 +22418,11 @@ async function defaultConfirmBranchCreation(branch) {
|
|
|
22055
22418
|
});
|
|
22056
22419
|
return !pD(choice) && choice;
|
|
22057
22420
|
}
|
|
22058
|
-
async function promptForWorktree(worktrees, scope) {
|
|
22059
|
-
return promptForSingleWorktree("Choose a worktree", worktrees, {
|
|
22421
|
+
async function promptForWorktree(worktrees, scope, catalog) {
|
|
22422
|
+
return promptForSingleWorktree("Choose a worktree", worktrees, {
|
|
22423
|
+
catalog,
|
|
22424
|
+
scope
|
|
22425
|
+
});
|
|
22060
22426
|
}
|
|
22061
22427
|
|
|
22062
22428
|
// src/cli/commands/history-command.ts
|
|
@@ -22077,7 +22443,7 @@ async function runHistoryCommand(options) {
|
|
|
22077
22443
|
}
|
|
22078
22444
|
|
|
22079
22445
|
// src/cli/commands/hub.ts
|
|
22080
|
-
import { basename as
|
|
22446
|
+
import { basename as basename12 } from "node:path";
|
|
22081
22447
|
import { stdin as stdin2, stdout as stdout2 } from "node:process";
|
|
22082
22448
|
var MAX_HUB_REPOSITORY_CONCURRENCY = 4;
|
|
22083
22449
|
async function buildHubData(cwd, dependencies = {}, runtime = defaultCliDependencies) {
|
|
@@ -22089,10 +22455,10 @@ async function buildHubData(cwd, dependencies = {}, runtime = defaultCliDependen
|
|
|
22089
22455
|
};
|
|
22090
22456
|
const queryRepositoryPullRequests = dependencies.queryRepositoryPullRequests ?? runtime.pullRequests.listOpenPullRequestsForRepository;
|
|
22091
22457
|
const currentRepository = await detectRepository2(cwd).catch(() => null);
|
|
22092
|
-
const sources = await listDiscoverableWorktreeSources(
|
|
22458
|
+
const { sources } = await listDiscoverableWorktreeSources({
|
|
22093
22459
|
cwd,
|
|
22094
|
-
sourceDependencies
|
|
22095
|
-
);
|
|
22460
|
+
repositoryPort: sourceDependencies
|
|
22461
|
+
});
|
|
22096
22462
|
const groups = /* @__PURE__ */ new Map();
|
|
22097
22463
|
for (const source of sources) {
|
|
22098
22464
|
if (!source.repoRoot) continue;
|
|
@@ -22102,7 +22468,7 @@ async function buildHubData(cwd, dependencies = {}, runtime = defaultCliDependen
|
|
|
22102
22468
|
existing.sources.push(source);
|
|
22103
22469
|
} else {
|
|
22104
22470
|
groups.set(repoRoot, {
|
|
22105
|
-
name: source.repoName ||
|
|
22471
|
+
name: source.repoName || basename12(repoRoot),
|
|
22106
22472
|
sources: [source]
|
|
22107
22473
|
});
|
|
22108
22474
|
}
|
|
@@ -22353,7 +22719,7 @@ function formatHumanText(value, maxLength) {
|
|
|
22353
22719
|
import { constants as constants8 } from "node:fs";
|
|
22354
22720
|
import { access as access9, mkdir as mkdir9, readFile as readFile9, writeFile as writeFile8 } from "node:fs/promises";
|
|
22355
22721
|
import { homedir as homedir8 } from "node:os";
|
|
22356
|
-
import { dirname as
|
|
22722
|
+
import { dirname as dirname14 } from "node:path";
|
|
22357
22723
|
var ZSH_COMPLETION_PATH_LINE = "fpath=(~/.zsh/completions $fpath)";
|
|
22358
22724
|
var SHELL_WRAPPED_COMMANDS = [
|
|
22359
22725
|
{
|
|
@@ -22478,7 +22844,7 @@ async function runDefaultOnboarding(context, runtime) {
|
|
|
22478
22844
|
const installCompletion = await promptForCompletion(shell, completionPath);
|
|
22479
22845
|
if (installCompletion === null) return abortDefaultOnboarding();
|
|
22480
22846
|
if (installCompletion) {
|
|
22481
|
-
await mkdir9(
|
|
22847
|
+
await mkdir9(dirname14(completionPath), { recursive: true });
|
|
22482
22848
|
await writeFile8(completionPath, renderShellCompletion(shell), "utf8");
|
|
22483
22849
|
}
|
|
22484
22850
|
if (shell === "zsh" && (completionAlreadyInstalled || installCompletion)) {
|
|
@@ -22584,7 +22950,7 @@ async function applyOnboardingResult(result, home, runtime) {
|
|
|
22584
22950
|
runtime
|
|
22585
22951
|
);
|
|
22586
22952
|
if (result.installCompletion) {
|
|
22587
|
-
await mkdir9(
|
|
22953
|
+
await mkdir9(dirname14(completionPath), { recursive: true });
|
|
22588
22954
|
await writeFile8(
|
|
22589
22955
|
completionPath,
|
|
22590
22956
|
renderShellCompletion(result.shell),
|
|
@@ -22604,7 +22970,7 @@ async function applyOnboardingResult(result, home, runtime) {
|
|
|
22604
22970
|
}
|
|
22605
22971
|
async function installOnboardingShellIntegration(shell, home) {
|
|
22606
22972
|
const rcPath = resolveShellConfigPath(shell, home);
|
|
22607
|
-
await mkdir9(
|
|
22973
|
+
await mkdir9(dirname14(rcPath), { recursive: true });
|
|
22608
22974
|
const current = await readExistingConfig(rcPath);
|
|
22609
22975
|
if (shell === "fish") {
|
|
22610
22976
|
await writeFile8(
|
|
@@ -22692,7 +23058,7 @@ async function runLegacyInitCommand(options, runtime) {
|
|
|
22692
23058
|
return 0;
|
|
22693
23059
|
}
|
|
22694
23060
|
const rcPath = resolveShellConfigPath(shell, home);
|
|
22695
|
-
await mkdir9(
|
|
23061
|
+
await mkdir9(dirname14(rcPath), { recursive: true });
|
|
22696
23062
|
const current = await readExistingConfig(rcPath);
|
|
22697
23063
|
const next = upsertShellIntegration(current, script);
|
|
22698
23064
|
await writeFile8(rcPath, next, "utf8");
|
|
@@ -23050,7 +23416,7 @@ function sortWorktrees(worktrees) {
|
|
|
23050
23416
|
// src/cli/commands/open.ts
|
|
23051
23417
|
import { execFile as execFile9 } from "node:child_process";
|
|
23052
23418
|
import { access as access10, writeFile as writeFile9 } from "node:fs/promises";
|
|
23053
|
-
import { join as
|
|
23419
|
+
import { join as join15 } from "node:path";
|
|
23054
23420
|
import { promisify as promisify10 } from "node:util";
|
|
23055
23421
|
var execFileAsync9 = promisify10(execFile9);
|
|
23056
23422
|
function createOpenCommand(dependencies = {}) {
|
|
@@ -23229,7 +23595,7 @@ async function defaultPromptForEditor(editors) {
|
|
|
23229
23595
|
return choice;
|
|
23230
23596
|
}
|
|
23231
23597
|
async function ensureWorkspaceFile(worktreePath, repoName) {
|
|
23232
|
-
const workspacePath =
|
|
23598
|
+
const workspacePath = join15(worktreePath, `${repoName}.code-workspace`);
|
|
23233
23599
|
try {
|
|
23234
23600
|
await access10(workspacePath);
|
|
23235
23601
|
return workspacePath;
|
|
@@ -23616,7 +23982,7 @@ function formatError(error) {
|
|
|
23616
23982
|
}
|
|
23617
23983
|
|
|
23618
23984
|
// src/cli/commands/remove.ts
|
|
23619
|
-
import { basename as
|
|
23985
|
+
import { basename as basename13 } from "node:path";
|
|
23620
23986
|
var REMOVE_OUTPUT_FILE_ENV = "GJI_REMOVE_OUTPUT_FILE";
|
|
23621
23987
|
function createRemoveCommand(dependencies = {}) {
|
|
23622
23988
|
const promptForWorktree2 = dependencies.promptForWorktree ?? defaultPromptForWorktree3;
|
|
@@ -23745,7 +24111,7 @@ function createRemoveCommand(dependencies = {}) {
|
|
|
23745
24111
|
{
|
|
23746
24112
|
branch: worktree.branch ?? void 0,
|
|
23747
24113
|
path: worktree.path,
|
|
23748
|
-
repo:
|
|
24114
|
+
repo: basename13(repository.repoRoot)
|
|
23749
24115
|
},
|
|
23750
24116
|
options.stderr
|
|
23751
24117
|
);
|
|
@@ -24184,7 +24550,7 @@ function resolveConfiguredString2(value) {
|
|
|
24184
24550
|
|
|
24185
24551
|
// src/cli/commands/sync-files-command.ts
|
|
24186
24552
|
import { homedir as homedir9 } from "node:os";
|
|
24187
|
-
import { join as
|
|
24553
|
+
import { join as join16 } from "node:path";
|
|
24188
24554
|
async function runSyncFilesCommand(options) {
|
|
24189
24555
|
const runtime = options.runtime ?? defaultCliDependencies;
|
|
24190
24556
|
const { validateSyncFilePattern: validateSyncFilePattern2 } = runtime.filesystem;
|
|
@@ -24338,7 +24704,7 @@ function writeError(options, message) {
|
|
|
24338
24704
|
}
|
|
24339
24705
|
function expandTilde3(value, home) {
|
|
24340
24706
|
if (value === "~") return home;
|
|
24341
|
-
if (value.startsWith("~/")) return
|
|
24707
|
+
if (value.startsWith("~/")) return join16(home, value.slice(2));
|
|
24342
24708
|
return value;
|
|
24343
24709
|
}
|
|
24344
24710
|
|
|
@@ -24446,10 +24812,14 @@ async function resolveWarpTarget(options) {
|
|
|
24446
24812
|
);
|
|
24447
24813
|
return null;
|
|
24448
24814
|
}
|
|
24449
|
-
|
|
24450
|
-
|
|
24451
|
-
|
|
24452
|
-
})
|
|
24815
|
+
const registered = await listRegisteredWorktreeSources({
|
|
24816
|
+
cwd: options.cwd,
|
|
24817
|
+
repositoryPort: sourceDependencies
|
|
24818
|
+
});
|
|
24819
|
+
const skippedRegisteredRepos = registered.skipped.length;
|
|
24820
|
+
const allItems = registered.sources.filter(
|
|
24821
|
+
(item) => item.repoRoot !== options.excludeRepoRoot
|
|
24822
|
+
);
|
|
24453
24823
|
if (allItems.length === 0) {
|
|
24454
24824
|
emitError5(
|
|
24455
24825
|
"no accessible worktrees found in any registered repo.",
|
|
@@ -24702,6 +25072,10 @@ function registerCommands(program2) {
|
|
|
24702
25072
|
).option(
|
|
24703
25073
|
"--stale",
|
|
24704
25074
|
"only target clean worktrees whose upstream is gone and branch is merged into the default branch"
|
|
25075
|
+
).option(
|
|
25076
|
+
"--path <pattern>",
|
|
25077
|
+
"only target worktrees under a directory or matching a path glob (repeatable)",
|
|
25078
|
+
collectOption
|
|
24705
25079
|
).option("--dry-run", "show what would be deleted without removing anything").option(
|
|
24706
25080
|
"--json",
|
|
24707
25081
|
"emit JSON on success or error instead of human-readable output"
|
|
@@ -25069,6 +25443,7 @@ function attachCommandActions(program2, options) {
|
|
|
25069
25443
|
dryRun: commandOptions2.dryRun,
|
|
25070
25444
|
force: commandOptions2.force,
|
|
25071
25445
|
json: commandOptions2.json,
|
|
25446
|
+
paths: commandOptions2.path,
|
|
25072
25447
|
stale: commandOptions2.stale,
|
|
25073
25448
|
runtime: options.dependencies,
|
|
25074
25449
|
stderr: options.stderr,
|
|
@@ -25196,6 +25571,9 @@ function requireCommand(parent, name) {
|
|
|
25196
25571
|
}
|
|
25197
25572
|
return command;
|
|
25198
25573
|
}
|
|
25574
|
+
function collectOption(value, previous = []) {
|
|
25575
|
+
return [...previous, value];
|
|
25576
|
+
}
|
|
25199
25577
|
function notImplemented(commandName) {
|
|
25200
25578
|
return () => {
|
|
25201
25579
|
throw new Error(`'${commandName}' is not implemented yet.`);
|