@solaqua/gji 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -120
- package/dist/cli/commands/back.d.ts +1 -1
- package/dist/cli/commands/back.js +1 -0
- package/dist/cli/commands/clean.d.ts +1 -1
- package/dist/cli/commands/clean.js +11 -2
- package/dist/cli/commands/done.js +3 -2
- package/dist/cli/commands/go.js +18 -5
- package/dist/cli/commands/init.js +0 -14
- package/dist/cli/commands/new.d.ts +2 -1
- package/dist/cli/commands/new.js +71 -63
- package/dist/cli/commands/open.js +1 -1
- package/dist/cli/commands/pr-open.js +2 -2
- package/dist/cli/commands/pr.js +17 -6
- package/dist/cli/commands/run-hook.js +4 -0
- package/dist/cli/commands/undo.js +10 -74
- package/dist/cli/dependencies.d.ts +5 -1
- package/dist/cli/dependencies.js +7 -2
- package/dist/cli/program.js +1 -56
- package/dist/cli/runtime/headless.d.ts +3 -3
- package/dist/cli/runtime/headless.js +6 -4
- package/dist/gji-bundle.mjs +500 -842
- package/dist/infrastructure/filesystem/locked-file.d.ts +4 -0
- package/dist/infrastructure/filesystem/locked-file.js +53 -0
- package/dist/infrastructure/git/runner.d.ts +7 -0
- package/dist/infrastructure/git/runner.js +35 -1
- package/dist/infrastructure/persistence/history.js +12 -10
- package/dist/infrastructure/persistence/slots.js +24 -21
- package/dist/infrastructure/process/hooks.js +2 -0
- package/dist/infrastructure/repository/registry.js +11 -41
- package/dist/infrastructure/worktree/lifecycle.d.ts +25 -0
- package/dist/infrastructure/worktree/lifecycle.js +120 -19
- package/dist/ports/git.d.ts +1 -0
- package/dist/ports/hooks.d.ts +7 -0
- package/dist/presentation/shell/completion.js +7 -30
- package/dist/presentation/terminal/context-card.js +1 -1
- 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-doctor.1 +1 -1
- package/man/man1/gji-done.1 +1 -1
- package/man/man1/gji-go.1 +1 -1
- package/man/man1/gji-history.1 +1 -1
- package/man/man1/gji-init.1 +1 -1
- package/man/man1/gji-ls.1 +1 -1
- package/man/man1/gji-new.1 +1 -1
- package/man/man1/gji-open.1 +1 -1
- package/man/man1/gji-pr.1 +1 -1
- package/man/man1/gji-root.1 +1 -1
- package/man/man1/gji-run-hook.1 +3 -3
- package/man/man1/gji-status.1 +1 -1
- package/man/man1/gji-sync-files.1 +1 -1
- package/man/man1/gji-sync.1 +1 -1
- package/man/man1/gji-task.1 +1 -1
- package/man/man1/gji-undo.1 +1 -1
- package/man/man1/gji.1 +2 -12
- package/package.json +3 -2
- package/dist/cli/commands/remove.d.ts +0 -21
- package/dist/cli/commands/remove.js +0 -189
- package/dist/cli/commands/warp.d.ts +0 -28
- package/dist/cli/commands/warp.js +0 -123
- package/man/man1/gji-remove.1 +0 -19
- package/man/man1/gji-warp.1 +0 -16
package/dist/gji-bundle.mjs
CHANGED
|
@@ -3526,7 +3526,7 @@ var require_polyfills = __commonJS({
|
|
|
3526
3526
|
}
|
|
3527
3527
|
if (platform3 === "win32") {
|
|
3528
3528
|
fs5.rename = typeof fs5.rename !== "function" ? fs5.rename : (function(fs$rename) {
|
|
3529
|
-
function
|
|
3529
|
+
function rename2(from, to, cb) {
|
|
3530
3530
|
var start = Date.now();
|
|
3531
3531
|
var backoff = 0;
|
|
3532
3532
|
fs$rename(from, to, function CB(er) {
|
|
@@ -3546,8 +3546,8 @@ var require_polyfills = __commonJS({
|
|
|
3546
3546
|
if (cb) cb(er);
|
|
3547
3547
|
});
|
|
3548
3548
|
}
|
|
3549
|
-
if (Object.setPrototypeOf) Object.setPrototypeOf(
|
|
3550
|
-
return
|
|
3549
|
+
if (Object.setPrototypeOf) Object.setPrototypeOf(rename2, fs$rename);
|
|
3550
|
+
return rename2;
|
|
3551
3551
|
})(fs5.rename);
|
|
3552
3552
|
}
|
|
3553
3553
|
fs5.read = typeof fs5.read !== "function" ? fs5.read : (function(fs$read) {
|
|
@@ -3968,8 +3968,8 @@ var require_graceful_fs = __commonJS({
|
|
|
3968
3968
|
}
|
|
3969
3969
|
}
|
|
3970
3970
|
var fs$writeFile = fs6.writeFile;
|
|
3971
|
-
fs6.writeFile =
|
|
3972
|
-
function
|
|
3971
|
+
fs6.writeFile = writeFile7;
|
|
3972
|
+
function writeFile7(path9, data, options, cb) {
|
|
3973
3973
|
if (typeof options === "function")
|
|
3974
3974
|
cb = options, options = null;
|
|
3975
3975
|
return go$writeFile(path9, data, options, cb);
|
|
@@ -5500,7 +5500,7 @@ var require_polyfills2 = __commonJS({
|
|
|
5500
5500
|
}
|
|
5501
5501
|
if (platform3 === "win32") {
|
|
5502
5502
|
fs5.rename = typeof fs5.rename !== "function" ? fs5.rename : (function(fs$rename) {
|
|
5503
|
-
function
|
|
5503
|
+
function rename2(from, to, cb) {
|
|
5504
5504
|
var start = Date.now();
|
|
5505
5505
|
var backoff = 0;
|
|
5506
5506
|
fs$rename(from, to, function CB(er) {
|
|
@@ -5520,8 +5520,8 @@ var require_polyfills2 = __commonJS({
|
|
|
5520
5520
|
if (cb) cb(er);
|
|
5521
5521
|
});
|
|
5522
5522
|
}
|
|
5523
|
-
if (Object.setPrototypeOf) Object.setPrototypeOf(
|
|
5524
|
-
return
|
|
5523
|
+
if (Object.setPrototypeOf) Object.setPrototypeOf(rename2, fs$rename);
|
|
5524
|
+
return rename2;
|
|
5525
5525
|
})(fs5.rename);
|
|
5526
5526
|
}
|
|
5527
5527
|
fs5.read = typeof fs5.read !== "function" ? fs5.read : (function(fs$read) {
|
|
@@ -5942,8 +5942,8 @@ var require_graceful_fs2 = __commonJS({
|
|
|
5942
5942
|
}
|
|
5943
5943
|
}
|
|
5944
5944
|
var fs$writeFile = fs6.writeFile;
|
|
5945
|
-
fs6.writeFile =
|
|
5946
|
-
function
|
|
5945
|
+
fs6.writeFile = writeFile7;
|
|
5946
|
+
function writeFile7(path9, data, options, cb) {
|
|
5947
5947
|
if (typeof options === "function")
|
|
5948
5948
|
cb = options, options = null;
|
|
5949
5949
|
return go$writeFile(path9, data, options, cb);
|
|
@@ -6757,11 +6757,11 @@ var require_util = __commonJS({
|
|
|
6757
6757
|
if (files.includes("node_modules") || files.includes("package.json") || files.includes("package.json5") || files.includes("package.yaml") || files.includes("pnpm-workspace.yaml")) {
|
|
6758
6758
|
return name2;
|
|
6759
6759
|
}
|
|
6760
|
-
const
|
|
6761
|
-
if (
|
|
6760
|
+
const dirname12 = path9.dirname(name2);
|
|
6761
|
+
if (dirname12 === name2) {
|
|
6762
6762
|
return original;
|
|
6763
6763
|
}
|
|
6764
|
-
return find(
|
|
6764
|
+
return find(dirname12, original);
|
|
6765
6765
|
} catch (error) {
|
|
6766
6766
|
if (name2 === original) {
|
|
6767
6767
|
if (error.code === "ENOENT") {
|
|
@@ -9517,9 +9517,9 @@ var retryifyAsync = (fn, options) => {
|
|
|
9517
9517
|
throw error;
|
|
9518
9518
|
if (Date.now() >= timestamp)
|
|
9519
9519
|
throw error;
|
|
9520
|
-
const
|
|
9521
|
-
if (
|
|
9522
|
-
const delayPromise = new Promise((resolve13) => setTimeout(resolve13,
|
|
9520
|
+
const delay3 = Math.round(interval * Math.random());
|
|
9521
|
+
if (delay3 > 0) {
|
|
9522
|
+
const delayPromise = new Promise((resolve13) => setTimeout(resolve13, delay3));
|
|
9523
9523
|
return delayPromise.then(() => attempt.apply(void 0, args));
|
|
9524
9524
|
} else {
|
|
9525
9525
|
return attempt.apply(void 0, args);
|
|
@@ -12810,8 +12810,8 @@ function updateNotifier(options) {
|
|
|
12810
12810
|
// package.json
|
|
12811
12811
|
var package_default = {
|
|
12812
12812
|
name: "@solaqua/gji",
|
|
12813
|
-
version: "0.
|
|
12814
|
-
description: "Git worktree CLI for
|
|
12813
|
+
version: "0.16.0",
|
|
12814
|
+
description: "Git worktree CLI for switching tasks without disturbing your work.",
|
|
12815
12815
|
license: "MIT",
|
|
12816
12816
|
author: "sjquant",
|
|
12817
12817
|
type: "module",
|
|
@@ -12875,6 +12875,7 @@ var package_default = {
|
|
|
12875
12875
|
devDependencies: {
|
|
12876
12876
|
"@biomejs/biome": "^2.4.15",
|
|
12877
12877
|
"@j178/prek": "^0.3.13",
|
|
12878
|
+
"@resvg/resvg-js": "^2.6.2",
|
|
12878
12879
|
"@types/node": "^24.6.0",
|
|
12879
12880
|
"@vitest/coverage-v8": "3.2.4",
|
|
12880
12881
|
esbuild: "^0.27.0",
|
|
@@ -13859,18 +13860,44 @@ import { promisify as promisify5 } from "node:util";
|
|
|
13859
13860
|
import { execFile as execFile3 } from "node:child_process";
|
|
13860
13861
|
import { promisify as promisify4 } from "node:util";
|
|
13861
13862
|
var execFileAsync3 = promisify4(execFile3);
|
|
13863
|
+
var CONTENTION_RETRY_TIMEOUT_MS = 1e4;
|
|
13864
|
+
var CONTENTION_RETRY_MAX_DELAY_MS = 1e3;
|
|
13865
|
+
var GIT_ENV = { ...process.env, LC_ALL: "C" };
|
|
13862
13866
|
async function runGit(cwd, args) {
|
|
13863
13867
|
return (await runGitRaw(cwd, args)).trim();
|
|
13864
13868
|
}
|
|
13865
13869
|
async function runGitRaw(cwd, args) {
|
|
13870
|
+
return execGit(cwd, args);
|
|
13871
|
+
}
|
|
13872
|
+
async function runGitRetryingContention(cwd, args) {
|
|
13873
|
+
return (await retryGitContention(() => execGit(cwd, args, GIT_ENV))).trim();
|
|
13874
|
+
}
|
|
13875
|
+
async function retryGitContention(operation, timeoutMs = CONTENTION_RETRY_TIMEOUT_MS) {
|
|
13876
|
+
const deadline = Date.now() + timeoutMs;
|
|
13877
|
+
for (let delayMs = 25; ; delayMs *= 2) {
|
|
13878
|
+
try {
|
|
13879
|
+
return await operation();
|
|
13880
|
+
} catch (error) {
|
|
13881
|
+
if (!isTransientGitError(error) || Date.now() >= deadline) throw error;
|
|
13882
|
+
}
|
|
13883
|
+
const jitteredDelayMs = Math.min(delayMs, CONTENTION_RETRY_MAX_DELAY_MS) * (0.5 + Math.random() / 2);
|
|
13884
|
+
await new Promise((resolve13) => setTimeout(resolve13, jitteredDelayMs));
|
|
13885
|
+
}
|
|
13886
|
+
}
|
|
13887
|
+
async function execGit(cwd, args, env5) {
|
|
13866
13888
|
try {
|
|
13867
|
-
const { stdout: stdout3 } = await execFileAsync3("git", args, { cwd });
|
|
13889
|
+
const { stdout: stdout3 } = await execFileAsync3("git", args, { cwd, env: env5 });
|
|
13868
13890
|
return stdout3;
|
|
13869
13891
|
} catch (error) {
|
|
13870
13892
|
const message = error instanceof Error ? error.message : String(error);
|
|
13871
13893
|
throw new Error(`Git command failed in '${cwd}': ${message}`);
|
|
13872
13894
|
}
|
|
13873
13895
|
}
|
|
13896
|
+
function isTransientGitError(error) {
|
|
13897
|
+
return error instanceof Error && /(could not lock config file .*|Unable to create '.*\.lock'): File exists|cannot lock ref .*: is at .* but expected |failed to read .*\/commondir|Invalid path '.*\/worktrees\//.test(
|
|
13898
|
+
error.message
|
|
13899
|
+
);
|
|
13900
|
+
}
|
|
13874
13901
|
|
|
13875
13902
|
// src/infrastructure/git/refs.ts
|
|
13876
13903
|
var execFileAsync4 = promisify5(execFile4);
|
|
@@ -14690,9 +14717,72 @@ function levenshtein(a, b3) {
|
|
|
14690
14717
|
}
|
|
14691
14718
|
|
|
14692
14719
|
// src/infrastructure/persistence/history.ts
|
|
14693
|
-
import {
|
|
14720
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
14694
14721
|
import { homedir as homedir2 } from "node:os";
|
|
14695
|
-
import {
|
|
14722
|
+
import { join as join6, resolve as resolve6 } from "node:path";
|
|
14723
|
+
|
|
14724
|
+
// src/infrastructure/filesystem/locked-file.ts
|
|
14725
|
+
import { randomUUID } from "node:crypto";
|
|
14726
|
+
import {
|
|
14727
|
+
mkdir as mkdir3,
|
|
14728
|
+
open as open3,
|
|
14729
|
+
rename,
|
|
14730
|
+
rm,
|
|
14731
|
+
stat,
|
|
14732
|
+
unlink,
|
|
14733
|
+
writeFile as writeFile3
|
|
14734
|
+
} from "node:fs/promises";
|
|
14735
|
+
import { dirname as dirname4 } from "node:path";
|
|
14736
|
+
import { setTimeout as delay2 } from "node:timers/promises";
|
|
14737
|
+
var LOCK_STALE_AFTER_MS = 1e4;
|
|
14738
|
+
var LOCK_RETRY_DELAY_MS = 5;
|
|
14739
|
+
var LOCK_WAIT_TIMEOUT_MS = LOCK_STALE_AFTER_MS + LOCK_RETRY_DELAY_MS;
|
|
14740
|
+
async function withFileLock(path9, operation) {
|
|
14741
|
+
await mkdir3(dirname4(path9), { recursive: true });
|
|
14742
|
+
const release = await acquireFileLock(path9);
|
|
14743
|
+
try {
|
|
14744
|
+
return await operation();
|
|
14745
|
+
} finally {
|
|
14746
|
+
await release();
|
|
14747
|
+
}
|
|
14748
|
+
}
|
|
14749
|
+
async function writeJsonAtomic(path9, value) {
|
|
14750
|
+
const temporaryPath = `${path9}.tmp-${process.pid}-${randomUUID()}`;
|
|
14751
|
+
try {
|
|
14752
|
+
await writeFile3(
|
|
14753
|
+
temporaryPath,
|
|
14754
|
+
`${JSON.stringify(value, null, 2)}
|
|
14755
|
+
`,
|
|
14756
|
+
"utf8"
|
|
14757
|
+
);
|
|
14758
|
+
await rename(temporaryPath, path9);
|
|
14759
|
+
} catch (error) {
|
|
14760
|
+
await rm(temporaryPath, { force: true }).catch(() => void 0);
|
|
14761
|
+
throw error;
|
|
14762
|
+
}
|
|
14763
|
+
}
|
|
14764
|
+
async function acquireFileLock(path9) {
|
|
14765
|
+
const lockPath = `${path9}.lock`;
|
|
14766
|
+
const deadline = Date.now() + LOCK_WAIT_TIMEOUT_MS;
|
|
14767
|
+
while (Date.now() < deadline) {
|
|
14768
|
+
try {
|
|
14769
|
+
const handle = await open3(lockPath, "wx");
|
|
14770
|
+
return async () => {
|
|
14771
|
+
await handle.close();
|
|
14772
|
+
await unlink(lockPath).catch(() => void 0);
|
|
14773
|
+
};
|
|
14774
|
+
} catch (error) {
|
|
14775
|
+
if (!isAlreadyExistsError(error)) throw error;
|
|
14776
|
+
const lockStat = await stat(lockPath).catch(() => null);
|
|
14777
|
+
if (lockStat && Date.now() - lockStat.mtimeMs > LOCK_STALE_AFTER_MS)
|
|
14778
|
+
await unlink(lockPath).catch(() => void 0);
|
|
14779
|
+
await delay2(LOCK_RETRY_DELAY_MS);
|
|
14780
|
+
}
|
|
14781
|
+
}
|
|
14782
|
+
throw new Error(`timed out waiting for the lock on ${path9}`);
|
|
14783
|
+
}
|
|
14784
|
+
|
|
14785
|
+
// src/infrastructure/persistence/history.ts
|
|
14696
14786
|
var HISTORY_FILE_NAME = "history.json";
|
|
14697
14787
|
var MAX_HISTORY_ENTRIES = 50;
|
|
14698
14788
|
function HISTORY_FILE_PATH(home = homedir2()) {
|
|
@@ -14715,15 +14805,15 @@ async function loadHistory(home = homedir2()) {
|
|
|
14715
14805
|
}
|
|
14716
14806
|
async function appendHistory(path9, branch, home = homedir2()) {
|
|
14717
14807
|
const historyPath = HISTORY_FILE_PATH(home);
|
|
14718
|
-
|
|
14719
|
-
|
|
14720
|
-
|
|
14721
|
-
|
|
14722
|
-
|
|
14723
|
-
|
|
14724
|
-
|
|
14725
|
-
|
|
14726
|
-
|
|
14808
|
+
await withFileLock(historyPath, async () => {
|
|
14809
|
+
const existing = await loadHistory(home);
|
|
14810
|
+
const entry = { branch, path: path9, timestamp: Date.now() };
|
|
14811
|
+
const next = [
|
|
14812
|
+
entry,
|
|
14813
|
+
...existing.filter((existingEntry) => existingEntry.path !== path9)
|
|
14814
|
+
].slice(0, MAX_HISTORY_ENTRIES);
|
|
14815
|
+
await writeJsonAtomic(historyPath, next);
|
|
14816
|
+
});
|
|
14727
14817
|
}
|
|
14728
14818
|
async function recordWorktreeUsage(path9, branch, home = homedir2()) {
|
|
14729
14819
|
try {
|
|
@@ -14736,9 +14826,9 @@ function isHistoryEntry(value) {
|
|
|
14736
14826
|
}
|
|
14737
14827
|
|
|
14738
14828
|
// src/infrastructure/persistence/slots.ts
|
|
14739
|
-
import {
|
|
14829
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
14740
14830
|
import { homedir as homedir3 } from "node:os";
|
|
14741
|
-
import {
|
|
14831
|
+
import { join as join7, resolve as resolve7 } from "node:path";
|
|
14742
14832
|
function SLOTS_FILE_PATH(home = homedir3()) {
|
|
14743
14833
|
const configDir = process.env.GJI_CONFIG_DIR;
|
|
14744
14834
|
return configDir ? join7(resolve7(configDir), "slots.json") : join7(home, GLOBAL_CONFIG_DIRECTORY, "slots.json");
|
|
@@ -14755,32 +14845,33 @@ async function loadSlots(home = homedir3()) {
|
|
|
14755
14845
|
}
|
|
14756
14846
|
}
|
|
14757
14847
|
async function assignWorktreeSlot(repoRoot, worktreePath, home = homedir3()) {
|
|
14758
|
-
|
|
14759
|
-
|
|
14760
|
-
|
|
14761
|
-
|
|
14762
|
-
slots[repoRoot]
|
|
14763
|
-
|
|
14764
|
-
|
|
14765
|
-
|
|
14766
|
-
|
|
14767
|
-
|
|
14848
|
+
return withFileLock(SLOTS_FILE_PATH(home), async () => {
|
|
14849
|
+
const slots = await loadSlots(home);
|
|
14850
|
+
const existing = slots[worktreePath];
|
|
14851
|
+
if (existing !== void 0) return existing;
|
|
14852
|
+
if (worktreePath !== repoRoot && slots[repoRoot] === void 0)
|
|
14853
|
+
slots[repoRoot] = 0;
|
|
14854
|
+
const used = new Set(Object.values(slots));
|
|
14855
|
+
const slot = worktreePath === repoRoot ? 0 : nextAvailableSlot(used);
|
|
14856
|
+
slots[worktreePath] = slot;
|
|
14857
|
+
await saveSlots(slots, home);
|
|
14858
|
+
return slot;
|
|
14859
|
+
});
|
|
14768
14860
|
}
|
|
14769
14861
|
async function getWorktreeSlot(worktreePath, home = homedir3()) {
|
|
14770
14862
|
const slot = (await loadSlots(home))[worktreePath];
|
|
14771
14863
|
return slot === void 0 ? null : slot;
|
|
14772
14864
|
}
|
|
14773
14865
|
async function releaseWorktreeSlot(worktreePath, home = homedir3()) {
|
|
14774
|
-
|
|
14775
|
-
|
|
14776
|
-
|
|
14777
|
-
|
|
14866
|
+
await withFileLock(SLOTS_FILE_PATH(home), async () => {
|
|
14867
|
+
const slots = await loadSlots(home);
|
|
14868
|
+
if (!(worktreePath in slots)) return;
|
|
14869
|
+
delete slots[worktreePath];
|
|
14870
|
+
await saveSlots(slots, home);
|
|
14871
|
+
});
|
|
14778
14872
|
}
|
|
14779
14873
|
async function saveSlots(slots, home) {
|
|
14780
|
-
|
|
14781
|
-
await mkdir4(dirname5(path9), { recursive: true });
|
|
14782
|
-
await writeFile4(path9, `${JSON.stringify(slots, null, 2)}
|
|
14783
|
-
`, "utf8");
|
|
14874
|
+
await writeJsonAtomic(SLOTS_FILE_PATH(home), slots);
|
|
14784
14875
|
}
|
|
14785
14876
|
function nextAvailableSlot(used) {
|
|
14786
14877
|
let slot = 1;
|
|
@@ -14792,16 +14883,16 @@ function isSlotStore(value) {
|
|
|
14792
14883
|
}
|
|
14793
14884
|
|
|
14794
14885
|
// src/infrastructure/persistence/task.ts
|
|
14795
|
-
import { readFile as readFile5, unlink, writeFile as
|
|
14886
|
+
import { readFile as readFile5, unlink as unlink2, writeFile as writeFile4 } from "node:fs/promises";
|
|
14796
14887
|
import { join as join8 } from "node:path";
|
|
14797
14888
|
|
|
14798
14889
|
// src/infrastructure/repository/context.ts
|
|
14799
|
-
import { basename as basename3, dirname as
|
|
14890
|
+
import { basename as basename3, dirname as dirname5, isAbsolute as isAbsolute4, resolve as resolve8 } from "node:path";
|
|
14800
14891
|
async function detectRepository(cwd) {
|
|
14801
14892
|
const currentRoot = await runGit(cwd, ["rev-parse", "--show-toplevel"]);
|
|
14802
14893
|
const rawCommonDir = await runGit(cwd, ["rev-parse", "--git-common-dir"]);
|
|
14803
14894
|
const gitCommonDir = isAbsolute4(rawCommonDir) ? rawCommonDir : resolve8(currentRoot, rawCommonDir);
|
|
14804
|
-
const repoRoot =
|
|
14895
|
+
const repoRoot = dirname5(gitCommonDir);
|
|
14805
14896
|
return {
|
|
14806
14897
|
currentRoot,
|
|
14807
14898
|
isWorktree: currentRoot !== repoRoot,
|
|
@@ -14828,13 +14919,13 @@ async function readTask(worktreePath) {
|
|
|
14828
14919
|
async function writeTask(worktreePath, task, source = "manual") {
|
|
14829
14920
|
const metadata = { source, task, updatedAt: Date.now() };
|
|
14830
14921
|
const path9 = await taskPath(worktreePath);
|
|
14831
|
-
await
|
|
14922
|
+
await writeFile4(path9, `${JSON.stringify(metadata, null, 2)}
|
|
14832
14923
|
`, "utf8");
|
|
14833
14924
|
return metadata;
|
|
14834
14925
|
}
|
|
14835
14926
|
async function clearTask(worktreePath) {
|
|
14836
14927
|
try {
|
|
14837
|
-
await
|
|
14928
|
+
await unlink2(await taskPath(worktreePath));
|
|
14838
14929
|
} catch {
|
|
14839
14930
|
}
|
|
14840
14931
|
}
|
|
@@ -14946,10 +15037,16 @@ function extractHooks(config) {
|
|
|
14946
15037
|
}
|
|
14947
15038
|
const hooks = raw;
|
|
14948
15039
|
return {
|
|
15040
|
+
"after-branch-create": parseHookCommand(
|
|
15041
|
+
hooks["after-branch-create"] ?? hooks.afterBranchCreate
|
|
15042
|
+
),
|
|
14949
15043
|
"after-create": parseHookCommand(
|
|
14950
15044
|
hooks["after-create"] ?? hooks.afterCreate
|
|
14951
15045
|
),
|
|
14952
15046
|
"after-enter": parseHookCommand(hooks["after-enter"] ?? hooks.afterEnter),
|
|
15047
|
+
"after-switch": parseHookCommand(
|
|
15048
|
+
hooks["after-switch"] ?? hooks.afterSwitch
|
|
15049
|
+
),
|
|
14953
15050
|
"before-remove": parseHookCommand(
|
|
14954
15051
|
hooks["before-remove"] ?? hooks.beforeRemove
|
|
14955
15052
|
)
|
|
@@ -14999,22 +15096,12 @@ async function getRepositoryRemoteUrl(repoRoot, remote) {
|
|
|
14999
15096
|
}
|
|
15000
15097
|
|
|
15001
15098
|
// src/infrastructure/repository/registry.ts
|
|
15002
|
-
import { randomUUID } from "node:crypto";
|
|
15003
15099
|
import { constants as constants4 } from "node:fs";
|
|
15004
|
-
import {
|
|
15005
|
-
access,
|
|
15006
|
-
mkdir as mkdir5,
|
|
15007
|
-
readFile as readFile6,
|
|
15008
|
-
realpath as realpath4,
|
|
15009
|
-
rename,
|
|
15010
|
-
rm,
|
|
15011
|
-
writeFile as writeFile6
|
|
15012
|
-
} from "node:fs/promises";
|
|
15100
|
+
import { access, readFile as readFile6, realpath as realpath4 } from "node:fs/promises";
|
|
15013
15101
|
import { homedir as homedir4 } from "node:os";
|
|
15014
|
-
import { basename as basename4,
|
|
15102
|
+
import { basename as basename4, join as join9, resolve as resolve9 } from "node:path";
|
|
15015
15103
|
var REGISTRY_FILE_NAME = "repos.json";
|
|
15016
15104
|
var MAX_REGISTRY_ENTRIES = 100;
|
|
15017
|
-
var registryLocks = /* @__PURE__ */ new Map();
|
|
15018
15105
|
function REGISTRY_FILE_PATH(home = homedir4()) {
|
|
15019
15106
|
const configDir = process.env.GJI_CONFIG_DIR;
|
|
15020
15107
|
if (configDir) {
|
|
@@ -15042,9 +15129,10 @@ async function canonicalizeRepoPath(repoPath) {
|
|
|
15042
15129
|
}
|
|
15043
15130
|
async function registerRepo(repoPath, home = homedir4()) {
|
|
15044
15131
|
const registryPath = REGISTRY_FILE_PATH(home);
|
|
15045
|
-
|
|
15132
|
+
const canonicalRepoPath = await canonicalizeRepoPath(repoPath);
|
|
15133
|
+
if ((await loadRegistry(home))[0]?.path === canonicalRepoPath) return;
|
|
15134
|
+
await withFileLock(registryPath, async () => {
|
|
15046
15135
|
const existing = await normalizeRegistryForWrite(await loadRegistry(home));
|
|
15047
|
-
const canonicalRepoPath = await canonicalizeRepoPath(repoPath);
|
|
15048
15136
|
if (existing.length > 0 && existing[0].path === canonicalRepoPath) return;
|
|
15049
15137
|
const entry = {
|
|
15050
15138
|
lastUsed: Date.now(),
|
|
@@ -15053,13 +15141,13 @@ async function registerRepo(repoPath, home = homedir4()) {
|
|
|
15053
15141
|
};
|
|
15054
15142
|
const filtered = existing.filter((e2) => e2.path !== canonicalRepoPath);
|
|
15055
15143
|
const next = [entry, ...filtered].slice(0, MAX_REGISTRY_ENTRIES);
|
|
15056
|
-
await
|
|
15144
|
+
await writeJsonAtomic(registryPath, next);
|
|
15057
15145
|
});
|
|
15058
15146
|
}
|
|
15059
15147
|
async function removeMissingRegistryEntries(paths, home = homedir4()) {
|
|
15060
15148
|
if (paths.size === 0) return { removedPaths: [], skippedPaths: [] };
|
|
15061
15149
|
const registryPath = REGISTRY_FILE_PATH(home);
|
|
15062
|
-
return
|
|
15150
|
+
return withFileLock(registryPath, async () => {
|
|
15063
15151
|
const entries = await loadRegistry(home);
|
|
15064
15152
|
const candidates = entries.filter((entry) => paths.has(entry.path));
|
|
15065
15153
|
const status = await Promise.all(
|
|
@@ -15071,31 +15159,14 @@ async function removeMissingRegistryEntries(paths, home = homedir4()) {
|
|
|
15071
15159
|
const removedPaths = status.filter(({ missing }) => missing).map(({ entry }) => entry.path);
|
|
15072
15160
|
const skippedPaths = status.filter(({ missing }) => !missing).map(({ entry }) => entry.path);
|
|
15073
15161
|
if (removedPaths.length > 0) {
|
|
15074
|
-
await
|
|
15075
|
-
|
|
15076
|
-
|
|
15162
|
+
await writeJsonAtomic(
|
|
15163
|
+
registryPath,
|
|
15164
|
+
entries.filter((entry) => !removedPaths.includes(entry.path))
|
|
15077
15165
|
);
|
|
15078
15166
|
}
|
|
15079
15167
|
return { removedPaths, skippedPaths };
|
|
15080
15168
|
});
|
|
15081
15169
|
}
|
|
15082
|
-
async function withRegistryLock(registryPath, operation) {
|
|
15083
|
-
const previous = registryLocks.get(registryPath);
|
|
15084
|
-
let release;
|
|
15085
|
-
const current = new Promise((resolve13) => {
|
|
15086
|
-
release = resolve13;
|
|
15087
|
-
});
|
|
15088
|
-
registryLocks.set(registryPath, current);
|
|
15089
|
-
if (previous) await previous;
|
|
15090
|
-
try {
|
|
15091
|
-
return await operation();
|
|
15092
|
-
} finally {
|
|
15093
|
-
release();
|
|
15094
|
-
if (registryLocks.get(registryPath) === current) {
|
|
15095
|
-
registryLocks.delete(registryPath);
|
|
15096
|
-
}
|
|
15097
|
-
}
|
|
15098
|
-
}
|
|
15099
15170
|
async function isMissingPath(path9) {
|
|
15100
15171
|
try {
|
|
15101
15172
|
await access(path9, constants4.F_OK);
|
|
@@ -15109,22 +15180,6 @@ function isMissingPathError(error) {
|
|
|
15109
15180
|
const code = error.code;
|
|
15110
15181
|
return code === "ENOENT" || code === "ENOTDIR";
|
|
15111
15182
|
}
|
|
15112
|
-
async function writeRegistry(entries, registryPath) {
|
|
15113
|
-
await mkdir5(dirname7(registryPath), { recursive: true });
|
|
15114
|
-
const temporaryPath = `${registryPath}.tmp-${process.pid}-${randomUUID()}`;
|
|
15115
|
-
try {
|
|
15116
|
-
await writeFile6(
|
|
15117
|
-
temporaryPath,
|
|
15118
|
-
`${JSON.stringify(entries, null, 2)}
|
|
15119
|
-
`,
|
|
15120
|
-
"utf8"
|
|
15121
|
-
);
|
|
15122
|
-
await rename(temporaryPath, registryPath);
|
|
15123
|
-
} catch (error) {
|
|
15124
|
-
await rm(temporaryPath, { force: true }).catch(() => void 0);
|
|
15125
|
-
throw error;
|
|
15126
|
-
}
|
|
15127
|
-
}
|
|
15128
15183
|
async function normalizeRegistryForWrite(entries) {
|
|
15129
15184
|
const normalized = [];
|
|
15130
15185
|
const seenPaths = /* @__PURE__ */ new Set();
|
|
@@ -15368,15 +15423,93 @@ function serializeWorktreeInfo(info) {
|
|
|
15368
15423
|
|
|
15369
15424
|
// src/infrastructure/worktree/lifecycle.ts
|
|
15370
15425
|
import { execFile as execFile7 } from "node:child_process";
|
|
15371
|
-
import { rm as rm2 } from "node:fs/promises";
|
|
15426
|
+
import { mkdir as mkdir4, rm as rm2, rmdir } from "node:fs/promises";
|
|
15372
15427
|
import { promisify as promisify8 } from "node:util";
|
|
15373
15428
|
var execFileAsync7 = promisify8(execFile7);
|
|
15374
|
-
var
|
|
15429
|
+
var BRANCH_CONFIG_CLEANUP_TIMEOUT_MS = 1e3;
|
|
15430
|
+
async function addWorktree(repoRoot, worktreePath, target) {
|
|
15431
|
+
await claimWorktreePath(worktreePath);
|
|
15432
|
+
if ("create" in target) {
|
|
15433
|
+
try {
|
|
15434
|
+
await createBranch(repoRoot, target.branch, target.create);
|
|
15435
|
+
} catch (error) {
|
|
15436
|
+
await rmdir(worktreePath).catch(() => void 0);
|
|
15437
|
+
throw error;
|
|
15438
|
+
}
|
|
15439
|
+
}
|
|
15440
|
+
const args = "branch" in target ? [target.branch] : ["--detach", ...target.detachedAt ? [target.detachedAt] : []];
|
|
15441
|
+
try {
|
|
15442
|
+
await retryGitContention(
|
|
15443
|
+
() => execFileAsync7("git", ["worktree", "add", worktreePath, ...args], {
|
|
15444
|
+
cwd: repoRoot
|
|
15445
|
+
})
|
|
15446
|
+
);
|
|
15447
|
+
} catch (error) {
|
|
15448
|
+
await removeCreatedWorktree(
|
|
15449
|
+
repoRoot,
|
|
15450
|
+
worktreePath,
|
|
15451
|
+
"create" in target ? target.branch : void 0
|
|
15452
|
+
);
|
|
15453
|
+
throw error;
|
|
15454
|
+
}
|
|
15455
|
+
}
|
|
15456
|
+
async function removeCreatedWorktree(repoRoot, worktreePath, createdBranch) {
|
|
15457
|
+
await runLifecycleGit(repoRoot, [
|
|
15458
|
+
"worktree",
|
|
15459
|
+
"remove",
|
|
15460
|
+
"--force",
|
|
15461
|
+
worktreePath
|
|
15462
|
+
]).catch(() => void 0);
|
|
15463
|
+
await rmdir(worktreePath).catch(() => void 0);
|
|
15464
|
+
if (createdBranch) await deleteCreatedBranch(repoRoot, createdBranch);
|
|
15465
|
+
}
|
|
15466
|
+
async function claimWorktreePath(worktreePath) {
|
|
15467
|
+
try {
|
|
15468
|
+
await mkdir4(worktreePath);
|
|
15469
|
+
} catch (error) {
|
|
15470
|
+
if (!isAlreadyExistsError(error)) throw error;
|
|
15471
|
+
throw new Error(`target worktree path already exists: ${worktreePath}`);
|
|
15472
|
+
}
|
|
15473
|
+
}
|
|
15474
|
+
async function createBranch(repoRoot, branch, options = {}) {
|
|
15475
|
+
const args = [
|
|
15476
|
+
"branch",
|
|
15477
|
+
...options.track ? ["--track"] : [],
|
|
15478
|
+
branch,
|
|
15479
|
+
...options.startPoint ? [options.startPoint] : []
|
|
15480
|
+
];
|
|
15481
|
+
try {
|
|
15482
|
+
await retryGitContention(async () => {
|
|
15483
|
+
try {
|
|
15484
|
+
await execFileAsync7("git", args, { cwd: repoRoot, env: GIT_ENV });
|
|
15485
|
+
} catch (error) {
|
|
15486
|
+
if (isGitConfigLockError(error))
|
|
15487
|
+
await forceDeleteBranch(repoRoot, branch);
|
|
15488
|
+
throw error;
|
|
15489
|
+
}
|
|
15490
|
+
});
|
|
15491
|
+
} catch (error) {
|
|
15492
|
+
if (isGitConfigLockError(error))
|
|
15493
|
+
await removeBranchConfig(repoRoot, branch).catch(() => void 0);
|
|
15494
|
+
throw error;
|
|
15495
|
+
}
|
|
15496
|
+
}
|
|
15497
|
+
async function deleteCreatedBranch(repoRoot, branch) {
|
|
15498
|
+
try {
|
|
15499
|
+
await forceDeleteBranch(repoRoot, branch);
|
|
15500
|
+
await removeBranchConfig(repoRoot, branch);
|
|
15501
|
+
} catch {
|
|
15502
|
+
}
|
|
15503
|
+
}
|
|
15504
|
+
async function removeBranchConfig(repoRoot, branch) {
|
|
15505
|
+
await runLifecycleGit(
|
|
15506
|
+
repoRoot,
|
|
15507
|
+
["config", "--remove-section", `branch.${branch}`],
|
|
15508
|
+
BRANCH_CONFIG_CLEANUP_TIMEOUT_MS
|
|
15509
|
+
);
|
|
15510
|
+
}
|
|
15375
15511
|
async function removeWorktree(repoRoot, worktreePath) {
|
|
15376
|
-
await
|
|
15377
|
-
cwd: repoRoot,
|
|
15378
|
-
env: GIT_ENV
|
|
15379
|
-
});
|
|
15512
|
+
await runLifecycleGit(repoRoot, ["worktree", "remove", worktreePath]);
|
|
15380
15513
|
}
|
|
15381
15514
|
async function forceRemoveWorktree(repoRoot, worktreePath) {
|
|
15382
15515
|
try {
|
|
@@ -15397,10 +15530,12 @@ async function forceRemoveWorktree(repoRoot, worktreePath) {
|
|
|
15397
15530
|
}
|
|
15398
15531
|
}
|
|
15399
15532
|
async function removeWorktreeWithForce(repoRoot, worktreePath) {
|
|
15400
|
-
await
|
|
15401
|
-
|
|
15402
|
-
|
|
15403
|
-
|
|
15533
|
+
await runLifecycleGit(repoRoot, [
|
|
15534
|
+
"worktree",
|
|
15535
|
+
"remove",
|
|
15536
|
+
"--force",
|
|
15537
|
+
worktreePath
|
|
15538
|
+
]);
|
|
15404
15539
|
}
|
|
15405
15540
|
async function deinitializeSubmodules(worktreePath) {
|
|
15406
15541
|
await execFileAsync7("git", ["submodule", "deinit", "--force", "--all"], {
|
|
@@ -15409,16 +15544,10 @@ async function deinitializeSubmodules(worktreePath) {
|
|
|
15409
15544
|
});
|
|
15410
15545
|
}
|
|
15411
15546
|
async function deleteBranch(repoRoot, branch) {
|
|
15412
|
-
await
|
|
15413
|
-
cwd: repoRoot,
|
|
15414
|
-
env: GIT_ENV
|
|
15415
|
-
});
|
|
15547
|
+
await runLifecycleGit(repoRoot, ["branch", "-d", branch]);
|
|
15416
15548
|
}
|
|
15417
15549
|
async function forceDeleteBranch(repoRoot, branch) {
|
|
15418
|
-
await
|
|
15419
|
-
cwd: repoRoot,
|
|
15420
|
-
env: GIT_ENV
|
|
15421
|
-
});
|
|
15550
|
+
await runLifecycleGit(repoRoot, ["branch", "-D", branch]);
|
|
15422
15551
|
}
|
|
15423
15552
|
function isWorktreeForceRemovalError(error) {
|
|
15424
15553
|
if (!hasStderr(error)) {
|
|
@@ -15440,6 +15569,15 @@ function isBranchUnmergedError(error) {
|
|
|
15440
15569
|
function hasStderr(error) {
|
|
15441
15570
|
return error instanceof Error && "stderr" in error && typeof error.stderr === "string";
|
|
15442
15571
|
}
|
|
15572
|
+
async function runLifecycleGit(repoRoot, args, timeoutMs) {
|
|
15573
|
+
await retryGitContention(
|
|
15574
|
+
() => execFileAsync7("git", args, { cwd: repoRoot, env: GIT_ENV }),
|
|
15575
|
+
timeoutMs
|
|
15576
|
+
);
|
|
15577
|
+
}
|
|
15578
|
+
function isGitConfigLockError(error) {
|
|
15579
|
+
return error instanceof Error && error.message.includes("could not lock config file");
|
|
15580
|
+
}
|
|
15443
15581
|
|
|
15444
15582
|
// src/presentation/worktree/format.ts
|
|
15445
15583
|
function formatUpstreamState(upstream) {
|
|
@@ -15507,6 +15645,11 @@ function createCliDependencies() {
|
|
|
15507
15645
|
git: {
|
|
15508
15646
|
runGit,
|
|
15509
15647
|
runGitRaw,
|
|
15648
|
+
runGitRetryingContention,
|
|
15649
|
+
addWorktree,
|
|
15650
|
+
createBranch,
|
|
15651
|
+
deleteCreatedBranch,
|
|
15652
|
+
removeCreatedWorktree,
|
|
15510
15653
|
isDirtyWorktree,
|
|
15511
15654
|
readWorktreeHealth,
|
|
15512
15655
|
isBranchMergedInto,
|
|
@@ -15650,7 +15793,8 @@ async function runBackCommand(options) {
|
|
|
15650
15793
|
{
|
|
15651
15794
|
branch: target.branch ?? void 0,
|
|
15652
15795
|
path: target.path,
|
|
15653
|
-
repo: basename6(repository.repoRoot)
|
|
15796
|
+
repo: basename6(repository.repoRoot),
|
|
15797
|
+
slot: await runtime.slots.getWorktreeSlot(target.path)
|
|
15654
15798
|
},
|
|
15655
15799
|
options.stderr
|
|
15656
15800
|
);
|
|
@@ -15692,7 +15836,7 @@ function formatAge(timestamp) {
|
|
|
15692
15836
|
|
|
15693
15837
|
// src/cli/commands/clean.ts
|
|
15694
15838
|
import { realpath as realpath5 } from "node:fs/promises";
|
|
15695
|
-
import { basename as basename8, dirname as
|
|
15839
|
+
import { basename as basename8, dirname as dirname7, join as join12, resolve as resolve11 } from "node:path";
|
|
15696
15840
|
|
|
15697
15841
|
// node_modules/.pnpm/@clack+core@0.5.0/node_modules/@clack/core/dist/index.mjs
|
|
15698
15842
|
var import_sisteransi = __toESM(require_src(), 1);
|
|
@@ -16544,7 +16688,7 @@ function globToRegExp(pattern) {
|
|
|
16544
16688
|
|
|
16545
16689
|
// src/domain/worktree/policy.ts
|
|
16546
16690
|
import { homedir as homedir5 } from "node:os";
|
|
16547
|
-
import { basename as basename7, dirname as
|
|
16691
|
+
import { basename as basename7, dirname as dirname6, join as join10 } from "node:path";
|
|
16548
16692
|
function resolveWorktreePath(repoRoot, branch, basePath) {
|
|
16549
16693
|
const segments = branch.split("/").filter(Boolean);
|
|
16550
16694
|
if (segments.length === 0) {
|
|
@@ -16555,7 +16699,7 @@ function resolveWorktreePath(repoRoot, branch, basePath) {
|
|
|
16555
16699
|
`Branch name '${branch}' contains an invalid path segment.`
|
|
16556
16700
|
);
|
|
16557
16701
|
}
|
|
16558
|
-
const base = basePath ? expandTildeInPath(basePath) : join10(
|
|
16702
|
+
const base = basePath ? expandTildeInPath(basePath) : join10(dirname6(repoRoot), "worktrees", basename7(repoRoot));
|
|
16559
16703
|
return join10(base, ...segments);
|
|
16560
16704
|
}
|
|
16561
16705
|
function validateBranchName(name) {
|
|
@@ -17594,27 +17738,14 @@ async function defaultConfirmForceDeleteBranch(branch) {
|
|
|
17594
17738
|
|
|
17595
17739
|
// src/cli/runtime/headless.ts
|
|
17596
17740
|
function isHeadless() {
|
|
17597
|
-
return process.env.GJI_NO_TUI === "1";
|
|
17741
|
+
return process.env.GJI_NO_TUI === "1" || process.stdin.isTTY !== true || process.stdout.isTTY !== true;
|
|
17598
17742
|
}
|
|
17599
17743
|
|
|
17600
17744
|
// src/cli/commands/undo.ts
|
|
17601
|
-
import {
|
|
17602
|
-
access as access4,
|
|
17603
|
-
mkdir as mkdir6,
|
|
17604
|
-
open as open3,
|
|
17605
|
-
readFile as readFile7,
|
|
17606
|
-
rename as rename2,
|
|
17607
|
-
stat,
|
|
17608
|
-
unlink as unlink2,
|
|
17609
|
-
writeFile as writeFile7
|
|
17610
|
-
} from "node:fs/promises";
|
|
17745
|
+
import { access as access4, readFile as readFile7 } from "node:fs/promises";
|
|
17611
17746
|
import { homedir as homedir6 } from "node:os";
|
|
17612
|
-
import {
|
|
17747
|
+
import { join as join11, resolve as resolve10 } from "node:path";
|
|
17613
17748
|
var MAX_UNDO_RECORDS = 20;
|
|
17614
|
-
var UNDO_LOCK_STALE_AFTER_MS = 6e4;
|
|
17615
|
-
var UNDO_LOCK_RETRY_DELAY_MS = 5;
|
|
17616
|
-
var UNDO_LOCK_WAIT_TIMEOUT_MS = UNDO_LOCK_STALE_AFTER_MS + UNDO_LOCK_RETRY_DELAY_MS;
|
|
17617
|
-
var undoLogQueues = /* @__PURE__ */ new Map();
|
|
17618
17749
|
function undoLogPath(home = homedir6(), globalConfigDirectory = defaultCliDependencies.configStore.GLOBAL_CONFIG_DIRECTORY) {
|
|
17619
17750
|
const configured = process.env.GJI_CONFIG_DIR;
|
|
17620
17751
|
return join11(
|
|
@@ -17668,39 +17799,31 @@ async function recordUndoOperation(op, repoRoot, worktrees, home = homedir6(), r
|
|
|
17668
17799
|
timestamp,
|
|
17669
17800
|
entries
|
|
17670
17801
|
};
|
|
17671
|
-
return
|
|
17672
|
-
home,
|
|
17673
|
-
|
|
17674
|
-
|
|
17675
|
-
|
|
17676
|
-
|
|
17677
|
-
|
|
17678
|
-
|
|
17679
|
-
|
|
17680
|
-
return record;
|
|
17681
|
-
},
|
|
17682
|
-
globalConfigDirectory
|
|
17683
|
-
);
|
|
17802
|
+
return withFileLock(undoLogPath(home, globalConfigDirectory), async () => {
|
|
17803
|
+
const existing = await loadUndoRecords(home, globalConfigDirectory);
|
|
17804
|
+
await writeUndoRecords(
|
|
17805
|
+
home,
|
|
17806
|
+
[record, ...existing].slice(0, MAX_UNDO_RECORDS),
|
|
17807
|
+
globalConfigDirectory
|
|
17808
|
+
);
|
|
17809
|
+
return record;
|
|
17810
|
+
});
|
|
17684
17811
|
}
|
|
17685
17812
|
async function finalizeUndoOperation(record, removedWorktrees, home = homedir6(), globalConfigDirectory = defaultCliDependencies.configStore.GLOBAL_CONFIG_DIRECTORY) {
|
|
17686
17813
|
const removedPaths = new Set(
|
|
17687
17814
|
removedWorktrees.map((worktree) => worktree.path)
|
|
17688
17815
|
);
|
|
17689
|
-
await
|
|
17690
|
-
home,
|
|
17691
|
-
|
|
17692
|
-
|
|
17693
|
-
const
|
|
17694
|
-
|
|
17695
|
-
|
|
17696
|
-
|
|
17697
|
-
|
|
17698
|
-
|
|
17699
|
-
|
|
17700
|
-
await writeUndoRecords(home, remaining, globalConfigDirectory);
|
|
17701
|
-
},
|
|
17702
|
-
globalConfigDirectory
|
|
17703
|
-
);
|
|
17816
|
+
await withFileLock(undoLogPath(home, globalConfigDirectory), async () => {
|
|
17817
|
+
const records = await loadUndoRecords(home, globalConfigDirectory);
|
|
17818
|
+
const remaining = records.flatMap((candidate) => {
|
|
17819
|
+
if (candidate.id !== record.id) return [candidate];
|
|
17820
|
+
const entries = candidate.entries.filter(
|
|
17821
|
+
(entry) => removedPaths.has(entry.path)
|
|
17822
|
+
);
|
|
17823
|
+
return entries.length === 0 ? [] : [{ ...candidate, entries }];
|
|
17824
|
+
});
|
|
17825
|
+
await writeUndoRecords(home, remaining, globalConfigDirectory);
|
|
17826
|
+
});
|
|
17704
17827
|
}
|
|
17705
17828
|
async function restoreUndoRecord(record, home = homedir6(), runtime = defaultCliDependencies) {
|
|
17706
17829
|
const globalConfigDirectory = runtime.configStore.GLOBAL_CONFIG_DIRECTORY;
|
|
@@ -17724,19 +17847,15 @@ async function restoreUndoRecord(record, home = homedir6(), runtime = defaultCli
|
|
|
17724
17847
|
unresolved.push(entry);
|
|
17725
17848
|
}
|
|
17726
17849
|
}
|
|
17727
|
-
await
|
|
17728
|
-
home,
|
|
17729
|
-
|
|
17730
|
-
|
|
17731
|
-
|
|
17732
|
-
|
|
17733
|
-
|
|
17734
|
-
|
|
17735
|
-
|
|
17736
|
-
await writeUndoRecords(home, remaining, globalConfigDirectory);
|
|
17737
|
-
},
|
|
17738
|
-
globalConfigDirectory
|
|
17739
|
-
);
|
|
17850
|
+
await withFileLock(undoLogPath(home, globalConfigDirectory), async () => {
|
|
17851
|
+
const records = await loadUndoRecords(home, globalConfigDirectory);
|
|
17852
|
+
const remaining = records.flatMap((candidate) => {
|
|
17853
|
+
if (candidate.id !== record.id) return [candidate];
|
|
17854
|
+
if (unresolved.length === 0) return [];
|
|
17855
|
+
return [{ ...candidate, entries: unresolved }];
|
|
17856
|
+
});
|
|
17857
|
+
await writeUndoRecords(home, remaining, globalConfigDirectory);
|
|
17858
|
+
});
|
|
17740
17859
|
return { restored, failed };
|
|
17741
17860
|
}
|
|
17742
17861
|
async function runUndoCommand(options) {
|
|
@@ -17912,69 +18031,8 @@ async function assertCommitExists(repoRoot, headSha, runGit2) {
|
|
|
17912
18031
|
throw new Error("commit no longer exists (gc'd) \u2014 cannot restore");
|
|
17913
18032
|
}
|
|
17914
18033
|
}
|
|
17915
|
-
async function enqueueUndoLogWrite(home, operation, globalConfigDirectory = defaultCliDependencies.configStore.GLOBAL_CONFIG_DIRECTORY) {
|
|
17916
|
-
const path9 = undoLogPath(home, globalConfigDirectory);
|
|
17917
|
-
await mkdir6(dirname9(path9), { recursive: true });
|
|
17918
|
-
const previous = undoLogQueues.get(path9) ?? Promise.resolve();
|
|
17919
|
-
const runLocked = async () => {
|
|
17920
|
-
const release = await acquireUndoLogLock(path9);
|
|
17921
|
-
try {
|
|
17922
|
-
return await operation();
|
|
17923
|
-
} finally {
|
|
17924
|
-
await release();
|
|
17925
|
-
}
|
|
17926
|
-
};
|
|
17927
|
-
const current = previous.then(runLocked, runLocked);
|
|
17928
|
-
const settled = current.then(
|
|
17929
|
-
() => void 0,
|
|
17930
|
-
() => void 0
|
|
17931
|
-
);
|
|
17932
|
-
undoLogQueues.set(path9, settled);
|
|
17933
|
-
try {
|
|
17934
|
-
return await current;
|
|
17935
|
-
} finally {
|
|
17936
|
-
if (undoLogQueues.get(path9) === settled) undoLogQueues.delete(path9);
|
|
17937
|
-
}
|
|
17938
|
-
}
|
|
17939
|
-
async function acquireUndoLogLock(path9) {
|
|
17940
|
-
const lockPath = `${path9}.lock`;
|
|
17941
|
-
const deadline = Date.now() + UNDO_LOCK_WAIT_TIMEOUT_MS;
|
|
17942
|
-
while (Date.now() < deadline) {
|
|
17943
|
-
try {
|
|
17944
|
-
const handle = await open3(lockPath, "wx");
|
|
17945
|
-
return async () => {
|
|
17946
|
-
await handle.close();
|
|
17947
|
-
await unlink2(lockPath).catch(() => void 0);
|
|
17948
|
-
};
|
|
17949
|
-
} catch (error) {
|
|
17950
|
-
if (!(error instanceof Error && "code" in error && error.code === "EEXIST"))
|
|
17951
|
-
throw error;
|
|
17952
|
-
const lockStat = await stat(lockPath).catch(() => null);
|
|
17953
|
-
if (lockStat && Date.now() - lockStat.mtimeMs > UNDO_LOCK_STALE_AFTER_MS)
|
|
17954
|
-
await unlink2(lockPath).catch(() => void 0);
|
|
17955
|
-
const remainingMs = deadline - Date.now();
|
|
17956
|
-
if (remainingMs > 0)
|
|
17957
|
-
await new Promise(
|
|
17958
|
-
(resolve13) => setTimeout(resolve13, Math.min(UNDO_LOCK_RETRY_DELAY_MS, remainingMs))
|
|
17959
|
-
);
|
|
17960
|
-
}
|
|
17961
|
-
}
|
|
17962
|
-
throw new Error("timed out waiting for the undo journal lock");
|
|
17963
|
-
}
|
|
17964
18034
|
async function writeUndoRecords(home, records, globalConfigDirectory = defaultCliDependencies.configStore.GLOBAL_CONFIG_DIRECTORY) {
|
|
17965
|
-
|
|
17966
|
-
await mkdir6(dirname9(path9), { recursive: true });
|
|
17967
|
-
const temporaryPath = `${path9}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
17968
|
-
try {
|
|
17969
|
-
await writeFile7(temporaryPath, `${JSON.stringify(records, null, 2)}
|
|
17970
|
-
`, {
|
|
17971
|
-
encoding: "utf8",
|
|
17972
|
-
flag: "wx"
|
|
17973
|
-
});
|
|
17974
|
-
await rename2(temporaryPath, path9);
|
|
17975
|
-
} finally {
|
|
17976
|
-
await unlink2(temporaryPath).catch(() => void 0);
|
|
17977
|
-
}
|
|
18035
|
+
await writeJsonAtomic(undoLogPath(home, globalConfigDirectory), records);
|
|
17978
18036
|
}
|
|
17979
18037
|
function isUndoRecord(value) {
|
|
17980
18038
|
if (!value || typeof value !== "object") return false;
|
|
@@ -18029,7 +18087,8 @@ function createCleanCommand(dependencies = {}) {
|
|
|
18029
18087
|
runGit: runGit2
|
|
18030
18088
|
} = runtime.git;
|
|
18031
18089
|
const { loadEffectiveConfig: loadEffectiveConfig2 } = runtime.config;
|
|
18032
|
-
const { releaseWorktreeSlot: releaseWorktreeSlot2 } = runtime.slots;
|
|
18090
|
+
const { getWorktreeSlot: getWorktreeSlot2, releaseWorktreeSlot: releaseWorktreeSlot2 } = runtime.slots;
|
|
18091
|
+
const { extractHooks: extractHooks2, runHook: runHook2 } = runtime.hooks;
|
|
18033
18092
|
const sourceDependencies = {
|
|
18034
18093
|
...runtime.repositoryContext,
|
|
18035
18094
|
...runtime.repositoryRegistry,
|
|
@@ -18170,9 +18229,10 @@ function createCleanCommand(dependencies = {}) {
|
|
|
18170
18229
|
if (options.json) {
|
|
18171
18230
|
emitError(options, message);
|
|
18172
18231
|
} else {
|
|
18232
|
+
options.stderr(`gji clean: ${message} in non-interactive mode
|
|
18233
|
+
`);
|
|
18173
18234
|
options.stderr(
|
|
18174
|
-
|
|
18175
|
-
`
|
|
18235
|
+
"Hint: --force removes every candidate; use --dry-run to preview them first\n"
|
|
18176
18236
|
);
|
|
18177
18237
|
}
|
|
18178
18238
|
return 1;
|
|
@@ -18277,6 +18337,19 @@ function createCleanCommand(dependencies = {}) {
|
|
|
18277
18337
|
);
|
|
18278
18338
|
continue;
|
|
18279
18339
|
}
|
|
18340
|
+
const hooks = extractHooks2(await loadEffectiveConfig2(repoRoot));
|
|
18341
|
+
await runHook2(
|
|
18342
|
+
hooks["before-remove"],
|
|
18343
|
+
worktree.path,
|
|
18344
|
+
{
|
|
18345
|
+
branch: worktree.branch ?? void 0,
|
|
18346
|
+
path: worktree.path,
|
|
18347
|
+
repo: basename8(repoRoot),
|
|
18348
|
+
slot: await getWorktreeSlot2(worktree.path)
|
|
18349
|
+
},
|
|
18350
|
+
options.stderr,
|
|
18351
|
+
options.json ? () => void 0 : options.stdout
|
|
18352
|
+
);
|
|
18280
18353
|
try {
|
|
18281
18354
|
await removeWorktree2(repoRoot, worktree.path);
|
|
18282
18355
|
} catch (error) {
|
|
@@ -18399,7 +18472,7 @@ async function canonicalizePathPatterns(cwd, patterns) {
|
|
|
18399
18472
|
}
|
|
18400
18473
|
async function canonicalizeLiteralPrefix(pattern) {
|
|
18401
18474
|
const globIndex = findGlobIndex(pattern);
|
|
18402
|
-
let prefix = globIndex === -1 ? pattern :
|
|
18475
|
+
let prefix = globIndex === -1 ? pattern : dirname7(`${pattern.slice(0, globIndex)}x`);
|
|
18403
18476
|
let suffix = pattern.slice(prefix.length);
|
|
18404
18477
|
for (; ; ) {
|
|
18405
18478
|
try {
|
|
@@ -18408,7 +18481,7 @@ async function canonicalizeLiteralPrefix(pattern) {
|
|
|
18408
18481
|
);
|
|
18409
18482
|
return `${canonicalPrefix}${suffix}`.replace(/^\/\//, "/");
|
|
18410
18483
|
} catch {
|
|
18411
|
-
const parent =
|
|
18484
|
+
const parent = dirname7(prefix);
|
|
18412
18485
|
if (parent === prefix) return pattern;
|
|
18413
18486
|
suffix = join12("/", basename8(prefix), suffix);
|
|
18414
18487
|
prefix = parent;
|
|
@@ -18727,23 +18800,20 @@ var TOP_LEVEL_COMMANDS = [
|
|
|
18727
18800
|
},
|
|
18728
18801
|
{ name: "ls", description: "list active worktrees" },
|
|
18729
18802
|
{ name: "clean", description: "interactively prune linked worktrees" },
|
|
18730
|
-
{
|
|
18731
|
-
name: "remove",
|
|
18732
|
-
description: "deprecated: use done for one worktree or clean for bulk cleanup"
|
|
18733
|
-
},
|
|
18734
|
-
{ name: "rm", description: "deprecated alias of remove" },
|
|
18735
18803
|
{
|
|
18736
18804
|
name: "run-hook",
|
|
18737
18805
|
description: "run a named hook in the current worktree"
|
|
18738
18806
|
},
|
|
18739
|
-
{
|
|
18740
|
-
name: "warp",
|
|
18741
|
-
description: "deprecated: use go (press Tab for all known repos)"
|
|
18742
|
-
},
|
|
18743
18807
|
{ name: "config", description: "manage global config defaults" }
|
|
18744
18808
|
];
|
|
18745
18809
|
var SHELL_NAMES = ["bash", "fish", "zsh"];
|
|
18746
|
-
var HOOK_NAMES = [
|
|
18810
|
+
var HOOK_NAMES = [
|
|
18811
|
+
"after-branch-create",
|
|
18812
|
+
"after-create",
|
|
18813
|
+
"after-enter",
|
|
18814
|
+
"after-switch",
|
|
18815
|
+
"before-remove"
|
|
18816
|
+
];
|
|
18747
18817
|
var CONFIG_KEYS = Array.from(KNOWN_GLOBAL_CONFIG_KEYS);
|
|
18748
18818
|
function renderShellCompletion(shell) {
|
|
18749
18819
|
switch (shell) {
|
|
@@ -18922,15 +18992,9 @@ _gji_completion() {
|
|
|
18922
18992
|
COMPREPLY=( $(compgen -W "-f --force --stale --path --dry-run --json --help" -- "$cur") )
|
|
18923
18993
|
fi
|
|
18924
18994
|
;;
|
|
18925
|
-
remove|rm)
|
|
18926
|
-
COMPREPLY=( $(compgen -W "$(__gji_worktree_branches) -f --force --dry-run --json --help" -- "$cur") )
|
|
18927
|
-
;;
|
|
18928
18995
|
run-hook)
|
|
18929
18996
|
COMPREPLY=( $(compgen -W "${hooks} --help" -- "$cur") )
|
|
18930
18997
|
;;
|
|
18931
|
-
warp)
|
|
18932
|
-
COMPREPLY=( $(compgen -W "--print --json --help" -- "$cur") )
|
|
18933
|
-
;;
|
|
18934
18998
|
config)
|
|
18935
18999
|
if [ "$COMP_CWORD" -eq 2 ]; then
|
|
18936
19000
|
COMPREPLY=( $(compgen -W "get set unset" -- "$cur") )
|
|
@@ -19167,18 +19231,10 @@ complete -c gji -n '__fish_seen_subcommand_from clean' -l path -r -F -d 'only ta
|
|
|
19167
19231
|
complete -c gji -n '__fish_seen_subcommand_from clean' -l dry-run -d 'show what would be deleted without removing anything'
|
|
19168
19232
|
complete -c gji -n '__fish_seen_subcommand_from clean' -l json -d 'emit JSON on success or error instead of human-readable output'
|
|
19169
19233
|
|
|
19170
|
-
complete -c gji -n '__fish_seen_subcommand_from remove rm' -s f -l force -d 'bypass prompts, force-remove a dirty worktree, and force-delete an unmerged branch'
|
|
19171
|
-
complete -c gji -n '__fish_seen_subcommand_from remove rm' -l dry-run -d 'show what would be deleted without removing anything'
|
|
19172
|
-
complete -c gji -n '__fish_seen_subcommand_from remove rm' -l json -d 'emit JSON on success or error instead of human-readable output'
|
|
19173
|
-
complete -c gji -n '__fish_seen_subcommand_from remove rm' -a '(__gji_worktree_branches)' -d 'worktree branch'
|
|
19174
|
-
|
|
19175
19234
|
${hookLines}
|
|
19176
19235
|
|
|
19177
19236
|
complete -c gji -n '__fish_use_subcommand' -l json -d 'emit the repository hub as JSON'
|
|
19178
19237
|
|
|
19179
|
-
complete -c gji -n '__fish_seen_subcommand_from warp' -l print -d 'print the resolved worktree path without changing directory'
|
|
19180
|
-
complete -c gji -n '__fish_seen_subcommand_from warp' -l json -d 'emit JSON on success or error instead of human-readable output'
|
|
19181
|
-
|
|
19182
19238
|
complete -c gji -n '__fish_seen_subcommand_from config; and __gji_should_complete_config_action' -a 'get set unset' -d 'config action'
|
|
19183
19239
|
${configKeyLines}`;
|
|
19184
19240
|
}
|
|
@@ -19326,15 +19382,9 @@ case "\${words[2]}" in
|
|
|
19326
19382
|
clean)
|
|
19327
19383
|
_arguments '(-f --force)'{-f,--force}'[bypass prompts, force-remove dirty worktrees, and force-delete unmerged branches]' '--stale[only target clean worktrees whose upstream is gone and branch is merged into the default branch]' '*--path[only target worktrees under a directory or matching a path glob]:pattern:_files' '--dry-run[show what would be deleted without removing anything]' '--json[emit JSON on success or error instead of human-readable output]'
|
|
19328
19384
|
;;
|
|
19329
|
-
remove|rm)
|
|
19330
|
-
_arguments '(-f --force)'{-f,--force}'[bypass prompts, force-remove a dirty worktree, and force-delete an unmerged branch]' '--dry-run[show what would be deleted without removing anything]' '--json[emit JSON on success or error instead of human-readable output]' '2:branch:->worktrees'
|
|
19331
|
-
;;
|
|
19332
19385
|
run-hook)
|
|
19333
19386
|
_arguments "2:hook:(${hooks})"
|
|
19334
19387
|
;;
|
|
19335
|
-
warp)
|
|
19336
|
-
_arguments '--print[print the resolved worktree path without changing directory]' '--json[emit JSON on success or error instead of human-readable output]' '2:branch: '
|
|
19337
|
-
;;
|
|
19338
19388
|
config)
|
|
19339
19389
|
if (( CURRENT == 3 )); then
|
|
19340
19390
|
_values 'config action' get set unset
|
|
@@ -19462,7 +19512,7 @@ import { execFile as execFile8 } from "node:child_process";
|
|
|
19462
19512
|
import { constants as constants6 } from "node:fs";
|
|
19463
19513
|
import { access as access6, readFile as readFile8 } from "node:fs/promises";
|
|
19464
19514
|
import { homedir as homedir7 } from "node:os";
|
|
19465
|
-
import { basename as basename9, dirname as
|
|
19515
|
+
import { basename as basename9, dirname as dirname8, join as join14 } from "node:path";
|
|
19466
19516
|
import { promisify as promisify9 } from "node:util";
|
|
19467
19517
|
|
|
19468
19518
|
// src/presentation/shell/setup.ts
|
|
@@ -19911,7 +19961,7 @@ function resolveWorktreeBase(repoRoot, config, home) {
|
|
|
19911
19961
|
if (typeof configuredPath === "string" && (configuredPath.startsWith("/") || configuredPath.startsWith("~"))) {
|
|
19912
19962
|
return expandTilde2(configuredPath, home);
|
|
19913
19963
|
}
|
|
19914
|
-
return join14(
|
|
19964
|
+
return join14(dirname8(repoRoot), "worktrees", basename9(repoRoot));
|
|
19915
19965
|
}
|
|
19916
19966
|
async function findNearestExistingPath(path9) {
|
|
19917
19967
|
let candidate = path9;
|
|
@@ -19920,7 +19970,7 @@ async function findNearestExistingPath(path9) {
|
|
|
19920
19970
|
await access6(candidate, constants6.F_OK);
|
|
19921
19971
|
return candidate;
|
|
19922
19972
|
} catch {
|
|
19923
|
-
const parent =
|
|
19973
|
+
const parent = dirname8(candidate);
|
|
19924
19974
|
if (parent === candidate) return null;
|
|
19925
19975
|
candidate = parent;
|
|
19926
19976
|
}
|
|
@@ -20083,7 +20133,7 @@ async function runDoneCommand(options) {
|
|
|
20083
20133
|
} = runtime.git;
|
|
20084
20134
|
const { loadEffectiveConfig: loadEffectiveConfig2, resolveConfigString: resolveConfigString2 } = runtime.config;
|
|
20085
20135
|
const { loadHistory: loadHistory2 } = runtime.historyStore;
|
|
20086
|
-
const { releaseWorktreeSlot: releaseWorktreeSlot2 } = runtime.slots;
|
|
20136
|
+
const { getWorktreeSlot: getWorktreeSlot2, releaseWorktreeSlot: releaseWorktreeSlot2 } = runtime.slots;
|
|
20087
20137
|
const { extractHooks: extractHooks2, runHook: runHook2 } = runtime.hooks;
|
|
20088
20138
|
const { detectRepository: detectRepository2 } = runtime.repositoryContext;
|
|
20089
20139
|
const { listWorktrees: listWorktrees2 } = runtime.worktrees;
|
|
@@ -20179,9 +20229,11 @@ async function runDoneCommand(options) {
|
|
|
20179
20229
|
{
|
|
20180
20230
|
branch: target.branch ?? void 0,
|
|
20181
20231
|
path: target.path,
|
|
20182
|
-
repo: basename10(repository.repoRoot)
|
|
20232
|
+
repo: basename10(repository.repoRoot),
|
|
20233
|
+
slot: await getWorktreeSlot2(target.path)
|
|
20183
20234
|
},
|
|
20184
|
-
options.stderr
|
|
20235
|
+
options.stderr,
|
|
20236
|
+
options.json ? () => void 0 : options.stdout
|
|
20185
20237
|
);
|
|
20186
20238
|
try {
|
|
20187
20239
|
await removeWorktree2(repository.repoRoot, target.path);
|
|
@@ -20370,7 +20422,7 @@ function toMessage3(error) {
|
|
|
20370
20422
|
}
|
|
20371
20423
|
|
|
20372
20424
|
// src/cli/commands/go.ts
|
|
20373
|
-
import { basename as
|
|
20425
|
+
import { basename as basename12 } from "node:path";
|
|
20374
20426
|
|
|
20375
20427
|
// src/application/worktree/context-card.ts
|
|
20376
20428
|
async function loadContextCardModel(worktreePath, dependencies) {
|
|
@@ -20696,12 +20748,12 @@ function formatState(info) {
|
|
|
20696
20748
|
return `${info.status}${info.upstream.kind === "tracked" ? ` \xB7 \u2191${info.upstream.ahead} \u2193${info.upstream.behind}` : ""}`;
|
|
20697
20749
|
}
|
|
20698
20750
|
function formatLastCommit2(info) {
|
|
20699
|
-
return new Date(info.lastCommitTimestamp).toISOString();
|
|
20751
|
+
return new Date(info.lastCommitTimestamp * 1e3).toISOString();
|
|
20700
20752
|
}
|
|
20701
20753
|
|
|
20702
20754
|
// src/cli/commands/new.ts
|
|
20703
|
-
import { access as access8, mkdir as
|
|
20704
|
-
import { dirname as
|
|
20755
|
+
import { access as access8, mkdir as mkdir5, rmdir as rmdir2 } from "node:fs/promises";
|
|
20756
|
+
import { basename as basename11, dirname as dirname9, resolve as resolve12 } from "node:path";
|
|
20705
20757
|
|
|
20706
20758
|
// src/presentation/bootstrap/output.ts
|
|
20707
20759
|
function createBootstrapReporter(write, json) {
|
|
@@ -20816,10 +20868,8 @@ function createNewCommand(dependencies = {}) {
|
|
|
20816
20868
|
options.stderr(`${JSON.stringify({ error: message }, null, 2)}
|
|
20817
20869
|
`);
|
|
20818
20870
|
} else {
|
|
20819
|
-
options.stderr(
|
|
20820
|
-
|
|
20821
|
-
`
|
|
20822
|
-
);
|
|
20871
|
+
options.stderr(`gji new: ${message} in non-interactive mode
|
|
20872
|
+
`);
|
|
20823
20873
|
}
|
|
20824
20874
|
return 1;
|
|
20825
20875
|
}
|
|
@@ -20870,13 +20920,14 @@ function createNewCommand(dependencies = {}) {
|
|
|
20870
20920
|
if (options.force) {
|
|
20871
20921
|
if (!options.dryRun) {
|
|
20872
20922
|
try {
|
|
20873
|
-
await runtime.git.
|
|
20923
|
+
await runtime.git.runGitRetryingContention(repository.repoRoot, [
|
|
20874
20924
|
"worktree",
|
|
20875
20925
|
"remove",
|
|
20876
20926
|
"--force",
|
|
20877
20927
|
worktreePath
|
|
20878
20928
|
]);
|
|
20879
20929
|
} catch (err) {
|
|
20930
|
+
await rmdir2(worktreePath).catch(() => void 0);
|
|
20880
20931
|
if (!isNotRegisteredWorktreeError(err)) {
|
|
20881
20932
|
const msg = `could not remove existing worktree at ${worktreePath}: ${toExecMessage(err)}`;
|
|
20882
20933
|
if (options.json) {
|
|
@@ -20907,10 +20958,8 @@ function createNewCommand(dependencies = {}) {
|
|
|
20907
20958
|
options.stderr(`${JSON.stringify({ error: message }, null, 2)}
|
|
20908
20959
|
`);
|
|
20909
20960
|
} else {
|
|
20910
|
-
options.stderr(
|
|
20911
|
-
|
|
20912
|
-
`
|
|
20913
|
-
);
|
|
20961
|
+
options.stderr(`gji new: ${message} in non-interactive mode
|
|
20962
|
+
`);
|
|
20914
20963
|
options.stderr(
|
|
20915
20964
|
`Hint: Use 'gji done ${worktreeName}' or 'gji clean' to remove the existing worktree
|
|
20916
20965
|
`
|
|
@@ -21011,7 +21060,7 @@ ${formatDependencyBootstrapPreview(dryRunDependencyBootstrap)}`
|
|
|
21011
21060
|
}
|
|
21012
21061
|
return 0;
|
|
21013
21062
|
}
|
|
21014
|
-
await
|
|
21063
|
+
await mkdir5(dirname9(worktreePath), { recursive: true });
|
|
21015
21064
|
const startPoint = options.fromCurrent && !options.detached ? await resolveCurrentWorktreeHead(options.cwd, runtime.git.runGit) : void 0;
|
|
21016
21065
|
const takeStartPoint = options.take ? await resolveCurrentWorktreeHead(options.cwd, runtime.git.runGit) : void 0;
|
|
21017
21066
|
const changedTakeFiles = options.take ? await listTakeFiles(options.cwd, runtime.git.runGitRaw) : [];
|
|
@@ -21039,7 +21088,8 @@ ${formatDependencyBootstrapPreview(dryRunDependencyBootstrap)}`
|
|
|
21039
21088
|
worktreeName,
|
|
21040
21089
|
runtime
|
|
21041
21090
|
);
|
|
21042
|
-
const
|
|
21091
|
+
const createsBranch = !options.detached && options.mode !== "track" && options.mode !== "checkout" && !existingLocalBranch;
|
|
21092
|
+
const shouldRefreshBase = createsBranch && !options.noFetch && !options.fromCurrent && !options.take;
|
|
21043
21093
|
const freshBaseRef = shouldRefreshBase ? await resolveFreshBaseRef(
|
|
21044
21094
|
repository.repoRoot,
|
|
21045
21095
|
config,
|
|
@@ -21072,30 +21122,19 @@ ${formatDependencyBootstrapPreview(dryRunDependencyBootstrap)}`
|
|
|
21072
21122
|
);
|
|
21073
21123
|
}
|
|
21074
21124
|
}
|
|
21075
|
-
const
|
|
21076
|
-
"worktree",
|
|
21077
|
-
"add",
|
|
21078
|
-
"--detach",
|
|
21079
|
-
worktreePath,
|
|
21080
|
-
...takeStartPoint ? [takeStartPoint] : []
|
|
21081
|
-
] : options.mode === "track" ? [
|
|
21082
|
-
"worktree",
|
|
21083
|
-
"add",
|
|
21084
|
-
"--track",
|
|
21085
|
-
"-b",
|
|
21086
|
-
worktreeName,
|
|
21087
|
-
worktreePath,
|
|
21088
|
-
`${options.remote ?? "origin"}/${worktreeName}`
|
|
21089
|
-
] : options.mode === "checkout" || existingLocalBranch ? ["worktree", "add", worktreePath, worktreeName] : [
|
|
21090
|
-
"worktree",
|
|
21091
|
-
"add",
|
|
21092
|
-
"-b",
|
|
21093
|
-
worktreeName,
|
|
21094
|
-
worktreePath,
|
|
21095
|
-
...takeStartPoint ? [takeStartPoint] : startPoint ? [startPoint] : freshBaseRef ? [freshBaseRef] : []
|
|
21096
|
-
];
|
|
21125
|
+
const addsNewBranch = createsBranch || !options.detached && options.mode === "track";
|
|
21097
21126
|
try {
|
|
21098
|
-
await runtime.git.
|
|
21127
|
+
await runtime.git.addWorktree(
|
|
21128
|
+
repository.repoRoot,
|
|
21129
|
+
worktreePath,
|
|
21130
|
+
options.detached ? { detachedAt: takeStartPoint } : addsNewBranch ? {
|
|
21131
|
+
branch: worktreeName,
|
|
21132
|
+
create: {
|
|
21133
|
+
startPoint: options.mode === "track" ? `${options.remote ?? "origin"}/${worktreeName}` : takeStartPoint ?? startPoint ?? freshBaseRef,
|
|
21134
|
+
track: options.mode === "track"
|
|
21135
|
+
}
|
|
21136
|
+
} : { branch: worktreeName }
|
|
21137
|
+
);
|
|
21099
21138
|
} catch (error) {
|
|
21100
21139
|
if (stashSha)
|
|
21101
21140
|
await restoreTakeStash(
|
|
@@ -21117,15 +21156,11 @@ ${formatDependencyBootstrapPreview(dryRunDependencyBootstrap)}`
|
|
|
21117
21156
|
options.stderr,
|
|
21118
21157
|
runtime.git.runGit
|
|
21119
21158
|
)) {
|
|
21120
|
-
|
|
21121
|
-
|
|
21122
|
-
|
|
21123
|
-
|
|
21124
|
-
|
|
21125
|
-
worktreePath
|
|
21126
|
-
]);
|
|
21127
|
-
} catch {
|
|
21128
|
-
}
|
|
21159
|
+
await runtime.git.removeCreatedWorktree(
|
|
21160
|
+
repository.repoRoot,
|
|
21161
|
+
worktreePath,
|
|
21162
|
+
addsNewBranch ? worktreeName : void 0
|
|
21163
|
+
);
|
|
21129
21164
|
return emitNewError(
|
|
21130
21165
|
options,
|
|
21131
21166
|
`could not apply taken changes; your changes are safe in stash: ${stashSha}`
|
|
@@ -21161,6 +21196,17 @@ ${formatDependencyBootstrapPreview(dryRunDependencyBootstrap)}`
|
|
|
21161
21196
|
syncFiles: bootstrap.syncFileFailures
|
|
21162
21197
|
});
|
|
21163
21198
|
}
|
|
21199
|
+
if (createsBranch) {
|
|
21200
|
+
await runNewHooks({
|
|
21201
|
+
branch: worktreeName,
|
|
21202
|
+
config,
|
|
21203
|
+
hookNames: ["after-branch-create"],
|
|
21204
|
+
options,
|
|
21205
|
+
path: worktreePath,
|
|
21206
|
+
repository,
|
|
21207
|
+
runtime
|
|
21208
|
+
});
|
|
21209
|
+
}
|
|
21164
21210
|
if (options.json) {
|
|
21165
21211
|
const navigation = createNavigationTarget(
|
|
21166
21212
|
createNavigationRepository(repository.repoName, repository.repoRoot),
|
|
@@ -21293,7 +21339,7 @@ async function runBranchOnly(options, runtime, repository, config, rawBranch) {
|
|
|
21293
21339
|
try {
|
|
21294
21340
|
let startPoint = baseBranch;
|
|
21295
21341
|
if (!options.noFetch) {
|
|
21296
|
-
await runtime.git.
|
|
21342
|
+
await runtime.git.runGitRetryingContention(repository.repoRoot, [
|
|
21297
21343
|
"fetch",
|
|
21298
21344
|
"--prune",
|
|
21299
21345
|
remote,
|
|
@@ -21301,19 +21347,31 @@ async function runBranchOnly(options, runtime, repository, config, rawBranch) {
|
|
|
21301
21347
|
]);
|
|
21302
21348
|
startPoint = `${remote}/${baseBranch}`;
|
|
21303
21349
|
}
|
|
21304
|
-
await runtime.git.
|
|
21305
|
-
"switch",
|
|
21306
|
-
"-c",
|
|
21307
|
-
branchName,
|
|
21350
|
+
await runtime.git.createBranch(repository.repoRoot, branchName, {
|
|
21308
21351
|
startPoint
|
|
21309
|
-
|
|
21352
|
+
});
|
|
21353
|
+
try {
|
|
21354
|
+
await runtime.git.runGit(options.cwd, ["switch", branchName]);
|
|
21355
|
+
} catch (error) {
|
|
21356
|
+
await runtime.git.deleteCreatedBranch(repository.repoRoot, branchName);
|
|
21357
|
+
throw error;
|
|
21358
|
+
}
|
|
21310
21359
|
} catch (error) {
|
|
21311
21360
|
return emitNewError(
|
|
21312
21361
|
options,
|
|
21313
21362
|
`failed to create branch in the current worktree: ${toExecMessage(error)}`
|
|
21314
21363
|
);
|
|
21315
21364
|
}
|
|
21316
|
-
|
|
21365
|
+
await runNewHooks({
|
|
21366
|
+
branch: branchName,
|
|
21367
|
+
config,
|
|
21368
|
+
hookNames: ["after-branch-create", "after-switch"],
|
|
21369
|
+
options,
|
|
21370
|
+
path: repository.currentRoot,
|
|
21371
|
+
repository,
|
|
21372
|
+
runtime
|
|
21373
|
+
});
|
|
21374
|
+
if (options.json) {
|
|
21317
21375
|
const navigation = createNavigationTarget(
|
|
21318
21376
|
createNavigationRepository(repository.repoName, repository.repoRoot),
|
|
21319
21377
|
repository.currentRoot,
|
|
@@ -21340,6 +21398,32 @@ async function runBranchOnly(options, runtime, repository, config, rawBranch) {
|
|
|
21340
21398
|
}
|
|
21341
21399
|
return 0;
|
|
21342
21400
|
}
|
|
21401
|
+
async function runNewHooks({
|
|
21402
|
+
branch,
|
|
21403
|
+
config,
|
|
21404
|
+
hookNames,
|
|
21405
|
+
options,
|
|
21406
|
+
path: path9,
|
|
21407
|
+
repository,
|
|
21408
|
+
runtime
|
|
21409
|
+
}) {
|
|
21410
|
+
const hooks = runtime.hooks.extractHooks(config);
|
|
21411
|
+
const context = {
|
|
21412
|
+
branch,
|
|
21413
|
+
path: path9,
|
|
21414
|
+
repo: basename11(repository.repoRoot),
|
|
21415
|
+
slot: await runtime.slots.getWorktreeSlot(path9)
|
|
21416
|
+
};
|
|
21417
|
+
for (const hookName of hookNames) {
|
|
21418
|
+
await runtime.hooks.runHook(
|
|
21419
|
+
hooks[hookName],
|
|
21420
|
+
path9,
|
|
21421
|
+
context,
|
|
21422
|
+
options.json ? () => void 0 : options.stderr,
|
|
21423
|
+
options.json ? () => void 0 : options.stdout
|
|
21424
|
+
);
|
|
21425
|
+
}
|
|
21426
|
+
}
|
|
21343
21427
|
var runNewCommand = createNewCommand();
|
|
21344
21428
|
async function resolveFreshBaseRef(repoRoot, config, options, promptForFetchFailure, runtime) {
|
|
21345
21429
|
const remote = runtime.config.resolveConfigString(config, "syncRemote") ?? "origin";
|
|
@@ -21362,7 +21446,7 @@ async function resolveFreshBaseRef(repoRoot, config, options, promptForFetchFail
|
|
|
21362
21446
|
throw new Error("the remote default branch is unknown");
|
|
21363
21447
|
}
|
|
21364
21448
|
baseBranch = remoteBase.branch;
|
|
21365
|
-
await runtime.git.
|
|
21449
|
+
await runtime.git.runGitRetryingContention(repoRoot, [
|
|
21366
21450
|
"fetch",
|
|
21367
21451
|
"--prune",
|
|
21368
21452
|
remote,
|
|
@@ -21754,8 +21838,8 @@ function emitNewError(options, message, details) {
|
|
|
21754
21838
|
}
|
|
21755
21839
|
|
|
21756
21840
|
// src/cli/commands/pr.ts
|
|
21757
|
-
import { mkdir as
|
|
21758
|
-
import { dirname as
|
|
21841
|
+
import { mkdir as mkdir6 } from "node:fs/promises";
|
|
21842
|
+
import { dirname as dirname10 } from "node:path";
|
|
21759
21843
|
var PR_OUTPUT_FILE_ENV = "GJI_PR_OUTPUT_FILE";
|
|
21760
21844
|
function createPrCommand(dependencies = {}) {
|
|
21761
21845
|
const prompt = dependencies.promptForPathConflict ?? promptForPathConflict;
|
|
@@ -21813,10 +21897,8 @@ function createPrCommand(dependencies = {}) {
|
|
|
21813
21897
|
options.stderr(`${JSON.stringify({ error: message }, null, 2)}
|
|
21814
21898
|
`);
|
|
21815
21899
|
} else {
|
|
21816
|
-
options.stderr(
|
|
21817
|
-
|
|
21818
|
-
`
|
|
21819
|
-
);
|
|
21900
|
+
options.stderr(`gji pr: ${message} in non-interactive mode
|
|
21901
|
+
`);
|
|
21820
21902
|
options.stderr(
|
|
21821
21903
|
`Hint: Use 'gji done pr/${prNumber}' or 'gji clean' to remove the existing worktree
|
|
21822
21904
|
`
|
|
@@ -21894,14 +21976,29 @@ ${formatDependencyBootstrapPreview(dryRunDependencyBootstrap)}`
|
|
|
21894
21976
|
}
|
|
21895
21977
|
return 1;
|
|
21896
21978
|
}
|
|
21897
|
-
await
|
|
21979
|
+
await mkdir6(dirname10(worktreePath), { recursive: true });
|
|
21898
21980
|
const branchAlreadyExists = await localBranchExists2(
|
|
21899
21981
|
repository.repoRoot,
|
|
21900
21982
|
branchName,
|
|
21901
21983
|
runtime
|
|
21902
21984
|
);
|
|
21903
|
-
|
|
21904
|
-
|
|
21985
|
+
try {
|
|
21986
|
+
await runtime.git.addWorktree(
|
|
21987
|
+
repository.repoRoot,
|
|
21988
|
+
worktreePath,
|
|
21989
|
+
branchAlreadyExists ? { branch: branchName } : { branch: branchName, create: { startPoint: remoteRef } }
|
|
21990
|
+
);
|
|
21991
|
+
} catch (error) {
|
|
21992
|
+
const message = `failed to create worktree: ${error instanceof Error ? error.message : String(error)}`;
|
|
21993
|
+
if (options.json) {
|
|
21994
|
+
options.stderr(`${JSON.stringify({ error: message }, null, 2)}
|
|
21995
|
+
`);
|
|
21996
|
+
} else {
|
|
21997
|
+
options.stderr(`gji pr: ${message}
|
|
21998
|
+
`);
|
|
21999
|
+
}
|
|
22000
|
+
return 1;
|
|
22001
|
+
}
|
|
21905
22002
|
const bootstrap = await runtime.bootstrap.bootstrapWorktree({
|
|
21906
22003
|
branch: branchName,
|
|
21907
22004
|
config,
|
|
@@ -21974,7 +22071,7 @@ var runPrCommand = createPrCommand();
|
|
|
21974
22071
|
async function fetchPullRequestRef(repoRoot, input, prNumber, remoteRef, runtime) {
|
|
21975
22072
|
for (const sourceRef of listPullRequestSourceRefs(input, prNumber)) {
|
|
21976
22073
|
try {
|
|
21977
|
-
await runtime.git.
|
|
22074
|
+
await runtime.git.runGitRetryingContention(repoRoot, [
|
|
21978
22075
|
"fetch",
|
|
21979
22076
|
"origin",
|
|
21980
22077
|
`${sourceRef}:${remoteRef}`
|
|
@@ -22062,7 +22159,7 @@ function createGoCommand(dependencies = {}) {
|
|
|
22062
22159
|
if (options.json || isHeadless()) {
|
|
22063
22160
|
return emitError2(
|
|
22064
22161
|
options,
|
|
22065
|
-
"branch argument is required in non-interactive mode
|
|
22162
|
+
"branch argument is required in non-interactive mode"
|
|
22066
22163
|
);
|
|
22067
22164
|
}
|
|
22068
22165
|
let skippedRegisteredRepos = 0;
|
|
@@ -22206,10 +22303,10 @@ async function handleGoBranchResolution(options, resolution, confirmBranchCreati
|
|
|
22206
22303
|
runtime
|
|
22207
22304
|
);
|
|
22208
22305
|
case "pull-request":
|
|
22209
|
-
if (options.json ||
|
|
22306
|
+
if (options.json || options.print) {
|
|
22210
22307
|
return emitError2(
|
|
22211
22308
|
options,
|
|
22212
|
-
"PR navigation creates a worktree; use `gji pr <ref>`
|
|
22309
|
+
"PR navigation creates a worktree; use `gji pr <ref>`"
|
|
22213
22310
|
);
|
|
22214
22311
|
}
|
|
22215
22312
|
return runPrCommand({
|
|
@@ -22254,9 +22351,16 @@ function deduplicateSources(sources) {
|
|
|
22254
22351
|
}
|
|
22255
22352
|
async function createExistingBranchWorktree(options, confirmBranchCreation, repository, branch, mode, remote, runtime) {
|
|
22256
22353
|
if (options.json || isHeadless() || options.print) {
|
|
22354
|
+
const command = await resolveNonInteractiveCreateCommand(
|
|
22355
|
+
repository,
|
|
22356
|
+
branch,
|
|
22357
|
+
mode,
|
|
22358
|
+
remote,
|
|
22359
|
+
runtime ?? defaultCliDependencies
|
|
22360
|
+
);
|
|
22257
22361
|
return emitError2(
|
|
22258
22362
|
options,
|
|
22259
|
-
`branch "${branch}" exists but has no worktree;
|
|
22363
|
+
`branch "${branch}" exists but has no worktree; run ${command} to create one`
|
|
22260
22364
|
);
|
|
22261
22365
|
}
|
|
22262
22366
|
if (!await confirmBranchCreation(branch)) {
|
|
@@ -22274,6 +22378,13 @@ async function createExistingBranchWorktree(options, confirmBranchCreation, repo
|
|
|
22274
22378
|
runtime
|
|
22275
22379
|
});
|
|
22276
22380
|
}
|
|
22381
|
+
async function resolveNonInteractiveCreateCommand(repository, branch, mode, remote, runtime) {
|
|
22382
|
+
if (mode === "track") {
|
|
22383
|
+
return `git worktree add --track -b ${branch} <path> ${remote ?? "origin"}/${branch}`;
|
|
22384
|
+
}
|
|
22385
|
+
const config = await runtime.config.loadEffectiveConfig(repository.repoRoot).catch(() => null);
|
|
22386
|
+
return applyConfiguredBranchPrefix(branch, config?.branchPrefix) === branch ? `gji new ${branch}` : `git worktree add <path> ${branch}`;
|
|
22387
|
+
}
|
|
22277
22388
|
async function navigateToExistingWorktree(options, path9, worktree, runtime) {
|
|
22278
22389
|
const repository = await runtime.repositoryContext.detectRepository(path9);
|
|
22279
22390
|
if (options.json) {
|
|
@@ -22303,7 +22414,7 @@ async function navigateToExistingWorktree(options, path9, worktree, runtime) {
|
|
|
22303
22414
|
{
|
|
22304
22415
|
branch: worktree?.branch ?? void 0,
|
|
22305
22416
|
path: path9,
|
|
22306
|
-
repo:
|
|
22417
|
+
repo: basename12(repository.repoRoot),
|
|
22307
22418
|
slot: await runtime.slots.getWorktreeSlot(path9)
|
|
22308
22419
|
},
|
|
22309
22420
|
options.stderr
|
|
@@ -22443,7 +22554,7 @@ async function runHistoryCommand(options) {
|
|
|
22443
22554
|
}
|
|
22444
22555
|
|
|
22445
22556
|
// src/cli/commands/hub.ts
|
|
22446
|
-
import { basename as
|
|
22557
|
+
import { basename as basename13 } from "node:path";
|
|
22447
22558
|
import { stdin as stdin2, stdout as stdout2 } from "node:process";
|
|
22448
22559
|
var MAX_HUB_REPOSITORY_CONCURRENCY = 4;
|
|
22449
22560
|
async function buildHubData(cwd, dependencies = {}, runtime = defaultCliDependencies) {
|
|
@@ -22468,7 +22579,7 @@ async function buildHubData(cwd, dependencies = {}, runtime = defaultCliDependen
|
|
|
22468
22579
|
existing.sources.push(source);
|
|
22469
22580
|
} else {
|
|
22470
22581
|
groups.set(repoRoot, {
|
|
22471
|
-
name: source.repoName ||
|
|
22582
|
+
name: source.repoName || basename13(repoRoot),
|
|
22472
22583
|
sources: [source]
|
|
22473
22584
|
});
|
|
22474
22585
|
}
|
|
@@ -22717,9 +22828,9 @@ function formatHumanText(value, maxLength) {
|
|
|
22717
22828
|
|
|
22718
22829
|
// src/cli/commands/init.ts
|
|
22719
22830
|
import { constants as constants8 } from "node:fs";
|
|
22720
|
-
import { access as access9, mkdir as
|
|
22831
|
+
import { access as access9, mkdir as mkdir7, readFile as readFile9, writeFile as writeFile5 } from "node:fs/promises";
|
|
22721
22832
|
import { homedir as homedir8 } from "node:os";
|
|
22722
|
-
import { dirname as
|
|
22833
|
+
import { dirname as dirname11 } from "node:path";
|
|
22723
22834
|
var ZSH_COMPLETION_PATH_LINE = "fpath=(~/.zsh/completions $fpath)";
|
|
22724
22835
|
var SHELL_WRAPPED_COMMANDS = [
|
|
22725
22836
|
{
|
|
@@ -22757,26 +22868,12 @@ var SHELL_WRAPPED_COMMANDS = [
|
|
|
22757
22868
|
names: ["root"],
|
|
22758
22869
|
tempPrefix: "gji-root"
|
|
22759
22870
|
},
|
|
22760
|
-
{
|
|
22761
|
-
bypassOptions: ["--help", "-h", "--json"],
|
|
22762
|
-
commandName: "remove",
|
|
22763
|
-
envVar: "GJI_REMOVE_OUTPUT_FILE",
|
|
22764
|
-
names: ["remove", "rm"],
|
|
22765
|
-
tempPrefix: "gji-remove"
|
|
22766
|
-
},
|
|
22767
22871
|
{
|
|
22768
22872
|
bypassOptions: ["--help", "-h", "--json"],
|
|
22769
22873
|
commandName: "done",
|
|
22770
22874
|
envVar: "GJI_DONE_OUTPUT_FILE",
|
|
22771
22875
|
names: ["done"],
|
|
22772
22876
|
tempPrefix: "gji-done"
|
|
22773
|
-
},
|
|
22774
|
-
{
|
|
22775
|
-
bypassOptions: ["--print", "--json", "--help", "-h"],
|
|
22776
|
-
commandName: "warp",
|
|
22777
|
-
envVar: "GJI_WARP_OUTPUT_FILE",
|
|
22778
|
-
names: ["warp"],
|
|
22779
|
-
tempPrefix: "gji-warp"
|
|
22780
22877
|
}
|
|
22781
22878
|
];
|
|
22782
22879
|
async function runInitCommand(options) {
|
|
@@ -22844,8 +22941,8 @@ async function runDefaultOnboarding(context, runtime) {
|
|
|
22844
22941
|
const installCompletion = await promptForCompletion(shell, completionPath);
|
|
22845
22942
|
if (installCompletion === null) return abortDefaultOnboarding();
|
|
22846
22943
|
if (installCompletion) {
|
|
22847
|
-
await
|
|
22848
|
-
await
|
|
22944
|
+
await mkdir7(dirname11(completionPath), { recursive: true });
|
|
22945
|
+
await writeFile5(completionPath, renderShellCompletion(shell), "utf8");
|
|
22849
22946
|
}
|
|
22850
22947
|
if (shell === "zsh" && (completionAlreadyInstalled || installCompletion)) {
|
|
22851
22948
|
await ensureZshCompletionPath(rcPath);
|
|
@@ -22950,8 +23047,8 @@ async function applyOnboardingResult(result, home, runtime) {
|
|
|
22950
23047
|
runtime
|
|
22951
23048
|
);
|
|
22952
23049
|
if (result.installCompletion) {
|
|
22953
|
-
await
|
|
22954
|
-
await
|
|
23050
|
+
await mkdir7(dirname11(completionPath), { recursive: true });
|
|
23051
|
+
await writeFile5(
|
|
22955
23052
|
completionPath,
|
|
22956
23053
|
renderShellCompletion(result.shell),
|
|
22957
23054
|
"utf8"
|
|
@@ -22970,17 +23067,17 @@ async function applyOnboardingResult(result, home, runtime) {
|
|
|
22970
23067
|
}
|
|
22971
23068
|
async function installOnboardingShellIntegration(shell, home) {
|
|
22972
23069
|
const rcPath = resolveShellConfigPath(shell, home);
|
|
22973
|
-
await
|
|
23070
|
+
await mkdir7(dirname11(rcPath), { recursive: true });
|
|
22974
23071
|
const current = await readExistingConfig(rcPath);
|
|
22975
23072
|
if (shell === "fish") {
|
|
22976
|
-
await
|
|
23073
|
+
await writeFile5(
|
|
22977
23074
|
rcPath,
|
|
22978
23075
|
upsertShellIntegration(current, renderShellIntegration(shell)),
|
|
22979
23076
|
"utf8"
|
|
22980
23077
|
);
|
|
22981
23078
|
return;
|
|
22982
23079
|
}
|
|
22983
|
-
await
|
|
23080
|
+
await writeFile5(
|
|
22984
23081
|
rcPath,
|
|
22985
23082
|
upsertOnboardingShellIntegration(current, shell),
|
|
22986
23083
|
"utf8"
|
|
@@ -23014,7 +23111,7 @@ async function ensureZshCompletionPath(rcPath) {
|
|
|
23014
23111
|
const current = await readExistingConfig(rcPath);
|
|
23015
23112
|
const next = upsertZshCompletionPath(current);
|
|
23016
23113
|
if (next !== current) {
|
|
23017
|
-
await
|
|
23114
|
+
await writeFile5(rcPath, next, "utf8");
|
|
23018
23115
|
}
|
|
23019
23116
|
}
|
|
23020
23117
|
function upsertZshCompletionPath(existingConfig) {
|
|
@@ -23058,10 +23155,10 @@ async function runLegacyInitCommand(options, runtime) {
|
|
|
23058
23155
|
return 0;
|
|
23059
23156
|
}
|
|
23060
23157
|
const rcPath = resolveShellConfigPath(shell, home);
|
|
23061
|
-
await
|
|
23158
|
+
await mkdir7(dirname11(rcPath), { recursive: true });
|
|
23062
23159
|
const current = await readExistingConfig(rcPath);
|
|
23063
23160
|
const next = upsertShellIntegration(current, script);
|
|
23064
|
-
await
|
|
23161
|
+
await writeFile5(rcPath, next, "utf8");
|
|
23065
23162
|
options.stdout(`${rcPath}
|
|
23066
23163
|
`);
|
|
23067
23164
|
const { config: globalConfig } = await runtime.configStore.loadGlobalConfig(home);
|
|
@@ -23415,7 +23512,7 @@ function sortWorktrees(worktrees) {
|
|
|
23415
23512
|
|
|
23416
23513
|
// src/cli/commands/open.ts
|
|
23417
23514
|
import { execFile as execFile9 } from "node:child_process";
|
|
23418
|
-
import { access as access10, writeFile as
|
|
23515
|
+
import { access as access10, writeFile as writeFile6 } from "node:fs/promises";
|
|
23419
23516
|
import { join as join15 } from "node:path";
|
|
23420
23517
|
import { promisify as promisify10 } from "node:util";
|
|
23421
23518
|
var execFileAsync9 = promisify10(execFile9);
|
|
@@ -23438,7 +23535,7 @@ function createOpenCommand(dependencies = {}) {
|
|
|
23438
23535
|
}
|
|
23439
23536
|
if (options.select && isHeadless()) {
|
|
23440
23537
|
options.stderr(
|
|
23441
|
-
"gji open --select: selector is unavailable in non-interactive mode
|
|
23538
|
+
"gji open --select: selector is unavailable in non-interactive mode; pass a branch instead\n"
|
|
23442
23539
|
);
|
|
23443
23540
|
return 1;
|
|
23444
23541
|
}
|
|
@@ -23602,7 +23699,7 @@ async function ensureWorkspaceFile(worktreePath, repoName) {
|
|
|
23602
23699
|
} catch {
|
|
23603
23700
|
}
|
|
23604
23701
|
const workspace = { folders: [{ path: "." }], settings: {} };
|
|
23605
|
-
await
|
|
23702
|
+
await writeFile6(
|
|
23606
23703
|
workspacePath,
|
|
23607
23704
|
`${JSON.stringify(workspace, null, 2)}
|
|
23608
23705
|
`,
|
|
@@ -23630,7 +23727,7 @@ function createPrOpenCommand(dependencies = {}) {
|
|
|
23630
23727
|
}
|
|
23631
23728
|
if (options.select && isHeadless()) {
|
|
23632
23729
|
options.stderr(
|
|
23633
|
-
"gji pr open --select: selector is unavailable in non-interactive mode
|
|
23730
|
+
"gji pr open --select: selector is unavailable in non-interactive mode; pass a branch or PR number instead\n"
|
|
23634
23731
|
);
|
|
23635
23732
|
return 1;
|
|
23636
23733
|
}
|
|
@@ -23930,7 +24027,7 @@ async function openPullRequests(pullRequests, options, promptForPullRequest, ope
|
|
|
23930
24027
|
}
|
|
23931
24028
|
if (pullRequests.length > 1 && isHeadless()) {
|
|
23932
24029
|
options.stderr(
|
|
23933
|
-
"gji pr open: multiple open PRs found; pass an explicit PR number in non-interactive mode
|
|
24030
|
+
"gji pr open: multiple open PRs found; pass an explicit PR number in non-interactive mode\n"
|
|
23934
24031
|
);
|
|
23935
24032
|
return 1;
|
|
23936
24033
|
}
|
|
@@ -23981,250 +24078,6 @@ function formatError(error) {
|
|
|
23981
24078
|
return error instanceof Error ? error.message : String(error);
|
|
23982
24079
|
}
|
|
23983
24080
|
|
|
23984
|
-
// src/cli/commands/remove.ts
|
|
23985
|
-
import { basename as basename13 } from "node:path";
|
|
23986
|
-
var REMOVE_OUTPUT_FILE_ENV = "GJI_REMOVE_OUTPUT_FILE";
|
|
23987
|
-
function createRemoveCommand(dependencies = {}) {
|
|
23988
|
-
const promptForWorktree2 = dependencies.promptForWorktree ?? defaultPromptForWorktree3;
|
|
23989
|
-
const confirmRemoval = dependencies.confirmRemoval ?? defaultConfirmRemoval2;
|
|
23990
|
-
const confirmForceRemoveWorktree = dependencies.confirmForceRemoveWorktree ?? defaultConfirmForceRemoveWorktree;
|
|
23991
|
-
const confirmForceDeleteBranch = dependencies.confirmForceDeleteBranch ?? defaultConfirmForceDeleteBranch;
|
|
23992
|
-
return async function runRemoveCommand2(options) {
|
|
23993
|
-
const runtime = options.runtime ?? defaultCliDependencies;
|
|
23994
|
-
const { loadEffectiveConfig: loadEffectiveConfig2 } = runtime.config;
|
|
23995
|
-
const { releaseWorktreeSlot: releaseWorktreeSlot2 } = runtime.slots;
|
|
23996
|
-
const { extractHooks: extractHooks2, runHook: runHook2 } = runtime.hooks;
|
|
23997
|
-
const sourceDependencies = {
|
|
23998
|
-
...runtime.repositoryContext,
|
|
23999
|
-
...runtime.repositoryRegistry,
|
|
24000
|
-
...runtime.worktrees
|
|
24001
|
-
};
|
|
24002
|
-
const {
|
|
24003
|
-
deleteBranch: deleteBranch2,
|
|
24004
|
-
forceDeleteBranch: forceDeleteBranch2,
|
|
24005
|
-
forceRemoveWorktree: forceRemoveWorktree2,
|
|
24006
|
-
isBranchUnmergedError: isBranchUnmergedError2,
|
|
24007
|
-
isWorktreeForceRemovalError: isWorktreeForceRemovalError2,
|
|
24008
|
-
removeWorktree: removeWorktree2
|
|
24009
|
-
} = runtime.worktreeLifecycle;
|
|
24010
|
-
const { linkedWorktrees, repository } = await loadLinkedWorktrees(
|
|
24011
|
-
options.cwd,
|
|
24012
|
-
sourceDependencies
|
|
24013
|
-
);
|
|
24014
|
-
if (linkedWorktrees.length === 0) {
|
|
24015
|
-
emitError3(options, "No linked worktrees to finish");
|
|
24016
|
-
return 1;
|
|
24017
|
-
}
|
|
24018
|
-
if (!options.branch && (options.json || isHeadless())) {
|
|
24019
|
-
const message = "branch argument is required";
|
|
24020
|
-
if (options.json) {
|
|
24021
|
-
emitError3(options, message);
|
|
24022
|
-
} else {
|
|
24023
|
-
options.stderr(
|
|
24024
|
-
`gji remove: ${message} in non-interactive mode (GJI_NO_TUI=1)
|
|
24025
|
-
`
|
|
24026
|
-
);
|
|
24027
|
-
}
|
|
24028
|
-
return 1;
|
|
24029
|
-
}
|
|
24030
|
-
const selection = options.branch ?? await promptForWorktree2(
|
|
24031
|
-
await buildWorktreePromptEntries(
|
|
24032
|
-
linkedWorktrees.map((worktree2) => ({
|
|
24033
|
-
repoName: repository.repoName,
|
|
24034
|
-
worktree: worktree2
|
|
24035
|
-
})),
|
|
24036
|
-
{ catalog: runtime.worktreeCatalog }
|
|
24037
|
-
)
|
|
24038
|
-
);
|
|
24039
|
-
if (!selection) {
|
|
24040
|
-
options.stderr("Aborted\n");
|
|
24041
|
-
return 1;
|
|
24042
|
-
}
|
|
24043
|
-
const worktree = linkedWorktrees.find(
|
|
24044
|
-
(entry) => entry.branch === selection || entry.path === selection
|
|
24045
|
-
);
|
|
24046
|
-
if (!worktree) {
|
|
24047
|
-
emitError3(options, `No linked worktree found for branch: ${selection}`);
|
|
24048
|
-
return 1;
|
|
24049
|
-
}
|
|
24050
|
-
if (!options.dryRun && !options.force && (options.json || isHeadless())) {
|
|
24051
|
-
const message = "--force is required";
|
|
24052
|
-
if (options.json) {
|
|
24053
|
-
emitError3(options, message);
|
|
24054
|
-
} else {
|
|
24055
|
-
options.stderr(
|
|
24056
|
-
`gji remove: ${message} in non-interactive mode (GJI_NO_TUI=1)
|
|
24057
|
-
`
|
|
24058
|
-
);
|
|
24059
|
-
}
|
|
24060
|
-
return 1;
|
|
24061
|
-
}
|
|
24062
|
-
if (!options.dryRun && !options.force && !await confirmRemoval(worktree)) {
|
|
24063
|
-
options.stderr("Aborted\n");
|
|
24064
|
-
return 1;
|
|
24065
|
-
}
|
|
24066
|
-
if (options.dryRun) {
|
|
24067
|
-
if (options.json) {
|
|
24068
|
-
options.stdout(
|
|
24069
|
-
`${JSON.stringify({ branch: worktree.branch, path: worktree.path, dryRun: true }, null, 2)}
|
|
24070
|
-
`
|
|
24071
|
-
);
|
|
24072
|
-
} else {
|
|
24073
|
-
const desc = worktree.branch ? `branch: ${worktree.branch}` : "detached";
|
|
24074
|
-
options.stdout(`Would remove worktree at ${worktree.path} (${desc})
|
|
24075
|
-
`);
|
|
24076
|
-
}
|
|
24077
|
-
return 0;
|
|
24078
|
-
}
|
|
24079
|
-
const config = await loadEffectiveConfig2(
|
|
24080
|
-
repository.repoRoot,
|
|
24081
|
-
void 0,
|
|
24082
|
-
options.stderr
|
|
24083
|
-
);
|
|
24084
|
-
const hooks = extractHooks2(config);
|
|
24085
|
-
let journal;
|
|
24086
|
-
try {
|
|
24087
|
-
journal = await recordUndoOperation(
|
|
24088
|
-
"remove",
|
|
24089
|
-
repository.repoRoot,
|
|
24090
|
-
[worktree],
|
|
24091
|
-
void 0,
|
|
24092
|
-
runtime
|
|
24093
|
-
);
|
|
24094
|
-
} catch (error) {
|
|
24095
|
-
emitError3(
|
|
24096
|
-
options,
|
|
24097
|
-
`could not write undo journal; no worktree was removed: ${toMessage4(error)}`
|
|
24098
|
-
);
|
|
24099
|
-
return 1;
|
|
24100
|
-
}
|
|
24101
|
-
if (!journal) {
|
|
24102
|
-
emitError3(
|
|
24103
|
-
options,
|
|
24104
|
-
"could not capture undo state; no worktree was removed"
|
|
24105
|
-
);
|
|
24106
|
-
return 1;
|
|
24107
|
-
}
|
|
24108
|
-
await runHook2(
|
|
24109
|
-
hooks["before-remove"],
|
|
24110
|
-
worktree.path,
|
|
24111
|
-
{
|
|
24112
|
-
branch: worktree.branch ?? void 0,
|
|
24113
|
-
path: worktree.path,
|
|
24114
|
-
repo: basename13(repository.repoRoot)
|
|
24115
|
-
},
|
|
24116
|
-
options.stderr
|
|
24117
|
-
);
|
|
24118
|
-
try {
|
|
24119
|
-
await removeWorktree2(repository.repoRoot, worktree.path);
|
|
24120
|
-
} catch (error) {
|
|
24121
|
-
if (!isWorktreeForceRemovalError2(error)) {
|
|
24122
|
-
await finalizeUndoOperation(
|
|
24123
|
-
journal,
|
|
24124
|
-
[],
|
|
24125
|
-
void 0,
|
|
24126
|
-
runtime.configStore.GLOBAL_CONFIG_DIRECTORY
|
|
24127
|
-
);
|
|
24128
|
-
throw error;
|
|
24129
|
-
}
|
|
24130
|
-
if (!options.force && !await confirmForceRemoveWorktree(worktree.path)) {
|
|
24131
|
-
await finalizeUndoOperation(
|
|
24132
|
-
journal,
|
|
24133
|
-
[],
|
|
24134
|
-
void 0,
|
|
24135
|
-
runtime.configStore.GLOBAL_CONFIG_DIRECTORY
|
|
24136
|
-
);
|
|
24137
|
-
options.stderr("Aborted\n");
|
|
24138
|
-
return 1;
|
|
24139
|
-
}
|
|
24140
|
-
try {
|
|
24141
|
-
await forceRemoveWorktree2(repository.repoRoot, worktree.path);
|
|
24142
|
-
} catch (forceError) {
|
|
24143
|
-
await finalizeUndoOperation(
|
|
24144
|
-
journal,
|
|
24145
|
-
[],
|
|
24146
|
-
void 0,
|
|
24147
|
-
runtime.configStore.GLOBAL_CONFIG_DIRECTORY
|
|
24148
|
-
);
|
|
24149
|
-
emitError3(
|
|
24150
|
-
options,
|
|
24151
|
-
`Failed to remove worktree at ${worktree.path}: ${toMessage4(forceError)}`
|
|
24152
|
-
);
|
|
24153
|
-
return 1;
|
|
24154
|
-
}
|
|
24155
|
-
}
|
|
24156
|
-
await finalizeUndoOperation(
|
|
24157
|
-
journal,
|
|
24158
|
-
[worktree],
|
|
24159
|
-
void 0,
|
|
24160
|
-
runtime.configStore.GLOBAL_CONFIG_DIRECTORY
|
|
24161
|
-
);
|
|
24162
|
-
await releaseWorktreeSlot2(worktree.path);
|
|
24163
|
-
if (worktree.branch) {
|
|
24164
|
-
try {
|
|
24165
|
-
await deleteBranch2(repository.repoRoot, worktree.branch);
|
|
24166
|
-
} catch (error) {
|
|
24167
|
-
if (!isBranchUnmergedError2(error)) {
|
|
24168
|
-
throw error;
|
|
24169
|
-
}
|
|
24170
|
-
if (options.force || await confirmForceDeleteBranch(worktree.branch)) {
|
|
24171
|
-
try {
|
|
24172
|
-
await forceDeleteBranch2(repository.repoRoot, worktree.branch);
|
|
24173
|
-
} catch (forceError) {
|
|
24174
|
-
options.stderr(
|
|
24175
|
-
`Failed to delete branch ${worktree.branch}: ${toMessage4(forceError)}
|
|
24176
|
-
`
|
|
24177
|
-
);
|
|
24178
|
-
}
|
|
24179
|
-
} else {
|
|
24180
|
-
options.stderr(
|
|
24181
|
-
`Branch ${worktree.branch} was not deleted (has unmerged commits)
|
|
24182
|
-
`
|
|
24183
|
-
);
|
|
24184
|
-
}
|
|
24185
|
-
}
|
|
24186
|
-
}
|
|
24187
|
-
if (options.json) {
|
|
24188
|
-
options.stdout(
|
|
24189
|
-
`${JSON.stringify({ branch: worktree.branch, path: worktree.path, deleted: true }, null, 2)}
|
|
24190
|
-
`
|
|
24191
|
-
);
|
|
24192
|
-
} else {
|
|
24193
|
-
options.stderr("undo: gji undo\n");
|
|
24194
|
-
await writeOutput3(repository.repoRoot, options.stdout);
|
|
24195
|
-
}
|
|
24196
|
-
return 0;
|
|
24197
|
-
};
|
|
24198
|
-
}
|
|
24199
|
-
var runRemoveCommand = createRemoveCommand();
|
|
24200
|
-
async function defaultPromptForWorktree3(worktrees) {
|
|
24201
|
-
return promptForSingleWorktree("Choose a worktree to finish", worktrees);
|
|
24202
|
-
}
|
|
24203
|
-
async function defaultConfirmRemoval2(worktree) {
|
|
24204
|
-
const choice = await ye({
|
|
24205
|
-
message: worktree.branch ? `Remove worktree and delete branch ${worktree.branch}?` : `Remove detached worktree ${worktree.path}?`,
|
|
24206
|
-
active: "Yes",
|
|
24207
|
-
inactive: "No",
|
|
24208
|
-
initialValue: true
|
|
24209
|
-
});
|
|
24210
|
-
return !pD(choice) && choice;
|
|
24211
|
-
}
|
|
24212
|
-
async function writeOutput3(repoRoot, stdout3) {
|
|
24213
|
-
await writeShellOutput(REMOVE_OUTPUT_FILE_ENV, repoRoot, stdout3);
|
|
24214
|
-
}
|
|
24215
|
-
function emitError3(options, message) {
|
|
24216
|
-
if (options.json) {
|
|
24217
|
-
options.stderr(`${JSON.stringify({ error: message }, null, 2)}
|
|
24218
|
-
`);
|
|
24219
|
-
} else {
|
|
24220
|
-
options.stderr(`${message}
|
|
24221
|
-
`);
|
|
24222
|
-
}
|
|
24223
|
-
}
|
|
24224
|
-
function toMessage4(error) {
|
|
24225
|
-
return error instanceof Error ? error.message : String(error);
|
|
24226
|
-
}
|
|
24227
|
-
|
|
24228
24081
|
// src/cli/commands/root.ts
|
|
24229
24082
|
var ROOT_OUTPUT_FILE_ENV = "GJI_ROOT_OUTPUT_FILE";
|
|
24230
24083
|
async function runRootCommand(options) {
|
|
@@ -24245,13 +24098,17 @@ async function runRootCommand(options) {
|
|
|
24245
24098
|
|
|
24246
24099
|
// src/cli/commands/run-hook.ts
|
|
24247
24100
|
var VALID_HOOKS = [
|
|
24101
|
+
"after-branch-create",
|
|
24248
24102
|
"after-create",
|
|
24249
24103
|
"after-enter",
|
|
24104
|
+
"after-switch",
|
|
24250
24105
|
"before-remove"
|
|
24251
24106
|
];
|
|
24252
24107
|
var CAMEL_ALIASES = {
|
|
24108
|
+
afterBranchCreate: "after-branch-create",
|
|
24253
24109
|
afterCreate: "after-create",
|
|
24254
24110
|
afterEnter: "after-enter",
|
|
24111
|
+
afterSwitch: "after-switch",
|
|
24255
24112
|
beforeRemove: "before-remove"
|
|
24256
24113
|
};
|
|
24257
24114
|
function isValidHook(hook) {
|
|
@@ -24448,7 +24305,7 @@ async function runSyncCommand(options) {
|
|
|
24448
24305
|
resolveConfiguredString2(config.syncDefaultBranch) ?? void 0
|
|
24449
24306
|
);
|
|
24450
24307
|
} catch {
|
|
24451
|
-
|
|
24308
|
+
emitError3(options, `Unable to reach remote '${remote}'`);
|
|
24452
24309
|
if (!options.json) {
|
|
24453
24310
|
options.stderr(
|
|
24454
24311
|
`Hint: Add the remote with: git remote add ${remote} <url>
|
|
@@ -24458,7 +24315,7 @@ async function runSyncCommand(options) {
|
|
|
24458
24315
|
return 1;
|
|
24459
24316
|
}
|
|
24460
24317
|
if (!remoteBase) {
|
|
24461
|
-
|
|
24318
|
+
emitError3(options, "Unable to determine the default branch for sync.");
|
|
24462
24319
|
if (!options.json) {
|
|
24463
24320
|
options.stderr(
|
|
24464
24321
|
`Hint: Add the remote with: git remote add ${remote} <url>
|
|
@@ -24473,7 +24330,7 @@ async function runSyncCommand(options) {
|
|
|
24473
24330
|
options.all
|
|
24474
24331
|
);
|
|
24475
24332
|
if (targetWorktrees === "detached") {
|
|
24476
|
-
|
|
24333
|
+
emitError3(
|
|
24477
24334
|
options,
|
|
24478
24335
|
`Cannot sync detached worktree: ${repository.currentRoot}`
|
|
24479
24336
|
);
|
|
@@ -24481,14 +24338,14 @@ async function runSyncCommand(options) {
|
|
|
24481
24338
|
}
|
|
24482
24339
|
for (const worktree of targetWorktrees) {
|
|
24483
24340
|
if (await isDirtyWorktree2(worktree.path)) {
|
|
24484
|
-
|
|
24341
|
+
emitError3(options, `Cannot sync dirty worktree: ${worktree.path}`);
|
|
24485
24342
|
return 1;
|
|
24486
24343
|
}
|
|
24487
24344
|
}
|
|
24488
24345
|
try {
|
|
24489
24346
|
await runGit2(repository.repoRoot, ["fetch", "--prune", remote]);
|
|
24490
24347
|
} catch {
|
|
24491
|
-
|
|
24348
|
+
emitError3(options, `Failed to fetch from remote '${remote}'`);
|
|
24492
24349
|
if (!options.json) {
|
|
24493
24350
|
options.stderr(
|
|
24494
24351
|
`Hint: Add the remote with: git remote add ${remote} <url>
|
|
@@ -24520,7 +24377,7 @@ async function runSyncCommand(options) {
|
|
|
24520
24377
|
}
|
|
24521
24378
|
return 0;
|
|
24522
24379
|
}
|
|
24523
|
-
function
|
|
24380
|
+
function emitError3(options, message) {
|
|
24524
24381
|
if (options.json) {
|
|
24525
24382
|
options.stderr(`${JSON.stringify({ error: message }, null, 2)}
|
|
24526
24383
|
`);
|
|
@@ -24741,144 +24598,6 @@ function emitTaskError(options, message) {
|
|
|
24741
24598
|
return 1;
|
|
24742
24599
|
}
|
|
24743
24600
|
|
|
24744
|
-
// src/cli/commands/warp.ts
|
|
24745
|
-
var WARP_OUTPUT_FILE_ENV = "GJI_WARP_OUTPUT_FILE";
|
|
24746
|
-
async function runWarpCommand(options) {
|
|
24747
|
-
return runWarpNavigate(options);
|
|
24748
|
-
}
|
|
24749
|
-
async function runWarpNavigate(options) {
|
|
24750
|
-
const runtime = options.runtime ?? defaultCliDependencies;
|
|
24751
|
-
const { recordWorktreeUsage: recordWorktreeUsage2 } = runtime.history;
|
|
24752
|
-
if ((isHeadless() || options.json) && !options.branch) {
|
|
24753
|
-
const message = "branch argument is required";
|
|
24754
|
-
if (options.json) {
|
|
24755
|
-
options.stderr(`${JSON.stringify({ error: message }, null, 2)}
|
|
24756
|
-
`);
|
|
24757
|
-
} else {
|
|
24758
|
-
options.stderr(
|
|
24759
|
-
"gji warp: branch argument is required in non-interactive mode (GJI_NO_TUI=1)\n"
|
|
24760
|
-
);
|
|
24761
|
-
}
|
|
24762
|
-
return 1;
|
|
24763
|
-
}
|
|
24764
|
-
const target = await resolveWarpTarget({
|
|
24765
|
-
...options,
|
|
24766
|
-
commandName: "gji warp",
|
|
24767
|
-
json: options.json,
|
|
24768
|
-
queryPullRequests: options.queryPullRequests,
|
|
24769
|
-
runtime
|
|
24770
|
-
});
|
|
24771
|
-
if (!target) return 1;
|
|
24772
|
-
if (options.json) {
|
|
24773
|
-
options.stdout(
|
|
24774
|
-
`${JSON.stringify(
|
|
24775
|
-
createNavigationTarget(target.repository, target.path, target.branch),
|
|
24776
|
-
null,
|
|
24777
|
-
2
|
|
24778
|
-
)}
|
|
24779
|
-
`
|
|
24780
|
-
);
|
|
24781
|
-
return 0;
|
|
24782
|
-
}
|
|
24783
|
-
await recordWorktreeUsage2(target.path, target.branch);
|
|
24784
|
-
await writeShellOutput(WARP_OUTPUT_FILE_ENV, target.path, options.stdout);
|
|
24785
|
-
return 0;
|
|
24786
|
-
}
|
|
24787
|
-
async function resolveWarpTarget(options) {
|
|
24788
|
-
const cmd = options.commandName ?? "gji";
|
|
24789
|
-
const runtime = options.runtime ?? defaultCliDependencies;
|
|
24790
|
-
const { detectRepository: detectRepository2 } = runtime.repositoryContext;
|
|
24791
|
-
const { loadRegistry: loadRegistry2 } = runtime.repositoryRegistry;
|
|
24792
|
-
const sourceDependencies = {
|
|
24793
|
-
...runtime.repositoryContext,
|
|
24794
|
-
...runtime.repositoryRegistry,
|
|
24795
|
-
...runtime.worktrees
|
|
24796
|
-
};
|
|
24797
|
-
const emitError5 = (message, hint) => {
|
|
24798
|
-
if (options.json) {
|
|
24799
|
-
options.stderr(`${JSON.stringify({ error: message }, null, 2)}
|
|
24800
|
-
`);
|
|
24801
|
-
} else {
|
|
24802
|
-
options.stderr(`${cmd}: ${message}
|
|
24803
|
-
`);
|
|
24804
|
-
if (hint) options.stderr(hint);
|
|
24805
|
-
}
|
|
24806
|
-
};
|
|
24807
|
-
const registry = await loadRegistry2();
|
|
24808
|
-
if (registry.length === 0) {
|
|
24809
|
-
emitError5(
|
|
24810
|
-
"not in a git repository and no repos registered yet.",
|
|
24811
|
-
"Use any gji command inside a repository to register it.\n"
|
|
24812
|
-
);
|
|
24813
|
-
return null;
|
|
24814
|
-
}
|
|
24815
|
-
const registered = await listRegisteredWorktreeSources({
|
|
24816
|
-
cwd: options.cwd,
|
|
24817
|
-
repositoryPort: sourceDependencies
|
|
24818
|
-
});
|
|
24819
|
-
const skippedRegisteredRepos = registered.skipped.length;
|
|
24820
|
-
const allItems = registered.sources.filter(
|
|
24821
|
-
(item) => item.repoRoot !== options.excludeRepoRoot
|
|
24822
|
-
);
|
|
24823
|
-
if (allItems.length === 0) {
|
|
24824
|
-
emitError5(
|
|
24825
|
-
"no accessible worktrees found in any registered repo.",
|
|
24826
|
-
skippedRegisteredRepos > 0 ? "Hint: Run 'gji doctor' to inspect stale repository entries.\n" : void 0
|
|
24827
|
-
);
|
|
24828
|
-
return null;
|
|
24829
|
-
}
|
|
24830
|
-
const promptSources = allItems.map((item) => ({
|
|
24831
|
-
repoRoot: item.repoRoot,
|
|
24832
|
-
repoName: item.repoName,
|
|
24833
|
-
worktree: item.worktree
|
|
24834
|
-
}));
|
|
24835
|
-
if (options.branch) {
|
|
24836
|
-
const match = resolveWorktreeQuery(promptSources, options.branch);
|
|
24837
|
-
if (!match) {
|
|
24838
|
-
emitError5(
|
|
24839
|
-
`no worktree found matching: ${options.branch}`,
|
|
24840
|
-
skippedRegisteredRepos > 0 ? "Hint: Run 'gji doctor' to inspect stale repository entries.\n" : void 0
|
|
24841
|
-
);
|
|
24842
|
-
return null;
|
|
24843
|
-
}
|
|
24844
|
-
return {
|
|
24845
|
-
branch: match.worktree.branch,
|
|
24846
|
-
path: match.worktree.path,
|
|
24847
|
-
repository: createNavigationRepository(
|
|
24848
|
-
match.repoName,
|
|
24849
|
-
match.repoRoot ?? (await detectRepository2(match.worktree.path)).repoRoot
|
|
24850
|
-
)
|
|
24851
|
-
};
|
|
24852
|
-
}
|
|
24853
|
-
const promptEntries = await buildWorktreePromptEntries(promptSources, {
|
|
24854
|
-
metadata: "fast",
|
|
24855
|
-
catalog: withPullRequestQueries(runtime, options.queryPullRequests)
|
|
24856
|
-
});
|
|
24857
|
-
const path9 = await promptForWarpTarget(promptEntries);
|
|
24858
|
-
if (!path9) {
|
|
24859
|
-
options.stderr("Aborted\n");
|
|
24860
|
-
return null;
|
|
24861
|
-
}
|
|
24862
|
-
const chosenSource = promptSources.find(
|
|
24863
|
-
(item) => item.worktree.path === path9
|
|
24864
|
-
);
|
|
24865
|
-
if (chosenSource === void 0) {
|
|
24866
|
-
emitError5("selected worktree is no longer registered");
|
|
24867
|
-
return null;
|
|
24868
|
-
}
|
|
24869
|
-
return {
|
|
24870
|
-
branch: chosenSource.worktree.branch,
|
|
24871
|
-
path: path9,
|
|
24872
|
-
repository: createNavigationRepository(
|
|
24873
|
-
chosenSource.repoName,
|
|
24874
|
-
chosenSource.repoRoot ?? (await detectRepository2(chosenSource.worktree.path)).repoRoot
|
|
24875
|
-
)
|
|
24876
|
-
};
|
|
24877
|
-
}
|
|
24878
|
-
async function promptForWarpTarget(items) {
|
|
24879
|
-
return promptForSingleWorktree("Warp to a worktree", items);
|
|
24880
|
-
}
|
|
24881
|
-
|
|
24882
24601
|
// src/cli/program.ts
|
|
24883
24602
|
function createProgram() {
|
|
24884
24603
|
const program2 = new Command();
|
|
@@ -25080,25 +24799,9 @@ function registerCommands(program2) {
|
|
|
25080
24799
|
"--json",
|
|
25081
24800
|
"emit JSON on success or error instead of human-readable output"
|
|
25082
24801
|
).action(notImplemented("clean"));
|
|
25083
|
-
program2.command("remove [branch]").alias("rm").description(
|
|
25084
|
-
"deprecated: use gji done for one worktree or gji clean for bulk cleanup"
|
|
25085
|
-
).option(
|
|
25086
|
-
"-f, --force",
|
|
25087
|
-
"bypass prompts, force-remove a dirty worktree, and force-delete an unmerged branch"
|
|
25088
|
-
).option("--dry-run", "show what would be deleted without removing anything").option(
|
|
25089
|
-
"--json",
|
|
25090
|
-
"emit JSON on success or error instead of human-readable output"
|
|
25091
|
-
).action(notImplemented("remove"));
|
|
25092
24802
|
program2.command("run-hook <hook>").alias("trigger-hook").description(
|
|
25093
|
-
"run a named hook (after-create, after-enter, before-remove) in the current worktree"
|
|
24803
|
+
"run a named hook (after-branch-create, after-create, after-enter, after-switch, before-remove) in the current worktree"
|
|
25094
24804
|
).action(notImplemented("run-hook"));
|
|
25095
|
-
program2.command("warp [branch]").description("deprecated: use gji go (press Tab for all known repos)").option(
|
|
25096
|
-
"--print",
|
|
25097
|
-
"print the resolved worktree path without changing directory"
|
|
25098
|
-
).option(
|
|
25099
|
-
"--json",
|
|
25100
|
-
"emit JSON on success or error instead of human-readable output"
|
|
25101
|
-
).action(notImplemented("warp"));
|
|
25102
24805
|
const configCommand = program2.command("config").description("manage global config defaults").action(notImplemented("config"));
|
|
25103
24806
|
configCommand.command("get [key]").description("print the global config or a single key").action(notImplemented("config get"));
|
|
25104
24807
|
configCommand.command("set <key> <value>").description("set a global config value").action(notImplemented("config set"));
|
|
@@ -25454,31 +25157,6 @@ function attachCommandActions(program2, options) {
|
|
|
25454
25157
|
}
|
|
25455
25158
|
}
|
|
25456
25159
|
);
|
|
25457
|
-
const runRemovalCommand = async (branch, commandOptions2 = {}) => {
|
|
25458
|
-
const exitCode = await runRemoveCommand({
|
|
25459
|
-
branch,
|
|
25460
|
-
cwd: options.cwd,
|
|
25461
|
-
dryRun: commandOptions2.dryRun,
|
|
25462
|
-
force: commandOptions2.force,
|
|
25463
|
-
json: commandOptions2.json,
|
|
25464
|
-
runtime: options.dependencies,
|
|
25465
|
-
stderr: options.stderr,
|
|
25466
|
-
stdout: options.stdout
|
|
25467
|
-
});
|
|
25468
|
-
if (exitCode !== 0) {
|
|
25469
|
-
throw commanderExit(exitCode);
|
|
25470
|
-
}
|
|
25471
|
-
};
|
|
25472
|
-
requireCommand(program2, "remove").action(
|
|
25473
|
-
async (branch, commandOptions2) => {
|
|
25474
|
-
if (!commandOptions2.json) {
|
|
25475
|
-
options.stderr(
|
|
25476
|
-
"gji remove is deprecated; use 'gji done' for one worktree or 'gji clean' for bulk cleanup.\n"
|
|
25477
|
-
);
|
|
25478
|
-
}
|
|
25479
|
-
return runRemovalCommand(branch, commandOptions2);
|
|
25480
|
-
}
|
|
25481
|
-
);
|
|
25482
25160
|
requireCommand(program2, "run-hook").action(async (hook) => {
|
|
25483
25161
|
const exitCode = await runHookCommand({
|
|
25484
25162
|
cwd: options.cwd,
|
|
@@ -25490,26 +25168,6 @@ function attachCommandActions(program2, options) {
|
|
|
25490
25168
|
throw commanderExit(exitCode);
|
|
25491
25169
|
}
|
|
25492
25170
|
});
|
|
25493
|
-
requireCommand(program2, "warp").action(
|
|
25494
|
-
async (branch, commandOptions2) => {
|
|
25495
|
-
if (!commandOptions2.json) {
|
|
25496
|
-
options.stderr(
|
|
25497
|
-
"gji warp is deprecated; use 'gji go' (press Tab for all repositories).\n"
|
|
25498
|
-
);
|
|
25499
|
-
}
|
|
25500
|
-
const exitCode = await runWarpCommand({
|
|
25501
|
-
branch,
|
|
25502
|
-
cwd: options.cwd,
|
|
25503
|
-
json: commandOptions2.json,
|
|
25504
|
-
runtime: options.dependencies,
|
|
25505
|
-
stderr: options.stderr,
|
|
25506
|
-
stdout: options.stdout
|
|
25507
|
-
});
|
|
25508
|
-
if (exitCode !== 0) {
|
|
25509
|
-
throw commanderExit(exitCode);
|
|
25510
|
-
}
|
|
25511
|
-
}
|
|
25512
|
-
);
|
|
25513
25171
|
const configCommand = requireCommand(program2, "config");
|
|
25514
25172
|
configCommand.action(async () => {
|
|
25515
25173
|
const exitCode = await runConfigCommand({
|