@solaqua/gji 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -4
- package/dist/cli.js +64 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.js +2 -1
- package/dist/editor.d.ts +8 -0
- package/dist/editor.js +17 -0
- package/dist/gji-bundle.mjs +1574 -994
- package/dist/go.js +22 -4
- package/dist/hooks.d.ts +5 -4
- package/dist/hooks.js +61 -9
- package/dist/init.js +26 -11
- package/dist/install-prompt.js +9 -1
- package/dist/new.d.ts +3 -0
- package/dist/new.js +33 -1
- package/dist/open.d.ts +20 -0
- package/dist/open.js +155 -0
- package/dist/repo-registry.d.ts +8 -0
- package/dist/repo-registry.js +52 -0
- package/dist/shell-completion.js +124 -89
- package/dist/warp.d.ts +20 -0
- package/dist/warp.js +196 -0
- package/man/man1/gji-back.1 +1 -1
- package/man/man1/gji-clean.1 +1 -1
- package/man/man1/gji-completion.1 +1 -1
- package/man/man1/gji-config.1 +1 -1
- package/man/man1/gji-go.1 +1 -1
- package/man/man1/gji-history.1 +1 -1
- package/man/man1/gji-init.1 +1 -1
- package/man/man1/gji-ls.1 +1 -1
- package/man/man1/gji-new.1 +7 -1
- package/man/man1/gji-open.1 +19 -0
- package/man/man1/gji-pr.1 +1 -1
- package/man/man1/gji-remove.1 +1 -1
- package/man/man1/gji-root.1 +1 -1
- package/man/man1/gji-status.1 +1 -1
- package/man/man1/gji-sync.1 +1 -1
- package/man/man1/gji-trigger-hook.1 +1 -1
- package/man/man1/gji-warp.1 +19 -0
- package/man/man1/gji.1 +11 -1
- package/package.json +4 -2
package/dist/gji-bundle.mjs
CHANGED
|
@@ -3951,8 +3951,8 @@ var require_graceful_fs = __commonJS({
|
|
|
3951
3951
|
fs6.createReadStream = createReadStream;
|
|
3952
3952
|
fs6.createWriteStream = createWriteStream;
|
|
3953
3953
|
var fs$readFile = fs6.readFile;
|
|
3954
|
-
fs6.readFile =
|
|
3955
|
-
function
|
|
3954
|
+
fs6.readFile = readFile5;
|
|
3955
|
+
function readFile5(path9, options, cb) {
|
|
3956
3956
|
if (typeof options === "function")
|
|
3957
3957
|
cb = options, options = null;
|
|
3958
3958
|
return go$readFile(path9, options, cb);
|
|
@@ -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 join9 = __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
|
+
join9(etc, name, "config"),
|
|
5415
|
+
join9(etc, name + "rc")
|
|
5416
5416
|
].forEach(addConfigFile);
|
|
5417
5417
|
if (home)
|
|
5418
5418
|
[
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5419
|
+
join9(home, ".config", name, "config"),
|
|
5420
|
+
join9(home, ".config", name),
|
|
5421
|
+
join9(home, "." + name, "config"),
|
|
5422
|
+
join9(home, "." + name + "rc")
|
|
5423
5423
|
].forEach(addConfigFile);
|
|
5424
5424
|
addConfigFile(cc.find("." + name + "rc"));
|
|
5425
5425
|
if (env4.config) addConfigFile(env4.config);
|
|
@@ -5925,8 +5925,8 @@ var require_graceful_fs2 = __commonJS({
|
|
|
5925
5925
|
fs6.createReadStream = createReadStream;
|
|
5926
5926
|
fs6.createWriteStream = createWriteStream;
|
|
5927
5927
|
var fs$readFile = fs6.readFile;
|
|
5928
|
-
fs6.readFile =
|
|
5929
|
-
function
|
|
5928
|
+
fs6.readFile = readFile5;
|
|
5929
|
+
function readFile5(path9, options, cb) {
|
|
5930
5930
|
if (typeof options === "function")
|
|
5931
5931
|
cb = options, options = null;
|
|
5932
5932
|
return go$readFile(path9, options, cb);
|
|
@@ -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 dirname9 = path9.dirname(name2);
|
|
6761
|
+
if (dirname9 === name2) {
|
|
6762
6762
|
return original;
|
|
6763
6763
|
}
|
|
6764
|
-
return find(
|
|
6764
|
+
return find(dirname9, original);
|
|
6765
6765
|
} catch (error) {
|
|
6766
6766
|
if (name2 === original) {
|
|
6767
6767
|
if (error.code === "ENOENT") {
|
|
@@ -9422,7 +9422,7 @@ var require_picocolors = __commonJS({
|
|
|
9422
9422
|
});
|
|
9423
9423
|
|
|
9424
9424
|
// src/index.ts
|
|
9425
|
-
import { homedir as
|
|
9425
|
+
import { homedir as homedir6 } from "node:os";
|
|
9426
9426
|
|
|
9427
9427
|
// src/config.ts
|
|
9428
9428
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
@@ -9433,6 +9433,7 @@ var GLOBAL_CONFIG_DIRECTORY = ".config/gji";
|
|
|
9433
9433
|
var GLOBAL_CONFIG_NAME = "config.json";
|
|
9434
9434
|
var KNOWN_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
9435
9435
|
"branchPrefix",
|
|
9436
|
+
"editor",
|
|
9436
9437
|
"hooks",
|
|
9437
9438
|
"installSaveTarget",
|
|
9438
9439
|
"shellIntegration",
|
|
@@ -9739,7 +9740,7 @@ var retryifyAsync = (fn, options) => {
|
|
|
9739
9740
|
throw error;
|
|
9740
9741
|
const delay2 = Math.round(interval * Math.random());
|
|
9741
9742
|
if (delay2 > 0) {
|
|
9742
|
-
const delayPromise = new Promise((
|
|
9743
|
+
const delayPromise = new Promise((resolve5) => setTimeout(resolve5, delay2));
|
|
9743
9744
|
return delayPromise.then(() => attempt.apply(void 0, args));
|
|
9744
9745
|
} else {
|
|
9745
9746
|
return attempt.apply(void 0, args);
|
|
@@ -9998,14 +9999,14 @@ var Temp = {
|
|
|
9998
9999
|
}
|
|
9999
10000
|
},
|
|
10000
10001
|
truncate: (filePath) => {
|
|
10001
|
-
const
|
|
10002
|
-
if (
|
|
10002
|
+
const basename8 = path2.basename(filePath);
|
|
10003
|
+
if (basename8.length <= LIMIT_BASENAME_LENGTH)
|
|
10003
10004
|
return filePath;
|
|
10004
|
-
const truncable = /^(\.?)(.*?)((?:\.[^.]+)?(?:\.tmp-\d{10}[a-f0-9]{6})?)$/.exec(
|
|
10005
|
+
const truncable = /^(\.?)(.*?)((?:\.[^.]+)?(?:\.tmp-\d{10}[a-f0-9]{6})?)$/.exec(basename8);
|
|
10005
10006
|
if (!truncable)
|
|
10006
10007
|
return filePath;
|
|
10007
|
-
const truncationLength =
|
|
10008
|
-
return `${filePath.slice(0, -
|
|
10008
|
+
const truncationLength = basename8.length - LIMIT_BASENAME_LENGTH;
|
|
10009
|
+
return `${filePath.slice(0, -basename8.length)}${truncable[1]}${truncable[2].slice(0, -truncationLength)}${truncable[3]}`;
|
|
10009
10010
|
}
|
|
10010
10011
|
};
|
|
10011
10012
|
node_default(Temp.purgeSyncAll);
|
|
@@ -11305,14 +11306,14 @@ var TimeoutError = class extends Error {
|
|
|
11305
11306
|
|
|
11306
11307
|
// node_modules/.pnpm/ky@1.14.3/node_modules/ky/distribution/utils/timeout.js
|
|
11307
11308
|
async function timeout(request, init, abortController, options) {
|
|
11308
|
-
return new Promise((
|
|
11309
|
+
return new Promise((resolve5, reject) => {
|
|
11309
11310
|
const timeoutId = setTimeout(() => {
|
|
11310
11311
|
if (abortController) {
|
|
11311
11312
|
abortController.abort();
|
|
11312
11313
|
}
|
|
11313
11314
|
reject(new TimeoutError(request));
|
|
11314
11315
|
}, options.timeout);
|
|
11315
|
-
void options.fetch(request, init).then(
|
|
11316
|
+
void options.fetch(request, init).then(resolve5).catch(reject).then(() => {
|
|
11316
11317
|
clearTimeout(timeoutId);
|
|
11317
11318
|
});
|
|
11318
11319
|
});
|
|
@@ -11320,7 +11321,7 @@ async function timeout(request, init, abortController, options) {
|
|
|
11320
11321
|
|
|
11321
11322
|
// node_modules/.pnpm/ky@1.14.3/node_modules/ky/distribution/utils/delay.js
|
|
11322
11323
|
async function delay(ms, { signal }) {
|
|
11323
|
-
return new Promise((
|
|
11324
|
+
return new Promise((resolve5, reject) => {
|
|
11324
11325
|
if (signal) {
|
|
11325
11326
|
signal.throwIfAborted();
|
|
11326
11327
|
signal.addEventListener("abort", abortHandler, { once: true });
|
|
@@ -11331,7 +11332,7 @@ async function delay(ms, { signal }) {
|
|
|
11331
11332
|
}
|
|
11332
11333
|
const timeoutId = setTimeout(() => {
|
|
11333
11334
|
signal?.removeEventListener("abort", abortHandler);
|
|
11334
|
-
|
|
11335
|
+
resolve5();
|
|
11335
11336
|
}, ms);
|
|
11336
11337
|
});
|
|
11337
11338
|
}
|
|
@@ -13035,18 +13036,50 @@ import { basename as basename2 } from "node:path";
|
|
|
13035
13036
|
import { spawn as spawn2 } from "node:child_process";
|
|
13036
13037
|
async function runHook(hookCmd, cwd, context, stderr) {
|
|
13037
13038
|
if (!hookCmd) return;
|
|
13039
|
+
if (Array.isArray(hookCmd)) {
|
|
13040
|
+
await runArgvHook(hookCmd, cwd, context, stderr);
|
|
13041
|
+
return;
|
|
13042
|
+
}
|
|
13043
|
+
await runShellHook(hookCmd, cwd, context, stderr);
|
|
13044
|
+
}
|
|
13045
|
+
async function runArgvHook(hookCmd, cwd, context, stderr) {
|
|
13046
|
+
const [command, ...args] = hookCmd.map((arg) => interpolate(arg, context));
|
|
13047
|
+
if (!command) {
|
|
13048
|
+
stderr("gji: hook argv command must include a non-empty command\n");
|
|
13049
|
+
return;
|
|
13050
|
+
}
|
|
13051
|
+
await new Promise((resolve5) => {
|
|
13052
|
+
const child = spawn2(command, args, {
|
|
13053
|
+
cwd,
|
|
13054
|
+
shell: false,
|
|
13055
|
+
stdio: ["ignore", "inherit", "pipe"],
|
|
13056
|
+
env: hookEnvironment(context)
|
|
13057
|
+
});
|
|
13058
|
+
child.stderr.on("data", (chunk) => {
|
|
13059
|
+
stderr(chunk.toString());
|
|
13060
|
+
});
|
|
13061
|
+
child.on("close", (code) => {
|
|
13062
|
+
if (code !== 0) {
|
|
13063
|
+
stderr(`gji: hook exited with code ${code}: ${formatArgvHook(command, args)}
|
|
13064
|
+
`);
|
|
13065
|
+
}
|
|
13066
|
+
resolve5();
|
|
13067
|
+
});
|
|
13068
|
+
child.on("error", (err) => {
|
|
13069
|
+
stderr(`gji: hook failed to start: ${err.message}
|
|
13070
|
+
`);
|
|
13071
|
+
resolve5();
|
|
13072
|
+
});
|
|
13073
|
+
});
|
|
13074
|
+
}
|
|
13075
|
+
async function runShellHook(hookCmd, cwd, context, stderr) {
|
|
13038
13076
|
const interpolated = interpolate(hookCmd, context);
|
|
13039
|
-
await new Promise((
|
|
13077
|
+
await new Promise((resolve5) => {
|
|
13040
13078
|
const child = spawn2(interpolated, {
|
|
13041
13079
|
cwd,
|
|
13042
13080
|
shell: true,
|
|
13043
13081
|
stdio: ["ignore", "inherit", "pipe"],
|
|
13044
|
-
env:
|
|
13045
|
-
...process.env,
|
|
13046
|
-
GJI_BRANCH: context.branch ?? "",
|
|
13047
|
-
GJI_PATH: context.path,
|
|
13048
|
-
GJI_REPO: context.repo
|
|
13049
|
-
}
|
|
13082
|
+
env: hookEnvironment(context)
|
|
13050
13083
|
});
|
|
13051
13084
|
child.stderr.on("data", (chunk) => {
|
|
13052
13085
|
stderr(chunk.toString());
|
|
@@ -13056,15 +13089,26 @@ async function runHook(hookCmd, cwd, context, stderr) {
|
|
|
13056
13089
|
stderr(`gji: hook exited with code ${code}: ${interpolated}
|
|
13057
13090
|
`);
|
|
13058
13091
|
}
|
|
13059
|
-
|
|
13092
|
+
resolve5();
|
|
13060
13093
|
});
|
|
13061
13094
|
child.on("error", (err) => {
|
|
13062
13095
|
stderr(`gji: hook failed to start: ${err.message}
|
|
13063
13096
|
`);
|
|
13064
|
-
|
|
13097
|
+
resolve5();
|
|
13065
13098
|
});
|
|
13066
13099
|
});
|
|
13067
13100
|
}
|
|
13101
|
+
function hookEnvironment(context) {
|
|
13102
|
+
return {
|
|
13103
|
+
...process.env,
|
|
13104
|
+
GJI_BRANCH: context.branch ?? "",
|
|
13105
|
+
GJI_PATH: context.path,
|
|
13106
|
+
GJI_REPO: context.repo
|
|
13107
|
+
};
|
|
13108
|
+
}
|
|
13109
|
+
function formatArgvHook(command, args) {
|
|
13110
|
+
return JSON.stringify([command, ...args]);
|
|
13111
|
+
}
|
|
13068
13112
|
function interpolate(template, context) {
|
|
13069
13113
|
return template.replace(/\{\{branch\}\}/g, context.branch ?? "").replace(/\{\{path\}\}/g, context.path).replace(/\{\{repo\}\}/g, context.repo);
|
|
13070
13114
|
}
|
|
@@ -13075,11 +13119,18 @@ function extractHooks(config) {
|
|
|
13075
13119
|
}
|
|
13076
13120
|
const hooks = raw;
|
|
13077
13121
|
return {
|
|
13078
|
-
afterCreate:
|
|
13079
|
-
afterEnter:
|
|
13080
|
-
beforeRemove:
|
|
13122
|
+
afterCreate: parseHookCommand(hooks.afterCreate),
|
|
13123
|
+
afterEnter: parseHookCommand(hooks.afterEnter),
|
|
13124
|
+
beforeRemove: parseHookCommand(hooks.beforeRemove)
|
|
13081
13125
|
};
|
|
13082
13126
|
}
|
|
13127
|
+
function parseHookCommand(value) {
|
|
13128
|
+
if (typeof value === "string") return value;
|
|
13129
|
+
if (Array.isArray(value) && value.length > 0 && value[0] !== "" && value.every((item) => typeof item === "string")) {
|
|
13130
|
+
return value;
|
|
13131
|
+
}
|
|
13132
|
+
return void 0;
|
|
13133
|
+
}
|
|
13083
13134
|
|
|
13084
13135
|
// src/history.ts
|
|
13085
13136
|
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
@@ -14431,7 +14482,11 @@ var TOP_LEVEL_COMMANDS = [
|
|
|
14431
14482
|
{ name: "init", description: "print or install shell integration" },
|
|
14432
14483
|
{ name: "completion", description: "print shell completion definitions" },
|
|
14433
14484
|
{ name: "pr", description: "fetch a pull request into a linked worktree" },
|
|
14485
|
+
{ name: "back", description: "navigate to the previously visited worktree" },
|
|
14486
|
+
{ name: "history", description: "show navigation history" },
|
|
14487
|
+
{ name: "open", description: "open the worktree in an editor" },
|
|
14434
14488
|
{ name: "go", description: "print or select a worktree path" },
|
|
14489
|
+
{ name: "jump", description: "alias of go" },
|
|
14435
14490
|
{ name: "root", description: "print the main repository root path" },
|
|
14436
14491
|
{ name: "status", description: "summarize repository and worktree health" },
|
|
14437
14492
|
{ name: "sync", description: "fetch and update one or all worktrees" },
|
|
@@ -14440,20 +14495,12 @@ var TOP_LEVEL_COMMANDS = [
|
|
|
14440
14495
|
{ name: "remove", description: "remove a linked worktree and delete its branch when present" },
|
|
14441
14496
|
{ name: "rm", description: "alias of remove" },
|
|
14442
14497
|
{ name: "trigger-hook", description: "run a named hook in the current worktree" },
|
|
14498
|
+
{ name: "warp", description: "jump to any worktree across all known repos" },
|
|
14443
14499
|
{ name: "config", description: "manage global config defaults" }
|
|
14444
14500
|
];
|
|
14445
14501
|
var SHELL_NAMES = ["bash", "fish", "zsh"];
|
|
14446
14502
|
var HOOK_NAMES = ["afterCreate", "afterEnter", "beforeRemove"];
|
|
14447
|
-
var CONFIG_KEYS =
|
|
14448
|
-
"branchPrefix",
|
|
14449
|
-
"syncRemote",
|
|
14450
|
-
"syncDefaultBranch",
|
|
14451
|
-
"syncFiles",
|
|
14452
|
-
"skipInstallPrompt",
|
|
14453
|
-
"installSaveTarget",
|
|
14454
|
-
"hooks",
|
|
14455
|
-
"repos"
|
|
14456
|
-
];
|
|
14503
|
+
var CONFIG_KEYS = Array.from(KNOWN_GLOBAL_CONFIG_KEYS);
|
|
14457
14504
|
function renderShellCompletion(shell) {
|
|
14458
14505
|
switch (shell) {
|
|
14459
14506
|
case "bash":
|
|
@@ -14487,7 +14534,7 @@ _gji_completion() {
|
|
|
14487
14534
|
|
|
14488
14535
|
case "$command_name" in
|
|
14489
14536
|
new)
|
|
14490
|
-
COMPREPLY=( $(compgen -W "--detached --dry-run --json --help" -- "$cur") )
|
|
14537
|
+
COMPREPLY=( $(compgen -W "--detached --force --open --editor --dry-run --json --help" -- "$cur") )
|
|
14491
14538
|
;;
|
|
14492
14539
|
init)
|
|
14493
14540
|
COMPREPLY=( $(compgen -W "${shells} --write --help" -- "$cur") )
|
|
@@ -14498,7 +14545,16 @@ _gji_completion() {
|
|
|
14498
14545
|
pr)
|
|
14499
14546
|
COMPREPLY=( $(compgen -W "--dry-run --json --help" -- "$cur") )
|
|
14500
14547
|
;;
|
|
14501
|
-
|
|
14548
|
+
back)
|
|
14549
|
+
COMPREPLY=( $(compgen -W "--print --help" -- "$cur") )
|
|
14550
|
+
;;
|
|
14551
|
+
history)
|
|
14552
|
+
COMPREPLY=( $(compgen -W "--json --help" -- "$cur") )
|
|
14553
|
+
;;
|
|
14554
|
+
open)
|
|
14555
|
+
COMPREPLY=( $(compgen -W "$(__gji_worktree_branches) --editor --save --workspace --help" -- "$cur") )
|
|
14556
|
+
;;
|
|
14557
|
+
go|jump)
|
|
14502
14558
|
COMPREPLY=( $(compgen -W "$(__gji_worktree_branches) --print --help" -- "$cur") )
|
|
14503
14559
|
;;
|
|
14504
14560
|
root)
|
|
@@ -14522,6 +14578,9 @@ _gji_completion() {
|
|
|
14522
14578
|
trigger-hook)
|
|
14523
14579
|
COMPREPLY=( $(compgen -W "${hooks} --help" -- "$cur") )
|
|
14524
14580
|
;;
|
|
14581
|
+
warp)
|
|
14582
|
+
COMPREPLY=( $(compgen -W "-n --new --print --json --help" -- "$cur") )
|
|
14583
|
+
;;
|
|
14525
14584
|
config)
|
|
14526
14585
|
if [ "$COMP_CWORD" -eq 2 ]; then
|
|
14527
14586
|
COMPREPLY=( $(compgen -W "get set unset" -- "$cur") )
|
|
@@ -14585,6 +14644,9 @@ complete -c gji -f
|
|
|
14585
14644
|
${commandLines}
|
|
14586
14645
|
|
|
14587
14646
|
complete -c gji -n '__fish_seen_subcommand_from new' -l detached -d 'create a detached worktree without a branch'
|
|
14647
|
+
complete -c gji -n '__fish_seen_subcommand_from new' -l force -d 'remove and recreate the worktree if the target path already exists'
|
|
14648
|
+
complete -c gji -n '__fish_seen_subcommand_from new' -l open -d 'open the new worktree in an editor after creation'
|
|
14649
|
+
complete -c gji -n '__fish_seen_subcommand_from new' -l editor -r -d 'editor CLI to use with --open (code, cursor, zed, \u2026)'
|
|
14588
14650
|
complete -c gji -n '__fish_seen_subcommand_from new' -l dry-run -d 'show what would be created without executing any git commands or writing files'
|
|
14589
14651
|
complete -c gji -n '__fish_seen_subcommand_from new' -l json -d 'emit JSON on success or error instead of human-readable output'
|
|
14590
14652
|
|
|
@@ -14598,8 +14660,17 @@ complete -c gji -n '__fish_seen_subcommand_from completion' -a 'zsh' -d 'shell'
|
|
|
14598
14660
|
complete -c gji -n '__fish_seen_subcommand_from pr' -l dry-run -d 'show what would be created without executing any git commands or writing files'
|
|
14599
14661
|
complete -c gji -n '__fish_seen_subcommand_from pr' -l json -d 'emit JSON on success or error instead of human-readable output'
|
|
14600
14662
|
|
|
14601
|
-
complete -c gji -n '__fish_seen_subcommand_from
|
|
14602
|
-
|
|
14663
|
+
complete -c gji -n '__fish_seen_subcommand_from back' -l print -d 'print the resolved worktree path explicitly'
|
|
14664
|
+
|
|
14665
|
+
complete -c gji -n '__fish_seen_subcommand_from history' -l json -d 'print history as JSON'
|
|
14666
|
+
|
|
14667
|
+
complete -c gji -n '__fish_seen_subcommand_from open' -l editor -r -d 'editor CLI to use (code, cursor, zed, windsurf, subl, \u2026)'
|
|
14668
|
+
complete -c gji -n '__fish_seen_subcommand_from open' -l save -d 'save the chosen editor to global config'
|
|
14669
|
+
complete -c gji -n '__fish_seen_subcommand_from open' -l workspace -d 'generate a .code-workspace file before opening (VS Code / Cursor / Windsurf)'
|
|
14670
|
+
complete -c gji -n '__fish_seen_subcommand_from open' -a '(__gji_worktree_branches)' -d 'worktree branch'
|
|
14671
|
+
|
|
14672
|
+
complete -c gji -n '__fish_seen_subcommand_from go jump' -l print -d 'print the resolved worktree path explicitly'
|
|
14673
|
+
complete -c gji -n '__fish_seen_subcommand_from go jump' -a '(__gji_worktree_branches)' -d 'worktree branch'
|
|
14603
14674
|
|
|
14604
14675
|
complete -c gji -n '__fish_seen_subcommand_from root' -l print -d 'print the resolved repository root path explicitly'
|
|
14605
14676
|
|
|
@@ -14623,6 +14694,10 @@ complete -c gji -n '__fish_seen_subcommand_from remove rm' -a '(__gji_worktree_b
|
|
|
14623
14694
|
|
|
14624
14695
|
${hookLines}
|
|
14625
14696
|
|
|
14697
|
+
complete -c gji -n '__fish_seen_subcommand_from warp' -s n -l new -d 'create a new worktree in a registered repo'
|
|
14698
|
+
complete -c gji -n '__fish_seen_subcommand_from warp' -l print -d 'print the resolved worktree path without changing directory'
|
|
14699
|
+
complete -c gji -n '__fish_seen_subcommand_from warp' -l json -d 'emit JSON on success or error instead of human-readable output'
|
|
14700
|
+
|
|
14626
14701
|
complete -c gji -n '__fish_seen_subcommand_from config; and __gji_should_complete_config_action' -a 'get set unset' -d 'config action'
|
|
14627
14702
|
${configKeyLines}`;
|
|
14628
14703
|
}
|
|
@@ -14633,89 +14708,99 @@ function renderZshCompletion() {
|
|
|
14633
14708
|
const configKeys = CONFIG_KEYS.join(" ");
|
|
14634
14709
|
const shells = SHELL_NAMES.join(" ");
|
|
14635
14710
|
const hooks = HOOK_NAMES.join(" ");
|
|
14636
|
-
return
|
|
14711
|
+
return `#compdef gji
|
|
14712
|
+
|
|
14713
|
+
__gji_worktree_branches() {
|
|
14637
14714
|
command gji ls --compact 2>/dev/null | awk 'NR > 1 { branch = ($1 == "*" ? $2 : $1); if (branch != "(detached)") print branch }'
|
|
14638
14715
|
}
|
|
14639
14716
|
|
|
14640
|
-
|
|
14641
|
-
|
|
14642
|
-
local -a commands worktree_branches
|
|
14643
|
-
|
|
14644
|
-
commands=(
|
|
14645
|
-
${commandLines}
|
|
14646
|
-
)
|
|
14717
|
+
local context state line
|
|
14718
|
+
local -a commands worktree_branches
|
|
14647
14719
|
|
|
14648
|
-
|
|
14649
|
-
|
|
14650
|
-
|
|
14651
|
-
fi
|
|
14720
|
+
commands=(
|
|
14721
|
+
${commandLines}
|
|
14722
|
+
)
|
|
14652
14723
|
|
|
14653
|
-
|
|
14654
|
-
|
|
14655
|
-
|
|
14656
|
-
|
|
14657
|
-
init)
|
|
14658
|
-
_arguments '--write[write the integration to the shell config file]' '2:shell:(${shells})'
|
|
14659
|
-
;;
|
|
14660
|
-
completion)
|
|
14661
|
-
_arguments '2:shell:(${shells})'
|
|
14662
|
-
;;
|
|
14663
|
-
pr)
|
|
14664
|
-
_arguments '--dry-run[show what would be created without executing any git commands or writing files]' '--json[emit JSON on success or error instead of human-readable output]' '2:ref: '
|
|
14665
|
-
;;
|
|
14666
|
-
go)
|
|
14667
|
-
_arguments '--print[print the resolved worktree path explicitly]' '2:branch:->worktrees'
|
|
14668
|
-
;;
|
|
14669
|
-
root)
|
|
14670
|
-
_arguments '--print[print the resolved repository root path explicitly]'
|
|
14671
|
-
;;
|
|
14672
|
-
status)
|
|
14673
|
-
_arguments '--json[print repository and worktree health as JSON]'
|
|
14674
|
-
;;
|
|
14675
|
-
sync)
|
|
14676
|
-
_arguments '--all[sync every worktree in the repository]' '--json[emit JSON on success or error instead of human-readable output]'
|
|
14677
|
-
;;
|
|
14678
|
-
ls)
|
|
14679
|
-
_arguments '--compact[show only branch and path columns]' '--json[print active worktrees as JSON]'
|
|
14680
|
-
;;
|
|
14681
|
-
clean)
|
|
14682
|
-
_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]'
|
|
14683
|
-
;;
|
|
14684
|
-
remove|rm)
|
|
14685
|
-
_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'
|
|
14686
|
-
;;
|
|
14687
|
-
trigger-hook)
|
|
14688
|
-
_arguments "2:hook:(${hooks})"
|
|
14689
|
-
;;
|
|
14690
|
-
config)
|
|
14691
|
-
if (( CURRENT == 3 )); then
|
|
14692
|
-
_values 'config action' get set unset
|
|
14693
|
-
return
|
|
14694
|
-
fi
|
|
14724
|
+
if (( CURRENT == 2 )); then
|
|
14725
|
+
_describe 'command' commands
|
|
14726
|
+
return
|
|
14727
|
+
fi
|
|
14695
14728
|
|
|
14696
|
-
|
|
14697
|
-
|
|
14698
|
-
|
|
14699
|
-
|
|
14700
|
-
|
|
14701
|
-
|
|
14702
|
-
|
|
14703
|
-
|
|
14704
|
-
|
|
14705
|
-
|
|
14729
|
+
case "\${words[2]}" in
|
|
14730
|
+
new)
|
|
14731
|
+
_arguments '--detached[create a detached worktree without a branch]' '--force[remove and recreate the worktree if the target path already exists]' '--open[open the new worktree in an editor after creation]' '--editor[editor CLI to use with --open (code, cursor, zed, \u2026)]:editor:' '--dry-run[show what would be created without executing any git commands or writing files]' '--json[emit JSON on success or error instead of human-readable output]' '2:branch: '
|
|
14732
|
+
;;
|
|
14733
|
+
init)
|
|
14734
|
+
_arguments '--write[write the integration to the shell config file]' '2:shell:(${shells})'
|
|
14735
|
+
;;
|
|
14736
|
+
completion)
|
|
14737
|
+
_arguments '2:shell:(${shells})'
|
|
14738
|
+
;;
|
|
14739
|
+
pr)
|
|
14740
|
+
_arguments '--dry-run[show what would be created without executing any git commands or writing files]' '--json[emit JSON on success or error instead of human-readable output]' '2:ref: '
|
|
14741
|
+
;;
|
|
14742
|
+
back)
|
|
14743
|
+
_arguments '--print[print the resolved worktree path explicitly]' '2:steps: '
|
|
14744
|
+
;;
|
|
14745
|
+
history)
|
|
14746
|
+
_arguments '--json[print history as JSON]'
|
|
14747
|
+
;;
|
|
14748
|
+
open)
|
|
14749
|
+
_arguments '--editor[editor CLI to use (code, cursor, zed, windsurf, subl, \u2026)]:editor:' '--save[save the chosen editor to global config]' '--workspace[generate a .code-workspace file before opening (VS Code / Cursor / Windsurf)]' '2:branch:->worktrees'
|
|
14750
|
+
;;
|
|
14751
|
+
go|jump)
|
|
14752
|
+
_arguments '--print[print the resolved worktree path explicitly]' '2:branch:->worktrees'
|
|
14753
|
+
;;
|
|
14754
|
+
root)
|
|
14755
|
+
_arguments '--print[print the resolved repository root path explicitly]'
|
|
14756
|
+
;;
|
|
14757
|
+
status)
|
|
14758
|
+
_arguments '--json[print repository and worktree health as JSON]'
|
|
14759
|
+
;;
|
|
14760
|
+
sync)
|
|
14761
|
+
_arguments '--all[sync every worktree in the repository]' '--json[emit JSON on success or error instead of human-readable output]'
|
|
14762
|
+
;;
|
|
14763
|
+
ls)
|
|
14764
|
+
_arguments '--compact[show only branch and path columns]' '--json[print active worktrees as JSON]'
|
|
14765
|
+
;;
|
|
14766
|
+
clean)
|
|
14767
|
+
_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]'
|
|
14768
|
+
;;
|
|
14769
|
+
remove|rm)
|
|
14770
|
+
_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'
|
|
14771
|
+
;;
|
|
14772
|
+
trigger-hook)
|
|
14773
|
+
_arguments "2:hook:(${hooks})"
|
|
14774
|
+
;;
|
|
14775
|
+
warp)
|
|
14776
|
+
_arguments '(-n --new)'{-n,--new}'[create a new worktree in a registered repo]:branch:' '--print[print the resolved worktree path without changing directory]' '--json[emit JSON on success or error instead of human-readable output]' '2:branch: '
|
|
14777
|
+
;;
|
|
14778
|
+
config)
|
|
14779
|
+
if (( CURRENT == 3 )); then
|
|
14780
|
+
_values 'config action' get set unset
|
|
14781
|
+
return
|
|
14782
|
+
fi
|
|
14706
14783
|
|
|
14707
|
-
|
|
14708
|
-
|
|
14709
|
-
|
|
14710
|
-
|
|
14711
|
-
|
|
14712
|
-
|
|
14713
|
-
|
|
14714
|
-
|
|
14715
|
-
|
|
14716
|
-
|
|
14784
|
+
case "\${words[3]}" in
|
|
14785
|
+
get|unset)
|
|
14786
|
+
_arguments '3:key:->config_keys'
|
|
14787
|
+
;;
|
|
14788
|
+
set)
|
|
14789
|
+
_arguments '3:key:->config_keys' '4:value: '
|
|
14790
|
+
;;
|
|
14791
|
+
esac
|
|
14792
|
+
;;
|
|
14793
|
+
esac
|
|
14717
14794
|
|
|
14718
|
-
|
|
14795
|
+
case "$state" in
|
|
14796
|
+
worktrees)
|
|
14797
|
+
worktree_branches=(\${(@f)$(__gji_worktree_branches)})
|
|
14798
|
+
_describe 'worktree branch' worktree_branches
|
|
14799
|
+
;;
|
|
14800
|
+
config_keys)
|
|
14801
|
+
_values 'config key' ${configKeys}
|
|
14802
|
+
;;
|
|
14803
|
+
esac`;
|
|
14719
14804
|
}
|
|
14720
14805
|
function escapeSingleQuotes(value) {
|
|
14721
14806
|
return value.replace(/'/g, `'\\''`);
|
|
@@ -14796,990 +14881,1449 @@ function writeJson(stdout, value) {
|
|
|
14796
14881
|
}
|
|
14797
14882
|
|
|
14798
14883
|
// src/go.ts
|
|
14799
|
-
import { basename as
|
|
14800
|
-
|
|
14801
|
-
|
|
14802
|
-
|
|
14803
|
-
|
|
14804
|
-
|
|
14805
|
-
|
|
14806
|
-
|
|
14807
|
-
|
|
14808
|
-
|
|
14809
|
-
|
|
14810
|
-
|
|
14884
|
+
import { basename as basename5 } from "node:path";
|
|
14885
|
+
|
|
14886
|
+
// src/new.ts
|
|
14887
|
+
import { mkdir as mkdir4 } from "node:fs/promises";
|
|
14888
|
+
import { basename as basename3, dirname as dirname5 } from "node:path";
|
|
14889
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
14890
|
+
import { promisify as promisify4 } from "node:util";
|
|
14891
|
+
|
|
14892
|
+
// src/editor.ts
|
|
14893
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
14894
|
+
var EDITORS = [
|
|
14895
|
+
{ cli: "cursor", name: "Cursor", newWindowFlag: "--new-window", supportsWorkspace: true },
|
|
14896
|
+
{ cli: "code", name: "VS Code", newWindowFlag: "--new-window", supportsWorkspace: true },
|
|
14897
|
+
{ cli: "windsurf", name: "Windsurf", newWindowFlag: "--new-window", supportsWorkspace: true },
|
|
14898
|
+
{ cli: "zed", name: "Zed", supportsWorkspace: false },
|
|
14899
|
+
{ cli: "subl", name: "Sublime Text", newWindowFlag: "--new-window", supportsWorkspace: false }
|
|
14900
|
+
];
|
|
14901
|
+
async function defaultSpawnEditor(cli, args) {
|
|
14902
|
+
const child = spawn3(cli, args, { detached: true, stdio: "ignore" });
|
|
14903
|
+
await new Promise((resolve5, reject) => {
|
|
14904
|
+
child.once("error", reject);
|
|
14905
|
+
child.once("spawn", resolve5);
|
|
14906
|
+
});
|
|
14907
|
+
child.unref();
|
|
14908
|
+
}
|
|
14909
|
+
|
|
14910
|
+
// src/file-sync.ts
|
|
14911
|
+
import { copyFile, mkdir as mkdir3, stat } from "node:fs/promises";
|
|
14912
|
+
import { dirname as dirname4, isAbsolute as isAbsolute2, join as join4, normalize } from "node:path";
|
|
14913
|
+
async function syncFiles(mainRoot, targetPath, patterns) {
|
|
14914
|
+
for (const pattern of patterns) {
|
|
14915
|
+
if (isAbsolute2(pattern)) {
|
|
14916
|
+
throw new Error(`syncFiles: pattern must be a relative path, got: ${pattern}`);
|
|
14811
14917
|
}
|
|
14812
|
-
const
|
|
14813
|
-
|
|
14814
|
-
|
|
14815
|
-
if (options.branch) {
|
|
14816
|
-
options.stderr(`No worktree found for branch: ${options.branch}
|
|
14817
|
-
`);
|
|
14818
|
-
options.stderr(`Hint: Use 'gji ls' to see available worktrees
|
|
14819
|
-
`);
|
|
14820
|
-
} else {
|
|
14821
|
-
options.stderr("Aborted\n");
|
|
14822
|
-
}
|
|
14823
|
-
return 1;
|
|
14918
|
+
const normalized = normalize(pattern);
|
|
14919
|
+
if (normalized.startsWith("..")) {
|
|
14920
|
+
throw new Error(`syncFiles: pattern must not contain '..' segments, got: ${pattern}`);
|
|
14824
14921
|
}
|
|
14825
|
-
const
|
|
14826
|
-
const
|
|
14827
|
-
const
|
|
14828
|
-
|
|
14829
|
-
|
|
14830
|
-
|
|
14831
|
-
|
|
14832
|
-
|
|
14833
|
-
|
|
14834
|
-
|
|
14835
|
-
await
|
|
14836
|
-
|
|
14837
|
-
}
|
|
14922
|
+
const sourcePath = join4(mainRoot, normalized);
|
|
14923
|
+
const destPath = join4(targetPath, normalized);
|
|
14924
|
+
const sourceExists = await fileExists(sourcePath);
|
|
14925
|
+
if (!sourceExists) {
|
|
14926
|
+
continue;
|
|
14927
|
+
}
|
|
14928
|
+
const destExists = await fileExists(destPath);
|
|
14929
|
+
if (destExists) {
|
|
14930
|
+
continue;
|
|
14931
|
+
}
|
|
14932
|
+
await mkdir3(dirname4(destPath), { recursive: true });
|
|
14933
|
+
await copyFile(sourcePath, destPath);
|
|
14934
|
+
}
|
|
14838
14935
|
}
|
|
14839
|
-
|
|
14840
|
-
|
|
14841
|
-
|
|
14842
|
-
|
|
14843
|
-
)
|
|
14844
|
-
|
|
14845
|
-
|
|
14846
|
-
|
|
14847
|
-
|
|
14848
|
-
const pathHint = worktree.isCurrent ? `${worktree.path} (current)` : worktree.path;
|
|
14849
|
-
const upstream = health ? formatUpstreamHint(worktree.branch, health) : null;
|
|
14850
|
-
return {
|
|
14851
|
-
value: worktree.path,
|
|
14852
|
-
label: worktree.branch ?? "(detached)",
|
|
14853
|
-
hint: upstream ? `${upstream} \xB7 ${pathHint}` : pathHint
|
|
14854
|
-
};
|
|
14855
|
-
})
|
|
14856
|
-
});
|
|
14857
|
-
if (pD(choice)) {
|
|
14858
|
-
return null;
|
|
14936
|
+
async function fileExists(path9) {
|
|
14937
|
+
try {
|
|
14938
|
+
await stat(path9);
|
|
14939
|
+
return true;
|
|
14940
|
+
} catch (error) {
|
|
14941
|
+
if (isNotFoundError(error)) {
|
|
14942
|
+
return false;
|
|
14943
|
+
}
|
|
14944
|
+
throw error;
|
|
14859
14945
|
}
|
|
14860
|
-
return choice;
|
|
14861
14946
|
}
|
|
14862
|
-
function
|
|
14863
|
-
|
|
14864
|
-
if (!health.hasUpstream) return "no upstream";
|
|
14865
|
-
if (health.upstreamGone) return "upstream gone";
|
|
14866
|
-
if (health.ahead === 0 && health.behind === 0) return "up to date";
|
|
14867
|
-
if (health.ahead === 0) return `behind ${health.behind}`;
|
|
14868
|
-
if (health.behind === 0) return `ahead ${health.ahead}`;
|
|
14869
|
-
return `ahead ${health.ahead}, behind ${health.behind}`;
|
|
14947
|
+
function isNotFoundError(error) {
|
|
14948
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
14870
14949
|
}
|
|
14871
14950
|
|
|
14872
|
-
// src/
|
|
14873
|
-
import {
|
|
14874
|
-
|
|
14875
|
-
|
|
14876
|
-
|
|
14877
|
-
|
|
14878
|
-
var
|
|
14879
|
-
|
|
14880
|
-
|
|
14881
|
-
|
|
14882
|
-
|
|
14883
|
-
|
|
14884
|
-
|
|
14885
|
-
|
|
14886
|
-
{
|
|
14887
|
-
|
|
14888
|
-
|
|
14889
|
-
|
|
14890
|
-
|
|
14891
|
-
|
|
14892
|
-
|
|
14893
|
-
{
|
|
14894
|
-
|
|
14895
|
-
|
|
14896
|
-
|
|
14897
|
-
|
|
14898
|
-
|
|
14899
|
-
},
|
|
14900
|
-
|
|
14901
|
-
|
|
14902
|
-
|
|
14903
|
-
|
|
14904
|
-
|
|
14905
|
-
|
|
14906
|
-
},
|
|
14907
|
-
|
|
14908
|
-
|
|
14909
|
-
|
|
14910
|
-
|
|
14911
|
-
|
|
14912
|
-
|
|
14913
|
-
},
|
|
14914
|
-
|
|
14915
|
-
|
|
14916
|
-
|
|
14917
|
-
|
|
14918
|
-
|
|
14919
|
-
|
|
14920
|
-
|
|
14921
|
-
]
|
|
14922
|
-
|
|
14923
|
-
|
|
14924
|
-
|
|
14925
|
-
|
|
14926
|
-
|
|
14927
|
-
|
|
14928
|
-
|
|
14929
|
-
|
|
14930
|
-
}
|
|
14931
|
-
|
|
14932
|
-
|
|
14933
|
-
|
|
14934
|
-
|
|
14935
|
-
|
|
14936
|
-
|
|
14937
|
-
|
|
14938
|
-
|
|
14939
|
-
|
|
14940
|
-
|
|
14941
|
-
|
|
14942
|
-
|
|
14943
|
-
|
|
14944
|
-
|
|
14945
|
-
const
|
|
14946
|
-
|
|
14947
|
-
|
|
14948
|
-
|
|
14949
|
-
const result = await prompt();
|
|
14950
|
-
if (result) {
|
|
14951
|
-
await updateGlobalConfigKey("installSaveTarget", result.installSaveTarget, home);
|
|
14952
|
-
await saveWizardConfig(result, options.cwd, home);
|
|
14951
|
+
// src/install-prompt.ts
|
|
14952
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
14953
|
+
|
|
14954
|
+
// src/package-manager.ts
|
|
14955
|
+
import { access as access2, readdir } from "node:fs/promises";
|
|
14956
|
+
import { join as join5 } from "node:path";
|
|
14957
|
+
var ENTRIES = [
|
|
14958
|
+
// JavaScript / TypeScript
|
|
14959
|
+
{ name: "pnpm", signals: ["pnpm-lock.yaml"], command: "pnpm install" },
|
|
14960
|
+
{ name: "yarn", signals: ["yarn.lock"], command: "yarn install" },
|
|
14961
|
+
{ name: "bun", signals: ["bun.lockb"], command: "bun install" },
|
|
14962
|
+
{ name: "npm", signals: ["package-lock.json"], command: "npm install" },
|
|
14963
|
+
{ name: "deno", signals: ["deno.json", "deno.jsonc"], command: "deno cache" },
|
|
14964
|
+
// Python
|
|
14965
|
+
{ name: "poetry", signals: ["poetry.lock"], command: "poetry install" },
|
|
14966
|
+
{ name: "uv", signals: ["uv.lock"], command: "uv sync" },
|
|
14967
|
+
{ name: "pipenv", signals: ["Pipfile.lock"], command: "pipenv install" },
|
|
14968
|
+
{ name: "pdm", signals: ["pdm.lock"], command: "pdm install" },
|
|
14969
|
+
{ name: "conda-lock", signals: ["conda-lock.yml"], command: "conda-lock install" },
|
|
14970
|
+
{ name: "conda", signals: ["environment.yml"], command: "conda env update --file environment.yml" },
|
|
14971
|
+
// R
|
|
14972
|
+
{ name: "renv", signals: ["renv.lock"], command: "Rscript -e 'renv::restore()'" },
|
|
14973
|
+
// Rust
|
|
14974
|
+
{ name: "cargo", signals: ["Cargo.lock"], command: "cargo build" },
|
|
14975
|
+
// Go
|
|
14976
|
+
{ name: "go", signals: ["go.sum"], command: "go mod download" },
|
|
14977
|
+
// Ruby
|
|
14978
|
+
{ name: "bundler", signals: ["Gemfile.lock"], command: "bundle install" },
|
|
14979
|
+
// PHP
|
|
14980
|
+
{ name: "composer", signals: ["composer.lock"], command: "composer install" },
|
|
14981
|
+
// Elixir / Erlang
|
|
14982
|
+
{ name: "mix", signals: ["mix.lock"], command: "mix deps.get" },
|
|
14983
|
+
{ name: "rebar3", signals: ["rebar.lock"], command: "rebar3 deps" },
|
|
14984
|
+
// Dart / Flutter
|
|
14985
|
+
{ name: "dart", signals: ["pubspec.lock"], command: "dart pub get" },
|
|
14986
|
+
// Java / Kotlin / Scala
|
|
14987
|
+
{ name: "maven", signals: ["pom.xml"], command: "mvn install" },
|
|
14988
|
+
{ name: "gradle", signals: ["gradlew"], command: "./gradlew build" },
|
|
14989
|
+
{ name: "gradle", signals: ["build.gradle", "build.gradle.kts"], command: "gradle build" },
|
|
14990
|
+
{ name: "sbt", signals: ["build.sbt"], command: "sbt compile" },
|
|
14991
|
+
// .NET (C# / F# / VB)
|
|
14992
|
+
{ name: "dotnet", signals: ["*.sln", "*.csproj", "*.fsproj", "*.vbproj"], command: "dotnet restore", glob: true },
|
|
14993
|
+
// Swift
|
|
14994
|
+
{ name: "swift", signals: ["Package.swift"], command: "swift package resolve" },
|
|
14995
|
+
// Haskell
|
|
14996
|
+
{ name: "stack", signals: ["stack.yaml"], command: "stack build" },
|
|
14997
|
+
{ name: "cabal", signals: ["cabal.project"], command: "cabal install --only-dependencies" },
|
|
14998
|
+
{ name: "cabal", signals: ["*.cabal"], command: "cabal install --only-dependencies", glob: true },
|
|
14999
|
+
// Clojure
|
|
15000
|
+
{ name: "clojure", signals: ["deps.edn"], command: "clojure -P" },
|
|
15001
|
+
{ name: "leiningen", signals: ["project.clj"], command: "lein deps" },
|
|
15002
|
+
// OCaml
|
|
15003
|
+
{ name: "dune", signals: ["dune-project"], command: "dune build" },
|
|
15004
|
+
// Julia
|
|
15005
|
+
{ name: "julia", signals: ["Manifest.toml"], command: "julia --project -e 'using Pkg; Pkg.instantiate()'" },
|
|
15006
|
+
// Nim
|
|
15007
|
+
{ name: "nimble", signals: ["*.nimble"], command: "nimble install", glob: true },
|
|
15008
|
+
// Crystal
|
|
15009
|
+
{ name: "shards", signals: ["shard.yml"], command: "shards install" },
|
|
15010
|
+
// Perl
|
|
15011
|
+
{ name: "cpanm", signals: ["cpanfile"], command: "cpanm --installdeps ." },
|
|
15012
|
+
// Zig
|
|
15013
|
+
{ name: "zig", signals: ["build.zig.zon"], command: "zig build" },
|
|
15014
|
+
// C / C++
|
|
15015
|
+
{ name: "vcpkg", signals: ["vcpkg.json"], command: "vcpkg install" },
|
|
15016
|
+
{ name: "conan", signals: ["conanfile.py", "conanfile.txt"], command: "conan install ." },
|
|
15017
|
+
// Nix
|
|
15018
|
+
{ name: "nix", signals: ["flake.nix"], command: "nix develop" },
|
|
15019
|
+
{ name: "nix-shell", signals: ["shell.nix"], command: "nix-shell" },
|
|
15020
|
+
// Terraform / OpenTofu
|
|
15021
|
+
{ name: "terraform", signals: ["terraform.lock.hcl"], command: "terraform init" }
|
|
15022
|
+
];
|
|
15023
|
+
async function detectPackageManager(repoRoot) {
|
|
15024
|
+
for (const entry of ENTRIES) {
|
|
15025
|
+
const matched = entry.glob ? await matchesGlob(repoRoot, entry.signals) : await matchesExact(repoRoot, entry.signals);
|
|
15026
|
+
if (matched) {
|
|
15027
|
+
return { name: entry.name, installCommand: entry.command };
|
|
14953
15028
|
}
|
|
14954
15029
|
}
|
|
14955
|
-
|
|
14956
|
-
return 0;
|
|
15030
|
+
return null;
|
|
14957
15031
|
}
|
|
14958
|
-
function
|
|
14959
|
-
const
|
|
14960
|
-
|
|
14961
|
-
|
|
14962
|
-
|
|
14963
|
-
|
|
14964
|
-
|
|
14965
|
-
|
|
14966
|
-
|
|
14967
|
-
|
|
14968
|
-
command gji $argv
|
|
14969
|
-
end
|
|
14970
|
-
${END_MARKER}
|
|
14971
|
-
`;
|
|
14972
|
-
case "bash":
|
|
14973
|
-
case "zsh":
|
|
14974
|
-
return `${START_MARKER}
|
|
14975
|
-
gji() {
|
|
14976
|
-
${indentBlock(commandBlocks, 2)}
|
|
14977
|
-
|
|
14978
|
-
command gji "$@"
|
|
15032
|
+
async function matchesExact(repoRoot, signals) {
|
|
15033
|
+
for (const signal of signals) {
|
|
15034
|
+
try {
|
|
15035
|
+
await access2(join5(repoRoot, signal));
|
|
15036
|
+
return true;
|
|
15037
|
+
} catch {
|
|
15038
|
+
}
|
|
15039
|
+
}
|
|
15040
|
+
return false;
|
|
14979
15041
|
}
|
|
14980
|
-
|
|
14981
|
-
|
|
15042
|
+
async function matchesGlob(repoRoot, patterns) {
|
|
15043
|
+
let files;
|
|
15044
|
+
try {
|
|
15045
|
+
files = await readdir(repoRoot);
|
|
15046
|
+
} catch {
|
|
15047
|
+
return false;
|
|
14982
15048
|
}
|
|
15049
|
+
const regexes = patterns.map(patternToRegex);
|
|
15050
|
+
return files.some((file) => regexes.some((re) => re.test(file)));
|
|
14983
15051
|
}
|
|
14984
|
-
function
|
|
14985
|
-
const
|
|
14986
|
-
|
|
14987
|
-
|
|
14988
|
-
|
|
14989
|
-
|
|
14990
|
-
|
|
14991
|
-
|
|
14992
|
-
|
|
14993
|
-
`)
|
|
14994
|
-
);
|
|
15052
|
+
function patternToRegex(pattern) {
|
|
15053
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^/]*");
|
|
15054
|
+
return new RegExp(`^${escaped}$`);
|
|
15055
|
+
}
|
|
15056
|
+
|
|
15057
|
+
// src/install-prompt.ts
|
|
15058
|
+
async function maybeRunInstallPrompt(worktreePath, repoRoot, config, stderr, dependencies = {}, nonInteractive = false) {
|
|
15059
|
+
if (isHeadless() || nonInteractive) {
|
|
15060
|
+
return;
|
|
14995
15061
|
}
|
|
14996
|
-
const
|
|
14997
|
-
if (
|
|
14998
|
-
return
|
|
15062
|
+
const hooks = isPlainObject2(config.hooks) ? config.hooks : null;
|
|
15063
|
+
if (isConfiguredHookCommand(hooks?.afterCreate)) {
|
|
15064
|
+
return;
|
|
14999
15065
|
}
|
|
15000
|
-
|
|
15001
|
-
|
|
15002
|
-
${trimmedScript}`);
|
|
15003
|
-
}
|
|
15004
|
-
async function saveWizardConfig(result, cwd, home) {
|
|
15005
|
-
const values = {};
|
|
15006
|
-
if (result.branchPrefix) values.branchPrefix = result.branchPrefix;
|
|
15007
|
-
if (result.worktreePath) values.worktreePath = result.worktreePath;
|
|
15008
|
-
const hooks = {};
|
|
15009
|
-
if (result.hooks?.afterCreate) hooks.afterCreate = result.hooks.afterCreate;
|
|
15010
|
-
if (result.hooks?.afterEnter) hooks.afterEnter = result.hooks.afterEnter;
|
|
15011
|
-
if (result.hooks?.beforeRemove) hooks.beforeRemove = result.hooks.beforeRemove;
|
|
15012
|
-
if (Object.keys(hooks).length > 0) values.hooks = hooks;
|
|
15013
|
-
if (Object.keys(values).length === 0) return;
|
|
15014
|
-
if (result.installSaveTarget === "local") {
|
|
15015
|
-
const loaded = await loadConfig(cwd);
|
|
15016
|
-
await saveLocalConfig(cwd, { ...loaded.config, ...values });
|
|
15017
|
-
} else {
|
|
15018
|
-
const { config: existing } = await loadGlobalConfig(home);
|
|
15019
|
-
await saveGlobalConfig({ ...existing, ...values }, home);
|
|
15066
|
+
if (config.skipInstallPrompt === true) {
|
|
15067
|
+
return;
|
|
15020
15068
|
}
|
|
15021
|
-
|
|
15022
|
-
|
|
15023
|
-
|
|
15024
|
-
|
|
15025
|
-
return join4(home, ".bashrc");
|
|
15026
|
-
case "fish":
|
|
15027
|
-
return join4(home, ".config", "fish", "config.fish");
|
|
15028
|
-
case "zsh":
|
|
15029
|
-
return join4(home, ".zshrc");
|
|
15069
|
+
const detect = dependencies.detectInstallPackageManager ?? detectPackageManager;
|
|
15070
|
+
const pm = await detect(worktreePath);
|
|
15071
|
+
if (!pm) {
|
|
15072
|
+
return;
|
|
15030
15073
|
}
|
|
15031
|
-
|
|
15032
|
-
|
|
15033
|
-
|
|
15034
|
-
return
|
|
15035
|
-
}
|
|
15036
|
-
|
|
15037
|
-
|
|
15074
|
+
const prompt = dependencies.promptForInstallChoice ?? defaultPromptForInstallChoice;
|
|
15075
|
+
const choice = await prompt(pm);
|
|
15076
|
+
if (!choice || choice === "no") {
|
|
15077
|
+
return;
|
|
15078
|
+
}
|
|
15079
|
+
if (choice === "yes" || choice === "always") {
|
|
15080
|
+
const runner = dependencies.runInstallCommand ?? defaultRunInstallCommand;
|
|
15081
|
+
try {
|
|
15082
|
+
await runner(pm.installCommand, worktreePath, stderr);
|
|
15083
|
+
} catch (error) {
|
|
15084
|
+
stderr(`gji: install command failed: ${error instanceof Error ? error.message : String(error)}
|
|
15085
|
+
`);
|
|
15086
|
+
}
|
|
15087
|
+
}
|
|
15088
|
+
const saveGlobal = config.installSaveTarget === "global";
|
|
15089
|
+
const writeKey = dependencies.writeConfigKey ?? defaultWriteConfigKey;
|
|
15090
|
+
const writeGlobalKey = dependencies.writeGlobalRepoConfigKey ?? defaultWriteGlobalRepoConfigKey;
|
|
15091
|
+
if (choice === "always") {
|
|
15092
|
+
try {
|
|
15093
|
+
if (saveGlobal) {
|
|
15094
|
+
const existingHooks = await loadExistingGlobalRepoHooks(repoRoot);
|
|
15095
|
+
await writeGlobalKey(repoRoot, "hooks", { ...existingHooks, afterCreate: pm.installCommand });
|
|
15096
|
+
} else {
|
|
15097
|
+
const { config: localConfig } = await loadConfig(repoRoot);
|
|
15098
|
+
const existingLocalHooks = isPlainObject2(localConfig.hooks) ? localConfig.hooks : {};
|
|
15099
|
+
await writeKey(repoRoot, "hooks", { ...existingLocalHooks, afterCreate: pm.installCommand });
|
|
15100
|
+
}
|
|
15101
|
+
} catch (error) {
|
|
15102
|
+
stderr(`gji: failed to save config: ${error instanceof Error ? error.message : String(error)}
|
|
15103
|
+
`);
|
|
15104
|
+
}
|
|
15105
|
+
}
|
|
15106
|
+
if (choice === "never") {
|
|
15107
|
+
try {
|
|
15108
|
+
if (saveGlobal) {
|
|
15109
|
+
await writeGlobalKey(repoRoot, "skipInstallPrompt", true);
|
|
15110
|
+
} else {
|
|
15111
|
+
await writeKey(repoRoot, "skipInstallPrompt", true);
|
|
15112
|
+
}
|
|
15113
|
+
} catch (error) {
|
|
15114
|
+
stderr(`gji: failed to save config: ${error instanceof Error ? error.message : String(error)}
|
|
15115
|
+
`);
|
|
15038
15116
|
}
|
|
15039
|
-
throw error;
|
|
15040
15117
|
}
|
|
15041
15118
|
}
|
|
15042
|
-
function
|
|
15043
|
-
|
|
15044
|
-
|
|
15119
|
+
async function defaultRunInstallCommand(command, cwd, stderr) {
|
|
15120
|
+
await new Promise((resolve5, reject) => {
|
|
15121
|
+
const child = spawn4(command, { cwd, shell: true, stdio: ["ignore", "inherit", "pipe"] });
|
|
15122
|
+
child.stderr.on("data", (chunk) => {
|
|
15123
|
+
stderr(chunk.toString());
|
|
15124
|
+
});
|
|
15125
|
+
child.on("close", (code) => {
|
|
15126
|
+
if (code !== 0) {
|
|
15127
|
+
reject(new Error(`exited with code ${code}`));
|
|
15128
|
+
} else {
|
|
15129
|
+
resolve5();
|
|
15130
|
+
}
|
|
15131
|
+
});
|
|
15132
|
+
child.on("error", (err) => {
|
|
15133
|
+
reject(err);
|
|
15134
|
+
});
|
|
15135
|
+
});
|
|
15045
15136
|
}
|
|
15046
|
-
function
|
|
15047
|
-
|
|
15137
|
+
async function defaultWriteConfigKey(root, key, value) {
|
|
15138
|
+
await updateLocalConfigKey(root, key, value);
|
|
15048
15139
|
}
|
|
15049
|
-
function
|
|
15050
|
-
|
|
15140
|
+
async function defaultWriteGlobalRepoConfigKey(repoRoot, key, value) {
|
|
15141
|
+
await updateGlobalRepoConfigKey(repoRoot, key, value);
|
|
15051
15142
|
}
|
|
15052
|
-
function
|
|
15053
|
-
const
|
|
15054
|
-
|
|
15055
|
-
|
|
15056
|
-
|
|
15057
|
-
command gji ${command.commandName} $argv
|
|
15058
|
-
return $status
|
|
15059
|
-
end
|
|
15060
|
-
|
|
15061
|
-
set -l output_file (mktemp -t ${command.tempPrefix}.XXXXXX)
|
|
15062
|
-
or return 1
|
|
15063
|
-
env ${command.envVar}=$output_file command gji ${command.commandName} $argv
|
|
15064
|
-
or begin
|
|
15065
|
-
set -l status_code $status
|
|
15066
|
-
rm -f $output_file
|
|
15067
|
-
return $status_code
|
|
15068
|
-
end
|
|
15069
|
-
set -l target (cat $output_file)
|
|
15070
|
-
rm -f $output_file
|
|
15071
|
-
cd $target
|
|
15072
|
-
return $status
|
|
15073
|
-
end`;
|
|
15074
|
-
}
|
|
15075
|
-
function renderPosixWrapper(command) {
|
|
15076
|
-
const tests = command.names.map((name) => `[ "$1" = "${name}" ]`).join(" || ");
|
|
15077
|
-
return `if ${tests}; then
|
|
15078
|
-
shift
|
|
15079
|
-
if [ "\${1:-}" = "${command.bypassOption}" ]; then
|
|
15080
|
-
command gji ${command.commandName} "$@"
|
|
15081
|
-
return $?
|
|
15082
|
-
fi
|
|
15083
|
-
|
|
15084
|
-
local target
|
|
15085
|
-
local output_file
|
|
15086
|
-
output_file="$(mktemp -t ${command.tempPrefix}.XXXXXX)" || return 1
|
|
15087
|
-
${command.envVar}="$output_file" command gji ${command.commandName} "$@" || { local exit_code=$?; rm -f "$output_file"; return $exit_code; }
|
|
15088
|
-
target="$(cat "$output_file")"
|
|
15089
|
-
rm -f "$output_file"
|
|
15090
|
-
cd "$target" || return $?
|
|
15091
|
-
return 0
|
|
15092
|
-
fi`;
|
|
15093
|
-
}
|
|
15094
|
-
function indentBlock(value, spaces) {
|
|
15095
|
-
const prefix = " ".repeat(spaces);
|
|
15096
|
-
return value.split("\n").map((line) => line.length === 0 ? "" : `${prefix}${line}`).join("\n");
|
|
15143
|
+
async function loadExistingGlobalRepoHooks(repoRoot) {
|
|
15144
|
+
const { config: globalConfig } = await loadGlobalConfig();
|
|
15145
|
+
const repos = isPlainObject2(globalConfig.repos) ? globalConfig.repos : {};
|
|
15146
|
+
const perRepo = isPlainObject2(repos[repoRoot]) ? repos[repoRoot] : {};
|
|
15147
|
+
return isPlainObject2(perRepo.hooks) ? perRepo.hooks : {};
|
|
15097
15148
|
}
|
|
15098
|
-
async function
|
|
15099
|
-
|
|
15100
|
-
|
|
15101
|
-
message: "Where should preferences be saved?",
|
|
15149
|
+
async function defaultPromptForInstallChoice(pm) {
|
|
15150
|
+
const choice = await ve({
|
|
15151
|
+
message: `Run \`${pm.installCommand}\` in the new worktree?`,
|
|
15102
15152
|
options: [
|
|
15103
|
-
{ value: "
|
|
15104
|
-
{ value: "
|
|
15153
|
+
{ value: "yes", label: "Yes", hint: "run once" },
|
|
15154
|
+
{ value: "no", label: "No", hint: "skip this time" },
|
|
15155
|
+
{ value: "always", label: "Always", hint: "save as afterCreate hook" },
|
|
15156
|
+
{ value: "never", label: "Never", hint: "disable this prompt for this repo" }
|
|
15105
15157
|
]
|
|
15106
15158
|
});
|
|
15107
|
-
if (pD(
|
|
15108
|
-
Se("Setup skipped.");
|
|
15109
|
-
return null;
|
|
15110
|
-
}
|
|
15111
|
-
const branchPrefix = await he({
|
|
15112
|
-
message: "Default branch prefix?",
|
|
15113
|
-
placeholder: "e.g. feat/ or fix/ \u2014 leave blank to skip"
|
|
15114
|
-
});
|
|
15115
|
-
if (pD(branchPrefix)) {
|
|
15116
|
-
Se("Setup skipped.");
|
|
15117
|
-
return null;
|
|
15118
|
-
}
|
|
15119
|
-
const worktreePath = await he({
|
|
15120
|
-
message: "Worktree base path?",
|
|
15121
|
-
placeholder: "leave blank to use the default path"
|
|
15122
|
-
});
|
|
15123
|
-
if (pD(worktreePath)) {
|
|
15124
|
-
Se("Setup skipped.");
|
|
15125
|
-
return null;
|
|
15126
|
-
}
|
|
15127
|
-
const afterCreate = await he({
|
|
15128
|
-
message: "afterCreate hook \u2014 run after creating a worktree?",
|
|
15129
|
-
placeholder: "e.g. pnpm install \u2014 leave blank to skip"
|
|
15130
|
-
});
|
|
15131
|
-
if (pD(afterCreate)) {
|
|
15132
|
-
Se("Setup skipped.");
|
|
15133
|
-
return null;
|
|
15134
|
-
}
|
|
15135
|
-
const afterEnter = await he({
|
|
15136
|
-
message: "afterEnter hook \u2014 run after entering a worktree?",
|
|
15137
|
-
placeholder: "e.g. nvm use \u2014 leave blank to skip"
|
|
15138
|
-
});
|
|
15139
|
-
if (pD(afterEnter)) {
|
|
15140
|
-
Se("Setup skipped.");
|
|
15141
|
-
return null;
|
|
15142
|
-
}
|
|
15143
|
-
const beforeRemove = await he({
|
|
15144
|
-
message: "beforeRemove hook \u2014 run before removing a worktree?",
|
|
15145
|
-
placeholder: "leave blank to skip"
|
|
15146
|
-
});
|
|
15147
|
-
if (pD(beforeRemove)) {
|
|
15148
|
-
Se("Setup skipped.");
|
|
15159
|
+
if (pD(choice)) {
|
|
15149
15160
|
return null;
|
|
15150
15161
|
}
|
|
15151
|
-
|
|
15152
|
-
const hooks = {};
|
|
15153
|
-
if (afterCreate) hooks.afterCreate = afterCreate;
|
|
15154
|
-
if (afterEnter) hooks.afterEnter = afterEnter;
|
|
15155
|
-
if (beforeRemove) hooks.beforeRemove = beforeRemove;
|
|
15156
|
-
return {
|
|
15157
|
-
branchPrefix: branchPrefix || void 0,
|
|
15158
|
-
hooks: Object.keys(hooks).length > 0 ? hooks : void 0,
|
|
15159
|
-
installSaveTarget,
|
|
15160
|
-
worktreePath: worktreePath || void 0
|
|
15161
|
-
};
|
|
15162
|
-
}
|
|
15163
|
-
|
|
15164
|
-
// src/paths.ts
|
|
15165
|
-
function comparePaths(left, right) {
|
|
15166
|
-
if (left < right) {
|
|
15167
|
-
return -1;
|
|
15168
|
-
}
|
|
15169
|
-
if (left > right) {
|
|
15170
|
-
return 1;
|
|
15171
|
-
}
|
|
15172
|
-
return 0;
|
|
15162
|
+
return choice;
|
|
15173
15163
|
}
|
|
15174
|
-
|
|
15175
|
-
|
|
15176
|
-
async function runLsCommand(options) {
|
|
15177
|
-
const worktrees = sortWorktrees(await listWorktrees(options.cwd));
|
|
15178
|
-
if (options.compact) {
|
|
15179
|
-
if (options.json) {
|
|
15180
|
-
options.stdout(`${JSON.stringify(worktrees, null, 2)}
|
|
15181
|
-
`);
|
|
15182
|
-
return 0;
|
|
15183
|
-
}
|
|
15184
|
-
options.stdout(`${formatWorktreeTable(worktrees)}
|
|
15185
|
-
`);
|
|
15186
|
-
return 0;
|
|
15187
|
-
}
|
|
15188
|
-
const infos = await readWorktreeInfos(worktrees);
|
|
15189
|
-
if (options.json) {
|
|
15190
|
-
options.stdout(`${JSON.stringify(infos, null, 2)}
|
|
15191
|
-
`);
|
|
15192
|
-
return 0;
|
|
15193
|
-
}
|
|
15194
|
-
options.stdout(`${formatDetailedWorktreeTable(infos)}
|
|
15195
|
-
`);
|
|
15196
|
-
return 0;
|
|
15164
|
+
function isPlainObject2(value) {
|
|
15165
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15197
15166
|
}
|
|
15198
|
-
function
|
|
15199
|
-
|
|
15200
|
-
|
|
15201
|
-
isCurrent: worktree.isCurrent,
|
|
15202
|
-
lastCommit: formatLastCommit(worktree.lastCommitTimestamp),
|
|
15203
|
-
path: worktree.path,
|
|
15204
|
-
status: worktree.status,
|
|
15205
|
-
upstream: formatUpstreamState(worktree.upstream)
|
|
15206
|
-
}));
|
|
15207
|
-
const branchWidth = Math.max("BRANCH".length, ...rows.map((row) => row.branch.length));
|
|
15208
|
-
const statusWidth = Math.max("STATUS".length, ...rows.map((row) => row.status.length));
|
|
15209
|
-
const upstreamWidth = Math.max("UPSTREAM".length, ...rows.map((row) => row.upstream.length));
|
|
15210
|
-
const lastCommitWidth = Math.max("LAST".length, ...rows.map((row) => row.lastCommit.length));
|
|
15211
|
-
const lines = [
|
|
15212
|
-
" " + "BRANCH".padEnd(branchWidth, " ") + " " + "STATUS".padEnd(statusWidth, " ") + " " + "UPSTREAM".padEnd(upstreamWidth, " ") + " " + "LAST".padEnd(lastCommitWidth, " ") + " PATH"
|
|
15213
|
-
];
|
|
15214
|
-
for (const row of rows) {
|
|
15215
|
-
lines.push(
|
|
15216
|
-
`${row.isCurrent ? "*" : " "} ${row.branch.padEnd(branchWidth, " ")} ${row.status.padEnd(statusWidth, " ")} ${row.upstream.padEnd(upstreamWidth, " ")} ${row.lastCommit.padEnd(lastCommitWidth, " ")} ` + row.path
|
|
15217
|
-
);
|
|
15218
|
-
}
|
|
15219
|
-
return lines.join("\n");
|
|
15167
|
+
function isConfiguredHookCommand(value) {
|
|
15168
|
+
if (typeof value === "string") return value.length > 0;
|
|
15169
|
+
return Array.isArray(value) && value.length > 0 && value[0] !== "" && value.every((item) => typeof item === "string");
|
|
15220
15170
|
}
|
|
15221
|
-
|
|
15222
|
-
|
|
15223
|
-
|
|
15224
|
-
|
|
15225
|
-
|
|
15226
|
-
|
|
15227
|
-
|
|
15228
|
-
|
|
15229
|
-
|
|
15230
|
-
|
|
15231
|
-
const lines = [" " + "BRANCH".padEnd(branchWidth, " ") + " PATH"];
|
|
15232
|
-
for (const row of rows) {
|
|
15233
|
-
lines.push(`${row.isCurrent ? "*" : " "} ${row.branch.padEnd(branchWidth, " ")} ${row.path}`);
|
|
15171
|
+
|
|
15172
|
+
// src/conflict.ts
|
|
15173
|
+
import { access as access3 } from "node:fs/promises";
|
|
15174
|
+
import { constants } from "node:fs";
|
|
15175
|
+
async function pathExists(path9) {
|
|
15176
|
+
try {
|
|
15177
|
+
await access3(path9, constants.F_OK);
|
|
15178
|
+
return true;
|
|
15179
|
+
} catch {
|
|
15180
|
+
return false;
|
|
15234
15181
|
}
|
|
15235
|
-
return lines.join("\n");
|
|
15236
15182
|
}
|
|
15237
|
-
function
|
|
15238
|
-
|
|
15239
|
-
|
|
15240
|
-
|
|
15241
|
-
|
|
15183
|
+
async function promptForPathConflict(path9) {
|
|
15184
|
+
const choice = await ve({
|
|
15185
|
+
message: `Target path already exists: ${path9}`,
|
|
15186
|
+
options: [
|
|
15187
|
+
{ value: "abort", label: "Abort", hint: "Keep the existing directory untouched" },
|
|
15188
|
+
{ value: "reuse", label: "Reuse path", hint: "Print the existing path and stop" }
|
|
15189
|
+
]
|
|
15242
15190
|
});
|
|
15191
|
+
if (pD(choice)) {
|
|
15192
|
+
return "abort";
|
|
15193
|
+
}
|
|
15194
|
+
return choice;
|
|
15243
15195
|
}
|
|
15244
15196
|
|
|
15245
15197
|
// src/new.ts
|
|
15246
|
-
|
|
15247
|
-
|
|
15248
|
-
|
|
15249
|
-
|
|
15250
|
-
|
|
15251
|
-
|
|
15252
|
-
|
|
15253
|
-
|
|
15254
|
-
|
|
15255
|
-
|
|
15256
|
-
|
|
15257
|
-
|
|
15198
|
+
var execFileAsync3 = promisify4(execFile3);
|
|
15199
|
+
var NEW_OUTPUT_FILE_ENV = "GJI_NEW_OUTPUT_FILE";
|
|
15200
|
+
function createNewCommand(dependencies = {}) {
|
|
15201
|
+
const createBranchPlaceholder = dependencies.createBranchPlaceholder ?? generateBranchPlaceholder;
|
|
15202
|
+
const promptForBranch = dependencies.promptForBranch ?? defaultPromptForBranch;
|
|
15203
|
+
const prompt = dependencies.promptForPathConflict ?? promptForPathConflict;
|
|
15204
|
+
const spawnEditor = dependencies.spawnEditor ?? defaultSpawnEditor;
|
|
15205
|
+
return async function runNewCommand2(options) {
|
|
15206
|
+
const repository = await detectRepository(options.cwd);
|
|
15207
|
+
const config = await loadEffectiveConfig(repository.repoRoot, void 0, options.stderr);
|
|
15208
|
+
const usesGeneratedDetachedName = options.detached && options.branch === void 0;
|
|
15209
|
+
if (options.editor && !options.open) {
|
|
15210
|
+
options.stderr("gji new: --editor has no effect without --open\n");
|
|
15258
15211
|
}
|
|
15259
|
-
|
|
15260
|
-
|
|
15261
|
-
|
|
15212
|
+
if (!options.detached && !options.branch && (options.json || isHeadless())) {
|
|
15213
|
+
const message = "branch argument is required";
|
|
15214
|
+
if (options.json) {
|
|
15215
|
+
options.stderr(`${JSON.stringify({ error: message }, null, 2)}
|
|
15216
|
+
`);
|
|
15217
|
+
} else {
|
|
15218
|
+
options.stderr(`gji new: ${message} in non-interactive mode (GJI_NO_TUI=1)
|
|
15219
|
+
`);
|
|
15220
|
+
}
|
|
15221
|
+
return 1;
|
|
15262
15222
|
}
|
|
15263
|
-
const
|
|
15264
|
-
|
|
15265
|
-
|
|
15266
|
-
|
|
15267
|
-
|
|
15223
|
+
const rawBranch = options.detached ? options.branch ?? createBranchPlaceholder() : options.branch ?? await promptForBranch(createBranchPlaceholder());
|
|
15224
|
+
if (!rawBranch) {
|
|
15225
|
+
if (options.json) {
|
|
15226
|
+
options.stderr(`${JSON.stringify({ error: "Aborted" }, null, 2)}
|
|
15227
|
+
`);
|
|
15228
|
+
} else {
|
|
15229
|
+
options.stderr("Aborted\n");
|
|
15230
|
+
}
|
|
15231
|
+
return 1;
|
|
15268
15232
|
}
|
|
15269
|
-
|
|
15270
|
-
|
|
15271
|
-
|
|
15233
|
+
if (!options.detached) {
|
|
15234
|
+
const branchError = validateBranchName(rawBranch);
|
|
15235
|
+
if (branchError) {
|
|
15236
|
+
if (options.json) {
|
|
15237
|
+
options.stderr(`${JSON.stringify({ error: branchError }, null, 2)}
|
|
15238
|
+
`);
|
|
15239
|
+
} else {
|
|
15240
|
+
options.stderr(`gji new: ${branchError}
|
|
15241
|
+
`);
|
|
15242
|
+
}
|
|
15243
|
+
return 1;
|
|
15244
|
+
}
|
|
15272
15245
|
}
|
|
15273
|
-
|
|
15274
|
-
|
|
15275
|
-
|
|
15276
|
-
|
|
15277
|
-
|
|
15278
|
-
|
|
15279
|
-
|
|
15280
|
-
|
|
15281
|
-
|
|
15282
|
-
|
|
15283
|
-
|
|
15246
|
+
const rawBasePath = resolveConfigString(config, "worktreePath");
|
|
15247
|
+
const configuredBasePath = rawBasePath?.startsWith("/") || rawBasePath?.startsWith("~") ? rawBasePath : void 0;
|
|
15248
|
+
const worktreeName = options.detached ? rawBranch : applyConfiguredBranchPrefix(rawBranch, config.branchPrefix);
|
|
15249
|
+
const worktreePath = usesGeneratedDetachedName ? await resolveUniqueDetachedWorktreePath(repository.repoRoot, worktreeName, configuredBasePath) : resolveWorktreePath(repository.repoRoot, worktreeName, configuredBasePath);
|
|
15250
|
+
if (!usesGeneratedDetachedName && await pathExists(worktreePath)) {
|
|
15251
|
+
if (options.force) {
|
|
15252
|
+
if (!options.dryRun) {
|
|
15253
|
+
try {
|
|
15254
|
+
await execFileAsync3("git", ["worktree", "remove", "--force", worktreePath], { cwd: repository.repoRoot });
|
|
15255
|
+
} catch (err) {
|
|
15256
|
+
if (!isNotRegisteredWorktreeError(err)) {
|
|
15257
|
+
const msg = `could not remove existing worktree at ${worktreePath}: ${toExecMessage(err)}`;
|
|
15258
|
+
if (options.json) {
|
|
15259
|
+
options.stderr(`${JSON.stringify({ warning: msg }, null, 2)}
|
|
15260
|
+
`);
|
|
15261
|
+
} else {
|
|
15262
|
+
options.stderr(`Warning: ${msg}
|
|
15263
|
+
`);
|
|
15264
|
+
}
|
|
15265
|
+
}
|
|
15266
|
+
}
|
|
15267
|
+
if (!options.detached) {
|
|
15268
|
+
try {
|
|
15269
|
+
await execFileAsync3("git", ["branch", "-D", worktreeName], { cwd: repository.repoRoot });
|
|
15270
|
+
} catch {
|
|
15271
|
+
}
|
|
15272
|
+
}
|
|
15273
|
+
}
|
|
15274
|
+
} else if (options.json || isHeadless()) {
|
|
15275
|
+
const message = `target worktree path already exists: ${worktreePath}`;
|
|
15276
|
+
if (options.json) {
|
|
15277
|
+
options.stderr(`${JSON.stringify({ error: message }, null, 2)}
|
|
15278
|
+
`);
|
|
15279
|
+
} else {
|
|
15280
|
+
options.stderr(`gji new: ${message} in non-interactive mode (GJI_NO_TUI=1)
|
|
15281
|
+
`);
|
|
15282
|
+
options.stderr(`Hint: Use 'gji remove ${worktreeName}' or 'gji clean' to remove the existing worktree
|
|
15283
|
+
`);
|
|
15284
|
+
options.stderr(`Hint: Use 'gji trigger-hook afterCreate' inside the worktree to re-run setup hooks
|
|
15285
|
+
`);
|
|
15286
|
+
}
|
|
15287
|
+
return 1;
|
|
15288
|
+
} else {
|
|
15289
|
+
const choice = await prompt(worktreePath);
|
|
15290
|
+
if (choice === "reuse") {
|
|
15291
|
+
appendHistory(worktreePath, worktreeName).catch(() => void 0);
|
|
15292
|
+
await writeOutput(worktreePath, options.stdout);
|
|
15293
|
+
return 0;
|
|
15294
|
+
}
|
|
15295
|
+
options.stderr(`Aborted because target worktree path already exists: ${worktreePath}
|
|
15296
|
+
`);
|
|
15297
|
+
return 1;
|
|
15298
|
+
}
|
|
15284
15299
|
}
|
|
15285
|
-
|
|
15286
|
-
|
|
15300
|
+
if (options.dryRun) {
|
|
15301
|
+
if (options.json) {
|
|
15302
|
+
options.stdout(`${JSON.stringify({ branch: worktreeName, path: worktreePath, dryRun: true }, null, 2)}
|
|
15303
|
+
`);
|
|
15304
|
+
} else {
|
|
15305
|
+
const resolvedEditor = options.open ? options.editor ?? resolveConfigString(config, "editor") : void 0;
|
|
15306
|
+
const openNote = resolvedEditor ? `, then open in ${resolvedEditor}` : "";
|
|
15307
|
+
options.stdout(`Would create worktree at ${worktreePath} (branch: ${worktreeName}${openNote})
|
|
15308
|
+
`);
|
|
15309
|
+
}
|
|
15310
|
+
return 0;
|
|
15311
|
+
}
|
|
15312
|
+
await mkdir4(dirname5(worktreePath), { recursive: true });
|
|
15313
|
+
const gitArgs = options.detached ? ["worktree", "add", "--detach", worktreePath] : await localBranchExists(repository.repoRoot, worktreeName) ? ["worktree", "add", worktreePath, worktreeName] : ["worktree", "add", "-b", worktreeName, worktreePath];
|
|
15314
|
+
await execFileAsync3("git", gitArgs, { cwd: repository.repoRoot });
|
|
15315
|
+
const syncPatterns = Array.isArray(config.syncFiles) ? config.syncFiles.filter((p2) => typeof p2 === "string") : [];
|
|
15316
|
+
for (const pattern of syncPatterns) {
|
|
15317
|
+
try {
|
|
15318
|
+
await syncFiles(repository.repoRoot, worktreePath, [pattern]);
|
|
15319
|
+
} catch (error) {
|
|
15320
|
+
options.stderr(`Warning: failed to sync file "${pattern}": ${error instanceof Error ? error.message : String(error)}
|
|
15321
|
+
`);
|
|
15322
|
+
}
|
|
15323
|
+
}
|
|
15324
|
+
await maybeRunInstallPrompt(worktreePath, repository.repoRoot, config, options.stderr, dependencies, !!options.json);
|
|
15325
|
+
const hooks = extractHooks(config);
|
|
15326
|
+
await runHook(
|
|
15327
|
+
hooks.afterCreate,
|
|
15328
|
+
worktreePath,
|
|
15329
|
+
{ branch: worktreeName, path: worktreePath, repo: basename3(repository.repoRoot) },
|
|
15330
|
+
options.stderr
|
|
15331
|
+
);
|
|
15332
|
+
if (options.json) {
|
|
15333
|
+
options.stdout(`${JSON.stringify({ branch: worktreeName, path: worktreePath }, null, 2)}
|
|
15334
|
+
`);
|
|
15335
|
+
} else {
|
|
15336
|
+
await appendHistory(worktreePath, worktreeName);
|
|
15337
|
+
await writeOutput(worktreePath, options.stdout);
|
|
15338
|
+
}
|
|
15339
|
+
if (options.open) {
|
|
15340
|
+
const resolvedEditor = options.editor ?? resolveConfigString(config, "editor");
|
|
15341
|
+
await openWorktree(worktreePath, resolvedEditor, spawnEditor, options.stderr);
|
|
15342
|
+
}
|
|
15343
|
+
return 0;
|
|
15344
|
+
};
|
|
15287
15345
|
}
|
|
15288
|
-
|
|
15289
|
-
|
|
15346
|
+
var runNewCommand = createNewCommand();
|
|
15347
|
+
function generateBranchPlaceholder(random = Math.random) {
|
|
15348
|
+
const roots = [
|
|
15349
|
+
"socrates",
|
|
15350
|
+
"prometheus",
|
|
15351
|
+
"beethoven",
|
|
15352
|
+
"ada",
|
|
15353
|
+
"turing",
|
|
15354
|
+
"hypatia",
|
|
15355
|
+
"tesla",
|
|
15356
|
+
"curie",
|
|
15357
|
+
"diogenes",
|
|
15358
|
+
"plato",
|
|
15359
|
+
"hephaestus",
|
|
15360
|
+
"athena",
|
|
15361
|
+
"archimedes",
|
|
15362
|
+
"euclid",
|
|
15363
|
+
"heraclitus",
|
|
15364
|
+
"galileo",
|
|
15365
|
+
"newton",
|
|
15366
|
+
"lovelace",
|
|
15367
|
+
"nietzsche",
|
|
15368
|
+
"kafka"
|
|
15369
|
+
];
|
|
15370
|
+
const antics = [
|
|
15371
|
+
"borrowed-a-bike",
|
|
15372
|
+
"brought-snacks",
|
|
15373
|
+
"missed-the-bus",
|
|
15374
|
+
"lost-the-keys",
|
|
15375
|
+
"spilled-the-coffee",
|
|
15376
|
+
"forgot-the-umbrella",
|
|
15377
|
+
"walked-the-dog",
|
|
15378
|
+
"missed-the-train",
|
|
15379
|
+
"wrote-a-poem",
|
|
15380
|
+
"burned-the-toast",
|
|
15381
|
+
"fed-the-pigeons",
|
|
15382
|
+
"watered-the-plants",
|
|
15383
|
+
"washed-the-dishes",
|
|
15384
|
+
"folded-the-laundry",
|
|
15385
|
+
"took-a-nap"
|
|
15386
|
+
];
|
|
15387
|
+
return `${pickRandom(roots, random)}-${pickRandom(antics, random)}`;
|
|
15290
15388
|
}
|
|
15291
|
-
|
|
15292
|
-
|
|
15293
|
-
|
|
15294
|
-
|
|
15295
|
-
|
|
15296
|
-
|
|
15297
|
-
|
|
15298
|
-
|
|
15299
|
-
|
|
15300
|
-
|
|
15301
|
-
|
|
15302
|
-
|
|
15303
|
-
|
|
15304
|
-
|
|
15305
|
-
|
|
15306
|
-
|
|
15307
|
-
{ name: "uv", signals: ["uv.lock"], command: "uv sync" },
|
|
15308
|
-
{ name: "pipenv", signals: ["Pipfile.lock"], command: "pipenv install" },
|
|
15309
|
-
{ name: "pdm", signals: ["pdm.lock"], command: "pdm install" },
|
|
15310
|
-
{ name: "conda-lock", signals: ["conda-lock.yml"], command: "conda-lock install" },
|
|
15311
|
-
{ name: "conda", signals: ["environment.yml"], command: "conda env update --file environment.yml" },
|
|
15312
|
-
// R
|
|
15313
|
-
{ name: "renv", signals: ["renv.lock"], command: "Rscript -e 'renv::restore()'" },
|
|
15314
|
-
// Rust
|
|
15315
|
-
{ name: "cargo", signals: ["Cargo.lock"], command: "cargo build" },
|
|
15316
|
-
// Go
|
|
15317
|
-
{ name: "go", signals: ["go.sum"], command: "go mod download" },
|
|
15318
|
-
// Ruby
|
|
15319
|
-
{ name: "bundler", signals: ["Gemfile.lock"], command: "bundle install" },
|
|
15320
|
-
// PHP
|
|
15321
|
-
{ name: "composer", signals: ["composer.lock"], command: "composer install" },
|
|
15322
|
-
// Elixir / Erlang
|
|
15323
|
-
{ name: "mix", signals: ["mix.lock"], command: "mix deps.get" },
|
|
15324
|
-
{ name: "rebar3", signals: ["rebar.lock"], command: "rebar3 deps" },
|
|
15325
|
-
// Dart / Flutter
|
|
15326
|
-
{ name: "dart", signals: ["pubspec.lock"], command: "dart pub get" },
|
|
15327
|
-
// Java / Kotlin / Scala
|
|
15328
|
-
{ name: "maven", signals: ["pom.xml"], command: "mvn install" },
|
|
15329
|
-
{ name: "gradle", signals: ["gradlew"], command: "./gradlew build" },
|
|
15330
|
-
{ name: "gradle", signals: ["build.gradle", "build.gradle.kts"], command: "gradle build" },
|
|
15331
|
-
{ name: "sbt", signals: ["build.sbt"], command: "sbt compile" },
|
|
15332
|
-
// .NET (C# / F# / VB)
|
|
15333
|
-
{ name: "dotnet", signals: ["*.sln", "*.csproj", "*.fsproj", "*.vbproj"], command: "dotnet restore", glob: true },
|
|
15334
|
-
// Swift
|
|
15335
|
-
{ name: "swift", signals: ["Package.swift"], command: "swift package resolve" },
|
|
15336
|
-
// Haskell
|
|
15337
|
-
{ name: "stack", signals: ["stack.yaml"], command: "stack build" },
|
|
15338
|
-
{ name: "cabal", signals: ["cabal.project"], command: "cabal install --only-dependencies" },
|
|
15339
|
-
{ name: "cabal", signals: ["*.cabal"], command: "cabal install --only-dependencies", glob: true },
|
|
15340
|
-
// Clojure
|
|
15341
|
-
{ name: "clojure", signals: ["deps.edn"], command: "clojure -P" },
|
|
15342
|
-
{ name: "leiningen", signals: ["project.clj"], command: "lein deps" },
|
|
15343
|
-
// OCaml
|
|
15344
|
-
{ name: "dune", signals: ["dune-project"], command: "dune build" },
|
|
15345
|
-
// Julia
|
|
15346
|
-
{ name: "julia", signals: ["Manifest.toml"], command: "julia --project -e 'using Pkg; Pkg.instantiate()'" },
|
|
15347
|
-
// Nim
|
|
15348
|
-
{ name: "nimble", signals: ["*.nimble"], command: "nimble install", glob: true },
|
|
15349
|
-
// Crystal
|
|
15350
|
-
{ name: "shards", signals: ["shard.yml"], command: "shards install" },
|
|
15351
|
-
// Perl
|
|
15352
|
-
{ name: "cpanm", signals: ["cpanfile"], command: "cpanm --installdeps ." },
|
|
15353
|
-
// Zig
|
|
15354
|
-
{ name: "zig", signals: ["build.zig.zon"], command: "zig build" },
|
|
15355
|
-
// C / C++
|
|
15356
|
-
{ name: "vcpkg", signals: ["vcpkg.json"], command: "vcpkg install" },
|
|
15357
|
-
{ name: "conan", signals: ["conanfile.py", "conanfile.txt"], command: "conan install ." },
|
|
15358
|
-
// Nix
|
|
15359
|
-
{ name: "nix", signals: ["flake.nix"], command: "nix develop" },
|
|
15360
|
-
{ name: "nix-shell", signals: ["shell.nix"], command: "nix-shell" },
|
|
15361
|
-
// Terraform / OpenTofu
|
|
15362
|
-
{ name: "terraform", signals: ["terraform.lock.hcl"], command: "terraform init" }
|
|
15363
|
-
];
|
|
15364
|
-
async function detectPackageManager(repoRoot) {
|
|
15365
|
-
for (const entry of ENTRIES) {
|
|
15366
|
-
const matched = entry.glob ? await matchesGlob(repoRoot, entry.signals) : await matchesExact(repoRoot, entry.signals);
|
|
15367
|
-
if (matched) {
|
|
15368
|
-
return { name: entry.name, installCommand: entry.command };
|
|
15389
|
+
function applyConfiguredBranchPrefix(branch, branchPrefix) {
|
|
15390
|
+
if (typeof branchPrefix !== "string" || branchPrefix.length === 0) {
|
|
15391
|
+
return branch;
|
|
15392
|
+
}
|
|
15393
|
+
if (branch.startsWith(branchPrefix)) {
|
|
15394
|
+
return branch;
|
|
15395
|
+
}
|
|
15396
|
+
return `${branchPrefix}${branch}`;
|
|
15397
|
+
}
|
|
15398
|
+
async function resolveUniqueDetachedWorktreePath(repoRoot, baseName, basePath) {
|
|
15399
|
+
let attempt = 1;
|
|
15400
|
+
while (true) {
|
|
15401
|
+
const candidateName = attempt === 1 ? baseName : `${baseName}-${attempt}`;
|
|
15402
|
+
const candidatePath = resolveWorktreePath(repoRoot, candidateName, basePath);
|
|
15403
|
+
if (!await pathExists(candidatePath)) {
|
|
15404
|
+
return candidatePath;
|
|
15369
15405
|
}
|
|
15406
|
+
attempt += 1;
|
|
15370
15407
|
}
|
|
15371
|
-
return null;
|
|
15372
15408
|
}
|
|
15373
|
-
async function
|
|
15374
|
-
|
|
15375
|
-
|
|
15376
|
-
|
|
15377
|
-
|
|
15378
|
-
|
|
15409
|
+
async function defaultPromptForBranch(placeholder) {
|
|
15410
|
+
const choice = await he({
|
|
15411
|
+
defaultValue: placeholder,
|
|
15412
|
+
message: "Name the new branch",
|
|
15413
|
+
placeholder,
|
|
15414
|
+
validate: (value) => {
|
|
15415
|
+
const trimmed = value.trim();
|
|
15416
|
+
return validateBranchName(trimmed) ?? void 0;
|
|
15379
15417
|
}
|
|
15418
|
+
});
|
|
15419
|
+
if (pD(choice)) {
|
|
15420
|
+
return null;
|
|
15380
15421
|
}
|
|
15381
|
-
return
|
|
15422
|
+
return choice.trim();
|
|
15382
15423
|
}
|
|
15383
|
-
|
|
15384
|
-
|
|
15424
|
+
function pickRandom(values, random) {
|
|
15425
|
+
const index = Math.floor(random() * values.length);
|
|
15426
|
+
return values[Math.min(index, values.length - 1)];
|
|
15427
|
+
}
|
|
15428
|
+
async function localBranchExists(repoRoot, branchName) {
|
|
15385
15429
|
try {
|
|
15386
|
-
|
|
15430
|
+
await execFileAsync3("git", ["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`], { cwd: repoRoot });
|
|
15431
|
+
return true;
|
|
15387
15432
|
} catch {
|
|
15388
15433
|
return false;
|
|
15389
15434
|
}
|
|
15390
|
-
const regexes = patterns.map(patternToRegex);
|
|
15391
|
-
return files.some((file) => regexes.some((re) => re.test(file)));
|
|
15392
15435
|
}
|
|
15393
|
-
function
|
|
15394
|
-
|
|
15395
|
-
return new RegExp(`^${escaped}$`);
|
|
15436
|
+
async function writeOutput(worktreePath, stdout) {
|
|
15437
|
+
await writeShellOutput(NEW_OUTPUT_FILE_ENV, worktreePath, stdout);
|
|
15396
15438
|
}
|
|
15397
|
-
|
|
15398
|
-
|
|
15399
|
-
|
|
15400
|
-
|
|
15439
|
+
function isNotRegisteredWorktreeError(error) {
|
|
15440
|
+
const stderr = hasExecStderr(error) ? error.stderr : String(error);
|
|
15441
|
+
return stderr.includes("is not a working tree") || stderr.includes("not a linked working tree");
|
|
15442
|
+
}
|
|
15443
|
+
function hasExecStderr(error) {
|
|
15444
|
+
return error instanceof Error && "stderr" in error && typeof error.stderr === "string";
|
|
15445
|
+
}
|
|
15446
|
+
function toExecMessage(error) {
|
|
15447
|
+
return hasExecStderr(error) ? error.stderr.trim() : String(error);
|
|
15448
|
+
}
|
|
15449
|
+
async function openWorktree(worktreePath, editorCli, spawnFn, stderr) {
|
|
15450
|
+
if (!editorCli) {
|
|
15451
|
+
stderr("gji new: --open requires --editor <cli> or a saved editor in config\n");
|
|
15401
15452
|
return;
|
|
15402
15453
|
}
|
|
15403
|
-
const
|
|
15404
|
-
|
|
15405
|
-
|
|
15454
|
+
const editorDef = EDITORS.find((e2) => e2.cli === editorCli);
|
|
15455
|
+
const args = [];
|
|
15456
|
+
if (editorDef?.newWindowFlag) {
|
|
15457
|
+
args.push(editorDef.newWindowFlag);
|
|
15406
15458
|
}
|
|
15407
|
-
|
|
15408
|
-
|
|
15459
|
+
args.push(worktreePath);
|
|
15460
|
+
try {
|
|
15461
|
+
await spawnFn(editorCli, args);
|
|
15462
|
+
} catch (error) {
|
|
15463
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
15464
|
+
stderr(`gji new: failed to open editor: ${message}
|
|
15465
|
+
`);
|
|
15409
15466
|
}
|
|
15410
|
-
|
|
15411
|
-
|
|
15412
|
-
|
|
15413
|
-
|
|
15467
|
+
}
|
|
15468
|
+
|
|
15469
|
+
// src/repo-registry.ts
|
|
15470
|
+
import { mkdir as mkdir5, readFile as readFile3, writeFile as writeFile4 } from "node:fs/promises";
|
|
15471
|
+
import { homedir as homedir4 } from "node:os";
|
|
15472
|
+
import { basename as basename4, dirname as dirname6, join as join6, resolve as resolve4 } from "node:path";
|
|
15473
|
+
var REGISTRY_FILE_NAME = "repos.json";
|
|
15474
|
+
var MAX_REGISTRY_ENTRIES = 100;
|
|
15475
|
+
function REGISTRY_FILE_PATH(home = homedir4()) {
|
|
15476
|
+
const configDir = process.env.GJI_CONFIG_DIR;
|
|
15477
|
+
if (configDir) {
|
|
15478
|
+
return join6(resolve4(configDir), REGISTRY_FILE_NAME);
|
|
15414
15479
|
}
|
|
15415
|
-
|
|
15416
|
-
|
|
15417
|
-
|
|
15418
|
-
|
|
15480
|
+
return join6(home, GLOBAL_CONFIG_DIRECTORY, REGISTRY_FILE_NAME);
|
|
15481
|
+
}
|
|
15482
|
+
async function loadRegistry(home = homedir4()) {
|
|
15483
|
+
const path9 = REGISTRY_FILE_PATH(home);
|
|
15484
|
+
try {
|
|
15485
|
+
const raw = await readFile3(path9, "utf8");
|
|
15486
|
+
const parsed = JSON.parse(raw);
|
|
15487
|
+
if (!Array.isArray(parsed)) return [];
|
|
15488
|
+
return parsed.filter(isRegistryEntry);
|
|
15489
|
+
} catch {
|
|
15490
|
+
return [];
|
|
15419
15491
|
}
|
|
15420
|
-
|
|
15421
|
-
|
|
15422
|
-
|
|
15423
|
-
|
|
15424
|
-
|
|
15425
|
-
|
|
15426
|
-
|
|
15492
|
+
}
|
|
15493
|
+
async function registerRepo(repoPath, home = homedir4()) {
|
|
15494
|
+
const registryPath = REGISTRY_FILE_PATH(home);
|
|
15495
|
+
const existing = await loadRegistry(home);
|
|
15496
|
+
if (existing.length > 0 && existing[0].path === repoPath) return;
|
|
15497
|
+
const entry = {
|
|
15498
|
+
lastUsed: Date.now(),
|
|
15499
|
+
name: basename4(repoPath),
|
|
15500
|
+
path: repoPath
|
|
15501
|
+
};
|
|
15502
|
+
const filtered = existing.filter((e2) => e2.path !== repoPath);
|
|
15503
|
+
const next = [entry, ...filtered].slice(0, MAX_REGISTRY_ENTRIES);
|
|
15504
|
+
await mkdir5(dirname6(registryPath), { recursive: true });
|
|
15505
|
+
await writeFile4(registryPath, `${JSON.stringify(next, null, 2)}
|
|
15506
|
+
`, "utf8");
|
|
15507
|
+
}
|
|
15508
|
+
function isRegistryEntry(value) {
|
|
15509
|
+
return typeof value === "object" && value !== null && "path" in value && typeof value.path === "string" && "name" in value && typeof value.name === "string" && "lastUsed" in value && typeof value.lastUsed === "number";
|
|
15510
|
+
}
|
|
15511
|
+
|
|
15512
|
+
// src/warp.ts
|
|
15513
|
+
var WARP_OUTPUT_FILE_ENV = "GJI_WARP_OUTPUT_FILE";
|
|
15514
|
+
async function runWarpCommand(options) {
|
|
15515
|
+
if (options.newWorktree) {
|
|
15516
|
+
const registry = await loadRegistry();
|
|
15517
|
+
if (registry.length === 0) {
|
|
15518
|
+
options.stderr(
|
|
15519
|
+
"gji warp: no repos registered yet.\nUse any gji command in a repository to register it automatically.\n"
|
|
15520
|
+
);
|
|
15521
|
+
return 1;
|
|
15427
15522
|
}
|
|
15523
|
+
return runWarpNew(options, registry);
|
|
15428
15524
|
}
|
|
15429
|
-
|
|
15430
|
-
|
|
15431
|
-
|
|
15432
|
-
if (
|
|
15433
|
-
|
|
15434
|
-
|
|
15435
|
-
|
|
15436
|
-
await writeGlobalKey(repoRoot, "hooks", { ...existingHooks, afterCreate: pm.installCommand });
|
|
15437
|
-
} else {
|
|
15438
|
-
const { config: localConfig } = await loadConfig(repoRoot);
|
|
15439
|
-
const existingLocalHooks = isPlainObject2(localConfig.hooks) ? localConfig.hooks : {};
|
|
15440
|
-
await writeKey(repoRoot, "hooks", { ...existingLocalHooks, afterCreate: pm.installCommand });
|
|
15441
|
-
}
|
|
15442
|
-
} catch (error) {
|
|
15443
|
-
stderr(`gji: failed to save config: ${error instanceof Error ? error.message : String(error)}
|
|
15525
|
+
return runWarpNavigate(options);
|
|
15526
|
+
}
|
|
15527
|
+
async function runWarpNavigate(options) {
|
|
15528
|
+
if ((isHeadless() || options.json) && !options.branch) {
|
|
15529
|
+
const message = "branch argument is required";
|
|
15530
|
+
if (options.json) {
|
|
15531
|
+
options.stderr(`${JSON.stringify({ error: message }, null, 2)}
|
|
15444
15532
|
`);
|
|
15533
|
+
} else {
|
|
15534
|
+
options.stderr(
|
|
15535
|
+
"gji warp: branch argument is required in non-interactive mode (GJI_NO_TUI=1)\n"
|
|
15536
|
+
);
|
|
15445
15537
|
}
|
|
15538
|
+
return 1;
|
|
15446
15539
|
}
|
|
15447
|
-
|
|
15448
|
-
|
|
15449
|
-
|
|
15450
|
-
|
|
15451
|
-
} else {
|
|
15452
|
-
await writeKey(repoRoot, "skipInstallPrompt", true);
|
|
15453
|
-
}
|
|
15454
|
-
} catch (error) {
|
|
15455
|
-
stderr(`gji: failed to save config: ${error instanceof Error ? error.message : String(error)}
|
|
15540
|
+
const target = await resolveWarpTarget({ ...options, commandName: "gji warp", json: options.json });
|
|
15541
|
+
if (!target) return 1;
|
|
15542
|
+
if (options.json) {
|
|
15543
|
+
options.stdout(`${JSON.stringify({ branch: target.branch, path: target.path }, null, 2)}
|
|
15456
15544
|
`);
|
|
15457
|
-
|
|
15545
|
+
return 0;
|
|
15458
15546
|
}
|
|
15547
|
+
appendHistory(target.path, target.branch).catch(() => void 0);
|
|
15548
|
+
await writeShellOutput(WARP_OUTPUT_FILE_ENV, target.path, options.stdout);
|
|
15549
|
+
return 0;
|
|
15459
15550
|
}
|
|
15460
|
-
async function
|
|
15461
|
-
|
|
15462
|
-
|
|
15463
|
-
|
|
15464
|
-
|
|
15465
|
-
|
|
15466
|
-
|
|
15467
|
-
|
|
15468
|
-
|
|
15469
|
-
|
|
15470
|
-
|
|
15471
|
-
|
|
15551
|
+
async function runWarpNew(options, registry) {
|
|
15552
|
+
let targetRepoRoot;
|
|
15553
|
+
if (registry.length === 1) {
|
|
15554
|
+
targetRepoRoot = registry[0].path;
|
|
15555
|
+
} else {
|
|
15556
|
+
if (isHeadless()) {
|
|
15557
|
+
options.stderr(
|
|
15558
|
+
"gji warp: repo argument is required in non-interactive mode (GJI_NO_TUI=1)\n"
|
|
15559
|
+
);
|
|
15560
|
+
return 1;
|
|
15561
|
+
}
|
|
15562
|
+
const choice = await ve({
|
|
15563
|
+
message: "Create worktree in which repo?",
|
|
15564
|
+
options: registry.map((entry) => ({
|
|
15565
|
+
value: entry.path,
|
|
15566
|
+
label: entry.name,
|
|
15567
|
+
hint: entry.path
|
|
15568
|
+
}))
|
|
15472
15569
|
});
|
|
15473
|
-
|
|
15474
|
-
|
|
15570
|
+
if (pD(choice)) {
|
|
15571
|
+
options.stderr("Aborted\n");
|
|
15572
|
+
return 1;
|
|
15573
|
+
}
|
|
15574
|
+
targetRepoRoot = choice;
|
|
15575
|
+
}
|
|
15576
|
+
if (options.json) {
|
|
15577
|
+
return runNewCommand({
|
|
15578
|
+
branch: options.branch,
|
|
15579
|
+
cwd: targetRepoRoot,
|
|
15580
|
+
json: true,
|
|
15581
|
+
stderr: options.stderr,
|
|
15582
|
+
stdout: options.stdout
|
|
15475
15583
|
});
|
|
15584
|
+
}
|
|
15585
|
+
let capturedPath = "";
|
|
15586
|
+
const captureStdout = (chunk) => {
|
|
15587
|
+
capturedPath = chunk.trim();
|
|
15588
|
+
};
|
|
15589
|
+
const exitCode = await runNewCommand({
|
|
15590
|
+
branch: options.branch,
|
|
15591
|
+
cwd: targetRepoRoot,
|
|
15592
|
+
stderr: options.stderr,
|
|
15593
|
+
stdout: captureStdout
|
|
15476
15594
|
});
|
|
15595
|
+
if (exitCode !== 0) {
|
|
15596
|
+
return exitCode;
|
|
15597
|
+
}
|
|
15598
|
+
if (!capturedPath) {
|
|
15599
|
+
options.stderr("gji warp: could not determine new worktree path\n");
|
|
15600
|
+
return 1;
|
|
15601
|
+
}
|
|
15602
|
+
await writeShellOutput(WARP_OUTPUT_FILE_ENV, capturedPath, options.stdout);
|
|
15603
|
+
return 0;
|
|
15477
15604
|
}
|
|
15478
|
-
|
|
15479
|
-
|
|
15480
|
-
|
|
15481
|
-
|
|
15482
|
-
|
|
15483
|
-
|
|
15484
|
-
|
|
15485
|
-
|
|
15486
|
-
|
|
15487
|
-
|
|
15488
|
-
return
|
|
15605
|
+
function findByQuery(items, query) {
|
|
15606
|
+
const slashIdx = query.indexOf("/");
|
|
15607
|
+
if (slashIdx !== -1) {
|
|
15608
|
+
const repoQuery = query.slice(0, slashIdx);
|
|
15609
|
+
const branchQuery = query.slice(slashIdx + 1);
|
|
15610
|
+
const match = items.find(
|
|
15611
|
+
(item) => item.repoName === repoQuery && item.worktree.branch === branchQuery
|
|
15612
|
+
);
|
|
15613
|
+
if (match) return match;
|
|
15614
|
+
}
|
|
15615
|
+
return items.find((item) => item.worktree.branch === query) ?? null;
|
|
15489
15616
|
}
|
|
15490
|
-
async function
|
|
15491
|
-
const
|
|
15492
|
-
|
|
15493
|
-
options
|
|
15494
|
-
{
|
|
15495
|
-
|
|
15496
|
-
|
|
15497
|
-
{
|
|
15498
|
-
|
|
15499
|
-
|
|
15500
|
-
|
|
15617
|
+
async function resolveWarpTarget(options) {
|
|
15618
|
+
const cmd = options.commandName ?? "gji";
|
|
15619
|
+
const emitError4 = (message, hint) => {
|
|
15620
|
+
if (options.json) {
|
|
15621
|
+
options.stderr(`${JSON.stringify({ error: message }, null, 2)}
|
|
15622
|
+
`);
|
|
15623
|
+
} else {
|
|
15624
|
+
options.stderr(`${cmd}: ${message}
|
|
15625
|
+
`);
|
|
15626
|
+
if (hint) options.stderr(hint);
|
|
15627
|
+
}
|
|
15628
|
+
};
|
|
15629
|
+
const registry = await loadRegistry();
|
|
15630
|
+
if (registry.length === 0) {
|
|
15631
|
+
emitError4(
|
|
15632
|
+
"not in a git repository and no repos registered yet.",
|
|
15633
|
+
"Use any gji command inside a repository to register it.\n"
|
|
15634
|
+
);
|
|
15501
15635
|
return null;
|
|
15502
15636
|
}
|
|
15503
|
-
|
|
15504
|
-
|
|
15505
|
-
|
|
15506
|
-
|
|
15507
|
-
}
|
|
15508
|
-
|
|
15509
|
-
|
|
15510
|
-
|
|
15511
|
-
|
|
15512
|
-
|
|
15513
|
-
|
|
15514
|
-
|
|
15515
|
-
|
|
15516
|
-
}
|
|
15517
|
-
|
|
15637
|
+
const results = await Promise.allSettled(
|
|
15638
|
+
registry.map(async (entry) => {
|
|
15639
|
+
const worktrees = await listWorktrees(entry.path);
|
|
15640
|
+
return { repoName: entry.name, worktrees };
|
|
15641
|
+
})
|
|
15642
|
+
);
|
|
15643
|
+
const allItems = [];
|
|
15644
|
+
for (const result of results) {
|
|
15645
|
+
if (result.status === "rejected") continue;
|
|
15646
|
+
const { repoName, worktrees } = result.value;
|
|
15647
|
+
for (const worktree of worktrees) {
|
|
15648
|
+
allItems.push({ repoName, worktree });
|
|
15649
|
+
}
|
|
15650
|
+
}
|
|
15651
|
+
if (allItems.length === 0) {
|
|
15652
|
+
emitError4("no accessible worktrees found in any registered repo.");
|
|
15653
|
+
return null;
|
|
15654
|
+
}
|
|
15655
|
+
if (options.branch) {
|
|
15656
|
+
const match = findByQuery(allItems, options.branch);
|
|
15657
|
+
if (!match) {
|
|
15658
|
+
emitError4(`no worktree found matching: ${options.branch}`);
|
|
15659
|
+
return null;
|
|
15660
|
+
}
|
|
15661
|
+
return { branch: match.worktree.branch, path: match.worktree.path };
|
|
15518
15662
|
}
|
|
15663
|
+
const path9 = await promptForWarpTarget(allItems);
|
|
15664
|
+
if (!path9) {
|
|
15665
|
+
options.stderr("Aborted\n");
|
|
15666
|
+
return null;
|
|
15667
|
+
}
|
|
15668
|
+
const chosen = allItems.find((item) => item.worktree.path === path9);
|
|
15669
|
+
return { branch: chosen?.worktree.branch ?? null, path: path9 };
|
|
15519
15670
|
}
|
|
15520
|
-
async function
|
|
15671
|
+
async function promptForWarpTarget(items) {
|
|
15672
|
+
const healthResults = await Promise.allSettled(
|
|
15673
|
+
items.map((item) => readWorktreeHealth(item.worktree.path))
|
|
15674
|
+
);
|
|
15521
15675
|
const choice = await ve({
|
|
15522
|
-
message:
|
|
15523
|
-
options:
|
|
15524
|
-
|
|
15525
|
-
|
|
15526
|
-
|
|
15676
|
+
message: "Warp to a worktree",
|
|
15677
|
+
options: items.map((item, i) => {
|
|
15678
|
+
const health = healthResults[i].status === "fulfilled" ? healthResults[i].value : null;
|
|
15679
|
+
const upstream = health ? formatHint(item.worktree.branch, health) : null;
|
|
15680
|
+
const label = `${item.repoName} \u203A ${item.worktree.branch ?? "(detached)"}`;
|
|
15681
|
+
const pathHint = item.worktree.isCurrent ? `${item.worktree.path} (current)` : item.worktree.path;
|
|
15682
|
+
const hint = upstream ? `${upstream} \xB7 ${pathHint}` : pathHint;
|
|
15683
|
+
return { hint, label, value: item.worktree.path };
|
|
15684
|
+
})
|
|
15527
15685
|
});
|
|
15528
15686
|
if (pD(choice)) {
|
|
15529
|
-
return
|
|
15687
|
+
return null;
|
|
15530
15688
|
}
|
|
15531
15689
|
return choice;
|
|
15532
15690
|
}
|
|
15691
|
+
function formatHint(branch, health) {
|
|
15692
|
+
if (branch === null) return null;
|
|
15693
|
+
if (!health.hasUpstream) return "no upstream";
|
|
15694
|
+
if (health.upstreamGone) return "upstream gone";
|
|
15695
|
+
if (health.ahead === 0 && health.behind === 0) return "up to date";
|
|
15696
|
+
if (health.ahead === 0) return `behind ${health.behind}`;
|
|
15697
|
+
if (health.behind === 0) return `ahead ${health.ahead}`;
|
|
15698
|
+
return `ahead ${health.ahead}, behind ${health.behind}`;
|
|
15699
|
+
}
|
|
15533
15700
|
|
|
15534
|
-
// src/
|
|
15535
|
-
var
|
|
15536
|
-
|
|
15537
|
-
|
|
15538
|
-
|
|
15539
|
-
|
|
15540
|
-
|
|
15541
|
-
|
|
15542
|
-
|
|
15543
|
-
|
|
15544
|
-
|
|
15545
|
-
|
|
15546
|
-
|
|
15547
|
-
if (options.
|
|
15548
|
-
options.stderr(
|
|
15549
|
-
|
|
15550
|
-
|
|
15551
|
-
|
|
15552
|
-
`);
|
|
15701
|
+
// src/go.ts
|
|
15702
|
+
var GO_OUTPUT_FILE_ENV = "GJI_GO_OUTPUT_FILE";
|
|
15703
|
+
function createGoCommand(dependencies = {}) {
|
|
15704
|
+
const prompt = dependencies.promptForWorktree ?? promptForWorktree;
|
|
15705
|
+
return async function runGoCommand2(options) {
|
|
15706
|
+
let worktrees;
|
|
15707
|
+
let repository;
|
|
15708
|
+
try {
|
|
15709
|
+
[worktrees, repository] = await Promise.all([
|
|
15710
|
+
listWorktrees(options.cwd),
|
|
15711
|
+
detectRepository(options.cwd)
|
|
15712
|
+
]);
|
|
15713
|
+
} catch {
|
|
15714
|
+
if (isHeadless() && !options.branch) {
|
|
15715
|
+
options.stderr(
|
|
15716
|
+
"gji go: branch argument is required in non-interactive mode (GJI_NO_TUI=1)\n"
|
|
15717
|
+
);
|
|
15718
|
+
return 1;
|
|
15553
15719
|
}
|
|
15720
|
+
const target = await resolveWarpTarget({ ...options, commandName: "gji go" });
|
|
15721
|
+
if (!target) return 1;
|
|
15722
|
+
appendHistory(target.path, target.branch).catch(() => void 0);
|
|
15723
|
+
await writeShellOutput(GO_OUTPUT_FILE_ENV, target.path, options.stdout);
|
|
15724
|
+
return 0;
|
|
15725
|
+
}
|
|
15726
|
+
if (!options.branch && isHeadless()) {
|
|
15727
|
+
options.stderr("gji go: branch argument is required in non-interactive mode (GJI_NO_TUI=1)\n");
|
|
15554
15728
|
return 1;
|
|
15555
15729
|
}
|
|
15556
|
-
const
|
|
15557
|
-
|
|
15558
|
-
|
|
15559
|
-
|
|
15730
|
+
const prompted = options.branch ? null : await prompt(sortByCurrentFirst(worktrees));
|
|
15731
|
+
const resolvedPath = options.branch ? worktrees.find((entry) => entry.branch === options.branch)?.path : prompted ?? void 0;
|
|
15732
|
+
if (!resolvedPath) {
|
|
15733
|
+
if (options.branch) {
|
|
15734
|
+
options.stderr(`No worktree found for branch: ${options.branch}
|
|
15735
|
+
`);
|
|
15736
|
+
options.stderr(`Hint: Use 'gji ls' to see available worktrees
|
|
15560
15737
|
`);
|
|
15561
15738
|
} else {
|
|
15562
15739
|
options.stderr("Aborted\n");
|
|
15563
15740
|
}
|
|
15564
15741
|
return 1;
|
|
15565
15742
|
}
|
|
15566
|
-
|
|
15567
|
-
|
|
15568
|
-
|
|
15569
|
-
|
|
15570
|
-
|
|
15743
|
+
const chosenWorktree = worktrees.find((w2) => w2.path === resolvedPath);
|
|
15744
|
+
const config = await loadEffectiveConfig(repository.repoRoot, void 0, options.stderr);
|
|
15745
|
+
const hooks = extractHooks(config);
|
|
15746
|
+
await runHook(
|
|
15747
|
+
hooks.afterEnter,
|
|
15748
|
+
resolvedPath,
|
|
15749
|
+
{ branch: chosenWorktree?.branch ?? void 0, path: resolvedPath, repo: basename5(repository.repoRoot) },
|
|
15750
|
+
options.stderr
|
|
15751
|
+
);
|
|
15752
|
+
appendHistory(resolvedPath, chosenWorktree?.branch ?? null).catch(() => void 0);
|
|
15753
|
+
await writeShellOutput(GO_OUTPUT_FILE_ENV, resolvedPath, options.stdout);
|
|
15754
|
+
return 0;
|
|
15755
|
+
};
|
|
15756
|
+
}
|
|
15757
|
+
var runGoCommand = createGoCommand();
|
|
15758
|
+
async function promptForWorktree(worktrees) {
|
|
15759
|
+
const healthResults = await Promise.allSettled(
|
|
15760
|
+
worktrees.map((w2) => readWorktreeHealth(w2.path))
|
|
15761
|
+
);
|
|
15762
|
+
const choice = await ve({
|
|
15763
|
+
message: "Choose a worktree",
|
|
15764
|
+
options: worktrees.map((worktree, i) => {
|
|
15765
|
+
const health = healthResults[i].status === "fulfilled" ? healthResults[i].value : null;
|
|
15766
|
+
const pathHint = worktree.isCurrent ? `${worktree.path} (current)` : worktree.path;
|
|
15767
|
+
const upstream = health ? formatUpstreamHint(worktree.branch, health) : null;
|
|
15768
|
+
return {
|
|
15769
|
+
value: worktree.path,
|
|
15770
|
+
label: worktree.branch ?? "(detached)",
|
|
15771
|
+
hint: upstream ? `${upstream} \xB7 ${pathHint}` : pathHint
|
|
15772
|
+
};
|
|
15773
|
+
})
|
|
15774
|
+
});
|
|
15775
|
+
if (pD(choice)) {
|
|
15776
|
+
return null;
|
|
15777
|
+
}
|
|
15778
|
+
return choice;
|
|
15779
|
+
}
|
|
15780
|
+
function formatUpstreamHint(branch, health) {
|
|
15781
|
+
if (branch === null) return null;
|
|
15782
|
+
if (!health.hasUpstream) return "no upstream";
|
|
15783
|
+
if (health.upstreamGone) return "upstream gone";
|
|
15784
|
+
if (health.ahead === 0 && health.behind === 0) return "up to date";
|
|
15785
|
+
if (health.ahead === 0) return `behind ${health.behind}`;
|
|
15786
|
+
if (health.behind === 0) return `ahead ${health.ahead}`;
|
|
15787
|
+
return `ahead ${health.ahead}, behind ${health.behind}`;
|
|
15788
|
+
}
|
|
15789
|
+
|
|
15790
|
+
// src/open.ts
|
|
15791
|
+
import { execFile as execFile4 } from "node:child_process";
|
|
15792
|
+
import { access as access4, writeFile as writeFile5 } from "node:fs/promises";
|
|
15793
|
+
import { join as join7 } from "node:path";
|
|
15794
|
+
import { promisify as promisify5 } from "node:util";
|
|
15795
|
+
var execFileAsync4 = promisify5(execFile4);
|
|
15796
|
+
function createOpenCommand(dependencies = {}) {
|
|
15797
|
+
const detectEditors = dependencies.detectEditors ?? detectInstalledEditors;
|
|
15798
|
+
const promptForEditor = dependencies.promptForEditor ?? defaultPromptForEditor;
|
|
15799
|
+
const promptForWorktree2 = dependencies.promptForWorktree ?? defaultPromptForWorktree;
|
|
15800
|
+
const spawnEditor = dependencies.spawnEditor ?? defaultSpawnEditor;
|
|
15801
|
+
return async function runOpenCommand2(options) {
|
|
15802
|
+
const [worktrees, repository] = await Promise.all([
|
|
15803
|
+
listWorktrees(options.cwd),
|
|
15804
|
+
detectRepository(options.cwd)
|
|
15805
|
+
]);
|
|
15806
|
+
let targetPath;
|
|
15807
|
+
if (options.branch) {
|
|
15808
|
+
const entry = worktrees.find((w2) => w2.branch === options.branch);
|
|
15809
|
+
if (!entry) {
|
|
15810
|
+
options.stderr(`gji open: no worktree found for branch: ${options.branch}
|
|
15571
15811
|
`);
|
|
15572
|
-
|
|
15573
|
-
options.stderr(`gji new: ${branchError}
|
|
15812
|
+
options.stderr(`Hint: Use 'gji ls' to see available worktrees
|
|
15574
15813
|
`);
|
|
15575
|
-
}
|
|
15576
15814
|
return 1;
|
|
15577
15815
|
}
|
|
15816
|
+
targetPath = entry.path;
|
|
15817
|
+
} else if (isHeadless()) {
|
|
15818
|
+
targetPath = worktrees.find((w2) => w2.isCurrent)?.path ?? options.cwd;
|
|
15819
|
+
} else {
|
|
15820
|
+
const chosen = await promptForWorktree2(sortByCurrentFirst(worktrees));
|
|
15821
|
+
if (!chosen) {
|
|
15822
|
+
options.stderr("Aborted\n");
|
|
15823
|
+
return 1;
|
|
15824
|
+
}
|
|
15825
|
+
targetPath = chosen;
|
|
15578
15826
|
}
|
|
15579
|
-
const
|
|
15580
|
-
const
|
|
15581
|
-
|
|
15582
|
-
|
|
15583
|
-
|
|
15584
|
-
|
|
15585
|
-
|
|
15586
|
-
|
|
15587
|
-
|
|
15588
|
-
|
|
15589
|
-
|
|
15590
|
-
|
|
15591
|
-
|
|
15592
|
-
options.stderr(`${JSON.stringify({ warning: msg }, null, 2)}
|
|
15593
|
-
`);
|
|
15594
|
-
} else {
|
|
15595
|
-
options.stderr(`Warning: ${msg}
|
|
15596
|
-
`);
|
|
15597
|
-
}
|
|
15598
|
-
}
|
|
15599
|
-
}
|
|
15600
|
-
if (!options.detached) {
|
|
15601
|
-
try {
|
|
15602
|
-
await execFileAsync3("git", ["branch", "-D", worktreeName], { cwd: repository.repoRoot });
|
|
15603
|
-
} catch {
|
|
15604
|
-
}
|
|
15605
|
-
}
|
|
15606
|
-
}
|
|
15607
|
-
} else if (options.json || isHeadless()) {
|
|
15608
|
-
const message = `target worktree path already exists: ${worktreePath}`;
|
|
15609
|
-
if (options.json) {
|
|
15610
|
-
options.stderr(`${JSON.stringify({ error: message }, null, 2)}
|
|
15611
|
-
`);
|
|
15612
|
-
} else {
|
|
15613
|
-
options.stderr(`gji new: ${message} in non-interactive mode (GJI_NO_TUI=1)
|
|
15614
|
-
`);
|
|
15615
|
-
options.stderr(`Hint: Use 'gji remove ${worktreeName}' or 'gji clean' to remove the existing worktree
|
|
15616
|
-
`);
|
|
15617
|
-
options.stderr(`Hint: Use 'gji trigger-hook afterCreate' inside the worktree to re-run setup hooks
|
|
15618
|
-
`);
|
|
15619
|
-
}
|
|
15827
|
+
const config = await loadEffectiveConfig(repository.repoRoot, void 0, options.stderr);
|
|
15828
|
+
const savedEditor = resolveConfigString(config, "editor");
|
|
15829
|
+
let editorCli;
|
|
15830
|
+
if (options.editor) {
|
|
15831
|
+
editorCli = options.editor;
|
|
15832
|
+
} else if (savedEditor) {
|
|
15833
|
+
editorCli = savedEditor;
|
|
15834
|
+
} else {
|
|
15835
|
+
const installed = await detectEditors();
|
|
15836
|
+
if (installed.length === 0) {
|
|
15837
|
+
options.stderr(
|
|
15838
|
+
"gji open: no supported editor detected. Use --editor <code|cursor|zed|...> to specify one.\n"
|
|
15839
|
+
);
|
|
15620
15840
|
return 1;
|
|
15841
|
+
}
|
|
15842
|
+
if (installed.length === 1 || isHeadless()) {
|
|
15843
|
+
editorCli = installed[0].cli;
|
|
15621
15844
|
} else {
|
|
15622
|
-
const
|
|
15623
|
-
if (
|
|
15624
|
-
|
|
15625
|
-
|
|
15626
|
-
return 0;
|
|
15845
|
+
const chosen = await promptForEditor(installed);
|
|
15846
|
+
if (!chosen) {
|
|
15847
|
+
options.stderr("Aborted\n");
|
|
15848
|
+
return 1;
|
|
15627
15849
|
}
|
|
15628
|
-
|
|
15629
|
-
`);
|
|
15630
|
-
return 1;
|
|
15850
|
+
editorCli = chosen;
|
|
15631
15851
|
}
|
|
15632
15852
|
}
|
|
15633
|
-
if (options.
|
|
15634
|
-
|
|
15635
|
-
|
|
15853
|
+
if (options.save && editorCli !== savedEditor) {
|
|
15854
|
+
await updateGlobalConfigKey("editor", editorCli);
|
|
15855
|
+
const displayName2 = EDITORS.find((e2) => e2.cli === editorCli)?.name ?? editorCli;
|
|
15856
|
+
options.stdout(`Saved editor "${displayName2}" to global config
|
|
15636
15857
|
`);
|
|
15858
|
+
}
|
|
15859
|
+
const editorDef = EDITORS.find((e2) => e2.cli === editorCli);
|
|
15860
|
+
let openTarget = targetPath;
|
|
15861
|
+
if (options.workspace) {
|
|
15862
|
+
if (editorDef?.supportsWorkspace) {
|
|
15863
|
+
openTarget = await ensureWorkspaceFile(targetPath, repository.repoName);
|
|
15637
15864
|
} else {
|
|
15638
|
-
|
|
15865
|
+
const displayName2 = editorDef?.name ?? editorCli;
|
|
15866
|
+
options.stderr(`gji open: --workspace is not supported for ${displayName2}, ignoring
|
|
15639
15867
|
`);
|
|
15640
15868
|
}
|
|
15641
|
-
return 0;
|
|
15642
15869
|
}
|
|
15643
|
-
|
|
15644
|
-
|
|
15645
|
-
|
|
15646
|
-
const syncPatterns = Array.isArray(config.syncFiles) ? config.syncFiles.filter((p2) => typeof p2 === "string") : [];
|
|
15647
|
-
for (const pattern of syncPatterns) {
|
|
15648
|
-
try {
|
|
15649
|
-
await syncFiles(repository.repoRoot, worktreePath, [pattern]);
|
|
15650
|
-
} catch (error) {
|
|
15651
|
-
options.stderr(`Warning: failed to sync file "${pattern}": ${error instanceof Error ? error.message : String(error)}
|
|
15652
|
-
`);
|
|
15653
|
-
}
|
|
15870
|
+
const args = [];
|
|
15871
|
+
if (editorDef?.newWindowFlag) {
|
|
15872
|
+
args.push(editorDef.newWindowFlag);
|
|
15654
15873
|
}
|
|
15655
|
-
|
|
15656
|
-
|
|
15657
|
-
|
|
15658
|
-
|
|
15659
|
-
|
|
15660
|
-
|
|
15661
|
-
options.stderr
|
|
15662
|
-
);
|
|
15663
|
-
if (options.json) {
|
|
15664
|
-
options.stdout(`${JSON.stringify({ branch: worktreeName, path: worktreePath }, null, 2)}
|
|
15874
|
+
args.push(openTarget);
|
|
15875
|
+
try {
|
|
15876
|
+
await spawnEditor(editorCli, args);
|
|
15877
|
+
} catch (error) {
|
|
15878
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
15879
|
+
options.stderr(`gji open: failed to launch editor: ${message}
|
|
15665
15880
|
`);
|
|
15666
|
-
|
|
15667
|
-
await appendHistory(worktreePath, worktreeName);
|
|
15668
|
-
await writeOutput(worktreePath, options.stdout);
|
|
15881
|
+
return 1;
|
|
15669
15882
|
}
|
|
15883
|
+
const displayName = editorDef?.name ?? editorCli;
|
|
15884
|
+
options.stdout(`Opened ${targetPath} in ${displayName}
|
|
15885
|
+
`);
|
|
15670
15886
|
return 0;
|
|
15671
15887
|
};
|
|
15672
15888
|
}
|
|
15673
|
-
var
|
|
15674
|
-
function
|
|
15675
|
-
const
|
|
15676
|
-
|
|
15677
|
-
|
|
15678
|
-
|
|
15679
|
-
|
|
15680
|
-
|
|
15681
|
-
|
|
15682
|
-
"
|
|
15683
|
-
|
|
15684
|
-
|
|
15685
|
-
|
|
15686
|
-
|
|
15687
|
-
|
|
15688
|
-
|
|
15689
|
-
|
|
15690
|
-
"
|
|
15691
|
-
|
|
15692
|
-
|
|
15693
|
-
|
|
15694
|
-
|
|
15695
|
-
|
|
15696
|
-
|
|
15697
|
-
|
|
15698
|
-
|
|
15699
|
-
|
|
15700
|
-
|
|
15701
|
-
|
|
15702
|
-
"
|
|
15703
|
-
|
|
15704
|
-
|
|
15705
|
-
|
|
15706
|
-
|
|
15707
|
-
|
|
15708
|
-
|
|
15709
|
-
|
|
15710
|
-
|
|
15711
|
-
|
|
15712
|
-
|
|
15713
|
-
|
|
15714
|
-
|
|
15889
|
+
var runOpenCommand = createOpenCommand();
|
|
15890
|
+
async function detectInstalledEditors() {
|
|
15891
|
+
const results = await Promise.all(
|
|
15892
|
+
EDITORS.map(async (editor) => ({ editor, available: await isCommandAvailable(editor.cli) }))
|
|
15893
|
+
);
|
|
15894
|
+
return results.filter((r2) => r2.available).map((r2) => r2.editor);
|
|
15895
|
+
}
|
|
15896
|
+
async function isCommandAvailable(command) {
|
|
15897
|
+
try {
|
|
15898
|
+
await execFileAsync4("which", [command]);
|
|
15899
|
+
return true;
|
|
15900
|
+
} catch {
|
|
15901
|
+
return false;
|
|
15902
|
+
}
|
|
15903
|
+
}
|
|
15904
|
+
async function defaultPromptForWorktree(worktrees) {
|
|
15905
|
+
const choice = await ve({
|
|
15906
|
+
message: "Choose a worktree to open",
|
|
15907
|
+
options: worktrees.map((w2) => ({
|
|
15908
|
+
value: w2.path,
|
|
15909
|
+
label: w2.branch ?? "(detached)",
|
|
15910
|
+
hint: w2.isCurrent ? `${w2.path} (current)` : w2.path
|
|
15911
|
+
}))
|
|
15912
|
+
});
|
|
15913
|
+
if (pD(choice)) return null;
|
|
15914
|
+
return choice;
|
|
15915
|
+
}
|
|
15916
|
+
async function defaultPromptForEditor(editors) {
|
|
15917
|
+
const choice = await ve({
|
|
15918
|
+
message: "Choose an editor",
|
|
15919
|
+
options: editors.map((e2) => ({ value: e2.cli, label: e2.name }))
|
|
15920
|
+
});
|
|
15921
|
+
if (pD(choice)) return null;
|
|
15922
|
+
return choice;
|
|
15923
|
+
}
|
|
15924
|
+
async function ensureWorkspaceFile(worktreePath, repoName) {
|
|
15925
|
+
const workspacePath = join7(worktreePath, `${repoName}.code-workspace`);
|
|
15926
|
+
try {
|
|
15927
|
+
await access4(workspacePath);
|
|
15928
|
+
return workspacePath;
|
|
15929
|
+
} catch {
|
|
15930
|
+
}
|
|
15931
|
+
const workspace = { folders: [{ path: "." }], settings: {} };
|
|
15932
|
+
await writeFile5(workspacePath, `${JSON.stringify(workspace, null, 2)}
|
|
15933
|
+
`, "utf8");
|
|
15934
|
+
return workspacePath;
|
|
15935
|
+
}
|
|
15936
|
+
|
|
15937
|
+
// src/init.ts
|
|
15938
|
+
import { mkdir as mkdir6, readFile as readFile4, writeFile as writeFile6 } from "node:fs/promises";
|
|
15939
|
+
import { homedir as homedir5 } from "node:os";
|
|
15940
|
+
import { dirname as dirname7, join as join8 } from "node:path";
|
|
15941
|
+
var START_MARKER = "# >>> gji init >>>";
|
|
15942
|
+
var END_MARKER = "# <<< gji init <<<";
|
|
15943
|
+
var SHELL_WRAPPED_COMMANDS = [
|
|
15944
|
+
{
|
|
15945
|
+
bypassOptions: ["--help"],
|
|
15946
|
+
commandName: "new",
|
|
15947
|
+
envVar: "GJI_NEW_OUTPUT_FILE",
|
|
15948
|
+
names: ["new"],
|
|
15949
|
+
tempPrefix: "gji-new"
|
|
15950
|
+
},
|
|
15951
|
+
{
|
|
15952
|
+
bypassOptions: ["--help"],
|
|
15953
|
+
commandName: "pr",
|
|
15954
|
+
envVar: "GJI_PR_OUTPUT_FILE",
|
|
15955
|
+
names: ["pr"],
|
|
15956
|
+
tempPrefix: "gji-pr"
|
|
15957
|
+
},
|
|
15958
|
+
{
|
|
15959
|
+
bypassOptions: ["--print"],
|
|
15960
|
+
commandName: "back",
|
|
15961
|
+
envVar: "GJI_BACK_OUTPUT_FILE",
|
|
15962
|
+
names: ["back"],
|
|
15963
|
+
tempPrefix: "gji-back"
|
|
15964
|
+
},
|
|
15965
|
+
{
|
|
15966
|
+
bypassOptions: ["--print"],
|
|
15967
|
+
commandName: "go",
|
|
15968
|
+
envVar: "GJI_GO_OUTPUT_FILE",
|
|
15969
|
+
names: ["go", "jump"],
|
|
15970
|
+
tempPrefix: "gji-go"
|
|
15971
|
+
},
|
|
15972
|
+
{
|
|
15973
|
+
bypassOptions: ["--print"],
|
|
15974
|
+
commandName: "root",
|
|
15975
|
+
envVar: "GJI_ROOT_OUTPUT_FILE",
|
|
15976
|
+
names: ["root"],
|
|
15977
|
+
tempPrefix: "gji-root"
|
|
15978
|
+
},
|
|
15979
|
+
{
|
|
15980
|
+
bypassOptions: ["--help"],
|
|
15981
|
+
commandName: "remove",
|
|
15982
|
+
envVar: "GJI_REMOVE_OUTPUT_FILE",
|
|
15983
|
+
names: ["remove", "rm"],
|
|
15984
|
+
tempPrefix: "gji-remove"
|
|
15985
|
+
},
|
|
15986
|
+
{
|
|
15987
|
+
bypassOptions: ["--print", "--json"],
|
|
15988
|
+
commandName: "warp",
|
|
15989
|
+
envVar: "GJI_WARP_OUTPUT_FILE",
|
|
15990
|
+
names: ["warp"],
|
|
15991
|
+
tempPrefix: "gji-warp"
|
|
15992
|
+
}
|
|
15993
|
+
];
|
|
15994
|
+
async function runInitCommand(options) {
|
|
15995
|
+
const shell = resolveSupportedShell(options.shell, process.env.SHELL);
|
|
15996
|
+
const home = options.home ?? homedir5();
|
|
15997
|
+
if (!shell) {
|
|
15998
|
+
options.stderr?.(
|
|
15999
|
+
"Unable to detect a supported shell. Specify one explicitly: bash, fish, or zsh.\n"
|
|
16000
|
+
);
|
|
16001
|
+
return 1;
|
|
16002
|
+
}
|
|
16003
|
+
const script = renderShellIntegration(shell);
|
|
16004
|
+
if (!options.write) {
|
|
16005
|
+
options.stdout(script);
|
|
16006
|
+
return 0;
|
|
16007
|
+
}
|
|
16008
|
+
const rcPath = resolveShellConfigPath(shell, home);
|
|
16009
|
+
await mkdir6(dirname7(rcPath), { recursive: true });
|
|
16010
|
+
const current = await readExistingConfig(rcPath);
|
|
16011
|
+
const next = upsertShellIntegration(current, script);
|
|
16012
|
+
await writeFile6(rcPath, next, "utf8");
|
|
16013
|
+
options.stdout(`${rcPath}
|
|
16014
|
+
`);
|
|
16015
|
+
const { config: globalConfig } = await loadGlobalConfig(home);
|
|
16016
|
+
const alreadyConfigured = "shellIntegration" in globalConfig || "installSaveTarget" in globalConfig;
|
|
16017
|
+
const hasCustomPrompt = options.promptForSetup !== void 0;
|
|
16018
|
+
const canPrompt = hasCustomPrompt || process.stdout.isTTY === true;
|
|
16019
|
+
if (!alreadyConfigured && canPrompt) {
|
|
16020
|
+
const prompt = options.promptForSetup ?? defaultPromptForSetup;
|
|
16021
|
+
const result = await prompt();
|
|
16022
|
+
if (result) {
|
|
16023
|
+
await updateGlobalConfigKey("installSaveTarget", result.installSaveTarget, home);
|
|
16024
|
+
await saveWizardConfig(result, options.cwd, home);
|
|
16025
|
+
}
|
|
16026
|
+
}
|
|
16027
|
+
await updateGlobalConfigKey("shellIntegration", true, home);
|
|
16028
|
+
return 0;
|
|
16029
|
+
}
|
|
16030
|
+
function renderShellIntegration(shell) {
|
|
16031
|
+
const commandBlocks = SHELL_WRAPPED_COMMANDS.map(
|
|
16032
|
+
(command) => shell === "fish" ? renderFishWrapper(command) : renderPosixWrapper(command)
|
|
16033
|
+
).join("\n\n");
|
|
16034
|
+
switch (shell) {
|
|
16035
|
+
case "fish":
|
|
16036
|
+
return `${START_MARKER}
|
|
16037
|
+
function gji --wraps gji --description 'gji shell integration'
|
|
16038
|
+
${indentBlock(commandBlocks, 4)}
|
|
16039
|
+
|
|
16040
|
+
command gji $argv
|
|
16041
|
+
end
|
|
16042
|
+
${END_MARKER}
|
|
16043
|
+
`;
|
|
16044
|
+
case "bash":
|
|
16045
|
+
case "zsh":
|
|
16046
|
+
return `${START_MARKER}
|
|
16047
|
+
gji() {
|
|
16048
|
+
${indentBlock(commandBlocks, 2)}
|
|
16049
|
+
|
|
16050
|
+
command gji "$@"
|
|
16051
|
+
}
|
|
16052
|
+
${END_MARKER}
|
|
16053
|
+
`;
|
|
16054
|
+
}
|
|
16055
|
+
}
|
|
16056
|
+
function upsertShellIntegration(existingConfig, script) {
|
|
16057
|
+
const trimmedScript = script.trimEnd();
|
|
16058
|
+
const blockPattern = new RegExp(
|
|
16059
|
+
`${escapeForRegExp(START_MARKER)}[\\s\\S]*?${escapeForRegExp(END_MARKER)}\\n?`,
|
|
16060
|
+
"m"
|
|
16061
|
+
);
|
|
16062
|
+
if (blockPattern.test(existingConfig)) {
|
|
16063
|
+
return ensureTrailingNewline(
|
|
16064
|
+
existingConfig.replace(blockPattern, `${trimmedScript}
|
|
16065
|
+
`)
|
|
16066
|
+
);
|
|
16067
|
+
}
|
|
16068
|
+
const prefix = existingConfig.trimEnd();
|
|
16069
|
+
if (prefix.length === 0) {
|
|
16070
|
+
return ensureTrailingNewline(trimmedScript);
|
|
16071
|
+
}
|
|
16072
|
+
return ensureTrailingNewline(`${prefix}
|
|
16073
|
+
|
|
16074
|
+
${trimmedScript}`);
|
|
16075
|
+
}
|
|
16076
|
+
async function saveWizardConfig(result, cwd, home) {
|
|
16077
|
+
const values = {};
|
|
16078
|
+
if (result.branchPrefix) values.branchPrefix = result.branchPrefix;
|
|
16079
|
+
if (result.worktreePath) values.worktreePath = result.worktreePath;
|
|
16080
|
+
const hooks = {};
|
|
16081
|
+
if (result.hooks?.afterCreate) hooks.afterCreate = result.hooks.afterCreate;
|
|
16082
|
+
if (result.hooks?.afterEnter) hooks.afterEnter = result.hooks.afterEnter;
|
|
16083
|
+
if (result.hooks?.beforeRemove) hooks.beforeRemove = result.hooks.beforeRemove;
|
|
16084
|
+
if (Object.keys(hooks).length > 0) values.hooks = hooks;
|
|
16085
|
+
if (Object.keys(values).length === 0) return;
|
|
16086
|
+
if (result.installSaveTarget === "local") {
|
|
16087
|
+
const loaded = await loadConfig(cwd);
|
|
16088
|
+
await saveLocalConfig(cwd, { ...loaded.config, ...values });
|
|
16089
|
+
} else {
|
|
16090
|
+
const { config: existing } = await loadGlobalConfig(home);
|
|
16091
|
+
await saveGlobalConfig({ ...existing, ...values }, home);
|
|
16092
|
+
}
|
|
16093
|
+
}
|
|
16094
|
+
function resolveShellConfigPath(shell, home) {
|
|
16095
|
+
switch (shell) {
|
|
16096
|
+
case "bash":
|
|
16097
|
+
return join8(home, ".bashrc");
|
|
16098
|
+
case "fish":
|
|
16099
|
+
return join8(home, ".config", "fish", "config.fish");
|
|
16100
|
+
case "zsh":
|
|
16101
|
+
return join8(home, ".zshrc");
|
|
16102
|
+
}
|
|
16103
|
+
}
|
|
16104
|
+
async function readExistingConfig(path9) {
|
|
16105
|
+
try {
|
|
16106
|
+
return await readFile4(path9, "utf8");
|
|
16107
|
+
} catch (error) {
|
|
16108
|
+
if (isMissingFileError2(error)) {
|
|
16109
|
+
return "";
|
|
16110
|
+
}
|
|
16111
|
+
throw error;
|
|
16112
|
+
}
|
|
16113
|
+
}
|
|
16114
|
+
function ensureTrailingNewline(value) {
|
|
16115
|
+
return value.endsWith("\n") ? value : `${value}
|
|
16116
|
+
`;
|
|
16117
|
+
}
|
|
16118
|
+
function escapeForRegExp(value) {
|
|
16119
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
16120
|
+
}
|
|
16121
|
+
function isMissingFileError2(error) {
|
|
16122
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
16123
|
+
}
|
|
16124
|
+
function renderFishWrapper(command) {
|
|
16125
|
+
const nameTests = command.names.map((name) => `test $argv[1] = ${name}`);
|
|
16126
|
+
const nameCondition = nameTests.length === 1 ? nameTests[0] : `begin; ${nameTests.join("; or ")}; end`;
|
|
16127
|
+
const bypassTests = command.bypassOptions.map((opt) => `test $argv[1] = ${opt}`);
|
|
16128
|
+
const bypassCondition = bypassTests.length === 1 ? bypassTests[0] : `begin; ${bypassTests.join("; or ")}; end`;
|
|
16129
|
+
return `if test (count $argv) -gt 0; and ${nameCondition}
|
|
16130
|
+
set -e argv[1]
|
|
16131
|
+
if test (count $argv) -gt 0; and ${bypassCondition}
|
|
16132
|
+
command gji ${command.commandName} $argv
|
|
16133
|
+
return $status
|
|
16134
|
+
end
|
|
16135
|
+
|
|
16136
|
+
set -l output_file (mktemp -t ${command.tempPrefix}.XXXXXX)
|
|
16137
|
+
or return 1
|
|
16138
|
+
env ${command.envVar}=$output_file command gji ${command.commandName} $argv
|
|
16139
|
+
or begin
|
|
16140
|
+
set -l status_code $status
|
|
16141
|
+
rm -f $output_file
|
|
16142
|
+
return $status_code
|
|
16143
|
+
end
|
|
16144
|
+
set -l target (cat $output_file)
|
|
16145
|
+
rm -f $output_file
|
|
16146
|
+
cd $target
|
|
16147
|
+
return $status
|
|
16148
|
+
end`;
|
|
16149
|
+
}
|
|
16150
|
+
function renderPosixWrapper(command) {
|
|
16151
|
+
const tests = command.names.map((name) => `[ "$1" = "${name}" ]`).join(" || ");
|
|
16152
|
+
const bypassTests = command.bypassOptions.map((opt) => `[ "\${1:-}" = "${opt}" ]`).join(" || ");
|
|
16153
|
+
return `if ${tests}; then
|
|
16154
|
+
shift
|
|
16155
|
+
if ${bypassTests}; then
|
|
16156
|
+
command gji ${command.commandName} "$@"
|
|
16157
|
+
return $?
|
|
16158
|
+
fi
|
|
16159
|
+
|
|
16160
|
+
local target
|
|
16161
|
+
local output_file
|
|
16162
|
+
output_file="$(mktemp -t ${command.tempPrefix}.XXXXXX)" || return 1
|
|
16163
|
+
${command.envVar}="$output_file" command gji ${command.commandName} "$@" || { local exit_code=$?; rm -f "$output_file"; return $exit_code; }
|
|
16164
|
+
target="$(cat "$output_file")"
|
|
16165
|
+
rm -f "$output_file"
|
|
16166
|
+
cd "$target" || return $?
|
|
16167
|
+
return 0
|
|
16168
|
+
fi`;
|
|
16169
|
+
}
|
|
16170
|
+
function indentBlock(value, spaces) {
|
|
16171
|
+
const prefix = " ".repeat(spaces);
|
|
16172
|
+
return value.split("\n").map((line) => line.length === 0 ? "" : `${prefix}${line}`).join("\n");
|
|
15715
16173
|
}
|
|
15716
|
-
function
|
|
15717
|
-
|
|
15718
|
-
|
|
16174
|
+
async function defaultPromptForSetup() {
|
|
16175
|
+
Ie("gji setup");
|
|
16176
|
+
const installSaveTarget = await ve({
|
|
16177
|
+
message: "Where should preferences be saved?",
|
|
16178
|
+
options: [
|
|
16179
|
+
{ value: "global", label: "~/.config/gji/config.json", hint: "personal \u2014 never committed" },
|
|
16180
|
+
{ value: "local", label: ".gji.json", hint: "repo \u2014 committed with the project" }
|
|
16181
|
+
]
|
|
16182
|
+
});
|
|
16183
|
+
if (pD(installSaveTarget)) {
|
|
16184
|
+
Se("Setup skipped.");
|
|
16185
|
+
return null;
|
|
15719
16186
|
}
|
|
15720
|
-
|
|
15721
|
-
|
|
16187
|
+
const branchPrefix = await he({
|
|
16188
|
+
message: "Default branch prefix?",
|
|
16189
|
+
placeholder: "e.g. feat/ or fix/ \u2014 leave blank to skip"
|
|
16190
|
+
});
|
|
16191
|
+
if (pD(branchPrefix)) {
|
|
16192
|
+
Se("Setup skipped.");
|
|
16193
|
+
return null;
|
|
15722
16194
|
}
|
|
15723
|
-
|
|
15724
|
-
|
|
15725
|
-
|
|
15726
|
-
|
|
15727
|
-
|
|
15728
|
-
|
|
15729
|
-
|
|
15730
|
-
if (!await pathExists(candidatePath)) {
|
|
15731
|
-
return candidatePath;
|
|
15732
|
-
}
|
|
15733
|
-
attempt += 1;
|
|
16195
|
+
const worktreePath = await he({
|
|
16196
|
+
message: "Worktree base path?",
|
|
16197
|
+
placeholder: "leave blank to use the default path"
|
|
16198
|
+
});
|
|
16199
|
+
if (pD(worktreePath)) {
|
|
16200
|
+
Se("Setup skipped.");
|
|
16201
|
+
return null;
|
|
15734
16202
|
}
|
|
15735
|
-
|
|
15736
|
-
|
|
15737
|
-
|
|
15738
|
-
defaultValue: placeholder,
|
|
15739
|
-
message: "Name the new branch",
|
|
15740
|
-
placeholder,
|
|
15741
|
-
validate: (value) => {
|
|
15742
|
-
const trimmed = value.trim();
|
|
15743
|
-
return validateBranchName(trimmed) ?? void 0;
|
|
15744
|
-
}
|
|
16203
|
+
const afterCreate = await he({
|
|
16204
|
+
message: "afterCreate hook \u2014 run after creating a worktree?",
|
|
16205
|
+
placeholder: "e.g. pnpm install \u2014 leave blank to skip"
|
|
15745
16206
|
});
|
|
15746
|
-
if (pD(
|
|
16207
|
+
if (pD(afterCreate)) {
|
|
16208
|
+
Se("Setup skipped.");
|
|
15747
16209
|
return null;
|
|
15748
16210
|
}
|
|
15749
|
-
|
|
15750
|
-
|
|
15751
|
-
|
|
15752
|
-
|
|
15753
|
-
|
|
16211
|
+
const afterEnter = await he({
|
|
16212
|
+
message: "afterEnter hook \u2014 run after entering a worktree?",
|
|
16213
|
+
placeholder: "e.g. nvm use \u2014 leave blank to skip"
|
|
16214
|
+
});
|
|
16215
|
+
if (pD(afterEnter)) {
|
|
16216
|
+
Se("Setup skipped.");
|
|
16217
|
+
return null;
|
|
16218
|
+
}
|
|
16219
|
+
const beforeRemove = await he({
|
|
16220
|
+
message: "beforeRemove hook \u2014 run before removing a worktree?",
|
|
16221
|
+
placeholder: "leave blank to skip"
|
|
16222
|
+
});
|
|
16223
|
+
if (pD(beforeRemove)) {
|
|
16224
|
+
Se("Setup skipped.");
|
|
16225
|
+
return null;
|
|
16226
|
+
}
|
|
16227
|
+
Se("Setup complete!");
|
|
16228
|
+
const hooks = {};
|
|
16229
|
+
if (afterCreate) hooks.afterCreate = afterCreate;
|
|
16230
|
+
if (afterEnter) hooks.afterEnter = afterEnter;
|
|
16231
|
+
if (beforeRemove) hooks.beforeRemove = beforeRemove;
|
|
16232
|
+
return {
|
|
16233
|
+
branchPrefix: branchPrefix || void 0,
|
|
16234
|
+
hooks: Object.keys(hooks).length > 0 ? hooks : void 0,
|
|
16235
|
+
installSaveTarget,
|
|
16236
|
+
worktreePath: worktreePath || void 0
|
|
16237
|
+
};
|
|
15754
16238
|
}
|
|
15755
|
-
|
|
15756
|
-
|
|
15757
|
-
|
|
15758
|
-
|
|
15759
|
-
|
|
15760
|
-
|
|
16239
|
+
|
|
16240
|
+
// src/paths.ts
|
|
16241
|
+
function comparePaths(left, right) {
|
|
16242
|
+
if (left < right) {
|
|
16243
|
+
return -1;
|
|
16244
|
+
}
|
|
16245
|
+
if (left > right) {
|
|
16246
|
+
return 1;
|
|
15761
16247
|
}
|
|
16248
|
+
return 0;
|
|
15762
16249
|
}
|
|
15763
|
-
|
|
15764
|
-
|
|
16250
|
+
|
|
16251
|
+
// src/ls.ts
|
|
16252
|
+
async function runLsCommand(options) {
|
|
16253
|
+
const worktrees = sortWorktrees(await listWorktrees(options.cwd));
|
|
16254
|
+
if (options.compact) {
|
|
16255
|
+
if (options.json) {
|
|
16256
|
+
options.stdout(`${JSON.stringify(worktrees, null, 2)}
|
|
16257
|
+
`);
|
|
16258
|
+
return 0;
|
|
16259
|
+
}
|
|
16260
|
+
options.stdout(`${formatWorktreeTable(worktrees)}
|
|
16261
|
+
`);
|
|
16262
|
+
return 0;
|
|
16263
|
+
}
|
|
16264
|
+
const infos = await readWorktreeInfos(worktrees);
|
|
16265
|
+
if (options.json) {
|
|
16266
|
+
options.stdout(`${JSON.stringify(infos, null, 2)}
|
|
16267
|
+
`);
|
|
16268
|
+
return 0;
|
|
16269
|
+
}
|
|
16270
|
+
options.stdout(`${formatDetailedWorktreeTable(infos)}
|
|
16271
|
+
`);
|
|
16272
|
+
return 0;
|
|
15765
16273
|
}
|
|
15766
|
-
function
|
|
15767
|
-
const
|
|
15768
|
-
|
|
16274
|
+
function formatDetailedWorktreeTable(worktrees) {
|
|
16275
|
+
const rows = worktrees.map((worktree) => ({
|
|
16276
|
+
branch: worktree.branch ?? "(detached)",
|
|
16277
|
+
isCurrent: worktree.isCurrent,
|
|
16278
|
+
lastCommit: formatLastCommit(worktree.lastCommitTimestamp),
|
|
16279
|
+
path: worktree.path,
|
|
16280
|
+
status: worktree.status,
|
|
16281
|
+
upstream: formatUpstreamState(worktree.upstream)
|
|
16282
|
+
}));
|
|
16283
|
+
const branchWidth = Math.max("BRANCH".length, ...rows.map((row) => row.branch.length));
|
|
16284
|
+
const statusWidth = Math.max("STATUS".length, ...rows.map((row) => row.status.length));
|
|
16285
|
+
const upstreamWidth = Math.max("UPSTREAM".length, ...rows.map((row) => row.upstream.length));
|
|
16286
|
+
const lastCommitWidth = Math.max("LAST".length, ...rows.map((row) => row.lastCommit.length));
|
|
16287
|
+
const lines = [
|
|
16288
|
+
" " + "BRANCH".padEnd(branchWidth, " ") + " " + "STATUS".padEnd(statusWidth, " ") + " " + "UPSTREAM".padEnd(upstreamWidth, " ") + " " + "LAST".padEnd(lastCommitWidth, " ") + " PATH"
|
|
16289
|
+
];
|
|
16290
|
+
for (const row of rows) {
|
|
16291
|
+
lines.push(
|
|
16292
|
+
`${row.isCurrent ? "*" : " "} ${row.branch.padEnd(branchWidth, " ")} ${row.status.padEnd(statusWidth, " ")} ${row.upstream.padEnd(upstreamWidth, " ")} ${row.lastCommit.padEnd(lastCommitWidth, " ")} ` + row.path
|
|
16293
|
+
);
|
|
16294
|
+
}
|
|
16295
|
+
return lines.join("\n");
|
|
15769
16296
|
}
|
|
15770
|
-
function
|
|
15771
|
-
|
|
16297
|
+
function formatWorktreeTable(worktrees) {
|
|
16298
|
+
const rows = worktrees.map((worktree) => ({
|
|
16299
|
+
branch: worktree.branch ?? "(detached)",
|
|
16300
|
+
isCurrent: worktree.isCurrent,
|
|
16301
|
+
path: worktree.path
|
|
16302
|
+
}));
|
|
16303
|
+
const branchWidth = Math.max(
|
|
16304
|
+
"BRANCH".length,
|
|
16305
|
+
...rows.map((row) => row.branch.length)
|
|
16306
|
+
);
|
|
16307
|
+
const lines = [" " + "BRANCH".padEnd(branchWidth, " ") + " PATH"];
|
|
16308
|
+
for (const row of rows) {
|
|
16309
|
+
lines.push(`${row.isCurrent ? "*" : " "} ${row.branch.padEnd(branchWidth, " ")} ${row.path}`);
|
|
16310
|
+
}
|
|
16311
|
+
return lines.join("\n");
|
|
15772
16312
|
}
|
|
15773
|
-
function
|
|
15774
|
-
return
|
|
16313
|
+
function sortWorktrees(worktrees) {
|
|
16314
|
+
return [...worktrees].sort((left, right) => {
|
|
16315
|
+
if (left.isCurrent && !right.isCurrent) return -1;
|
|
16316
|
+
if (!left.isCurrent && right.isCurrent) return 1;
|
|
16317
|
+
return comparePaths(left.path, right.path);
|
|
16318
|
+
});
|
|
15775
16319
|
}
|
|
15776
16320
|
|
|
15777
16321
|
// src/pr.ts
|
|
15778
|
-
import { mkdir as
|
|
15779
|
-
import { basename as
|
|
15780
|
-
import { execFile as
|
|
15781
|
-
import { promisify as
|
|
15782
|
-
var
|
|
16322
|
+
import { mkdir as mkdir7 } from "node:fs/promises";
|
|
16323
|
+
import { basename as basename6, dirname as dirname8 } from "node:path";
|
|
16324
|
+
import { execFile as execFile5 } from "node:child_process";
|
|
16325
|
+
import { promisify as promisify6 } from "node:util";
|
|
16326
|
+
var execFileAsync5 = promisify6(execFile5);
|
|
15783
16327
|
var PR_OUTPUT_FILE_ENV = "GJI_PR_OUTPUT_FILE";
|
|
15784
16328
|
function parsePrInput(input) {
|
|
15785
16329
|
if (/^\d+$/.test(input)) return input;
|
|
@@ -15860,10 +16404,10 @@ function createPrCommand(dependencies = {}) {
|
|
|
15860
16404
|
}
|
|
15861
16405
|
return 1;
|
|
15862
16406
|
}
|
|
15863
|
-
await
|
|
16407
|
+
await mkdir7(dirname8(worktreePath), { recursive: true });
|
|
15864
16408
|
const branchAlreadyExists = await localBranchExists2(repository.repoRoot, branchName);
|
|
15865
16409
|
const worktreeArgs = branchAlreadyExists ? ["worktree", "add", worktreePath, branchName] : ["worktree", "add", "-b", branchName, worktreePath, remoteRef];
|
|
15866
|
-
await
|
|
16410
|
+
await execFileAsync5("git", worktreeArgs, { cwd: repository.repoRoot });
|
|
15867
16411
|
const syncPatterns = Array.isArray(config.syncFiles) ? config.syncFiles.filter((p2) => typeof p2 === "string") : [];
|
|
15868
16412
|
for (const pattern of syncPatterns) {
|
|
15869
16413
|
try {
|
|
@@ -15878,7 +16422,7 @@ function createPrCommand(dependencies = {}) {
|
|
|
15878
16422
|
await runHook(
|
|
15879
16423
|
hooks.afterCreate,
|
|
15880
16424
|
worktreePath,
|
|
15881
|
-
{ branch: branchName, path: worktreePath, repo:
|
|
16425
|
+
{ branch: branchName, path: worktreePath, repo: basename6(repository.repoRoot) },
|
|
15882
16426
|
options.stderr
|
|
15883
16427
|
);
|
|
15884
16428
|
if (options.json) {
|
|
@@ -15893,7 +16437,7 @@ function createPrCommand(dependencies = {}) {
|
|
|
15893
16437
|
}
|
|
15894
16438
|
async function localBranchExists2(repoRoot, branchName) {
|
|
15895
16439
|
try {
|
|
15896
|
-
await
|
|
16440
|
+
await execFileAsync5(
|
|
15897
16441
|
"git",
|
|
15898
16442
|
["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`],
|
|
15899
16443
|
{ cwd: repoRoot }
|
|
@@ -15907,7 +16451,7 @@ var runPrCommand = createPrCommand();
|
|
|
15907
16451
|
async function fetchPullRequestRef(repoRoot, input, prNumber, remoteRef) {
|
|
15908
16452
|
for (const sourceRef of listPullRequestSourceRefs(input, prNumber)) {
|
|
15909
16453
|
try {
|
|
15910
|
-
await
|
|
16454
|
+
await execFileAsync5(
|
|
15911
16455
|
"git",
|
|
15912
16456
|
["fetch", "origin", `${sourceRef}:${remoteRef}`],
|
|
15913
16457
|
{ cwd: repoRoot }
|
|
@@ -15951,10 +16495,10 @@ async function writeOutput2(worktreePath, stdout) {
|
|
|
15951
16495
|
}
|
|
15952
16496
|
|
|
15953
16497
|
// src/remove.ts
|
|
15954
|
-
import { basename as
|
|
16498
|
+
import { basename as basename7 } from "node:path";
|
|
15955
16499
|
var REMOVE_OUTPUT_FILE_ENV = "GJI_REMOVE_OUTPUT_FILE";
|
|
15956
16500
|
function createRemoveCommand(dependencies = {}) {
|
|
15957
|
-
const promptForWorktree2 = dependencies.promptForWorktree ??
|
|
16501
|
+
const promptForWorktree2 = dependencies.promptForWorktree ?? defaultPromptForWorktree2;
|
|
15958
16502
|
const confirmRemoval = dependencies.confirmRemoval ?? defaultConfirmRemoval2;
|
|
15959
16503
|
const confirmForceRemoveWorktree = dependencies.confirmForceRemoveWorktree ?? defaultConfirmForceRemoveWorktree;
|
|
15960
16504
|
const confirmForceDeleteBranch = dependencies.confirmForceDeleteBranch ?? defaultConfirmForceDeleteBranch;
|
|
@@ -16016,7 +16560,7 @@ function createRemoveCommand(dependencies = {}) {
|
|
|
16016
16560
|
await runHook(
|
|
16017
16561
|
hooks.beforeRemove,
|
|
16018
16562
|
worktree.path,
|
|
16019
|
-
{ branch: worktree.branch ?? void 0, path: worktree.path, repo:
|
|
16563
|
+
{ branch: worktree.branch ?? void 0, path: worktree.path, repo: basename7(repository.repoRoot) },
|
|
16020
16564
|
options.stderr
|
|
16021
16565
|
);
|
|
16022
16566
|
try {
|
|
@@ -16066,7 +16610,7 @@ function createRemoveCommand(dependencies = {}) {
|
|
|
16066
16610
|
};
|
|
16067
16611
|
}
|
|
16068
16612
|
var runRemoveCommand = createRemoveCommand();
|
|
16069
|
-
async function
|
|
16613
|
+
async function defaultPromptForWorktree2(worktrees) {
|
|
16070
16614
|
const choice = await ve({
|
|
16071
16615
|
message: "Choose a worktree to finish",
|
|
16072
16616
|
options: worktrees.map((worktree) => ({
|
|
@@ -16355,6 +16899,7 @@ function readPackageMetadata() {
|
|
|
16355
16899
|
}
|
|
16356
16900
|
async function runCli(argv, options = {}) {
|
|
16357
16901
|
await maybeNotifyForUpdates(argv);
|
|
16902
|
+
maybeRegisterCurrentRepo(options.cwd ?? process.cwd());
|
|
16358
16903
|
const program2 = createProgram();
|
|
16359
16904
|
const cwd = options.cwd ?? process.cwd();
|
|
16360
16905
|
const stdout = options.stdout ?? (() => void 0);
|
|
@@ -16398,14 +16943,18 @@ function defaultNotifyForUpdates(pkg) {
|
|
|
16398
16943
|
const notifier = updateNotifier({ pkg });
|
|
16399
16944
|
notifier.notify();
|
|
16400
16945
|
}
|
|
16946
|
+
function maybeRegisterCurrentRepo(cwd) {
|
|
16947
|
+
detectRepository(cwd).then(({ repoRoot }) => registerRepo(repoRoot)).catch(() => void 0);
|
|
16948
|
+
}
|
|
16401
16949
|
function registerCommands(program2) {
|
|
16402
|
-
program2.command("new [branch]").description("create a new branch or detached linked worktree").option("-f, --force", "remove and recreate the worktree if the target path already exists").option("--detached", "create a detached worktree without a branch").option("--dry-run", "show what would be created without executing any git commands or writing files").option("--json", "emit JSON on success or error instead of human-readable output").action(notImplemented("new"));
|
|
16950
|
+
program2.command("new [branch]").description("create a new branch or detached linked worktree").option("-f, --force", "remove and recreate the worktree if the target path already exists").option("--detached", "create a detached worktree without a branch").option("--open", "open the new worktree in an editor after creation").option("--editor <cli>", "editor CLI to use with --open (code, cursor, zed, \u2026)").option("--dry-run", "show what would be created without executing any git commands or writing files").option("--json", "emit JSON on success or error instead of human-readable output").action(notImplemented("new"));
|
|
16403
16951
|
program2.command("init [shell]").description("print or install shell integration").option("--write", "write the integration to the shell config file").action(notImplemented("init"));
|
|
16404
16952
|
program2.command("completion [shell]").description("print shell completion definitions").action(notImplemented("completion"));
|
|
16405
16953
|
program2.command("pr <ref>").description("fetch a pull request by number, #number, or URL into a linked worktree").option("--dry-run", "show what would be created without executing any git commands or writing files").option("--json", "emit JSON on success or error instead of human-readable output").action(notImplemented("pr"));
|
|
16406
16954
|
program2.command("back [n]").description("navigate to the previously visited worktree, optionally N steps back").option("--print", "print the resolved worktree path explicitly").action(notImplemented("back"));
|
|
16407
16955
|
program2.command("history").description("show navigation history").option("--json", "print history as JSON").action(notImplemented("history"));
|
|
16408
|
-
program2.command("
|
|
16956
|
+
program2.command("open [branch]").description("open the worktree in an editor").option("--editor <cli>", "editor CLI to use (code, cursor, zed, windsurf, subl, \u2026)").option("--save", "save the chosen editor to global config").option("--workspace", "generate a .code-workspace file before opening (VS Code / Cursor / Windsurf)").action(notImplemented("open"));
|
|
16957
|
+
program2.command("go [branch]").alias("jump").description("print or select a worktree path").option("--print", "print the resolved worktree path explicitly").action(notImplemented("go"));
|
|
16409
16958
|
program2.command("root").description("print the main repository root path").option("--print", "print the resolved repository root path explicitly").action(notImplemented("root"));
|
|
16410
16959
|
program2.command("status").description("summarize repository and worktree health").option("--json", "print repository and worktree health as JSON").action(notImplemented("status"));
|
|
16411
16960
|
program2.command("sync").description("fetch and update one or all worktrees").option("--all", "sync every worktree in the repository").option("--json", "emit JSON on success or error instead of human-readable output").action(notImplemented("sync"));
|
|
@@ -16413,6 +16962,7 @@ function registerCommands(program2) {
|
|
|
16413
16962
|
program2.command("clean").description("interactively prune linked worktrees").option("-f, --force", "bypass prompts, force-remove dirty worktrees, and force-delete unmerged branches").option("--stale", "only target clean worktrees whose upstream is gone and branch is merged into the default branch").option("--dry-run", "show what would be deleted without removing anything").option("--json", "emit JSON on success or error instead of human-readable output").action(notImplemented("clean"));
|
|
16414
16963
|
program2.command("remove [branch]").alias("rm").description("remove a linked worktree and delete its branch when present").option("-f, --force", "bypass prompts, force-remove a dirty worktree, and force-delete an unmerged branch").option("--dry-run", "show what would be deleted without removing anything").option("--json", "emit JSON on success or error instead of human-readable output").action(notImplemented("remove"));
|
|
16415
16964
|
program2.command("trigger-hook <hook>").description("run a named hook (afterCreate, afterEnter, beforeRemove) in the current worktree").action(notImplemented("trigger-hook"));
|
|
16965
|
+
program2.command("warp [branch]").description("jump to any worktree across all known repos").option("-n, --new [branch]", "create a new worktree in a registered repo").option("--print", "print the resolved worktree path without changing directory").option("--json", "emit JSON on success or error instead of human-readable output").action(notImplemented("warp"));
|
|
16416
16966
|
const configCommand = program2.command("config").description("manage global config defaults").action(notImplemented("config"));
|
|
16417
16967
|
configCommand.command("get [key]").description("print the global config or a single key").action(notImplemented("config get"));
|
|
16418
16968
|
configCommand.command("set <key> <value>").description("set a global config value").action(notImplemented("config set"));
|
|
@@ -16420,7 +16970,7 @@ function registerCommands(program2) {
|
|
|
16420
16970
|
}
|
|
16421
16971
|
function attachCommandActions(program2, options) {
|
|
16422
16972
|
program2.commands.find((command) => command.name() === "new")?.action(async (branch, commandOptions) => {
|
|
16423
|
-
const exitCode = await runNewCommand({ ...options, branch, detached: commandOptions.detached, dryRun: commandOptions.dryRun, force: commandOptions.force, json: commandOptions.json });
|
|
16973
|
+
const exitCode = await runNewCommand({ ...options, branch, detached: commandOptions.detached, dryRun: commandOptions.dryRun, editor: commandOptions.editor, force: commandOptions.force, json: commandOptions.json, open: commandOptions.open });
|
|
16424
16974
|
if (exitCode !== 0) {
|
|
16425
16975
|
throw commanderExit(exitCode);
|
|
16426
16976
|
}
|
|
@@ -16481,6 +17031,20 @@ function attachCommandActions(program2, options) {
|
|
|
16481
17031
|
throw commanderExit(exitCode);
|
|
16482
17032
|
}
|
|
16483
17033
|
});
|
|
17034
|
+
program2.commands.find((command) => command.name() === "open")?.action(async (branch, commandOptions) => {
|
|
17035
|
+
const exitCode = await runOpenCommand({
|
|
17036
|
+
branch,
|
|
17037
|
+
cwd: options.cwd,
|
|
17038
|
+
editor: commandOptions.editor,
|
|
17039
|
+
save: commandOptions.save,
|
|
17040
|
+
stderr: options.stderr,
|
|
17041
|
+
stdout: options.stdout,
|
|
17042
|
+
workspace: commandOptions.workspace
|
|
17043
|
+
});
|
|
17044
|
+
if (exitCode !== 0) {
|
|
17045
|
+
throw commanderExit(exitCode);
|
|
17046
|
+
}
|
|
17047
|
+
});
|
|
16484
17048
|
program2.commands.find((command) => command.name() === "go")?.action(async (branch, commandOptions) => {
|
|
16485
17049
|
const exitCode = await runGoCommand({
|
|
16486
17050
|
branch,
|
|
@@ -16575,6 +17139,22 @@ function attachCommandActions(program2, options) {
|
|
|
16575
17139
|
throw commanderExit(exitCode);
|
|
16576
17140
|
}
|
|
16577
17141
|
});
|
|
17142
|
+
program2.commands.find((command) => command.name() === "warp")?.action(async (branch, commandOptions) => {
|
|
17143
|
+
const newFlag = commandOptions.new;
|
|
17144
|
+
const newWorktree = newFlag !== void 0 && newFlag !== false;
|
|
17145
|
+
const newBranch = typeof newFlag === "string" ? newFlag : void 0;
|
|
17146
|
+
const exitCode = await runWarpCommand({
|
|
17147
|
+
branch: newWorktree ? newBranch ?? branch : branch,
|
|
17148
|
+
cwd: options.cwd,
|
|
17149
|
+
json: commandOptions.json,
|
|
17150
|
+
newWorktree,
|
|
17151
|
+
stderr: options.stderr,
|
|
17152
|
+
stdout: options.stdout
|
|
17153
|
+
});
|
|
17154
|
+
if (exitCode !== 0) {
|
|
17155
|
+
throw commanderExit(exitCode);
|
|
17156
|
+
}
|
|
17157
|
+
});
|
|
16578
17158
|
const configCommand = program2.commands.find((command) => command.name() === "config");
|
|
16579
17159
|
configCommand?.action(async () => {
|
|
16580
17160
|
const exitCode = await runConfigCommand({
|
|
@@ -16657,7 +17237,7 @@ async function main() {
|
|
|
16657
17237
|
}
|
|
16658
17238
|
async function warnIfMissingShellIntegration() {
|
|
16659
17239
|
try {
|
|
16660
|
-
const { config } = await loadGlobalConfig(
|
|
17240
|
+
const { config } = await loadGlobalConfig(homedir6());
|
|
16661
17241
|
if (!config.shellIntegration) {
|
|
16662
17242
|
const shellBin = (process.env.SHELL ?? "").split("/").at(-1);
|
|
16663
17243
|
const shellArg = shellBin && ["bash", "zsh", "fish"].includes(shellBin) ? ` ${shellBin}` : "";
|