@solaqua/gji 0.6.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -1
- package/dist/back.d.ts +1 -1
- package/dist/back.js +23 -17
- package/dist/clean.d.ts +1 -1
- package/dist/clean.js +44 -35
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +264 -164
- package/dist/completion.js +3 -3
- package/dist/config-command.js +5 -5
- package/dist/config.js +41 -35
- package/dist/conflict.d.ts +1 -1
- package/dist/conflict.js +14 -6
- package/dist/editor.js +29 -9
- package/dist/file-sync.d.ts +1 -0
- package/dist/file-sync.js +15 -11
- package/dist/git.d.ts +1 -1
- package/dist/git.js +21 -19
- package/dist/gji-bundle.mjs +1624 -819
- package/dist/go.d.ts +2 -2
- package/dist/go.js +39 -26
- package/dist/headless.js +1 -1
- package/dist/history-command.js +3 -3
- package/dist/history.js +12 -12
- package/dist/hooks.js +16 -16
- package/dist/index.js +13 -9
- package/dist/init.d.ts +2 -2
- package/dist/init.js +106 -94
- package/dist/install-prompt.d.ts +3 -3
- package/dist/install-prompt.js +46 -28
- package/dist/ls.d.ts +2 -2
- package/dist/ls.js +29 -29
- package/dist/new.d.ts +2 -2
- package/dist/new.js +96 -81
- package/dist/open.d.ts +2 -2
- package/dist/open.js +24 -21
- package/dist/package-manager.js +96 -45
- package/dist/pr.d.ts +2 -2
- package/dist/pr.js +47 -34
- package/dist/remove.d.ts +1 -1
- package/dist/remove.js +39 -27
- package/dist/repo-registry.js +14 -14
- package/dist/repo.js +29 -28
- package/dist/root.js +3 -3
- package/dist/shell-completion.d.ts +1 -1
- package/dist/shell-completion.js +65 -37
- package/dist/shell-handoff.js +2 -2
- package/dist/shell.d.ts +1 -1
- package/dist/shell.js +4 -4
- package/dist/status.d.ts +5 -5
- package/dist/status.js +23 -23
- package/dist/sync-files-command.d.ts +10 -0
- package/dist/sync-files-command.js +137 -0
- package/dist/sync.js +23 -15
- package/dist/trigger-hook.js +9 -5
- package/dist/warp.js +37 -33
- package/dist/worktree-info.d.ts +9 -9
- package/dist/worktree-info.js +31 -29
- package/dist/worktree-management.d.ts +1 -1
- package/dist/worktree-management.js +26 -11
- package/dist/worktree-prompts.js +5 -5
- package/man/man1/gji-back.1 +1 -1
- package/man/man1/gji-clean.1 +1 -1
- package/man/man1/gji-completion.1 +1 -1
- package/man/man1/gji-config.1 +1 -1
- package/man/man1/gji-go.1 +1 -1
- package/man/man1/gji-history.1 +1 -1
- package/man/man1/gji-init.1 +1 -1
- package/man/man1/gji-ls.1 +1 -1
- package/man/man1/gji-new.1 +1 -1
- package/man/man1/gji-open.1 +1 -1
- package/man/man1/gji-pr.1 +1 -1
- package/man/man1/gji-remove.1 +1 -1
- package/man/man1/gji-root.1 +1 -1
- package/man/man1/gji-status.1 +1 -1
- package/man/man1/gji-sync-files.1 +23 -0
- package/man/man1/gji-sync.1 +1 -1
- package/man/man1/gji-trigger-hook.1 +1 -1
- package/man/man1/gji-warp.1 +1 -1
- package/man/man1/gji.1 +5 -1
- package/package.json +8 -2
package/dist/gji-bundle.mjs
CHANGED
|
@@ -5386,7 +5386,7 @@ var require_minimist = __commonJS({
|
|
|
5386
5386
|
var require_rc = __commonJS({
|
|
5387
5387
|
"node_modules/.pnpm/rc@1.2.8/node_modules/rc/index.js"(exports, module) {
|
|
5388
5388
|
var cc = require_utils();
|
|
5389
|
-
var
|
|
5389
|
+
var join10 = __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
|
+
join10(etc, name, "config"),
|
|
5415
|
+
join10(etc, name + "rc")
|
|
5416
5416
|
].forEach(addConfigFile);
|
|
5417
5417
|
if (home)
|
|
5418
5418
|
[
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5419
|
+
join10(home, ".config", name, "config"),
|
|
5420
|
+
join10(home, ".config", name),
|
|
5421
|
+
join10(home, "." + name, "config"),
|
|
5422
|
+
join10(home, "." + name + "rc")
|
|
5423
5423
|
].forEach(addConfigFile);
|
|
5424
5424
|
addConfigFile(cc.find("." + name + "rc"));
|
|
5425
5425
|
if (env4.config) addConfigFile(env4.config);
|
|
@@ -9422,225 +9422,7 @@ var require_picocolors = __commonJS({
|
|
|
9422
9422
|
});
|
|
9423
9423
|
|
|
9424
9424
|
// src/index.ts
|
|
9425
|
-
import { homedir as
|
|
9426
|
-
|
|
9427
|
-
// src/config.ts
|
|
9428
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
9429
|
-
import { homedir } from "node:os";
|
|
9430
|
-
import { dirname, join, resolve } from "node:path";
|
|
9431
|
-
var CONFIG_FILE_NAME = ".gji.json";
|
|
9432
|
-
var GLOBAL_CONFIG_DIRECTORY = ".config/gji";
|
|
9433
|
-
var GLOBAL_CONFIG_NAME = "config.json";
|
|
9434
|
-
var KNOWN_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
9435
|
-
"branchPrefix",
|
|
9436
|
-
"editor",
|
|
9437
|
-
"hooks",
|
|
9438
|
-
"installSaveTarget",
|
|
9439
|
-
"shellIntegration",
|
|
9440
|
-
"skipInstallPrompt",
|
|
9441
|
-
"syncDefaultBranch",
|
|
9442
|
-
"syncFiles",
|
|
9443
|
-
"syncRemote",
|
|
9444
|
-
"worktreePath"
|
|
9445
|
-
]);
|
|
9446
|
-
var KNOWN_GLOBAL_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
9447
|
-
...KNOWN_CONFIG_KEYS,
|
|
9448
|
-
"repos"
|
|
9449
|
-
]);
|
|
9450
|
-
var DEFAULT_CONFIG = Object.freeze({});
|
|
9451
|
-
async function loadConfig(root) {
|
|
9452
|
-
const path9 = join(root, CONFIG_FILE_NAME);
|
|
9453
|
-
return loadConfigFile(path9);
|
|
9454
|
-
}
|
|
9455
|
-
async function loadEffectiveConfig(root, home = homedir(), onWarning) {
|
|
9456
|
-
const [globalConfig, localConfig] = await Promise.all([
|
|
9457
|
-
loadGlobalConfig(home),
|
|
9458
|
-
loadConfig(root)
|
|
9459
|
-
]);
|
|
9460
|
-
const repos = globalConfig.config.repos;
|
|
9461
|
-
const perRepoConfig = isPlainObject(repos) ? findPerRepoConfig(repos, root, home) : {};
|
|
9462
|
-
const globalBase = { ...globalConfig.config };
|
|
9463
|
-
delete globalBase.repos;
|
|
9464
|
-
if (onWarning) {
|
|
9465
|
-
if (globalConfig.exists) {
|
|
9466
|
-
warnUnknownKeys(globalBase, globalConfig.path, KNOWN_GLOBAL_CONFIG_KEYS, onWarning);
|
|
9467
|
-
if (Object.keys(perRepoConfig).length > 0) {
|
|
9468
|
-
warnUnknownKeys(perRepoConfig, globalConfig.path, KNOWN_CONFIG_KEYS, onWarning);
|
|
9469
|
-
}
|
|
9470
|
-
}
|
|
9471
|
-
if (localConfig.exists) {
|
|
9472
|
-
warnUnknownKeys(localConfig.config, localConfig.path, KNOWN_CONFIG_KEYS, onWarning);
|
|
9473
|
-
}
|
|
9474
|
-
}
|
|
9475
|
-
const merged = mergeConfig(globalBase, perRepoConfig, localConfig.config);
|
|
9476
|
-
const worktreePathValue = merged.worktreePath;
|
|
9477
|
-
if (onWarning && typeof worktreePathValue === "string" && worktreePathValue.length > 0 && !worktreePathValue.startsWith("/") && !worktreePathValue.startsWith("~")) {
|
|
9478
|
-
onWarning(
|
|
9479
|
-
`gji: "worktreePath" must be an absolute path or start with ~, got "${worktreePathValue}" \u2014 using default
|
|
9480
|
-
`
|
|
9481
|
-
);
|
|
9482
|
-
}
|
|
9483
|
-
const globalHooks = isPlainObject(globalBase.hooks) ? globalBase.hooks : {};
|
|
9484
|
-
const perRepoHooks = isPlainObject(perRepoConfig.hooks) ? perRepoConfig.hooks : {};
|
|
9485
|
-
const localHooks = isPlainObject(localConfig.config.hooks) ? localConfig.config.hooks : {};
|
|
9486
|
-
if (Object.keys(globalHooks).length > 0 || Object.keys(perRepoHooks).length > 0 || Object.keys(localHooks).length > 0) {
|
|
9487
|
-
merged.hooks = { ...globalHooks, ...perRepoHooks, ...localHooks };
|
|
9488
|
-
}
|
|
9489
|
-
return merged;
|
|
9490
|
-
}
|
|
9491
|
-
async function loadGlobalConfig(home = homedir()) {
|
|
9492
|
-
return loadConfigFile(GLOBAL_CONFIG_FILE_PATH(home));
|
|
9493
|
-
}
|
|
9494
|
-
async function saveLocalConfig(root, config) {
|
|
9495
|
-
const path9 = join(root, CONFIG_FILE_NAME);
|
|
9496
|
-
await writeFile(path9, `${JSON.stringify(config, null, 2)}
|
|
9497
|
-
`, "utf8");
|
|
9498
|
-
return path9;
|
|
9499
|
-
}
|
|
9500
|
-
async function updateLocalConfigKey(root, key, value) {
|
|
9501
|
-
const loaded = await loadConfig(root);
|
|
9502
|
-
const nextConfig = {
|
|
9503
|
-
...loaded.config,
|
|
9504
|
-
[key]: value
|
|
9505
|
-
};
|
|
9506
|
-
await saveLocalConfig(root, nextConfig);
|
|
9507
|
-
return nextConfig;
|
|
9508
|
-
}
|
|
9509
|
-
async function saveGlobalConfig(config, home = homedir()) {
|
|
9510
|
-
const path9 = GLOBAL_CONFIG_FILE_PATH(home);
|
|
9511
|
-
await mkdir(dirname(path9), { recursive: true });
|
|
9512
|
-
await writeFile(path9, `${JSON.stringify(config, null, 2)}
|
|
9513
|
-
`, "utf8");
|
|
9514
|
-
return path9;
|
|
9515
|
-
}
|
|
9516
|
-
async function unsetGlobalConfigKey(key, home = homedir()) {
|
|
9517
|
-
const loaded = await loadGlobalConfig(home);
|
|
9518
|
-
const nextConfig = { ...loaded.config };
|
|
9519
|
-
delete nextConfig[key];
|
|
9520
|
-
await saveGlobalConfig(nextConfig, home);
|
|
9521
|
-
return nextConfig;
|
|
9522
|
-
}
|
|
9523
|
-
async function updateGlobalConfigKey(key, value, home = homedir()) {
|
|
9524
|
-
const loaded = await loadGlobalConfig(home);
|
|
9525
|
-
const nextConfig = {
|
|
9526
|
-
...loaded.config,
|
|
9527
|
-
[key]: value
|
|
9528
|
-
};
|
|
9529
|
-
await saveGlobalConfig(nextConfig, home);
|
|
9530
|
-
return nextConfig;
|
|
9531
|
-
}
|
|
9532
|
-
async function updateGlobalRepoConfigKey(repoRoot, key, value, home = homedir()) {
|
|
9533
|
-
const loaded = await loadGlobalConfig(home);
|
|
9534
|
-
const repos = isPlainObject(loaded.config.repos) ? { ...loaded.config.repos } : {};
|
|
9535
|
-
const existing = isPlainObject(repos[repoRoot]) ? repos[repoRoot] : {};
|
|
9536
|
-
repos[repoRoot] = { ...existing, [key]: value };
|
|
9537
|
-
const nextConfig = { ...loaded.config, repos };
|
|
9538
|
-
await saveGlobalConfig(nextConfig, home);
|
|
9539
|
-
return nextConfig;
|
|
9540
|
-
}
|
|
9541
|
-
function GLOBAL_CONFIG_FILE_PATH(home = homedir()) {
|
|
9542
|
-
const configDir = process.env.GJI_CONFIG_DIR;
|
|
9543
|
-
if (configDir) {
|
|
9544
|
-
return join(resolve(configDir), GLOBAL_CONFIG_NAME);
|
|
9545
|
-
}
|
|
9546
|
-
return join(home, GLOBAL_CONFIG_DIRECTORY, GLOBAL_CONFIG_NAME);
|
|
9547
|
-
}
|
|
9548
|
-
function parseConfigValue(value) {
|
|
9549
|
-
try {
|
|
9550
|
-
return JSON.parse(value);
|
|
9551
|
-
} catch {
|
|
9552
|
-
return value;
|
|
9553
|
-
}
|
|
9554
|
-
}
|
|
9555
|
-
function resolveConfigString(config, key) {
|
|
9556
|
-
const value = config[key];
|
|
9557
|
-
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
9558
|
-
}
|
|
9559
|
-
async function loadConfigFile(path9) {
|
|
9560
|
-
try {
|
|
9561
|
-
const rawConfig = await readFile(path9, "utf8");
|
|
9562
|
-
const parsedConfig = JSON.parse(rawConfig);
|
|
9563
|
-
return {
|
|
9564
|
-
config: mergeConfig(parsedConfig),
|
|
9565
|
-
exists: true,
|
|
9566
|
-
path: path9
|
|
9567
|
-
};
|
|
9568
|
-
} catch (error) {
|
|
9569
|
-
if (isMissingFileError(error)) {
|
|
9570
|
-
return {
|
|
9571
|
-
config: DEFAULT_CONFIG,
|
|
9572
|
-
exists: false,
|
|
9573
|
-
path: path9
|
|
9574
|
-
};
|
|
9575
|
-
}
|
|
9576
|
-
throw error;
|
|
9577
|
-
}
|
|
9578
|
-
}
|
|
9579
|
-
function mergeConfig(...values) {
|
|
9580
|
-
return values.reduce(
|
|
9581
|
-
(config, value) => ({
|
|
9582
|
-
...config,
|
|
9583
|
-
...value
|
|
9584
|
-
}),
|
|
9585
|
-
{ ...DEFAULT_CONFIG }
|
|
9586
|
-
);
|
|
9587
|
-
}
|
|
9588
|
-
function findPerRepoConfig(repos, repoRoot, home) {
|
|
9589
|
-
for (const [key, value] of Object.entries(repos)) {
|
|
9590
|
-
const expandedKey = expandTilde(key, home);
|
|
9591
|
-
if (expandedKey === repoRoot && isPlainObject(value)) {
|
|
9592
|
-
return value;
|
|
9593
|
-
}
|
|
9594
|
-
}
|
|
9595
|
-
return {};
|
|
9596
|
-
}
|
|
9597
|
-
function expandTilde(value, home) {
|
|
9598
|
-
if (value === "~") return home;
|
|
9599
|
-
if (value.startsWith("~/")) return join(home, value.slice(2));
|
|
9600
|
-
return value;
|
|
9601
|
-
}
|
|
9602
|
-
function isPlainObject(value) {
|
|
9603
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9604
|
-
}
|
|
9605
|
-
function isMissingFileError(error) {
|
|
9606
|
-
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
9607
|
-
}
|
|
9608
|
-
function warnUnknownKeys(config, filePath, knownKeys, onWarning) {
|
|
9609
|
-
for (const key of Object.keys(config)) {
|
|
9610
|
-
if (!knownKeys.has(key)) {
|
|
9611
|
-
const suggestion = closestKey(key, knownKeys);
|
|
9612
|
-
const hint = suggestion ? ` (did you mean "${suggestion}"?)` : "";
|
|
9613
|
-
onWarning(`gji: unknown config key "${key}" in ${filePath}${hint}
|
|
9614
|
-
`);
|
|
9615
|
-
}
|
|
9616
|
-
}
|
|
9617
|
-
}
|
|
9618
|
-
function closestKey(unknown, knownKeys) {
|
|
9619
|
-
let best = null;
|
|
9620
|
-
let bestDist = Infinity;
|
|
9621
|
-
for (const key of knownKeys) {
|
|
9622
|
-
const dist = levenshtein(unknown, key);
|
|
9623
|
-
if (dist < bestDist) {
|
|
9624
|
-
bestDist = dist;
|
|
9625
|
-
best = key;
|
|
9626
|
-
}
|
|
9627
|
-
}
|
|
9628
|
-
return bestDist <= Math.max(2, Math.floor(unknown.length / 2)) ? best : null;
|
|
9629
|
-
}
|
|
9630
|
-
function levenshtein(a, b3) {
|
|
9631
|
-
const m2 = a.length;
|
|
9632
|
-
const n = b3.length;
|
|
9633
|
-
const dp = Array.from(
|
|
9634
|
-
{ length: m2 + 1 },
|
|
9635
|
-
(_3, i) => Array.from({ length: n + 1 }, (_4, j2) => i === 0 ? j2 : j2 === 0 ? i : 0)
|
|
9636
|
-
);
|
|
9637
|
-
for (let i = 1; i <= m2; i++) {
|
|
9638
|
-
for (let j2 = 1; j2 <= n; j2++) {
|
|
9639
|
-
dp[i][j2] = a[i - 1] === b3[j2 - 1] ? dp[i - 1][j2 - 1] : 1 + Math.min(dp[i - 1][j2], dp[i][j2 - 1], dp[i - 1][j2 - 1]);
|
|
9640
|
-
}
|
|
9641
|
-
}
|
|
9642
|
-
return dp[m2][n];
|
|
9643
|
-
}
|
|
9425
|
+
import { homedir as homedir7 } from "node:os";
|
|
9644
9426
|
|
|
9645
9427
|
// src/cli.ts
|
|
9646
9428
|
import { createRequire } from "node:module";
|
|
@@ -13032,20 +12814,293 @@ function updateNotifier(options) {
|
|
|
13032
12814
|
import { access } from "node:fs/promises";
|
|
13033
12815
|
import { basename as basename2 } from "node:path";
|
|
13034
12816
|
|
|
13035
|
-
// src/
|
|
13036
|
-
import {
|
|
13037
|
-
|
|
13038
|
-
|
|
13039
|
-
|
|
13040
|
-
|
|
13041
|
-
|
|
13042
|
-
|
|
13043
|
-
|
|
12817
|
+
// src/config.ts
|
|
12818
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
12819
|
+
import { homedir } from "node:os";
|
|
12820
|
+
import { dirname, join, resolve } from "node:path";
|
|
12821
|
+
var CONFIG_FILE_NAME = ".gji.json";
|
|
12822
|
+
var GLOBAL_CONFIG_DIRECTORY = ".config/gji";
|
|
12823
|
+
var GLOBAL_CONFIG_NAME = "config.json";
|
|
12824
|
+
var KNOWN_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
12825
|
+
"branchPrefix",
|
|
12826
|
+
"editor",
|
|
12827
|
+
"hooks",
|
|
12828
|
+
"installSaveTarget",
|
|
12829
|
+
"shellIntegration",
|
|
12830
|
+
"skipInstallPrompt",
|
|
12831
|
+
"syncDefaultBranch",
|
|
12832
|
+
"syncFiles",
|
|
12833
|
+
"syncRemote",
|
|
12834
|
+
"worktreePath"
|
|
12835
|
+
]);
|
|
12836
|
+
var KNOWN_GLOBAL_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
12837
|
+
...KNOWN_CONFIG_KEYS,
|
|
12838
|
+
"repos"
|
|
12839
|
+
]);
|
|
12840
|
+
var DEFAULT_CONFIG = Object.freeze({});
|
|
12841
|
+
async function loadConfig(root) {
|
|
12842
|
+
const path9 = join(root, CONFIG_FILE_NAME);
|
|
12843
|
+
return loadConfigFile(path9);
|
|
13044
12844
|
}
|
|
13045
|
-
async function
|
|
13046
|
-
const [
|
|
13047
|
-
|
|
13048
|
-
|
|
12845
|
+
async function loadEffectiveConfig(root, home = homedir(), onWarning) {
|
|
12846
|
+
const [globalConfig, localConfig] = await Promise.all([
|
|
12847
|
+
loadGlobalConfig(home),
|
|
12848
|
+
loadConfig(root)
|
|
12849
|
+
]);
|
|
12850
|
+
const repos = globalConfig.config.repos;
|
|
12851
|
+
const perRepoConfig = isPlainObject(repos) ? findPerRepoConfig(repos, root, home) : {};
|
|
12852
|
+
const globalBase = { ...globalConfig.config };
|
|
12853
|
+
delete globalBase.repos;
|
|
12854
|
+
if (onWarning) {
|
|
12855
|
+
if (globalConfig.exists) {
|
|
12856
|
+
warnUnknownKeys(
|
|
12857
|
+
globalBase,
|
|
12858
|
+
globalConfig.path,
|
|
12859
|
+
KNOWN_GLOBAL_CONFIG_KEYS,
|
|
12860
|
+
onWarning
|
|
12861
|
+
);
|
|
12862
|
+
if (Object.keys(perRepoConfig).length > 0) {
|
|
12863
|
+
warnUnknownKeys(
|
|
12864
|
+
perRepoConfig,
|
|
12865
|
+
globalConfig.path,
|
|
12866
|
+
KNOWN_CONFIG_KEYS,
|
|
12867
|
+
onWarning
|
|
12868
|
+
);
|
|
12869
|
+
}
|
|
12870
|
+
}
|
|
12871
|
+
if (localConfig.exists) {
|
|
12872
|
+
warnUnknownKeys(
|
|
12873
|
+
localConfig.config,
|
|
12874
|
+
localConfig.path,
|
|
12875
|
+
KNOWN_CONFIG_KEYS,
|
|
12876
|
+
onWarning
|
|
12877
|
+
);
|
|
12878
|
+
}
|
|
12879
|
+
}
|
|
12880
|
+
const merged = mergeConfig(globalBase, perRepoConfig, localConfig.config);
|
|
12881
|
+
const worktreePathValue = merged.worktreePath;
|
|
12882
|
+
if (onWarning && typeof worktreePathValue === "string" && worktreePathValue.length > 0 && !worktreePathValue.startsWith("/") && !worktreePathValue.startsWith("~")) {
|
|
12883
|
+
onWarning(
|
|
12884
|
+
`gji: "worktreePath" must be an absolute path or start with ~, got "${worktreePathValue}" \u2014 using default
|
|
12885
|
+
`
|
|
12886
|
+
);
|
|
12887
|
+
}
|
|
12888
|
+
const globalHooks = isPlainObject(globalBase.hooks) ? globalBase.hooks : {};
|
|
12889
|
+
const perRepoHooks = isPlainObject(perRepoConfig.hooks) ? perRepoConfig.hooks : {};
|
|
12890
|
+
const localHooks = isPlainObject(localConfig.config.hooks) ? localConfig.config.hooks : {};
|
|
12891
|
+
if (Object.keys(globalHooks).length > 0 || Object.keys(perRepoHooks).length > 0 || Object.keys(localHooks).length > 0) {
|
|
12892
|
+
merged.hooks = { ...globalHooks, ...perRepoHooks, ...localHooks };
|
|
12893
|
+
}
|
|
12894
|
+
return merged;
|
|
12895
|
+
}
|
|
12896
|
+
async function loadGlobalConfig(home = homedir()) {
|
|
12897
|
+
return loadConfigFile(GLOBAL_CONFIG_FILE_PATH(home));
|
|
12898
|
+
}
|
|
12899
|
+
async function saveLocalConfig(root, config) {
|
|
12900
|
+
const path9 = join(root, CONFIG_FILE_NAME);
|
|
12901
|
+
await writeFile(path9, `${JSON.stringify(config, null, 2)}
|
|
12902
|
+
`, "utf8");
|
|
12903
|
+
return path9;
|
|
12904
|
+
}
|
|
12905
|
+
async function updateLocalConfigKey(root, key, value) {
|
|
12906
|
+
const loaded = await loadConfig(root);
|
|
12907
|
+
const nextConfig = {
|
|
12908
|
+
...loaded.config,
|
|
12909
|
+
[key]: value
|
|
12910
|
+
};
|
|
12911
|
+
await saveLocalConfig(root, nextConfig);
|
|
12912
|
+
return nextConfig;
|
|
12913
|
+
}
|
|
12914
|
+
async function saveGlobalConfig(config, home = homedir()) {
|
|
12915
|
+
const path9 = GLOBAL_CONFIG_FILE_PATH(home);
|
|
12916
|
+
await mkdir(dirname(path9), { recursive: true });
|
|
12917
|
+
await writeFile(path9, `${JSON.stringify(config, null, 2)}
|
|
12918
|
+
`, "utf8");
|
|
12919
|
+
return path9;
|
|
12920
|
+
}
|
|
12921
|
+
async function unsetGlobalConfigKey(key, home = homedir()) {
|
|
12922
|
+
const loaded = await loadGlobalConfig(home);
|
|
12923
|
+
const nextConfig = { ...loaded.config };
|
|
12924
|
+
delete nextConfig[key];
|
|
12925
|
+
await saveGlobalConfig(nextConfig, home);
|
|
12926
|
+
return nextConfig;
|
|
12927
|
+
}
|
|
12928
|
+
async function updateGlobalConfigKey(key, value, home = homedir()) {
|
|
12929
|
+
const loaded = await loadGlobalConfig(home);
|
|
12930
|
+
const nextConfig = {
|
|
12931
|
+
...loaded.config,
|
|
12932
|
+
[key]: value
|
|
12933
|
+
};
|
|
12934
|
+
await saveGlobalConfig(nextConfig, home);
|
|
12935
|
+
return nextConfig;
|
|
12936
|
+
}
|
|
12937
|
+
async function updateGlobalRepoConfigKey(repoRoot, key, value, home = homedir()) {
|
|
12938
|
+
const loaded = await loadGlobalConfig(home);
|
|
12939
|
+
const repos = isPlainObject(loaded.config.repos) ? { ...loaded.config.repos } : {};
|
|
12940
|
+
const existing = isPlainObject(repos[repoRoot]) ? repos[repoRoot] : {};
|
|
12941
|
+
repos[repoRoot] = { ...existing, [key]: value };
|
|
12942
|
+
const nextConfig = { ...loaded.config, repos };
|
|
12943
|
+
await saveGlobalConfig(nextConfig, home);
|
|
12944
|
+
return nextConfig;
|
|
12945
|
+
}
|
|
12946
|
+
function GLOBAL_CONFIG_FILE_PATH(home = homedir()) {
|
|
12947
|
+
const configDir = process.env.GJI_CONFIG_DIR;
|
|
12948
|
+
if (configDir) {
|
|
12949
|
+
return join(resolve(configDir), GLOBAL_CONFIG_NAME);
|
|
12950
|
+
}
|
|
12951
|
+
return join(home, GLOBAL_CONFIG_DIRECTORY, GLOBAL_CONFIG_NAME);
|
|
12952
|
+
}
|
|
12953
|
+
function parseConfigValue(value) {
|
|
12954
|
+
try {
|
|
12955
|
+
return JSON.parse(value);
|
|
12956
|
+
} catch {
|
|
12957
|
+
return value;
|
|
12958
|
+
}
|
|
12959
|
+
}
|
|
12960
|
+
function resolveConfigString(config, key) {
|
|
12961
|
+
const value = config[key];
|
|
12962
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
12963
|
+
}
|
|
12964
|
+
async function loadConfigFile(path9) {
|
|
12965
|
+
try {
|
|
12966
|
+
const rawConfig = await readFile(path9, "utf8");
|
|
12967
|
+
const parsedConfig = JSON.parse(rawConfig);
|
|
12968
|
+
return {
|
|
12969
|
+
config: mergeConfig(parsedConfig),
|
|
12970
|
+
exists: true,
|
|
12971
|
+
path: path9
|
|
12972
|
+
};
|
|
12973
|
+
} catch (error) {
|
|
12974
|
+
if (isMissingFileError(error)) {
|
|
12975
|
+
return {
|
|
12976
|
+
config: DEFAULT_CONFIG,
|
|
12977
|
+
exists: false,
|
|
12978
|
+
path: path9
|
|
12979
|
+
};
|
|
12980
|
+
}
|
|
12981
|
+
throw error;
|
|
12982
|
+
}
|
|
12983
|
+
}
|
|
12984
|
+
function mergeConfig(...values) {
|
|
12985
|
+
return values.reduce(
|
|
12986
|
+
(config, value) => ({
|
|
12987
|
+
...config,
|
|
12988
|
+
...value
|
|
12989
|
+
}),
|
|
12990
|
+
{ ...DEFAULT_CONFIG }
|
|
12991
|
+
);
|
|
12992
|
+
}
|
|
12993
|
+
function findPerRepoConfig(repos, repoRoot, home) {
|
|
12994
|
+
for (const [key, value] of Object.entries(repos)) {
|
|
12995
|
+
const expandedKey = expandTilde(key, home);
|
|
12996
|
+
if (expandedKey === repoRoot && isPlainObject(value)) {
|
|
12997
|
+
return value;
|
|
12998
|
+
}
|
|
12999
|
+
}
|
|
13000
|
+
return {};
|
|
13001
|
+
}
|
|
13002
|
+
function expandTilde(value, home) {
|
|
13003
|
+
if (value === "~") return home;
|
|
13004
|
+
if (value.startsWith("~/")) return join(home, value.slice(2));
|
|
13005
|
+
return value;
|
|
13006
|
+
}
|
|
13007
|
+
function isPlainObject(value) {
|
|
13008
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13009
|
+
}
|
|
13010
|
+
function isMissingFileError(error) {
|
|
13011
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
13012
|
+
}
|
|
13013
|
+
function warnUnknownKeys(config, filePath, knownKeys, onWarning) {
|
|
13014
|
+
for (const key of Object.keys(config)) {
|
|
13015
|
+
if (!knownKeys.has(key)) {
|
|
13016
|
+
const suggestion = closestKey(key, knownKeys);
|
|
13017
|
+
const hint = suggestion ? ` (did you mean "${suggestion}"?)` : "";
|
|
13018
|
+
onWarning(`gji: unknown config key "${key}" in ${filePath}${hint}
|
|
13019
|
+
`);
|
|
13020
|
+
}
|
|
13021
|
+
}
|
|
13022
|
+
}
|
|
13023
|
+
function closestKey(unknown, knownKeys) {
|
|
13024
|
+
let best = null;
|
|
13025
|
+
let bestDist = Infinity;
|
|
13026
|
+
for (const key of knownKeys) {
|
|
13027
|
+
const dist = levenshtein(unknown, key);
|
|
13028
|
+
if (dist < bestDist) {
|
|
13029
|
+
bestDist = dist;
|
|
13030
|
+
best = key;
|
|
13031
|
+
}
|
|
13032
|
+
}
|
|
13033
|
+
return bestDist <= Math.max(2, Math.floor(unknown.length / 2)) ? best : null;
|
|
13034
|
+
}
|
|
13035
|
+
function levenshtein(a, b3) {
|
|
13036
|
+
const m2 = a.length;
|
|
13037
|
+
const n = b3.length;
|
|
13038
|
+
const dp = Array.from(
|
|
13039
|
+
{ length: m2 + 1 },
|
|
13040
|
+
(_3, i) => Array.from({ length: n + 1 }, (_4, j2) => i === 0 ? j2 : j2 === 0 ? i : 0)
|
|
13041
|
+
);
|
|
13042
|
+
for (let i = 1; i <= m2; i++) {
|
|
13043
|
+
for (let j2 = 1; j2 <= n; j2++) {
|
|
13044
|
+
dp[i][j2] = a[i - 1] === b3[j2 - 1] ? dp[i - 1][j2 - 1] : 1 + Math.min(dp[i - 1][j2], dp[i][j2 - 1], dp[i - 1][j2 - 1]);
|
|
13045
|
+
}
|
|
13046
|
+
}
|
|
13047
|
+
return dp[m2][n];
|
|
13048
|
+
}
|
|
13049
|
+
|
|
13050
|
+
// src/history.ts
|
|
13051
|
+
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
13052
|
+
import { homedir as homedir2 } from "node:os";
|
|
13053
|
+
import { dirname as dirname2, join as join2, resolve as resolve2 } from "node:path";
|
|
13054
|
+
var HISTORY_FILE_NAME = "history.json";
|
|
13055
|
+
var MAX_HISTORY_ENTRIES = 50;
|
|
13056
|
+
function HISTORY_FILE_PATH(home = homedir2()) {
|
|
13057
|
+
const configDir = process.env.GJI_CONFIG_DIR;
|
|
13058
|
+
if (configDir) {
|
|
13059
|
+
return join2(resolve2(configDir), HISTORY_FILE_NAME);
|
|
13060
|
+
}
|
|
13061
|
+
return join2(home, GLOBAL_CONFIG_DIRECTORY, HISTORY_FILE_NAME);
|
|
13062
|
+
}
|
|
13063
|
+
async function loadHistory(home = homedir2()) {
|
|
13064
|
+
const path9 = HISTORY_FILE_PATH(home);
|
|
13065
|
+
try {
|
|
13066
|
+
const raw = await readFile2(path9, "utf8");
|
|
13067
|
+
const parsed = JSON.parse(raw);
|
|
13068
|
+
if (!Array.isArray(parsed)) return [];
|
|
13069
|
+
return parsed.filter(isHistoryEntry);
|
|
13070
|
+
} catch {
|
|
13071
|
+
return [];
|
|
13072
|
+
}
|
|
13073
|
+
}
|
|
13074
|
+
async function appendHistory(path9, branch, home = homedir2()) {
|
|
13075
|
+
const historyPath = HISTORY_FILE_PATH(home);
|
|
13076
|
+
const existing = await loadHistory(home);
|
|
13077
|
+
if (existing.length > 0 && existing[0].path === path9) {
|
|
13078
|
+
return;
|
|
13079
|
+
}
|
|
13080
|
+
const entry = { branch, path: path9, timestamp: Date.now() };
|
|
13081
|
+
const next = [entry, ...existing].slice(0, MAX_HISTORY_ENTRIES);
|
|
13082
|
+
await mkdir2(dirname2(historyPath), { recursive: true });
|
|
13083
|
+
await writeFile2(historyPath, `${JSON.stringify(next, null, 2)}
|
|
13084
|
+
`, "utf8");
|
|
13085
|
+
}
|
|
13086
|
+
function isHistoryEntry(value) {
|
|
13087
|
+
return typeof value === "object" && value !== null && "path" in value && typeof value.path === "string" && "timestamp" in value && typeof value.timestamp === "number";
|
|
13088
|
+
}
|
|
13089
|
+
|
|
13090
|
+
// src/hooks.ts
|
|
13091
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
13092
|
+
async function runHook(hookCmd, cwd, context, stderr) {
|
|
13093
|
+
if (!hookCmd) return;
|
|
13094
|
+
if (Array.isArray(hookCmd)) {
|
|
13095
|
+
await runArgvHook(hookCmd, cwd, context, stderr);
|
|
13096
|
+
return;
|
|
13097
|
+
}
|
|
13098
|
+
await runShellHook(hookCmd, cwd, context, stderr);
|
|
13099
|
+
}
|
|
13100
|
+
async function runArgvHook(hookCmd, cwd, context, stderr) {
|
|
13101
|
+
const [command, ...args] = hookCmd.map((arg) => interpolate(arg, context));
|
|
13102
|
+
if (!command) {
|
|
13103
|
+
stderr("gji: hook argv command must include a non-empty command\n");
|
|
13049
13104
|
return;
|
|
13050
13105
|
}
|
|
13051
13106
|
await new Promise((resolve6) => {
|
|
@@ -13060,8 +13115,10 @@ async function runArgvHook(hookCmd, cwd, context, stderr) {
|
|
|
13060
13115
|
});
|
|
13061
13116
|
child.on("close", (code) => {
|
|
13062
13117
|
if (code !== 0) {
|
|
13063
|
-
stderr(
|
|
13064
|
-
`)
|
|
13118
|
+
stderr(
|
|
13119
|
+
`gji: hook exited with code ${code}: ${formatArgvHook(command, args)}
|
|
13120
|
+
`
|
|
13121
|
+
);
|
|
13065
13122
|
}
|
|
13066
13123
|
resolve6();
|
|
13067
13124
|
});
|
|
@@ -13132,49 +13189,9 @@ function parseHookCommand(value) {
|
|
|
13132
13189
|
return void 0;
|
|
13133
13190
|
}
|
|
13134
13191
|
|
|
13135
|
-
// src/history.ts
|
|
13136
|
-
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
13137
|
-
import { homedir as homedir2 } from "node:os";
|
|
13138
|
-
import { dirname as dirname2, join as join2, resolve as resolve2 } from "node:path";
|
|
13139
|
-
var HISTORY_FILE_NAME = "history.json";
|
|
13140
|
-
var MAX_HISTORY_ENTRIES = 50;
|
|
13141
|
-
function HISTORY_FILE_PATH(home = homedir2()) {
|
|
13142
|
-
const configDir = process.env.GJI_CONFIG_DIR;
|
|
13143
|
-
if (configDir) {
|
|
13144
|
-
return join2(resolve2(configDir), HISTORY_FILE_NAME);
|
|
13145
|
-
}
|
|
13146
|
-
return join2(home, GLOBAL_CONFIG_DIRECTORY, HISTORY_FILE_NAME);
|
|
13147
|
-
}
|
|
13148
|
-
async function loadHistory(home = homedir2()) {
|
|
13149
|
-
const path9 = HISTORY_FILE_PATH(home);
|
|
13150
|
-
try {
|
|
13151
|
-
const raw = await readFile2(path9, "utf8");
|
|
13152
|
-
const parsed = JSON.parse(raw);
|
|
13153
|
-
if (!Array.isArray(parsed)) return [];
|
|
13154
|
-
return parsed.filter(isHistoryEntry);
|
|
13155
|
-
} catch {
|
|
13156
|
-
return [];
|
|
13157
|
-
}
|
|
13158
|
-
}
|
|
13159
|
-
async function appendHistory(path9, branch, home = homedir2()) {
|
|
13160
|
-
const historyPath = HISTORY_FILE_PATH(home);
|
|
13161
|
-
const existing = await loadHistory(home);
|
|
13162
|
-
if (existing.length > 0 && existing[0].path === path9) {
|
|
13163
|
-
return;
|
|
13164
|
-
}
|
|
13165
|
-
const entry = { branch, path: path9, timestamp: Date.now() };
|
|
13166
|
-
const next = [entry, ...existing].slice(0, MAX_HISTORY_ENTRIES);
|
|
13167
|
-
await mkdir2(dirname2(historyPath), { recursive: true });
|
|
13168
|
-
await writeFile2(historyPath, `${JSON.stringify(next, null, 2)}
|
|
13169
|
-
`, "utf8");
|
|
13170
|
-
}
|
|
13171
|
-
function isHistoryEntry(value) {
|
|
13172
|
-
return typeof value === "object" && value !== null && "path" in value && typeof value.path === "string" && "timestamp" in value && typeof value.timestamp === "number";
|
|
13173
|
-
}
|
|
13174
|
-
|
|
13175
13192
|
// src/repo.ts
|
|
13176
|
-
import { basename, dirname as dirname3, isAbsolute, join as join3, resolve as resolve3 } from "node:path";
|
|
13177
13193
|
import { homedir as homedir3 } from "node:os";
|
|
13194
|
+
import { basename, dirname as dirname3, isAbsolute, join as join3, resolve as resolve3 } from "node:path";
|
|
13178
13195
|
|
|
13179
13196
|
// src/git.ts
|
|
13180
13197
|
import { execFile } from "node:child_process";
|
|
@@ -13190,7 +13207,11 @@ async function runGit(cwd, args) {
|
|
|
13190
13207
|
}
|
|
13191
13208
|
}
|
|
13192
13209
|
async function readWorktreeHealth(cwd) {
|
|
13193
|
-
const { stdout } = await execFileAsync(
|
|
13210
|
+
const { stdout } = await execFileAsync(
|
|
13211
|
+
"git",
|
|
13212
|
+
["status", "--porcelain=v2", "--branch"],
|
|
13213
|
+
{ cwd }
|
|
13214
|
+
);
|
|
13194
13215
|
return parseWorktreeHealth(stdout);
|
|
13195
13216
|
}
|
|
13196
13217
|
async function isDirtyWorktree(cwd) {
|
|
@@ -13199,7 +13220,9 @@ async function isDirtyWorktree(cwd) {
|
|
|
13199
13220
|
}
|
|
13200
13221
|
async function isBranchMergedInto(cwd, branch, base = "HEAD") {
|
|
13201
13222
|
try {
|
|
13202
|
-
await execFileAsync("git", ["merge-base", "--is-ancestor", branch, base], {
|
|
13223
|
+
await execFileAsync("git", ["merge-base", "--is-ancestor", branch, base], {
|
|
13224
|
+
cwd
|
|
13225
|
+
});
|
|
13203
13226
|
return true;
|
|
13204
13227
|
} catch (error) {
|
|
13205
13228
|
if (hasExitCode(error, 1)) {
|
|
@@ -13209,7 +13232,11 @@ async function isBranchMergedInto(cwd, branch, base = "HEAD") {
|
|
|
13209
13232
|
}
|
|
13210
13233
|
}
|
|
13211
13234
|
async function resolveRemoteDefaultBranch(cwd, remote) {
|
|
13212
|
-
const { stdout } = await execFileAsync(
|
|
13235
|
+
const { stdout } = await execFileAsync(
|
|
13236
|
+
"git",
|
|
13237
|
+
["ls-remote", "--symref", remote, "HEAD"],
|
|
13238
|
+
{ cwd }
|
|
13239
|
+
);
|
|
13213
13240
|
const refLine = stdout.split("\n").find((line) => line.startsWith("ref: refs/heads/"));
|
|
13214
13241
|
if (!refLine) {
|
|
13215
13242
|
return null;
|
|
@@ -13219,7 +13246,11 @@ async function resolveRemoteDefaultBranch(cwd, remote) {
|
|
|
13219
13246
|
}
|
|
13220
13247
|
async function readBranchLastCommitTimestamp(cwd, branch) {
|
|
13221
13248
|
try {
|
|
13222
|
-
const { stdout } = await execFileAsync(
|
|
13249
|
+
const { stdout } = await execFileAsync(
|
|
13250
|
+
"git",
|
|
13251
|
+
["log", "-1", "--format=%ct", branch],
|
|
13252
|
+
{ cwd }
|
|
13253
|
+
);
|
|
13223
13254
|
const timestamp = Number(stdout.trim());
|
|
13224
13255
|
return Number.isFinite(timestamp) ? timestamp : null;
|
|
13225
13256
|
} catch {
|
|
@@ -13283,7 +13314,9 @@ function resolveWorktreePath(repoRoot, branch, basePath) {
|
|
|
13283
13314
|
throw new Error("Branch name must not be empty.");
|
|
13284
13315
|
}
|
|
13285
13316
|
if (segments.some((segment) => segment === "." || segment === "..")) {
|
|
13286
|
-
throw new Error(
|
|
13317
|
+
throw new Error(
|
|
13318
|
+
`Branch name '${branch}' contains an invalid path segment.`
|
|
13319
|
+
);
|
|
13287
13320
|
}
|
|
13288
13321
|
const base = basePath ? expandTildeInPath(basePath) : join3(dirname3(repoRoot), "worktrees", basename(repoRoot));
|
|
13289
13322
|
return join3(base, ...segments);
|
|
@@ -13403,17 +13436,27 @@ async function runBackCommand(options) {
|
|
|
13403
13436
|
}
|
|
13404
13437
|
if (!target) {
|
|
13405
13438
|
options.stderr("gji back: no previous worktree in history\n");
|
|
13406
|
-
options.stderr(
|
|
13439
|
+
options.stderr(
|
|
13440
|
+
"Hint: Use 'gji go', 'gji new', or 'gji pr' to navigate between worktrees\n"
|
|
13441
|
+
);
|
|
13407
13442
|
return 1;
|
|
13408
13443
|
}
|
|
13409
13444
|
try {
|
|
13410
13445
|
const repository = await detectRepository(target.path);
|
|
13411
|
-
const config = await loadEffectiveConfig(
|
|
13446
|
+
const config = await loadEffectiveConfig(
|
|
13447
|
+
repository.repoRoot,
|
|
13448
|
+
options.home,
|
|
13449
|
+
options.stderr
|
|
13450
|
+
);
|
|
13412
13451
|
const hooks = extractHooks(config);
|
|
13413
13452
|
await runHook(
|
|
13414
13453
|
hooks.afterEnter,
|
|
13415
13454
|
target.path,
|
|
13416
|
-
{
|
|
13455
|
+
{
|
|
13456
|
+
branch: target.branch ?? void 0,
|
|
13457
|
+
path: target.path,
|
|
13458
|
+
repo: basename2(repository.repoRoot)
|
|
13459
|
+
},
|
|
13417
13460
|
options.stderr
|
|
13418
13461
|
);
|
|
13419
13462
|
} catch {
|
|
@@ -13427,7 +13470,9 @@ function formatHistoryList(history, cwd) {
|
|
|
13427
13470
|
"BRANCH".length,
|
|
13428
13471
|
...history.map((e2) => (e2.branch ?? "(detached)").length)
|
|
13429
13472
|
);
|
|
13430
|
-
const lines = [
|
|
13473
|
+
const lines = [
|
|
13474
|
+
" " + "BRANCH".padEnd(branchWidth) + " WHEN PATH"
|
|
13475
|
+
];
|
|
13431
13476
|
for (const entry of history) {
|
|
13432
13477
|
const isCurrent = entry.path === cwd;
|
|
13433
13478
|
const branch = (entry.branch ?? "(detached)").padEnd(branchWidth);
|
|
@@ -14121,7 +14166,10 @@ function formatLastCommit(timestampSeconds) {
|
|
|
14121
14166
|
return timestampSeconds === null ? "n/a" : formatRelativeAge(timestampSeconds);
|
|
14122
14167
|
}
|
|
14123
14168
|
function formatRelativeAge(timestampSeconds) {
|
|
14124
|
-
const ageSeconds = Math.max(
|
|
14169
|
+
const ageSeconds = Math.max(
|
|
14170
|
+
0,
|
|
14171
|
+
Math.floor(Date.now() / 1e3) - timestampSeconds
|
|
14172
|
+
);
|
|
14125
14173
|
const units = [
|
|
14126
14174
|
{ label: "y", seconds: 365 * 24 * 60 * 60 },
|
|
14127
14175
|
{ label: "mo", seconds: 30 * 24 * 60 * 60 },
|
|
@@ -14154,16 +14202,28 @@ async function loadLinkedWorktrees(cwd) {
|
|
|
14154
14202
|
};
|
|
14155
14203
|
}
|
|
14156
14204
|
async function removeWorktree(repoRoot, worktreePath) {
|
|
14157
|
-
await execFileAsync2("git", ["worktree", "remove", worktreePath], {
|
|
14205
|
+
await execFileAsync2("git", ["worktree", "remove", worktreePath], {
|
|
14206
|
+
cwd: repoRoot,
|
|
14207
|
+
env: GIT_ENV
|
|
14208
|
+
});
|
|
14158
14209
|
}
|
|
14159
14210
|
async function forceRemoveWorktree(repoRoot, worktreePath) {
|
|
14160
|
-
await execFileAsync2("git", ["worktree", "remove", "--force", worktreePath], {
|
|
14211
|
+
await execFileAsync2("git", ["worktree", "remove", "--force", worktreePath], {
|
|
14212
|
+
cwd: repoRoot,
|
|
14213
|
+
env: GIT_ENV
|
|
14214
|
+
});
|
|
14161
14215
|
}
|
|
14162
14216
|
async function deleteBranch(repoRoot, branch) {
|
|
14163
|
-
await execFileAsync2("git", ["branch", "-d", branch], {
|
|
14217
|
+
await execFileAsync2("git", ["branch", "-d", branch], {
|
|
14218
|
+
cwd: repoRoot,
|
|
14219
|
+
env: GIT_ENV
|
|
14220
|
+
});
|
|
14164
14221
|
}
|
|
14165
14222
|
async function forceDeleteBranch(repoRoot, branch) {
|
|
14166
|
-
await execFileAsync2("git", ["branch", "-D", branch], {
|
|
14223
|
+
await execFileAsync2("git", ["branch", "-D", branch], {
|
|
14224
|
+
cwd: repoRoot,
|
|
14225
|
+
env: GIT_ENV
|
|
14226
|
+
});
|
|
14167
14227
|
}
|
|
14168
14228
|
function isWorktreeDirtyError(error) {
|
|
14169
14229
|
return hasStderr(error) && error.stderr.includes("contains modified or untracked files");
|
|
@@ -14202,12 +14262,18 @@ function createCleanCommand(dependencies = {}) {
|
|
|
14202
14262
|
const confirmForceRemoveWorktree = dependencies.confirmForceRemoveWorktree ?? defaultConfirmForceRemoveWorktree;
|
|
14203
14263
|
const confirmForceDeleteBranch = dependencies.confirmForceDeleteBranch ?? defaultConfirmForceDeleteBranch;
|
|
14204
14264
|
return async function runCleanCommand2(options) {
|
|
14205
|
-
const { linkedWorktrees, repository } = await loadLinkedWorktrees(
|
|
14265
|
+
const { linkedWorktrees, repository } = await loadLinkedWorktrees(
|
|
14266
|
+
options.cwd
|
|
14267
|
+
);
|
|
14206
14268
|
const linkedCleanupCandidates = linkedWorktrees.filter(
|
|
14207
14269
|
(worktree) => worktree.path !== repository.currentRoot
|
|
14208
14270
|
);
|
|
14209
14271
|
const staleBaseRef = options.stale ? await resolveStaleBaseRef(repository.repoRoot, options.stderr) : null;
|
|
14210
|
-
const cleanupCandidates = options.stale ? await filterStaleCleanupCandidates(
|
|
14272
|
+
const cleanupCandidates = options.stale ? await filterStaleCleanupCandidates(
|
|
14273
|
+
repository.repoRoot,
|
|
14274
|
+
linkedCleanupCandidates,
|
|
14275
|
+
staleBaseRef
|
|
14276
|
+
) : linkedCleanupCandidates;
|
|
14211
14277
|
if (cleanupCandidates.length === 0) {
|
|
14212
14278
|
if (options.stale) {
|
|
14213
14279
|
emitNoStaleCandidates(options);
|
|
@@ -14221,8 +14287,10 @@ function createCleanCommand(dependencies = {}) {
|
|
|
14221
14287
|
if (options.json) {
|
|
14222
14288
|
emitError(options, message);
|
|
14223
14289
|
} else {
|
|
14224
|
-
options.stderr(
|
|
14225
|
-
`)
|
|
14290
|
+
options.stderr(
|
|
14291
|
+
`gji clean: ${message} in non-interactive mode (GJI_NO_TUI=1)
|
|
14292
|
+
`
|
|
14293
|
+
);
|
|
14226
14294
|
}
|
|
14227
14295
|
return 1;
|
|
14228
14296
|
}
|
|
@@ -14232,7 +14300,10 @@ function createCleanCommand(dependencies = {}) {
|
|
|
14232
14300
|
options.stderr("Aborted\n");
|
|
14233
14301
|
return 1;
|
|
14234
14302
|
}
|
|
14235
|
-
const selectedWorktrees = resolveSelectedWorktrees(
|
|
14303
|
+
const selectedWorktrees = resolveSelectedWorktrees(
|
|
14304
|
+
cleanupCandidates,
|
|
14305
|
+
selections
|
|
14306
|
+
);
|
|
14236
14307
|
if (selectedWorktrees.length !== selections.length) {
|
|
14237
14308
|
options.stderr("Selected worktree no longer exists\n");
|
|
14238
14309
|
return 1;
|
|
@@ -14247,13 +14318,19 @@ function createCleanCommand(dependencies = {}) {
|
|
|
14247
14318
|
}
|
|
14248
14319
|
if (options.dryRun) {
|
|
14249
14320
|
if (options.json) {
|
|
14250
|
-
const removed = selectedWorktreeInfos.map(
|
|
14251
|
-
|
|
14252
|
-
|
|
14321
|
+
const removed = selectedWorktreeInfos.map(
|
|
14322
|
+
(info) => serializeWorktreeInfo(info)
|
|
14323
|
+
);
|
|
14324
|
+
options.stdout(
|
|
14325
|
+
`${JSON.stringify({ removed, dryRun: true }, null, 2)}
|
|
14326
|
+
`
|
|
14327
|
+
);
|
|
14253
14328
|
} else {
|
|
14254
14329
|
for (const info of selectedWorktreeInfos) {
|
|
14255
|
-
options.stdout(
|
|
14256
|
-
`)
|
|
14330
|
+
options.stdout(
|
|
14331
|
+
`Would remove worktree at ${info.path} (${formatCleanInfo(info)})
|
|
14332
|
+
`
|
|
14333
|
+
);
|
|
14257
14334
|
}
|
|
14258
14335
|
}
|
|
14259
14336
|
return 0;
|
|
@@ -14261,9 +14338,15 @@ function createCleanCommand(dependencies = {}) {
|
|
|
14261
14338
|
const removedPaths = [];
|
|
14262
14339
|
const removedWorktrees = [];
|
|
14263
14340
|
for (const worktree of selectedWorktrees) {
|
|
14264
|
-
if (options.stale && !await isStaleCleanupCandidate(
|
|
14265
|
-
|
|
14266
|
-
|
|
14341
|
+
if (options.stale && !await isStaleCleanupCandidate(
|
|
14342
|
+
repository.repoRoot,
|
|
14343
|
+
worktree,
|
|
14344
|
+
staleBaseRef
|
|
14345
|
+
)) {
|
|
14346
|
+
options.stderr(
|
|
14347
|
+
`Skipped ${worktree.path}: no longer a safe stale cleanup candidate
|
|
14348
|
+
`
|
|
14349
|
+
);
|
|
14267
14350
|
continue;
|
|
14268
14351
|
}
|
|
14269
14352
|
try {
|
|
@@ -14273,8 +14356,10 @@ function createCleanCommand(dependencies = {}) {
|
|
|
14273
14356
|
throw error;
|
|
14274
14357
|
}
|
|
14275
14358
|
if (options.stale) {
|
|
14276
|
-
options.stderr(
|
|
14277
|
-
`
|
|
14359
|
+
options.stderr(
|
|
14360
|
+
`Skipped ${worktree.path}: no longer a safe stale cleanup candidate
|
|
14361
|
+
`
|
|
14362
|
+
);
|
|
14278
14363
|
continue;
|
|
14279
14364
|
}
|
|
14280
14365
|
if (!options.force && !await confirmForceRemoveWorktree(worktree.path)) {
|
|
@@ -14288,7 +14373,10 @@ function createCleanCommand(dependencies = {}) {
|
|
|
14288
14373
|
if (!options.json) {
|
|
14289
14374
|
reportRemovedPaths(removedPaths, options.stderr);
|
|
14290
14375
|
}
|
|
14291
|
-
emitError(
|
|
14376
|
+
emitError(
|
|
14377
|
+
options,
|
|
14378
|
+
`Failed to remove worktree at ${worktree.path}: ${toMessage(forceError)}`
|
|
14379
|
+
);
|
|
14292
14380
|
return 1;
|
|
14293
14381
|
}
|
|
14294
14382
|
}
|
|
@@ -14305,12 +14393,16 @@ function createCleanCommand(dependencies = {}) {
|
|
|
14305
14393
|
try {
|
|
14306
14394
|
await forceDeleteBranch(repository.repoRoot, worktree.branch);
|
|
14307
14395
|
} catch (forceError) {
|
|
14308
|
-
options.stderr(
|
|
14309
|
-
`)
|
|
14396
|
+
options.stderr(
|
|
14397
|
+
`Failed to delete branch ${worktree.branch}: ${toMessage(forceError)}
|
|
14398
|
+
`
|
|
14399
|
+
);
|
|
14310
14400
|
}
|
|
14311
14401
|
} else {
|
|
14312
|
-
options.stderr(
|
|
14313
|
-
`)
|
|
14402
|
+
options.stderr(
|
|
14403
|
+
`Branch ${worktree.branch} was not deleted (has unmerged commits)
|
|
14404
|
+
`
|
|
14405
|
+
);
|
|
14314
14406
|
}
|
|
14315
14407
|
}
|
|
14316
14408
|
}
|
|
@@ -14335,19 +14427,30 @@ async function filterStaleCleanupCandidates(repoRoot, worktrees, baseBranch) {
|
|
|
14335
14427
|
return [];
|
|
14336
14428
|
}
|
|
14337
14429
|
const results = await Promise.all(
|
|
14338
|
-
worktrees.map(
|
|
14430
|
+
worktrees.map(
|
|
14431
|
+
(worktree) => isStaleCleanupCandidate(repoRoot, worktree, baseBranch)
|
|
14432
|
+
)
|
|
14339
14433
|
);
|
|
14340
14434
|
return worktrees.filter((_3, index) => results[index]);
|
|
14341
14435
|
}
|
|
14342
14436
|
async function resolveStaleBaseRef(repoRoot, stderr) {
|
|
14343
14437
|
const config = await loadEffectiveConfig(repoRoot, void 0, stderr);
|
|
14344
14438
|
const remote = resolveConfiguredString(config.syncRemote) ?? "origin";
|
|
14345
|
-
const configuredDefaultBranch = resolveConfiguredString(
|
|
14439
|
+
const configuredDefaultBranch = resolveConfiguredString(
|
|
14440
|
+
config.syncDefaultBranch
|
|
14441
|
+
);
|
|
14346
14442
|
if (configuredDefaultBranch) {
|
|
14347
|
-
return await resolveFetchedRemoteRef(
|
|
14443
|
+
return await resolveFetchedRemoteRef(
|
|
14444
|
+
repoRoot,
|
|
14445
|
+
remote,
|
|
14446
|
+
configuredDefaultBranch
|
|
14447
|
+
);
|
|
14348
14448
|
}
|
|
14349
14449
|
try {
|
|
14350
|
-
const remoteDefaultBranch = await resolveRemoteDefaultBranch(
|
|
14450
|
+
const remoteDefaultBranch = await resolveRemoteDefaultBranch(
|
|
14451
|
+
repoRoot,
|
|
14452
|
+
remote
|
|
14453
|
+
);
|
|
14351
14454
|
return remoteDefaultBranch === null ? null : await resolveFetchedRemoteRef(repoRoot, remote, remoteDefaultBranch);
|
|
14352
14455
|
} catch {
|
|
14353
14456
|
return null;
|
|
@@ -14442,14 +14545,22 @@ async function defaultPromptForWorktrees(worktrees) {
|
|
|
14442
14545
|
return pD(choice) ? null : choice;
|
|
14443
14546
|
}
|
|
14444
14547
|
async function defaultConfirmRemoval(worktrees) {
|
|
14445
|
-
const branchCount = worktrees.filter(
|
|
14548
|
+
const branchCount = worktrees.filter(
|
|
14549
|
+
(worktree) => worktree.branch !== null
|
|
14550
|
+
).length;
|
|
14446
14551
|
const detachedCount = worktrees.length - branchCount;
|
|
14447
|
-
const messageParts = [
|
|
14552
|
+
const messageParts = [
|
|
14553
|
+
`Remove ${worktrees.length} linked worktree${worktrees.length === 1 ? "" : "s"}`
|
|
14554
|
+
];
|
|
14448
14555
|
if (branchCount > 0) {
|
|
14449
|
-
messageParts.push(
|
|
14556
|
+
messageParts.push(
|
|
14557
|
+
`delete ${branchCount} branch${branchCount === 1 ? "" : "es"}`
|
|
14558
|
+
);
|
|
14450
14559
|
}
|
|
14451
14560
|
if (detachedCount > 0) {
|
|
14452
|
-
messageParts.push(
|
|
14561
|
+
messageParts.push(
|
|
14562
|
+
`remove ${detachedCount} detached worktree${detachedCount === 1 ? "" : "s"}`
|
|
14563
|
+
);
|
|
14453
14564
|
}
|
|
14454
14565
|
const choice = await ye({
|
|
14455
14566
|
active: "Yes",
|
|
@@ -14460,25 +14571,35 @@ async function defaultConfirmRemoval(worktrees) {
|
|
|
14460
14571
|
return !pD(choice) && choice;
|
|
14461
14572
|
}
|
|
14462
14573
|
|
|
14463
|
-
// src/
|
|
14464
|
-
|
|
14465
|
-
const
|
|
14466
|
-
if (
|
|
14467
|
-
|
|
14468
|
-
`);
|
|
14469
|
-
return 0;
|
|
14574
|
+
// src/shell.ts
|
|
14575
|
+
function resolveSupportedShell(requestedShell, detectedShell) {
|
|
14576
|
+
const requested = normalizeShell(requestedShell);
|
|
14577
|
+
if (requested) {
|
|
14578
|
+
return requested;
|
|
14470
14579
|
}
|
|
14471
|
-
|
|
14472
|
-
|
|
14473
|
-
|
|
14580
|
+
return normalizeShell(detectedShell);
|
|
14581
|
+
}
|
|
14582
|
+
function normalizeShell(value) {
|
|
14583
|
+
if (!value) {
|
|
14584
|
+
return null;
|
|
14585
|
+
}
|
|
14586
|
+
const candidate = value.split("/").at(-1)?.toLowerCase();
|
|
14587
|
+
switch (candidate) {
|
|
14588
|
+
case "bash":
|
|
14589
|
+
case "fish":
|
|
14590
|
+
case "zsh":
|
|
14591
|
+
return candidate;
|
|
14592
|
+
default:
|
|
14593
|
+
return null;
|
|
14474
14594
|
}
|
|
14475
|
-
options.stdout(formatHistoryList(history, options.cwd));
|
|
14476
|
-
return 0;
|
|
14477
14595
|
}
|
|
14478
14596
|
|
|
14479
14597
|
// src/shell-completion.ts
|
|
14480
14598
|
var TOP_LEVEL_COMMANDS = [
|
|
14481
|
-
{
|
|
14599
|
+
{
|
|
14600
|
+
name: "new",
|
|
14601
|
+
description: "create a new branch or detached linked worktree"
|
|
14602
|
+
},
|
|
14482
14603
|
{ name: "init", description: "print or install shell integration" },
|
|
14483
14604
|
{ name: "completion", description: "print shell completion definitions" },
|
|
14484
14605
|
{ name: "pr", description: "fetch a pull request into a linked worktree" },
|
|
@@ -14490,11 +14611,21 @@ var TOP_LEVEL_COMMANDS = [
|
|
|
14490
14611
|
{ name: "root", description: "print the main repository root path" },
|
|
14491
14612
|
{ name: "status", description: "summarize repository and worktree health" },
|
|
14492
14613
|
{ name: "sync", description: "fetch and update one or all worktrees" },
|
|
14614
|
+
{
|
|
14615
|
+
name: "sync-files",
|
|
14616
|
+
description: "manage local files copied into new worktrees"
|
|
14617
|
+
},
|
|
14493
14618
|
{ name: "ls", description: "list active worktrees" },
|
|
14494
14619
|
{ name: "clean", description: "interactively prune linked worktrees" },
|
|
14495
|
-
{
|
|
14620
|
+
{
|
|
14621
|
+
name: "remove",
|
|
14622
|
+
description: "remove a linked worktree and delete its branch when present"
|
|
14623
|
+
},
|
|
14496
14624
|
{ name: "rm", description: "alias of remove" },
|
|
14497
|
-
{
|
|
14625
|
+
{
|
|
14626
|
+
name: "trigger-hook",
|
|
14627
|
+
description: "run a named hook in the current worktree"
|
|
14628
|
+
},
|
|
14498
14629
|
{ name: "warp", description: "jump to any worktree across all known repos" },
|
|
14499
14630
|
{ name: "config", description: "manage global config defaults" }
|
|
14500
14631
|
];
|
|
@@ -14512,7 +14643,9 @@ function renderShellCompletion(shell) {
|
|
|
14512
14643
|
}
|
|
14513
14644
|
}
|
|
14514
14645
|
function renderBashCompletion() {
|
|
14515
|
-
const topLevelCommands = TOP_LEVEL_COMMANDS.map(
|
|
14646
|
+
const topLevelCommands = TOP_LEVEL_COMMANDS.map(
|
|
14647
|
+
(command) => command.name
|
|
14648
|
+
).join(" ");
|
|
14516
14649
|
const shells = SHELL_NAMES.join(" ");
|
|
14517
14650
|
const hooks = HOOK_NAMES.join(" ");
|
|
14518
14651
|
const configKeys = CONFIG_KEYS.join(" ");
|
|
@@ -14566,6 +14699,12 @@ _gji_completion() {
|
|
|
14566
14699
|
sync)
|
|
14567
14700
|
COMPREPLY=( $(compgen -W "--all --json --help" -- "$cur") )
|
|
14568
14701
|
;;
|
|
14702
|
+
sync-files)
|
|
14703
|
+
if [ "$COMP_CWORD" -eq 2 ]; then
|
|
14704
|
+
COMPREPLY=( $(compgen -W "list add remove rm --json --help" -- "$cur") )
|
|
14705
|
+
return 0
|
|
14706
|
+
fi
|
|
14707
|
+
;;
|
|
14569
14708
|
ls)
|
|
14570
14709
|
COMPREPLY=( $(compgen -W "--compact --json --help" -- "$cur") )
|
|
14571
14710
|
;;
|
|
@@ -14679,6 +14818,12 @@ complete -c gji -n '__fish_seen_subcommand_from status' -l json -d 'print reposi
|
|
|
14679
14818
|
complete -c gji -n '__fish_seen_subcommand_from sync' -l all -d 'sync every worktree in the repository'
|
|
14680
14819
|
complete -c gji -n '__fish_seen_subcommand_from sync' -l json -d 'emit JSON on success or error instead of human-readable output'
|
|
14681
14820
|
|
|
14821
|
+
complete -c gji -n '__fish_seen_subcommand_from sync-files' -a 'list add remove rm' -d 'sync-files action'
|
|
14822
|
+
complete -c gji -n '__fish_seen_subcommand_from sync-files' -l json -d 'emit JSON instead of human-readable output'
|
|
14823
|
+
complete -c gji -n '__fish_seen_subcommand_from list; and __fish_seen_subcommand_from sync-files' -l json -d 'emit JSON instead of human-readable output'
|
|
14824
|
+
complete -c gji -n '__fish_seen_subcommand_from add; and __fish_seen_subcommand_from sync-files' -l json -d 'emit JSON instead of human-readable output'
|
|
14825
|
+
complete -c gji -n '__fish_seen_subcommand_from remove rm; and __fish_seen_subcommand_from sync-files' -l json -d 'emit JSON instead of human-readable output'
|
|
14826
|
+
|
|
14682
14827
|
complete -c gji -n '__fish_seen_subcommand_from ls' -l compact -d 'show only branch and path columns'
|
|
14683
14828
|
complete -c gji -n '__fish_seen_subcommand_from ls' -l json -d 'print active worktrees as JSON'
|
|
14684
14829
|
|
|
@@ -14760,6 +14905,9 @@ case "\${words[2]}" in
|
|
|
14760
14905
|
sync)
|
|
14761
14906
|
_arguments '--all[sync every worktree in the repository]' '--json[emit JSON on success or error instead of human-readable output]'
|
|
14762
14907
|
;;
|
|
14908
|
+
sync-files)
|
|
14909
|
+
_arguments '--json[emit JSON instead of human-readable output]' '2:action:(list add remove rm)' '*:path: '
|
|
14910
|
+
;;
|
|
14763
14911
|
ls)
|
|
14764
14912
|
_arguments '--compact[show only branch and path columns]' '--json[print active worktrees as JSON]'
|
|
14765
14913
|
;;
|
|
@@ -14806,29 +14954,6 @@ function escapeSingleQuotes(value) {
|
|
|
14806
14954
|
return value.replace(/'/g, `'\\''`);
|
|
14807
14955
|
}
|
|
14808
14956
|
|
|
14809
|
-
// src/shell.ts
|
|
14810
|
-
function resolveSupportedShell(requestedShell, detectedShell) {
|
|
14811
|
-
const requested = normalizeShell(requestedShell);
|
|
14812
|
-
if (requested) {
|
|
14813
|
-
return requested;
|
|
14814
|
-
}
|
|
14815
|
-
return normalizeShell(detectedShell);
|
|
14816
|
-
}
|
|
14817
|
-
function normalizeShell(value) {
|
|
14818
|
-
if (!value) {
|
|
14819
|
-
return null;
|
|
14820
|
-
}
|
|
14821
|
-
const candidate = value.split("/").at(-1)?.toLowerCase();
|
|
14822
|
-
switch (candidate) {
|
|
14823
|
-
case "bash":
|
|
14824
|
-
case "fish":
|
|
14825
|
-
case "zsh":
|
|
14826
|
-
return candidate;
|
|
14827
|
-
default:
|
|
14828
|
-
return null;
|
|
14829
|
-
}
|
|
14830
|
-
}
|
|
14831
|
-
|
|
14832
14957
|
// src/completion.ts
|
|
14833
14958
|
async function runCompletionCommand(options) {
|
|
14834
14959
|
const shell = resolveSupportedShell(options.shell, process.env.SHELL);
|
|
@@ -14860,7 +14985,10 @@ async function runConfigCommand(options) {
|
|
|
14860
14985
|
}
|
|
14861
14986
|
case "set":
|
|
14862
14987
|
if (options.key && options.value !== void 0) {
|
|
14863
|
-
await updateGlobalConfigKey(
|
|
14988
|
+
await updateGlobalConfigKey(
|
|
14989
|
+
options.key,
|
|
14990
|
+
parseConfigValue(options.value)
|
|
14991
|
+
);
|
|
14864
14992
|
return 0;
|
|
14865
14993
|
}
|
|
14866
14994
|
break;
|
|
@@ -14888,19 +15016,72 @@ import { realpath as realpath2 } from "node:fs/promises";
|
|
|
14888
15016
|
import { basename as basename5, resolve as resolve5 } from "node:path";
|
|
14889
15017
|
|
|
14890
15018
|
// src/new.ts
|
|
15019
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
14891
15020
|
import { mkdir as mkdir4 } from "node:fs/promises";
|
|
14892
15021
|
import { basename as basename3, dirname as dirname5 } from "node:path";
|
|
14893
|
-
import { execFile as execFile3 } from "node:child_process";
|
|
14894
15022
|
import { promisify as promisify4 } from "node:util";
|
|
14895
15023
|
|
|
15024
|
+
// src/conflict.ts
|
|
15025
|
+
import { constants } from "node:fs";
|
|
15026
|
+
import { access as access2 } from "node:fs/promises";
|
|
15027
|
+
async function pathExists(path9) {
|
|
15028
|
+
try {
|
|
15029
|
+
await access2(path9, constants.F_OK);
|
|
15030
|
+
return true;
|
|
15031
|
+
} catch {
|
|
15032
|
+
return false;
|
|
15033
|
+
}
|
|
15034
|
+
}
|
|
15035
|
+
async function promptForPathConflict(path9) {
|
|
15036
|
+
const choice = await ve({
|
|
15037
|
+
message: `Target path already exists: ${path9}`,
|
|
15038
|
+
options: [
|
|
15039
|
+
{
|
|
15040
|
+
value: "abort",
|
|
15041
|
+
label: "Abort",
|
|
15042
|
+
hint: "Keep the existing directory untouched"
|
|
15043
|
+
},
|
|
15044
|
+
{
|
|
15045
|
+
value: "reuse",
|
|
15046
|
+
label: "Reuse path",
|
|
15047
|
+
hint: "Print the existing path and stop"
|
|
15048
|
+
}
|
|
15049
|
+
]
|
|
15050
|
+
});
|
|
15051
|
+
if (pD(choice)) {
|
|
15052
|
+
return "abort";
|
|
15053
|
+
}
|
|
15054
|
+
return choice;
|
|
15055
|
+
}
|
|
15056
|
+
|
|
14896
15057
|
// src/editor.ts
|
|
14897
15058
|
import { spawn as spawn3 } from "node:child_process";
|
|
14898
15059
|
var EDITORS = [
|
|
14899
|
-
{
|
|
14900
|
-
|
|
14901
|
-
|
|
15060
|
+
{
|
|
15061
|
+
cli: "cursor",
|
|
15062
|
+
name: "Cursor",
|
|
15063
|
+
newWindowFlag: "--new-window",
|
|
15064
|
+
supportsWorkspace: true
|
|
15065
|
+
},
|
|
15066
|
+
{
|
|
15067
|
+
cli: "code",
|
|
15068
|
+
name: "VS Code",
|
|
15069
|
+
newWindowFlag: "--new-window",
|
|
15070
|
+
supportsWorkspace: true
|
|
15071
|
+
},
|
|
15072
|
+
{
|
|
15073
|
+
cli: "windsurf",
|
|
15074
|
+
name: "Windsurf",
|
|
15075
|
+
newWindowFlag: "--new-window",
|
|
15076
|
+
supportsWorkspace: true
|
|
15077
|
+
},
|
|
14902
15078
|
{ cli: "zed", name: "Zed", supportsWorkspace: false },
|
|
14903
|
-
{
|
|
15079
|
+
{
|
|
15080
|
+
cli: "subl",
|
|
15081
|
+
name: "Sublime Text",
|
|
15082
|
+
newWindowFlag: "--new-window",
|
|
15083
|
+
supportsWorkspace: false
|
|
15084
|
+
}
|
|
14904
15085
|
];
|
|
14905
15086
|
async function defaultSpawnEditor(cli, args) {
|
|
14906
15087
|
const child = spawn3(cli, args, { detached: true, stdio: "ignore" });
|
|
@@ -14915,14 +15096,8 @@ async function defaultSpawnEditor(cli, args) {
|
|
|
14915
15096
|
import { copyFile, mkdir as mkdir3, stat } from "node:fs/promises";
|
|
14916
15097
|
import { dirname as dirname4, isAbsolute as isAbsolute2, join as join4, normalize } from "node:path";
|
|
14917
15098
|
async function syncFiles(mainRoot, targetPath, patterns) {
|
|
14918
|
-
for (const pattern of patterns) {
|
|
14919
|
-
|
|
14920
|
-
throw new Error(`syncFiles: pattern must be a relative path, got: ${pattern}`);
|
|
14921
|
-
}
|
|
14922
|
-
const normalized = normalize(pattern);
|
|
14923
|
-
if (normalized.startsWith("..")) {
|
|
14924
|
-
throw new Error(`syncFiles: pattern must not contain '..' segments, got: ${pattern}`);
|
|
14925
|
-
}
|
|
15099
|
+
for (const pattern of patterns) {
|
|
15100
|
+
const normalized = validateSyncFilePattern(pattern);
|
|
14926
15101
|
const sourcePath = join4(mainRoot, normalized);
|
|
14927
15102
|
const destPath = join4(targetPath, normalized);
|
|
14928
15103
|
const sourceExists = await fileExists(sourcePath);
|
|
@@ -14937,6 +15112,20 @@ async function syncFiles(mainRoot, targetPath, patterns) {
|
|
|
14937
15112
|
await copyFile(sourcePath, destPath);
|
|
14938
15113
|
}
|
|
14939
15114
|
}
|
|
15115
|
+
function validateSyncFilePattern(pattern) {
|
|
15116
|
+
if (isAbsolute2(pattern)) {
|
|
15117
|
+
throw new Error(
|
|
15118
|
+
`syncFiles: pattern must be a relative path, got: ${pattern}`
|
|
15119
|
+
);
|
|
15120
|
+
}
|
|
15121
|
+
const normalized = normalize(pattern);
|
|
15122
|
+
if (normalized.startsWith("..")) {
|
|
15123
|
+
throw new Error(
|
|
15124
|
+
`syncFiles: pattern must not contain '..' segments, got: ${pattern}`
|
|
15125
|
+
);
|
|
15126
|
+
}
|
|
15127
|
+
return normalized;
|
|
15128
|
+
}
|
|
14940
15129
|
async function fileExists(path9) {
|
|
14941
15130
|
try {
|
|
14942
15131
|
await stat(path9);
|
|
@@ -14956,7 +15145,7 @@ function isNotFoundError(error) {
|
|
|
14956
15145
|
import { spawn as spawn4 } from "node:child_process";
|
|
14957
15146
|
|
|
14958
15147
|
// src/package-manager.ts
|
|
14959
|
-
import { access as
|
|
15148
|
+
import { access as access3, readdir } from "node:fs/promises";
|
|
14960
15149
|
import { join as join5 } from "node:path";
|
|
14961
15150
|
var ENTRIES = [
|
|
14962
15151
|
// JavaScript / TypeScript
|
|
@@ -14970,10 +15159,22 @@ var ENTRIES = [
|
|
|
14970
15159
|
{ name: "uv", signals: ["uv.lock"], command: "uv sync" },
|
|
14971
15160
|
{ name: "pipenv", signals: ["Pipfile.lock"], command: "pipenv install" },
|
|
14972
15161
|
{ name: "pdm", signals: ["pdm.lock"], command: "pdm install" },
|
|
14973
|
-
{
|
|
14974
|
-
|
|
15162
|
+
{
|
|
15163
|
+
name: "conda-lock",
|
|
15164
|
+
signals: ["conda-lock.yml"],
|
|
15165
|
+
command: "conda-lock install"
|
|
15166
|
+
},
|
|
15167
|
+
{
|
|
15168
|
+
name: "conda",
|
|
15169
|
+
signals: ["environment.yml"],
|
|
15170
|
+
command: "conda env update --file environment.yml"
|
|
15171
|
+
},
|
|
14975
15172
|
// R
|
|
14976
|
-
{
|
|
15173
|
+
{
|
|
15174
|
+
name: "renv",
|
|
15175
|
+
signals: ["renv.lock"],
|
|
15176
|
+
command: "Rscript -e 'renv::restore()'"
|
|
15177
|
+
},
|
|
14977
15178
|
// Rust
|
|
14978
15179
|
{ name: "cargo", signals: ["Cargo.lock"], command: "cargo build" },
|
|
14979
15180
|
// Go
|
|
@@ -14990,25 +15191,56 @@ var ENTRIES = [
|
|
|
14990
15191
|
// Java / Kotlin / Scala
|
|
14991
15192
|
{ name: "maven", signals: ["pom.xml"], command: "mvn install" },
|
|
14992
15193
|
{ name: "gradle", signals: ["gradlew"], command: "./gradlew build" },
|
|
14993
|
-
{
|
|
15194
|
+
{
|
|
15195
|
+
name: "gradle",
|
|
15196
|
+
signals: ["build.gradle", "build.gradle.kts"],
|
|
15197
|
+
command: "gradle build"
|
|
15198
|
+
},
|
|
14994
15199
|
{ name: "sbt", signals: ["build.sbt"], command: "sbt compile" },
|
|
14995
15200
|
// .NET (C# / F# / VB)
|
|
14996
|
-
{
|
|
15201
|
+
{
|
|
15202
|
+
name: "dotnet",
|
|
15203
|
+
signals: ["*.sln", "*.csproj", "*.fsproj", "*.vbproj"],
|
|
15204
|
+
command: "dotnet restore",
|
|
15205
|
+
glob: true
|
|
15206
|
+
},
|
|
14997
15207
|
// Swift
|
|
14998
|
-
{
|
|
15208
|
+
{
|
|
15209
|
+
name: "swift",
|
|
15210
|
+
signals: ["Package.swift"],
|
|
15211
|
+
command: "swift package resolve"
|
|
15212
|
+
},
|
|
14999
15213
|
// Haskell
|
|
15000
15214
|
{ name: "stack", signals: ["stack.yaml"], command: "stack build" },
|
|
15001
|
-
{
|
|
15002
|
-
|
|
15215
|
+
{
|
|
15216
|
+
name: "cabal",
|
|
15217
|
+
signals: ["cabal.project"],
|
|
15218
|
+
command: "cabal install --only-dependencies"
|
|
15219
|
+
},
|
|
15220
|
+
{
|
|
15221
|
+
name: "cabal",
|
|
15222
|
+
signals: ["*.cabal"],
|
|
15223
|
+
command: "cabal install --only-dependencies",
|
|
15224
|
+
glob: true
|
|
15225
|
+
},
|
|
15003
15226
|
// Clojure
|
|
15004
15227
|
{ name: "clojure", signals: ["deps.edn"], command: "clojure -P" },
|
|
15005
15228
|
{ name: "leiningen", signals: ["project.clj"], command: "lein deps" },
|
|
15006
15229
|
// OCaml
|
|
15007
15230
|
{ name: "dune", signals: ["dune-project"], command: "dune build" },
|
|
15008
15231
|
// Julia
|
|
15009
|
-
{
|
|
15232
|
+
{
|
|
15233
|
+
name: "julia",
|
|
15234
|
+
signals: ["Manifest.toml"],
|
|
15235
|
+
command: "julia --project -e 'using Pkg; Pkg.instantiate()'"
|
|
15236
|
+
},
|
|
15010
15237
|
// Nim
|
|
15011
|
-
{
|
|
15238
|
+
{
|
|
15239
|
+
name: "nimble",
|
|
15240
|
+
signals: ["*.nimble"],
|
|
15241
|
+
command: "nimble install",
|
|
15242
|
+
glob: true
|
|
15243
|
+
},
|
|
15012
15244
|
// Crystal
|
|
15013
15245
|
{ name: "shards", signals: ["shard.yml"], command: "shards install" },
|
|
15014
15246
|
// Perl
|
|
@@ -15017,12 +15249,20 @@ var ENTRIES = [
|
|
|
15017
15249
|
{ name: "zig", signals: ["build.zig.zon"], command: "zig build" },
|
|
15018
15250
|
// C / C++
|
|
15019
15251
|
{ name: "vcpkg", signals: ["vcpkg.json"], command: "vcpkg install" },
|
|
15020
|
-
{
|
|
15252
|
+
{
|
|
15253
|
+
name: "conan",
|
|
15254
|
+
signals: ["conanfile.py", "conanfile.txt"],
|
|
15255
|
+
command: "conan install ."
|
|
15256
|
+
},
|
|
15021
15257
|
// Nix
|
|
15022
15258
|
{ name: "nix", signals: ["flake.nix"], command: "nix develop" },
|
|
15023
15259
|
{ name: "nix-shell", signals: ["shell.nix"], command: "nix-shell" },
|
|
15024
15260
|
// Terraform / OpenTofu
|
|
15025
|
-
{
|
|
15261
|
+
{
|
|
15262
|
+
name: "terraform",
|
|
15263
|
+
signals: ["terraform.lock.hcl"],
|
|
15264
|
+
command: "terraform init"
|
|
15265
|
+
}
|
|
15026
15266
|
];
|
|
15027
15267
|
async function detectPackageManager(repoRoot) {
|
|
15028
15268
|
for (const entry of ENTRIES) {
|
|
@@ -15036,7 +15276,7 @@ async function detectPackageManager(repoRoot) {
|
|
|
15036
15276
|
async function matchesExact(repoRoot, signals) {
|
|
15037
15277
|
for (const signal of signals) {
|
|
15038
15278
|
try {
|
|
15039
|
-
await
|
|
15279
|
+
await access3(join5(repoRoot, signal));
|
|
15040
15280
|
return true;
|
|
15041
15281
|
} catch {
|
|
15042
15282
|
}
|
|
@@ -15085,8 +15325,10 @@ async function maybeRunInstallPrompt(worktreePath, repoRoot, config, stderr, dep
|
|
|
15085
15325
|
try {
|
|
15086
15326
|
await runner(pm.installCommand, worktreePath, stderr);
|
|
15087
15327
|
} catch (error) {
|
|
15088
|
-
stderr(
|
|
15089
|
-
`)
|
|
15328
|
+
stderr(
|
|
15329
|
+
`gji: install command failed: ${error instanceof Error ? error.message : String(error)}
|
|
15330
|
+
`
|
|
15331
|
+
);
|
|
15090
15332
|
}
|
|
15091
15333
|
}
|
|
15092
15334
|
const saveGlobal = config.installSaveTarget === "global";
|
|
@@ -15096,15 +15338,23 @@ async function maybeRunInstallPrompt(worktreePath, repoRoot, config, stderr, dep
|
|
|
15096
15338
|
try {
|
|
15097
15339
|
if (saveGlobal) {
|
|
15098
15340
|
const existingHooks = await loadExistingGlobalRepoHooks(repoRoot);
|
|
15099
|
-
await writeGlobalKey(repoRoot, "hooks", {
|
|
15341
|
+
await writeGlobalKey(repoRoot, "hooks", {
|
|
15342
|
+
...existingHooks,
|
|
15343
|
+
afterCreate: pm.installCommand
|
|
15344
|
+
});
|
|
15100
15345
|
} else {
|
|
15101
15346
|
const { config: localConfig } = await loadConfig(repoRoot);
|
|
15102
15347
|
const existingLocalHooks = isPlainObject2(localConfig.hooks) ? localConfig.hooks : {};
|
|
15103
|
-
await writeKey(repoRoot, "hooks", {
|
|
15348
|
+
await writeKey(repoRoot, "hooks", {
|
|
15349
|
+
...existingLocalHooks,
|
|
15350
|
+
afterCreate: pm.installCommand
|
|
15351
|
+
});
|
|
15104
15352
|
}
|
|
15105
15353
|
} catch (error) {
|
|
15106
|
-
stderr(
|
|
15107
|
-
`)
|
|
15354
|
+
stderr(
|
|
15355
|
+
`gji: failed to save config: ${error instanceof Error ? error.message : String(error)}
|
|
15356
|
+
`
|
|
15357
|
+
);
|
|
15108
15358
|
}
|
|
15109
15359
|
}
|
|
15110
15360
|
if (choice === "never") {
|
|
@@ -15115,14 +15365,20 @@ async function maybeRunInstallPrompt(worktreePath, repoRoot, config, stderr, dep
|
|
|
15115
15365
|
await writeKey(repoRoot, "skipInstallPrompt", true);
|
|
15116
15366
|
}
|
|
15117
15367
|
} catch (error) {
|
|
15118
|
-
stderr(
|
|
15119
|
-
`)
|
|
15368
|
+
stderr(
|
|
15369
|
+
`gji: failed to save config: ${error instanceof Error ? error.message : String(error)}
|
|
15370
|
+
`
|
|
15371
|
+
);
|
|
15120
15372
|
}
|
|
15121
15373
|
}
|
|
15122
15374
|
}
|
|
15123
15375
|
async function defaultRunInstallCommand(command, cwd, stderr) {
|
|
15124
15376
|
await new Promise((resolve6, reject) => {
|
|
15125
|
-
const child = spawn4(command, {
|
|
15377
|
+
const child = spawn4(command, {
|
|
15378
|
+
cwd,
|
|
15379
|
+
shell: true,
|
|
15380
|
+
stdio: ["ignore", "inherit", "pipe"]
|
|
15381
|
+
});
|
|
15126
15382
|
child.stderr.on("data", (chunk) => {
|
|
15127
15383
|
stderr(chunk.toString());
|
|
15128
15384
|
});
|
|
@@ -15157,7 +15413,11 @@ async function defaultPromptForInstallChoice(pm) {
|
|
|
15157
15413
|
{ value: "yes", label: "Yes", hint: "run once" },
|
|
15158
15414
|
{ value: "no", label: "No", hint: "skip this time" },
|
|
15159
15415
|
{ value: "always", label: "Always", hint: "save as afterCreate hook" },
|
|
15160
|
-
{
|
|
15416
|
+
{
|
|
15417
|
+
value: "never",
|
|
15418
|
+
label: "Never",
|
|
15419
|
+
hint: "disable this prompt for this repo"
|
|
15420
|
+
}
|
|
15161
15421
|
]
|
|
15162
15422
|
});
|
|
15163
15423
|
if (pD(choice)) {
|
|
@@ -15173,31 +15433,6 @@ function isConfiguredHookCommand(value) {
|
|
|
15173
15433
|
return Array.isArray(value) && value.length > 0 && value[0] !== "" && value.every((item) => typeof item === "string");
|
|
15174
15434
|
}
|
|
15175
15435
|
|
|
15176
|
-
// src/conflict.ts
|
|
15177
|
-
import { access as access3 } from "node:fs/promises";
|
|
15178
|
-
import { constants } from "node:fs";
|
|
15179
|
-
async function pathExists(path9) {
|
|
15180
|
-
try {
|
|
15181
|
-
await access3(path9, constants.F_OK);
|
|
15182
|
-
return true;
|
|
15183
|
-
} catch {
|
|
15184
|
-
return false;
|
|
15185
|
-
}
|
|
15186
|
-
}
|
|
15187
|
-
async function promptForPathConflict(path9) {
|
|
15188
|
-
const choice = await ve({
|
|
15189
|
-
message: `Target path already exists: ${path9}`,
|
|
15190
|
-
options: [
|
|
15191
|
-
{ value: "abort", label: "Abort", hint: "Keep the existing directory untouched" },
|
|
15192
|
-
{ value: "reuse", label: "Reuse path", hint: "Print the existing path and stop" }
|
|
15193
|
-
]
|
|
15194
|
-
});
|
|
15195
|
-
if (pD(choice)) {
|
|
15196
|
-
return "abort";
|
|
15197
|
-
}
|
|
15198
|
-
return choice;
|
|
15199
|
-
}
|
|
15200
|
-
|
|
15201
15436
|
// src/new.ts
|
|
15202
15437
|
var execFileAsync3 = promisify4(execFile3);
|
|
15203
15438
|
var NEW_OUTPUT_FILE_ENV = "GJI_NEW_OUTPUT_FILE";
|
|
@@ -15208,7 +15443,11 @@ function createNewCommand(dependencies = {}) {
|
|
|
15208
15443
|
const spawnEditor = dependencies.spawnEditor ?? defaultSpawnEditor;
|
|
15209
15444
|
return async function runNewCommand2(options) {
|
|
15210
15445
|
const repository = await detectRepository(options.cwd);
|
|
15211
|
-
const config = await loadEffectiveConfig(
|
|
15446
|
+
const config = await loadEffectiveConfig(
|
|
15447
|
+
repository.repoRoot,
|
|
15448
|
+
void 0,
|
|
15449
|
+
options.stderr
|
|
15450
|
+
);
|
|
15212
15451
|
const usesGeneratedDetachedName = options.detached && options.branch === void 0;
|
|
15213
15452
|
if (options.editor && !options.open) {
|
|
15214
15453
|
options.stderr("gji new: --editor has no effect without --open\n");
|
|
@@ -15219,8 +15458,10 @@ function createNewCommand(dependencies = {}) {
|
|
|
15219
15458
|
options.stderr(`${JSON.stringify({ error: message }, null, 2)}
|
|
15220
15459
|
`);
|
|
15221
15460
|
} else {
|
|
15222
|
-
options.stderr(
|
|
15223
|
-
`)
|
|
15461
|
+
options.stderr(
|
|
15462
|
+
`gji new: ${message} in non-interactive mode (GJI_NO_TUI=1)
|
|
15463
|
+
`
|
|
15464
|
+
);
|
|
15224
15465
|
}
|
|
15225
15466
|
return 1;
|
|
15226
15467
|
}
|
|
@@ -15238,8 +15479,10 @@ function createNewCommand(dependencies = {}) {
|
|
|
15238
15479
|
const branchError = validateBranchName(rawBranch);
|
|
15239
15480
|
if (branchError) {
|
|
15240
15481
|
if (options.json) {
|
|
15241
|
-
options.stderr(
|
|
15242
|
-
|
|
15482
|
+
options.stderr(
|
|
15483
|
+
`${JSON.stringify({ error: branchError }, null, 2)}
|
|
15484
|
+
`
|
|
15485
|
+
);
|
|
15243
15486
|
} else {
|
|
15244
15487
|
options.stderr(`gji new: ${branchError}
|
|
15245
15488
|
`);
|
|
@@ -15250,18 +15493,32 @@ function createNewCommand(dependencies = {}) {
|
|
|
15250
15493
|
const rawBasePath = resolveConfigString(config, "worktreePath");
|
|
15251
15494
|
const configuredBasePath = rawBasePath?.startsWith("/") || rawBasePath?.startsWith("~") ? rawBasePath : void 0;
|
|
15252
15495
|
const worktreeName = options.detached ? rawBranch : applyConfiguredBranchPrefix(rawBranch, config.branchPrefix);
|
|
15253
|
-
const worktreePath = usesGeneratedDetachedName ? await resolveUniqueDetachedWorktreePath(
|
|
15496
|
+
const worktreePath = usesGeneratedDetachedName ? await resolveUniqueDetachedWorktreePath(
|
|
15497
|
+
repository.repoRoot,
|
|
15498
|
+
worktreeName,
|
|
15499
|
+
configuredBasePath
|
|
15500
|
+
) : resolveWorktreePath(
|
|
15501
|
+
repository.repoRoot,
|
|
15502
|
+
worktreeName,
|
|
15503
|
+
configuredBasePath
|
|
15504
|
+
);
|
|
15254
15505
|
if (!usesGeneratedDetachedName && await pathExists(worktreePath)) {
|
|
15255
15506
|
if (options.force) {
|
|
15256
15507
|
if (!options.dryRun) {
|
|
15257
15508
|
try {
|
|
15258
|
-
await execFileAsync3(
|
|
15509
|
+
await execFileAsync3(
|
|
15510
|
+
"git",
|
|
15511
|
+
["worktree", "remove", "--force", worktreePath],
|
|
15512
|
+
{ cwd: repository.repoRoot }
|
|
15513
|
+
);
|
|
15259
15514
|
} catch (err) {
|
|
15260
15515
|
if (!isNotRegisteredWorktreeError(err)) {
|
|
15261
15516
|
const msg = `could not remove existing worktree at ${worktreePath}: ${toExecMessage(err)}`;
|
|
15262
15517
|
if (options.json) {
|
|
15263
|
-
options.stderr(
|
|
15264
|
-
|
|
15518
|
+
options.stderr(
|
|
15519
|
+
`${JSON.stringify({ warning: msg }, null, 2)}
|
|
15520
|
+
`
|
|
15521
|
+
);
|
|
15265
15522
|
} else {
|
|
15266
15523
|
options.stderr(`Warning: ${msg}
|
|
15267
15524
|
`);
|
|
@@ -15270,7 +15527,9 @@ function createNewCommand(dependencies = {}) {
|
|
|
15270
15527
|
}
|
|
15271
15528
|
if (!options.detached) {
|
|
15272
15529
|
try {
|
|
15273
|
-
await execFileAsync3("git", ["branch", "-D", worktreeName], {
|
|
15530
|
+
await execFileAsync3("git", ["branch", "-D", worktreeName], {
|
|
15531
|
+
cwd: repository.repoRoot
|
|
15532
|
+
});
|
|
15274
15533
|
} catch {
|
|
15275
15534
|
}
|
|
15276
15535
|
}
|
|
@@ -15281,12 +15540,18 @@ function createNewCommand(dependencies = {}) {
|
|
|
15281
15540
|
options.stderr(`${JSON.stringify({ error: message }, null, 2)}
|
|
15282
15541
|
`);
|
|
15283
15542
|
} else {
|
|
15284
|
-
options.stderr(
|
|
15285
|
-
`)
|
|
15286
|
-
|
|
15287
|
-
|
|
15288
|
-
options.stderr(
|
|
15289
|
-
`
|
|
15543
|
+
options.stderr(
|
|
15544
|
+
`gji new: ${message} in non-interactive mode (GJI_NO_TUI=1)
|
|
15545
|
+
`
|
|
15546
|
+
);
|
|
15547
|
+
options.stderr(
|
|
15548
|
+
`Hint: Use 'gji remove ${worktreeName}' or 'gji clean' to remove the existing worktree
|
|
15549
|
+
`
|
|
15550
|
+
);
|
|
15551
|
+
options.stderr(
|
|
15552
|
+
`Hint: Use 'gji trigger-hook afterCreate' inside the worktree to re-run setup hooks
|
|
15553
|
+
`
|
|
15554
|
+
);
|
|
15290
15555
|
}
|
|
15291
15556
|
return 1;
|
|
15292
15557
|
} else {
|
|
@@ -15296,53 +15561,81 @@ function createNewCommand(dependencies = {}) {
|
|
|
15296
15561
|
await writeOutput(worktreePath, options.stdout);
|
|
15297
15562
|
return 0;
|
|
15298
15563
|
}
|
|
15299
|
-
options.stderr(
|
|
15300
|
-
`
|
|
15564
|
+
options.stderr(
|
|
15565
|
+
`Aborted because target worktree path already exists: ${worktreePath}
|
|
15566
|
+
`
|
|
15567
|
+
);
|
|
15301
15568
|
return 1;
|
|
15302
15569
|
}
|
|
15303
15570
|
}
|
|
15304
15571
|
if (options.dryRun) {
|
|
15305
15572
|
if (options.json) {
|
|
15306
|
-
options.stdout(
|
|
15307
|
-
|
|
15573
|
+
options.stdout(
|
|
15574
|
+
`${JSON.stringify({ branch: worktreeName, path: worktreePath, dryRun: true }, null, 2)}
|
|
15575
|
+
`
|
|
15576
|
+
);
|
|
15308
15577
|
} else {
|
|
15309
15578
|
const resolvedEditor = options.open ? options.editor ?? resolveConfigString(config, "editor") : void 0;
|
|
15310
15579
|
const openNote = resolvedEditor ? `, then open in ${resolvedEditor}` : "";
|
|
15311
|
-
options.stdout(
|
|
15312
|
-
`)
|
|
15580
|
+
options.stdout(
|
|
15581
|
+
`Would create worktree at ${worktreePath} (branch: ${worktreeName}${openNote})
|
|
15582
|
+
`
|
|
15583
|
+
);
|
|
15313
15584
|
}
|
|
15314
15585
|
return 0;
|
|
15315
15586
|
}
|
|
15316
15587
|
await mkdir4(dirname5(worktreePath), { recursive: true });
|
|
15317
15588
|
const gitArgs = options.detached ? ["worktree", "add", "--detach", worktreePath] : await localBranchExists(repository.repoRoot, worktreeName) ? ["worktree", "add", worktreePath, worktreeName] : ["worktree", "add", "-b", worktreeName, worktreePath];
|
|
15318
15589
|
await execFileAsync3("git", gitArgs, { cwd: repository.repoRoot });
|
|
15319
|
-
const syncPatterns = Array.isArray(config.syncFiles) ? config.syncFiles.filter(
|
|
15590
|
+
const syncPatterns = Array.isArray(config.syncFiles) ? config.syncFiles.filter(
|
|
15591
|
+
(p2) => typeof p2 === "string"
|
|
15592
|
+
) : [];
|
|
15320
15593
|
for (const pattern of syncPatterns) {
|
|
15321
15594
|
try {
|
|
15322
15595
|
await syncFiles(repository.repoRoot, worktreePath, [pattern]);
|
|
15323
15596
|
} catch (error) {
|
|
15324
|
-
options.stderr(
|
|
15325
|
-
`)
|
|
15597
|
+
options.stderr(
|
|
15598
|
+
`Warning: failed to sync file "${pattern}": ${error instanceof Error ? error.message : String(error)}
|
|
15599
|
+
`
|
|
15600
|
+
);
|
|
15326
15601
|
}
|
|
15327
15602
|
}
|
|
15328
|
-
await maybeRunInstallPrompt(
|
|
15603
|
+
await maybeRunInstallPrompt(
|
|
15604
|
+
worktreePath,
|
|
15605
|
+
repository.repoRoot,
|
|
15606
|
+
config,
|
|
15607
|
+
options.stderr,
|
|
15608
|
+
dependencies,
|
|
15609
|
+
!!options.json
|
|
15610
|
+
);
|
|
15329
15611
|
const hooks = extractHooks(config);
|
|
15330
15612
|
await runHook(
|
|
15331
15613
|
hooks.afterCreate,
|
|
15332
15614
|
worktreePath,
|
|
15333
|
-
{
|
|
15615
|
+
{
|
|
15616
|
+
branch: worktreeName,
|
|
15617
|
+
path: worktreePath,
|
|
15618
|
+
repo: basename3(repository.repoRoot)
|
|
15619
|
+
},
|
|
15334
15620
|
options.stderr
|
|
15335
15621
|
);
|
|
15336
15622
|
if (options.json) {
|
|
15337
|
-
options.stdout(
|
|
15338
|
-
|
|
15623
|
+
options.stdout(
|
|
15624
|
+
`${JSON.stringify({ branch: worktreeName, path: worktreePath }, null, 2)}
|
|
15625
|
+
`
|
|
15626
|
+
);
|
|
15339
15627
|
} else {
|
|
15340
15628
|
await appendHistory(worktreePath, worktreeName);
|
|
15341
15629
|
await writeOutput(worktreePath, options.stdout);
|
|
15342
15630
|
}
|
|
15343
15631
|
if (options.open) {
|
|
15344
15632
|
const resolvedEditor = options.editor ?? resolveConfigString(config, "editor");
|
|
15345
|
-
await openWorktree(
|
|
15633
|
+
await openWorktree(
|
|
15634
|
+
worktreePath,
|
|
15635
|
+
resolvedEditor,
|
|
15636
|
+
spawnEditor,
|
|
15637
|
+
options.stderr
|
|
15638
|
+
);
|
|
15346
15639
|
}
|
|
15347
15640
|
return 0;
|
|
15348
15641
|
};
|
|
@@ -15403,7 +15696,11 @@ async function resolveUniqueDetachedWorktreePath(repoRoot, baseName, basePath) {
|
|
|
15403
15696
|
let attempt = 1;
|
|
15404
15697
|
while (true) {
|
|
15405
15698
|
const candidateName = attempt === 1 ? baseName : `${baseName}-${attempt}`;
|
|
15406
|
-
const candidatePath = resolveWorktreePath(
|
|
15699
|
+
const candidatePath = resolveWorktreePath(
|
|
15700
|
+
repoRoot,
|
|
15701
|
+
candidateName,
|
|
15702
|
+
basePath
|
|
15703
|
+
);
|
|
15407
15704
|
if (!await pathExists(candidatePath)) {
|
|
15408
15705
|
return candidatePath;
|
|
15409
15706
|
}
|
|
@@ -15431,7 +15728,11 @@ function pickRandom(values, random) {
|
|
|
15431
15728
|
}
|
|
15432
15729
|
async function localBranchExists(repoRoot, branchName) {
|
|
15433
15730
|
try {
|
|
15434
|
-
await execFileAsync3(
|
|
15731
|
+
await execFileAsync3(
|
|
15732
|
+
"git",
|
|
15733
|
+
["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`],
|
|
15734
|
+
{ cwd: repoRoot }
|
|
15735
|
+
);
|
|
15435
15736
|
return true;
|
|
15436
15737
|
} catch {
|
|
15437
15738
|
return false;
|
|
@@ -15452,7 +15753,9 @@ function toExecMessage(error) {
|
|
|
15452
15753
|
}
|
|
15453
15754
|
async function openWorktree(worktreePath, editorCli, spawnFn, stderr) {
|
|
15454
15755
|
if (!editorCli) {
|
|
15455
|
-
stderr(
|
|
15756
|
+
stderr(
|
|
15757
|
+
"gji new: --open requires --editor <cli> or a saved editor in config\n"
|
|
15758
|
+
);
|
|
15456
15759
|
return;
|
|
15457
15760
|
}
|
|
15458
15761
|
const editorDef = EDITORS.find((e2) => e2.cli === editorCli);
|
|
@@ -15566,11 +15869,17 @@ async function runWarpNavigate(options) {
|
|
|
15566
15869
|
}
|
|
15567
15870
|
return 1;
|
|
15568
15871
|
}
|
|
15569
|
-
const target = await resolveWarpTarget({
|
|
15872
|
+
const target = await resolveWarpTarget({
|
|
15873
|
+
...options,
|
|
15874
|
+
commandName: "gji warp",
|
|
15875
|
+
json: options.json
|
|
15876
|
+
});
|
|
15570
15877
|
if (!target) return 1;
|
|
15571
15878
|
if (options.json) {
|
|
15572
|
-
options.stdout(
|
|
15573
|
-
|
|
15879
|
+
options.stdout(
|
|
15880
|
+
`${JSON.stringify({ branch: target.branch, path: target.path }, null, 2)}
|
|
15881
|
+
`
|
|
15882
|
+
);
|
|
15574
15883
|
return 0;
|
|
15575
15884
|
}
|
|
15576
15885
|
appendHistory(target.path, target.branch).catch(() => void 0);
|
|
@@ -15771,14 +16080,19 @@ function createGoCommand(dependencies = {}) {
|
|
|
15771
16080
|
);
|
|
15772
16081
|
return 1;
|
|
15773
16082
|
}
|
|
15774
|
-
const target = await resolveWarpTarget({
|
|
16083
|
+
const target = await resolveWarpTarget({
|
|
16084
|
+
...options,
|
|
16085
|
+
commandName: "gji go"
|
|
16086
|
+
});
|
|
15775
16087
|
if (!target) return 1;
|
|
15776
16088
|
appendHistory(target.path, target.branch).catch(() => void 0);
|
|
15777
16089
|
await writeShellOutput(GO_OUTPUT_FILE_ENV, target.path, options.stdout);
|
|
15778
16090
|
return 0;
|
|
15779
16091
|
}
|
|
15780
16092
|
if (!options.branch && isHeadless()) {
|
|
15781
|
-
options.stderr(
|
|
16093
|
+
options.stderr(
|
|
16094
|
+
"gji go: branch argument is required in non-interactive mode (GJI_NO_TUI=1)\n"
|
|
16095
|
+
);
|
|
15782
16096
|
return 1;
|
|
15783
16097
|
}
|
|
15784
16098
|
const prompted = options.branch ? null : await prompt(sortByCurrentFirst(worktrees));
|
|
@@ -15794,204 +16108,83 @@ function createGoCommand(dependencies = {}) {
|
|
|
15794
16108
|
}
|
|
15795
16109
|
return 1;
|
|
15796
16110
|
}
|
|
15797
|
-
const chosenWorktree = worktrees.find((w2) => w2.path === resolvedPath);
|
|
15798
|
-
const config = await loadEffectiveConfig(
|
|
15799
|
-
|
|
15800
|
-
|
|
15801
|
-
|
|
15802
|
-
|
|
15803
|
-
|
|
15804
|
-
|
|
15805
|
-
|
|
15806
|
-
|
|
15807
|
-
|
|
15808
|
-
|
|
15809
|
-
|
|
15810
|
-
|
|
15811
|
-
|
|
15812
|
-
|
|
15813
|
-
|
|
15814
|
-
|
|
15815
|
-
|
|
15816
|
-
|
|
15817
|
-
|
|
15818
|
-
options: worktrees.map((worktree, i) => {
|
|
15819
|
-
const health = healthResults[i].status === "fulfilled" ? healthResults[i].value : null;
|
|
15820
|
-
const pathHint = worktree.isCurrent ? `${worktree.path} (current)` : worktree.path;
|
|
15821
|
-
const upstream = health ? formatUpstreamHint(worktree.branch, health) : null;
|
|
15822
|
-
return {
|
|
15823
|
-
value: worktree.path,
|
|
15824
|
-
label: worktree.branch ?? "(detached)",
|
|
15825
|
-
hint: upstream ? `${upstream} \xB7 ${pathHint}` : pathHint
|
|
15826
|
-
};
|
|
15827
|
-
})
|
|
15828
|
-
});
|
|
15829
|
-
if (pD(choice)) {
|
|
15830
|
-
return null;
|
|
15831
|
-
}
|
|
15832
|
-
return choice;
|
|
15833
|
-
}
|
|
15834
|
-
function formatUpstreamHint(branch, health) {
|
|
15835
|
-
if (branch === null) return null;
|
|
15836
|
-
if (!health.hasUpstream) return "no upstream";
|
|
15837
|
-
if (health.upstreamGone) return "upstream gone";
|
|
15838
|
-
if (health.ahead === 0 && health.behind === 0) return "up to date";
|
|
15839
|
-
if (health.ahead === 0) return `behind ${health.behind}`;
|
|
15840
|
-
if (health.behind === 0) return `ahead ${health.ahead}`;
|
|
15841
|
-
return `ahead ${health.ahead}, behind ${health.behind}`;
|
|
15842
|
-
}
|
|
15843
|
-
|
|
15844
|
-
// src/open.ts
|
|
15845
|
-
import { execFile as execFile4 } from "node:child_process";
|
|
15846
|
-
import { access as access4, writeFile as writeFile5 } from "node:fs/promises";
|
|
15847
|
-
import { join as join7 } from "node:path";
|
|
15848
|
-
import { promisify as promisify5 } from "node:util";
|
|
15849
|
-
var execFileAsync4 = promisify5(execFile4);
|
|
15850
|
-
function createOpenCommand(dependencies = {}) {
|
|
15851
|
-
const detectEditors = dependencies.detectEditors ?? detectInstalledEditors;
|
|
15852
|
-
const promptForEditor = dependencies.promptForEditor ?? defaultPromptForEditor;
|
|
15853
|
-
const promptForWorktree2 = dependencies.promptForWorktree ?? defaultPromptForWorktree;
|
|
15854
|
-
const spawnEditor = dependencies.spawnEditor ?? defaultSpawnEditor;
|
|
15855
|
-
return async function runOpenCommand2(options) {
|
|
15856
|
-
const [worktrees, repository] = await Promise.all([
|
|
15857
|
-
listWorktrees(options.cwd),
|
|
15858
|
-
detectRepository(options.cwd)
|
|
15859
|
-
]);
|
|
15860
|
-
let targetPath;
|
|
15861
|
-
if (options.branch) {
|
|
15862
|
-
const entry = worktrees.find((w2) => w2.branch === options.branch);
|
|
15863
|
-
if (!entry) {
|
|
15864
|
-
options.stderr(`gji open: no worktree found for branch: ${options.branch}
|
|
15865
|
-
`);
|
|
15866
|
-
options.stderr(`Hint: Use 'gji ls' to see available worktrees
|
|
15867
|
-
`);
|
|
15868
|
-
return 1;
|
|
15869
|
-
}
|
|
15870
|
-
targetPath = entry.path;
|
|
15871
|
-
} else if (isHeadless()) {
|
|
15872
|
-
targetPath = worktrees.find((w2) => w2.isCurrent)?.path ?? options.cwd;
|
|
15873
|
-
} else {
|
|
15874
|
-
const chosen = await promptForWorktree2(sortByCurrentFirst(worktrees));
|
|
15875
|
-
if (!chosen) {
|
|
15876
|
-
options.stderr("Aborted\n");
|
|
15877
|
-
return 1;
|
|
15878
|
-
}
|
|
15879
|
-
targetPath = chosen;
|
|
15880
|
-
}
|
|
15881
|
-
const config = await loadEffectiveConfig(repository.repoRoot, void 0, options.stderr);
|
|
15882
|
-
const savedEditor = resolveConfigString(config, "editor");
|
|
15883
|
-
let editorCli;
|
|
15884
|
-
if (options.editor) {
|
|
15885
|
-
editorCli = options.editor;
|
|
15886
|
-
} else if (savedEditor) {
|
|
15887
|
-
editorCli = savedEditor;
|
|
15888
|
-
} else {
|
|
15889
|
-
const installed = await detectEditors();
|
|
15890
|
-
if (installed.length === 0) {
|
|
15891
|
-
options.stderr(
|
|
15892
|
-
"gji open: no supported editor detected. Use --editor <code|cursor|zed|...> to specify one.\n"
|
|
15893
|
-
);
|
|
15894
|
-
return 1;
|
|
15895
|
-
}
|
|
15896
|
-
if (installed.length === 1 || isHeadless()) {
|
|
15897
|
-
editorCli = installed[0].cli;
|
|
15898
|
-
} else {
|
|
15899
|
-
const chosen = await promptForEditor(installed);
|
|
15900
|
-
if (!chosen) {
|
|
15901
|
-
options.stderr("Aborted\n");
|
|
15902
|
-
return 1;
|
|
15903
|
-
}
|
|
15904
|
-
editorCli = chosen;
|
|
15905
|
-
}
|
|
15906
|
-
}
|
|
15907
|
-
if (options.save && editorCli !== savedEditor) {
|
|
15908
|
-
await updateGlobalConfigKey("editor", editorCli);
|
|
15909
|
-
const displayName2 = EDITORS.find((e2) => e2.cli === editorCli)?.name ?? editorCli;
|
|
15910
|
-
options.stdout(`Saved editor "${displayName2}" to global config
|
|
15911
|
-
`);
|
|
15912
|
-
}
|
|
15913
|
-
const editorDef = EDITORS.find((e2) => e2.cli === editorCli);
|
|
15914
|
-
let openTarget = targetPath;
|
|
15915
|
-
if (options.workspace) {
|
|
15916
|
-
if (editorDef?.supportsWorkspace) {
|
|
15917
|
-
openTarget = await ensureWorkspaceFile(targetPath, repository.repoName);
|
|
15918
|
-
} else {
|
|
15919
|
-
const displayName2 = editorDef?.name ?? editorCli;
|
|
15920
|
-
options.stderr(`gji open: --workspace is not supported for ${displayName2}, ignoring
|
|
15921
|
-
`);
|
|
15922
|
-
}
|
|
15923
|
-
}
|
|
15924
|
-
const args = [];
|
|
15925
|
-
if (editorDef?.newWindowFlag) {
|
|
15926
|
-
args.push(editorDef.newWindowFlag);
|
|
15927
|
-
}
|
|
15928
|
-
args.push(openTarget);
|
|
15929
|
-
try {
|
|
15930
|
-
await spawnEditor(editorCli, args);
|
|
15931
|
-
} catch (error) {
|
|
15932
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
15933
|
-
options.stderr(`gji open: failed to launch editor: ${message}
|
|
15934
|
-
`);
|
|
15935
|
-
return 1;
|
|
15936
|
-
}
|
|
15937
|
-
const displayName = editorDef?.name ?? editorCli;
|
|
15938
|
-
options.stdout(`Opened ${targetPath} in ${displayName}
|
|
15939
|
-
`);
|
|
16111
|
+
const chosenWorktree = worktrees.find((w2) => w2.path === resolvedPath);
|
|
16112
|
+
const config = await loadEffectiveConfig(
|
|
16113
|
+
repository.repoRoot,
|
|
16114
|
+
void 0,
|
|
16115
|
+
options.stderr
|
|
16116
|
+
);
|
|
16117
|
+
const hooks = extractHooks(config);
|
|
16118
|
+
await runHook(
|
|
16119
|
+
hooks.afterEnter,
|
|
16120
|
+
resolvedPath,
|
|
16121
|
+
{
|
|
16122
|
+
branch: chosenWorktree?.branch ?? void 0,
|
|
16123
|
+
path: resolvedPath,
|
|
16124
|
+
repo: basename6(repository.repoRoot)
|
|
16125
|
+
},
|
|
16126
|
+
options.stderr
|
|
16127
|
+
);
|
|
16128
|
+
appendHistory(resolvedPath, chosenWorktree?.branch ?? null).catch(
|
|
16129
|
+
() => void 0
|
|
16130
|
+
);
|
|
16131
|
+
await writeShellOutput(GO_OUTPUT_FILE_ENV, resolvedPath, options.stdout);
|
|
15940
16132
|
return 0;
|
|
15941
16133
|
};
|
|
15942
16134
|
}
|
|
15943
|
-
var
|
|
15944
|
-
async function
|
|
15945
|
-
const
|
|
15946
|
-
|
|
16135
|
+
var runGoCommand = createGoCommand();
|
|
16136
|
+
async function promptForWorktree(worktrees) {
|
|
16137
|
+
const healthResults = await Promise.allSettled(
|
|
16138
|
+
worktrees.map((w2) => readWorktreeHealth(w2.path))
|
|
15947
16139
|
);
|
|
15948
|
-
return results.filter((r2) => r2.available).map((r2) => r2.editor);
|
|
15949
|
-
}
|
|
15950
|
-
async function isCommandAvailable(command) {
|
|
15951
|
-
try {
|
|
15952
|
-
await execFileAsync4("which", [command]);
|
|
15953
|
-
return true;
|
|
15954
|
-
} catch {
|
|
15955
|
-
return false;
|
|
15956
|
-
}
|
|
15957
|
-
}
|
|
15958
|
-
async function defaultPromptForWorktree(worktrees) {
|
|
15959
16140
|
const choice = await ve({
|
|
15960
|
-
message: "Choose a worktree
|
|
15961
|
-
options: worktrees.map((
|
|
15962
|
-
value:
|
|
15963
|
-
|
|
15964
|
-
|
|
15965
|
-
|
|
16141
|
+
message: "Choose a worktree",
|
|
16142
|
+
options: worktrees.map((worktree, i) => {
|
|
16143
|
+
const health = healthResults[i].status === "fulfilled" ? healthResults[i].value : null;
|
|
16144
|
+
const pathHint = worktree.isCurrent ? `${worktree.path} (current)` : worktree.path;
|
|
16145
|
+
const upstream = health ? formatUpstreamHint(worktree.branch, health) : null;
|
|
16146
|
+
return {
|
|
16147
|
+
value: worktree.path,
|
|
16148
|
+
label: worktree.branch ?? "(detached)",
|
|
16149
|
+
hint: upstream ? `${upstream} \xB7 ${pathHint}` : pathHint
|
|
16150
|
+
};
|
|
16151
|
+
})
|
|
15966
16152
|
});
|
|
15967
|
-
if (pD(choice))
|
|
16153
|
+
if (pD(choice)) {
|
|
16154
|
+
return null;
|
|
16155
|
+
}
|
|
15968
16156
|
return choice;
|
|
15969
16157
|
}
|
|
15970
|
-
|
|
15971
|
-
|
|
15972
|
-
|
|
15973
|
-
|
|
15974
|
-
|
|
15975
|
-
if (
|
|
15976
|
-
return
|
|
16158
|
+
function formatUpstreamHint(branch, health) {
|
|
16159
|
+
if (branch === null) return null;
|
|
16160
|
+
if (!health.hasUpstream) return "no upstream";
|
|
16161
|
+
if (health.upstreamGone) return "upstream gone";
|
|
16162
|
+
if (health.ahead === 0 && health.behind === 0) return "up to date";
|
|
16163
|
+
if (health.ahead === 0) return `behind ${health.behind}`;
|
|
16164
|
+
if (health.behind === 0) return `ahead ${health.ahead}`;
|
|
16165
|
+
return `ahead ${health.ahead}, behind ${health.behind}`;
|
|
15977
16166
|
}
|
|
15978
|
-
|
|
15979
|
-
|
|
15980
|
-
|
|
15981
|
-
|
|
15982
|
-
|
|
15983
|
-
|
|
16167
|
+
|
|
16168
|
+
// src/history-command.ts
|
|
16169
|
+
async function runHistoryCommand(options) {
|
|
16170
|
+
const history = await loadHistory(options.home);
|
|
16171
|
+
if (options.json) {
|
|
16172
|
+
options.stdout(`${JSON.stringify(history, null, 2)}
|
|
16173
|
+
`);
|
|
16174
|
+
return 0;
|
|
15984
16175
|
}
|
|
15985
|
-
|
|
15986
|
-
|
|
15987
|
-
|
|
15988
|
-
|
|
16176
|
+
if (history.length === 0) {
|
|
16177
|
+
options.stdout("No navigation history.\n");
|
|
16178
|
+
return 0;
|
|
16179
|
+
}
|
|
16180
|
+
options.stdout(formatHistoryList(history, options.cwd));
|
|
16181
|
+
return 0;
|
|
15989
16182
|
}
|
|
15990
16183
|
|
|
15991
16184
|
// src/init.ts
|
|
15992
|
-
import { mkdir as mkdir6, readFile as readFile4, writeFile as
|
|
16185
|
+
import { mkdir as mkdir6, readFile as readFile4, writeFile as writeFile5 } from "node:fs/promises";
|
|
15993
16186
|
import { homedir as homedir5 } from "node:os";
|
|
15994
|
-
import { dirname as dirname7, join as
|
|
16187
|
+
import { dirname as dirname7, join as join7 } from "node:path";
|
|
15995
16188
|
var START_MARKER = "# >>> gji init >>>";
|
|
15996
16189
|
var END_MARKER = "# <<< gji init <<<";
|
|
15997
16190
|
var SHELL_WRAPPED_COMMANDS = [
|
|
@@ -16063,7 +16256,7 @@ async function runInitCommand(options) {
|
|
|
16063
16256
|
await mkdir6(dirname7(rcPath), { recursive: true });
|
|
16064
16257
|
const current = await readExistingConfig(rcPath);
|
|
16065
16258
|
const next = upsertShellIntegration(current, script);
|
|
16066
|
-
await
|
|
16259
|
+
await writeFile5(rcPath, next, "utf8");
|
|
16067
16260
|
options.stdout(`${rcPath}
|
|
16068
16261
|
`);
|
|
16069
16262
|
const { config: globalConfig } = await loadGlobalConfig(home);
|
|
@@ -16074,7 +16267,11 @@ async function runInitCommand(options) {
|
|
|
16074
16267
|
const prompt = options.promptForSetup ?? defaultPromptForSetup;
|
|
16075
16268
|
const result = await prompt();
|
|
16076
16269
|
if (result) {
|
|
16077
|
-
await updateGlobalConfigKey(
|
|
16270
|
+
await updateGlobalConfigKey(
|
|
16271
|
+
"installSaveTarget",
|
|
16272
|
+
result.installSaveTarget,
|
|
16273
|
+
home
|
|
16274
|
+
);
|
|
16078
16275
|
await saveWizardConfig(result, options.cwd, home);
|
|
16079
16276
|
}
|
|
16080
16277
|
}
|
|
@@ -16134,7 +16331,8 @@ async function saveWizardConfig(result, cwd, home) {
|
|
|
16134
16331
|
const hooks = {};
|
|
16135
16332
|
if (result.hooks?.afterCreate) hooks.afterCreate = result.hooks.afterCreate;
|
|
16136
16333
|
if (result.hooks?.afterEnter) hooks.afterEnter = result.hooks.afterEnter;
|
|
16137
|
-
if (result.hooks?.beforeRemove)
|
|
16334
|
+
if (result.hooks?.beforeRemove)
|
|
16335
|
+
hooks.beforeRemove = result.hooks.beforeRemove;
|
|
16138
16336
|
if (Object.keys(hooks).length > 0) values.hooks = hooks;
|
|
16139
16337
|
if (Object.keys(values).length === 0) return;
|
|
16140
16338
|
if (result.installSaveTarget === "local") {
|
|
@@ -16148,11 +16346,11 @@ async function saveWizardConfig(result, cwd, home) {
|
|
|
16148
16346
|
function resolveShellConfigPath(shell, home) {
|
|
16149
16347
|
switch (shell) {
|
|
16150
16348
|
case "bash":
|
|
16151
|
-
return
|
|
16349
|
+
return join7(home, ".bashrc");
|
|
16152
16350
|
case "fish":
|
|
16153
|
-
return
|
|
16351
|
+
return join7(home, ".config", "fish", "config.fish");
|
|
16154
16352
|
case "zsh":
|
|
16155
|
-
return
|
|
16353
|
+
return join7(home, ".zshrc");
|
|
16156
16354
|
}
|
|
16157
16355
|
}
|
|
16158
16356
|
async function readExistingConfig(path9) {
|
|
@@ -16178,7 +16376,9 @@ function isMissingFileError2(error) {
|
|
|
16178
16376
|
function renderFishWrapper(command) {
|
|
16179
16377
|
const nameTests = command.names.map((name) => `test $argv[1] = ${name}`);
|
|
16180
16378
|
const nameCondition = nameTests.length === 1 ? nameTests[0] : `begin; ${nameTests.join("; or ")}; end`;
|
|
16181
|
-
const bypassTests = command.bypassOptions.map(
|
|
16379
|
+
const bypassTests = command.bypassOptions.map(
|
|
16380
|
+
(opt) => `test $argv[1] = ${opt}`
|
|
16381
|
+
);
|
|
16182
16382
|
const bypassCondition = bypassTests.length === 1 ? bypassTests[0] : `begin; ${bypassTests.join("; or ")}; end`;
|
|
16183
16383
|
return `if test (count $argv) -gt 0; and ${nameCondition}
|
|
16184
16384
|
set -e argv[1]
|
|
@@ -16230,8 +16430,16 @@ async function defaultPromptForSetup() {
|
|
|
16230
16430
|
const installSaveTarget = await ve({
|
|
16231
16431
|
message: "Where should preferences be saved?",
|
|
16232
16432
|
options: [
|
|
16233
|
-
{
|
|
16234
|
-
|
|
16433
|
+
{
|
|
16434
|
+
value: "global",
|
|
16435
|
+
label: "~/.config/gji/config.json",
|
|
16436
|
+
hint: "personal \u2014 never committed"
|
|
16437
|
+
},
|
|
16438
|
+
{
|
|
16439
|
+
value: "local",
|
|
16440
|
+
label: ".gji.json",
|
|
16441
|
+
hint: "repo \u2014 committed with the project"
|
|
16442
|
+
}
|
|
16235
16443
|
]
|
|
16236
16444
|
});
|
|
16237
16445
|
if (pD(installSaveTarget)) {
|
|
@@ -16334,10 +16542,22 @@ function formatDetailedWorktreeTable(worktrees) {
|
|
|
16334
16542
|
status: worktree.status,
|
|
16335
16543
|
upstream: formatUpstreamState(worktree.upstream)
|
|
16336
16544
|
}));
|
|
16337
|
-
const branchWidth = Math.max(
|
|
16338
|
-
|
|
16339
|
-
|
|
16340
|
-
|
|
16545
|
+
const branchWidth = Math.max(
|
|
16546
|
+
"BRANCH".length,
|
|
16547
|
+
...rows.map((row) => row.branch.length)
|
|
16548
|
+
);
|
|
16549
|
+
const statusWidth = Math.max(
|
|
16550
|
+
"STATUS".length,
|
|
16551
|
+
...rows.map((row) => row.status.length)
|
|
16552
|
+
);
|
|
16553
|
+
const upstreamWidth = Math.max(
|
|
16554
|
+
"UPSTREAM".length,
|
|
16555
|
+
...rows.map((row) => row.upstream.length)
|
|
16556
|
+
);
|
|
16557
|
+
const lastCommitWidth = Math.max(
|
|
16558
|
+
"LAST".length,
|
|
16559
|
+
...rows.map((row) => row.lastCommit.length)
|
|
16560
|
+
);
|
|
16341
16561
|
const lines = [
|
|
16342
16562
|
" " + "BRANCH".padEnd(branchWidth, " ") + " " + "STATUS".padEnd(statusWidth, " ") + " " + "UPSTREAM".padEnd(upstreamWidth, " ") + " " + "LAST".padEnd(lastCommitWidth, " ") + " PATH"
|
|
16343
16563
|
];
|
|
@@ -16360,7 +16580,9 @@ function formatWorktreeTable(worktrees) {
|
|
|
16360
16580
|
);
|
|
16361
16581
|
const lines = [" " + "BRANCH".padEnd(branchWidth, " ") + " PATH"];
|
|
16362
16582
|
for (const row of rows) {
|
|
16363
|
-
lines.push(
|
|
16583
|
+
lines.push(
|
|
16584
|
+
`${row.isCurrent ? "*" : " "} ${row.branch.padEnd(branchWidth, " ")} ${row.path}`
|
|
16585
|
+
);
|
|
16364
16586
|
}
|
|
16365
16587
|
return lines.join("\n");
|
|
16366
16588
|
}
|
|
@@ -16372,10 +16594,172 @@ function sortWorktrees(worktrees) {
|
|
|
16372
16594
|
});
|
|
16373
16595
|
}
|
|
16374
16596
|
|
|
16597
|
+
// src/open.ts
|
|
16598
|
+
import { execFile as execFile4 } from "node:child_process";
|
|
16599
|
+
import { access as access4, writeFile as writeFile6 } from "node:fs/promises";
|
|
16600
|
+
import { join as join8 } from "node:path";
|
|
16601
|
+
import { promisify as promisify5 } from "node:util";
|
|
16602
|
+
var execFileAsync4 = promisify5(execFile4);
|
|
16603
|
+
function createOpenCommand(dependencies = {}) {
|
|
16604
|
+
const detectEditors = dependencies.detectEditors ?? detectInstalledEditors;
|
|
16605
|
+
const promptForEditor = dependencies.promptForEditor ?? defaultPromptForEditor;
|
|
16606
|
+
const promptForWorktree2 = dependencies.promptForWorktree ?? defaultPromptForWorktree;
|
|
16607
|
+
const spawnEditor = dependencies.spawnEditor ?? defaultSpawnEditor;
|
|
16608
|
+
return async function runOpenCommand2(options) {
|
|
16609
|
+
const [worktrees, repository] = await Promise.all([
|
|
16610
|
+
listWorktrees(options.cwd),
|
|
16611
|
+
detectRepository(options.cwd)
|
|
16612
|
+
]);
|
|
16613
|
+
let targetPath;
|
|
16614
|
+
if (options.branch) {
|
|
16615
|
+
const entry = worktrees.find((w2) => w2.branch === options.branch);
|
|
16616
|
+
if (!entry) {
|
|
16617
|
+
options.stderr(
|
|
16618
|
+
`gji open: no worktree found for branch: ${options.branch}
|
|
16619
|
+
`
|
|
16620
|
+
);
|
|
16621
|
+
options.stderr(`Hint: Use 'gji ls' to see available worktrees
|
|
16622
|
+
`);
|
|
16623
|
+
return 1;
|
|
16624
|
+
}
|
|
16625
|
+
targetPath = entry.path;
|
|
16626
|
+
} else if (isHeadless()) {
|
|
16627
|
+
targetPath = worktrees.find((w2) => w2.isCurrent)?.path ?? options.cwd;
|
|
16628
|
+
} else {
|
|
16629
|
+
const chosen = await promptForWorktree2(sortByCurrentFirst(worktrees));
|
|
16630
|
+
if (!chosen) {
|
|
16631
|
+
options.stderr("Aborted\n");
|
|
16632
|
+
return 1;
|
|
16633
|
+
}
|
|
16634
|
+
targetPath = chosen;
|
|
16635
|
+
}
|
|
16636
|
+
const config = await loadEffectiveConfig(
|
|
16637
|
+
repository.repoRoot,
|
|
16638
|
+
void 0,
|
|
16639
|
+
options.stderr
|
|
16640
|
+
);
|
|
16641
|
+
const savedEditor = resolveConfigString(config, "editor");
|
|
16642
|
+
let editorCli;
|
|
16643
|
+
if (options.editor) {
|
|
16644
|
+
editorCli = options.editor;
|
|
16645
|
+
} else if (savedEditor) {
|
|
16646
|
+
editorCli = savedEditor;
|
|
16647
|
+
} else {
|
|
16648
|
+
const installed = await detectEditors();
|
|
16649
|
+
if (installed.length === 0) {
|
|
16650
|
+
options.stderr(
|
|
16651
|
+
"gji open: no supported editor detected. Use --editor <code|cursor|zed|...> to specify one.\n"
|
|
16652
|
+
);
|
|
16653
|
+
return 1;
|
|
16654
|
+
}
|
|
16655
|
+
if (installed.length === 1 || isHeadless()) {
|
|
16656
|
+
editorCli = installed[0].cli;
|
|
16657
|
+
} else {
|
|
16658
|
+
const chosen = await promptForEditor(installed);
|
|
16659
|
+
if (!chosen) {
|
|
16660
|
+
options.stderr("Aborted\n");
|
|
16661
|
+
return 1;
|
|
16662
|
+
}
|
|
16663
|
+
editorCli = chosen;
|
|
16664
|
+
}
|
|
16665
|
+
}
|
|
16666
|
+
if (options.save && editorCli !== savedEditor) {
|
|
16667
|
+
await updateGlobalConfigKey("editor", editorCli);
|
|
16668
|
+
const displayName2 = EDITORS.find((e2) => e2.cli === editorCli)?.name ?? editorCli;
|
|
16669
|
+
options.stdout(`Saved editor "${displayName2}" to global config
|
|
16670
|
+
`);
|
|
16671
|
+
}
|
|
16672
|
+
const editorDef = EDITORS.find((e2) => e2.cli === editorCli);
|
|
16673
|
+
let openTarget = targetPath;
|
|
16674
|
+
if (options.workspace) {
|
|
16675
|
+
if (editorDef?.supportsWorkspace) {
|
|
16676
|
+
openTarget = await ensureWorkspaceFile(targetPath, repository.repoName);
|
|
16677
|
+
} else {
|
|
16678
|
+
const displayName2 = editorDef?.name ?? editorCli;
|
|
16679
|
+
options.stderr(
|
|
16680
|
+
`gji open: --workspace is not supported for ${displayName2}, ignoring
|
|
16681
|
+
`
|
|
16682
|
+
);
|
|
16683
|
+
}
|
|
16684
|
+
}
|
|
16685
|
+
const args = [];
|
|
16686
|
+
if (editorDef?.newWindowFlag) {
|
|
16687
|
+
args.push(editorDef.newWindowFlag);
|
|
16688
|
+
}
|
|
16689
|
+
args.push(openTarget);
|
|
16690
|
+
try {
|
|
16691
|
+
await spawnEditor(editorCli, args);
|
|
16692
|
+
} catch (error) {
|
|
16693
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
16694
|
+
options.stderr(`gji open: failed to launch editor: ${message}
|
|
16695
|
+
`);
|
|
16696
|
+
return 1;
|
|
16697
|
+
}
|
|
16698
|
+
const displayName = editorDef?.name ?? editorCli;
|
|
16699
|
+
options.stdout(`Opened ${targetPath} in ${displayName}
|
|
16700
|
+
`);
|
|
16701
|
+
return 0;
|
|
16702
|
+
};
|
|
16703
|
+
}
|
|
16704
|
+
var runOpenCommand = createOpenCommand();
|
|
16705
|
+
async function detectInstalledEditors() {
|
|
16706
|
+
const results = await Promise.all(
|
|
16707
|
+
EDITORS.map(async (editor) => ({
|
|
16708
|
+
editor,
|
|
16709
|
+
available: await isCommandAvailable(editor.cli)
|
|
16710
|
+
}))
|
|
16711
|
+
);
|
|
16712
|
+
return results.filter((r2) => r2.available).map((r2) => r2.editor);
|
|
16713
|
+
}
|
|
16714
|
+
async function isCommandAvailable(command) {
|
|
16715
|
+
try {
|
|
16716
|
+
await execFileAsync4("which", [command]);
|
|
16717
|
+
return true;
|
|
16718
|
+
} catch {
|
|
16719
|
+
return false;
|
|
16720
|
+
}
|
|
16721
|
+
}
|
|
16722
|
+
async function defaultPromptForWorktree(worktrees) {
|
|
16723
|
+
const choice = await ve({
|
|
16724
|
+
message: "Choose a worktree to open",
|
|
16725
|
+
options: worktrees.map((w2) => ({
|
|
16726
|
+
value: w2.path,
|
|
16727
|
+
label: w2.branch ?? "(detached)",
|
|
16728
|
+
hint: w2.isCurrent ? `${w2.path} (current)` : w2.path
|
|
16729
|
+
}))
|
|
16730
|
+
});
|
|
16731
|
+
if (pD(choice)) return null;
|
|
16732
|
+
return choice;
|
|
16733
|
+
}
|
|
16734
|
+
async function defaultPromptForEditor(editors) {
|
|
16735
|
+
const choice = await ve({
|
|
16736
|
+
message: "Choose an editor",
|
|
16737
|
+
options: editors.map((e2) => ({ value: e2.cli, label: e2.name }))
|
|
16738
|
+
});
|
|
16739
|
+
if (pD(choice)) return null;
|
|
16740
|
+
return choice;
|
|
16741
|
+
}
|
|
16742
|
+
async function ensureWorkspaceFile(worktreePath, repoName) {
|
|
16743
|
+
const workspacePath = join8(worktreePath, `${repoName}.code-workspace`);
|
|
16744
|
+
try {
|
|
16745
|
+
await access4(workspacePath);
|
|
16746
|
+
return workspacePath;
|
|
16747
|
+
} catch {
|
|
16748
|
+
}
|
|
16749
|
+
const workspace = { folders: [{ path: "." }], settings: {} };
|
|
16750
|
+
await writeFile6(
|
|
16751
|
+
workspacePath,
|
|
16752
|
+
`${JSON.stringify(workspace, null, 2)}
|
|
16753
|
+
`,
|
|
16754
|
+
"utf8"
|
|
16755
|
+
);
|
|
16756
|
+
return workspacePath;
|
|
16757
|
+
}
|
|
16758
|
+
|
|
16375
16759
|
// src/pr.ts
|
|
16760
|
+
import { execFile as execFile5 } from "node:child_process";
|
|
16376
16761
|
import { mkdir as mkdir7 } from "node:fs/promises";
|
|
16377
16762
|
import { basename as basename7, dirname as dirname8 } from "node:path";
|
|
16378
|
-
import { execFile as execFile5 } from "node:child_process";
|
|
16379
16763
|
import { promisify as promisify6 } from "node:util";
|
|
16380
16764
|
var execFileAsync5 = promisify6(execFile5);
|
|
16381
16765
|
var PR_OUTPUT_FILE_ENV = "GJI_PR_OUTPUT_FILE";
|
|
@@ -16383,7 +16767,9 @@ function parsePrInput(input) {
|
|
|
16383
16767
|
if (/^\d+$/.test(input)) return input;
|
|
16384
16768
|
const hashMatch = input.match(/^#(\d+)$/);
|
|
16385
16769
|
if (hashMatch) return hashMatch[1];
|
|
16386
|
-
const urlMatch = input.match(
|
|
16770
|
+
const urlMatch = input.match(
|
|
16771
|
+
/\/(?:pull|pull-requests|merge_requests)\/(\d+)/
|
|
16772
|
+
);
|
|
16387
16773
|
if (urlMatch) return urlMatch[1];
|
|
16388
16774
|
return null;
|
|
16389
16775
|
}
|
|
@@ -16403,12 +16789,20 @@ function createPrCommand(dependencies = {}) {
|
|
|
16403
16789
|
return 1;
|
|
16404
16790
|
}
|
|
16405
16791
|
const repository = await detectRepository(options.cwd);
|
|
16406
|
-
const config = await loadEffectiveConfig(
|
|
16792
|
+
const config = await loadEffectiveConfig(
|
|
16793
|
+
repository.repoRoot,
|
|
16794
|
+
void 0,
|
|
16795
|
+
options.stderr
|
|
16796
|
+
);
|
|
16407
16797
|
const branchName = `pr/${prNumber}`;
|
|
16408
16798
|
const remoteRef = `refs/remotes/origin/pull/${prNumber}/head`;
|
|
16409
16799
|
const rawBasePath = resolveConfigString(config, "worktreePath");
|
|
16410
16800
|
const configuredBasePath = rawBasePath?.startsWith("/") || rawBasePath?.startsWith("~") ? rawBasePath : void 0;
|
|
16411
|
-
const worktreePath = resolveWorktreePath(
|
|
16801
|
+
const worktreePath = resolveWorktreePath(
|
|
16802
|
+
repository.repoRoot,
|
|
16803
|
+
branchName,
|
|
16804
|
+
configuredBasePath
|
|
16805
|
+
);
|
|
16412
16806
|
if (await pathExists(worktreePath)) {
|
|
16413
16807
|
if (options.json || isHeadless()) {
|
|
16414
16808
|
const message = `target worktree path already exists: ${worktreePath}`;
|
|
@@ -16416,10 +16810,14 @@ function createPrCommand(dependencies = {}) {
|
|
|
16416
16810
|
options.stderr(`${JSON.stringify({ error: message }, null, 2)}
|
|
16417
16811
|
`);
|
|
16418
16812
|
} else {
|
|
16419
|
-
options.stderr(
|
|
16420
|
-
`)
|
|
16421
|
-
|
|
16422
|
-
|
|
16813
|
+
options.stderr(
|
|
16814
|
+
`gji pr: ${message} in non-interactive mode (GJI_NO_TUI=1)
|
|
16815
|
+
`
|
|
16816
|
+
);
|
|
16817
|
+
options.stderr(
|
|
16818
|
+
`Hint: Use 'gji remove pr/${prNumber}' or 'gji clean' to remove the existing worktree
|
|
16819
|
+
`
|
|
16820
|
+
);
|
|
16423
16821
|
}
|
|
16424
16822
|
return 1;
|
|
16425
16823
|
}
|
|
@@ -16429,22 +16827,33 @@ function createPrCommand(dependencies = {}) {
|
|
|
16429
16827
|
await writeOutput2(worktreePath, options.stdout);
|
|
16430
16828
|
return 0;
|
|
16431
16829
|
}
|
|
16432
|
-
options.stderr(
|
|
16433
|
-
`
|
|
16830
|
+
options.stderr(
|
|
16831
|
+
`Aborted because target worktree path already exists: ${worktreePath}
|
|
16832
|
+
`
|
|
16833
|
+
);
|
|
16434
16834
|
return 1;
|
|
16435
16835
|
}
|
|
16436
16836
|
if (options.dryRun) {
|
|
16437
16837
|
if (options.json) {
|
|
16438
|
-
options.stdout(
|
|
16439
|
-
|
|
16838
|
+
options.stdout(
|
|
16839
|
+
`${JSON.stringify({ branch: branchName, path: worktreePath, dryRun: true }, null, 2)}
|
|
16840
|
+
`
|
|
16841
|
+
);
|
|
16440
16842
|
} else {
|
|
16441
|
-
options.stdout(
|
|
16442
|
-
`)
|
|
16843
|
+
options.stdout(
|
|
16844
|
+
`Would create worktree at ${worktreePath} (branch: ${branchName})
|
|
16845
|
+
`
|
|
16846
|
+
);
|
|
16443
16847
|
}
|
|
16444
16848
|
return 0;
|
|
16445
16849
|
}
|
|
16446
16850
|
try {
|
|
16447
|
-
await fetchPullRequestRef(
|
|
16851
|
+
await fetchPullRequestRef(
|
|
16852
|
+
repository.repoRoot,
|
|
16853
|
+
options.number,
|
|
16854
|
+
prNumber,
|
|
16855
|
+
remoteRef
|
|
16856
|
+
);
|
|
16448
16857
|
} catch {
|
|
16449
16858
|
const message = `Failed to fetch PR #${prNumber} from origin`;
|
|
16450
16859
|
if (options.json) {
|
|
@@ -16453,35 +16862,57 @@ function createPrCommand(dependencies = {}) {
|
|
|
16453
16862
|
} else {
|
|
16454
16863
|
options.stderr(`${message}
|
|
16455
16864
|
`);
|
|
16456
|
-
options.stderr(
|
|
16457
|
-
`
|
|
16865
|
+
options.stderr(
|
|
16866
|
+
`Hint: Verify the remote is reachable: git fetch origin
|
|
16867
|
+
`
|
|
16868
|
+
);
|
|
16458
16869
|
}
|
|
16459
16870
|
return 1;
|
|
16460
16871
|
}
|
|
16461
16872
|
await mkdir7(dirname8(worktreePath), { recursive: true });
|
|
16462
|
-
const branchAlreadyExists = await localBranchExists2(
|
|
16873
|
+
const branchAlreadyExists = await localBranchExists2(
|
|
16874
|
+
repository.repoRoot,
|
|
16875
|
+
branchName
|
|
16876
|
+
);
|
|
16463
16877
|
const worktreeArgs = branchAlreadyExists ? ["worktree", "add", worktreePath, branchName] : ["worktree", "add", "-b", branchName, worktreePath, remoteRef];
|
|
16464
16878
|
await execFileAsync5("git", worktreeArgs, { cwd: repository.repoRoot });
|
|
16465
|
-
const syncPatterns = Array.isArray(config.syncFiles) ? config.syncFiles.filter(
|
|
16879
|
+
const syncPatterns = Array.isArray(config.syncFiles) ? config.syncFiles.filter(
|
|
16880
|
+
(p2) => typeof p2 === "string"
|
|
16881
|
+
) : [];
|
|
16466
16882
|
for (const pattern of syncPatterns) {
|
|
16467
16883
|
try {
|
|
16468
16884
|
await syncFiles(repository.repoRoot, worktreePath, [pattern]);
|
|
16469
16885
|
} catch (error) {
|
|
16470
|
-
options.stderr(
|
|
16471
|
-
`)
|
|
16886
|
+
options.stderr(
|
|
16887
|
+
`Warning: failed to sync file "${pattern}": ${error instanceof Error ? error.message : String(error)}
|
|
16888
|
+
`
|
|
16889
|
+
);
|
|
16472
16890
|
}
|
|
16473
16891
|
}
|
|
16474
|
-
await maybeRunInstallPrompt(
|
|
16892
|
+
await maybeRunInstallPrompt(
|
|
16893
|
+
worktreePath,
|
|
16894
|
+
repository.repoRoot,
|
|
16895
|
+
config,
|
|
16896
|
+
options.stderr,
|
|
16897
|
+
dependencies,
|
|
16898
|
+
!!options.json
|
|
16899
|
+
);
|
|
16475
16900
|
const hooks = extractHooks(config);
|
|
16476
16901
|
await runHook(
|
|
16477
16902
|
hooks.afterCreate,
|
|
16478
16903
|
worktreePath,
|
|
16479
|
-
{
|
|
16904
|
+
{
|
|
16905
|
+
branch: branchName,
|
|
16906
|
+
path: worktreePath,
|
|
16907
|
+
repo: basename7(repository.repoRoot)
|
|
16908
|
+
},
|
|
16480
16909
|
options.stderr
|
|
16481
16910
|
);
|
|
16482
16911
|
if (options.json) {
|
|
16483
|
-
options.stdout(
|
|
16484
|
-
|
|
16912
|
+
options.stdout(
|
|
16913
|
+
`${JSON.stringify({ branch: branchName, path: worktreePath }, null, 2)}
|
|
16914
|
+
`
|
|
16915
|
+
);
|
|
16485
16916
|
} else {
|
|
16486
16917
|
await appendHistory(worktreePath, branchName);
|
|
16487
16918
|
await writeOutput2(worktreePath, options.stdout);
|
|
@@ -16517,9 +16948,16 @@ async function fetchPullRequestRef(repoRoot, input, prNumber, remoteRef) {
|
|
|
16517
16948
|
throw new Error(`No pull request ref found for #${prNumber}`);
|
|
16518
16949
|
}
|
|
16519
16950
|
function listPullRequestSourceRefs(input, prNumber) {
|
|
16520
|
-
const allForges = [
|
|
16951
|
+
const allForges = [
|
|
16952
|
+
"github",
|
|
16953
|
+
"gitlab",
|
|
16954
|
+
"bitbucket"
|
|
16955
|
+
];
|
|
16521
16956
|
const preferredForge = detectPullRequestForge(input);
|
|
16522
|
-
const orderedForges = preferredForge === "unknown" ? allForges : [
|
|
16957
|
+
const orderedForges = preferredForge === "unknown" ? allForges : [
|
|
16958
|
+
preferredForge,
|
|
16959
|
+
...allForges.filter((forge) => forge !== preferredForge)
|
|
16960
|
+
];
|
|
16523
16961
|
return orderedForges.map((forge) => sourceRefForForge(forge, prNumber));
|
|
16524
16962
|
}
|
|
16525
16963
|
function detectPullRequestForge(input) {
|
|
@@ -16557,7 +16995,9 @@ function createRemoveCommand(dependencies = {}) {
|
|
|
16557
16995
|
const confirmForceRemoveWorktree = dependencies.confirmForceRemoveWorktree ?? defaultConfirmForceRemoveWorktree;
|
|
16558
16996
|
const confirmForceDeleteBranch = dependencies.confirmForceDeleteBranch ?? defaultConfirmForceDeleteBranch;
|
|
16559
16997
|
return async function runRemoveCommand2(options) {
|
|
16560
|
-
const { linkedWorktrees, repository } = await loadLinkedWorktrees(
|
|
16998
|
+
const { linkedWorktrees, repository } = await loadLinkedWorktrees(
|
|
16999
|
+
options.cwd
|
|
17000
|
+
);
|
|
16561
17001
|
if (linkedWorktrees.length === 0) {
|
|
16562
17002
|
emitError2(options, "No linked worktrees to finish");
|
|
16563
17003
|
return 1;
|
|
@@ -16567,8 +17007,10 @@ function createRemoveCommand(dependencies = {}) {
|
|
|
16567
17007
|
if (options.json) {
|
|
16568
17008
|
emitError2(options, message);
|
|
16569
17009
|
} else {
|
|
16570
|
-
options.stderr(
|
|
16571
|
-
`)
|
|
17010
|
+
options.stderr(
|
|
17011
|
+
`gji remove: ${message} in non-interactive mode (GJI_NO_TUI=1)
|
|
17012
|
+
`
|
|
17013
|
+
);
|
|
16572
17014
|
}
|
|
16573
17015
|
return 1;
|
|
16574
17016
|
}
|
|
@@ -16589,8 +17031,10 @@ function createRemoveCommand(dependencies = {}) {
|
|
|
16589
17031
|
if (options.json) {
|
|
16590
17032
|
emitError2(options, message);
|
|
16591
17033
|
} else {
|
|
16592
|
-
options.stderr(
|
|
16593
|
-
`)
|
|
17034
|
+
options.stderr(
|
|
17035
|
+
`gji remove: ${message} in non-interactive mode (GJI_NO_TUI=1)
|
|
17036
|
+
`
|
|
17037
|
+
);
|
|
16594
17038
|
}
|
|
16595
17039
|
return 1;
|
|
16596
17040
|
}
|
|
@@ -16600,8 +17044,10 @@ function createRemoveCommand(dependencies = {}) {
|
|
|
16600
17044
|
}
|
|
16601
17045
|
if (options.dryRun) {
|
|
16602
17046
|
if (options.json) {
|
|
16603
|
-
options.stdout(
|
|
16604
|
-
|
|
17047
|
+
options.stdout(
|
|
17048
|
+
`${JSON.stringify({ branch: worktree.branch, path: worktree.path, dryRun: true }, null, 2)}
|
|
17049
|
+
`
|
|
17050
|
+
);
|
|
16605
17051
|
} else {
|
|
16606
17052
|
const desc = worktree.branch ? `branch: ${worktree.branch}` : "detached";
|
|
16607
17053
|
options.stdout(`Would remove worktree at ${worktree.path} (${desc})
|
|
@@ -16609,12 +17055,20 @@ function createRemoveCommand(dependencies = {}) {
|
|
|
16609
17055
|
}
|
|
16610
17056
|
return 0;
|
|
16611
17057
|
}
|
|
16612
|
-
const config = await loadEffectiveConfig(
|
|
17058
|
+
const config = await loadEffectiveConfig(
|
|
17059
|
+
repository.repoRoot,
|
|
17060
|
+
void 0,
|
|
17061
|
+
options.stderr
|
|
17062
|
+
);
|
|
16613
17063
|
const hooks = extractHooks(config);
|
|
16614
17064
|
await runHook(
|
|
16615
17065
|
hooks.beforeRemove,
|
|
16616
17066
|
worktree.path,
|
|
16617
|
-
{
|
|
17067
|
+
{
|
|
17068
|
+
branch: worktree.branch ?? void 0,
|
|
17069
|
+
path: worktree.path,
|
|
17070
|
+
repo: basename8(repository.repoRoot)
|
|
17071
|
+
},
|
|
16618
17072
|
options.stderr
|
|
16619
17073
|
);
|
|
16620
17074
|
try {
|
|
@@ -16630,7 +17084,10 @@ function createRemoveCommand(dependencies = {}) {
|
|
|
16630
17084
|
try {
|
|
16631
17085
|
await forceRemoveWorktree(repository.repoRoot, worktree.path);
|
|
16632
17086
|
} catch (forceError) {
|
|
16633
|
-
emitError2(
|
|
17087
|
+
emitError2(
|
|
17088
|
+
options,
|
|
17089
|
+
`Failed to remove worktree at ${worktree.path}: ${toMessage2(forceError)}`
|
|
17090
|
+
);
|
|
16634
17091
|
return 1;
|
|
16635
17092
|
}
|
|
16636
17093
|
}
|
|
@@ -16645,18 +17102,24 @@ function createRemoveCommand(dependencies = {}) {
|
|
|
16645
17102
|
try {
|
|
16646
17103
|
await forceDeleteBranch(repository.repoRoot, worktree.branch);
|
|
16647
17104
|
} catch (forceError) {
|
|
16648
|
-
options.stderr(
|
|
16649
|
-
`)
|
|
17105
|
+
options.stderr(
|
|
17106
|
+
`Failed to delete branch ${worktree.branch}: ${toMessage2(forceError)}
|
|
17107
|
+
`
|
|
17108
|
+
);
|
|
16650
17109
|
}
|
|
16651
17110
|
} else {
|
|
16652
|
-
options.stderr(
|
|
16653
|
-
`)
|
|
17111
|
+
options.stderr(
|
|
17112
|
+
`Branch ${worktree.branch} was not deleted (has unmerged commits)
|
|
17113
|
+
`
|
|
17114
|
+
);
|
|
16654
17115
|
}
|
|
16655
17116
|
}
|
|
16656
17117
|
}
|
|
16657
17118
|
if (options.json) {
|
|
16658
|
-
options.stdout(
|
|
16659
|
-
|
|
17119
|
+
options.stdout(
|
|
17120
|
+
`${JSON.stringify({ branch: worktree.branch, path: worktree.path, deleted: true }, null, 2)}
|
|
17121
|
+
`
|
|
17122
|
+
);
|
|
16660
17123
|
} else {
|
|
16661
17124
|
await writeOutput3(repository.repoRoot, options.stdout);
|
|
16662
17125
|
}
|
|
@@ -16705,7 +17168,11 @@ var ROOT_OUTPUT_FILE_ENV = "GJI_ROOT_OUTPUT_FILE";
|
|
|
16705
17168
|
async function runRootCommand(options) {
|
|
16706
17169
|
const repository = await detectRepository(options.cwd);
|
|
16707
17170
|
if (!options.print && process.env[ROOT_OUTPUT_FILE_ENV]) {
|
|
16708
|
-
await writeShellOutput(
|
|
17171
|
+
await writeShellOutput(
|
|
17172
|
+
ROOT_OUTPUT_FILE_ENV,
|
|
17173
|
+
repository.repoRoot,
|
|
17174
|
+
options.stdout
|
|
17175
|
+
);
|
|
16709
17176
|
return 0;
|
|
16710
17177
|
}
|
|
16711
17178
|
options.stdout(`${repository.repoRoot}
|
|
@@ -16721,18 +17188,31 @@ async function runStatusCommand(options) {
|
|
|
16721
17188
|
worktrees.map(async (worktree) => buildStatusRow(worktree))
|
|
16722
17189
|
);
|
|
16723
17190
|
if (options.json) {
|
|
16724
|
-
options.stdout(
|
|
16725
|
-
|
|
17191
|
+
options.stdout(
|
|
17192
|
+
`${JSON.stringify(formatStatusJson(repository.repoRoot, repository.currentRoot, rows), null, 2)}
|
|
17193
|
+
`
|
|
17194
|
+
);
|
|
16726
17195
|
return 0;
|
|
16727
17196
|
}
|
|
16728
|
-
options.stdout(
|
|
16729
|
-
|
|
17197
|
+
options.stdout(
|
|
17198
|
+
`${formatStatusOutput(repository.repoRoot, repository.currentRoot, rows)}
|
|
17199
|
+
`
|
|
17200
|
+
);
|
|
16730
17201
|
return 0;
|
|
16731
17202
|
}
|
|
16732
17203
|
function formatStatusOutput(repoRoot, currentRoot, rows) {
|
|
16733
|
-
const currentWidth = Math.max(
|
|
16734
|
-
|
|
16735
|
-
|
|
17204
|
+
const currentWidth = Math.max(
|
|
17205
|
+
"CURRENT".length,
|
|
17206
|
+
...rows.map((row) => row.current ? 1 : 0)
|
|
17207
|
+
);
|
|
17208
|
+
const branchWidth = Math.max(
|
|
17209
|
+
"BRANCH".length,
|
|
17210
|
+
...rows.map((row) => formatBranch(row.branch).length)
|
|
17211
|
+
);
|
|
17212
|
+
const statusWidth = Math.max(
|
|
17213
|
+
"STATUS".length,
|
|
17214
|
+
...rows.map((row) => row.status.length)
|
|
17215
|
+
);
|
|
16736
17216
|
const upstreamWidth = Math.max(
|
|
16737
17217
|
"UPSTREAM".length,
|
|
16738
17218
|
...rows.map((row) => formatUpstreamState2(row.upstream).length)
|
|
@@ -16768,7 +17248,9 @@ async function buildStatusRow(worktree) {
|
|
|
16768
17248
|
};
|
|
16769
17249
|
}
|
|
16770
17250
|
function sortWorktreesByPath(worktrees) {
|
|
16771
|
-
return [...worktrees].sort(
|
|
17251
|
+
return [...worktrees].sort(
|
|
17252
|
+
(left, right) => comparePaths(left.path, right.path)
|
|
17253
|
+
);
|
|
16772
17254
|
}
|
|
16773
17255
|
function formatBranch(branch) {
|
|
16774
17256
|
return branch ?? "(detached)";
|
|
@@ -16814,7 +17296,11 @@ function formatUpstreamState2(upstream) {
|
|
|
16814
17296
|
// src/sync.ts
|
|
16815
17297
|
async function runSyncCommand(options) {
|
|
16816
17298
|
const repository = await detectRepository(options.cwd);
|
|
16817
|
-
const config = await loadEffectiveConfig(
|
|
17299
|
+
const config = await loadEffectiveConfig(
|
|
17300
|
+
repository.repoRoot,
|
|
17301
|
+
void 0,
|
|
17302
|
+
options.stderr
|
|
17303
|
+
);
|
|
16818
17304
|
const worktrees = await listWorktrees(options.cwd);
|
|
16819
17305
|
const remote = resolveConfiguredString2(config.syncRemote) ?? "origin";
|
|
16820
17306
|
let defaultBranch;
|
|
@@ -16823,22 +17309,33 @@ async function runSyncCommand(options) {
|
|
|
16823
17309
|
} catch {
|
|
16824
17310
|
emitError3(options, `Unable to reach remote '${remote}'`);
|
|
16825
17311
|
if (!options.json) {
|
|
16826
|
-
options.stderr(
|
|
16827
|
-
`
|
|
17312
|
+
options.stderr(
|
|
17313
|
+
`Hint: Add the remote with: git remote add ${remote} <url>
|
|
17314
|
+
`
|
|
17315
|
+
);
|
|
16828
17316
|
}
|
|
16829
17317
|
return 1;
|
|
16830
17318
|
}
|
|
16831
17319
|
if (!defaultBranch) {
|
|
16832
17320
|
emitError3(options, "Unable to determine the default branch for sync.");
|
|
16833
17321
|
if (!options.json) {
|
|
16834
|
-
options.stderr(
|
|
16835
|
-
`
|
|
17322
|
+
options.stderr(
|
|
17323
|
+
`Hint: Add the remote with: git remote add ${remote} <url>
|
|
17324
|
+
`
|
|
17325
|
+
);
|
|
16836
17326
|
}
|
|
16837
17327
|
return 1;
|
|
16838
17328
|
}
|
|
16839
|
-
const targetWorktrees = selectTargetWorktrees(
|
|
17329
|
+
const targetWorktrees = selectTargetWorktrees(
|
|
17330
|
+
worktrees,
|
|
17331
|
+
repository.currentRoot,
|
|
17332
|
+
options.all
|
|
17333
|
+
);
|
|
16840
17334
|
if (targetWorktrees === "detached") {
|
|
16841
|
-
emitError3(
|
|
17335
|
+
emitError3(
|
|
17336
|
+
options,
|
|
17337
|
+
`Cannot sync detached worktree: ${repository.currentRoot}`
|
|
17338
|
+
);
|
|
16842
17339
|
return 1;
|
|
16843
17340
|
}
|
|
16844
17341
|
for (const worktree of targetWorktrees) {
|
|
@@ -16852,15 +17349,21 @@ async function runSyncCommand(options) {
|
|
|
16852
17349
|
} catch {
|
|
16853
17350
|
emitError3(options, `Failed to fetch from remote '${remote}'`);
|
|
16854
17351
|
if (!options.json) {
|
|
16855
|
-
options.stderr(
|
|
16856
|
-
`
|
|
17352
|
+
options.stderr(
|
|
17353
|
+
`Hint: Add the remote with: git remote add ${remote} <url>
|
|
17354
|
+
`
|
|
17355
|
+
);
|
|
16857
17356
|
}
|
|
16858
17357
|
return 1;
|
|
16859
17358
|
}
|
|
16860
17359
|
const updatedWorktrees = [];
|
|
16861
17360
|
for (const worktree of targetWorktrees) {
|
|
16862
17361
|
if (worktree.branch === defaultBranch) {
|
|
16863
|
-
await runGit(worktree.path, [
|
|
17362
|
+
await runGit(worktree.path, [
|
|
17363
|
+
"merge",
|
|
17364
|
+
"--ff-only",
|
|
17365
|
+
`${remote}/${defaultBranch}`
|
|
17366
|
+
]);
|
|
16864
17367
|
} else {
|
|
16865
17368
|
await runGit(worktree.path, ["rebase", `${remote}/${defaultBranch}`]);
|
|
16866
17369
|
}
|
|
@@ -16871,7 +17374,10 @@ async function runSyncCommand(options) {
|
|
|
16871
17374
|
}
|
|
16872
17375
|
}
|
|
16873
17376
|
if (options.json) {
|
|
16874
|
-
const updated = updatedWorktrees.map((w2) => ({
|
|
17377
|
+
const updated = updatedWorktrees.map((w2) => ({
|
|
17378
|
+
branch: w2.branch,
|
|
17379
|
+
path: w2.path
|
|
17380
|
+
}));
|
|
16875
17381
|
options.stdout(`${JSON.stringify({ updated }, null, 2)}
|
|
16876
17382
|
`);
|
|
16877
17383
|
}
|
|
@@ -16890,7 +17396,9 @@ function selectTargetWorktrees(worktrees, currentRoot, all) {
|
|
|
16890
17396
|
if (all) {
|
|
16891
17397
|
return worktrees.filter((worktree) => worktree.branch !== null).sort((left, right) => comparePaths(left.path, right.path));
|
|
16892
17398
|
}
|
|
16893
|
-
const currentWorktree = worktrees.find(
|
|
17399
|
+
const currentWorktree = worktrees.find(
|
|
17400
|
+
(worktree) => worktree.path === currentRoot
|
|
17401
|
+
);
|
|
16894
17402
|
if (!currentWorktree) {
|
|
16895
17403
|
return [];
|
|
16896
17404
|
}
|
|
@@ -16903,8 +17411,153 @@ function resolveConfiguredString2(value) {
|
|
|
16903
17411
|
return typeof value === "string" && value.length > 0 ? value : null;
|
|
16904
17412
|
}
|
|
16905
17413
|
|
|
17414
|
+
// src/sync-files-command.ts
|
|
17415
|
+
import { homedir as homedir6 } from "node:os";
|
|
17416
|
+
import { join as join9 } from "node:path";
|
|
17417
|
+
async function runSyncFilesCommand(options) {
|
|
17418
|
+
const repository = await detectRepository(options.cwd);
|
|
17419
|
+
const home = options.home ?? homedir6();
|
|
17420
|
+
const loaded = await loadGlobalConfig(home);
|
|
17421
|
+
const repoEntry = findRepoConfigEntry(
|
|
17422
|
+
loaded.config,
|
|
17423
|
+
repository.repoRoot,
|
|
17424
|
+
home
|
|
17425
|
+
);
|
|
17426
|
+
const repoConfig = repoEntry?.config ?? {};
|
|
17427
|
+
switch (options.action) {
|
|
17428
|
+
case void 0:
|
|
17429
|
+
case "list": {
|
|
17430
|
+
writeSyncFiles(options.stdout, readSyncFiles(repoConfig), !!options.json);
|
|
17431
|
+
return 0;
|
|
17432
|
+
}
|
|
17433
|
+
case "add": {
|
|
17434
|
+
const paths = validatePaths(options.paths ?? [], options);
|
|
17435
|
+
if (!paths) return 1;
|
|
17436
|
+
const nextFiles = mergeSyncFiles(readSyncFiles(repoConfig), paths);
|
|
17437
|
+
await saveRepoSyncFiles(
|
|
17438
|
+
loaded.config,
|
|
17439
|
+
repoEntry?.key ?? repository.repoRoot,
|
|
17440
|
+
nextFiles,
|
|
17441
|
+
home
|
|
17442
|
+
);
|
|
17443
|
+
writeSyncFiles(options.stdout, nextFiles, !!options.json);
|
|
17444
|
+
return 0;
|
|
17445
|
+
}
|
|
17446
|
+
case "remove": {
|
|
17447
|
+
const paths = validatePaths(options.paths ?? [], options);
|
|
17448
|
+
if (!paths) return 1;
|
|
17449
|
+
const existingFiles = readSyncFiles(repoConfig);
|
|
17450
|
+
const nextFiles = removeSyncFiles(existingFiles, paths);
|
|
17451
|
+
if (repoEntry && nextFiles.length !== existingFiles.length) {
|
|
17452
|
+
await saveRepoSyncFiles(loaded.config, repoEntry.key, nextFiles, home);
|
|
17453
|
+
}
|
|
17454
|
+
writeSyncFiles(options.stdout, nextFiles, !!options.json);
|
|
17455
|
+
return 0;
|
|
17456
|
+
}
|
|
17457
|
+
}
|
|
17458
|
+
writeError(options, `unknown action: ${options.action}`);
|
|
17459
|
+
return 1;
|
|
17460
|
+
}
|
|
17461
|
+
function findRepoConfigEntry(config, repoRoot, home) {
|
|
17462
|
+
const repos = config.repos;
|
|
17463
|
+
if (!isPlainObject3(repos)) return null;
|
|
17464
|
+
for (const [key, value] of Object.entries(repos)) {
|
|
17465
|
+
if (expandTilde2(key, home) === repoRoot && isPlainObject3(value)) {
|
|
17466
|
+
return { config: value, key };
|
|
17467
|
+
}
|
|
17468
|
+
}
|
|
17469
|
+
return null;
|
|
17470
|
+
}
|
|
17471
|
+
function readSyncFiles(config) {
|
|
17472
|
+
const syncFiles2 = config.syncFiles;
|
|
17473
|
+
if (!Array.isArray(syncFiles2)) return [];
|
|
17474
|
+
return syncFiles2.filter((item) => typeof item === "string");
|
|
17475
|
+
}
|
|
17476
|
+
function writeSyncFiles(stdout, files, json) {
|
|
17477
|
+
if (json) {
|
|
17478
|
+
stdout(`${JSON.stringify(files, null, 2)}
|
|
17479
|
+
`);
|
|
17480
|
+
return;
|
|
17481
|
+
}
|
|
17482
|
+
if (files.length === 0) {
|
|
17483
|
+
stdout("No sync files configured for this repo.\n");
|
|
17484
|
+
return;
|
|
17485
|
+
}
|
|
17486
|
+
stdout(`${files.join("\n")}
|
|
17487
|
+
`);
|
|
17488
|
+
}
|
|
17489
|
+
function validatePaths(paths, options) {
|
|
17490
|
+
if (paths.length === 0) {
|
|
17491
|
+
writeError(options, "at least one path is required");
|
|
17492
|
+
return null;
|
|
17493
|
+
}
|
|
17494
|
+
const validatedPaths = [];
|
|
17495
|
+
for (const path9 of paths) {
|
|
17496
|
+
try {
|
|
17497
|
+
validatedPaths.push(validateSyncFilePattern(path9));
|
|
17498
|
+
} catch (error) {
|
|
17499
|
+
writeError(
|
|
17500
|
+
options,
|
|
17501
|
+
error instanceof Error ? error.message : String(error)
|
|
17502
|
+
);
|
|
17503
|
+
return null;
|
|
17504
|
+
}
|
|
17505
|
+
}
|
|
17506
|
+
return validatedPaths;
|
|
17507
|
+
}
|
|
17508
|
+
function mergeSyncFiles(existing, additions) {
|
|
17509
|
+
const nextFiles = [...existing];
|
|
17510
|
+
for (const path9 of additions) {
|
|
17511
|
+
if (!nextFiles.includes(path9)) {
|
|
17512
|
+
nextFiles.push(path9);
|
|
17513
|
+
}
|
|
17514
|
+
}
|
|
17515
|
+
return nextFiles;
|
|
17516
|
+
}
|
|
17517
|
+
function removeSyncFiles(existing, removals) {
|
|
17518
|
+
const removalSet = new Set(removals);
|
|
17519
|
+
return existing.filter((path9) => !removalSet.has(path9));
|
|
17520
|
+
}
|
|
17521
|
+
async function saveRepoSyncFiles(config, repoKey, syncFiles2, home) {
|
|
17522
|
+
const repos = isPlainObject3(config.repos) ? { ...config.repos } : {};
|
|
17523
|
+
const repoConfig = isPlainObject3(repos[repoKey]) ? repos[repoKey] : {};
|
|
17524
|
+
const nextRepoConfig = { ...repoConfig };
|
|
17525
|
+
if (syncFiles2.length > 0) {
|
|
17526
|
+
nextRepoConfig.syncFiles = syncFiles2;
|
|
17527
|
+
} else {
|
|
17528
|
+
delete nextRepoConfig.syncFiles;
|
|
17529
|
+
}
|
|
17530
|
+
if (Object.keys(nextRepoConfig).length > 0) {
|
|
17531
|
+
repos[repoKey] = nextRepoConfig;
|
|
17532
|
+
} else {
|
|
17533
|
+
delete repos[repoKey];
|
|
17534
|
+
}
|
|
17535
|
+
await saveGlobalConfig({ ...config, repos }, home);
|
|
17536
|
+
}
|
|
17537
|
+
function isPlainObject3(value) {
|
|
17538
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17539
|
+
}
|
|
17540
|
+
function writeError(options, message) {
|
|
17541
|
+
if (options.json) {
|
|
17542
|
+
options.stderr(`${JSON.stringify({ error: message }, null, 2)}
|
|
17543
|
+
`);
|
|
17544
|
+
return;
|
|
17545
|
+
}
|
|
17546
|
+
options.stderr(`gji sync-files: ${message}
|
|
17547
|
+
`);
|
|
17548
|
+
}
|
|
17549
|
+
function expandTilde2(value, home) {
|
|
17550
|
+
if (value === "~") return home;
|
|
17551
|
+
if (value.startsWith("~/")) return join9(home, value.slice(2));
|
|
17552
|
+
return value;
|
|
17553
|
+
}
|
|
17554
|
+
|
|
16906
17555
|
// src/trigger-hook.ts
|
|
16907
|
-
var VALID_HOOKS = [
|
|
17556
|
+
var VALID_HOOKS = [
|
|
17557
|
+
"afterCreate",
|
|
17558
|
+
"afterEnter",
|
|
17559
|
+
"beforeRemove"
|
|
17560
|
+
];
|
|
16908
17561
|
function isValidHook(hook) {
|
|
16909
17562
|
return VALID_HOOKS.includes(hook);
|
|
16910
17563
|
}
|
|
@@ -16918,10 +17571,16 @@ async function runTriggerHookCommand(options) {
|
|
|
16918
17571
|
}
|
|
16919
17572
|
const hookName = options.hook;
|
|
16920
17573
|
const repository = await detectRepository(options.cwd);
|
|
16921
|
-
const config = await loadEffectiveConfig(
|
|
17574
|
+
const config = await loadEffectiveConfig(
|
|
17575
|
+
repository.repoRoot,
|
|
17576
|
+
void 0,
|
|
17577
|
+
options.stderr
|
|
17578
|
+
);
|
|
16922
17579
|
const hooks = extractHooks(config);
|
|
16923
17580
|
const worktrees = await listWorktrees(options.cwd);
|
|
16924
|
-
const currentWorktree = worktrees.find(
|
|
17581
|
+
const currentWorktree = worktrees.find(
|
|
17582
|
+
(w2) => w2.path === repository.currentRoot
|
|
17583
|
+
);
|
|
16925
17584
|
await runHook(
|
|
16926
17585
|
hooks[hookName],
|
|
16927
17586
|
repository.currentRoot,
|
|
@@ -17001,46 +17660,117 @@ function maybeRegisterCurrentRepo(cwd) {
|
|
|
17001
17660
|
detectRepository(cwd).then(({ repoRoot }) => registerRepo(repoRoot)).catch(() => void 0);
|
|
17002
17661
|
}
|
|
17003
17662
|
function registerCommands(program2) {
|
|
17004
|
-
program2.command("new [branch]").description("create a new branch or detached linked worktree").option(
|
|
17663
|
+
program2.command("new [branch]").description("create a new branch or detached linked worktree").option(
|
|
17664
|
+
"-f, --force",
|
|
17665
|
+
"remove and recreate the worktree if the target path already exists"
|
|
17666
|
+
).option("--detached", "create a detached worktree without a branch").option("--open", "open the new worktree in an editor after creation").option(
|
|
17667
|
+
"--editor <cli>",
|
|
17668
|
+
"editor CLI to use with --open (code, cursor, zed, \u2026)"
|
|
17669
|
+
).option(
|
|
17670
|
+
"--dry-run",
|
|
17671
|
+
"show what would be created without executing any git commands or writing files"
|
|
17672
|
+
).option(
|
|
17673
|
+
"--json",
|
|
17674
|
+
"emit JSON on success or error instead of human-readable output"
|
|
17675
|
+
).action(notImplemented("new"));
|
|
17005
17676
|
program2.command("init [shell]").description("print or install shell integration").option("--write", "write the integration to the shell config file").action(notImplemented("init"));
|
|
17006
17677
|
program2.command("completion [shell]").description("print shell completion definitions").action(notImplemented("completion"));
|
|
17007
|
-
program2.command("pr <ref>").description(
|
|
17008
|
-
|
|
17678
|
+
program2.command("pr <ref>").description(
|
|
17679
|
+
"fetch a pull request by number, #number, or URL into a linked worktree"
|
|
17680
|
+
).option(
|
|
17681
|
+
"--dry-run",
|
|
17682
|
+
"show what would be created without executing any git commands or writing files"
|
|
17683
|
+
).option(
|
|
17684
|
+
"--json",
|
|
17685
|
+
"emit JSON on success or error instead of human-readable output"
|
|
17686
|
+
).action(notImplemented("pr"));
|
|
17687
|
+
program2.command("back [n]").description(
|
|
17688
|
+
"navigate to the previously visited worktree, optionally N steps back"
|
|
17689
|
+
).option("--print", "print the resolved worktree path explicitly").action(notImplemented("back"));
|
|
17009
17690
|
program2.command("history").description("show navigation history").option("--json", "print history as JSON").action(notImplemented("history"));
|
|
17010
|
-
program2.command("open [branch]").description("open the worktree in an editor").option(
|
|
17691
|
+
program2.command("open [branch]").description("open the worktree in an editor").option(
|
|
17692
|
+
"--editor <cli>",
|
|
17693
|
+
"editor CLI to use (code, cursor, zed, windsurf, subl, \u2026)"
|
|
17694
|
+
).option("--save", "save the chosen editor to global config").option(
|
|
17695
|
+
"--workspace",
|
|
17696
|
+
"generate a .code-workspace file before opening (VS Code / Cursor / Windsurf)"
|
|
17697
|
+
).action(notImplemented("open"));
|
|
17011
17698
|
program2.command("go [branch]").alias("jump").description("print or select a worktree path").option("--print", "print the resolved worktree path explicitly").action(notImplemented("go"));
|
|
17012
17699
|
program2.command("root").description("print the main repository root path").option("--print", "print the resolved repository root path explicitly").action(notImplemented("root"));
|
|
17013
17700
|
program2.command("status").description("summarize repository and worktree health").option("--json", "print repository and worktree health as JSON").action(notImplemented("status"));
|
|
17014
|
-
program2.command("sync").description("fetch and update one or all worktrees").option("--all", "sync every worktree in the repository").option(
|
|
17701
|
+
program2.command("sync").description("fetch and update one or all worktrees").option("--all", "sync every worktree in the repository").option(
|
|
17702
|
+
"--json",
|
|
17703
|
+
"emit JSON on success or error instead of human-readable output"
|
|
17704
|
+
).action(notImplemented("sync"));
|
|
17705
|
+
const syncFilesCommand = program2.command("sync-files").description("manage local files copied into new worktrees").option("--json", "emit JSON instead of human-readable output").action(notImplemented("sync-files"));
|
|
17706
|
+
syncFilesCommand.command("list").description("list files synced into new worktrees for this repo").option("--json", "emit JSON instead of human-readable output").action(notImplemented("sync-files list"));
|
|
17707
|
+
syncFilesCommand.command("add <paths...>").description("add repo-local sync files to global config").option("--json", "emit JSON instead of human-readable output").action(notImplemented("sync-files add"));
|
|
17708
|
+
syncFilesCommand.command("remove <paths...>").alias("rm").description("remove repo-local sync files from global config").option("--json", "emit JSON instead of human-readable output").action(notImplemented("sync-files remove"));
|
|
17015
17709
|
program2.command("ls").description("list active worktrees").option("--compact", "show only branch and path columns").option("--json", "print active worktrees as JSON").action(notImplemented("ls"));
|
|
17016
|
-
program2.command("clean").description("interactively prune linked worktrees").option(
|
|
17017
|
-
|
|
17018
|
-
|
|
17019
|
-
|
|
17710
|
+
program2.command("clean").description("interactively prune linked worktrees").option(
|
|
17711
|
+
"-f, --force",
|
|
17712
|
+
"bypass prompts, force-remove dirty worktrees, and force-delete unmerged branches"
|
|
17713
|
+
).option(
|
|
17714
|
+
"--stale",
|
|
17715
|
+
"only target clean worktrees whose upstream is gone and branch is merged into the default branch"
|
|
17716
|
+
).option("--dry-run", "show what would be deleted without removing anything").option(
|
|
17717
|
+
"--json",
|
|
17718
|
+
"emit JSON on success or error instead of human-readable output"
|
|
17719
|
+
).action(notImplemented("clean"));
|
|
17720
|
+
program2.command("remove [branch]").alias("rm").description("remove a linked worktree and delete its branch when present").option(
|
|
17721
|
+
"-f, --force",
|
|
17722
|
+
"bypass prompts, force-remove a dirty worktree, and force-delete an unmerged branch"
|
|
17723
|
+
).option("--dry-run", "show what would be deleted without removing anything").option(
|
|
17724
|
+
"--json",
|
|
17725
|
+
"emit JSON on success or error instead of human-readable output"
|
|
17726
|
+
).action(notImplemented("remove"));
|
|
17727
|
+
program2.command("trigger-hook <hook>").description(
|
|
17728
|
+
"run a named hook (afterCreate, afterEnter, beforeRemove) in the current worktree"
|
|
17729
|
+
).action(notImplemented("trigger-hook"));
|
|
17730
|
+
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(
|
|
17731
|
+
"--print",
|
|
17732
|
+
"print the resolved worktree path without changing directory"
|
|
17733
|
+
).option(
|
|
17734
|
+
"--json",
|
|
17735
|
+
"emit JSON on success or error instead of human-readable output"
|
|
17736
|
+
).action(notImplemented("warp"));
|
|
17020
17737
|
const configCommand = program2.command("config").description("manage global config defaults").action(notImplemented("config"));
|
|
17021
17738
|
configCommand.command("get [key]").description("print the global config or a single key").action(notImplemented("config get"));
|
|
17022
17739
|
configCommand.command("set <key> <value>").description("set a global config value").action(notImplemented("config set"));
|
|
17023
17740
|
configCommand.command("unset <key>").description("remove a global config value").action(notImplemented("config unset"));
|
|
17024
17741
|
}
|
|
17025
17742
|
function attachCommandActions(program2, options) {
|
|
17026
|
-
program2.commands.find((command) => command.name() === "new")?.action(
|
|
17027
|
-
|
|
17028
|
-
|
|
17029
|
-
|
|
17743
|
+
program2.commands.find((command) => command.name() === "new")?.action(
|
|
17744
|
+
async (branch, commandOptions) => {
|
|
17745
|
+
const exitCode = await runNewCommand({
|
|
17746
|
+
...options,
|
|
17747
|
+
branch,
|
|
17748
|
+
detached: commandOptions.detached,
|
|
17749
|
+
dryRun: commandOptions.dryRun,
|
|
17750
|
+
editor: commandOptions.editor,
|
|
17751
|
+
force: commandOptions.force,
|
|
17752
|
+
json: commandOptions.json,
|
|
17753
|
+
open: commandOptions.open
|
|
17754
|
+
});
|
|
17755
|
+
if (exitCode !== 0) {
|
|
17756
|
+
throw commanderExit(exitCode);
|
|
17757
|
+
}
|
|
17030
17758
|
}
|
|
17031
|
-
|
|
17032
|
-
program2.commands.find((command) => command.name() === "init")?.action(
|
|
17033
|
-
|
|
17034
|
-
|
|
17035
|
-
|
|
17036
|
-
|
|
17037
|
-
|
|
17038
|
-
|
|
17039
|
-
|
|
17040
|
-
|
|
17041
|
-
|
|
17759
|
+
);
|
|
17760
|
+
program2.commands.find((command) => command.name() === "init")?.action(
|
|
17761
|
+
async (shell, commandOptions) => {
|
|
17762
|
+
const exitCode = await runInitCommand({
|
|
17763
|
+
cwd: options.cwd,
|
|
17764
|
+
shell,
|
|
17765
|
+
stderr: options.stderr,
|
|
17766
|
+
stdout: options.stdout,
|
|
17767
|
+
write: commandOptions.write
|
|
17768
|
+
});
|
|
17769
|
+
if (exitCode !== 0) {
|
|
17770
|
+
throw commanderExit(exitCode);
|
|
17771
|
+
}
|
|
17042
17772
|
}
|
|
17043
|
-
|
|
17773
|
+
);
|
|
17044
17774
|
program2.commands.find((command) => command.name() === "completion")?.action(async (shell) => {
|
|
17045
17775
|
const exitCode = await runCompletionCommand({
|
|
17046
17776
|
shell,
|
|
@@ -17051,32 +17781,93 @@ function attachCommandActions(program2, options) {
|
|
|
17051
17781
|
throw commanderExit(exitCode);
|
|
17052
17782
|
}
|
|
17053
17783
|
});
|
|
17054
|
-
program2.commands.find((command) => command.name() === "pr")?.action(
|
|
17055
|
-
|
|
17784
|
+
program2.commands.find((command) => command.name() === "pr")?.action(
|
|
17785
|
+
async (number, commandOptions) => {
|
|
17786
|
+
const exitCode = await runPrCommand({
|
|
17787
|
+
cwd: options.cwd,
|
|
17788
|
+
dryRun: commandOptions.dryRun,
|
|
17789
|
+
json: commandOptions.json,
|
|
17790
|
+
number,
|
|
17791
|
+
stderr: options.stderr,
|
|
17792
|
+
stdout: options.stdout
|
|
17793
|
+
});
|
|
17794
|
+
if (exitCode !== 0) {
|
|
17795
|
+
throw commanderExit(exitCode);
|
|
17796
|
+
}
|
|
17797
|
+
}
|
|
17798
|
+
);
|
|
17799
|
+
program2.commands.find((command) => command.name() === "back")?.action(
|
|
17800
|
+
async (n, commandOptions) => {
|
|
17801
|
+
if (n !== void 0 && !/^\d+$/.test(n)) {
|
|
17802
|
+
options.stderr(`gji back: invalid step count: ${n}
|
|
17803
|
+
`);
|
|
17804
|
+
throw commanderExit(1);
|
|
17805
|
+
}
|
|
17806
|
+
const steps = n !== void 0 ? parseInt(n, 10) : void 0;
|
|
17807
|
+
const exitCode = await runBackCommand({
|
|
17808
|
+
cwd: options.cwd,
|
|
17809
|
+
n: steps,
|
|
17810
|
+
print: commandOptions.print,
|
|
17811
|
+
stderr: options.stderr,
|
|
17812
|
+
stdout: options.stdout
|
|
17813
|
+
});
|
|
17814
|
+
if (exitCode !== 0) {
|
|
17815
|
+
throw commanderExit(exitCode);
|
|
17816
|
+
}
|
|
17817
|
+
}
|
|
17818
|
+
);
|
|
17819
|
+
program2.commands.find((command) => command.name() === "history")?.action(async (commandOptions) => {
|
|
17820
|
+
const exitCode = await runHistoryCommand({
|
|
17821
|
+
cwd: options.cwd,
|
|
17822
|
+
json: commandOptions.json,
|
|
17823
|
+
stdout: options.stdout
|
|
17824
|
+
});
|
|
17056
17825
|
if (exitCode !== 0) {
|
|
17057
17826
|
throw commanderExit(exitCode);
|
|
17058
17827
|
}
|
|
17059
17828
|
});
|
|
17060
|
-
program2.commands.find((command) => command.name() === "
|
|
17061
|
-
|
|
17062
|
-
|
|
17063
|
-
|
|
17064
|
-
|
|
17829
|
+
program2.commands.find((command) => command.name() === "open")?.action(
|
|
17830
|
+
async (branch, commandOptions) => {
|
|
17831
|
+
const exitCode = await runOpenCommand({
|
|
17832
|
+
branch,
|
|
17833
|
+
cwd: options.cwd,
|
|
17834
|
+
editor: commandOptions.editor,
|
|
17835
|
+
save: commandOptions.save,
|
|
17836
|
+
stderr: options.stderr,
|
|
17837
|
+
stdout: options.stdout,
|
|
17838
|
+
workspace: commandOptions.workspace
|
|
17839
|
+
});
|
|
17840
|
+
if (exitCode !== 0) {
|
|
17841
|
+
throw commanderExit(exitCode);
|
|
17842
|
+
}
|
|
17843
|
+
}
|
|
17844
|
+
);
|
|
17845
|
+
program2.commands.find((command) => command.name() === "go")?.action(
|
|
17846
|
+
async (branch, commandOptions) => {
|
|
17847
|
+
const exitCode = await runGoCommand({
|
|
17848
|
+
branch,
|
|
17849
|
+
cwd: options.cwd,
|
|
17850
|
+
print: commandOptions.print,
|
|
17851
|
+
stderr: options.stderr,
|
|
17852
|
+
stdout: options.stdout
|
|
17853
|
+
});
|
|
17854
|
+
if (exitCode !== 0) {
|
|
17855
|
+
throw commanderExit(exitCode);
|
|
17856
|
+
}
|
|
17065
17857
|
}
|
|
17066
|
-
|
|
17067
|
-
|
|
17858
|
+
);
|
|
17859
|
+
program2.commands.find((command) => command.name() === "root")?.action(async (commandOptions) => {
|
|
17860
|
+
const exitCode = await runRootCommand({
|
|
17068
17861
|
cwd: options.cwd,
|
|
17069
|
-
n: steps,
|
|
17070
17862
|
print: commandOptions.print,
|
|
17071
|
-
stderr: options.stderr,
|
|
17072
17863
|
stdout: options.stdout
|
|
17073
17864
|
});
|
|
17074
17865
|
if (exitCode !== 0) {
|
|
17075
17866
|
throw commanderExit(exitCode);
|
|
17076
17867
|
}
|
|
17077
17868
|
});
|
|
17078
|
-
program2.commands.find((command) => command.name() === "
|
|
17079
|
-
const exitCode = await
|
|
17869
|
+
program2.commands.find((command) => command.name() === "status")?.action(async (commandOptions) => {
|
|
17870
|
+
const exitCode = await runStatusCommand({
|
|
17080
17871
|
cwd: options.cwd,
|
|
17081
17872
|
json: commandOptions.json,
|
|
17082
17873
|
stdout: options.stdout
|
|
@@ -17085,25 +17876,26 @@ function attachCommandActions(program2, options) {
|
|
|
17085
17876
|
throw commanderExit(exitCode);
|
|
17086
17877
|
}
|
|
17087
17878
|
});
|
|
17088
|
-
program2.commands.find((command) => command.name() === "
|
|
17089
|
-
const exitCode = await
|
|
17090
|
-
|
|
17879
|
+
program2.commands.find((command) => command.name() === "sync")?.action(async (commandOptions) => {
|
|
17880
|
+
const exitCode = await runSyncCommand({
|
|
17881
|
+
all: commandOptions.all,
|
|
17091
17882
|
cwd: options.cwd,
|
|
17092
|
-
|
|
17093
|
-
save: commandOptions.save,
|
|
17883
|
+
json: commandOptions.json,
|
|
17094
17884
|
stderr: options.stderr,
|
|
17095
|
-
stdout: options.stdout
|
|
17096
|
-
workspace: commandOptions.workspace
|
|
17885
|
+
stdout: options.stdout
|
|
17097
17886
|
});
|
|
17098
17887
|
if (exitCode !== 0) {
|
|
17099
17888
|
throw commanderExit(exitCode);
|
|
17100
17889
|
}
|
|
17101
17890
|
});
|
|
17102
|
-
program2.commands.find(
|
|
17103
|
-
|
|
17104
|
-
|
|
17891
|
+
const syncFilesCommand = program2.commands.find(
|
|
17892
|
+
(command) => command.name() === "sync-files"
|
|
17893
|
+
);
|
|
17894
|
+
syncFilesCommand?.action(async (commandOptions) => {
|
|
17895
|
+
const exitCode = await runSyncFilesCommand({
|
|
17896
|
+
action: "list",
|
|
17105
17897
|
cwd: options.cwd,
|
|
17106
|
-
|
|
17898
|
+
json: commandOptions.json,
|
|
17107
17899
|
stderr: options.stderr,
|
|
17108
17900
|
stdout: options.stdout
|
|
17109
17901
|
});
|
|
@@ -17111,38 +17903,45 @@ function attachCommandActions(program2, options) {
|
|
|
17111
17903
|
throw commanderExit(exitCode);
|
|
17112
17904
|
}
|
|
17113
17905
|
});
|
|
17114
|
-
|
|
17115
|
-
const exitCode = await
|
|
17906
|
+
syncFilesCommand?.commands.find((command) => command.name() === "list")?.action(async (commandOptions) => {
|
|
17907
|
+
const exitCode = await runSyncFilesCommand({
|
|
17908
|
+
action: "list",
|
|
17116
17909
|
cwd: options.cwd,
|
|
17117
|
-
|
|
17910
|
+
json: commandOptions.json || syncFilesCommand?.opts().json,
|
|
17911
|
+
stderr: options.stderr,
|
|
17118
17912
|
stdout: options.stdout
|
|
17119
17913
|
});
|
|
17120
17914
|
if (exitCode !== 0) {
|
|
17121
17915
|
throw commanderExit(exitCode);
|
|
17122
17916
|
}
|
|
17123
17917
|
});
|
|
17124
|
-
|
|
17125
|
-
const exitCode = await
|
|
17918
|
+
syncFilesCommand?.commands.find((command) => command.name() === "add")?.action(async (paths, commandOptions) => {
|
|
17919
|
+
const exitCode = await runSyncFilesCommand({
|
|
17920
|
+
action: "add",
|
|
17126
17921
|
cwd: options.cwd,
|
|
17127
|
-
json: commandOptions.json,
|
|
17922
|
+
json: commandOptions.json || syncFilesCommand?.opts().json,
|
|
17923
|
+
paths,
|
|
17924
|
+
stderr: options.stderr,
|
|
17128
17925
|
stdout: options.stdout
|
|
17129
17926
|
});
|
|
17130
17927
|
if (exitCode !== 0) {
|
|
17131
17928
|
throw commanderExit(exitCode);
|
|
17132
17929
|
}
|
|
17133
17930
|
});
|
|
17134
|
-
|
|
17135
|
-
const exitCode = await
|
|
17136
|
-
|
|
17931
|
+
const runSyncFilesRemoveCommand = async (paths, commandOptions) => {
|
|
17932
|
+
const exitCode = await runSyncFilesCommand({
|
|
17933
|
+
action: "remove",
|
|
17137
17934
|
cwd: options.cwd,
|
|
17138
|
-
json: commandOptions.json,
|
|
17935
|
+
json: commandOptions.json || syncFilesCommand?.opts().json,
|
|
17936
|
+
paths,
|
|
17139
17937
|
stderr: options.stderr,
|
|
17140
17938
|
stdout: options.stdout
|
|
17141
17939
|
});
|
|
17142
17940
|
if (exitCode !== 0) {
|
|
17143
17941
|
throw commanderExit(exitCode);
|
|
17144
17942
|
}
|
|
17145
|
-
}
|
|
17943
|
+
};
|
|
17944
|
+
syncFilesCommand?.commands.find((command) => command.name() === "remove")?.action(runSyncFilesRemoveCommand);
|
|
17146
17945
|
program2.commands.find((command) => command.name() === "ls")?.action(async (commandOptions) => {
|
|
17147
17946
|
const exitCode = await runLsCommand({
|
|
17148
17947
|
compact: commandOptions.compact,
|
|
@@ -17154,20 +17953,22 @@ function attachCommandActions(program2, options) {
|
|
|
17154
17953
|
throw commanderExit(exitCode);
|
|
17155
17954
|
}
|
|
17156
17955
|
});
|
|
17157
|
-
program2.commands.find((command) => command.name() === "clean")?.action(
|
|
17158
|
-
|
|
17159
|
-
|
|
17160
|
-
|
|
17161
|
-
|
|
17162
|
-
|
|
17163
|
-
|
|
17164
|
-
|
|
17165
|
-
|
|
17166
|
-
|
|
17167
|
-
|
|
17168
|
-
|
|
17956
|
+
program2.commands.find((command) => command.name() === "clean")?.action(
|
|
17957
|
+
async (commandOptions) => {
|
|
17958
|
+
const exitCode = await runCleanCommand({
|
|
17959
|
+
cwd: options.cwd,
|
|
17960
|
+
dryRun: commandOptions.dryRun,
|
|
17961
|
+
force: commandOptions.force,
|
|
17962
|
+
json: commandOptions.json,
|
|
17963
|
+
stale: commandOptions.stale,
|
|
17964
|
+
stderr: options.stderr,
|
|
17965
|
+
stdout: options.stdout
|
|
17966
|
+
});
|
|
17967
|
+
if (exitCode !== 0) {
|
|
17968
|
+
throw commanderExit(exitCode);
|
|
17969
|
+
}
|
|
17169
17970
|
}
|
|
17170
|
-
|
|
17971
|
+
);
|
|
17171
17972
|
const runRemovalCommand = async (branch, commandOptions = {}) => {
|
|
17172
17973
|
const exitCode = await runRemoveCommand({
|
|
17173
17974
|
branch,
|
|
@@ -17193,23 +17994,27 @@ function attachCommandActions(program2, options) {
|
|
|
17193
17994
|
throw commanderExit(exitCode);
|
|
17194
17995
|
}
|
|
17195
17996
|
});
|
|
17196
|
-
program2.commands.find((command) => command.name() === "warp")?.action(
|
|
17197
|
-
|
|
17198
|
-
|
|
17199
|
-
|
|
17200
|
-
|
|
17201
|
-
|
|
17202
|
-
|
|
17203
|
-
|
|
17204
|
-
|
|
17205
|
-
|
|
17206
|
-
|
|
17207
|
-
|
|
17208
|
-
|
|
17209
|
-
|
|
17997
|
+
program2.commands.find((command) => command.name() === "warp")?.action(
|
|
17998
|
+
async (branch, commandOptions) => {
|
|
17999
|
+
const newFlag = commandOptions.new;
|
|
18000
|
+
const newWorktree = newFlag !== void 0 && newFlag !== false;
|
|
18001
|
+
const newBranch = typeof newFlag === "string" ? newFlag : void 0;
|
|
18002
|
+
const exitCode = await runWarpCommand({
|
|
18003
|
+
branch: newWorktree ? newBranch ?? branch : branch,
|
|
18004
|
+
cwd: options.cwd,
|
|
18005
|
+
json: commandOptions.json,
|
|
18006
|
+
newWorktree,
|
|
18007
|
+
stderr: options.stderr,
|
|
18008
|
+
stdout: options.stdout
|
|
18009
|
+
});
|
|
18010
|
+
if (exitCode !== 0) {
|
|
18011
|
+
throw commanderExit(exitCode);
|
|
18012
|
+
}
|
|
17210
18013
|
}
|
|
17211
|
-
|
|
17212
|
-
const configCommand = program2.commands.find(
|
|
18014
|
+
);
|
|
18015
|
+
const configCommand = program2.commands.find(
|
|
18016
|
+
(command) => command.name() === "config"
|
|
18017
|
+
);
|
|
17213
18018
|
configCommand?.action(async () => {
|
|
17214
18019
|
const exitCode = await runConfigCommand({
|
|
17215
18020
|
cwd: options.cwd,
|
|
@@ -17291,7 +18096,7 @@ async function main() {
|
|
|
17291
18096
|
}
|
|
17292
18097
|
async function warnIfMissingShellIntegration() {
|
|
17293
18098
|
try {
|
|
17294
|
-
const { config } = await loadGlobalConfig(
|
|
18099
|
+
const { config } = await loadGlobalConfig(homedir7());
|
|
17295
18100
|
if (!config.shellIntegration) {
|
|
17296
18101
|
const shellBin = (process.env.SHELL ?? "").split("/").at(-1);
|
|
17297
18102
|
const shellArg = shellBin && ["bash", "zsh", "fish"].includes(shellBin) ? ` ${shellBin}` : "";
|