@solaqua/gji 0.14.0 → 0.16.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/README.md +63 -120
- package/dist/cli/commands/back.d.ts +1 -1
- package/dist/cli/commands/back.js +1 -0
- package/dist/cli/commands/clean.d.ts +2 -1
- package/dist/cli/commands/clean.js +66 -18
- package/dist/cli/commands/done.js +3 -2
- package/dist/cli/commands/go.js +18 -5
- package/dist/cli/commands/init.js +0 -14
- package/dist/cli/commands/new.d.ts +2 -1
- package/dist/cli/commands/new.js +71 -63
- package/dist/cli/commands/open.js +1 -1
- package/dist/cli/commands/pr-open.js +2 -2
- package/dist/cli/commands/pr.js +17 -6
- package/dist/cli/commands/run-hook.js +4 -0
- package/dist/cli/commands/undo.js +10 -74
- package/dist/cli/dependencies.d.ts +5 -1
- package/dist/cli/dependencies.js +7 -2
- package/dist/cli/program.js +6 -56
- package/dist/cli/runtime/headless.d.ts +3 -3
- package/dist/cli/runtime/headless.js +6 -4
- 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 +738 -969
- package/dist/infrastructure/filesystem/locked-file.d.ts +4 -0
- package/dist/infrastructure/filesystem/locked-file.js +53 -0
- package/dist/infrastructure/git/runner.d.ts +7 -0
- package/dist/infrastructure/git/runner.js +35 -1
- package/dist/infrastructure/persistence/history.js +12 -10
- package/dist/infrastructure/persistence/slots.js +24 -21
- package/dist/infrastructure/process/hooks.js +2 -0
- package/dist/infrastructure/repository/registry.js +11 -41
- package/dist/infrastructure/worktree/lifecycle.d.ts +25 -0
- package/dist/infrastructure/worktree/lifecycle.js +120 -19
- package/dist/ports/git.d.ts +1 -0
- package/dist/ports/hooks.d.ts +7 -0
- package/dist/presentation/shell/completion.js +14 -32
- package/dist/presentation/terminal/context-card.js +1 -1
- 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-root.1 +1 -1
- package/man/man1/gji-run-hook.1 +3 -3
- 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.1 +2 -12
- package/package.json +3 -2
- package/dist/cli/commands/remove.d.ts +0 -21
- package/dist/cli/commands/remove.js +0 -189
- package/dist/cli/commands/warp.d.ts +0 -28
- package/dist/cli/commands/warp.js +0 -123
- package/man/man1/gji-remove.1 +0 -19
- package/man/man1/gji-warp.1 +0 -16
package/dist/gji-bundle.mjs
CHANGED
|
@@ -3526,7 +3526,7 @@ var require_polyfills = __commonJS({
|
|
|
3526
3526
|
}
|
|
3527
3527
|
if (platform3 === "win32") {
|
|
3528
3528
|
fs5.rename = typeof fs5.rename !== "function" ? fs5.rename : (function(fs$rename) {
|
|
3529
|
-
function
|
|
3529
|
+
function rename2(from, to, cb) {
|
|
3530
3530
|
var start = Date.now();
|
|
3531
3531
|
var backoff = 0;
|
|
3532
3532
|
fs$rename(from, to, function CB(er) {
|
|
@@ -3546,8 +3546,8 @@ var require_polyfills = __commonJS({
|
|
|
3546
3546
|
if (cb) cb(er);
|
|
3547
3547
|
});
|
|
3548
3548
|
}
|
|
3549
|
-
if (Object.setPrototypeOf) Object.setPrototypeOf(
|
|
3550
|
-
return
|
|
3549
|
+
if (Object.setPrototypeOf) Object.setPrototypeOf(rename2, fs$rename);
|
|
3550
|
+
return rename2;
|
|
3551
3551
|
})(fs5.rename);
|
|
3552
3552
|
}
|
|
3553
3553
|
fs5.read = typeof fs5.read !== "function" ? fs5.read : (function(fs$read) {
|
|
@@ -3968,8 +3968,8 @@ var require_graceful_fs = __commonJS({
|
|
|
3968
3968
|
}
|
|
3969
3969
|
}
|
|
3970
3970
|
var fs$writeFile = fs6.writeFile;
|
|
3971
|
-
fs6.writeFile =
|
|
3972
|
-
function
|
|
3971
|
+
fs6.writeFile = writeFile7;
|
|
3972
|
+
function writeFile7(path9, data, options, cb) {
|
|
3973
3973
|
if (typeof options === "function")
|
|
3974
3974
|
cb = options, options = null;
|
|
3975
3975
|
return go$writeFile(path9, data, options, cb);
|
|
@@ -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);
|
|
@@ -5500,7 +5500,7 @@ var require_polyfills2 = __commonJS({
|
|
|
5500
5500
|
}
|
|
5501
5501
|
if (platform3 === "win32") {
|
|
5502
5502
|
fs5.rename = typeof fs5.rename !== "function" ? fs5.rename : (function(fs$rename) {
|
|
5503
|
-
function
|
|
5503
|
+
function rename2(from, to, cb) {
|
|
5504
5504
|
var start = Date.now();
|
|
5505
5505
|
var backoff = 0;
|
|
5506
5506
|
fs$rename(from, to, function CB(er) {
|
|
@@ -5520,8 +5520,8 @@ var require_polyfills2 = __commonJS({
|
|
|
5520
5520
|
if (cb) cb(er);
|
|
5521
5521
|
});
|
|
5522
5522
|
}
|
|
5523
|
-
if (Object.setPrototypeOf) Object.setPrototypeOf(
|
|
5524
|
-
return
|
|
5523
|
+
if (Object.setPrototypeOf) Object.setPrototypeOf(rename2, fs$rename);
|
|
5524
|
+
return rename2;
|
|
5525
5525
|
})(fs5.rename);
|
|
5526
5526
|
}
|
|
5527
5527
|
fs5.read = typeof fs5.read !== "function" ? fs5.read : (function(fs$read) {
|
|
@@ -5942,8 +5942,8 @@ var require_graceful_fs2 = __commonJS({
|
|
|
5942
5942
|
}
|
|
5943
5943
|
}
|
|
5944
5944
|
var fs$writeFile = fs6.writeFile;
|
|
5945
|
-
fs6.writeFile =
|
|
5946
|
-
function
|
|
5945
|
+
fs6.writeFile = writeFile7;
|
|
5946
|
+
function writeFile7(path9, data, options, cb) {
|
|
5947
5947
|
if (typeof options === "function")
|
|
5948
5948
|
cb = options, options = null;
|
|
5949
5949
|
return go$writeFile(path9, data, options, cb);
|
|
@@ -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 dirname12 = path9.dirname(name2);
|
|
6761
|
+
if (dirname12 === name2) {
|
|
6762
6762
|
return original;
|
|
6763
6763
|
}
|
|
6764
|
-
return find(
|
|
6764
|
+
return find(dirname12, original);
|
|
6765
6765
|
} catch (error) {
|
|
6766
6766
|
if (name2 === original) {
|
|
6767
6767
|
if (error.code === "ENOENT") {
|
|
@@ -9517,9 +9517,9 @@ var retryifyAsync = (fn, options) => {
|
|
|
9517
9517
|
throw error;
|
|
9518
9518
|
if (Date.now() >= timestamp)
|
|
9519
9519
|
throw error;
|
|
9520
|
-
const
|
|
9521
|
-
if (
|
|
9522
|
-
const delayPromise = new Promise((
|
|
9520
|
+
const delay3 = Math.round(interval * Math.random());
|
|
9521
|
+
if (delay3 > 0) {
|
|
9522
|
+
const delayPromise = new Promise((resolve13) => setTimeout(resolve13, delay3));
|
|
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,8 +12810,8 @@ function updateNotifier(options) {
|
|
|
12810
12810
|
// package.json
|
|
12811
12811
|
var package_default = {
|
|
12812
12812
|
name: "@solaqua/gji",
|
|
12813
|
-
version: "0.
|
|
12814
|
-
description: "Git worktree CLI for
|
|
12813
|
+
version: "0.16.0",
|
|
12814
|
+
description: "Git worktree CLI for switching tasks without disturbing your work.",
|
|
12815
12815
|
license: "MIT",
|
|
12816
12816
|
author: "sjquant",
|
|
12817
12817
|
type: "module",
|
|
@@ -12875,6 +12875,7 @@ var package_default = {
|
|
|
12875
12875
|
devDependencies: {
|
|
12876
12876
|
"@biomejs/biome": "^2.4.15",
|
|
12877
12877
|
"@j178/prek": "^0.3.13",
|
|
12878
|
+
"@resvg/resvg-js": "^2.6.2",
|
|
12878
12879
|
"@types/node": "^24.6.0",
|
|
12879
12880
|
"@vitest/coverage-v8": "3.2.4",
|
|
12880
12881
|
esbuild: "^0.27.0",
|
|
@@ -13093,7 +13094,7 @@ async function ensureDestinationDirectory(root, path9) {
|
|
|
13093
13094
|
// src/infrastructure/process/command-runner.ts
|
|
13094
13095
|
import { spawn as spawn2 } from "node:child_process";
|
|
13095
13096
|
var runCommand = async (command, cwd, stderr, stdout3 = (chunk) => process.stdout.write(chunk), options) => {
|
|
13096
|
-
await new Promise((
|
|
13097
|
+
await new Promise((resolve13, reject) => {
|
|
13097
13098
|
const shell = options?.shell ?? true;
|
|
13098
13099
|
const [executable, args] = shell ? [command, []] : splitCommand(command);
|
|
13099
13100
|
const child = spawn2(executable, args, {
|
|
@@ -13112,7 +13113,7 @@ var runCommand = async (command, cwd, stderr, stdout3 = (chunk) => process.stdou
|
|
|
13112
13113
|
if (code !== 0) {
|
|
13113
13114
|
reject(new Error(`exited with code ${code}`));
|
|
13114
13115
|
} else {
|
|
13115
|
-
|
|
13116
|
+
resolve13();
|
|
13116
13117
|
}
|
|
13117
13118
|
});
|
|
13118
13119
|
child.on("error", reject);
|
|
@@ -13859,18 +13860,44 @@ import { promisify as promisify5 } from "node:util";
|
|
|
13859
13860
|
import { execFile as execFile3 } from "node:child_process";
|
|
13860
13861
|
import { promisify as promisify4 } from "node:util";
|
|
13861
13862
|
var execFileAsync3 = promisify4(execFile3);
|
|
13863
|
+
var CONTENTION_RETRY_TIMEOUT_MS = 1e4;
|
|
13864
|
+
var CONTENTION_RETRY_MAX_DELAY_MS = 1e3;
|
|
13865
|
+
var GIT_ENV = { ...process.env, LC_ALL: "C" };
|
|
13862
13866
|
async function runGit(cwd, args) {
|
|
13863
13867
|
return (await runGitRaw(cwd, args)).trim();
|
|
13864
13868
|
}
|
|
13865
13869
|
async function runGitRaw(cwd, args) {
|
|
13870
|
+
return execGit(cwd, args);
|
|
13871
|
+
}
|
|
13872
|
+
async function runGitRetryingContention(cwd, args) {
|
|
13873
|
+
return (await retryGitContention(() => execGit(cwd, args, GIT_ENV))).trim();
|
|
13874
|
+
}
|
|
13875
|
+
async function retryGitContention(operation, timeoutMs = CONTENTION_RETRY_TIMEOUT_MS) {
|
|
13876
|
+
const deadline = Date.now() + timeoutMs;
|
|
13877
|
+
for (let delayMs = 25; ; delayMs *= 2) {
|
|
13878
|
+
try {
|
|
13879
|
+
return await operation();
|
|
13880
|
+
} catch (error) {
|
|
13881
|
+
if (!isTransientGitError(error) || Date.now() >= deadline) throw error;
|
|
13882
|
+
}
|
|
13883
|
+
const jitteredDelayMs = Math.min(delayMs, CONTENTION_RETRY_MAX_DELAY_MS) * (0.5 + Math.random() / 2);
|
|
13884
|
+
await new Promise((resolve13) => setTimeout(resolve13, jitteredDelayMs));
|
|
13885
|
+
}
|
|
13886
|
+
}
|
|
13887
|
+
async function execGit(cwd, args, env5) {
|
|
13866
13888
|
try {
|
|
13867
|
-
const { stdout: stdout3 } = await execFileAsync3("git", args, { cwd });
|
|
13889
|
+
const { stdout: stdout3 } = await execFileAsync3("git", args, { cwd, env: env5 });
|
|
13868
13890
|
return stdout3;
|
|
13869
13891
|
} catch (error) {
|
|
13870
13892
|
const message = error instanceof Error ? error.message : String(error);
|
|
13871
13893
|
throw new Error(`Git command failed in '${cwd}': ${message}`);
|
|
13872
13894
|
}
|
|
13873
13895
|
}
|
|
13896
|
+
function isTransientGitError(error) {
|
|
13897
|
+
return error instanceof Error && /(could not lock config file .*|Unable to create '.*\.lock'): File exists|cannot lock ref .*: is at .* but expected |failed to read .*\/commondir|Invalid path '.*\/worktrees\//.test(
|
|
13898
|
+
error.message
|
|
13899
|
+
);
|
|
13900
|
+
}
|
|
13874
13901
|
|
|
13875
13902
|
// src/infrastructure/git/refs.ts
|
|
13876
13903
|
var execFileAsync4 = promisify5(execFile4);
|
|
@@ -14019,9 +14046,9 @@ var EDITORS = [
|
|
|
14019
14046
|
];
|
|
14020
14047
|
async function defaultSpawnEditor(cli, args) {
|
|
14021
14048
|
const child = spawn3(cli, args, { detached: true, stdio: "ignore" });
|
|
14022
|
-
await new Promise((
|
|
14049
|
+
await new Promise((resolve13, reject) => {
|
|
14023
14050
|
child.once("error", reject);
|
|
14024
|
-
child.once("spawn",
|
|
14051
|
+
child.once("spawn", resolve13);
|
|
14025
14052
|
});
|
|
14026
14053
|
child.unref();
|
|
14027
14054
|
}
|
|
@@ -14690,9 +14717,72 @@ function levenshtein(a, b3) {
|
|
|
14690
14717
|
}
|
|
14691
14718
|
|
|
14692
14719
|
// src/infrastructure/persistence/history.ts
|
|
14693
|
-
import {
|
|
14720
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
14694
14721
|
import { homedir as homedir2 } from "node:os";
|
|
14695
|
-
import {
|
|
14722
|
+
import { join as join6, resolve as resolve6 } from "node:path";
|
|
14723
|
+
|
|
14724
|
+
// src/infrastructure/filesystem/locked-file.ts
|
|
14725
|
+
import { randomUUID } from "node:crypto";
|
|
14726
|
+
import {
|
|
14727
|
+
mkdir as mkdir3,
|
|
14728
|
+
open as open3,
|
|
14729
|
+
rename,
|
|
14730
|
+
rm,
|
|
14731
|
+
stat,
|
|
14732
|
+
unlink,
|
|
14733
|
+
writeFile as writeFile3
|
|
14734
|
+
} from "node:fs/promises";
|
|
14735
|
+
import { dirname as dirname4 } from "node:path";
|
|
14736
|
+
import { setTimeout as delay2 } from "node:timers/promises";
|
|
14737
|
+
var LOCK_STALE_AFTER_MS = 1e4;
|
|
14738
|
+
var LOCK_RETRY_DELAY_MS = 5;
|
|
14739
|
+
var LOCK_WAIT_TIMEOUT_MS = LOCK_STALE_AFTER_MS + LOCK_RETRY_DELAY_MS;
|
|
14740
|
+
async function withFileLock(path9, operation) {
|
|
14741
|
+
await mkdir3(dirname4(path9), { recursive: true });
|
|
14742
|
+
const release = await acquireFileLock(path9);
|
|
14743
|
+
try {
|
|
14744
|
+
return await operation();
|
|
14745
|
+
} finally {
|
|
14746
|
+
await release();
|
|
14747
|
+
}
|
|
14748
|
+
}
|
|
14749
|
+
async function writeJsonAtomic(path9, value) {
|
|
14750
|
+
const temporaryPath = `${path9}.tmp-${process.pid}-${randomUUID()}`;
|
|
14751
|
+
try {
|
|
14752
|
+
await writeFile3(
|
|
14753
|
+
temporaryPath,
|
|
14754
|
+
`${JSON.stringify(value, null, 2)}
|
|
14755
|
+
`,
|
|
14756
|
+
"utf8"
|
|
14757
|
+
);
|
|
14758
|
+
await rename(temporaryPath, path9);
|
|
14759
|
+
} catch (error) {
|
|
14760
|
+
await rm(temporaryPath, { force: true }).catch(() => void 0);
|
|
14761
|
+
throw error;
|
|
14762
|
+
}
|
|
14763
|
+
}
|
|
14764
|
+
async function acquireFileLock(path9) {
|
|
14765
|
+
const lockPath = `${path9}.lock`;
|
|
14766
|
+
const deadline = Date.now() + LOCK_WAIT_TIMEOUT_MS;
|
|
14767
|
+
while (Date.now() < deadline) {
|
|
14768
|
+
try {
|
|
14769
|
+
const handle = await open3(lockPath, "wx");
|
|
14770
|
+
return async () => {
|
|
14771
|
+
await handle.close();
|
|
14772
|
+
await unlink(lockPath).catch(() => void 0);
|
|
14773
|
+
};
|
|
14774
|
+
} catch (error) {
|
|
14775
|
+
if (!isAlreadyExistsError(error)) throw error;
|
|
14776
|
+
const lockStat = await stat(lockPath).catch(() => null);
|
|
14777
|
+
if (lockStat && Date.now() - lockStat.mtimeMs > LOCK_STALE_AFTER_MS)
|
|
14778
|
+
await unlink(lockPath).catch(() => void 0);
|
|
14779
|
+
await delay2(LOCK_RETRY_DELAY_MS);
|
|
14780
|
+
}
|
|
14781
|
+
}
|
|
14782
|
+
throw new Error(`timed out waiting for the lock on ${path9}`);
|
|
14783
|
+
}
|
|
14784
|
+
|
|
14785
|
+
// src/infrastructure/persistence/history.ts
|
|
14696
14786
|
var HISTORY_FILE_NAME = "history.json";
|
|
14697
14787
|
var MAX_HISTORY_ENTRIES = 50;
|
|
14698
14788
|
function HISTORY_FILE_PATH(home = homedir2()) {
|
|
@@ -14715,15 +14805,15 @@ async function loadHistory(home = homedir2()) {
|
|
|
14715
14805
|
}
|
|
14716
14806
|
async function appendHistory(path9, branch, home = homedir2()) {
|
|
14717
14807
|
const historyPath = HISTORY_FILE_PATH(home);
|
|
14718
|
-
|
|
14719
|
-
|
|
14720
|
-
|
|
14721
|
-
|
|
14722
|
-
|
|
14723
|
-
|
|
14724
|
-
|
|
14725
|
-
|
|
14726
|
-
|
|
14808
|
+
await withFileLock(historyPath, async () => {
|
|
14809
|
+
const existing = await loadHistory(home);
|
|
14810
|
+
const entry = { branch, path: path9, timestamp: Date.now() };
|
|
14811
|
+
const next = [
|
|
14812
|
+
entry,
|
|
14813
|
+
...existing.filter((existingEntry) => existingEntry.path !== path9)
|
|
14814
|
+
].slice(0, MAX_HISTORY_ENTRIES);
|
|
14815
|
+
await writeJsonAtomic(historyPath, next);
|
|
14816
|
+
});
|
|
14727
14817
|
}
|
|
14728
14818
|
async function recordWorktreeUsage(path9, branch, home = homedir2()) {
|
|
14729
14819
|
try {
|
|
@@ -14736,9 +14826,9 @@ function isHistoryEntry(value) {
|
|
|
14736
14826
|
}
|
|
14737
14827
|
|
|
14738
14828
|
// src/infrastructure/persistence/slots.ts
|
|
14739
|
-
import {
|
|
14829
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
14740
14830
|
import { homedir as homedir3 } from "node:os";
|
|
14741
|
-
import {
|
|
14831
|
+
import { join as join7, resolve as resolve7 } from "node:path";
|
|
14742
14832
|
function SLOTS_FILE_PATH(home = homedir3()) {
|
|
14743
14833
|
const configDir = process.env.GJI_CONFIG_DIR;
|
|
14744
14834
|
return configDir ? join7(resolve7(configDir), "slots.json") : join7(home, GLOBAL_CONFIG_DIRECTORY, "slots.json");
|
|
@@ -14755,32 +14845,33 @@ async function loadSlots(home = homedir3()) {
|
|
|
14755
14845
|
}
|
|
14756
14846
|
}
|
|
14757
14847
|
async function assignWorktreeSlot(repoRoot, worktreePath, home = homedir3()) {
|
|
14758
|
-
|
|
14759
|
-
|
|
14760
|
-
|
|
14761
|
-
|
|
14762
|
-
slots[repoRoot]
|
|
14763
|
-
|
|
14764
|
-
|
|
14765
|
-
|
|
14766
|
-
|
|
14767
|
-
|
|
14848
|
+
return withFileLock(SLOTS_FILE_PATH(home), async () => {
|
|
14849
|
+
const slots = await loadSlots(home);
|
|
14850
|
+
const existing = slots[worktreePath];
|
|
14851
|
+
if (existing !== void 0) return existing;
|
|
14852
|
+
if (worktreePath !== repoRoot && slots[repoRoot] === void 0)
|
|
14853
|
+
slots[repoRoot] = 0;
|
|
14854
|
+
const used = new Set(Object.values(slots));
|
|
14855
|
+
const slot = worktreePath === repoRoot ? 0 : nextAvailableSlot(used);
|
|
14856
|
+
slots[worktreePath] = slot;
|
|
14857
|
+
await saveSlots(slots, home);
|
|
14858
|
+
return slot;
|
|
14859
|
+
});
|
|
14768
14860
|
}
|
|
14769
14861
|
async function getWorktreeSlot(worktreePath, home = homedir3()) {
|
|
14770
14862
|
const slot = (await loadSlots(home))[worktreePath];
|
|
14771
14863
|
return slot === void 0 ? null : slot;
|
|
14772
14864
|
}
|
|
14773
14865
|
async function releaseWorktreeSlot(worktreePath, home = homedir3()) {
|
|
14774
|
-
|
|
14775
|
-
|
|
14776
|
-
|
|
14777
|
-
|
|
14866
|
+
await withFileLock(SLOTS_FILE_PATH(home), async () => {
|
|
14867
|
+
const slots = await loadSlots(home);
|
|
14868
|
+
if (!(worktreePath in slots)) return;
|
|
14869
|
+
delete slots[worktreePath];
|
|
14870
|
+
await saveSlots(slots, home);
|
|
14871
|
+
});
|
|
14778
14872
|
}
|
|
14779
14873
|
async function saveSlots(slots, home) {
|
|
14780
|
-
|
|
14781
|
-
await mkdir4(dirname5(path9), { recursive: true });
|
|
14782
|
-
await writeFile4(path9, `${JSON.stringify(slots, null, 2)}
|
|
14783
|
-
`, "utf8");
|
|
14874
|
+
await writeJsonAtomic(SLOTS_FILE_PATH(home), slots);
|
|
14784
14875
|
}
|
|
14785
14876
|
function nextAvailableSlot(used) {
|
|
14786
14877
|
let slot = 1;
|
|
@@ -14792,16 +14883,16 @@ function isSlotStore(value) {
|
|
|
14792
14883
|
}
|
|
14793
14884
|
|
|
14794
14885
|
// src/infrastructure/persistence/task.ts
|
|
14795
|
-
import { readFile as readFile5, unlink, writeFile as
|
|
14886
|
+
import { readFile as readFile5, unlink as unlink2, writeFile as writeFile4 } from "node:fs/promises";
|
|
14796
14887
|
import { join as join8 } from "node:path";
|
|
14797
14888
|
|
|
14798
14889
|
// src/infrastructure/repository/context.ts
|
|
14799
|
-
import { basename as basename3, dirname as
|
|
14890
|
+
import { basename as basename3, dirname as dirname5, isAbsolute as isAbsolute4, resolve as resolve8 } from "node:path";
|
|
14800
14891
|
async function detectRepository(cwd) {
|
|
14801
14892
|
const currentRoot = await runGit(cwd, ["rev-parse", "--show-toplevel"]);
|
|
14802
14893
|
const rawCommonDir = await runGit(cwd, ["rev-parse", "--git-common-dir"]);
|
|
14803
14894
|
const gitCommonDir = isAbsolute4(rawCommonDir) ? rawCommonDir : resolve8(currentRoot, rawCommonDir);
|
|
14804
|
-
const repoRoot =
|
|
14895
|
+
const repoRoot = dirname5(gitCommonDir);
|
|
14805
14896
|
return {
|
|
14806
14897
|
currentRoot,
|
|
14807
14898
|
isWorktree: currentRoot !== repoRoot,
|
|
@@ -14828,13 +14919,13 @@ async function readTask(worktreePath) {
|
|
|
14828
14919
|
async function writeTask(worktreePath, task, source = "manual") {
|
|
14829
14920
|
const metadata = { source, task, updatedAt: Date.now() };
|
|
14830
14921
|
const path9 = await taskPath(worktreePath);
|
|
14831
|
-
await
|
|
14922
|
+
await writeFile4(path9, `${JSON.stringify(metadata, null, 2)}
|
|
14832
14923
|
`, "utf8");
|
|
14833
14924
|
return metadata;
|
|
14834
14925
|
}
|
|
14835
14926
|
async function clearTask(worktreePath) {
|
|
14836
14927
|
try {
|
|
14837
|
-
await
|
|
14928
|
+
await unlink2(await taskPath(worktreePath));
|
|
14838
14929
|
} catch {
|
|
14839
14930
|
}
|
|
14840
14931
|
}
|
|
@@ -14863,7 +14954,7 @@ async function runArgvHook(hookCmd, cwd, context, stderr, stdout3) {
|
|
|
14863
14954
|
stderr("gji: hook argv command must include a non-empty command\n");
|
|
14864
14955
|
return;
|
|
14865
14956
|
}
|
|
14866
|
-
await new Promise((
|
|
14957
|
+
await new Promise((resolve13) => {
|
|
14867
14958
|
const child = spawn4(command, args, {
|
|
14868
14959
|
cwd,
|
|
14869
14960
|
shell: false,
|
|
@@ -14883,18 +14974,18 @@ async function runArgvHook(hookCmd, cwd, context, stderr, stdout3) {
|
|
|
14883
14974
|
`
|
|
14884
14975
|
);
|
|
14885
14976
|
}
|
|
14886
|
-
|
|
14977
|
+
resolve13();
|
|
14887
14978
|
});
|
|
14888
14979
|
child.on("error", (err) => {
|
|
14889
14980
|
stderr(`gji: hook failed to start: ${err.message}
|
|
14890
14981
|
`);
|
|
14891
|
-
|
|
14982
|
+
resolve13();
|
|
14892
14983
|
});
|
|
14893
14984
|
});
|
|
14894
14985
|
}
|
|
14895
14986
|
async function runShellHook(hookCmd, cwd, context, stderr, stdout3) {
|
|
14896
14987
|
const interpolated = interpolate(hookCmd, context);
|
|
14897
|
-
await new Promise((
|
|
14988
|
+
await new Promise((resolve13) => {
|
|
14898
14989
|
const child = spawn4(interpolated, {
|
|
14899
14990
|
cwd,
|
|
14900
14991
|
shell: true,
|
|
@@ -14912,12 +15003,12 @@ async function runShellHook(hookCmd, cwd, context, stderr, stdout3) {
|
|
|
14912
15003
|
stderr(`gji: hook exited with code ${code}: ${interpolated}
|
|
14913
15004
|
`);
|
|
14914
15005
|
}
|
|
14915
|
-
|
|
15006
|
+
resolve13();
|
|
14916
15007
|
});
|
|
14917
15008
|
child.on("error", (err) => {
|
|
14918
15009
|
stderr(`gji: hook failed to start: ${err.message}
|
|
14919
15010
|
`);
|
|
14920
|
-
|
|
15011
|
+
resolve13();
|
|
14921
15012
|
});
|
|
14922
15013
|
});
|
|
14923
15014
|
}
|
|
@@ -14946,10 +15037,16 @@ function extractHooks(config) {
|
|
|
14946
15037
|
}
|
|
14947
15038
|
const hooks = raw;
|
|
14948
15039
|
return {
|
|
15040
|
+
"after-branch-create": parseHookCommand(
|
|
15041
|
+
hooks["after-branch-create"] ?? hooks.afterBranchCreate
|
|
15042
|
+
),
|
|
14949
15043
|
"after-create": parseHookCommand(
|
|
14950
15044
|
hooks["after-create"] ?? hooks.afterCreate
|
|
14951
15045
|
),
|
|
14952
15046
|
"after-enter": parseHookCommand(hooks["after-enter"] ?? hooks.afterEnter),
|
|
15047
|
+
"after-switch": parseHookCommand(
|
|
15048
|
+
hooks["after-switch"] ?? hooks.afterSwitch
|
|
15049
|
+
),
|
|
14953
15050
|
"before-remove": parseHookCommand(
|
|
14954
15051
|
hooks["before-remove"] ?? hooks.beforeRemove
|
|
14955
15052
|
)
|
|
@@ -14999,22 +15096,12 @@ async function getRepositoryRemoteUrl(repoRoot, remote) {
|
|
|
14999
15096
|
}
|
|
15000
15097
|
|
|
15001
15098
|
// src/infrastructure/repository/registry.ts
|
|
15002
|
-
import { randomUUID } from "node:crypto";
|
|
15003
15099
|
import { constants as constants4 } from "node:fs";
|
|
15004
|
-
import {
|
|
15005
|
-
access,
|
|
15006
|
-
mkdir as mkdir5,
|
|
15007
|
-
readFile as readFile6,
|
|
15008
|
-
realpath as realpath4,
|
|
15009
|
-
rename,
|
|
15010
|
-
rm,
|
|
15011
|
-
writeFile as writeFile6
|
|
15012
|
-
} from "node:fs/promises";
|
|
15100
|
+
import { access, readFile as readFile6, realpath as realpath4 } from "node:fs/promises";
|
|
15013
15101
|
import { homedir as homedir4 } from "node:os";
|
|
15014
|
-
import { basename as basename4,
|
|
15102
|
+
import { basename as basename4, join as join9, resolve as resolve9 } from "node:path";
|
|
15015
15103
|
var REGISTRY_FILE_NAME = "repos.json";
|
|
15016
15104
|
var MAX_REGISTRY_ENTRIES = 100;
|
|
15017
|
-
var registryLocks = /* @__PURE__ */ new Map();
|
|
15018
15105
|
function REGISTRY_FILE_PATH(home = homedir4()) {
|
|
15019
15106
|
const configDir = process.env.GJI_CONFIG_DIR;
|
|
15020
15107
|
if (configDir) {
|
|
@@ -15042,9 +15129,10 @@ async function canonicalizeRepoPath(repoPath) {
|
|
|
15042
15129
|
}
|
|
15043
15130
|
async function registerRepo(repoPath, home = homedir4()) {
|
|
15044
15131
|
const registryPath = REGISTRY_FILE_PATH(home);
|
|
15045
|
-
|
|
15132
|
+
const canonicalRepoPath = await canonicalizeRepoPath(repoPath);
|
|
15133
|
+
if ((await loadRegistry(home))[0]?.path === canonicalRepoPath) return;
|
|
15134
|
+
await withFileLock(registryPath, async () => {
|
|
15046
15135
|
const existing = await normalizeRegistryForWrite(await loadRegistry(home));
|
|
15047
|
-
const canonicalRepoPath = await canonicalizeRepoPath(repoPath);
|
|
15048
15136
|
if (existing.length > 0 && existing[0].path === canonicalRepoPath) return;
|
|
15049
15137
|
const entry = {
|
|
15050
15138
|
lastUsed: Date.now(),
|
|
@@ -15053,13 +15141,13 @@ async function registerRepo(repoPath, home = homedir4()) {
|
|
|
15053
15141
|
};
|
|
15054
15142
|
const filtered = existing.filter((e2) => e2.path !== canonicalRepoPath);
|
|
15055
15143
|
const next = [entry, ...filtered].slice(0, MAX_REGISTRY_ENTRIES);
|
|
15056
|
-
await
|
|
15144
|
+
await writeJsonAtomic(registryPath, next);
|
|
15057
15145
|
});
|
|
15058
15146
|
}
|
|
15059
15147
|
async function removeMissingRegistryEntries(paths, home = homedir4()) {
|
|
15060
15148
|
if (paths.size === 0) return { removedPaths: [], skippedPaths: [] };
|
|
15061
15149
|
const registryPath = REGISTRY_FILE_PATH(home);
|
|
15062
|
-
return
|
|
15150
|
+
return withFileLock(registryPath, async () => {
|
|
15063
15151
|
const entries = await loadRegistry(home);
|
|
15064
15152
|
const candidates = entries.filter((entry) => paths.has(entry.path));
|
|
15065
15153
|
const status = await Promise.all(
|
|
@@ -15071,31 +15159,14 @@ async function removeMissingRegistryEntries(paths, home = homedir4()) {
|
|
|
15071
15159
|
const removedPaths = status.filter(({ missing }) => missing).map(({ entry }) => entry.path);
|
|
15072
15160
|
const skippedPaths = status.filter(({ missing }) => !missing).map(({ entry }) => entry.path);
|
|
15073
15161
|
if (removedPaths.length > 0) {
|
|
15074
|
-
await
|
|
15075
|
-
|
|
15076
|
-
|
|
15162
|
+
await writeJsonAtomic(
|
|
15163
|
+
registryPath,
|
|
15164
|
+
entries.filter((entry) => !removedPaths.includes(entry.path))
|
|
15077
15165
|
);
|
|
15078
15166
|
}
|
|
15079
15167
|
return { removedPaths, skippedPaths };
|
|
15080
15168
|
});
|
|
15081
15169
|
}
|
|
15082
|
-
async function withRegistryLock(registryPath, operation) {
|
|
15083
|
-
const previous = registryLocks.get(registryPath);
|
|
15084
|
-
let release;
|
|
15085
|
-
const current = new Promise((resolve12) => {
|
|
15086
|
-
release = resolve12;
|
|
15087
|
-
});
|
|
15088
|
-
registryLocks.set(registryPath, current);
|
|
15089
|
-
if (previous) await previous;
|
|
15090
|
-
try {
|
|
15091
|
-
return await operation();
|
|
15092
|
-
} finally {
|
|
15093
|
-
release();
|
|
15094
|
-
if (registryLocks.get(registryPath) === current) {
|
|
15095
|
-
registryLocks.delete(registryPath);
|
|
15096
|
-
}
|
|
15097
|
-
}
|
|
15098
|
-
}
|
|
15099
15170
|
async function isMissingPath(path9) {
|
|
15100
15171
|
try {
|
|
15101
15172
|
await access(path9, constants4.F_OK);
|
|
@@ -15109,22 +15180,6 @@ function isMissingPathError(error) {
|
|
|
15109
15180
|
const code = error.code;
|
|
15110
15181
|
return code === "ENOENT" || code === "ENOTDIR";
|
|
15111
15182
|
}
|
|
15112
|
-
async function writeRegistry(entries, registryPath) {
|
|
15113
|
-
await mkdir5(dirname7(registryPath), { recursive: true });
|
|
15114
|
-
const temporaryPath = `${registryPath}.tmp-${process.pid}-${randomUUID()}`;
|
|
15115
|
-
try {
|
|
15116
|
-
await writeFile6(
|
|
15117
|
-
temporaryPath,
|
|
15118
|
-
`${JSON.stringify(entries, null, 2)}
|
|
15119
|
-
`,
|
|
15120
|
-
"utf8"
|
|
15121
|
-
);
|
|
15122
|
-
await rename(temporaryPath, registryPath);
|
|
15123
|
-
} catch (error) {
|
|
15124
|
-
await rm(temporaryPath, { force: true }).catch(() => void 0);
|
|
15125
|
-
throw error;
|
|
15126
|
-
}
|
|
15127
|
-
}
|
|
15128
15183
|
async function normalizeRegistryForWrite(entries) {
|
|
15129
15184
|
const normalized = [];
|
|
15130
15185
|
const seenPaths = /* @__PURE__ */ new Set();
|
|
@@ -15368,15 +15423,93 @@ function serializeWorktreeInfo(info) {
|
|
|
15368
15423
|
|
|
15369
15424
|
// src/infrastructure/worktree/lifecycle.ts
|
|
15370
15425
|
import { execFile as execFile7 } from "node:child_process";
|
|
15371
|
-
import { rm as rm2 } from "node:fs/promises";
|
|
15426
|
+
import { mkdir as mkdir4, rm as rm2, rmdir } from "node:fs/promises";
|
|
15372
15427
|
import { promisify as promisify8 } from "node:util";
|
|
15373
15428
|
var execFileAsync7 = promisify8(execFile7);
|
|
15374
|
-
var
|
|
15429
|
+
var BRANCH_CONFIG_CLEANUP_TIMEOUT_MS = 1e3;
|
|
15430
|
+
async function addWorktree(repoRoot, worktreePath, target) {
|
|
15431
|
+
await claimWorktreePath(worktreePath);
|
|
15432
|
+
if ("create" in target) {
|
|
15433
|
+
try {
|
|
15434
|
+
await createBranch(repoRoot, target.branch, target.create);
|
|
15435
|
+
} catch (error) {
|
|
15436
|
+
await rmdir(worktreePath).catch(() => void 0);
|
|
15437
|
+
throw error;
|
|
15438
|
+
}
|
|
15439
|
+
}
|
|
15440
|
+
const args = "branch" in target ? [target.branch] : ["--detach", ...target.detachedAt ? [target.detachedAt] : []];
|
|
15441
|
+
try {
|
|
15442
|
+
await retryGitContention(
|
|
15443
|
+
() => execFileAsync7("git", ["worktree", "add", worktreePath, ...args], {
|
|
15444
|
+
cwd: repoRoot
|
|
15445
|
+
})
|
|
15446
|
+
);
|
|
15447
|
+
} catch (error) {
|
|
15448
|
+
await removeCreatedWorktree(
|
|
15449
|
+
repoRoot,
|
|
15450
|
+
worktreePath,
|
|
15451
|
+
"create" in target ? target.branch : void 0
|
|
15452
|
+
);
|
|
15453
|
+
throw error;
|
|
15454
|
+
}
|
|
15455
|
+
}
|
|
15456
|
+
async function removeCreatedWorktree(repoRoot, worktreePath, createdBranch) {
|
|
15457
|
+
await runLifecycleGit(repoRoot, [
|
|
15458
|
+
"worktree",
|
|
15459
|
+
"remove",
|
|
15460
|
+
"--force",
|
|
15461
|
+
worktreePath
|
|
15462
|
+
]).catch(() => void 0);
|
|
15463
|
+
await rmdir(worktreePath).catch(() => void 0);
|
|
15464
|
+
if (createdBranch) await deleteCreatedBranch(repoRoot, createdBranch);
|
|
15465
|
+
}
|
|
15466
|
+
async function claimWorktreePath(worktreePath) {
|
|
15467
|
+
try {
|
|
15468
|
+
await mkdir4(worktreePath);
|
|
15469
|
+
} catch (error) {
|
|
15470
|
+
if (!isAlreadyExistsError(error)) throw error;
|
|
15471
|
+
throw new Error(`target worktree path already exists: ${worktreePath}`);
|
|
15472
|
+
}
|
|
15473
|
+
}
|
|
15474
|
+
async function createBranch(repoRoot, branch, options = {}) {
|
|
15475
|
+
const args = [
|
|
15476
|
+
"branch",
|
|
15477
|
+
...options.track ? ["--track"] : [],
|
|
15478
|
+
branch,
|
|
15479
|
+
...options.startPoint ? [options.startPoint] : []
|
|
15480
|
+
];
|
|
15481
|
+
try {
|
|
15482
|
+
await retryGitContention(async () => {
|
|
15483
|
+
try {
|
|
15484
|
+
await execFileAsync7("git", args, { cwd: repoRoot, env: GIT_ENV });
|
|
15485
|
+
} catch (error) {
|
|
15486
|
+
if (isGitConfigLockError(error))
|
|
15487
|
+
await forceDeleteBranch(repoRoot, branch);
|
|
15488
|
+
throw error;
|
|
15489
|
+
}
|
|
15490
|
+
});
|
|
15491
|
+
} catch (error) {
|
|
15492
|
+
if (isGitConfigLockError(error))
|
|
15493
|
+
await removeBranchConfig(repoRoot, branch).catch(() => void 0);
|
|
15494
|
+
throw error;
|
|
15495
|
+
}
|
|
15496
|
+
}
|
|
15497
|
+
async function deleteCreatedBranch(repoRoot, branch) {
|
|
15498
|
+
try {
|
|
15499
|
+
await forceDeleteBranch(repoRoot, branch);
|
|
15500
|
+
await removeBranchConfig(repoRoot, branch);
|
|
15501
|
+
} catch {
|
|
15502
|
+
}
|
|
15503
|
+
}
|
|
15504
|
+
async function removeBranchConfig(repoRoot, branch) {
|
|
15505
|
+
await runLifecycleGit(
|
|
15506
|
+
repoRoot,
|
|
15507
|
+
["config", "--remove-section", `branch.${branch}`],
|
|
15508
|
+
BRANCH_CONFIG_CLEANUP_TIMEOUT_MS
|
|
15509
|
+
);
|
|
15510
|
+
}
|
|
15375
15511
|
async function removeWorktree(repoRoot, worktreePath) {
|
|
15376
|
-
await
|
|
15377
|
-
cwd: repoRoot,
|
|
15378
|
-
env: GIT_ENV
|
|
15379
|
-
});
|
|
15512
|
+
await runLifecycleGit(repoRoot, ["worktree", "remove", worktreePath]);
|
|
15380
15513
|
}
|
|
15381
15514
|
async function forceRemoveWorktree(repoRoot, worktreePath) {
|
|
15382
15515
|
try {
|
|
@@ -15397,10 +15530,12 @@ async function forceRemoveWorktree(repoRoot, worktreePath) {
|
|
|
15397
15530
|
}
|
|
15398
15531
|
}
|
|
15399
15532
|
async function removeWorktreeWithForce(repoRoot, worktreePath) {
|
|
15400
|
-
await
|
|
15401
|
-
|
|
15402
|
-
|
|
15403
|
-
|
|
15533
|
+
await runLifecycleGit(repoRoot, [
|
|
15534
|
+
"worktree",
|
|
15535
|
+
"remove",
|
|
15536
|
+
"--force",
|
|
15537
|
+
worktreePath
|
|
15538
|
+
]);
|
|
15404
15539
|
}
|
|
15405
15540
|
async function deinitializeSubmodules(worktreePath) {
|
|
15406
15541
|
await execFileAsync7("git", ["submodule", "deinit", "--force", "--all"], {
|
|
@@ -15409,16 +15544,10 @@ async function deinitializeSubmodules(worktreePath) {
|
|
|
15409
15544
|
});
|
|
15410
15545
|
}
|
|
15411
15546
|
async function deleteBranch(repoRoot, branch) {
|
|
15412
|
-
await
|
|
15413
|
-
cwd: repoRoot,
|
|
15414
|
-
env: GIT_ENV
|
|
15415
|
-
});
|
|
15547
|
+
await runLifecycleGit(repoRoot, ["branch", "-d", branch]);
|
|
15416
15548
|
}
|
|
15417
15549
|
async function forceDeleteBranch(repoRoot, branch) {
|
|
15418
|
-
await
|
|
15419
|
-
cwd: repoRoot,
|
|
15420
|
-
env: GIT_ENV
|
|
15421
|
-
});
|
|
15550
|
+
await runLifecycleGit(repoRoot, ["branch", "-D", branch]);
|
|
15422
15551
|
}
|
|
15423
15552
|
function isWorktreeForceRemovalError(error) {
|
|
15424
15553
|
if (!hasStderr(error)) {
|
|
@@ -15440,6 +15569,15 @@ function isBranchUnmergedError(error) {
|
|
|
15440
15569
|
function hasStderr(error) {
|
|
15441
15570
|
return error instanceof Error && "stderr" in error && typeof error.stderr === "string";
|
|
15442
15571
|
}
|
|
15572
|
+
async function runLifecycleGit(repoRoot, args, timeoutMs) {
|
|
15573
|
+
await retryGitContention(
|
|
15574
|
+
() => execFileAsync7("git", args, { cwd: repoRoot, env: GIT_ENV }),
|
|
15575
|
+
timeoutMs
|
|
15576
|
+
);
|
|
15577
|
+
}
|
|
15578
|
+
function isGitConfigLockError(error) {
|
|
15579
|
+
return error instanceof Error && error.message.includes("could not lock config file");
|
|
15580
|
+
}
|
|
15443
15581
|
|
|
15444
15582
|
// src/presentation/worktree/format.ts
|
|
15445
15583
|
function formatUpstreamState(upstream) {
|
|
@@ -15507,6 +15645,11 @@ function createCliDependencies() {
|
|
|
15507
15645
|
git: {
|
|
15508
15646
|
runGit,
|
|
15509
15647
|
runGitRaw,
|
|
15648
|
+
runGitRetryingContention,
|
|
15649
|
+
addWorktree,
|
|
15650
|
+
createBranch,
|
|
15651
|
+
deleteCreatedBranch,
|
|
15652
|
+
removeCreatedWorktree,
|
|
15510
15653
|
isDirtyWorktree,
|
|
15511
15654
|
readWorktreeHealth,
|
|
15512
15655
|
isBranchMergedInto,
|
|
@@ -15650,7 +15793,8 @@ async function runBackCommand(options) {
|
|
|
15650
15793
|
{
|
|
15651
15794
|
branch: target.branch ?? void 0,
|
|
15652
15795
|
path: target.path,
|
|
15653
|
-
repo: basename6(repository.repoRoot)
|
|
15796
|
+
repo: basename6(repository.repoRoot),
|
|
15797
|
+
slot: await runtime.slots.getWorktreeSlot(target.path)
|
|
15654
15798
|
},
|
|
15655
15799
|
options.stderr
|
|
15656
15800
|
);
|
|
@@ -15690,6 +15834,10 @@ function formatAge(timestamp) {
|
|
|
15690
15834
|
return `${days}d ago`;
|
|
15691
15835
|
}
|
|
15692
15836
|
|
|
15837
|
+
// src/cli/commands/clean.ts
|
|
15838
|
+
import { realpath as realpath5 } from "node:fs/promises";
|
|
15839
|
+
import { basename as basename8, dirname as dirname7, join as join12, resolve as resolve11 } from "node:path";
|
|
15840
|
+
|
|
15693
15841
|
// node_modules/.pnpm/@clack+core@0.5.0/node_modules/@clack/core/dist/index.mjs
|
|
15694
15842
|
var import_sisteransi = __toESM(require_src(), 1);
|
|
15695
15843
|
var import_picocolors = __toESM(require_picocolors(), 1);
|
|
@@ -16480,6 +16628,121 @@ function throwIfAborted2(signal) {
|
|
|
16480
16628
|
if (signal?.aborted) throw new Error("Operation cancelled");
|
|
16481
16629
|
}
|
|
16482
16630
|
|
|
16631
|
+
// src/domain/worktree/path-filter.ts
|
|
16632
|
+
function createWorktreePathFilter(patterns) {
|
|
16633
|
+
const matchers = patterns.map(createPathMatcher);
|
|
16634
|
+
if (matchers.length === 0) return () => true;
|
|
16635
|
+
return (worktreePath) => matchers.some((matches) => matches(worktreePath));
|
|
16636
|
+
}
|
|
16637
|
+
function findGlobIndex(pattern) {
|
|
16638
|
+
for (let index = 0; index < pattern.length; index += 1) {
|
|
16639
|
+
const character = pattern[index];
|
|
16640
|
+
if (character === "\\") index += 1;
|
|
16641
|
+
else if (character === "*" || character === "?") return index;
|
|
16642
|
+
}
|
|
16643
|
+
return -1;
|
|
16644
|
+
}
|
|
16645
|
+
function escapePathPattern(path9) {
|
|
16646
|
+
return path9.replace(/[\\*?]/g, "\\$&");
|
|
16647
|
+
}
|
|
16648
|
+
function unescapePathPattern(pattern) {
|
|
16649
|
+
return pattern.replace(/\\(.)/g, "$1");
|
|
16650
|
+
}
|
|
16651
|
+
function createPathMatcher(pattern) {
|
|
16652
|
+
if (findGlobIndex(pattern) !== -1) {
|
|
16653
|
+
const regExp = globToRegExp(pattern);
|
|
16654
|
+
return (path9) => regExp.test(path9);
|
|
16655
|
+
}
|
|
16656
|
+
const literal = unescapePathPattern(pattern);
|
|
16657
|
+
const prefix = literal === "/" ? "/" : `${literal}/`;
|
|
16658
|
+
return (path9) => path9 === literal || path9.startsWith(prefix);
|
|
16659
|
+
}
|
|
16660
|
+
function globToRegExp(pattern) {
|
|
16661
|
+
let source = "";
|
|
16662
|
+
for (let index = 0; index < pattern.length; index += 1) {
|
|
16663
|
+
let character = pattern[index];
|
|
16664
|
+
if (character === "*") {
|
|
16665
|
+
if (pattern[index + 1] === "*") {
|
|
16666
|
+
index += 1;
|
|
16667
|
+
if (pattern[index + 1] === "/") {
|
|
16668
|
+
index += 1;
|
|
16669
|
+
source += "(?:.*/)?";
|
|
16670
|
+
} else {
|
|
16671
|
+
source += ".*";
|
|
16672
|
+
}
|
|
16673
|
+
} else {
|
|
16674
|
+
source += "[^/]*";
|
|
16675
|
+
}
|
|
16676
|
+
} else if (character === "?") {
|
|
16677
|
+
source += "[^/]";
|
|
16678
|
+
} else {
|
|
16679
|
+
if (character === "\\" && index + 1 < pattern.length) {
|
|
16680
|
+
index += 1;
|
|
16681
|
+
character = pattern[index];
|
|
16682
|
+
}
|
|
16683
|
+
source += character.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
16684
|
+
}
|
|
16685
|
+
}
|
|
16686
|
+
return new RegExp(`^${source}$`);
|
|
16687
|
+
}
|
|
16688
|
+
|
|
16689
|
+
// src/domain/worktree/policy.ts
|
|
16690
|
+
import { homedir as homedir5 } from "node:os";
|
|
16691
|
+
import { basename as basename7, dirname as dirname6, join as join10 } from "node:path";
|
|
16692
|
+
function resolveWorktreePath(repoRoot, branch, basePath) {
|
|
16693
|
+
const segments = branch.split("/").filter(Boolean);
|
|
16694
|
+
if (segments.length === 0) {
|
|
16695
|
+
throw new Error("Branch name must not be empty.");
|
|
16696
|
+
}
|
|
16697
|
+
if (segments.some((segment) => segment === "." || segment === "..")) {
|
|
16698
|
+
throw new Error(
|
|
16699
|
+
`Branch name '${branch}' contains an invalid path segment.`
|
|
16700
|
+
);
|
|
16701
|
+
}
|
|
16702
|
+
const base = basePath ? expandTildeInPath(basePath) : join10(dirname6(repoRoot), "worktrees", basename7(repoRoot));
|
|
16703
|
+
return join10(base, ...segments);
|
|
16704
|
+
}
|
|
16705
|
+
function validateBranchName(name) {
|
|
16706
|
+
if (name.length === 0) {
|
|
16707
|
+
return "Branch name must not be empty.";
|
|
16708
|
+
}
|
|
16709
|
+
if (/[\x00-\x1f\x7f ~^:?*[\\\s]/.test(name)) {
|
|
16710
|
+
return `Branch name '${name}' contains an invalid character.`;
|
|
16711
|
+
}
|
|
16712
|
+
if (name.startsWith("-")) {
|
|
16713
|
+
return `Branch name '${name}' must not start with a dash.`;
|
|
16714
|
+
}
|
|
16715
|
+
if (name.startsWith("/") || name.endsWith("/") || name.includes("//")) {
|
|
16716
|
+
return `Branch name '${name}' has invalid slash placement.`;
|
|
16717
|
+
}
|
|
16718
|
+
if (name.includes("..")) {
|
|
16719
|
+
return `Branch name '${name}' must not contain '..'.`;
|
|
16720
|
+
}
|
|
16721
|
+
if (name.endsWith(".")) {
|
|
16722
|
+
return `Branch name '${name}' must not end with '.'.`;
|
|
16723
|
+
}
|
|
16724
|
+
if (name.includes("@{")) {
|
|
16725
|
+
return `Branch name '${name}' must not contain '@{'.`;
|
|
16726
|
+
}
|
|
16727
|
+
if (name === "@") {
|
|
16728
|
+
return "Branch name cannot be '@'.";
|
|
16729
|
+
}
|
|
16730
|
+
for (const segment of name.split("/")) {
|
|
16731
|
+
if (segment.startsWith(".")) {
|
|
16732
|
+
return `Branch name '${name}' contains a path component starting with '.'.`;
|
|
16733
|
+
}
|
|
16734
|
+
if (segment.endsWith(".lock")) {
|
|
16735
|
+
return `Branch name '${name}' contains a path component ending with '.lock'.`;
|
|
16736
|
+
}
|
|
16737
|
+
}
|
|
16738
|
+
return null;
|
|
16739
|
+
}
|
|
16740
|
+
function expandTildeInPath(path9) {
|
|
16741
|
+
if (path9 === "~") return homedir5();
|
|
16742
|
+
if (path9.startsWith("~/")) return join10(homedir5(), path9.slice(2));
|
|
16743
|
+
return path9;
|
|
16744
|
+
}
|
|
16745
|
+
|
|
16483
16746
|
// src/presentation/worktree/picker.ts
|
|
16484
16747
|
import { env as env4, platform as platform2, stdin, stdout } from "node:process";
|
|
16485
16748
|
|
|
@@ -17475,35 +17738,22 @@ async function defaultConfirmForceDeleteBranch(branch) {
|
|
|
17475
17738
|
|
|
17476
17739
|
// src/cli/runtime/headless.ts
|
|
17477
17740
|
function isHeadless() {
|
|
17478
|
-
return process.env.GJI_NO_TUI === "1";
|
|
17741
|
+
return process.env.GJI_NO_TUI === "1" || process.stdin.isTTY !== true || process.stdout.isTTY !== true;
|
|
17479
17742
|
}
|
|
17480
17743
|
|
|
17481
17744
|
// src/cli/commands/undo.ts
|
|
17482
|
-
import {
|
|
17483
|
-
|
|
17484
|
-
|
|
17485
|
-
open as open3,
|
|
17486
|
-
readFile as readFile7,
|
|
17487
|
-
rename as rename2,
|
|
17488
|
-
stat,
|
|
17489
|
-
unlink as unlink2,
|
|
17490
|
-
writeFile as writeFile7
|
|
17491
|
-
} from "node:fs/promises";
|
|
17492
|
-
import { homedir as homedir5 } from "node:os";
|
|
17493
|
-
import { dirname as dirname8, join as join10, resolve as resolve10 } from "node:path";
|
|
17745
|
+
import { access as access4, readFile as readFile7 } from "node:fs/promises";
|
|
17746
|
+
import { homedir as homedir6 } from "node:os";
|
|
17747
|
+
import { join as join11, resolve as resolve10 } from "node:path";
|
|
17494
17748
|
var MAX_UNDO_RECORDS = 20;
|
|
17495
|
-
|
|
17496
|
-
var UNDO_LOCK_RETRY_DELAY_MS = 5;
|
|
17497
|
-
var UNDO_LOCK_WAIT_TIMEOUT_MS = UNDO_LOCK_STALE_AFTER_MS + UNDO_LOCK_RETRY_DELAY_MS;
|
|
17498
|
-
var undoLogQueues = /* @__PURE__ */ new Map();
|
|
17499
|
-
function undoLogPath(home = homedir5(), globalConfigDirectory = defaultCliDependencies.configStore.GLOBAL_CONFIG_DIRECTORY) {
|
|
17749
|
+
function undoLogPath(home = homedir6(), globalConfigDirectory = defaultCliDependencies.configStore.GLOBAL_CONFIG_DIRECTORY) {
|
|
17500
17750
|
const configured = process.env.GJI_CONFIG_DIR;
|
|
17501
|
-
return
|
|
17502
|
-
configured ? resolve10(configured) :
|
|
17751
|
+
return join11(
|
|
17752
|
+
configured ? resolve10(configured) : join11(home, globalConfigDirectory),
|
|
17503
17753
|
"undo-log.json"
|
|
17504
17754
|
);
|
|
17505
17755
|
}
|
|
17506
|
-
async function loadUndoRecords(home =
|
|
17756
|
+
async function loadUndoRecords(home = homedir6(), globalConfigDirectory = defaultCliDependencies.configStore.GLOBAL_CONFIG_DIRECTORY) {
|
|
17507
17757
|
try {
|
|
17508
17758
|
const parsed = JSON.parse(
|
|
17509
17759
|
await readFile7(undoLogPath(home, globalConfigDirectory), "utf8")
|
|
@@ -17513,7 +17763,7 @@ async function loadUndoRecords(home = homedir5(), globalConfigDirectory = defaul
|
|
|
17513
17763
|
return [];
|
|
17514
17764
|
}
|
|
17515
17765
|
}
|
|
17516
|
-
async function recordUndoOperation(op, repoRoot, worktrees, home =
|
|
17766
|
+
async function recordUndoOperation(op, repoRoot, worktrees, home = homedir6(), runtime = defaultCliDependencies) {
|
|
17517
17767
|
const globalConfigDirectory = runtime.configStore.GLOBAL_CONFIG_DIRECTORY;
|
|
17518
17768
|
const { isDirtyWorktree: isDirtyWorktree2, runGit: runGit2 } = runtime.git;
|
|
17519
17769
|
const entries = [];
|
|
@@ -17549,41 +17799,33 @@ async function recordUndoOperation(op, repoRoot, worktrees, home = homedir5(), r
|
|
|
17549
17799
|
timestamp,
|
|
17550
17800
|
entries
|
|
17551
17801
|
};
|
|
17552
|
-
return
|
|
17553
|
-
home,
|
|
17554
|
-
|
|
17555
|
-
|
|
17556
|
-
|
|
17557
|
-
|
|
17558
|
-
|
|
17559
|
-
|
|
17560
|
-
|
|
17561
|
-
return record;
|
|
17562
|
-
},
|
|
17563
|
-
globalConfigDirectory
|
|
17564
|
-
);
|
|
17802
|
+
return withFileLock(undoLogPath(home, globalConfigDirectory), async () => {
|
|
17803
|
+
const existing = await loadUndoRecords(home, globalConfigDirectory);
|
|
17804
|
+
await writeUndoRecords(
|
|
17805
|
+
home,
|
|
17806
|
+
[record, ...existing].slice(0, MAX_UNDO_RECORDS),
|
|
17807
|
+
globalConfigDirectory
|
|
17808
|
+
);
|
|
17809
|
+
return record;
|
|
17810
|
+
});
|
|
17565
17811
|
}
|
|
17566
|
-
async function finalizeUndoOperation(record, removedWorktrees, home =
|
|
17812
|
+
async function finalizeUndoOperation(record, removedWorktrees, home = homedir6(), globalConfigDirectory = defaultCliDependencies.configStore.GLOBAL_CONFIG_DIRECTORY) {
|
|
17567
17813
|
const removedPaths = new Set(
|
|
17568
17814
|
removedWorktrees.map((worktree) => worktree.path)
|
|
17569
17815
|
);
|
|
17570
|
-
await
|
|
17571
|
-
home,
|
|
17572
|
-
|
|
17573
|
-
|
|
17574
|
-
const
|
|
17575
|
-
|
|
17576
|
-
|
|
17577
|
-
|
|
17578
|
-
|
|
17579
|
-
|
|
17580
|
-
|
|
17581
|
-
await writeUndoRecords(home, remaining, globalConfigDirectory);
|
|
17582
|
-
},
|
|
17583
|
-
globalConfigDirectory
|
|
17584
|
-
);
|
|
17816
|
+
await withFileLock(undoLogPath(home, globalConfigDirectory), async () => {
|
|
17817
|
+
const records = await loadUndoRecords(home, globalConfigDirectory);
|
|
17818
|
+
const remaining = records.flatMap((candidate) => {
|
|
17819
|
+
if (candidate.id !== record.id) return [candidate];
|
|
17820
|
+
const entries = candidate.entries.filter(
|
|
17821
|
+
(entry) => removedPaths.has(entry.path)
|
|
17822
|
+
);
|
|
17823
|
+
return entries.length === 0 ? [] : [{ ...candidate, entries }];
|
|
17824
|
+
});
|
|
17825
|
+
await writeUndoRecords(home, remaining, globalConfigDirectory);
|
|
17826
|
+
});
|
|
17585
17827
|
}
|
|
17586
|
-
async function restoreUndoRecord(record, home =
|
|
17828
|
+
async function restoreUndoRecord(record, home = homedir6(), runtime = defaultCliDependencies) {
|
|
17587
17829
|
const globalConfigDirectory = runtime.configStore.GLOBAL_CONFIG_DIRECTORY;
|
|
17588
17830
|
const restored = [];
|
|
17589
17831
|
const failed = [];
|
|
@@ -17605,19 +17847,15 @@ async function restoreUndoRecord(record, home = homedir5(), runtime = defaultCli
|
|
|
17605
17847
|
unresolved.push(entry);
|
|
17606
17848
|
}
|
|
17607
17849
|
}
|
|
17608
|
-
await
|
|
17609
|
-
home,
|
|
17610
|
-
|
|
17611
|
-
|
|
17612
|
-
|
|
17613
|
-
|
|
17614
|
-
|
|
17615
|
-
|
|
17616
|
-
|
|
17617
|
-
await writeUndoRecords(home, remaining, globalConfigDirectory);
|
|
17618
|
-
},
|
|
17619
|
-
globalConfigDirectory
|
|
17620
|
-
);
|
|
17850
|
+
await withFileLock(undoLogPath(home, globalConfigDirectory), async () => {
|
|
17851
|
+
const records = await loadUndoRecords(home, globalConfigDirectory);
|
|
17852
|
+
const remaining = records.flatMap((candidate) => {
|
|
17853
|
+
if (candidate.id !== record.id) return [candidate];
|
|
17854
|
+
if (unresolved.length === 0) return [];
|
|
17855
|
+
return [{ ...candidate, entries: unresolved }];
|
|
17856
|
+
});
|
|
17857
|
+
await writeUndoRecords(home, remaining, globalConfigDirectory);
|
|
17858
|
+
});
|
|
17621
17859
|
return { restored, failed };
|
|
17622
17860
|
}
|
|
17623
17861
|
async function runUndoCommand(options) {
|
|
@@ -17628,7 +17866,7 @@ async function runUndoCommand(options) {
|
|
|
17628
17866
|
options.stderr(
|
|
17629
17867
|
"Warning: undo journal is invalid; starting with an empty journal\n"
|
|
17630
17868
|
);
|
|
17631
|
-
const records = await loadUndoRecords(
|
|
17869
|
+
const records = await loadUndoRecords(homedir6(), globalConfigDirectory);
|
|
17632
17870
|
if (options.list) {
|
|
17633
17871
|
if (options.json) options.stdout(`${JSON.stringify(records, null, 2)}
|
|
17634
17872
|
`);
|
|
@@ -17675,7 +17913,7 @@ async function runUndoCommand(options) {
|
|
|
17675
17913
|
}
|
|
17676
17914
|
if (!record)
|
|
17677
17915
|
return emitUndoError(options, `undo record not found: ${options.id}`);
|
|
17678
|
-
const result = await restoreUndoRecord(record,
|
|
17916
|
+
const result = await restoreUndoRecord(record, homedir6(), runtime);
|
|
17679
17917
|
if (options.json)
|
|
17680
17918
|
options.stdout(
|
|
17681
17919
|
`${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)}
|
|
@@ -17703,7 +17941,7 @@ async function runUndoCommand(options) {
|
|
|
17703
17941
|
async function hasMalformedUndoLog(globalConfigDirectory = defaultCliDependencies.configStore.GLOBAL_CONFIG_DIRECTORY) {
|
|
17704
17942
|
try {
|
|
17705
17943
|
const parsed = JSON.parse(
|
|
17706
|
-
await readFile7(undoLogPath(
|
|
17944
|
+
await readFile7(undoLogPath(homedir6(), globalConfigDirectory), "utf8")
|
|
17707
17945
|
);
|
|
17708
17946
|
return !Array.isArray(parsed) || parsed.some((entry) => !isUndoRecord(entry));
|
|
17709
17947
|
} catch (error) {
|
|
@@ -17793,69 +18031,8 @@ async function assertCommitExists(repoRoot, headSha, runGit2) {
|
|
|
17793
18031
|
throw new Error("commit no longer exists (gc'd) \u2014 cannot restore");
|
|
17794
18032
|
}
|
|
17795
18033
|
}
|
|
17796
|
-
async function enqueueUndoLogWrite(home, operation, globalConfigDirectory = defaultCliDependencies.configStore.GLOBAL_CONFIG_DIRECTORY) {
|
|
17797
|
-
const path9 = undoLogPath(home, globalConfigDirectory);
|
|
17798
|
-
await mkdir6(dirname8(path9), { recursive: true });
|
|
17799
|
-
const previous = undoLogQueues.get(path9) ?? Promise.resolve();
|
|
17800
|
-
const runLocked = async () => {
|
|
17801
|
-
const release = await acquireUndoLogLock(path9);
|
|
17802
|
-
try {
|
|
17803
|
-
return await operation();
|
|
17804
|
-
} finally {
|
|
17805
|
-
await release();
|
|
17806
|
-
}
|
|
17807
|
-
};
|
|
17808
|
-
const current = previous.then(runLocked, runLocked);
|
|
17809
|
-
const settled = current.then(
|
|
17810
|
-
() => void 0,
|
|
17811
|
-
() => void 0
|
|
17812
|
-
);
|
|
17813
|
-
undoLogQueues.set(path9, settled);
|
|
17814
|
-
try {
|
|
17815
|
-
return await current;
|
|
17816
|
-
} finally {
|
|
17817
|
-
if (undoLogQueues.get(path9) === settled) undoLogQueues.delete(path9);
|
|
17818
|
-
}
|
|
17819
|
-
}
|
|
17820
|
-
async function acquireUndoLogLock(path9) {
|
|
17821
|
-
const lockPath = `${path9}.lock`;
|
|
17822
|
-
const deadline = Date.now() + UNDO_LOCK_WAIT_TIMEOUT_MS;
|
|
17823
|
-
while (Date.now() < deadline) {
|
|
17824
|
-
try {
|
|
17825
|
-
const handle = await open3(lockPath, "wx");
|
|
17826
|
-
return async () => {
|
|
17827
|
-
await handle.close();
|
|
17828
|
-
await unlink2(lockPath).catch(() => void 0);
|
|
17829
|
-
};
|
|
17830
|
-
} catch (error) {
|
|
17831
|
-
if (!(error instanceof Error && "code" in error && error.code === "EEXIST"))
|
|
17832
|
-
throw error;
|
|
17833
|
-
const lockStat = await stat(lockPath).catch(() => null);
|
|
17834
|
-
if (lockStat && Date.now() - lockStat.mtimeMs > UNDO_LOCK_STALE_AFTER_MS)
|
|
17835
|
-
await unlink2(lockPath).catch(() => void 0);
|
|
17836
|
-
const remainingMs = deadline - Date.now();
|
|
17837
|
-
if (remainingMs > 0)
|
|
17838
|
-
await new Promise(
|
|
17839
|
-
(resolve12) => setTimeout(resolve12, Math.min(UNDO_LOCK_RETRY_DELAY_MS, remainingMs))
|
|
17840
|
-
);
|
|
17841
|
-
}
|
|
17842
|
-
}
|
|
17843
|
-
throw new Error("timed out waiting for the undo journal lock");
|
|
17844
|
-
}
|
|
17845
18034
|
async function writeUndoRecords(home, records, globalConfigDirectory = defaultCliDependencies.configStore.GLOBAL_CONFIG_DIRECTORY) {
|
|
17846
|
-
|
|
17847
|
-
await mkdir6(dirname8(path9), { recursive: true });
|
|
17848
|
-
const temporaryPath = `${path9}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
17849
|
-
try {
|
|
17850
|
-
await writeFile7(temporaryPath, `${JSON.stringify(records, null, 2)}
|
|
17851
|
-
`, {
|
|
17852
|
-
encoding: "utf8",
|
|
17853
|
-
flag: "wx"
|
|
17854
|
-
});
|
|
17855
|
-
await rename2(temporaryPath, path9);
|
|
17856
|
-
} finally {
|
|
17857
|
-
await unlink2(temporaryPath).catch(() => void 0);
|
|
17858
|
-
}
|
|
18035
|
+
await writeJsonAtomic(undoLogPath(home, globalConfigDirectory), records);
|
|
17859
18036
|
}
|
|
17860
18037
|
function isUndoRecord(value) {
|
|
17861
18038
|
if (!value || typeof value !== "object") return false;
|
|
@@ -17910,7 +18087,8 @@ function createCleanCommand(dependencies = {}) {
|
|
|
17910
18087
|
runGit: runGit2
|
|
17911
18088
|
} = runtime.git;
|
|
17912
18089
|
const { loadEffectiveConfig: loadEffectiveConfig2 } = runtime.config;
|
|
17913
|
-
const { releaseWorktreeSlot: releaseWorktreeSlot2 } = runtime.slots;
|
|
18090
|
+
const { getWorktreeSlot: getWorktreeSlot2, releaseWorktreeSlot: releaseWorktreeSlot2 } = runtime.slots;
|
|
18091
|
+
const { extractHooks: extractHooks2, runHook: runHook2 } = runtime.hooks;
|
|
17914
18092
|
const sourceDependencies = {
|
|
17915
18093
|
...runtime.repositoryContext,
|
|
17916
18094
|
...runtime.repositoryRegistry,
|
|
@@ -17936,9 +18114,30 @@ function createCleanCommand(dependencies = {}) {
|
|
|
17936
18114
|
options.cwd,
|
|
17937
18115
|
sourceDependencies
|
|
17938
18116
|
);
|
|
17939
|
-
const
|
|
17940
|
-
|
|
17941
|
-
|
|
18117
|
+
const pathPatterns = await canonicalizePathPatterns(
|
|
18118
|
+
options.cwd,
|
|
18119
|
+
options.paths ?? []
|
|
18120
|
+
);
|
|
18121
|
+
const matchesPathFilter = createWorktreePathFilter(pathPatterns);
|
|
18122
|
+
const searchAllRepositories = pathPatterns.length > 0;
|
|
18123
|
+
let allSourceCount = 0;
|
|
18124
|
+
const emitNoCandidates = (sourceCount) => {
|
|
18125
|
+
if (pathPatterns.length > 0 && sourceCount === 0) {
|
|
18126
|
+
emitError(options, "No linked worktrees match --path");
|
|
18127
|
+
return 1;
|
|
18128
|
+
}
|
|
18129
|
+
if (options.stale) {
|
|
18130
|
+
emitNoStaleCandidates(options);
|
|
18131
|
+
return 0;
|
|
18132
|
+
}
|
|
18133
|
+
emitError(options, "No linked worktrees to clean");
|
|
18134
|
+
return 1;
|
|
18135
|
+
};
|
|
18136
|
+
const currentSources = linkedWorktrees.filter(
|
|
18137
|
+
(worktree) => worktree.path !== repository.currentRoot && matchesPathFilter(worktree.path)
|
|
18138
|
+
).map((worktree) => ({
|
|
18139
|
+
repoName: repository.repoName,
|
|
18140
|
+
repoRoot: repository.repoRoot,
|
|
17942
18141
|
worktree
|
|
17943
18142
|
}));
|
|
17944
18143
|
const cleanupDependencies = {
|
|
@@ -17977,8 +18176,9 @@ function createCleanCommand(dependencies = {}) {
|
|
|
17977
18176
|
...currentSources,
|
|
17978
18177
|
...registered.sources
|
|
17979
18178
|
]).filter(
|
|
17980
|
-
(source) => source.repoRoot !== void 0 && source.worktree.path !== source.repoRoot && source.worktree.path !== repository.currentRoot
|
|
18179
|
+
(source) => source.repoRoot !== void 0 && source.worktree.path !== source.repoRoot && source.worktree.path !== repository.currentRoot && matchesPathFilter(source.worktree.path)
|
|
17981
18180
|
);
|
|
18181
|
+
allSourceCount = sources.length;
|
|
17982
18182
|
const otherRepositorySources = sources.filter(
|
|
17983
18183
|
(source) => source.repoRoot !== repository.repoRoot
|
|
17984
18184
|
);
|
|
@@ -18012,37 +18212,27 @@ function createCleanCommand(dependencies = {}) {
|
|
|
18012
18212
|
};
|
|
18013
18213
|
}
|
|
18014
18214
|
};
|
|
18015
|
-
if (currentCandidates.length === 0
|
|
18215
|
+
if (searchAllRepositories || interactiveSelection && currentCandidates.length === 0) {
|
|
18016
18216
|
const loadedAllCandidates = await loadAllCandidates();
|
|
18017
18217
|
if (loadedAllCandidates.length === 0) {
|
|
18018
|
-
|
|
18019
|
-
emitNoStaleCandidates(options);
|
|
18020
|
-
return 0;
|
|
18021
|
-
}
|
|
18022
|
-
emitError(options, "No linked worktrees to clean");
|
|
18023
|
-
return 1;
|
|
18218
|
+
return emitNoCandidates(allSourceCount);
|
|
18024
18219
|
}
|
|
18025
18220
|
currentScope = false;
|
|
18026
18221
|
activeCandidates = loadedAllCandidates;
|
|
18027
18222
|
scope.label = "all repositories";
|
|
18028
18223
|
scope.toggleLabel = "current repository";
|
|
18029
|
-
}
|
|
18030
|
-
|
|
18031
|
-
if (options.stale) {
|
|
18032
|
-
emitNoStaleCandidates(options);
|
|
18033
|
-
return 0;
|
|
18034
|
-
}
|
|
18035
|
-
emitError(options, "No linked worktrees to clean");
|
|
18036
|
-
return 1;
|
|
18224
|
+
} else if (currentCandidates.length === 0) {
|
|
18225
|
+
return emitNoCandidates(currentSources.length);
|
|
18037
18226
|
}
|
|
18038
18227
|
if (!options.dryRun && !options.force && (options.json || isHeadless())) {
|
|
18039
18228
|
const message = "--force is required";
|
|
18040
18229
|
if (options.json) {
|
|
18041
18230
|
emitError(options, message);
|
|
18042
18231
|
} else {
|
|
18232
|
+
options.stderr(`gji clean: ${message} in non-interactive mode
|
|
18233
|
+
`);
|
|
18043
18234
|
options.stderr(
|
|
18044
|
-
|
|
18045
|
-
`
|
|
18235
|
+
"Hint: --force removes every candidate; use --dry-run to preview them first\n"
|
|
18046
18236
|
);
|
|
18047
18237
|
}
|
|
18048
18238
|
return 1;
|
|
@@ -18147,6 +18337,19 @@ function createCleanCommand(dependencies = {}) {
|
|
|
18147
18337
|
);
|
|
18148
18338
|
continue;
|
|
18149
18339
|
}
|
|
18340
|
+
const hooks = extractHooks2(await loadEffectiveConfig2(repoRoot));
|
|
18341
|
+
await runHook2(
|
|
18342
|
+
hooks["before-remove"],
|
|
18343
|
+
worktree.path,
|
|
18344
|
+
{
|
|
18345
|
+
branch: worktree.branch ?? void 0,
|
|
18346
|
+
path: worktree.path,
|
|
18347
|
+
repo: basename8(repoRoot),
|
|
18348
|
+
slot: await getWorktreeSlot2(worktree.path)
|
|
18349
|
+
},
|
|
18350
|
+
options.stderr,
|
|
18351
|
+
options.json ? () => void 0 : options.stdout
|
|
18352
|
+
);
|
|
18150
18353
|
try {
|
|
18151
18354
|
await removeWorktree2(repoRoot, worktree.path);
|
|
18152
18355
|
} catch (error) {
|
|
@@ -18260,6 +18463,31 @@ function createCleanCommand(dependencies = {}) {
|
|
|
18260
18463
|
};
|
|
18261
18464
|
}
|
|
18262
18465
|
var runCleanCommand = createCleanCommand();
|
|
18466
|
+
async function canonicalizePathPatterns(cwd, patterns) {
|
|
18467
|
+
return Promise.all(
|
|
18468
|
+
patterns.map(
|
|
18469
|
+
(pattern) => canonicalizeLiteralPrefix(resolve11(cwd, expandTildeInPath(pattern)))
|
|
18470
|
+
)
|
|
18471
|
+
);
|
|
18472
|
+
}
|
|
18473
|
+
async function canonicalizeLiteralPrefix(pattern) {
|
|
18474
|
+
const globIndex = findGlobIndex(pattern);
|
|
18475
|
+
let prefix = globIndex === -1 ? pattern : dirname7(`${pattern.slice(0, globIndex)}x`);
|
|
18476
|
+
let suffix = pattern.slice(prefix.length);
|
|
18477
|
+
for (; ; ) {
|
|
18478
|
+
try {
|
|
18479
|
+
const canonicalPrefix = escapePathPattern(
|
|
18480
|
+
await realpath5(unescapePathPattern(prefix))
|
|
18481
|
+
);
|
|
18482
|
+
return `${canonicalPrefix}${suffix}`.replace(/^\/\//, "/");
|
|
18483
|
+
} catch {
|
|
18484
|
+
const parent = dirname7(prefix);
|
|
18485
|
+
if (parent === prefix) return pattern;
|
|
18486
|
+
suffix = join12("/", basename8(prefix), suffix);
|
|
18487
|
+
prefix = parent;
|
|
18488
|
+
}
|
|
18489
|
+
}
|
|
18490
|
+
}
|
|
18263
18491
|
async function resolveCleanupCandidates(options, dependencies) {
|
|
18264
18492
|
const { sources, stale, stderr, signal } = options;
|
|
18265
18493
|
const grouped = /* @__PURE__ */ new Map();
|
|
@@ -18572,23 +18800,20 @@ var TOP_LEVEL_COMMANDS = [
|
|
|
18572
18800
|
},
|
|
18573
18801
|
{ name: "ls", description: "list active worktrees" },
|
|
18574
18802
|
{ name: "clean", description: "interactively prune linked worktrees" },
|
|
18575
|
-
{
|
|
18576
|
-
name: "remove",
|
|
18577
|
-
description: "deprecated: use done for one worktree or clean for bulk cleanup"
|
|
18578
|
-
},
|
|
18579
|
-
{ name: "rm", description: "deprecated alias of remove" },
|
|
18580
18803
|
{
|
|
18581
18804
|
name: "run-hook",
|
|
18582
18805
|
description: "run a named hook in the current worktree"
|
|
18583
18806
|
},
|
|
18584
|
-
{
|
|
18585
|
-
name: "warp",
|
|
18586
|
-
description: "deprecated: use go (press Tab for all known repos)"
|
|
18587
|
-
},
|
|
18588
18807
|
{ name: "config", description: "manage global config defaults" }
|
|
18589
18808
|
];
|
|
18590
18809
|
var SHELL_NAMES = ["bash", "fish", "zsh"];
|
|
18591
|
-
var HOOK_NAMES = [
|
|
18810
|
+
var HOOK_NAMES = [
|
|
18811
|
+
"after-branch-create",
|
|
18812
|
+
"after-create",
|
|
18813
|
+
"after-enter",
|
|
18814
|
+
"after-switch",
|
|
18815
|
+
"before-remove"
|
|
18816
|
+
];
|
|
18592
18817
|
var CONFIG_KEYS = Array.from(KNOWN_GLOBAL_CONFIG_KEYS);
|
|
18593
18818
|
function renderShellCompletion(shell) {
|
|
18594
18819
|
switch (shell) {
|
|
@@ -18761,17 +18986,15 @@ _gji_completion() {
|
|
|
18761
18986
|
COMPREPLY=( $(compgen -W "--compact --json --help" -- "$cur") )
|
|
18762
18987
|
;;
|
|
18763
18988
|
clean)
|
|
18764
|
-
|
|
18765
|
-
|
|
18766
|
-
|
|
18767
|
-
|
|
18989
|
+
if [ "\${COMP_WORDS[COMP_CWORD-1]}" = "--path" ]; then
|
|
18990
|
+
COMPREPLY=( $(compgen -d -- "$cur") )
|
|
18991
|
+
else
|
|
18992
|
+
COMPREPLY=( $(compgen -W "-f --force --stale --path --dry-run --json --help" -- "$cur") )
|
|
18993
|
+
fi
|
|
18768
18994
|
;;
|
|
18769
18995
|
run-hook)
|
|
18770
18996
|
COMPREPLY=( $(compgen -W "${hooks} --help" -- "$cur") )
|
|
18771
18997
|
;;
|
|
18772
|
-
warp)
|
|
18773
|
-
COMPREPLY=( $(compgen -W "--print --json --help" -- "$cur") )
|
|
18774
|
-
;;
|
|
18775
18998
|
config)
|
|
18776
18999
|
if [ "$COMP_CWORD" -eq 2 ]; then
|
|
18777
19000
|
COMPREPLY=( $(compgen -W "get set unset" -- "$cur") )
|
|
@@ -19004,21 +19227,14 @@ complete -c gji -n '__fish_seen_subcommand_from ls' -l json -d 'print active wor
|
|
|
19004
19227
|
|
|
19005
19228
|
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'
|
|
19006
19229
|
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'
|
|
19230
|
+
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)'
|
|
19007
19231
|
complete -c gji -n '__fish_seen_subcommand_from clean' -l dry-run -d 'show what would be deleted without removing anything'
|
|
19008
19232
|
complete -c gji -n '__fish_seen_subcommand_from clean' -l json -d 'emit JSON on success or error instead of human-readable output'
|
|
19009
19233
|
|
|
19010
|
-
complete -c gji -n '__fish_seen_subcommand_from remove rm' -s f -l force -d 'bypass prompts, force-remove a dirty worktree, and force-delete an unmerged branch'
|
|
19011
|
-
complete -c gji -n '__fish_seen_subcommand_from remove rm' -l dry-run -d 'show what would be deleted without removing anything'
|
|
19012
|
-
complete -c gji -n '__fish_seen_subcommand_from remove rm' -l json -d 'emit JSON on success or error instead of human-readable output'
|
|
19013
|
-
complete -c gji -n '__fish_seen_subcommand_from remove rm' -a '(__gji_worktree_branches)' -d 'worktree branch'
|
|
19014
|
-
|
|
19015
19234
|
${hookLines}
|
|
19016
19235
|
|
|
19017
19236
|
complete -c gji -n '__fish_use_subcommand' -l json -d 'emit the repository hub as JSON'
|
|
19018
19237
|
|
|
19019
|
-
complete -c gji -n '__fish_seen_subcommand_from warp' -l print -d 'print the resolved worktree path without changing directory'
|
|
19020
|
-
complete -c gji -n '__fish_seen_subcommand_from warp' -l json -d 'emit JSON on success or error instead of human-readable output'
|
|
19021
|
-
|
|
19022
19238
|
complete -c gji -n '__fish_seen_subcommand_from config; and __gji_should_complete_config_action' -a 'get set unset' -d 'config action'
|
|
19023
19239
|
${configKeyLines}`;
|
|
19024
19240
|
}
|
|
@@ -19164,17 +19380,11 @@ case "\${words[2]}" in
|
|
|
19164
19380
|
_arguments '--compact[show only branch and path columns]' '--json[print active worktrees as JSON]'
|
|
19165
19381
|
;;
|
|
19166
19382
|
clean)
|
|
19167
|
-
_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]'
|
|
19168
|
-
;;
|
|
19169
|
-
remove|rm)
|
|
19170
|
-
_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'
|
|
19383
|
+
_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]'
|
|
19171
19384
|
;;
|
|
19172
19385
|
run-hook)
|
|
19173
19386
|
_arguments "2:hook:(${hooks})"
|
|
19174
19387
|
;;
|
|
19175
|
-
warp)
|
|
19176
|
-
_arguments '--print[print the resolved worktree path without changing directory]' '--json[emit JSON on success or error instead of human-readable output]' '2:branch: '
|
|
19177
|
-
;;
|
|
19178
19388
|
config)
|
|
19179
19389
|
if (( CURRENT == 3 )); then
|
|
19180
19390
|
_values 'config action' get set unset
|
|
@@ -19301,20 +19511,20 @@ function writeJson(stdout3, value) {
|
|
|
19301
19511
|
import { execFile as execFile8 } from "node:child_process";
|
|
19302
19512
|
import { constants as constants6 } from "node:fs";
|
|
19303
19513
|
import { access as access6, readFile as readFile8 } from "node:fs/promises";
|
|
19304
|
-
import { homedir as
|
|
19305
|
-
import { basename as
|
|
19514
|
+
import { homedir as homedir7 } from "node:os";
|
|
19515
|
+
import { basename as basename9, dirname as dirname8, join as join14 } from "node:path";
|
|
19306
19516
|
import { promisify as promisify9 } from "node:util";
|
|
19307
19517
|
|
|
19308
19518
|
// src/presentation/shell/setup.ts
|
|
19309
19519
|
import { constants as constants5 } from "node:fs";
|
|
19310
19520
|
import { access as access5 } from "node:fs/promises";
|
|
19311
|
-
import { delimiter, join as
|
|
19521
|
+
import { delimiter, join as join13 } from "node:path";
|
|
19312
19522
|
var SHELL_INTEGRATION_START_MARKER = "# >>> gji init >>>";
|
|
19313
19523
|
var SHELL_INTEGRATION_END_MARKER = "# <<< gji init <<<";
|
|
19314
19524
|
var ONBOARDING_SHELL_INTEGRATION_START_MARKER = "# >>> gji shell integration >>>";
|
|
19315
19525
|
var ONBOARDING_SHELL_INTEGRATION_END_MARKER = "# <<< gji shell integration <<<";
|
|
19316
19526
|
async function executableExists(command) {
|
|
19317
|
-
const candidates = command.includes("/") ? [command] : (process.env.PATH ?? "").split(delimiter).filter(Boolean).map((directory) =>
|
|
19527
|
+
const candidates = command.includes("/") ? [command] : (process.env.PATH ?? "").split(delimiter).filter(Boolean).map((directory) => join13(directory, command));
|
|
19318
19528
|
for (const path9 of candidates) {
|
|
19319
19529
|
try {
|
|
19320
19530
|
await access5(path9, constants5.X_OK);
|
|
@@ -19327,17 +19537,17 @@ async function executableExists(command) {
|
|
|
19327
19537
|
function resolveShellConfigPath(shell, home) {
|
|
19328
19538
|
switch (shell) {
|
|
19329
19539
|
case "bash":
|
|
19330
|
-
return
|
|
19540
|
+
return join13(home, ".bashrc");
|
|
19331
19541
|
case "fish":
|
|
19332
|
-
return
|
|
19542
|
+
return join13(home, ".config", "fish", "config.fish");
|
|
19333
19543
|
case "zsh":
|
|
19334
|
-
return
|
|
19544
|
+
return join13(home, ".zshrc");
|
|
19335
19545
|
}
|
|
19336
19546
|
}
|
|
19337
19547
|
function resolveCompletionPath(shell, home) {
|
|
19338
19548
|
switch (shell) {
|
|
19339
19549
|
case "bash":
|
|
19340
|
-
return
|
|
19550
|
+
return join13(
|
|
19341
19551
|
home,
|
|
19342
19552
|
".local",
|
|
19343
19553
|
"share",
|
|
@@ -19346,9 +19556,9 @@ function resolveCompletionPath(shell, home) {
|
|
|
19346
19556
|
"gji"
|
|
19347
19557
|
);
|
|
19348
19558
|
case "fish":
|
|
19349
|
-
return
|
|
19559
|
+
return join13(home, ".config", "fish", "completions", "gji.fish");
|
|
19350
19560
|
case "zsh":
|
|
19351
|
-
return
|
|
19561
|
+
return join13(home, ".zsh", "completions", "_gji");
|
|
19352
19562
|
}
|
|
19353
19563
|
}
|
|
19354
19564
|
function hasShellIntegration(config, shell) {
|
|
@@ -19380,7 +19590,7 @@ async function runDoctorCommand(options) {
|
|
|
19380
19590
|
}
|
|
19381
19591
|
return 1;
|
|
19382
19592
|
}
|
|
19383
|
-
const home = options.home ??
|
|
19593
|
+
const home = options.home ?? homedir7();
|
|
19384
19594
|
const shell = resolveSupportedShell(
|
|
19385
19595
|
void 0,
|
|
19386
19596
|
options.shell ?? process.env.SHELL
|
|
@@ -19445,7 +19655,7 @@ async function collectDoctorInspection(cwd, home, shell, runtime) {
|
|
|
19445
19655
|
KNOWN_GLOBAL_CONFIG_KEYS2
|
|
19446
19656
|
);
|
|
19447
19657
|
const localConfig = repository ? await inspectConfig(
|
|
19448
|
-
|
|
19658
|
+
join14(repository.repoRoot, CONFIG_FILE_NAME2),
|
|
19449
19659
|
"local",
|
|
19450
19660
|
KNOWN_CONFIG_KEYS2
|
|
19451
19661
|
) : null;
|
|
@@ -19624,7 +19834,7 @@ function resolvePerRepoConfig(repos, repoRoot, home) {
|
|
|
19624
19834
|
}
|
|
19625
19835
|
function expandTilde2(path9, home) {
|
|
19626
19836
|
if (path9 === "~") return home;
|
|
19627
|
-
if (path9.startsWith("~/")) return
|
|
19837
|
+
if (path9.startsWith("~/")) return join14(home, path9.slice(2));
|
|
19628
19838
|
return path9;
|
|
19629
19839
|
}
|
|
19630
19840
|
async function checkGitVersion() {
|
|
@@ -19751,7 +19961,7 @@ function resolveWorktreeBase(repoRoot, config, home) {
|
|
|
19751
19961
|
if (typeof configuredPath === "string" && (configuredPath.startsWith("/") || configuredPath.startsWith("~"))) {
|
|
19752
19962
|
return expandTilde2(configuredPath, home);
|
|
19753
19963
|
}
|
|
19754
|
-
return
|
|
19964
|
+
return join14(dirname8(repoRoot), "worktrees", basename9(repoRoot));
|
|
19755
19965
|
}
|
|
19756
19966
|
async function findNearestExistingPath(path9) {
|
|
19757
19967
|
let candidate = path9;
|
|
@@ -19760,7 +19970,7 @@ async function findNearestExistingPath(path9) {
|
|
|
19760
19970
|
await access6(candidate, constants6.F_OK);
|
|
19761
19971
|
return candidate;
|
|
19762
19972
|
} catch {
|
|
19763
|
-
const parent =
|
|
19973
|
+
const parent = dirname8(candidate);
|
|
19764
19974
|
if (parent === candidate) return null;
|
|
19765
19975
|
candidate = parent;
|
|
19766
19976
|
}
|
|
@@ -19911,7 +20121,7 @@ function statusSymbol(status) {
|
|
|
19911
20121
|
}
|
|
19912
20122
|
|
|
19913
20123
|
// src/cli/commands/done.ts
|
|
19914
|
-
import { basename as
|
|
20124
|
+
import { basename as basename10 } from "node:path";
|
|
19915
20125
|
var DONE_OUTPUT_FILE_ENV = "GJI_DONE_OUTPUT_FILE";
|
|
19916
20126
|
async function runDoneCommand(options) {
|
|
19917
20127
|
const runtime = options.runtime ?? defaultCliDependencies;
|
|
@@ -19923,7 +20133,7 @@ async function runDoneCommand(options) {
|
|
|
19923
20133
|
} = runtime.git;
|
|
19924
20134
|
const { loadEffectiveConfig: loadEffectiveConfig2, resolveConfigString: resolveConfigString2 } = runtime.config;
|
|
19925
20135
|
const { loadHistory: loadHistory2 } = runtime.historyStore;
|
|
19926
|
-
const { releaseWorktreeSlot: releaseWorktreeSlot2 } = runtime.slots;
|
|
20136
|
+
const { getWorktreeSlot: getWorktreeSlot2, releaseWorktreeSlot: releaseWorktreeSlot2 } = runtime.slots;
|
|
19927
20137
|
const { extractHooks: extractHooks2, runHook: runHook2 } = runtime.hooks;
|
|
19928
20138
|
const { detectRepository: detectRepository2 } = runtime.repositoryContext;
|
|
19929
20139
|
const { listWorktrees: listWorktrees2 } = runtime.worktrees;
|
|
@@ -20019,9 +20229,11 @@ async function runDoneCommand(options) {
|
|
|
20019
20229
|
{
|
|
20020
20230
|
branch: target.branch ?? void 0,
|
|
20021
20231
|
path: target.path,
|
|
20022
|
-
repo:
|
|
20232
|
+
repo: basename10(repository.repoRoot),
|
|
20233
|
+
slot: await getWorktreeSlot2(target.path)
|
|
20023
20234
|
},
|
|
20024
|
-
options.stderr
|
|
20235
|
+
options.stderr,
|
|
20236
|
+
options.json ? () => void 0 : options.stdout
|
|
20025
20237
|
);
|
|
20026
20238
|
try {
|
|
20027
20239
|
await removeWorktree2(repository.repoRoot, target.path);
|
|
@@ -20210,7 +20422,7 @@ function toMessage3(error) {
|
|
|
20210
20422
|
}
|
|
20211
20423
|
|
|
20212
20424
|
// src/cli/commands/go.ts
|
|
20213
|
-
import { basename as
|
|
20425
|
+
import { basename as basename12 } from "node:path";
|
|
20214
20426
|
|
|
20215
20427
|
// src/application/worktree/context-card.ts
|
|
20216
20428
|
async function loadContextCardModel(worktreePath, dependencies) {
|
|
@@ -20536,69 +20748,12 @@ function formatState(info) {
|
|
|
20536
20748
|
return `${info.status}${info.upstream.kind === "tracked" ? ` \xB7 \u2191${info.upstream.ahead} \u2193${info.upstream.behind}` : ""}`;
|
|
20537
20749
|
}
|
|
20538
20750
|
function formatLastCommit2(info) {
|
|
20539
|
-
return new Date(info.lastCommitTimestamp).toISOString();
|
|
20751
|
+
return new Date(info.lastCommitTimestamp * 1e3).toISOString();
|
|
20540
20752
|
}
|
|
20541
20753
|
|
|
20542
20754
|
// src/cli/commands/new.ts
|
|
20543
|
-
import { access as access8, mkdir as
|
|
20544
|
-
import { dirname as
|
|
20545
|
-
|
|
20546
|
-
// src/domain/worktree/policy.ts
|
|
20547
|
-
import { homedir as homedir7 } from "node:os";
|
|
20548
|
-
import { basename as basename9, dirname as dirname10, join as join13 } from "node:path";
|
|
20549
|
-
function resolveWorktreePath(repoRoot, branch, basePath) {
|
|
20550
|
-
const segments = branch.split("/").filter(Boolean);
|
|
20551
|
-
if (segments.length === 0) {
|
|
20552
|
-
throw new Error("Branch name must not be empty.");
|
|
20553
|
-
}
|
|
20554
|
-
if (segments.some((segment) => segment === "." || segment === "..")) {
|
|
20555
|
-
throw new Error(
|
|
20556
|
-
`Branch name '${branch}' contains an invalid path segment.`
|
|
20557
|
-
);
|
|
20558
|
-
}
|
|
20559
|
-
const base = basePath ? expandTildeInPath(basePath) : join13(dirname10(repoRoot), "worktrees", basename9(repoRoot));
|
|
20560
|
-
return join13(base, ...segments);
|
|
20561
|
-
}
|
|
20562
|
-
function validateBranchName(name) {
|
|
20563
|
-
if (name.length === 0) {
|
|
20564
|
-
return "Branch name must not be empty.";
|
|
20565
|
-
}
|
|
20566
|
-
if (/[\x00-\x1f\x7f ~^:?*[\\\s]/.test(name)) {
|
|
20567
|
-
return `Branch name '${name}' contains an invalid character.`;
|
|
20568
|
-
}
|
|
20569
|
-
if (name.startsWith("-")) {
|
|
20570
|
-
return `Branch name '${name}' must not start with a dash.`;
|
|
20571
|
-
}
|
|
20572
|
-
if (name.startsWith("/") || name.endsWith("/") || name.includes("//")) {
|
|
20573
|
-
return `Branch name '${name}' has invalid slash placement.`;
|
|
20574
|
-
}
|
|
20575
|
-
if (name.includes("..")) {
|
|
20576
|
-
return `Branch name '${name}' must not contain '..'.`;
|
|
20577
|
-
}
|
|
20578
|
-
if (name.endsWith(".")) {
|
|
20579
|
-
return `Branch name '${name}' must not end with '.'.`;
|
|
20580
|
-
}
|
|
20581
|
-
if (name.includes("@{")) {
|
|
20582
|
-
return `Branch name '${name}' must not contain '@{'.`;
|
|
20583
|
-
}
|
|
20584
|
-
if (name === "@") {
|
|
20585
|
-
return "Branch name cannot be '@'.";
|
|
20586
|
-
}
|
|
20587
|
-
for (const segment of name.split("/")) {
|
|
20588
|
-
if (segment.startsWith(".")) {
|
|
20589
|
-
return `Branch name '${name}' contains a path component starting with '.'.`;
|
|
20590
|
-
}
|
|
20591
|
-
if (segment.endsWith(".lock")) {
|
|
20592
|
-
return `Branch name '${name}' contains a path component ending with '.lock'.`;
|
|
20593
|
-
}
|
|
20594
|
-
}
|
|
20595
|
-
return null;
|
|
20596
|
-
}
|
|
20597
|
-
function expandTildeInPath(path9) {
|
|
20598
|
-
if (path9 === "~") return homedir7();
|
|
20599
|
-
if (path9.startsWith("~/")) return join13(homedir7(), path9.slice(2));
|
|
20600
|
-
return path9;
|
|
20601
|
-
}
|
|
20755
|
+
import { access as access8, mkdir as mkdir5, rmdir as rmdir2 } from "node:fs/promises";
|
|
20756
|
+
import { basename as basename11, dirname as dirname9, resolve as resolve12 } from "node:path";
|
|
20602
20757
|
|
|
20603
20758
|
// src/presentation/bootstrap/output.ts
|
|
20604
20759
|
function createBootstrapReporter(write, json) {
|
|
@@ -20713,10 +20868,8 @@ function createNewCommand(dependencies = {}) {
|
|
|
20713
20868
|
options.stderr(`${JSON.stringify({ error: message }, null, 2)}
|
|
20714
20869
|
`);
|
|
20715
20870
|
} else {
|
|
20716
|
-
options.stderr(
|
|
20717
|
-
|
|
20718
|
-
`
|
|
20719
|
-
);
|
|
20871
|
+
options.stderr(`gji new: ${message} in non-interactive mode
|
|
20872
|
+
`);
|
|
20720
20873
|
}
|
|
20721
20874
|
return 1;
|
|
20722
20875
|
}
|
|
@@ -20767,13 +20920,14 @@ function createNewCommand(dependencies = {}) {
|
|
|
20767
20920
|
if (options.force) {
|
|
20768
20921
|
if (!options.dryRun) {
|
|
20769
20922
|
try {
|
|
20770
|
-
await runtime.git.
|
|
20923
|
+
await runtime.git.runGitRetryingContention(repository.repoRoot, [
|
|
20771
20924
|
"worktree",
|
|
20772
20925
|
"remove",
|
|
20773
20926
|
"--force",
|
|
20774
20927
|
worktreePath
|
|
20775
20928
|
]);
|
|
20776
20929
|
} catch (err) {
|
|
20930
|
+
await rmdir2(worktreePath).catch(() => void 0);
|
|
20777
20931
|
if (!isNotRegisteredWorktreeError(err)) {
|
|
20778
20932
|
const msg = `could not remove existing worktree at ${worktreePath}: ${toExecMessage(err)}`;
|
|
20779
20933
|
if (options.json) {
|
|
@@ -20804,10 +20958,8 @@ function createNewCommand(dependencies = {}) {
|
|
|
20804
20958
|
options.stderr(`${JSON.stringify({ error: message }, null, 2)}
|
|
20805
20959
|
`);
|
|
20806
20960
|
} else {
|
|
20807
|
-
options.stderr(
|
|
20808
|
-
|
|
20809
|
-
`
|
|
20810
|
-
);
|
|
20961
|
+
options.stderr(`gji new: ${message} in non-interactive mode
|
|
20962
|
+
`);
|
|
20811
20963
|
options.stderr(
|
|
20812
20964
|
`Hint: Use 'gji done ${worktreeName}' or 'gji clean' to remove the existing worktree
|
|
20813
20965
|
`
|
|
@@ -20908,7 +21060,7 @@ ${formatDependencyBootstrapPreview(dryRunDependencyBootstrap)}`
|
|
|
20908
21060
|
}
|
|
20909
21061
|
return 0;
|
|
20910
21062
|
}
|
|
20911
|
-
await
|
|
21063
|
+
await mkdir5(dirname9(worktreePath), { recursive: true });
|
|
20912
21064
|
const startPoint = options.fromCurrent && !options.detached ? await resolveCurrentWorktreeHead(options.cwd, runtime.git.runGit) : void 0;
|
|
20913
21065
|
const takeStartPoint = options.take ? await resolveCurrentWorktreeHead(options.cwd, runtime.git.runGit) : void 0;
|
|
20914
21066
|
const changedTakeFiles = options.take ? await listTakeFiles(options.cwd, runtime.git.runGitRaw) : [];
|
|
@@ -20936,7 +21088,8 @@ ${formatDependencyBootstrapPreview(dryRunDependencyBootstrap)}`
|
|
|
20936
21088
|
worktreeName,
|
|
20937
21089
|
runtime
|
|
20938
21090
|
);
|
|
20939
|
-
const
|
|
21091
|
+
const createsBranch = !options.detached && options.mode !== "track" && options.mode !== "checkout" && !existingLocalBranch;
|
|
21092
|
+
const shouldRefreshBase = createsBranch && !options.noFetch && !options.fromCurrent && !options.take;
|
|
20940
21093
|
const freshBaseRef = shouldRefreshBase ? await resolveFreshBaseRef(
|
|
20941
21094
|
repository.repoRoot,
|
|
20942
21095
|
config,
|
|
@@ -20969,30 +21122,19 @@ ${formatDependencyBootstrapPreview(dryRunDependencyBootstrap)}`
|
|
|
20969
21122
|
);
|
|
20970
21123
|
}
|
|
20971
21124
|
}
|
|
20972
|
-
const
|
|
20973
|
-
"worktree",
|
|
20974
|
-
"add",
|
|
20975
|
-
"--detach",
|
|
20976
|
-
worktreePath,
|
|
20977
|
-
...takeStartPoint ? [takeStartPoint] : []
|
|
20978
|
-
] : options.mode === "track" ? [
|
|
20979
|
-
"worktree",
|
|
20980
|
-
"add",
|
|
20981
|
-
"--track",
|
|
20982
|
-
"-b",
|
|
20983
|
-
worktreeName,
|
|
20984
|
-
worktreePath,
|
|
20985
|
-
`${options.remote ?? "origin"}/${worktreeName}`
|
|
20986
|
-
] : options.mode === "checkout" || existingLocalBranch ? ["worktree", "add", worktreePath, worktreeName] : [
|
|
20987
|
-
"worktree",
|
|
20988
|
-
"add",
|
|
20989
|
-
"-b",
|
|
20990
|
-
worktreeName,
|
|
20991
|
-
worktreePath,
|
|
20992
|
-
...takeStartPoint ? [takeStartPoint] : startPoint ? [startPoint] : freshBaseRef ? [freshBaseRef] : []
|
|
20993
|
-
];
|
|
21125
|
+
const addsNewBranch = createsBranch || !options.detached && options.mode === "track";
|
|
20994
21126
|
try {
|
|
20995
|
-
await runtime.git.
|
|
21127
|
+
await runtime.git.addWorktree(
|
|
21128
|
+
repository.repoRoot,
|
|
21129
|
+
worktreePath,
|
|
21130
|
+
options.detached ? { detachedAt: takeStartPoint } : addsNewBranch ? {
|
|
21131
|
+
branch: worktreeName,
|
|
21132
|
+
create: {
|
|
21133
|
+
startPoint: options.mode === "track" ? `${options.remote ?? "origin"}/${worktreeName}` : takeStartPoint ?? startPoint ?? freshBaseRef,
|
|
21134
|
+
track: options.mode === "track"
|
|
21135
|
+
}
|
|
21136
|
+
} : { branch: worktreeName }
|
|
21137
|
+
);
|
|
20996
21138
|
} catch (error) {
|
|
20997
21139
|
if (stashSha)
|
|
20998
21140
|
await restoreTakeStash(
|
|
@@ -21014,15 +21156,11 @@ ${formatDependencyBootstrapPreview(dryRunDependencyBootstrap)}`
|
|
|
21014
21156
|
options.stderr,
|
|
21015
21157
|
runtime.git.runGit
|
|
21016
21158
|
)) {
|
|
21017
|
-
|
|
21018
|
-
|
|
21019
|
-
|
|
21020
|
-
|
|
21021
|
-
|
|
21022
|
-
worktreePath
|
|
21023
|
-
]);
|
|
21024
|
-
} catch {
|
|
21025
|
-
}
|
|
21159
|
+
await runtime.git.removeCreatedWorktree(
|
|
21160
|
+
repository.repoRoot,
|
|
21161
|
+
worktreePath,
|
|
21162
|
+
addsNewBranch ? worktreeName : void 0
|
|
21163
|
+
);
|
|
21026
21164
|
return emitNewError(
|
|
21027
21165
|
options,
|
|
21028
21166
|
`could not apply taken changes; your changes are safe in stash: ${stashSha}`
|
|
@@ -21058,6 +21196,17 @@ ${formatDependencyBootstrapPreview(dryRunDependencyBootstrap)}`
|
|
|
21058
21196
|
syncFiles: bootstrap.syncFileFailures
|
|
21059
21197
|
});
|
|
21060
21198
|
}
|
|
21199
|
+
if (createsBranch) {
|
|
21200
|
+
await runNewHooks({
|
|
21201
|
+
branch: worktreeName,
|
|
21202
|
+
config,
|
|
21203
|
+
hookNames: ["after-branch-create"],
|
|
21204
|
+
options,
|
|
21205
|
+
path: worktreePath,
|
|
21206
|
+
repository,
|
|
21207
|
+
runtime
|
|
21208
|
+
});
|
|
21209
|
+
}
|
|
21061
21210
|
if (options.json) {
|
|
21062
21211
|
const navigation = createNavigationTarget(
|
|
21063
21212
|
createNavigationRepository(repository.repoName, repository.repoRoot),
|
|
@@ -21190,7 +21339,7 @@ async function runBranchOnly(options, runtime, repository, config, rawBranch) {
|
|
|
21190
21339
|
try {
|
|
21191
21340
|
let startPoint = baseBranch;
|
|
21192
21341
|
if (!options.noFetch) {
|
|
21193
|
-
await runtime.git.
|
|
21342
|
+
await runtime.git.runGitRetryingContention(repository.repoRoot, [
|
|
21194
21343
|
"fetch",
|
|
21195
21344
|
"--prune",
|
|
21196
21345
|
remote,
|
|
@@ -21198,18 +21347,30 @@ async function runBranchOnly(options, runtime, repository, config, rawBranch) {
|
|
|
21198
21347
|
]);
|
|
21199
21348
|
startPoint = `${remote}/${baseBranch}`;
|
|
21200
21349
|
}
|
|
21201
|
-
await runtime.git.
|
|
21202
|
-
"switch",
|
|
21203
|
-
"-c",
|
|
21204
|
-
branchName,
|
|
21350
|
+
await runtime.git.createBranch(repository.repoRoot, branchName, {
|
|
21205
21351
|
startPoint
|
|
21206
|
-
|
|
21352
|
+
});
|
|
21353
|
+
try {
|
|
21354
|
+
await runtime.git.runGit(options.cwd, ["switch", branchName]);
|
|
21355
|
+
} catch (error) {
|
|
21356
|
+
await runtime.git.deleteCreatedBranch(repository.repoRoot, branchName);
|
|
21357
|
+
throw error;
|
|
21358
|
+
}
|
|
21207
21359
|
} catch (error) {
|
|
21208
21360
|
return emitNewError(
|
|
21209
21361
|
options,
|
|
21210
21362
|
`failed to create branch in the current worktree: ${toExecMessage(error)}`
|
|
21211
21363
|
);
|
|
21212
21364
|
}
|
|
21365
|
+
await runNewHooks({
|
|
21366
|
+
branch: branchName,
|
|
21367
|
+
config,
|
|
21368
|
+
hookNames: ["after-branch-create", "after-switch"],
|
|
21369
|
+
options,
|
|
21370
|
+
path: repository.currentRoot,
|
|
21371
|
+
repository,
|
|
21372
|
+
runtime
|
|
21373
|
+
});
|
|
21213
21374
|
if (options.json) {
|
|
21214
21375
|
const navigation = createNavigationTarget(
|
|
21215
21376
|
createNavigationRepository(repository.repoName, repository.repoRoot),
|
|
@@ -21237,6 +21398,32 @@ async function runBranchOnly(options, runtime, repository, config, rawBranch) {
|
|
|
21237
21398
|
}
|
|
21238
21399
|
return 0;
|
|
21239
21400
|
}
|
|
21401
|
+
async function runNewHooks({
|
|
21402
|
+
branch,
|
|
21403
|
+
config,
|
|
21404
|
+
hookNames,
|
|
21405
|
+
options,
|
|
21406
|
+
path: path9,
|
|
21407
|
+
repository,
|
|
21408
|
+
runtime
|
|
21409
|
+
}) {
|
|
21410
|
+
const hooks = runtime.hooks.extractHooks(config);
|
|
21411
|
+
const context = {
|
|
21412
|
+
branch,
|
|
21413
|
+
path: path9,
|
|
21414
|
+
repo: basename11(repository.repoRoot),
|
|
21415
|
+
slot: await runtime.slots.getWorktreeSlot(path9)
|
|
21416
|
+
};
|
|
21417
|
+
for (const hookName of hookNames) {
|
|
21418
|
+
await runtime.hooks.runHook(
|
|
21419
|
+
hooks[hookName],
|
|
21420
|
+
path9,
|
|
21421
|
+
context,
|
|
21422
|
+
options.json ? () => void 0 : options.stderr,
|
|
21423
|
+
options.json ? () => void 0 : options.stdout
|
|
21424
|
+
);
|
|
21425
|
+
}
|
|
21426
|
+
}
|
|
21240
21427
|
var runNewCommand = createNewCommand();
|
|
21241
21428
|
async function resolveFreshBaseRef(repoRoot, config, options, promptForFetchFailure, runtime) {
|
|
21242
21429
|
const remote = runtime.config.resolveConfigString(config, "syncRemote") ?? "origin";
|
|
@@ -21259,7 +21446,7 @@ async function resolveFreshBaseRef(repoRoot, config, options, promptForFetchFail
|
|
|
21259
21446
|
throw new Error("the remote default branch is unknown");
|
|
21260
21447
|
}
|
|
21261
21448
|
baseBranch = remoteBase.branch;
|
|
21262
|
-
await runtime.git.
|
|
21449
|
+
await runtime.git.runGitRetryingContention(repoRoot, [
|
|
21263
21450
|
"fetch",
|
|
21264
21451
|
"--prune",
|
|
21265
21452
|
remote,
|
|
@@ -21500,7 +21687,7 @@ async function detectInProgressGitState(cwd, runGit2) {
|
|
|
21500
21687
|
for (const [marker, name] of markers) {
|
|
21501
21688
|
try {
|
|
21502
21689
|
const gitPath = await runGit2(cwd, ["rev-parse", "--git-path", marker]);
|
|
21503
|
-
await access8(
|
|
21690
|
+
await access8(resolve12(cwd, gitPath));
|
|
21504
21691
|
return name;
|
|
21505
21692
|
} catch {
|
|
21506
21693
|
}
|
|
@@ -21651,8 +21838,8 @@ function emitNewError(options, message, details) {
|
|
|
21651
21838
|
}
|
|
21652
21839
|
|
|
21653
21840
|
// src/cli/commands/pr.ts
|
|
21654
|
-
import { mkdir as
|
|
21655
|
-
import { dirname as
|
|
21841
|
+
import { mkdir as mkdir6 } from "node:fs/promises";
|
|
21842
|
+
import { dirname as dirname10 } from "node:path";
|
|
21656
21843
|
var PR_OUTPUT_FILE_ENV = "GJI_PR_OUTPUT_FILE";
|
|
21657
21844
|
function createPrCommand(dependencies = {}) {
|
|
21658
21845
|
const prompt = dependencies.promptForPathConflict ?? promptForPathConflict;
|
|
@@ -21710,10 +21897,8 @@ function createPrCommand(dependencies = {}) {
|
|
|
21710
21897
|
options.stderr(`${JSON.stringify({ error: message }, null, 2)}
|
|
21711
21898
|
`);
|
|
21712
21899
|
} else {
|
|
21713
|
-
options.stderr(
|
|
21714
|
-
|
|
21715
|
-
`
|
|
21716
|
-
);
|
|
21900
|
+
options.stderr(`gji pr: ${message} in non-interactive mode
|
|
21901
|
+
`);
|
|
21717
21902
|
options.stderr(
|
|
21718
21903
|
`Hint: Use 'gji done pr/${prNumber}' or 'gji clean' to remove the existing worktree
|
|
21719
21904
|
`
|
|
@@ -21791,14 +21976,29 @@ ${formatDependencyBootstrapPreview(dryRunDependencyBootstrap)}`
|
|
|
21791
21976
|
}
|
|
21792
21977
|
return 1;
|
|
21793
21978
|
}
|
|
21794
|
-
await
|
|
21979
|
+
await mkdir6(dirname10(worktreePath), { recursive: true });
|
|
21795
21980
|
const branchAlreadyExists = await localBranchExists2(
|
|
21796
21981
|
repository.repoRoot,
|
|
21797
21982
|
branchName,
|
|
21798
21983
|
runtime
|
|
21799
21984
|
);
|
|
21800
|
-
|
|
21801
|
-
|
|
21985
|
+
try {
|
|
21986
|
+
await runtime.git.addWorktree(
|
|
21987
|
+
repository.repoRoot,
|
|
21988
|
+
worktreePath,
|
|
21989
|
+
branchAlreadyExists ? { branch: branchName } : { branch: branchName, create: { startPoint: remoteRef } }
|
|
21990
|
+
);
|
|
21991
|
+
} catch (error) {
|
|
21992
|
+
const message = `failed to create worktree: ${error instanceof Error ? error.message : String(error)}`;
|
|
21993
|
+
if (options.json) {
|
|
21994
|
+
options.stderr(`${JSON.stringify({ error: message }, null, 2)}
|
|
21995
|
+
`);
|
|
21996
|
+
} else {
|
|
21997
|
+
options.stderr(`gji pr: ${message}
|
|
21998
|
+
`);
|
|
21999
|
+
}
|
|
22000
|
+
return 1;
|
|
22001
|
+
}
|
|
21802
22002
|
const bootstrap = await runtime.bootstrap.bootstrapWorktree({
|
|
21803
22003
|
branch: branchName,
|
|
21804
22004
|
config,
|
|
@@ -21871,7 +22071,7 @@ var runPrCommand = createPrCommand();
|
|
|
21871
22071
|
async function fetchPullRequestRef(repoRoot, input, prNumber, remoteRef, runtime) {
|
|
21872
22072
|
for (const sourceRef of listPullRequestSourceRefs(input, prNumber)) {
|
|
21873
22073
|
try {
|
|
21874
|
-
await runtime.git.
|
|
22074
|
+
await runtime.git.runGitRetryingContention(repoRoot, [
|
|
21875
22075
|
"fetch",
|
|
21876
22076
|
"origin",
|
|
21877
22077
|
`${sourceRef}:${remoteRef}`
|
|
@@ -21959,7 +22159,7 @@ function createGoCommand(dependencies = {}) {
|
|
|
21959
22159
|
if (options.json || isHeadless()) {
|
|
21960
22160
|
return emitError2(
|
|
21961
22161
|
options,
|
|
21962
|
-
"branch argument is required in non-interactive mode
|
|
22162
|
+
"branch argument is required in non-interactive mode"
|
|
21963
22163
|
);
|
|
21964
22164
|
}
|
|
21965
22165
|
let skippedRegisteredRepos = 0;
|
|
@@ -22103,10 +22303,10 @@ async function handleGoBranchResolution(options, resolution, confirmBranchCreati
|
|
|
22103
22303
|
runtime
|
|
22104
22304
|
);
|
|
22105
22305
|
case "pull-request":
|
|
22106
|
-
if (options.json ||
|
|
22306
|
+
if (options.json || options.print) {
|
|
22107
22307
|
return emitError2(
|
|
22108
22308
|
options,
|
|
22109
|
-
"PR navigation creates a worktree; use `gji pr <ref>`
|
|
22309
|
+
"PR navigation creates a worktree; use `gji pr <ref>`"
|
|
22110
22310
|
);
|
|
22111
22311
|
}
|
|
22112
22312
|
return runPrCommand({
|
|
@@ -22151,9 +22351,16 @@ function deduplicateSources(sources) {
|
|
|
22151
22351
|
}
|
|
22152
22352
|
async function createExistingBranchWorktree(options, confirmBranchCreation, repository, branch, mode, remote, runtime) {
|
|
22153
22353
|
if (options.json || isHeadless() || options.print) {
|
|
22354
|
+
const command = await resolveNonInteractiveCreateCommand(
|
|
22355
|
+
repository,
|
|
22356
|
+
branch,
|
|
22357
|
+
mode,
|
|
22358
|
+
remote,
|
|
22359
|
+
runtime ?? defaultCliDependencies
|
|
22360
|
+
);
|
|
22154
22361
|
return emitError2(
|
|
22155
22362
|
options,
|
|
22156
|
-
`branch "${branch}" exists but has no worktree;
|
|
22363
|
+
`branch "${branch}" exists but has no worktree; run ${command} to create one`
|
|
22157
22364
|
);
|
|
22158
22365
|
}
|
|
22159
22366
|
if (!await confirmBranchCreation(branch)) {
|
|
@@ -22171,6 +22378,13 @@ async function createExistingBranchWorktree(options, confirmBranchCreation, repo
|
|
|
22171
22378
|
runtime
|
|
22172
22379
|
});
|
|
22173
22380
|
}
|
|
22381
|
+
async function resolveNonInteractiveCreateCommand(repository, branch, mode, remote, runtime) {
|
|
22382
|
+
if (mode === "track") {
|
|
22383
|
+
return `git worktree add --track -b ${branch} <path> ${remote ?? "origin"}/${branch}`;
|
|
22384
|
+
}
|
|
22385
|
+
const config = await runtime.config.loadEffectiveConfig(repository.repoRoot).catch(() => null);
|
|
22386
|
+
return applyConfiguredBranchPrefix(branch, config?.branchPrefix) === branch ? `gji new ${branch}` : `git worktree add <path> ${branch}`;
|
|
22387
|
+
}
|
|
22174
22388
|
async function navigateToExistingWorktree(options, path9, worktree, runtime) {
|
|
22175
22389
|
const repository = await runtime.repositoryContext.detectRepository(path9);
|
|
22176
22390
|
if (options.json) {
|
|
@@ -22200,7 +22414,7 @@ async function navigateToExistingWorktree(options, path9, worktree, runtime) {
|
|
|
22200
22414
|
{
|
|
22201
22415
|
branch: worktree?.branch ?? void 0,
|
|
22202
22416
|
path: path9,
|
|
22203
|
-
repo:
|
|
22417
|
+
repo: basename12(repository.repoRoot),
|
|
22204
22418
|
slot: await runtime.slots.getWorktreeSlot(path9)
|
|
22205
22419
|
},
|
|
22206
22420
|
options.stderr
|
|
@@ -22340,7 +22554,7 @@ async function runHistoryCommand(options) {
|
|
|
22340
22554
|
}
|
|
22341
22555
|
|
|
22342
22556
|
// src/cli/commands/hub.ts
|
|
22343
|
-
import { basename as
|
|
22557
|
+
import { basename as basename13 } from "node:path";
|
|
22344
22558
|
import { stdin as stdin2, stdout as stdout2 } from "node:process";
|
|
22345
22559
|
var MAX_HUB_REPOSITORY_CONCURRENCY = 4;
|
|
22346
22560
|
async function buildHubData(cwd, dependencies = {}, runtime = defaultCliDependencies) {
|
|
@@ -22365,7 +22579,7 @@ async function buildHubData(cwd, dependencies = {}, runtime = defaultCliDependen
|
|
|
22365
22579
|
existing.sources.push(source);
|
|
22366
22580
|
} else {
|
|
22367
22581
|
groups.set(repoRoot, {
|
|
22368
|
-
name: source.repoName ||
|
|
22582
|
+
name: source.repoName || basename13(repoRoot),
|
|
22369
22583
|
sources: [source]
|
|
22370
22584
|
});
|
|
22371
22585
|
}
|
|
@@ -22614,9 +22828,9 @@ function formatHumanText(value, maxLength) {
|
|
|
22614
22828
|
|
|
22615
22829
|
// src/cli/commands/init.ts
|
|
22616
22830
|
import { constants as constants8 } from "node:fs";
|
|
22617
|
-
import { access as access9, mkdir as
|
|
22831
|
+
import { access as access9, mkdir as mkdir7, readFile as readFile9, writeFile as writeFile5 } from "node:fs/promises";
|
|
22618
22832
|
import { homedir as homedir8 } from "node:os";
|
|
22619
|
-
import { dirname as
|
|
22833
|
+
import { dirname as dirname11 } from "node:path";
|
|
22620
22834
|
var ZSH_COMPLETION_PATH_LINE = "fpath=(~/.zsh/completions $fpath)";
|
|
22621
22835
|
var SHELL_WRAPPED_COMMANDS = [
|
|
22622
22836
|
{
|
|
@@ -22654,26 +22868,12 @@ var SHELL_WRAPPED_COMMANDS = [
|
|
|
22654
22868
|
names: ["root"],
|
|
22655
22869
|
tempPrefix: "gji-root"
|
|
22656
22870
|
},
|
|
22657
|
-
{
|
|
22658
|
-
bypassOptions: ["--help", "-h", "--json"],
|
|
22659
|
-
commandName: "remove",
|
|
22660
|
-
envVar: "GJI_REMOVE_OUTPUT_FILE",
|
|
22661
|
-
names: ["remove", "rm"],
|
|
22662
|
-
tempPrefix: "gji-remove"
|
|
22663
|
-
},
|
|
22664
22871
|
{
|
|
22665
22872
|
bypassOptions: ["--help", "-h", "--json"],
|
|
22666
22873
|
commandName: "done",
|
|
22667
22874
|
envVar: "GJI_DONE_OUTPUT_FILE",
|
|
22668
22875
|
names: ["done"],
|
|
22669
22876
|
tempPrefix: "gji-done"
|
|
22670
|
-
},
|
|
22671
|
-
{
|
|
22672
|
-
bypassOptions: ["--print", "--json", "--help", "-h"],
|
|
22673
|
-
commandName: "warp",
|
|
22674
|
-
envVar: "GJI_WARP_OUTPUT_FILE",
|
|
22675
|
-
names: ["warp"],
|
|
22676
|
-
tempPrefix: "gji-warp"
|
|
22677
22877
|
}
|
|
22678
22878
|
];
|
|
22679
22879
|
async function runInitCommand(options) {
|
|
@@ -22741,8 +22941,8 @@ async function runDefaultOnboarding(context, runtime) {
|
|
|
22741
22941
|
const installCompletion = await promptForCompletion(shell, completionPath);
|
|
22742
22942
|
if (installCompletion === null) return abortDefaultOnboarding();
|
|
22743
22943
|
if (installCompletion) {
|
|
22744
|
-
await
|
|
22745
|
-
await
|
|
22944
|
+
await mkdir7(dirname11(completionPath), { recursive: true });
|
|
22945
|
+
await writeFile5(completionPath, renderShellCompletion(shell), "utf8");
|
|
22746
22946
|
}
|
|
22747
22947
|
if (shell === "zsh" && (completionAlreadyInstalled || installCompletion)) {
|
|
22748
22948
|
await ensureZshCompletionPath(rcPath);
|
|
@@ -22847,8 +23047,8 @@ async function applyOnboardingResult(result, home, runtime) {
|
|
|
22847
23047
|
runtime
|
|
22848
23048
|
);
|
|
22849
23049
|
if (result.installCompletion) {
|
|
22850
|
-
await
|
|
22851
|
-
await
|
|
23050
|
+
await mkdir7(dirname11(completionPath), { recursive: true });
|
|
23051
|
+
await writeFile5(
|
|
22852
23052
|
completionPath,
|
|
22853
23053
|
renderShellCompletion(result.shell),
|
|
22854
23054
|
"utf8"
|
|
@@ -22867,17 +23067,17 @@ async function applyOnboardingResult(result, home, runtime) {
|
|
|
22867
23067
|
}
|
|
22868
23068
|
async function installOnboardingShellIntegration(shell, home) {
|
|
22869
23069
|
const rcPath = resolveShellConfigPath(shell, home);
|
|
22870
|
-
await
|
|
23070
|
+
await mkdir7(dirname11(rcPath), { recursive: true });
|
|
22871
23071
|
const current = await readExistingConfig(rcPath);
|
|
22872
23072
|
if (shell === "fish") {
|
|
22873
|
-
await
|
|
23073
|
+
await writeFile5(
|
|
22874
23074
|
rcPath,
|
|
22875
23075
|
upsertShellIntegration(current, renderShellIntegration(shell)),
|
|
22876
23076
|
"utf8"
|
|
22877
23077
|
);
|
|
22878
23078
|
return;
|
|
22879
23079
|
}
|
|
22880
|
-
await
|
|
23080
|
+
await writeFile5(
|
|
22881
23081
|
rcPath,
|
|
22882
23082
|
upsertOnboardingShellIntegration(current, shell),
|
|
22883
23083
|
"utf8"
|
|
@@ -22911,7 +23111,7 @@ async function ensureZshCompletionPath(rcPath) {
|
|
|
22911
23111
|
const current = await readExistingConfig(rcPath);
|
|
22912
23112
|
const next = upsertZshCompletionPath(current);
|
|
22913
23113
|
if (next !== current) {
|
|
22914
|
-
await
|
|
23114
|
+
await writeFile5(rcPath, next, "utf8");
|
|
22915
23115
|
}
|
|
22916
23116
|
}
|
|
22917
23117
|
function upsertZshCompletionPath(existingConfig) {
|
|
@@ -22955,10 +23155,10 @@ async function runLegacyInitCommand(options, runtime) {
|
|
|
22955
23155
|
return 0;
|
|
22956
23156
|
}
|
|
22957
23157
|
const rcPath = resolveShellConfigPath(shell, home);
|
|
22958
|
-
await
|
|
23158
|
+
await mkdir7(dirname11(rcPath), { recursive: true });
|
|
22959
23159
|
const current = await readExistingConfig(rcPath);
|
|
22960
23160
|
const next = upsertShellIntegration(current, script);
|
|
22961
|
-
await
|
|
23161
|
+
await writeFile5(rcPath, next, "utf8");
|
|
22962
23162
|
options.stdout(`${rcPath}
|
|
22963
23163
|
`);
|
|
22964
23164
|
const { config: globalConfig } = await runtime.configStore.loadGlobalConfig(home);
|
|
@@ -23312,8 +23512,8 @@ function sortWorktrees(worktrees) {
|
|
|
23312
23512
|
|
|
23313
23513
|
// src/cli/commands/open.ts
|
|
23314
23514
|
import { execFile as execFile9 } from "node:child_process";
|
|
23315
|
-
import { access as access10, writeFile as
|
|
23316
|
-
import { join as
|
|
23515
|
+
import { access as access10, writeFile as writeFile6 } from "node:fs/promises";
|
|
23516
|
+
import { join as join15 } from "node:path";
|
|
23317
23517
|
import { promisify as promisify10 } from "node:util";
|
|
23318
23518
|
var execFileAsync9 = promisify10(execFile9);
|
|
23319
23519
|
function createOpenCommand(dependencies = {}) {
|
|
@@ -23335,7 +23535,7 @@ function createOpenCommand(dependencies = {}) {
|
|
|
23335
23535
|
}
|
|
23336
23536
|
if (options.select && isHeadless()) {
|
|
23337
23537
|
options.stderr(
|
|
23338
|
-
"gji open --select: selector is unavailable in non-interactive mode
|
|
23538
|
+
"gji open --select: selector is unavailable in non-interactive mode; pass a branch instead\n"
|
|
23339
23539
|
);
|
|
23340
23540
|
return 1;
|
|
23341
23541
|
}
|
|
@@ -23492,14 +23692,14 @@ async function defaultPromptForEditor(editors) {
|
|
|
23492
23692
|
return choice;
|
|
23493
23693
|
}
|
|
23494
23694
|
async function ensureWorkspaceFile(worktreePath, repoName) {
|
|
23495
|
-
const workspacePath =
|
|
23695
|
+
const workspacePath = join15(worktreePath, `${repoName}.code-workspace`);
|
|
23496
23696
|
try {
|
|
23497
23697
|
await access10(workspacePath);
|
|
23498
23698
|
return workspacePath;
|
|
23499
23699
|
} catch {
|
|
23500
23700
|
}
|
|
23501
23701
|
const workspace = { folders: [{ path: "." }], settings: {} };
|
|
23502
|
-
await
|
|
23702
|
+
await writeFile6(
|
|
23503
23703
|
workspacePath,
|
|
23504
23704
|
`${JSON.stringify(workspace, null, 2)}
|
|
23505
23705
|
`,
|
|
@@ -23527,7 +23727,7 @@ function createPrOpenCommand(dependencies = {}) {
|
|
|
23527
23727
|
}
|
|
23528
23728
|
if (options.select && isHeadless()) {
|
|
23529
23729
|
options.stderr(
|
|
23530
|
-
"gji pr open --select: selector is unavailable in non-interactive mode
|
|
23730
|
+
"gji pr open --select: selector is unavailable in non-interactive mode; pass a branch or PR number instead\n"
|
|
23531
23731
|
);
|
|
23532
23732
|
return 1;
|
|
23533
23733
|
}
|
|
@@ -23827,7 +24027,7 @@ async function openPullRequests(pullRequests, options, promptForPullRequest, ope
|
|
|
23827
24027
|
}
|
|
23828
24028
|
if (pullRequests.length > 1 && isHeadless()) {
|
|
23829
24029
|
options.stderr(
|
|
23830
|
-
"gji pr open: multiple open PRs found; pass an explicit PR number in non-interactive mode
|
|
24030
|
+
"gji pr open: multiple open PRs found; pass an explicit PR number in non-interactive mode\n"
|
|
23831
24031
|
);
|
|
23832
24032
|
return 1;
|
|
23833
24033
|
}
|
|
@@ -23878,250 +24078,6 @@ function formatError(error) {
|
|
|
23878
24078
|
return error instanceof Error ? error.message : String(error);
|
|
23879
24079
|
}
|
|
23880
24080
|
|
|
23881
|
-
// src/cli/commands/remove.ts
|
|
23882
|
-
import { basename as basename12 } from "node:path";
|
|
23883
|
-
var REMOVE_OUTPUT_FILE_ENV = "GJI_REMOVE_OUTPUT_FILE";
|
|
23884
|
-
function createRemoveCommand(dependencies = {}) {
|
|
23885
|
-
const promptForWorktree2 = dependencies.promptForWorktree ?? defaultPromptForWorktree3;
|
|
23886
|
-
const confirmRemoval = dependencies.confirmRemoval ?? defaultConfirmRemoval2;
|
|
23887
|
-
const confirmForceRemoveWorktree = dependencies.confirmForceRemoveWorktree ?? defaultConfirmForceRemoveWorktree;
|
|
23888
|
-
const confirmForceDeleteBranch = dependencies.confirmForceDeleteBranch ?? defaultConfirmForceDeleteBranch;
|
|
23889
|
-
return async function runRemoveCommand2(options) {
|
|
23890
|
-
const runtime = options.runtime ?? defaultCliDependencies;
|
|
23891
|
-
const { loadEffectiveConfig: loadEffectiveConfig2 } = runtime.config;
|
|
23892
|
-
const { releaseWorktreeSlot: releaseWorktreeSlot2 } = runtime.slots;
|
|
23893
|
-
const { extractHooks: extractHooks2, runHook: runHook2 } = runtime.hooks;
|
|
23894
|
-
const sourceDependencies = {
|
|
23895
|
-
...runtime.repositoryContext,
|
|
23896
|
-
...runtime.repositoryRegistry,
|
|
23897
|
-
...runtime.worktrees
|
|
23898
|
-
};
|
|
23899
|
-
const {
|
|
23900
|
-
deleteBranch: deleteBranch2,
|
|
23901
|
-
forceDeleteBranch: forceDeleteBranch2,
|
|
23902
|
-
forceRemoveWorktree: forceRemoveWorktree2,
|
|
23903
|
-
isBranchUnmergedError: isBranchUnmergedError2,
|
|
23904
|
-
isWorktreeForceRemovalError: isWorktreeForceRemovalError2,
|
|
23905
|
-
removeWorktree: removeWorktree2
|
|
23906
|
-
} = runtime.worktreeLifecycle;
|
|
23907
|
-
const { linkedWorktrees, repository } = await loadLinkedWorktrees(
|
|
23908
|
-
options.cwd,
|
|
23909
|
-
sourceDependencies
|
|
23910
|
-
);
|
|
23911
|
-
if (linkedWorktrees.length === 0) {
|
|
23912
|
-
emitError3(options, "No linked worktrees to finish");
|
|
23913
|
-
return 1;
|
|
23914
|
-
}
|
|
23915
|
-
if (!options.branch && (options.json || isHeadless())) {
|
|
23916
|
-
const message = "branch argument is required";
|
|
23917
|
-
if (options.json) {
|
|
23918
|
-
emitError3(options, message);
|
|
23919
|
-
} else {
|
|
23920
|
-
options.stderr(
|
|
23921
|
-
`gji remove: ${message} in non-interactive mode (GJI_NO_TUI=1)
|
|
23922
|
-
`
|
|
23923
|
-
);
|
|
23924
|
-
}
|
|
23925
|
-
return 1;
|
|
23926
|
-
}
|
|
23927
|
-
const selection = options.branch ?? await promptForWorktree2(
|
|
23928
|
-
await buildWorktreePromptEntries(
|
|
23929
|
-
linkedWorktrees.map((worktree2) => ({
|
|
23930
|
-
repoName: repository.repoName,
|
|
23931
|
-
worktree: worktree2
|
|
23932
|
-
})),
|
|
23933
|
-
{ catalog: runtime.worktreeCatalog }
|
|
23934
|
-
)
|
|
23935
|
-
);
|
|
23936
|
-
if (!selection) {
|
|
23937
|
-
options.stderr("Aborted\n");
|
|
23938
|
-
return 1;
|
|
23939
|
-
}
|
|
23940
|
-
const worktree = linkedWorktrees.find(
|
|
23941
|
-
(entry) => entry.branch === selection || entry.path === selection
|
|
23942
|
-
);
|
|
23943
|
-
if (!worktree) {
|
|
23944
|
-
emitError3(options, `No linked worktree found for branch: ${selection}`);
|
|
23945
|
-
return 1;
|
|
23946
|
-
}
|
|
23947
|
-
if (!options.dryRun && !options.force && (options.json || isHeadless())) {
|
|
23948
|
-
const message = "--force is required";
|
|
23949
|
-
if (options.json) {
|
|
23950
|
-
emitError3(options, message);
|
|
23951
|
-
} else {
|
|
23952
|
-
options.stderr(
|
|
23953
|
-
`gji remove: ${message} in non-interactive mode (GJI_NO_TUI=1)
|
|
23954
|
-
`
|
|
23955
|
-
);
|
|
23956
|
-
}
|
|
23957
|
-
return 1;
|
|
23958
|
-
}
|
|
23959
|
-
if (!options.dryRun && !options.force && !await confirmRemoval(worktree)) {
|
|
23960
|
-
options.stderr("Aborted\n");
|
|
23961
|
-
return 1;
|
|
23962
|
-
}
|
|
23963
|
-
if (options.dryRun) {
|
|
23964
|
-
if (options.json) {
|
|
23965
|
-
options.stdout(
|
|
23966
|
-
`${JSON.stringify({ branch: worktree.branch, path: worktree.path, dryRun: true }, null, 2)}
|
|
23967
|
-
`
|
|
23968
|
-
);
|
|
23969
|
-
} else {
|
|
23970
|
-
const desc = worktree.branch ? `branch: ${worktree.branch}` : "detached";
|
|
23971
|
-
options.stdout(`Would remove worktree at ${worktree.path} (${desc})
|
|
23972
|
-
`);
|
|
23973
|
-
}
|
|
23974
|
-
return 0;
|
|
23975
|
-
}
|
|
23976
|
-
const config = await loadEffectiveConfig2(
|
|
23977
|
-
repository.repoRoot,
|
|
23978
|
-
void 0,
|
|
23979
|
-
options.stderr
|
|
23980
|
-
);
|
|
23981
|
-
const hooks = extractHooks2(config);
|
|
23982
|
-
let journal;
|
|
23983
|
-
try {
|
|
23984
|
-
journal = await recordUndoOperation(
|
|
23985
|
-
"remove",
|
|
23986
|
-
repository.repoRoot,
|
|
23987
|
-
[worktree],
|
|
23988
|
-
void 0,
|
|
23989
|
-
runtime
|
|
23990
|
-
);
|
|
23991
|
-
} catch (error) {
|
|
23992
|
-
emitError3(
|
|
23993
|
-
options,
|
|
23994
|
-
`could not write undo journal; no worktree was removed: ${toMessage4(error)}`
|
|
23995
|
-
);
|
|
23996
|
-
return 1;
|
|
23997
|
-
}
|
|
23998
|
-
if (!journal) {
|
|
23999
|
-
emitError3(
|
|
24000
|
-
options,
|
|
24001
|
-
"could not capture undo state; no worktree was removed"
|
|
24002
|
-
);
|
|
24003
|
-
return 1;
|
|
24004
|
-
}
|
|
24005
|
-
await runHook2(
|
|
24006
|
-
hooks["before-remove"],
|
|
24007
|
-
worktree.path,
|
|
24008
|
-
{
|
|
24009
|
-
branch: worktree.branch ?? void 0,
|
|
24010
|
-
path: worktree.path,
|
|
24011
|
-
repo: basename12(repository.repoRoot)
|
|
24012
|
-
},
|
|
24013
|
-
options.stderr
|
|
24014
|
-
);
|
|
24015
|
-
try {
|
|
24016
|
-
await removeWorktree2(repository.repoRoot, worktree.path);
|
|
24017
|
-
} catch (error) {
|
|
24018
|
-
if (!isWorktreeForceRemovalError2(error)) {
|
|
24019
|
-
await finalizeUndoOperation(
|
|
24020
|
-
journal,
|
|
24021
|
-
[],
|
|
24022
|
-
void 0,
|
|
24023
|
-
runtime.configStore.GLOBAL_CONFIG_DIRECTORY
|
|
24024
|
-
);
|
|
24025
|
-
throw error;
|
|
24026
|
-
}
|
|
24027
|
-
if (!options.force && !await confirmForceRemoveWorktree(worktree.path)) {
|
|
24028
|
-
await finalizeUndoOperation(
|
|
24029
|
-
journal,
|
|
24030
|
-
[],
|
|
24031
|
-
void 0,
|
|
24032
|
-
runtime.configStore.GLOBAL_CONFIG_DIRECTORY
|
|
24033
|
-
);
|
|
24034
|
-
options.stderr("Aborted\n");
|
|
24035
|
-
return 1;
|
|
24036
|
-
}
|
|
24037
|
-
try {
|
|
24038
|
-
await forceRemoveWorktree2(repository.repoRoot, worktree.path);
|
|
24039
|
-
} catch (forceError) {
|
|
24040
|
-
await finalizeUndoOperation(
|
|
24041
|
-
journal,
|
|
24042
|
-
[],
|
|
24043
|
-
void 0,
|
|
24044
|
-
runtime.configStore.GLOBAL_CONFIG_DIRECTORY
|
|
24045
|
-
);
|
|
24046
|
-
emitError3(
|
|
24047
|
-
options,
|
|
24048
|
-
`Failed to remove worktree at ${worktree.path}: ${toMessage4(forceError)}`
|
|
24049
|
-
);
|
|
24050
|
-
return 1;
|
|
24051
|
-
}
|
|
24052
|
-
}
|
|
24053
|
-
await finalizeUndoOperation(
|
|
24054
|
-
journal,
|
|
24055
|
-
[worktree],
|
|
24056
|
-
void 0,
|
|
24057
|
-
runtime.configStore.GLOBAL_CONFIG_DIRECTORY
|
|
24058
|
-
);
|
|
24059
|
-
await releaseWorktreeSlot2(worktree.path);
|
|
24060
|
-
if (worktree.branch) {
|
|
24061
|
-
try {
|
|
24062
|
-
await deleteBranch2(repository.repoRoot, worktree.branch);
|
|
24063
|
-
} catch (error) {
|
|
24064
|
-
if (!isBranchUnmergedError2(error)) {
|
|
24065
|
-
throw error;
|
|
24066
|
-
}
|
|
24067
|
-
if (options.force || await confirmForceDeleteBranch(worktree.branch)) {
|
|
24068
|
-
try {
|
|
24069
|
-
await forceDeleteBranch2(repository.repoRoot, worktree.branch);
|
|
24070
|
-
} catch (forceError) {
|
|
24071
|
-
options.stderr(
|
|
24072
|
-
`Failed to delete branch ${worktree.branch}: ${toMessage4(forceError)}
|
|
24073
|
-
`
|
|
24074
|
-
);
|
|
24075
|
-
}
|
|
24076
|
-
} else {
|
|
24077
|
-
options.stderr(
|
|
24078
|
-
`Branch ${worktree.branch} was not deleted (has unmerged commits)
|
|
24079
|
-
`
|
|
24080
|
-
);
|
|
24081
|
-
}
|
|
24082
|
-
}
|
|
24083
|
-
}
|
|
24084
|
-
if (options.json) {
|
|
24085
|
-
options.stdout(
|
|
24086
|
-
`${JSON.stringify({ branch: worktree.branch, path: worktree.path, deleted: true }, null, 2)}
|
|
24087
|
-
`
|
|
24088
|
-
);
|
|
24089
|
-
} else {
|
|
24090
|
-
options.stderr("undo: gji undo\n");
|
|
24091
|
-
await writeOutput3(repository.repoRoot, options.stdout);
|
|
24092
|
-
}
|
|
24093
|
-
return 0;
|
|
24094
|
-
};
|
|
24095
|
-
}
|
|
24096
|
-
var runRemoveCommand = createRemoveCommand();
|
|
24097
|
-
async function defaultPromptForWorktree3(worktrees) {
|
|
24098
|
-
return promptForSingleWorktree("Choose a worktree to finish", worktrees);
|
|
24099
|
-
}
|
|
24100
|
-
async function defaultConfirmRemoval2(worktree) {
|
|
24101
|
-
const choice = await ye({
|
|
24102
|
-
message: worktree.branch ? `Remove worktree and delete branch ${worktree.branch}?` : `Remove detached worktree ${worktree.path}?`,
|
|
24103
|
-
active: "Yes",
|
|
24104
|
-
inactive: "No",
|
|
24105
|
-
initialValue: true
|
|
24106
|
-
});
|
|
24107
|
-
return !pD(choice) && choice;
|
|
24108
|
-
}
|
|
24109
|
-
async function writeOutput3(repoRoot, stdout3) {
|
|
24110
|
-
await writeShellOutput(REMOVE_OUTPUT_FILE_ENV, repoRoot, stdout3);
|
|
24111
|
-
}
|
|
24112
|
-
function emitError3(options, message) {
|
|
24113
|
-
if (options.json) {
|
|
24114
|
-
options.stderr(`${JSON.stringify({ error: message }, null, 2)}
|
|
24115
|
-
`);
|
|
24116
|
-
} else {
|
|
24117
|
-
options.stderr(`${message}
|
|
24118
|
-
`);
|
|
24119
|
-
}
|
|
24120
|
-
}
|
|
24121
|
-
function toMessage4(error) {
|
|
24122
|
-
return error instanceof Error ? error.message : String(error);
|
|
24123
|
-
}
|
|
24124
|
-
|
|
24125
24081
|
// src/cli/commands/root.ts
|
|
24126
24082
|
var ROOT_OUTPUT_FILE_ENV = "GJI_ROOT_OUTPUT_FILE";
|
|
24127
24083
|
async function runRootCommand(options) {
|
|
@@ -24142,13 +24098,17 @@ async function runRootCommand(options) {
|
|
|
24142
24098
|
|
|
24143
24099
|
// src/cli/commands/run-hook.ts
|
|
24144
24100
|
var VALID_HOOKS = [
|
|
24101
|
+
"after-branch-create",
|
|
24145
24102
|
"after-create",
|
|
24146
24103
|
"after-enter",
|
|
24104
|
+
"after-switch",
|
|
24147
24105
|
"before-remove"
|
|
24148
24106
|
];
|
|
24149
24107
|
var CAMEL_ALIASES = {
|
|
24108
|
+
afterBranchCreate: "after-branch-create",
|
|
24150
24109
|
afterCreate: "after-create",
|
|
24151
24110
|
afterEnter: "after-enter",
|
|
24111
|
+
afterSwitch: "after-switch",
|
|
24152
24112
|
beforeRemove: "before-remove"
|
|
24153
24113
|
};
|
|
24154
24114
|
function isValidHook(hook) {
|
|
@@ -24345,7 +24305,7 @@ async function runSyncCommand(options) {
|
|
|
24345
24305
|
resolveConfiguredString2(config.syncDefaultBranch) ?? void 0
|
|
24346
24306
|
);
|
|
24347
24307
|
} catch {
|
|
24348
|
-
|
|
24308
|
+
emitError3(options, `Unable to reach remote '${remote}'`);
|
|
24349
24309
|
if (!options.json) {
|
|
24350
24310
|
options.stderr(
|
|
24351
24311
|
`Hint: Add the remote with: git remote add ${remote} <url>
|
|
@@ -24355,7 +24315,7 @@ async function runSyncCommand(options) {
|
|
|
24355
24315
|
return 1;
|
|
24356
24316
|
}
|
|
24357
24317
|
if (!remoteBase) {
|
|
24358
|
-
|
|
24318
|
+
emitError3(options, "Unable to determine the default branch for sync.");
|
|
24359
24319
|
if (!options.json) {
|
|
24360
24320
|
options.stderr(
|
|
24361
24321
|
`Hint: Add the remote with: git remote add ${remote} <url>
|
|
@@ -24370,7 +24330,7 @@ async function runSyncCommand(options) {
|
|
|
24370
24330
|
options.all
|
|
24371
24331
|
);
|
|
24372
24332
|
if (targetWorktrees === "detached") {
|
|
24373
|
-
|
|
24333
|
+
emitError3(
|
|
24374
24334
|
options,
|
|
24375
24335
|
`Cannot sync detached worktree: ${repository.currentRoot}`
|
|
24376
24336
|
);
|
|
@@ -24378,14 +24338,14 @@ async function runSyncCommand(options) {
|
|
|
24378
24338
|
}
|
|
24379
24339
|
for (const worktree of targetWorktrees) {
|
|
24380
24340
|
if (await isDirtyWorktree2(worktree.path)) {
|
|
24381
|
-
|
|
24341
|
+
emitError3(options, `Cannot sync dirty worktree: ${worktree.path}`);
|
|
24382
24342
|
return 1;
|
|
24383
24343
|
}
|
|
24384
24344
|
}
|
|
24385
24345
|
try {
|
|
24386
24346
|
await runGit2(repository.repoRoot, ["fetch", "--prune", remote]);
|
|
24387
24347
|
} catch {
|
|
24388
|
-
|
|
24348
|
+
emitError3(options, `Failed to fetch from remote '${remote}'`);
|
|
24389
24349
|
if (!options.json) {
|
|
24390
24350
|
options.stderr(
|
|
24391
24351
|
`Hint: Add the remote with: git remote add ${remote} <url>
|
|
@@ -24417,7 +24377,7 @@ async function runSyncCommand(options) {
|
|
|
24417
24377
|
}
|
|
24418
24378
|
return 0;
|
|
24419
24379
|
}
|
|
24420
|
-
function
|
|
24380
|
+
function emitError3(options, message) {
|
|
24421
24381
|
if (options.json) {
|
|
24422
24382
|
options.stderr(`${JSON.stringify({ error: message }, null, 2)}
|
|
24423
24383
|
`);
|
|
@@ -24447,7 +24407,7 @@ function resolveConfiguredString2(value) {
|
|
|
24447
24407
|
|
|
24448
24408
|
// src/cli/commands/sync-files-command.ts
|
|
24449
24409
|
import { homedir as homedir9 } from "node:os";
|
|
24450
|
-
import { join as
|
|
24410
|
+
import { join as join16 } from "node:path";
|
|
24451
24411
|
async function runSyncFilesCommand(options) {
|
|
24452
24412
|
const runtime = options.runtime ?? defaultCliDependencies;
|
|
24453
24413
|
const { validateSyncFilePattern: validateSyncFilePattern2 } = runtime.filesystem;
|
|
@@ -24601,7 +24561,7 @@ function writeError(options, message) {
|
|
|
24601
24561
|
}
|
|
24602
24562
|
function expandTilde3(value, home) {
|
|
24603
24563
|
if (value === "~") return home;
|
|
24604
|
-
if (value.startsWith("~/")) return
|
|
24564
|
+
if (value.startsWith("~/")) return join16(home, value.slice(2));
|
|
24605
24565
|
return value;
|
|
24606
24566
|
}
|
|
24607
24567
|
|
|
@@ -24638,144 +24598,6 @@ function emitTaskError(options, message) {
|
|
|
24638
24598
|
return 1;
|
|
24639
24599
|
}
|
|
24640
24600
|
|
|
24641
|
-
// src/cli/commands/warp.ts
|
|
24642
|
-
var WARP_OUTPUT_FILE_ENV = "GJI_WARP_OUTPUT_FILE";
|
|
24643
|
-
async function runWarpCommand(options) {
|
|
24644
|
-
return runWarpNavigate(options);
|
|
24645
|
-
}
|
|
24646
|
-
async function runWarpNavigate(options) {
|
|
24647
|
-
const runtime = options.runtime ?? defaultCliDependencies;
|
|
24648
|
-
const { recordWorktreeUsage: recordWorktreeUsage2 } = runtime.history;
|
|
24649
|
-
if ((isHeadless() || options.json) && !options.branch) {
|
|
24650
|
-
const message = "branch argument is required";
|
|
24651
|
-
if (options.json) {
|
|
24652
|
-
options.stderr(`${JSON.stringify({ error: message }, null, 2)}
|
|
24653
|
-
`);
|
|
24654
|
-
} else {
|
|
24655
|
-
options.stderr(
|
|
24656
|
-
"gji warp: branch argument is required in non-interactive mode (GJI_NO_TUI=1)\n"
|
|
24657
|
-
);
|
|
24658
|
-
}
|
|
24659
|
-
return 1;
|
|
24660
|
-
}
|
|
24661
|
-
const target = await resolveWarpTarget({
|
|
24662
|
-
...options,
|
|
24663
|
-
commandName: "gji warp",
|
|
24664
|
-
json: options.json,
|
|
24665
|
-
queryPullRequests: options.queryPullRequests,
|
|
24666
|
-
runtime
|
|
24667
|
-
});
|
|
24668
|
-
if (!target) return 1;
|
|
24669
|
-
if (options.json) {
|
|
24670
|
-
options.stdout(
|
|
24671
|
-
`${JSON.stringify(
|
|
24672
|
-
createNavigationTarget(target.repository, target.path, target.branch),
|
|
24673
|
-
null,
|
|
24674
|
-
2
|
|
24675
|
-
)}
|
|
24676
|
-
`
|
|
24677
|
-
);
|
|
24678
|
-
return 0;
|
|
24679
|
-
}
|
|
24680
|
-
await recordWorktreeUsage2(target.path, target.branch);
|
|
24681
|
-
await writeShellOutput(WARP_OUTPUT_FILE_ENV, target.path, options.stdout);
|
|
24682
|
-
return 0;
|
|
24683
|
-
}
|
|
24684
|
-
async function resolveWarpTarget(options) {
|
|
24685
|
-
const cmd = options.commandName ?? "gji";
|
|
24686
|
-
const runtime = options.runtime ?? defaultCliDependencies;
|
|
24687
|
-
const { detectRepository: detectRepository2 } = runtime.repositoryContext;
|
|
24688
|
-
const { loadRegistry: loadRegistry2 } = runtime.repositoryRegistry;
|
|
24689
|
-
const sourceDependencies = {
|
|
24690
|
-
...runtime.repositoryContext,
|
|
24691
|
-
...runtime.repositoryRegistry,
|
|
24692
|
-
...runtime.worktrees
|
|
24693
|
-
};
|
|
24694
|
-
const emitError5 = (message, hint) => {
|
|
24695
|
-
if (options.json) {
|
|
24696
|
-
options.stderr(`${JSON.stringify({ error: message }, null, 2)}
|
|
24697
|
-
`);
|
|
24698
|
-
} else {
|
|
24699
|
-
options.stderr(`${cmd}: ${message}
|
|
24700
|
-
`);
|
|
24701
|
-
if (hint) options.stderr(hint);
|
|
24702
|
-
}
|
|
24703
|
-
};
|
|
24704
|
-
const registry = await loadRegistry2();
|
|
24705
|
-
if (registry.length === 0) {
|
|
24706
|
-
emitError5(
|
|
24707
|
-
"not in a git repository and no repos registered yet.",
|
|
24708
|
-
"Use any gji command inside a repository to register it.\n"
|
|
24709
|
-
);
|
|
24710
|
-
return null;
|
|
24711
|
-
}
|
|
24712
|
-
const registered = await listRegisteredWorktreeSources({
|
|
24713
|
-
cwd: options.cwd,
|
|
24714
|
-
repositoryPort: sourceDependencies
|
|
24715
|
-
});
|
|
24716
|
-
const skippedRegisteredRepos = registered.skipped.length;
|
|
24717
|
-
const allItems = registered.sources.filter(
|
|
24718
|
-
(item) => item.repoRoot !== options.excludeRepoRoot
|
|
24719
|
-
);
|
|
24720
|
-
if (allItems.length === 0) {
|
|
24721
|
-
emitError5(
|
|
24722
|
-
"no accessible worktrees found in any registered repo.",
|
|
24723
|
-
skippedRegisteredRepos > 0 ? "Hint: Run 'gji doctor' to inspect stale repository entries.\n" : void 0
|
|
24724
|
-
);
|
|
24725
|
-
return null;
|
|
24726
|
-
}
|
|
24727
|
-
const promptSources = allItems.map((item) => ({
|
|
24728
|
-
repoRoot: item.repoRoot,
|
|
24729
|
-
repoName: item.repoName,
|
|
24730
|
-
worktree: item.worktree
|
|
24731
|
-
}));
|
|
24732
|
-
if (options.branch) {
|
|
24733
|
-
const match = resolveWorktreeQuery(promptSources, options.branch);
|
|
24734
|
-
if (!match) {
|
|
24735
|
-
emitError5(
|
|
24736
|
-
`no worktree found matching: ${options.branch}`,
|
|
24737
|
-
skippedRegisteredRepos > 0 ? "Hint: Run 'gji doctor' to inspect stale repository entries.\n" : void 0
|
|
24738
|
-
);
|
|
24739
|
-
return null;
|
|
24740
|
-
}
|
|
24741
|
-
return {
|
|
24742
|
-
branch: match.worktree.branch,
|
|
24743
|
-
path: match.worktree.path,
|
|
24744
|
-
repository: createNavigationRepository(
|
|
24745
|
-
match.repoName,
|
|
24746
|
-
match.repoRoot ?? (await detectRepository2(match.worktree.path)).repoRoot
|
|
24747
|
-
)
|
|
24748
|
-
};
|
|
24749
|
-
}
|
|
24750
|
-
const promptEntries = await buildWorktreePromptEntries(promptSources, {
|
|
24751
|
-
metadata: "fast",
|
|
24752
|
-
catalog: withPullRequestQueries(runtime, options.queryPullRequests)
|
|
24753
|
-
});
|
|
24754
|
-
const path9 = await promptForWarpTarget(promptEntries);
|
|
24755
|
-
if (!path9) {
|
|
24756
|
-
options.stderr("Aborted\n");
|
|
24757
|
-
return null;
|
|
24758
|
-
}
|
|
24759
|
-
const chosenSource = promptSources.find(
|
|
24760
|
-
(item) => item.worktree.path === path9
|
|
24761
|
-
);
|
|
24762
|
-
if (chosenSource === void 0) {
|
|
24763
|
-
emitError5("selected worktree is no longer registered");
|
|
24764
|
-
return null;
|
|
24765
|
-
}
|
|
24766
|
-
return {
|
|
24767
|
-
branch: chosenSource.worktree.branch,
|
|
24768
|
-
path: path9,
|
|
24769
|
-
repository: createNavigationRepository(
|
|
24770
|
-
chosenSource.repoName,
|
|
24771
|
-
chosenSource.repoRoot ?? (await detectRepository2(chosenSource.worktree.path)).repoRoot
|
|
24772
|
-
)
|
|
24773
|
-
};
|
|
24774
|
-
}
|
|
24775
|
-
async function promptForWarpTarget(items) {
|
|
24776
|
-
return promptForSingleWorktree("Warp to a worktree", items);
|
|
24777
|
-
}
|
|
24778
|
-
|
|
24779
24601
|
// src/cli/program.ts
|
|
24780
24602
|
function createProgram() {
|
|
24781
24603
|
const program2 = new Command();
|
|
@@ -24969,29 +24791,17 @@ function registerCommands(program2) {
|
|
|
24969
24791
|
).option(
|
|
24970
24792
|
"--stale",
|
|
24971
24793
|
"only target clean worktrees whose upstream is gone and branch is merged into the default branch"
|
|
24972
|
-
).option("--dry-run", "show what would be deleted without removing anything").option(
|
|
24973
|
-
"--json",
|
|
24974
|
-
"emit JSON on success or error instead of human-readable output"
|
|
24975
|
-
).action(notImplemented("clean"));
|
|
24976
|
-
program2.command("remove [branch]").alias("rm").description(
|
|
24977
|
-
"deprecated: use gji done for one worktree or gji clean for bulk cleanup"
|
|
24978
24794
|
).option(
|
|
24979
|
-
"
|
|
24980
|
-
"
|
|
24795
|
+
"--path <pattern>",
|
|
24796
|
+
"only target worktrees under a directory or matching a path glob (repeatable)",
|
|
24797
|
+
collectOption
|
|
24981
24798
|
).option("--dry-run", "show what would be deleted without removing anything").option(
|
|
24982
24799
|
"--json",
|
|
24983
24800
|
"emit JSON on success or error instead of human-readable output"
|
|
24984
|
-
).action(notImplemented("
|
|
24801
|
+
).action(notImplemented("clean"));
|
|
24985
24802
|
program2.command("run-hook <hook>").alias("trigger-hook").description(
|
|
24986
|
-
"run a named hook (after-create, after-enter, before-remove) in the current worktree"
|
|
24803
|
+
"run a named hook (after-branch-create, after-create, after-enter, after-switch, before-remove) in the current worktree"
|
|
24987
24804
|
).action(notImplemented("run-hook"));
|
|
24988
|
-
program2.command("warp [branch]").description("deprecated: use gji go (press Tab for all known repos)").option(
|
|
24989
|
-
"--print",
|
|
24990
|
-
"print the resolved worktree path without changing directory"
|
|
24991
|
-
).option(
|
|
24992
|
-
"--json",
|
|
24993
|
-
"emit JSON on success or error instead of human-readable output"
|
|
24994
|
-
).action(notImplemented("warp"));
|
|
24995
24805
|
const configCommand = program2.command("config").description("manage global config defaults").action(notImplemented("config"));
|
|
24996
24806
|
configCommand.command("get [key]").description("print the global config or a single key").action(notImplemented("config get"));
|
|
24997
24807
|
configCommand.command("set <key> <value>").description("set a global config value").action(notImplemented("config set"));
|
|
@@ -25336,6 +25146,7 @@ function attachCommandActions(program2, options) {
|
|
|
25336
25146
|
dryRun: commandOptions2.dryRun,
|
|
25337
25147
|
force: commandOptions2.force,
|
|
25338
25148
|
json: commandOptions2.json,
|
|
25149
|
+
paths: commandOptions2.path,
|
|
25339
25150
|
stale: commandOptions2.stale,
|
|
25340
25151
|
runtime: options.dependencies,
|
|
25341
25152
|
stderr: options.stderr,
|
|
@@ -25346,31 +25157,6 @@ function attachCommandActions(program2, options) {
|
|
|
25346
25157
|
}
|
|
25347
25158
|
}
|
|
25348
25159
|
);
|
|
25349
|
-
const runRemovalCommand = async (branch, commandOptions2 = {}) => {
|
|
25350
|
-
const exitCode = await runRemoveCommand({
|
|
25351
|
-
branch,
|
|
25352
|
-
cwd: options.cwd,
|
|
25353
|
-
dryRun: commandOptions2.dryRun,
|
|
25354
|
-
force: commandOptions2.force,
|
|
25355
|
-
json: commandOptions2.json,
|
|
25356
|
-
runtime: options.dependencies,
|
|
25357
|
-
stderr: options.stderr,
|
|
25358
|
-
stdout: options.stdout
|
|
25359
|
-
});
|
|
25360
|
-
if (exitCode !== 0) {
|
|
25361
|
-
throw commanderExit(exitCode);
|
|
25362
|
-
}
|
|
25363
|
-
};
|
|
25364
|
-
requireCommand(program2, "remove").action(
|
|
25365
|
-
async (branch, commandOptions2) => {
|
|
25366
|
-
if (!commandOptions2.json) {
|
|
25367
|
-
options.stderr(
|
|
25368
|
-
"gji remove is deprecated; use 'gji done' for one worktree or 'gji clean' for bulk cleanup.\n"
|
|
25369
|
-
);
|
|
25370
|
-
}
|
|
25371
|
-
return runRemovalCommand(branch, commandOptions2);
|
|
25372
|
-
}
|
|
25373
|
-
);
|
|
25374
25160
|
requireCommand(program2, "run-hook").action(async (hook) => {
|
|
25375
25161
|
const exitCode = await runHookCommand({
|
|
25376
25162
|
cwd: options.cwd,
|
|
@@ -25382,26 +25168,6 @@ function attachCommandActions(program2, options) {
|
|
|
25382
25168
|
throw commanderExit(exitCode);
|
|
25383
25169
|
}
|
|
25384
25170
|
});
|
|
25385
|
-
requireCommand(program2, "warp").action(
|
|
25386
|
-
async (branch, commandOptions2) => {
|
|
25387
|
-
if (!commandOptions2.json) {
|
|
25388
|
-
options.stderr(
|
|
25389
|
-
"gji warp is deprecated; use 'gji go' (press Tab for all repositories).\n"
|
|
25390
|
-
);
|
|
25391
|
-
}
|
|
25392
|
-
const exitCode = await runWarpCommand({
|
|
25393
|
-
branch,
|
|
25394
|
-
cwd: options.cwd,
|
|
25395
|
-
json: commandOptions2.json,
|
|
25396
|
-
runtime: options.dependencies,
|
|
25397
|
-
stderr: options.stderr,
|
|
25398
|
-
stdout: options.stdout
|
|
25399
|
-
});
|
|
25400
|
-
if (exitCode !== 0) {
|
|
25401
|
-
throw commanderExit(exitCode);
|
|
25402
|
-
}
|
|
25403
|
-
}
|
|
25404
|
-
);
|
|
25405
25171
|
const configCommand = requireCommand(program2, "config");
|
|
25406
25172
|
configCommand.action(async () => {
|
|
25407
25173
|
const exitCode = await runConfigCommand({
|
|
@@ -25463,6 +25229,9 @@ function requireCommand(parent, name) {
|
|
|
25463
25229
|
}
|
|
25464
25230
|
return command;
|
|
25465
25231
|
}
|
|
25232
|
+
function collectOption(value, previous = []) {
|
|
25233
|
+
return [...previous, value];
|
|
25234
|
+
}
|
|
25466
25235
|
function notImplemented(commandName) {
|
|
25467
25236
|
return () => {
|
|
25468
25237
|
throw new Error(`'${commandName}' is not implemented yet.`);
|