@solaqua/gji 0.11.0 → 0.12.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 +82 -496
- package/dist/clean.js +2 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +40 -4
- package/dist/context-card.d.ts +1 -0
- package/dist/context-card.js +25 -0
- package/dist/doctor.js +21 -0
- package/dist/done.js +2 -0
- package/dist/gji-bundle.mjs +988 -350
- package/dist/go.d.ts +1 -0
- package/dist/go.js +8 -0
- package/dist/hooks.d.ts +1 -0
- package/dist/hooks.js +7 -1
- package/dist/hub.d.ts +31 -0
- package/dist/hub.js +287 -0
- package/dist/ls.js +9 -1
- package/dist/new.d.ts +1 -0
- package/dist/new.js +9 -0
- package/dist/pr-open.js +2 -10
- package/dist/pull-requests.d.ts +1 -0
- package/dist/pull-requests.js +32 -3
- package/dist/remove.js +2 -0
- package/dist/repo.d.ts +1 -0
- package/dist/repo.js +4 -0
- package/dist/run-hook.js +2 -0
- package/dist/shell-completion.js +11 -3
- package/dist/slots.d.ts +8 -0
- package/dist/slots.js +61 -0
- package/dist/status.d.ts +2 -0
- package/dist/status.js +9 -1
- package/dist/task-command.d.ts +9 -0
- package/dist/task-command.js +29 -0
- package/dist/task.d.ts +9 -0
- package/dist/task.js +37 -0
- package/dist/terminal-text.d.ts +4 -0
- package/dist/terminal-text.js +72 -0
- package/dist/worktree-bootstrap.js +3 -0
- package/dist/worktree-info.d.ts +6 -1
- package/dist/worktree-info.js +14 -4
- package/dist/worktree-picker.d.ts +3 -3
- package/dist/worktree-picker.js +71 -82
- package/dist/worktree-sources.d.ts +1 -0
- package/dist/worktree-sources.js +57 -11
- 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 +4 -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 +4 -1
- package/man/man1/gji-open.1 +1 -1
- package/man/man1/gji-pr.1 +1 -1
- package/man/man1/gji-remove.1 +1 -1
- package/man/man1/gji-root.1 +1 -1
- package/man/man1/gji-run-hook.1 +1 -1
- package/man/man1/gji-status.1 +1 -1
- package/man/man1/gji-sync-files.1 +1 -1
- package/man/man1/gji-sync.1 +1 -1
- package/man/man1/gji-task.1 +16 -0
- package/man/man1/gji-undo.1 +1 -1
- package/man/man1/gji-warp.1 +1 -1
- package/man/man1/gji.1 +5 -1
- package/package.json +1 -1
package/dist/gji-bundle.mjs
CHANGED
|
@@ -3951,8 +3951,8 @@ var require_graceful_fs = __commonJS({
|
|
|
3951
3951
|
fs6.createReadStream = createReadStream;
|
|
3952
3952
|
fs6.createWriteStream = createWriteStream;
|
|
3953
3953
|
var fs$readFile = fs6.readFile;
|
|
3954
|
-
fs6.readFile =
|
|
3955
|
-
function
|
|
3954
|
+
fs6.readFile = readFile12;
|
|
3955
|
+
function readFile12(path9, options, cb) {
|
|
3956
3956
|
if (typeof options === "function")
|
|
3957
3957
|
cb = options, options = null;
|
|
3958
3958
|
return go$readFile(path9, options, cb);
|
|
@@ -3968,8 +3968,8 @@ var require_graceful_fs = __commonJS({
|
|
|
3968
3968
|
}
|
|
3969
3969
|
}
|
|
3970
3970
|
var fs$writeFile = fs6.writeFile;
|
|
3971
|
-
fs6.writeFile =
|
|
3972
|
-
function
|
|
3971
|
+
fs6.writeFile = writeFile12;
|
|
3972
|
+
function writeFile12(path9, data, options, cb) {
|
|
3973
3973
|
if (typeof options === "function")
|
|
3974
3974
|
cb = options, options = null;
|
|
3975
3975
|
return go$writeFile(path9, data, options, cb);
|
|
@@ -5386,7 +5386,7 @@ var require_minimist = __commonJS({
|
|
|
5386
5386
|
var require_rc = __commonJS({
|
|
5387
5387
|
"node_modules/.pnpm/rc@1.2.8/node_modules/rc/index.js"(exports, module) {
|
|
5388
5388
|
var cc = require_utils();
|
|
5389
|
-
var
|
|
5389
|
+
var join19 = __require("path").join;
|
|
5390
5390
|
var deepExtend = require_deep_extend();
|
|
5391
5391
|
var etc = "/etc";
|
|
5392
5392
|
var win = process.platform === "win32";
|
|
@@ -5411,15 +5411,15 @@ var require_rc = __commonJS({
|
|
|
5411
5411
|
}
|
|
5412
5412
|
if (!win)
|
|
5413
5413
|
[
|
|
5414
|
-
|
|
5415
|
-
|
|
5414
|
+
join19(etc, name, "config"),
|
|
5415
|
+
join19(etc, name + "rc")
|
|
5416
5416
|
].forEach(addConfigFile);
|
|
5417
5417
|
if (home)
|
|
5418
5418
|
[
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5419
|
+
join19(home, ".config", name, "config"),
|
|
5420
|
+
join19(home, ".config", name),
|
|
5421
|
+
join19(home, "." + name, "config"),
|
|
5422
|
+
join19(home, "." + name + "rc")
|
|
5423
5423
|
].forEach(addConfigFile);
|
|
5424
5424
|
addConfigFile(cc.find("." + name + "rc"));
|
|
5425
5425
|
if (env5.config) addConfigFile(env5.config);
|
|
@@ -5925,8 +5925,8 @@ var require_graceful_fs2 = __commonJS({
|
|
|
5925
5925
|
fs6.createReadStream = createReadStream;
|
|
5926
5926
|
fs6.createWriteStream = createWriteStream;
|
|
5927
5927
|
var fs$readFile = fs6.readFile;
|
|
5928
|
-
fs6.readFile =
|
|
5929
|
-
function
|
|
5928
|
+
fs6.readFile = readFile12;
|
|
5929
|
+
function readFile12(path9, options, cb) {
|
|
5930
5930
|
if (typeof options === "function")
|
|
5931
5931
|
cb = options, options = null;
|
|
5932
5932
|
return go$readFile(path9, options, cb);
|
|
@@ -5942,8 +5942,8 @@ var require_graceful_fs2 = __commonJS({
|
|
|
5942
5942
|
}
|
|
5943
5943
|
}
|
|
5944
5944
|
var fs$writeFile = fs6.writeFile;
|
|
5945
|
-
fs6.writeFile =
|
|
5946
|
-
function
|
|
5945
|
+
fs6.writeFile = writeFile12;
|
|
5946
|
+
function writeFile12(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 dirname14 = path9.dirname(name2);
|
|
6761
|
+
if (dirname14 === name2) {
|
|
6762
6762
|
return original;
|
|
6763
6763
|
}
|
|
6764
|
-
return find(
|
|
6764
|
+
return find(dirname14, original);
|
|
6765
6765
|
} catch (error) {
|
|
6766
6766
|
if (name2 === original) {
|
|
6767
6767
|
if (error.code === "ENOENT") {
|
|
@@ -9422,7 +9422,7 @@ var require_picocolors = __commonJS({
|
|
|
9422
9422
|
});
|
|
9423
9423
|
|
|
9424
9424
|
// src/index.ts
|
|
9425
|
-
import { homedir as
|
|
9425
|
+
import { homedir as homedir11 } from "node:os";
|
|
9426
9426
|
|
|
9427
9427
|
// src/cli.ts
|
|
9428
9428
|
import { createRequire } from "node:module";
|
|
@@ -9522,7 +9522,7 @@ var retryifyAsync = (fn, options) => {
|
|
|
9522
9522
|
throw error;
|
|
9523
9523
|
const delay2 = Math.round(interval * Math.random());
|
|
9524
9524
|
if (delay2 > 0) {
|
|
9525
|
-
const delayPromise = new Promise((
|
|
9525
|
+
const delayPromise = new Promise((resolve13) => setTimeout(resolve13, delay2));
|
|
9526
9526
|
return delayPromise.then(() => attempt.apply(void 0, args));
|
|
9527
9527
|
} else {
|
|
9528
9528
|
return attempt.apply(void 0, args);
|
|
@@ -9781,14 +9781,14 @@ var Temp = {
|
|
|
9781
9781
|
}
|
|
9782
9782
|
},
|
|
9783
9783
|
truncate: (filePath) => {
|
|
9784
|
-
const
|
|
9785
|
-
if (
|
|
9784
|
+
const basename12 = path2.basename(filePath);
|
|
9785
|
+
if (basename12.length <= LIMIT_BASENAME_LENGTH)
|
|
9786
9786
|
return filePath;
|
|
9787
|
-
const truncable = /^(\.?)(.*?)((?:\.[^.]+)?(?:\.tmp-\d{10}[a-f0-9]{6})?)$/.exec(
|
|
9787
|
+
const truncable = /^(\.?)(.*?)((?:\.[^.]+)?(?:\.tmp-\d{10}[a-f0-9]{6})?)$/.exec(basename12);
|
|
9788
9788
|
if (!truncable)
|
|
9789
9789
|
return filePath;
|
|
9790
|
-
const truncationLength =
|
|
9791
|
-
return `${filePath.slice(0, -
|
|
9790
|
+
const truncationLength = basename12.length - LIMIT_BASENAME_LENGTH;
|
|
9791
|
+
return `${filePath.slice(0, -basename12.length)}${truncable[1]}${truncable[2].slice(0, -truncationLength)}${truncable[3]}`;
|
|
9792
9792
|
}
|
|
9793
9793
|
};
|
|
9794
9794
|
node_default(Temp.purgeSyncAll);
|
|
@@ -11088,14 +11088,14 @@ var TimeoutError = class extends Error {
|
|
|
11088
11088
|
|
|
11089
11089
|
// node_modules/.pnpm/ky@1.14.3/node_modules/ky/distribution/utils/timeout.js
|
|
11090
11090
|
async function timeout(request, init, abortController, options) {
|
|
11091
|
-
return new Promise((
|
|
11091
|
+
return new Promise((resolve13, reject) => {
|
|
11092
11092
|
const timeoutId = setTimeout(() => {
|
|
11093
11093
|
if (abortController) {
|
|
11094
11094
|
abortController.abort();
|
|
11095
11095
|
}
|
|
11096
11096
|
reject(new TimeoutError(request));
|
|
11097
11097
|
}, options.timeout);
|
|
11098
|
-
void options.fetch(request, init).then(
|
|
11098
|
+
void options.fetch(request, init).then(resolve13).catch(reject).then(() => {
|
|
11099
11099
|
clearTimeout(timeoutId);
|
|
11100
11100
|
});
|
|
11101
11101
|
});
|
|
@@ -11103,7 +11103,7 @@ async function timeout(request, init, abortController, options) {
|
|
|
11103
11103
|
|
|
11104
11104
|
// node_modules/.pnpm/ky@1.14.3/node_modules/ky/distribution/utils/delay.js
|
|
11105
11105
|
async function delay(ms, { signal }) {
|
|
11106
|
-
return new Promise((
|
|
11106
|
+
return new Promise((resolve13, reject) => {
|
|
11107
11107
|
if (signal) {
|
|
11108
11108
|
signal.throwIfAborted();
|
|
11109
11109
|
signal.addEventListener("abort", abortHandler, { once: true });
|
|
@@ -11114,7 +11114,7 @@ async function delay(ms, { signal }) {
|
|
|
11114
11114
|
}
|
|
11115
11115
|
const timeoutId = setTimeout(() => {
|
|
11116
11116
|
signal?.removeEventListener("abort", abortHandler);
|
|
11117
|
-
|
|
11117
|
+
resolve13();
|
|
11118
11118
|
}, ms);
|
|
11119
11119
|
});
|
|
11120
11120
|
}
|
|
@@ -13219,7 +13219,7 @@ async function runArgvHook(hookCmd, cwd, context, stderr, stdout2) {
|
|
|
13219
13219
|
stderr("gji: hook argv command must include a non-empty command\n");
|
|
13220
13220
|
return;
|
|
13221
13221
|
}
|
|
13222
|
-
await new Promise((
|
|
13222
|
+
await new Promise((resolve13) => {
|
|
13223
13223
|
const child = spawn2(command, args, {
|
|
13224
13224
|
cwd,
|
|
13225
13225
|
shell: false,
|
|
@@ -13239,18 +13239,18 @@ async function runArgvHook(hookCmd, cwd, context, stderr, stdout2) {
|
|
|
13239
13239
|
`
|
|
13240
13240
|
);
|
|
13241
13241
|
}
|
|
13242
|
-
|
|
13242
|
+
resolve13();
|
|
13243
13243
|
});
|
|
13244
13244
|
child.on("error", (err) => {
|
|
13245
13245
|
stderr(`gji: hook failed to start: ${err.message}
|
|
13246
13246
|
`);
|
|
13247
|
-
|
|
13247
|
+
resolve13();
|
|
13248
13248
|
});
|
|
13249
13249
|
});
|
|
13250
13250
|
}
|
|
13251
13251
|
async function runShellHook(hookCmd, cwd, context, stderr, stdout2) {
|
|
13252
13252
|
const interpolated = interpolate(hookCmd, context);
|
|
13253
|
-
await new Promise((
|
|
13253
|
+
await new Promise((resolve13) => {
|
|
13254
13254
|
const child = spawn2(interpolated, {
|
|
13255
13255
|
cwd,
|
|
13256
13256
|
shell: true,
|
|
@@ -13268,12 +13268,12 @@ async function runShellHook(hookCmd, cwd, context, stderr, stdout2) {
|
|
|
13268
13268
|
stderr(`gji: hook exited with code ${code}: ${interpolated}
|
|
13269
13269
|
`);
|
|
13270
13270
|
}
|
|
13271
|
-
|
|
13271
|
+
resolve13();
|
|
13272
13272
|
});
|
|
13273
13273
|
child.on("error", (err) => {
|
|
13274
13274
|
stderr(`gji: hook failed to start: ${err.message}
|
|
13275
13275
|
`);
|
|
13276
|
-
|
|
13276
|
+
resolve13();
|
|
13277
13277
|
});
|
|
13278
13278
|
});
|
|
13279
13279
|
}
|
|
@@ -13282,14 +13282,18 @@ function hookEnvironment(context) {
|
|
|
13282
13282
|
...process.env,
|
|
13283
13283
|
GJI_BRANCH: context.branch ?? "",
|
|
13284
13284
|
GJI_PATH: context.path,
|
|
13285
|
-
GJI_REPO: context.repo
|
|
13285
|
+
GJI_REPO: context.repo,
|
|
13286
|
+
...context.slot === null || context.slot === void 0 ? {} : { GJI_SLOT: String(context.slot) }
|
|
13286
13287
|
};
|
|
13287
13288
|
}
|
|
13288
13289
|
function formatArgvHook(command, args) {
|
|
13289
13290
|
return JSON.stringify([command, ...args]);
|
|
13290
13291
|
}
|
|
13291
13292
|
function interpolate(template, context) {
|
|
13292
|
-
return template.replace(/\{\{branch\}\}/g, context.branch ?? "").replace(/\{\{path\}\}/g, context.path).replace(/\{\{repo\}\}/g, context.repo)
|
|
13293
|
+
return template.replace(/\{\{branch\}\}/g, context.branch ?? "").replace(/\{\{path\}\}/g, context.path).replace(/\{\{repo\}\}/g, context.repo).replace(
|
|
13294
|
+
/\{\{slot\}\}/g,
|
|
13295
|
+
context.slot === null || context.slot === void 0 ? "" : String(context.slot)
|
|
13296
|
+
);
|
|
13293
13297
|
}
|
|
13294
13298
|
function extractHooks(config) {
|
|
13295
13299
|
const raw = config.hooks;
|
|
@@ -13486,6 +13490,10 @@ async function detectRepository(cwd) {
|
|
|
13486
13490
|
repoRoot
|
|
13487
13491
|
};
|
|
13488
13492
|
}
|
|
13493
|
+
async function worktreeGitDir(worktreePath) {
|
|
13494
|
+
const rawGitDir = await runGit(worktreePath, ["rev-parse", "--git-dir"]);
|
|
13495
|
+
return isAbsolute2(rawGitDir) ? rawGitDir : resolve3(worktreePath, rawGitDir);
|
|
13496
|
+
}
|
|
13489
13497
|
function resolveWorktreePath(repoRoot, branch, basePath) {
|
|
13490
13498
|
const segments = branch.split("/").filter(Boolean);
|
|
13491
13499
|
if (segments.length === 0) {
|
|
@@ -14277,42 +14285,98 @@ function isHeadless() {
|
|
|
14277
14285
|
return process.env.GJI_NO_TUI === "1";
|
|
14278
14286
|
}
|
|
14279
14287
|
|
|
14288
|
+
// src/slots.ts
|
|
14289
|
+
import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile4 } from "node:fs/promises";
|
|
14290
|
+
import { homedir as homedir4 } from "node:os";
|
|
14291
|
+
import { dirname as dirname4, join as join4, resolve as resolve4 } from "node:path";
|
|
14292
|
+
function SLOTS_FILE_PATH(home = homedir4()) {
|
|
14293
|
+
const configDir = process.env.GJI_CONFIG_DIR;
|
|
14294
|
+
return configDir ? join4(resolve4(configDir), "slots.json") : join4(home, GLOBAL_CONFIG_DIRECTORY, "slots.json");
|
|
14295
|
+
}
|
|
14296
|
+
async function loadSlots(home = homedir4()) {
|
|
14297
|
+
try {
|
|
14298
|
+
const parsed = JSON.parse(
|
|
14299
|
+
await readFile3(SLOTS_FILE_PATH(home), "utf8")
|
|
14300
|
+
);
|
|
14301
|
+
if (!isSlotStore(parsed)) return {};
|
|
14302
|
+
return parsed;
|
|
14303
|
+
} catch {
|
|
14304
|
+
return {};
|
|
14305
|
+
}
|
|
14306
|
+
}
|
|
14307
|
+
async function assignWorktreeSlot(repoRoot, worktreePath, home = homedir4()) {
|
|
14308
|
+
const slots = await loadSlots(home);
|
|
14309
|
+
const existing = slots[worktreePath];
|
|
14310
|
+
if (existing !== void 0) return existing;
|
|
14311
|
+
if (worktreePath !== repoRoot && slots[repoRoot] === void 0)
|
|
14312
|
+
slots[repoRoot] = 0;
|
|
14313
|
+
const used = new Set(Object.values(slots));
|
|
14314
|
+
const slot = worktreePath === repoRoot ? 0 : nextAvailableSlot(used);
|
|
14315
|
+
slots[worktreePath] = slot;
|
|
14316
|
+
await saveSlots(slots, home);
|
|
14317
|
+
return slot;
|
|
14318
|
+
}
|
|
14319
|
+
async function getWorktreeSlot(worktreePath, home = homedir4()) {
|
|
14320
|
+
const slot = (await loadSlots(home))[worktreePath];
|
|
14321
|
+
return slot === void 0 ? null : slot;
|
|
14322
|
+
}
|
|
14323
|
+
async function releaseWorktreeSlot(worktreePath, home = homedir4()) {
|
|
14324
|
+
const slots = await loadSlots(home);
|
|
14325
|
+
if (!(worktreePath in slots)) return;
|
|
14326
|
+
delete slots[worktreePath];
|
|
14327
|
+
await saveSlots(slots, home);
|
|
14328
|
+
}
|
|
14329
|
+
async function saveSlots(slots, home) {
|
|
14330
|
+
const path9 = SLOTS_FILE_PATH(home);
|
|
14331
|
+
await mkdir3(dirname4(path9), { recursive: true });
|
|
14332
|
+
await writeFile4(path9, `${JSON.stringify(slots, null, 2)}
|
|
14333
|
+
`, "utf8");
|
|
14334
|
+
}
|
|
14335
|
+
function nextAvailableSlot(used) {
|
|
14336
|
+
let slot = 1;
|
|
14337
|
+
while (used.has(slot)) slot += 1;
|
|
14338
|
+
return slot;
|
|
14339
|
+
}
|
|
14340
|
+
function isSlotStore(value) {
|
|
14341
|
+
return typeof value === "object" && value !== null && Object.values(value).every((slot) => Number.isInteger(slot) && slot >= 0);
|
|
14342
|
+
}
|
|
14343
|
+
|
|
14280
14344
|
// src/undo.ts
|
|
14281
14345
|
import {
|
|
14282
14346
|
access as access3,
|
|
14283
|
-
mkdir as
|
|
14347
|
+
mkdir as mkdir4,
|
|
14284
14348
|
open,
|
|
14285
|
-
readFile as
|
|
14349
|
+
readFile as readFile4,
|
|
14286
14350
|
rename,
|
|
14287
14351
|
stat,
|
|
14288
14352
|
unlink,
|
|
14289
|
-
writeFile as
|
|
14353
|
+
writeFile as writeFile5
|
|
14290
14354
|
} from "node:fs/promises";
|
|
14291
|
-
import { homedir as
|
|
14292
|
-
import { dirname as
|
|
14355
|
+
import { homedir as homedir5 } from "node:os";
|
|
14356
|
+
import { dirname as dirname5, join as join5, resolve as resolve5 } from "node:path";
|
|
14293
14357
|
var MAX_UNDO_RECORDS = 20;
|
|
14294
14358
|
var UNDO_LOCK_STALE_AFTER_MS = 6e4;
|
|
14295
14359
|
var UNDO_LOCK_RETRY_DELAY_MS = 5;
|
|
14296
14360
|
var UNDO_LOCK_WAIT_TIMEOUT_MS = UNDO_LOCK_STALE_AFTER_MS + UNDO_LOCK_RETRY_DELAY_MS;
|
|
14297
14361
|
var undoLogQueues = /* @__PURE__ */ new Map();
|
|
14298
|
-
function undoLogPath(home =
|
|
14362
|
+
function undoLogPath(home = homedir5()) {
|
|
14299
14363
|
const configured = process.env.GJI_CONFIG_DIR;
|
|
14300
|
-
return
|
|
14301
|
-
configured ?
|
|
14364
|
+
return join5(
|
|
14365
|
+
configured ? resolve5(configured) : join5(home, GLOBAL_CONFIG_DIRECTORY),
|
|
14302
14366
|
"undo-log.json"
|
|
14303
14367
|
);
|
|
14304
14368
|
}
|
|
14305
|
-
async function loadUndoRecords(home =
|
|
14369
|
+
async function loadUndoRecords(home = homedir5()) {
|
|
14306
14370
|
try {
|
|
14307
14371
|
const parsed = JSON.parse(
|
|
14308
|
-
await
|
|
14372
|
+
await readFile4(undoLogPath(home), "utf8")
|
|
14309
14373
|
);
|
|
14310
14374
|
return Array.isArray(parsed) ? parsed.filter(isUndoRecord) : [];
|
|
14311
14375
|
} catch {
|
|
14312
14376
|
return [];
|
|
14313
14377
|
}
|
|
14314
14378
|
}
|
|
14315
|
-
async function recordUndoOperation(op, repoRoot, worktrees, home =
|
|
14379
|
+
async function recordUndoOperation(op, repoRoot, worktrees, home = homedir5()) {
|
|
14316
14380
|
const entries = [];
|
|
14317
14381
|
for (const worktree of worktrees) {
|
|
14318
14382
|
try {
|
|
@@ -14355,7 +14419,7 @@ async function recordUndoOperation(op, repoRoot, worktrees, home = homedir4()) {
|
|
|
14355
14419
|
return record;
|
|
14356
14420
|
});
|
|
14357
14421
|
}
|
|
14358
|
-
async function finalizeUndoOperation(record, removedWorktrees, home =
|
|
14422
|
+
async function finalizeUndoOperation(record, removedWorktrees, home = homedir5()) {
|
|
14359
14423
|
const removedPaths = new Set(
|
|
14360
14424
|
removedWorktrees.map((worktree) => worktree.path)
|
|
14361
14425
|
);
|
|
@@ -14371,7 +14435,7 @@ async function finalizeUndoOperation(record, removedWorktrees, home = homedir4()
|
|
|
14371
14435
|
await writeUndoRecords(home, remaining);
|
|
14372
14436
|
});
|
|
14373
14437
|
}
|
|
14374
|
-
async function restoreUndoRecord(record, home =
|
|
14438
|
+
async function restoreUndoRecord(record, home = homedir5()) {
|
|
14375
14439
|
const restored = [];
|
|
14376
14440
|
const failed = [];
|
|
14377
14441
|
const unresolved = [];
|
|
@@ -14478,7 +14542,7 @@ async function runUndoCommand(options) {
|
|
|
14478
14542
|
}
|
|
14479
14543
|
async function hasMalformedUndoLog() {
|
|
14480
14544
|
try {
|
|
14481
|
-
const parsed = JSON.parse(await
|
|
14545
|
+
const parsed = JSON.parse(await readFile4(undoLogPath(), "utf8"));
|
|
14482
14546
|
return !Array.isArray(parsed) || parsed.some((entry) => !isUndoRecord(entry));
|
|
14483
14547
|
} catch (error) {
|
|
14484
14548
|
return error instanceof Error && "code" in error && error.code !== "ENOENT";
|
|
@@ -14569,7 +14633,7 @@ async function assertCommitExists(repoRoot, headSha) {
|
|
|
14569
14633
|
}
|
|
14570
14634
|
async function enqueueUndoLogWrite(home, operation) {
|
|
14571
14635
|
const path9 = undoLogPath(home);
|
|
14572
|
-
await
|
|
14636
|
+
await mkdir4(dirname5(path9), { recursive: true });
|
|
14573
14637
|
const previous = undoLogQueues.get(path9) ?? Promise.resolve();
|
|
14574
14638
|
const runLocked = async () => {
|
|
14575
14639
|
const release = await acquireUndoLogLock(path9);
|
|
@@ -14610,7 +14674,7 @@ async function acquireUndoLogLock(path9) {
|
|
|
14610
14674
|
const remainingMs = deadline - Date.now();
|
|
14611
14675
|
if (remainingMs > 0)
|
|
14612
14676
|
await new Promise(
|
|
14613
|
-
(
|
|
14677
|
+
(resolve13) => setTimeout(resolve13, Math.min(UNDO_LOCK_RETRY_DELAY_MS, remainingMs))
|
|
14614
14678
|
);
|
|
14615
14679
|
}
|
|
14616
14680
|
}
|
|
@@ -14618,10 +14682,10 @@ async function acquireUndoLogLock(path9) {
|
|
|
14618
14682
|
}
|
|
14619
14683
|
async function writeUndoRecords(home, records) {
|
|
14620
14684
|
const path9 = undoLogPath(home);
|
|
14621
|
-
await
|
|
14685
|
+
await mkdir4(dirname5(path9), { recursive: true });
|
|
14622
14686
|
const temporaryPath = `${path9}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
14623
14687
|
try {
|
|
14624
|
-
await
|
|
14688
|
+
await writeFile5(temporaryPath, `${JSON.stringify(records, null, 2)}
|
|
14625
14689
|
`, {
|
|
14626
14690
|
encoding: "utf8",
|
|
14627
14691
|
flag: "wx"
|
|
@@ -14660,6 +14724,41 @@ function emitUndoError(options, message) {
|
|
|
14660
14724
|
return 1;
|
|
14661
14725
|
}
|
|
14662
14726
|
|
|
14727
|
+
// src/task.ts
|
|
14728
|
+
import { readFile as readFile5, unlink as unlink2, writeFile as writeFile6 } from "node:fs/promises";
|
|
14729
|
+
import { join as join6 } from "node:path";
|
|
14730
|
+
async function readTask(worktreePath) {
|
|
14731
|
+
try {
|
|
14732
|
+
const parsed = JSON.parse(
|
|
14733
|
+
await readFile5(await taskPath(worktreePath), "utf8")
|
|
14734
|
+
);
|
|
14735
|
+
return isTaskMetadata(parsed) ? parsed : null;
|
|
14736
|
+
} catch {
|
|
14737
|
+
return null;
|
|
14738
|
+
}
|
|
14739
|
+
}
|
|
14740
|
+
async function writeTask(worktreePath, task, source = "manual") {
|
|
14741
|
+
const metadata = { source, task, updatedAt: Date.now() };
|
|
14742
|
+
const path9 = await taskPath(worktreePath);
|
|
14743
|
+
await writeFile6(path9, `${JSON.stringify(metadata, null, 2)}
|
|
14744
|
+
`, "utf8");
|
|
14745
|
+
return metadata;
|
|
14746
|
+
}
|
|
14747
|
+
async function clearTask(worktreePath) {
|
|
14748
|
+
try {
|
|
14749
|
+
await unlink2(await taskPath(worktreePath));
|
|
14750
|
+
} catch {
|
|
14751
|
+
}
|
|
14752
|
+
}
|
|
14753
|
+
async function taskPath(worktreePath) {
|
|
14754
|
+
return join6(await worktreeGitDir(worktreePath), "gji-task.json");
|
|
14755
|
+
}
|
|
14756
|
+
function isTaskMetadata(value) {
|
|
14757
|
+
if (typeof value !== "object" || value === null) return false;
|
|
14758
|
+
const candidate = value;
|
|
14759
|
+
return typeof candidate.task === "string" && (candidate.source === "manual" || candidate.source === "pr") && typeof candidate.updatedAt === "number";
|
|
14760
|
+
}
|
|
14761
|
+
|
|
14663
14762
|
// src/worktree-info.ts
|
|
14664
14763
|
var MAX_WORKTREE_INFO_READ_CONCURRENCY = 8;
|
|
14665
14764
|
async function readWorktreeInfos(worktrees) {
|
|
@@ -14686,16 +14785,22 @@ async function mapWithConcurrency(items, limit, mapper) {
|
|
|
14686
14785
|
return results;
|
|
14687
14786
|
}
|
|
14688
14787
|
async function readWorktreeInfo(worktree) {
|
|
14689
|
-
const [healthResult, lastCommitResult] = await Promise.allSettled([
|
|
14788
|
+
const [healthResult, lastCommitResult, slotResult, taskResult] = await Promise.allSettled([
|
|
14690
14789
|
readWorktreeHealth(worktree.path),
|
|
14691
|
-
worktree.branch === null ? null : readBranchLastCommitTimestamp(worktree.path, worktree.branch)
|
|
14790
|
+
worktree.branch === null ? null : readBranchLastCommitTimestamp(worktree.path, worktree.branch),
|
|
14791
|
+
getWorktreeSlot(worktree.path),
|
|
14792
|
+
readTask(worktree.path)
|
|
14692
14793
|
]);
|
|
14693
14794
|
const health = healthResult.status === "fulfilled" ? healthResult.value : null;
|
|
14694
14795
|
const lastCommitTimestamp = lastCommitResult.status === "fulfilled" ? lastCommitResult.value : null;
|
|
14796
|
+
const slot = slotResult.status === "fulfilled" ? slotResult.value : null;
|
|
14797
|
+
const task = taskResult.status === "fulfilled" ? taskResult.value?.task ?? null : null;
|
|
14695
14798
|
return {
|
|
14696
14799
|
...worktree,
|
|
14697
14800
|
lastCommitTimestamp,
|
|
14801
|
+
slot,
|
|
14698
14802
|
status: health?.status ?? "unknown",
|
|
14803
|
+
task,
|
|
14699
14804
|
upstream: buildUpstreamState(worktree.branch, health)
|
|
14700
14805
|
};
|
|
14701
14806
|
}
|
|
@@ -14723,7 +14828,9 @@ function serializeWorktreeInfo(info) {
|
|
|
14723
14828
|
branch: info.branch,
|
|
14724
14829
|
lastCommitTimestamp: info.lastCommitTimestamp,
|
|
14725
14830
|
path: info.path,
|
|
14831
|
+
slot: info.slot,
|
|
14726
14832
|
status: info.status,
|
|
14833
|
+
task: info.task,
|
|
14727
14834
|
upstream: info.upstream
|
|
14728
14835
|
};
|
|
14729
14836
|
}
|
|
@@ -14757,11 +14864,8 @@ function formatAheadBehind(ahead, behind) {
|
|
|
14757
14864
|
function formatLastCommit(timestampSeconds) {
|
|
14758
14865
|
return timestampSeconds === null ? "n/a" : formatRelativeAge(timestampSeconds);
|
|
14759
14866
|
}
|
|
14760
|
-
function formatRelativeAge(timestampSeconds) {
|
|
14761
|
-
const ageSeconds = Math.max(
|
|
14762
|
-
0,
|
|
14763
|
-
Math.floor(Date.now() / 1e3) - timestampSeconds
|
|
14764
|
-
);
|
|
14867
|
+
function formatRelativeAge(timestampSeconds, nowSeconds = Math.floor(Date.now() / 1e3)) {
|
|
14868
|
+
const ageSeconds = Math.max(0, nowSeconds - timestampSeconds);
|
|
14765
14869
|
const units = [
|
|
14766
14870
|
{ label: "y", seconds: 365 * 24 * 60 * 60 },
|
|
14767
14871
|
{ label: "mo", seconds: 30 * 24 * 60 * 60 },
|
|
@@ -14871,6 +14975,7 @@ import { execFile as execFile3 } from "node:child_process";
|
|
|
14871
14975
|
import { promisify as promisify4 } from "node:util";
|
|
14872
14976
|
var execFileAsync3 = promisify4(execFile3);
|
|
14873
14977
|
var PULL_REQUEST_QUERY_TIMEOUT_MS = 2500;
|
|
14978
|
+
var PULL_REQUEST_CACHE_TTL_MS = 3e4;
|
|
14874
14979
|
function parsePullRequestRemote(remoteUrl) {
|
|
14875
14980
|
const parsed = parseRemoteUrl(remoteUrl.trim());
|
|
14876
14981
|
if (parsed === null) return null;
|
|
@@ -14905,10 +15010,37 @@ function parsePullRequestRemote(remoteUrl) {
|
|
|
14905
15010
|
function createPullRequestQuery(dependencies = {}) {
|
|
14906
15011
|
const runCommand2 = dependencies.runCommand ?? defaultRunCommand;
|
|
14907
15012
|
const fetcher = dependencies.fetch ?? fetch;
|
|
15013
|
+
const repositoryCache = /* @__PURE__ */ new Map();
|
|
15014
|
+
const repositoryInFlight = /* @__PURE__ */ new Map();
|
|
15015
|
+
const listRepositoryPullRequests = (repoRoot) => {
|
|
15016
|
+
const cached = repositoryCache.get(repoRoot);
|
|
15017
|
+
if (cached !== void 0 && cached.expiresAt > Date.now()) {
|
|
15018
|
+
return Promise.resolve(cached.value);
|
|
15019
|
+
}
|
|
15020
|
+
const inFlight = repositoryInFlight.get(repoRoot);
|
|
15021
|
+
if (inFlight !== void 0) return inFlight;
|
|
15022
|
+
const request = listOpenPullRequestsForRepository(
|
|
15023
|
+
repoRoot,
|
|
15024
|
+
runCommand2,
|
|
15025
|
+
fetcher
|
|
15026
|
+
).then((value) => {
|
|
15027
|
+
repositoryCache.set(repoRoot, {
|
|
15028
|
+
expiresAt: Date.now() + PULL_REQUEST_CACHE_TTL_MS,
|
|
15029
|
+
value
|
|
15030
|
+
});
|
|
15031
|
+
repositoryInFlight.delete(repoRoot);
|
|
15032
|
+
return value;
|
|
15033
|
+
}).catch((error) => {
|
|
15034
|
+
repositoryInFlight.delete(repoRoot);
|
|
15035
|
+
throw error;
|
|
15036
|
+
});
|
|
15037
|
+
repositoryInFlight.set(repoRoot, request);
|
|
15038
|
+
return request;
|
|
15039
|
+
};
|
|
14908
15040
|
return {
|
|
14909
15041
|
findOpenPullRequest: (repoRoot, number) => findOpenPullRequest(repoRoot, number, runCommand2, fetcher),
|
|
14910
15042
|
listOpenPullRequests: (repoRoot, sourceBranch) => listOpenPullRequests(repoRoot, sourceBranch, runCommand2, fetcher),
|
|
14911
|
-
listOpenPullRequestsForRepository:
|
|
15043
|
+
listOpenPullRequestsForRepository: listRepositoryPullRequests
|
|
14912
15044
|
};
|
|
14913
15045
|
}
|
|
14914
15046
|
async function listOpenPullRequests(repoRoot, sourceBranch, runCommand2, fetcher) {
|
|
@@ -15039,7 +15171,7 @@ function providerCliCommand(remote, query) {
|
|
|
15039
15171
|
"view",
|
|
15040
15172
|
String(query.number),
|
|
15041
15173
|
"--json",
|
|
15042
|
-
"number,url,headRefName,state",
|
|
15174
|
+
"number,url,headRefName,title,state",
|
|
15043
15175
|
"--repo",
|
|
15044
15176
|
coordinate
|
|
15045
15177
|
],
|
|
@@ -15054,7 +15186,7 @@ function providerCliCommand(remote, query) {
|
|
|
15054
15186
|
"open",
|
|
15055
15187
|
...query.kind === "branch" ? ["--head", sourceBranch] : [],
|
|
15056
15188
|
"--json",
|
|
15057
|
-
"number,url,headRefName,state",
|
|
15189
|
+
"number,url,headRefName,title,state",
|
|
15058
15190
|
"--limit",
|
|
15059
15191
|
"100",
|
|
15060
15192
|
"--repo",
|
|
@@ -15155,6 +15287,7 @@ function normalizePullRequest(value, query) {
|
|
|
15155
15287
|
const url = stringValue(
|
|
15156
15288
|
value.html_url ?? value.web_url ?? value.url ?? (isRecord(value.links) && isRecord(value.links.html) ? value.links.html.href : void 0)
|
|
15157
15289
|
);
|
|
15290
|
+
const title = stringValue(value.title);
|
|
15158
15291
|
const state = stringValue(value.state)?.toLowerCase();
|
|
15159
15292
|
if (number === null || url === null) return null;
|
|
15160
15293
|
if (query.kind !== "number" && sourceBranch === null) return null;
|
|
@@ -15163,6 +15296,7 @@ function normalizePullRequest(value, query) {
|
|
|
15163
15296
|
return {
|
|
15164
15297
|
number,
|
|
15165
15298
|
sourceBranch: sourceBranch ?? "",
|
|
15299
|
+
...title === null ? {} : { title },
|
|
15166
15300
|
url
|
|
15167
15301
|
};
|
|
15168
15302
|
}
|
|
@@ -15226,8 +15360,61 @@ function numberValue(value) {
|
|
|
15226
15360
|
return null;
|
|
15227
15361
|
}
|
|
15228
15362
|
|
|
15363
|
+
// src/terminal-text.ts
|
|
15364
|
+
function sanitizeTerminalText(value) {
|
|
15365
|
+
return Array.from(value, (character) => {
|
|
15366
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
15367
|
+
return codePoint <= 31 || codePoint >= 127 && codePoint <= 159 ? " " : character;
|
|
15368
|
+
}).join("");
|
|
15369
|
+
}
|
|
15370
|
+
function middleEllipsize(value, maxLength) {
|
|
15371
|
+
if (terminalWidth(value) <= maxLength) return value;
|
|
15372
|
+
if (maxLength <= 1) return "\u2026";
|
|
15373
|
+
const keep = maxLength - 1;
|
|
15374
|
+
const start = takeTerminalColumns(value, Math.ceil(keep / 2), "start");
|
|
15375
|
+
const end = takeTerminalColumns(value, Math.floor(keep / 2), "end");
|
|
15376
|
+
return `${start}\u2026${end}`;
|
|
15377
|
+
}
|
|
15378
|
+
function startEllipsize(value, maxLength) {
|
|
15379
|
+
if (terminalWidth(value) <= maxLength) return value;
|
|
15380
|
+
if (maxLength <= 1) return "\u2026";
|
|
15381
|
+
return `\u2026${takeTerminalColumns(value, maxLength - 1, "end")}`;
|
|
15382
|
+
}
|
|
15383
|
+
function terminalWidth(value) {
|
|
15384
|
+
let width = 0;
|
|
15385
|
+
for (const character of Array.from(value)) {
|
|
15386
|
+
width += characterTerminalWidth(character);
|
|
15387
|
+
}
|
|
15388
|
+
return width;
|
|
15389
|
+
}
|
|
15390
|
+
function takeTerminalColumns(value, maxWidth, direction) {
|
|
15391
|
+
const characters = direction === "start" ? Array.from(value) : Array.from(value).reverse();
|
|
15392
|
+
const kept = [];
|
|
15393
|
+
let width = 0;
|
|
15394
|
+
for (const character of characters) {
|
|
15395
|
+
const characterWidth = characterTerminalWidth(character);
|
|
15396
|
+
if (width + characterWidth > maxWidth) break;
|
|
15397
|
+
kept.push(character);
|
|
15398
|
+
width += characterWidth;
|
|
15399
|
+
}
|
|
15400
|
+
return direction === "start" ? kept.join("") : kept.reverse().join("");
|
|
15401
|
+
}
|
|
15402
|
+
function characterTerminalWidth(character) {
|
|
15403
|
+
const codePoint = character.codePointAt(0);
|
|
15404
|
+
if (codePoint === void 0) return 0;
|
|
15405
|
+
if (isZeroWidthCodePoint(codePoint) || new RegExp("\\p{Mark}", "u").test(character)) return 0;
|
|
15406
|
+
return isWideCodePoint(codePoint) ? 2 : 1;
|
|
15407
|
+
}
|
|
15408
|
+
function isZeroWidthCodePoint(codePoint) {
|
|
15409
|
+
return codePoint === 0 || codePoint === 8205 || codePoint >= 65024 && codePoint <= 65039;
|
|
15410
|
+
}
|
|
15411
|
+
function isWideCodePoint(codePoint) {
|
|
15412
|
+
return codePoint >= 4352 && codePoint <= 4447 || codePoint === 9001 || codePoint === 9002 || codePoint >= 11904 && codePoint <= 42191 || codePoint >= 44032 && codePoint <= 55203 || codePoint >= 63744 && codePoint <= 64255 || codePoint >= 65040 && codePoint <= 65049 || codePoint >= 65072 && codePoint <= 65135 || codePoint >= 65280 && codePoint <= 65376 || codePoint >= 65504 && codePoint <= 65510 || codePoint >= 127744 && codePoint <= 129791 || codePoint >= 131072 && codePoint <= 262141;
|
|
15413
|
+
}
|
|
15414
|
+
|
|
15229
15415
|
// src/worktree-picker.ts
|
|
15230
15416
|
var MAX_PULL_REQUEST_REPOSITORY_QUERY_CONCURRENCY = 4;
|
|
15417
|
+
var MAX_TASK_READ_CONCURRENCY = 8;
|
|
15231
15418
|
async function buildWorktreePromptEntries(sources, dependencies = {}) {
|
|
15232
15419
|
const metadataMode = dependencies.metadata ?? "full";
|
|
15233
15420
|
const includeMetadata = metadataMode === "full";
|
|
@@ -15237,10 +15424,10 @@ async function buildWorktreePromptEntries(sources, dependencies = {}) {
|
|
|
15237
15424
|
const repositoryPullRequests = queryRepositoryPullRequests === void 0 ? Promise.resolve(null) : readRepositoryPullRequests(sources, queryRepositoryPullRequests);
|
|
15238
15425
|
const [history, infos, pullRequestsByRepository] = await Promise.all([
|
|
15239
15426
|
loadHistory(),
|
|
15240
|
-
includeMetadata ? readWorktreeInfos(sources.map((source) => source.worktree)) :
|
|
15241
|
-
sources
|
|
15242
|
-
|
|
15243
|
-
)
|
|
15427
|
+
includeMetadata ? readWorktreeInfos(sources.map((source) => source.worktree)) : mapWithConcurrency2(
|
|
15428
|
+
sources,
|
|
15429
|
+
MAX_TASK_READ_CONCURRENCY,
|
|
15430
|
+
(source) => createUnhydratedWorktreeInfo(source.worktree)
|
|
15244
15431
|
),
|
|
15245
15432
|
repositoryPullRequests
|
|
15246
15433
|
]);
|
|
@@ -15267,11 +15454,18 @@ async function buildWorktreePromptEntries(sources, dependencies = {}) {
|
|
|
15267
15454
|
({ lastActivityTimestamp: _lastActivityTimestamp, ...entry }) => entry
|
|
15268
15455
|
);
|
|
15269
15456
|
}
|
|
15270
|
-
function createUnhydratedWorktreeInfo(worktree) {
|
|
15457
|
+
async function createUnhydratedWorktreeInfo(worktree) {
|
|
15458
|
+
let task = null;
|
|
15459
|
+
try {
|
|
15460
|
+
task = (await readTask(worktree.path))?.task ?? null;
|
|
15461
|
+
} catch {
|
|
15462
|
+
}
|
|
15271
15463
|
return {
|
|
15272
15464
|
...worktree,
|
|
15273
15465
|
lastCommitTimestamp: null,
|
|
15466
|
+
slot: null,
|
|
15274
15467
|
status: "unknown",
|
|
15468
|
+
task,
|
|
15275
15469
|
upstream: { kind: "unknown" }
|
|
15276
15470
|
};
|
|
15277
15471
|
}
|
|
@@ -15377,18 +15571,22 @@ function buildSearchableWorktreeEntry(worktree) {
|
|
|
15377
15571
|
const metadata = worktree.metadata ?? null;
|
|
15378
15572
|
const branch = worktree.branch ?? "(detached)";
|
|
15379
15573
|
return {
|
|
15380
|
-
detail: [
|
|
15574
|
+
detail: [
|
|
15575
|
+
worktree.repoName,
|
|
15576
|
+
branch,
|
|
15577
|
+
metadata,
|
|
15578
|
+
worktree.task == null ? null : sanitizeTerminalText(worktree.task),
|
|
15579
|
+
worktree.path
|
|
15580
|
+
].filter((part) => part !== null && part.length > 0).join(" \xB7 "),
|
|
15381
15581
|
label: worktree.label,
|
|
15382
15582
|
searchText: buildPromptSearchText(worktree),
|
|
15383
15583
|
value: worktree.path,
|
|
15384
15584
|
worktree: {
|
|
15385
|
-
branch: formatWorktreeBranchLabel(
|
|
15386
|
-
worktree.branch,
|
|
15387
|
-
worktree.pullRequestNumbers
|
|
15388
|
-
),
|
|
15585
|
+
branch: formatWorktreeBranchLabel(worktree.branch, worktree.pullRequests),
|
|
15389
15586
|
metadata,
|
|
15390
15587
|
path: worktree.path,
|
|
15391
|
-
repoName: worktree.repoName
|
|
15588
|
+
repoName: worktree.repoName,
|
|
15589
|
+
task: worktree.task == null ? null : sanitizeTerminalText(worktree.task)
|
|
15392
15590
|
}
|
|
15393
15591
|
};
|
|
15394
15592
|
}
|
|
@@ -15706,6 +15904,7 @@ var SearchablePrompt = class {
|
|
|
15706
15904
|
ellipsize: middleEllipsize,
|
|
15707
15905
|
max: 18,
|
|
15708
15906
|
min: 6,
|
|
15907
|
+
removablePriority: entry.worktree.task === null ? void 0 : 1,
|
|
15709
15908
|
value: entry.worktree.repoName
|
|
15710
15909
|
},
|
|
15711
15910
|
{
|
|
@@ -15719,13 +15918,23 @@ var SearchablePrompt = class {
|
|
|
15719
15918
|
ellipsize: middleEllipsize,
|
|
15720
15919
|
max: 30,
|
|
15721
15920
|
min: 10,
|
|
15921
|
+
removablePriority: entry.worktree.task === null ? void 0 : 0,
|
|
15722
15922
|
value: entry.worktree.metadata
|
|
15723
15923
|
}
|
|
15724
15924
|
],
|
|
15925
|
+
...entry.worktree.task === null ? [] : [
|
|
15926
|
+
{
|
|
15927
|
+
ellipsize: middleEllipsize,
|
|
15928
|
+
max: 36,
|
|
15929
|
+
min: 10,
|
|
15930
|
+
value: entry.worktree.task
|
|
15931
|
+
}
|
|
15932
|
+
],
|
|
15725
15933
|
{
|
|
15726
15934
|
ellipsize: startEllipsize,
|
|
15727
15935
|
max: 36,
|
|
15728
15936
|
min: 12,
|
|
15937
|
+
removablePriority: entry.worktree.task === null ? void 0 : 2,
|
|
15729
15938
|
value: entry.worktree.path
|
|
15730
15939
|
}
|
|
15731
15940
|
],
|
|
@@ -15940,12 +16149,27 @@ function windowPromptEntries(entries, cursor, maxItems) {
|
|
|
15940
16149
|
}
|
|
15941
16150
|
return window;
|
|
15942
16151
|
}
|
|
16152
|
+
function findPromptPieceToRemove(pieces, fallbackIndex) {
|
|
16153
|
+
let candidate = fallbackIndex;
|
|
16154
|
+
let priority = Infinity;
|
|
16155
|
+
for (let index = 0; index < pieces.length; index += 1) {
|
|
16156
|
+
const piecePriority = pieces[index].removablePriority;
|
|
16157
|
+
if (piecePriority !== void 0 && piecePriority < priority) {
|
|
16158
|
+
candidate = index;
|
|
16159
|
+
priority = piecePriority;
|
|
16160
|
+
}
|
|
16161
|
+
}
|
|
16162
|
+
return candidate;
|
|
16163
|
+
}
|
|
15943
16164
|
function fitPromptPieces(pieces, width) {
|
|
15944
16165
|
const separator = " \xB7 ";
|
|
15945
16166
|
let visiblePieces = pieces.filter((piece) => piece.value.length > 0);
|
|
15946
16167
|
let available = width - terminalWidth(separator) * Math.max(0, visiblePieces.length - 1);
|
|
15947
16168
|
while (visiblePieces.length > 1 && available < minimumPieceLength(visiblePieces)) {
|
|
15948
|
-
const removableIndex =
|
|
16169
|
+
const removableIndex = findPromptPieceToRemove(
|
|
16170
|
+
visiblePieces,
|
|
16171
|
+
visiblePieces.length === 4 ? 2 : visiblePieces.length - 2
|
|
16172
|
+
);
|
|
15949
16173
|
visiblePieces = visiblePieces.filter(
|
|
15950
16174
|
(_3, index) => index !== removableIndex
|
|
15951
16175
|
);
|
|
@@ -16015,9 +16239,13 @@ function buildPromptSearchText(worktree) {
|
|
|
16015
16239
|
worktree.branch ?? "detached",
|
|
16016
16240
|
worktree.path,
|
|
16017
16241
|
worktree.label,
|
|
16242
|
+
worktree.task ?? "",
|
|
16018
16243
|
`${worktree.repoName}/${worktree.branch ?? "detached"}`,
|
|
16019
|
-
...(worktree.
|
|
16020
|
-
|
|
16244
|
+
...(worktree.pullRequests ?? []).flatMap((pullRequest) => [
|
|
16245
|
+
`#${pullRequest.number}`,
|
|
16246
|
+
pullRequest.title ?? "",
|
|
16247
|
+
pullRequest.url
|
|
16248
|
+
])
|
|
16021
16249
|
].join(" ").toLowerCase();
|
|
16022
16250
|
}
|
|
16023
16251
|
function buildWorktreePromptEntry(source, info, pullRequests, lastUsedTimestamp, now) {
|
|
@@ -16026,7 +16254,7 @@ function buildWorktreePromptEntry(source, info, pullRequests, lastUsedTimestamp,
|
|
|
16026
16254
|
const lastActivityType = lastUsedTimestamp !== null ? "used" : lastWorkedTimestamp !== null ? "worked" : null;
|
|
16027
16255
|
const branch = formatWorktreeBranchLabel(
|
|
16028
16256
|
source.worktree.branch,
|
|
16029
|
-
pullRequests
|
|
16257
|
+
pullRequests
|
|
16030
16258
|
);
|
|
16031
16259
|
const badges = buildStatusBadges(info);
|
|
16032
16260
|
const recency = formatPromptRecency(
|
|
@@ -16044,6 +16272,7 @@ function buildWorktreePromptEntry(source, info, pullRequests, lastUsedTimestamp,
|
|
|
16044
16272
|
middleEllipsize(source.repoName, 22),
|
|
16045
16273
|
middleEllipsize(branch, 34),
|
|
16046
16274
|
metadata,
|
|
16275
|
+
info.task === null ? null : middleEllipsize(sanitizeTerminalText(info.task), 36),
|
|
16047
16276
|
path9
|
|
16048
16277
|
].filter((part) => part !== null && part.length > 0).join(" \xB7 ");
|
|
16049
16278
|
return {
|
|
@@ -16052,9 +16281,9 @@ function buildWorktreePromptEntry(source, info, pullRequests, lastUsedTimestamp,
|
|
|
16052
16281
|
label,
|
|
16053
16282
|
lastActivityTimestamp,
|
|
16054
16283
|
metadata,
|
|
16055
|
-
|
|
16056
|
-
|
|
16057
|
-
|
|
16284
|
+
pullRequests,
|
|
16285
|
+
repoName: source.repoName,
|
|
16286
|
+
task: info.task
|
|
16058
16287
|
};
|
|
16059
16288
|
}
|
|
16060
16289
|
async function readSourcePullRequests(source, queryPullRequests, pullRequestsByRepository) {
|
|
@@ -16121,10 +16350,13 @@ async function mapWithConcurrency2(items, limit, mapper) {
|
|
|
16121
16350
|
function sortSourcePullRequests(pullRequests) {
|
|
16122
16351
|
return [...pullRequests ?? []].sort((a, b3) => a.number - b3.number);
|
|
16123
16352
|
}
|
|
16124
|
-
function formatWorktreeBranchLabel(branch,
|
|
16353
|
+
function formatWorktreeBranchLabel(branch, pullRequests = []) {
|
|
16125
16354
|
const branchLabel = branch ?? "(detached)";
|
|
16126
|
-
if (
|
|
16127
|
-
return `${branchLabel} (${
|
|
16355
|
+
if (pullRequests.length === 0) return branchLabel;
|
|
16356
|
+
return `${branchLabel} (${pullRequests.map((pullRequest) => {
|
|
16357
|
+
const title = pullRequest.title ? ` ${middleEllipsize(sanitizeTerminalText(pullRequest.title), 48)}` : "";
|
|
16358
|
+
return `#${pullRequest.number}${title}`;
|
|
16359
|
+
}).join(", ")})`;
|
|
16128
16360
|
}
|
|
16129
16361
|
function buildStatusBadges(info) {
|
|
16130
16362
|
const badges = [];
|
|
@@ -16220,60 +16452,6 @@ function scoreWorktreeMatch(entry, query) {
|
|
|
16220
16452
|
}
|
|
16221
16453
|
return buildSearchText(entry.repoName, entry).includes(query) ? 1 : null;
|
|
16222
16454
|
}
|
|
16223
|
-
function middleEllipsize(value, maxLength) {
|
|
16224
|
-
if (terminalWidth(value) <= maxLength) {
|
|
16225
|
-
return value;
|
|
16226
|
-
}
|
|
16227
|
-
if (maxLength <= 1) {
|
|
16228
|
-
return "\u2026";
|
|
16229
|
-
}
|
|
16230
|
-
const keep = maxLength - 1;
|
|
16231
|
-
const start = takeTerminalColumns(value, Math.ceil(keep / 2), "start");
|
|
16232
|
-
const end = takeTerminalColumns(value, Math.floor(keep / 2), "end");
|
|
16233
|
-
return `${start}\u2026${end}`;
|
|
16234
|
-
}
|
|
16235
|
-
function startEllipsize(value, maxLength) {
|
|
16236
|
-
if (terminalWidth(value) <= maxLength) {
|
|
16237
|
-
return value;
|
|
16238
|
-
}
|
|
16239
|
-
if (maxLength <= 1) {
|
|
16240
|
-
return "\u2026";
|
|
16241
|
-
}
|
|
16242
|
-
return `\u2026${takeTerminalColumns(value, maxLength - 1, "end")}`;
|
|
16243
|
-
}
|
|
16244
|
-
function takeTerminalColumns(value, maxWidth, direction) {
|
|
16245
|
-
const characters = direction === "start" ? Array.from(value) : Array.from(value).reverse();
|
|
16246
|
-
const kept = [];
|
|
16247
|
-
let width = 0;
|
|
16248
|
-
for (const character of characters) {
|
|
16249
|
-
const characterWidth = terminalWidth(character);
|
|
16250
|
-
if (width + characterWidth > maxWidth) break;
|
|
16251
|
-
kept.push(character);
|
|
16252
|
-
width += characterWidth;
|
|
16253
|
-
}
|
|
16254
|
-
return direction === "start" ? kept.join("") : kept.reverse().join("");
|
|
16255
|
-
}
|
|
16256
|
-
function terminalWidth(value) {
|
|
16257
|
-
let width = 0;
|
|
16258
|
-
for (const character of Array.from(value)) {
|
|
16259
|
-
width += characterTerminalWidth(character);
|
|
16260
|
-
}
|
|
16261
|
-
return width;
|
|
16262
|
-
}
|
|
16263
|
-
function characterTerminalWidth(character) {
|
|
16264
|
-
const codePoint = character.codePointAt(0);
|
|
16265
|
-
if (codePoint === void 0) return 0;
|
|
16266
|
-
if (isZeroWidthCodePoint(codePoint) || new RegExp("\\p{Mark}", "u").test(character)) {
|
|
16267
|
-
return 0;
|
|
16268
|
-
}
|
|
16269
|
-
return isWideCodePoint(codePoint) ? 2 : 1;
|
|
16270
|
-
}
|
|
16271
|
-
function isZeroWidthCodePoint(codePoint) {
|
|
16272
|
-
return codePoint === 0 || codePoint === 8205 || codePoint >= 65024 && codePoint <= 65039;
|
|
16273
|
-
}
|
|
16274
|
-
function isWideCodePoint(codePoint) {
|
|
16275
|
-
return codePoint >= 4352 && codePoint <= 4447 || codePoint === 9001 || codePoint === 9002 || codePoint >= 11904 && codePoint <= 42191 || codePoint >= 44032 && codePoint <= 55203 || codePoint >= 63744 && codePoint <= 64255 || codePoint >= 65040 && codePoint <= 65049 || codePoint >= 65072 && codePoint <= 65135 || codePoint >= 65280 && codePoint <= 65376 || codePoint >= 65504 && codePoint <= 65510 || codePoint >= 127744 && codePoint <= 129791 || codePoint >= 131072 && codePoint <= 262141;
|
|
16276
|
-
}
|
|
16277
16455
|
|
|
16278
16456
|
// src/worktree-prompts.ts
|
|
16279
16457
|
async function defaultConfirmForceRemoveWorktree(worktreePath) {
|
|
@@ -16486,6 +16664,9 @@ function createCleanCommand(dependencies = {}) {
|
|
|
16486
16664
|
}
|
|
16487
16665
|
}
|
|
16488
16666
|
await finalizeUndoOperation(journal, removedWorktrees);
|
|
16667
|
+
await Promise.all(
|
|
16668
|
+
removedWorktrees.map((worktree) => releaseWorktreeSlot(worktree.path))
|
|
16669
|
+
);
|
|
16489
16670
|
if (options.json) {
|
|
16490
16671
|
const removed = removedWorktrees.map((worktree) => {
|
|
16491
16672
|
const info = selectedInfoByPath.get(worktree.path);
|
|
@@ -16700,6 +16881,7 @@ var TOP_LEVEL_COMMANDS = [
|
|
|
16700
16881
|
{ name: "jump", description: "alias of go" },
|
|
16701
16882
|
{ name: "root", description: "print the main repository root path" },
|
|
16702
16883
|
{ name: "status", description: "summarize repository and worktree health" },
|
|
16884
|
+
{ name: "task", description: "show or update the current worktree task" },
|
|
16703
16885
|
{ name: "sync", description: "fetch and update one or all worktrees" },
|
|
16704
16886
|
{
|
|
16705
16887
|
name: "sync-files",
|
|
@@ -16816,7 +16998,7 @@ _gji_completion() {
|
|
|
16816
16998
|
cur="\${COMP_WORDS[COMP_CWORD]:-}"
|
|
16817
16999
|
|
|
16818
17000
|
if [ "$COMP_CWORD" -eq 1 ]; then
|
|
16819
|
-
|
|
17001
|
+
COMPREPLY=( $(compgen -W "${topLevelCommands} --json --help" -- "$cur") )
|
|
16820
17002
|
return 0
|
|
16821
17003
|
fi
|
|
16822
17004
|
|
|
@@ -16824,7 +17006,7 @@ _gji_completion() {
|
|
|
16824
17006
|
|
|
16825
17007
|
case "$command_name" in
|
|
16826
17008
|
new)
|
|
16827
|
-
|
|
17009
|
+
COMPREPLY=( $(compgen -W "--detached --from-current --no-fetch --take --copy --task --force --open --editor --dry-run --json --help" -- "$cur") )
|
|
16828
17010
|
;;
|
|
16829
17011
|
done)
|
|
16830
17012
|
COMPREPLY=( $(compgen -W "--force --keep-branch --json --help" -- "$cur") )
|
|
@@ -17075,6 +17257,7 @@ complete -c gji -n '__fish_seen_subcommand_from new' -l detached -d 'create a de
|
|
|
17075
17257
|
complete -c gji -n '__fish_seen_subcommand_from new' -l from-current -d 'base the new branch on the current worktree instead of the main worktree'
|
|
17076
17258
|
complete -c gji -n '__fish_seen_subcommand_from new' -l take -d 'move current uncommitted changes into the new worktree'
|
|
17077
17259
|
complete -c gji -n '__fish_seen_subcommand_from new' -l copy -d 'copy current uncommitted changes instead of moving them (requires --take)'
|
|
17260
|
+
complete -c gji -n '__fish_seen_subcommand_from new' -l task -r -d 'record the purpose of the new worktree'
|
|
17078
17261
|
complete -c gji -n '__fish_seen_subcommand_from new' -l force -d 'remove and recreate the worktree if the target path already exists'
|
|
17079
17262
|
complete -c gji -n '__fish_seen_subcommand_from new' -l open -d 'open the new worktree in an editor after creation'
|
|
17080
17263
|
complete -c gji -n '__fish_seen_subcommand_from new' -l editor -r -d 'editor CLI to use with --open (code, cursor, zed, \u2026)'
|
|
@@ -17112,11 +17295,14 @@ complete -c gji -n '__fish_seen_subcommand_from open' -a '(__gji_worktree_branch
|
|
|
17112
17295
|
complete -c gji -n '__fish_seen_subcommand_from go jump' -l print -d 'print the resolved worktree path explicitly'
|
|
17113
17296
|
complete -c gji -n '__fish_seen_subcommand_from go jump' -l root -d 'navigate to the main repository root'
|
|
17114
17297
|
complete -c gji -n '__fish_seen_subcommand_from go jump' -l json -d 'emit JSON for an existing worktree destination'
|
|
17298
|
+
complete -c gji -n '__fish_seen_subcommand_from go jump' -l quiet -d 'suppress the context card'
|
|
17115
17299
|
complete -c gji -n '__fish_seen_subcommand_from go jump' -a '(__gji_worktree_branches)' -d 'worktree branch'
|
|
17116
17300
|
|
|
17117
17301
|
complete -c gji -n '__fish_seen_subcommand_from root' -l print -d 'print the resolved repository root path explicitly'
|
|
17118
17302
|
|
|
17119
17303
|
complete -c gji -n '__fish_seen_subcommand_from status' -l json -d 'print repository and worktree health as JSON'
|
|
17304
|
+
complete -c gji -n '__fish_seen_subcommand_from task' -l clear -d 'clear the current worktree task'
|
|
17305
|
+
complete -c gji -n '__fish_seen_subcommand_from task' -l json -d 'emit task metadata as JSON'
|
|
17120
17306
|
|
|
17121
17307
|
complete -c gji -n '__fish_seen_subcommand_from sync' -l all -d 'sync every worktree in the repository'
|
|
17122
17308
|
complete -c gji -n '__fish_seen_subcommand_from sync' -l json -d 'emit JSON on success or error instead of human-readable output'
|
|
@@ -17142,6 +17328,8 @@ complete -c gji -n '__fish_seen_subcommand_from remove rm' -a '(__gji_worktree_b
|
|
|
17142
17328
|
|
|
17143
17329
|
${hookLines}
|
|
17144
17330
|
|
|
17331
|
+
complete -c gji -n '__fish_use_subcommand' -l json -d 'emit the repository hub as JSON'
|
|
17332
|
+
|
|
17145
17333
|
complete -c gji -n '__fish_seen_subcommand_from warp' -l print -d 'print the resolved worktree path without changing directory'
|
|
17146
17334
|
complete -c gji -n '__fish_seen_subcommand_from warp' -l json -d 'emit JSON on success or error instead of human-readable output'
|
|
17147
17335
|
|
|
@@ -17222,6 +17410,7 @@ local -a command_entries worktree_branches
|
|
|
17222
17410
|
|
|
17223
17411
|
command_entries=(
|
|
17224
17412
|
${commandLines}
|
|
17413
|
+
'--json:emit the repository hub as JSON'
|
|
17225
17414
|
)
|
|
17226
17415
|
|
|
17227
17416
|
if (( CURRENT == 2 )); then
|
|
@@ -17231,7 +17420,7 @@ fi
|
|
|
17231
17420
|
|
|
17232
17421
|
case "\${words[2]}" in
|
|
17233
17422
|
new)
|
|
17234
|
-
_arguments '--detached[create a detached worktree without a branch]' '--from-current[base the new branch on the current worktree instead of the main worktree]' '--no-fetch[skip refreshing the remote default branch before creating the new branch]' '--take[move current uncommitted changes into the new worktree]' '--copy[copy current uncommitted changes instead of moving them (requires --take)]' '--force[remove and recreate the worktree if the target path already exists]' '--open[open the new worktree in an editor after creation]' '--editor[editor CLI to use with --open (code, cursor, zed, \u2026)]:editor:' '--dry-run[show what would be created without executing any git commands or writing files]' '--json[emit JSON on success or error instead of human-readable output]' '2:branch: '
|
|
17423
|
+
_arguments '--detached[create a detached worktree without a branch]' '--from-current[base the new branch on the current worktree instead of the main worktree]' '--no-fetch[skip refreshing the remote default branch before creating the new branch]' '--take[move current uncommitted changes into the new worktree]' '--copy[copy current uncommitted changes instead of moving them (requires --take)]' '--task[record the purpose of the new worktree]:description:' '--force[remove and recreate the worktree if the target path already exists]' '--open[open the new worktree in an editor after creation]' '--editor[editor CLI to use with --open (code, cursor, zed, \u2026)]:editor:' '--dry-run[show what would be created without executing any git commands or writing files]' '--json[emit JSON on success or error instead of human-readable output]' '2:branch: '
|
|
17235
17424
|
;;
|
|
17236
17425
|
done)
|
|
17237
17426
|
_arguments '--force[remove dirty or unmerged worktrees without prompting]' '--keep-branch[remove the worktree but preserve its branch]' '--json[emit JSON on success or error instead of human-readable output]' '2:branch: '
|
|
@@ -17400,9 +17589,9 @@ function writeJson(stdout2, value) {
|
|
|
17400
17589
|
// src/doctor.ts
|
|
17401
17590
|
import { execFile as execFile4 } from "node:child_process";
|
|
17402
17591
|
import { constants as constants3 } from "node:fs";
|
|
17403
|
-
import { access as access6, readFile as
|
|
17404
|
-
import { homedir as
|
|
17405
|
-
import { basename as basename4, dirname as
|
|
17592
|
+
import { access as access6, readFile as readFile7 } from "node:fs/promises";
|
|
17593
|
+
import { homedir as homedir7 } from "node:os";
|
|
17594
|
+
import { basename as basename4, dirname as dirname7, join as join9 } from "node:path";
|
|
17406
17595
|
import { promisify as promisify5 } from "node:util";
|
|
17407
17596
|
|
|
17408
17597
|
// src/editor.ts
|
|
@@ -17436,9 +17625,9 @@ var EDITORS = [
|
|
|
17436
17625
|
];
|
|
17437
17626
|
async function defaultSpawnEditor(cli, args) {
|
|
17438
17627
|
const child = spawn3(cli, args, { detached: true, stdio: "ignore" });
|
|
17439
|
-
await new Promise((
|
|
17628
|
+
await new Promise((resolve13, reject) => {
|
|
17440
17629
|
child.once("error", reject);
|
|
17441
|
-
child.once("spawn",
|
|
17630
|
+
child.once("spawn", resolve13);
|
|
17442
17631
|
});
|
|
17443
17632
|
child.unref();
|
|
17444
17633
|
}
|
|
@@ -17448,29 +17637,29 @@ import { randomUUID } from "node:crypto";
|
|
|
17448
17637
|
import { constants } from "node:fs";
|
|
17449
17638
|
import {
|
|
17450
17639
|
access as access4,
|
|
17451
|
-
mkdir as
|
|
17452
|
-
readFile as
|
|
17640
|
+
mkdir as mkdir5,
|
|
17641
|
+
readFile as readFile6,
|
|
17453
17642
|
realpath,
|
|
17454
17643
|
rename as rename2,
|
|
17455
17644
|
rm as rm2,
|
|
17456
|
-
writeFile as
|
|
17645
|
+
writeFile as writeFile7
|
|
17457
17646
|
} from "node:fs/promises";
|
|
17458
|
-
import { homedir as
|
|
17459
|
-
import { basename as basename3, dirname as
|
|
17647
|
+
import { homedir as homedir6 } from "node:os";
|
|
17648
|
+
import { basename as basename3, dirname as dirname6, join as join7, resolve as resolve6 } from "node:path";
|
|
17460
17649
|
var REGISTRY_FILE_NAME = "repos.json";
|
|
17461
17650
|
var MAX_REGISTRY_ENTRIES = 100;
|
|
17462
17651
|
var registryLocks = /* @__PURE__ */ new Map();
|
|
17463
|
-
function REGISTRY_FILE_PATH(home =
|
|
17652
|
+
function REGISTRY_FILE_PATH(home = homedir6()) {
|
|
17464
17653
|
const configDir = process.env.GJI_CONFIG_DIR;
|
|
17465
17654
|
if (configDir) {
|
|
17466
|
-
return
|
|
17655
|
+
return join7(resolve6(configDir), REGISTRY_FILE_NAME);
|
|
17467
17656
|
}
|
|
17468
|
-
return
|
|
17657
|
+
return join7(home, GLOBAL_CONFIG_DIRECTORY, REGISTRY_FILE_NAME);
|
|
17469
17658
|
}
|
|
17470
|
-
async function loadRegistry(home =
|
|
17659
|
+
async function loadRegistry(home = homedir6()) {
|
|
17471
17660
|
const path9 = REGISTRY_FILE_PATH(home);
|
|
17472
17661
|
try {
|
|
17473
|
-
const raw = await
|
|
17662
|
+
const raw = await readFile6(path9, "utf8");
|
|
17474
17663
|
const parsed = JSON.parse(raw);
|
|
17475
17664
|
if (!Array.isArray(parsed)) return [];
|
|
17476
17665
|
return parsed.filter(isRegistryEntry);
|
|
@@ -17482,10 +17671,10 @@ async function canonicalizeRepoPath(repoPath) {
|
|
|
17482
17671
|
try {
|
|
17483
17672
|
return await realpath(repoPath);
|
|
17484
17673
|
} catch {
|
|
17485
|
-
return
|
|
17674
|
+
return resolve6(repoPath);
|
|
17486
17675
|
}
|
|
17487
17676
|
}
|
|
17488
|
-
async function registerRepo(repoPath, home =
|
|
17677
|
+
async function registerRepo(repoPath, home = homedir6()) {
|
|
17489
17678
|
const registryPath = REGISTRY_FILE_PATH(home);
|
|
17490
17679
|
await withRegistryLock(registryPath, async () => {
|
|
17491
17680
|
const existing = await normalizeRegistryForWrite(await loadRegistry(home));
|
|
@@ -17501,7 +17690,7 @@ async function registerRepo(repoPath, home = homedir5()) {
|
|
|
17501
17690
|
await writeRegistry(next, registryPath);
|
|
17502
17691
|
});
|
|
17503
17692
|
}
|
|
17504
|
-
async function removeMissingRegistryEntries(paths, home =
|
|
17693
|
+
async function removeMissingRegistryEntries(paths, home = homedir6()) {
|
|
17505
17694
|
if (paths.size === 0) return { removedPaths: [], skippedPaths: [] };
|
|
17506
17695
|
const registryPath = REGISTRY_FILE_PATH(home);
|
|
17507
17696
|
return withRegistryLock(registryPath, async () => {
|
|
@@ -17527,8 +17716,8 @@ async function removeMissingRegistryEntries(paths, home = homedir5()) {
|
|
|
17527
17716
|
async function withRegistryLock(registryPath, operation) {
|
|
17528
17717
|
const previous = registryLocks.get(registryPath);
|
|
17529
17718
|
let release;
|
|
17530
|
-
const current = new Promise((
|
|
17531
|
-
release =
|
|
17719
|
+
const current = new Promise((resolve13) => {
|
|
17720
|
+
release = resolve13;
|
|
17532
17721
|
});
|
|
17533
17722
|
registryLocks.set(registryPath, current);
|
|
17534
17723
|
if (previous) await previous;
|
|
@@ -17555,10 +17744,10 @@ function isMissingPathError(error) {
|
|
|
17555
17744
|
return code === "ENOENT" || code === "ENOTDIR";
|
|
17556
17745
|
}
|
|
17557
17746
|
async function writeRegistry(entries, registryPath) {
|
|
17558
|
-
await
|
|
17747
|
+
await mkdir5(dirname6(registryPath), { recursive: true });
|
|
17559
17748
|
const temporaryPath = `${registryPath}.tmp-${process.pid}-${randomUUID()}`;
|
|
17560
17749
|
try {
|
|
17561
|
-
await
|
|
17750
|
+
await writeFile7(
|
|
17562
17751
|
temporaryPath,
|
|
17563
17752
|
`${JSON.stringify(entries, null, 2)}
|
|
17564
17753
|
`,
|
|
@@ -17594,13 +17783,13 @@ function isRegistryEntry(value) {
|
|
|
17594
17783
|
// src/shell-setup.ts
|
|
17595
17784
|
import { constants as constants2 } from "node:fs";
|
|
17596
17785
|
import { access as access5 } from "node:fs/promises";
|
|
17597
|
-
import { delimiter, join as
|
|
17786
|
+
import { delimiter, join as join8 } from "node:path";
|
|
17598
17787
|
var SHELL_INTEGRATION_START_MARKER = "# >>> gji init >>>";
|
|
17599
17788
|
var SHELL_INTEGRATION_END_MARKER = "# <<< gji init <<<";
|
|
17600
17789
|
var ONBOARDING_SHELL_INTEGRATION_START_MARKER = "# >>> gji shell integration >>>";
|
|
17601
17790
|
var ONBOARDING_SHELL_INTEGRATION_END_MARKER = "# <<< gji shell integration <<<";
|
|
17602
17791
|
async function executableExists(command) {
|
|
17603
|
-
const candidates = command.includes("/") ? [command] : (process.env.PATH ?? "").split(delimiter).filter(Boolean).map((directory) =>
|
|
17792
|
+
const candidates = command.includes("/") ? [command] : (process.env.PATH ?? "").split(delimiter).filter(Boolean).map((directory) => join8(directory, command));
|
|
17604
17793
|
for (const path9 of candidates) {
|
|
17605
17794
|
try {
|
|
17606
17795
|
await access5(path9, constants2.X_OK);
|
|
@@ -17613,17 +17802,17 @@ async function executableExists(command) {
|
|
|
17613
17802
|
function resolveShellConfigPath(shell, home) {
|
|
17614
17803
|
switch (shell) {
|
|
17615
17804
|
case "bash":
|
|
17616
|
-
return
|
|
17805
|
+
return join8(home, ".bashrc");
|
|
17617
17806
|
case "fish":
|
|
17618
|
-
return
|
|
17807
|
+
return join8(home, ".config", "fish", "config.fish");
|
|
17619
17808
|
case "zsh":
|
|
17620
|
-
return
|
|
17809
|
+
return join8(home, ".zshrc");
|
|
17621
17810
|
}
|
|
17622
17811
|
}
|
|
17623
17812
|
function resolveCompletionPath(shell, home) {
|
|
17624
17813
|
switch (shell) {
|
|
17625
17814
|
case "bash":
|
|
17626
|
-
return
|
|
17815
|
+
return join8(
|
|
17627
17816
|
home,
|
|
17628
17817
|
".local",
|
|
17629
17818
|
"share",
|
|
@@ -17632,9 +17821,9 @@ function resolveCompletionPath(shell, home) {
|
|
|
17632
17821
|
"gji"
|
|
17633
17822
|
);
|
|
17634
17823
|
case "fish":
|
|
17635
|
-
return
|
|
17824
|
+
return join8(home, ".config", "fish", "completions", "gji.fish");
|
|
17636
17825
|
case "zsh":
|
|
17637
|
-
return
|
|
17826
|
+
return join8(home, ".zsh", "completions", "_gji");
|
|
17638
17827
|
}
|
|
17639
17828
|
}
|
|
17640
17829
|
function hasShellIntegration(config, shell) {
|
|
@@ -17665,7 +17854,7 @@ async function runDoctorCommand(options) {
|
|
|
17665
17854
|
}
|
|
17666
17855
|
return 1;
|
|
17667
17856
|
}
|
|
17668
|
-
const home = options.home ??
|
|
17857
|
+
const home = options.home ?? homedir7();
|
|
17669
17858
|
const shell = resolveSupportedShell(
|
|
17670
17859
|
void 0,
|
|
17671
17860
|
options.shell ?? process.env.SHELL
|
|
@@ -17711,7 +17900,7 @@ async function collectDoctorInspection(cwd, home, shell) {
|
|
|
17711
17900
|
KNOWN_GLOBAL_CONFIG_KEYS
|
|
17712
17901
|
);
|
|
17713
17902
|
const localConfig = repository ? await inspectConfig(
|
|
17714
|
-
|
|
17903
|
+
join9(repository.repoRoot, CONFIG_FILE_NAME),
|
|
17715
17904
|
"local",
|
|
17716
17905
|
KNOWN_CONFIG_KEYS
|
|
17717
17906
|
) : null;
|
|
@@ -17734,11 +17923,30 @@ async function collectDoctorInspection(cwd, home, shell) {
|
|
|
17734
17923
|
),
|
|
17735
17924
|
await checkWorktreeBase(repository, effectiveConfig, home),
|
|
17736
17925
|
registry.check,
|
|
17737
|
-
await checkEditor(effectiveConfig)
|
|
17926
|
+
await checkEditor(effectiveConfig),
|
|
17927
|
+
await checkOrphanSlots(home)
|
|
17738
17928
|
],
|
|
17739
17929
|
missingRegistryPaths: registry.missingPaths
|
|
17740
17930
|
};
|
|
17741
17931
|
}
|
|
17932
|
+
async function checkOrphanSlots(home) {
|
|
17933
|
+
const slots = await loadSlots(home);
|
|
17934
|
+
const missing = (await Promise.all(
|
|
17935
|
+
Object.keys(slots).map(async (path9) => {
|
|
17936
|
+
try {
|
|
17937
|
+
await access6(path9);
|
|
17938
|
+
return false;
|
|
17939
|
+
} catch {
|
|
17940
|
+
return true;
|
|
17941
|
+
}
|
|
17942
|
+
})
|
|
17943
|
+
)).filter(Boolean).length;
|
|
17944
|
+
return {
|
|
17945
|
+
id: "slots",
|
|
17946
|
+
message: missing === 0 ? "worktree slots healthy" : `${missing} orphaned worktree slot${missing === 1 ? "" : "s"}`,
|
|
17947
|
+
status: "ok"
|
|
17948
|
+
};
|
|
17949
|
+
}
|
|
17742
17950
|
function buildDoctorFixes(missingRegistryPaths) {
|
|
17743
17951
|
if (missingRegistryPaths.length === 0) return [];
|
|
17744
17952
|
const count = missingRegistryPaths.length;
|
|
@@ -17801,7 +18009,7 @@ async function detectRepositoryOrSkip(cwd) {
|
|
|
17801
18009
|
}
|
|
17802
18010
|
async function inspectConfig(path9, label, knownKeys) {
|
|
17803
18011
|
try {
|
|
17804
|
-
const contents = await
|
|
18012
|
+
const contents = await readFile7(path9, "utf8");
|
|
17805
18013
|
const value = JSON.parse(contents);
|
|
17806
18014
|
if (!isConfigObject(value)) {
|
|
17807
18015
|
return {
|
|
@@ -17871,7 +18079,7 @@ function resolvePerRepoConfig(repos, repoRoot, home) {
|
|
|
17871
18079
|
}
|
|
17872
18080
|
function expandTilde2(path9, home) {
|
|
17873
18081
|
if (path9 === "~") return home;
|
|
17874
|
-
if (path9.startsWith("~/")) return
|
|
18082
|
+
if (path9.startsWith("~/")) return join9(home, path9.slice(2));
|
|
17875
18083
|
return path9;
|
|
17876
18084
|
}
|
|
17877
18085
|
async function checkGitVersion() {
|
|
@@ -17922,7 +18130,7 @@ async function checkShellIntegration(shell, home) {
|
|
|
17922
18130
|
}
|
|
17923
18131
|
const rcPath = resolveShellConfigPath(shell, home);
|
|
17924
18132
|
try {
|
|
17925
|
-
const contents = await
|
|
18133
|
+
const contents = await readFile7(rcPath, "utf8");
|
|
17926
18134
|
if (hasShellIntegration(contents, shell)) {
|
|
17927
18135
|
return okCheck(
|
|
17928
18136
|
"shell-integration",
|
|
@@ -17998,7 +18206,7 @@ function resolveWorktreeBase(repoRoot, config, home) {
|
|
|
17998
18206
|
if (typeof configuredPath === "string" && (configuredPath.startsWith("/") || configuredPath.startsWith("~"))) {
|
|
17999
18207
|
return expandTilde2(configuredPath, home);
|
|
18000
18208
|
}
|
|
18001
|
-
return
|
|
18209
|
+
return join9(dirname7(repoRoot), "worktrees", basename4(repoRoot));
|
|
18002
18210
|
}
|
|
18003
18211
|
async function findNearestExistingPath(path9) {
|
|
18004
18212
|
let candidate = path9;
|
|
@@ -18007,7 +18215,7 @@ async function findNearestExistingPath(path9) {
|
|
|
18007
18215
|
await access6(candidate, constants3.F_OK);
|
|
18008
18216
|
return candidate;
|
|
18009
18217
|
} catch {
|
|
18010
|
-
const parent =
|
|
18218
|
+
const parent = dirname7(candidate);
|
|
18011
18219
|
if (parent === candidate) return null;
|
|
18012
18220
|
candidate = parent;
|
|
18013
18221
|
}
|
|
@@ -18308,6 +18516,7 @@ async function runDoneCommand(options) {
|
|
|
18308
18516
|
}
|
|
18309
18517
|
}
|
|
18310
18518
|
await finalizeUndoOperation(journal, [target]);
|
|
18519
|
+
await releaseWorktreeSlot(target.path);
|
|
18311
18520
|
await runGit(repository.repoRoot, ["worktree", "prune"]).catch(
|
|
18312
18521
|
() => void 0
|
|
18313
18522
|
);
|
|
@@ -18397,10 +18606,34 @@ function toMessage3(error) {
|
|
|
18397
18606
|
// src/go.ts
|
|
18398
18607
|
import { basename as basename9 } from "node:path";
|
|
18399
18608
|
|
|
18609
|
+
// src/context-card.ts
|
|
18610
|
+
async function renderContextCard(worktreePath) {
|
|
18611
|
+
const worktree = (await listWorktrees(worktreePath)).find(
|
|
18612
|
+
(entry) => entry.path === worktreePath
|
|
18613
|
+
);
|
|
18614
|
+
if (!worktree) return null;
|
|
18615
|
+
const info = await readWorktreeInfo(worktree);
|
|
18616
|
+
const task = await readTask(worktreePath);
|
|
18617
|
+
if (!task) return null;
|
|
18618
|
+
const rows = [`\u250C ${info.branch ?? "(detached)"}`];
|
|
18619
|
+
rows.push(`\u2502 task ${task.task}`);
|
|
18620
|
+
rows.push(`\u2502 state ${formatState(info)}`);
|
|
18621
|
+
if (info.lastCommitTimestamp !== null)
|
|
18622
|
+
rows.push(`\u2502 last ${formatLastCommit2(info)}`);
|
|
18623
|
+
rows.push("\u2514");
|
|
18624
|
+
return rows.join("\n");
|
|
18625
|
+
}
|
|
18626
|
+
function formatState(info) {
|
|
18627
|
+
return `${info.status}${info.upstream.kind === "tracked" ? ` \xB7 \u2191${info.upstream.ahead} \u2193${info.upstream.behind}` : ""}`;
|
|
18628
|
+
}
|
|
18629
|
+
function formatLastCommit2(info) {
|
|
18630
|
+
return new Date(info.lastCommitTimestamp).toISOString();
|
|
18631
|
+
}
|
|
18632
|
+
|
|
18400
18633
|
// src/pr.ts
|
|
18401
18634
|
import { execFile as execFile7 } from "node:child_process";
|
|
18402
|
-
import { mkdir as
|
|
18403
|
-
import { dirname as
|
|
18635
|
+
import { mkdir as mkdir9 } from "node:fs/promises";
|
|
18636
|
+
import { dirname as dirname11 } from "node:path";
|
|
18404
18637
|
import { promisify as promisify8 } from "node:util";
|
|
18405
18638
|
|
|
18406
18639
|
// src/format-bytes.ts
|
|
@@ -18449,27 +18682,27 @@ function formatDuration(ms) {
|
|
|
18449
18682
|
|
|
18450
18683
|
// src/dependency-bootstrap.ts
|
|
18451
18684
|
import { execFile as execFile6 } from "node:child_process";
|
|
18452
|
-
import { lstat as lstat4, readFile as
|
|
18453
|
-
import { join as
|
|
18685
|
+
import { lstat as lstat4, readFile as readFile10, realpath as realpath3, rm as rm5 } from "node:fs/promises";
|
|
18686
|
+
import { join as join13, relative as relative2, resolve as resolve9, sep as sep2 } from "node:path";
|
|
18454
18687
|
import { promisify as promisify7 } from "node:util";
|
|
18455
18688
|
|
|
18456
18689
|
// src/clone-failure-store.ts
|
|
18457
18690
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
18458
18691
|
import {
|
|
18459
|
-
mkdir as
|
|
18692
|
+
mkdir as mkdir6,
|
|
18460
18693
|
mkdtemp,
|
|
18461
18694
|
readdir,
|
|
18462
|
-
readFile as
|
|
18695
|
+
readFile as readFile8,
|
|
18463
18696
|
rename as rename3,
|
|
18464
18697
|
rm as rm3,
|
|
18465
18698
|
rmdir,
|
|
18466
18699
|
stat as stat2,
|
|
18467
|
-
unlink as
|
|
18700
|
+
unlink as unlink3,
|
|
18468
18701
|
utimes,
|
|
18469
|
-
writeFile as
|
|
18702
|
+
writeFile as writeFile8
|
|
18470
18703
|
} from "node:fs/promises";
|
|
18471
|
-
import { homedir as
|
|
18472
|
-
import { dirname as
|
|
18704
|
+
import { homedir as homedir8 } from "node:os";
|
|
18705
|
+
import { dirname as dirname8, join as join10, resolve as resolve7 } from "node:path";
|
|
18473
18706
|
var STATE_FILE_NAME = "state.json";
|
|
18474
18707
|
var STATE_LOCK_SUFFIX = ".lock";
|
|
18475
18708
|
var CLONE_FAILURE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -18536,7 +18769,7 @@ var FileCloneFailureStore = class {
|
|
|
18536
18769
|
}
|
|
18537
18770
|
async readState() {
|
|
18538
18771
|
try {
|
|
18539
|
-
const raw = await
|
|
18772
|
+
const raw = await readFile8(this.stateFilePath(), "utf8");
|
|
18540
18773
|
const parsed = JSON.parse(raw);
|
|
18541
18774
|
if (!isPlainObject2(parsed)) return {};
|
|
18542
18775
|
return isPlainObject2(parsed.syncDirs) ? parsed : { ...parsed, syncDirs: {} };
|
|
@@ -18547,14 +18780,14 @@ var FileCloneFailureStore = class {
|
|
|
18547
18780
|
async writeState(state) {
|
|
18548
18781
|
try {
|
|
18549
18782
|
const path9 = this.stateFilePath();
|
|
18550
|
-
const directory =
|
|
18551
|
-
await
|
|
18783
|
+
const directory = dirname8(path9);
|
|
18784
|
+
await mkdir6(directory, { recursive: true });
|
|
18552
18785
|
const temporaryDirectory = await mkdtemp(
|
|
18553
|
-
|
|
18786
|
+
join10(directory, `.gji-state-${randomUUID2()}-`)
|
|
18554
18787
|
);
|
|
18555
|
-
const temporaryPath =
|
|
18788
|
+
const temporaryPath = join10(temporaryDirectory, STATE_FILE_NAME);
|
|
18556
18789
|
try {
|
|
18557
|
-
await
|
|
18790
|
+
await writeFile8(
|
|
18558
18791
|
temporaryPath,
|
|
18559
18792
|
`${JSON.stringify(state, null, 2)}
|
|
18560
18793
|
`,
|
|
@@ -18567,18 +18800,18 @@ var FileCloneFailureStore = class {
|
|
|
18567
18800
|
} catch {
|
|
18568
18801
|
}
|
|
18569
18802
|
}
|
|
18570
|
-
stateFilePath(home =
|
|
18803
|
+
stateFilePath(home = homedir8()) {
|
|
18571
18804
|
const configuredDirectory = process.env.GJI_CONFIG_DIR;
|
|
18572
|
-
const directory = configuredDirectory ?
|
|
18573
|
-
return
|
|
18805
|
+
const directory = configuredDirectory ? resolve7(configuredDirectory) : join10(home, GLOBAL_CONFIG_DIRECTORY);
|
|
18806
|
+
return join10(directory, STATE_FILE_NAME);
|
|
18574
18807
|
}
|
|
18575
18808
|
};
|
|
18576
18809
|
async function acquireStateLock(lockPath) {
|
|
18577
18810
|
const lockToken = randomUUID2();
|
|
18578
|
-
await
|
|
18811
|
+
await mkdir6(dirname8(lockPath), { recursive: true }).catch(() => void 0);
|
|
18579
18812
|
for (let attempt = 0; attempt < 200; attempt += 1) {
|
|
18580
18813
|
try {
|
|
18581
|
-
await
|
|
18814
|
+
await mkdir6(lockPath);
|
|
18582
18815
|
} catch (error) {
|
|
18583
18816
|
if (!isErrorCode(error, "EEXIST")) return void 0;
|
|
18584
18817
|
try {
|
|
@@ -18598,12 +18831,12 @@ async function acquireStateLock(lockPath) {
|
|
|
18598
18831
|
} catch (lockError) {
|
|
18599
18832
|
if (!isErrorCode(lockError, "ENOENT")) return void 0;
|
|
18600
18833
|
}
|
|
18601
|
-
await new Promise((
|
|
18834
|
+
await new Promise((resolve13) => setTimeout(resolve13, 25));
|
|
18602
18835
|
continue;
|
|
18603
18836
|
}
|
|
18604
18837
|
try {
|
|
18605
|
-
await
|
|
18606
|
-
|
|
18838
|
+
await writeFile8(
|
|
18839
|
+
join10(lockPath, ownerFileName(lockToken)),
|
|
18607
18840
|
`${lockToken}
|
|
18608
18841
|
`,
|
|
18609
18842
|
{
|
|
@@ -18625,7 +18858,7 @@ async function lockFreshnessPath(lockPath) {
|
|
|
18625
18858
|
const owner = (await readdir(lockPath)).find(
|
|
18626
18859
|
(entry) => entry.startsWith("owner-")
|
|
18627
18860
|
);
|
|
18628
|
-
return owner ?
|
|
18861
|
+
return owner ? join10(lockPath, owner) : lockPath;
|
|
18629
18862
|
} catch (error) {
|
|
18630
18863
|
if (isErrorCode(error, "ENOENT")) throw error;
|
|
18631
18864
|
return lockPath;
|
|
@@ -18640,8 +18873,8 @@ function startStateLockHeartbeat(lockPath, lockToken) {
|
|
|
18640
18873
|
}
|
|
18641
18874
|
async function refreshStateLock(lockPath, lockToken) {
|
|
18642
18875
|
try {
|
|
18643
|
-
const ownerPath =
|
|
18644
|
-
await
|
|
18876
|
+
const ownerPath = join10(lockPath, ownerFileName(lockToken));
|
|
18877
|
+
await readFile8(ownerPath, "utf8");
|
|
18645
18878
|
const now = /* @__PURE__ */ new Date();
|
|
18646
18879
|
await utimes(ownerPath, now, now);
|
|
18647
18880
|
} catch {
|
|
@@ -18649,8 +18882,8 @@ async function refreshStateLock(lockPath, lockToken) {
|
|
|
18649
18882
|
}
|
|
18650
18883
|
async function releaseStateLock(lockPath, lockToken) {
|
|
18651
18884
|
try {
|
|
18652
|
-
const ownerPath =
|
|
18653
|
-
await
|
|
18885
|
+
const ownerPath = join10(lockPath, ownerFileName(lockToken));
|
|
18886
|
+
await unlink3(ownerPath);
|
|
18654
18887
|
await rmdir(lockPath);
|
|
18655
18888
|
} catch (error) {
|
|
18656
18889
|
if (!isErrorCode(error, "ENOENT") && !isErrorCode(error, "ENOTEMPTY")) {
|
|
@@ -18663,8 +18896,8 @@ function ownerFileName(lockToken) {
|
|
|
18663
18896
|
}
|
|
18664
18897
|
var defaultCloneFailureStore = new FileCloneFailureStore();
|
|
18665
18898
|
async function cloneFailureScope(source, destination) {
|
|
18666
|
-
const sourcePath =
|
|
18667
|
-
const destinationParent =
|
|
18899
|
+
const sourcePath = resolve7(source);
|
|
18900
|
+
const destinationParent = resolve7(dirname8(destination));
|
|
18668
18901
|
const [sourceDevice, destinationDevice] = await Promise.all([
|
|
18669
18902
|
readDevice(sourcePath),
|
|
18670
18903
|
readDevice(destinationParent)
|
|
@@ -18691,7 +18924,7 @@ function isErrorCode(error, code) {
|
|
|
18691
18924
|
// src/command-runner.ts
|
|
18692
18925
|
import { spawn as spawn4 } from "node:child_process";
|
|
18693
18926
|
var runCommand = async (command, cwd, stderr, stdout2 = (chunk) => process.stdout.write(chunk), options) => {
|
|
18694
|
-
await new Promise((
|
|
18927
|
+
await new Promise((resolve13, reject) => {
|
|
18695
18928
|
const shell = options?.shell ?? true;
|
|
18696
18929
|
const [executable, args] = shell ? [command, []] : splitCommand(command);
|
|
18697
18930
|
const child = spawn4(executable, args, {
|
|
@@ -18710,7 +18943,7 @@ var runCommand = async (command, cwd, stderr, stdout2 = (chunk) => process.stdou
|
|
|
18710
18943
|
if (code !== 0) {
|
|
18711
18944
|
reject(new Error(`exited with code ${code}`));
|
|
18712
18945
|
} else {
|
|
18713
|
-
|
|
18946
|
+
resolve13();
|
|
18714
18947
|
}
|
|
18715
18948
|
});
|
|
18716
18949
|
child.on("error", reject);
|
|
@@ -18734,22 +18967,22 @@ import {
|
|
|
18734
18967
|
chmod,
|
|
18735
18968
|
cp,
|
|
18736
18969
|
lstat as lstat3,
|
|
18737
|
-
mkdir as
|
|
18970
|
+
mkdir as mkdir8,
|
|
18738
18971
|
mkdtemp as mkdtemp2,
|
|
18739
18972
|
opendir,
|
|
18740
18973
|
readdir as readdir2,
|
|
18741
|
-
readFile as
|
|
18974
|
+
readFile as readFile9,
|
|
18742
18975
|
readlink,
|
|
18743
18976
|
realpath as realpath2,
|
|
18744
18977
|
rename as rename4,
|
|
18745
18978
|
rm as rm4,
|
|
18746
18979
|
rmdir as rmdir2,
|
|
18747
18980
|
symlink,
|
|
18748
|
-
unlink as
|
|
18981
|
+
unlink as unlink4,
|
|
18749
18982
|
utimes as utimes2,
|
|
18750
|
-
writeFile as
|
|
18983
|
+
writeFile as writeFile9
|
|
18751
18984
|
} from "node:fs/promises";
|
|
18752
|
-
import { basename as basename6, dirname as
|
|
18985
|
+
import { basename as basename6, dirname as dirname9, join as join12 } from "node:path";
|
|
18753
18986
|
import { promisify as promisify6 } from "node:util";
|
|
18754
18987
|
|
|
18755
18988
|
// src/fs-utils.ts
|
|
@@ -18778,20 +19011,20 @@ function hasErrorCode(error, code) {
|
|
|
18778
19011
|
|
|
18779
19012
|
// src/safe-destination.ts
|
|
18780
19013
|
import { constants as constants4 } from "node:fs";
|
|
18781
|
-
import { lstat as lstat2, mkdir as
|
|
18782
|
-
import { isAbsolute as isAbsolute3, join as
|
|
19014
|
+
import { lstat as lstat2, mkdir as mkdir7, open as open2 } from "node:fs/promises";
|
|
19015
|
+
import { isAbsolute as isAbsolute3, join as join11, relative, resolve as resolve8, sep } from "node:path";
|
|
18783
19016
|
async function openDestinationDirectory(root, path9) {
|
|
18784
19017
|
const segments = destinationSegments(root, path9);
|
|
18785
19018
|
if (process.platform !== "linux") {
|
|
18786
19019
|
await ensureDestinationDirectory(root, path9);
|
|
18787
|
-
return { path:
|
|
19020
|
+
return { path: resolve8(path9), close: async () => void 0 };
|
|
18788
19021
|
}
|
|
18789
19022
|
let handle = await open2(root, destinationDirectoryFlags());
|
|
18790
19023
|
try {
|
|
18791
19024
|
for (const segment of segments) {
|
|
18792
|
-
const childPath =
|
|
19025
|
+
const childPath = join11(fileDescriptorPath(handle.fd), segment);
|
|
18793
19026
|
try {
|
|
18794
|
-
await
|
|
19027
|
+
await mkdir7(childPath);
|
|
18795
19028
|
} catch (error) {
|
|
18796
19029
|
if (!isAlreadyExistsError(error)) throw error;
|
|
18797
19030
|
}
|
|
@@ -18809,8 +19042,8 @@ async function openDestinationDirectory(root, path9) {
|
|
|
18809
19042
|
}
|
|
18810
19043
|
}
|
|
18811
19044
|
async function inspectDestination(root, path9) {
|
|
18812
|
-
const resolvedPath =
|
|
18813
|
-
const resolvedRoot =
|
|
19045
|
+
const resolvedPath = resolve8(path9);
|
|
19046
|
+
const resolvedRoot = resolve8(root);
|
|
18814
19047
|
const distance = relative(resolvedRoot, resolvedPath);
|
|
18815
19048
|
if (isAbsolute3(distance) || distance === ".." || distance.startsWith(`..${sep}`)) {
|
|
18816
19049
|
return { kind: "unsafe", reason: "destination escapes the worktree" };
|
|
@@ -18827,7 +19060,7 @@ async function inspectDestination(root, path9) {
|
|
|
18827
19060
|
}
|
|
18828
19061
|
const segments = distance.split(sep).filter(Boolean);
|
|
18829
19062
|
for (const [index, segment] of segments.entries()) {
|
|
18830
|
-
current =
|
|
19063
|
+
current = resolve8(current, segment);
|
|
18831
19064
|
try {
|
|
18832
19065
|
const stats = await lstat2(current);
|
|
18833
19066
|
if (stats.isSymbolicLink()) {
|
|
@@ -18868,8 +19101,8 @@ async function inspectDestination(root, path9) {
|
|
|
18868
19101
|
}
|
|
18869
19102
|
}
|
|
18870
19103
|
function destinationSegments(root, path9) {
|
|
18871
|
-
const resolvedRoot =
|
|
18872
|
-
const resolvedPath =
|
|
19104
|
+
const resolvedRoot = resolve8(root);
|
|
19105
|
+
const resolvedPath = resolve8(path9);
|
|
18873
19106
|
const distance = relative(resolvedRoot, resolvedPath);
|
|
18874
19107
|
if (isAbsolute3(distance) || distance === ".." || distance.startsWith(`..${sep}`)) {
|
|
18875
19108
|
throw new Error("destination escapes the worktree");
|
|
@@ -18887,8 +19120,8 @@ function fileDescriptorPath(fd) {
|
|
|
18887
19120
|
);
|
|
18888
19121
|
}
|
|
18889
19122
|
async function ensureDestinationDirectory(root, path9) {
|
|
18890
|
-
const resolvedRoot =
|
|
18891
|
-
const resolvedPath =
|
|
19123
|
+
const resolvedRoot = resolve8(root);
|
|
19124
|
+
const resolvedPath = resolve8(path9);
|
|
18892
19125
|
const distance = relative(resolvedRoot, resolvedPath);
|
|
18893
19126
|
if (isAbsolute3(distance) || distance === ".." || distance.startsWith(`..${sep}`)) {
|
|
18894
19127
|
throw new Error("destination escapes the worktree");
|
|
@@ -18899,7 +19132,7 @@ async function ensureDestinationDirectory(root, path9) {
|
|
|
18899
19132
|
throw new Error("worktree root is not a directory");
|
|
18900
19133
|
}
|
|
18901
19134
|
for (const segment of distance.split(sep).filter(Boolean)) {
|
|
18902
|
-
current =
|
|
19135
|
+
current = resolve8(current, segment);
|
|
18903
19136
|
try {
|
|
18904
19137
|
const stats = await lstat2(current);
|
|
18905
19138
|
if (stats.isSymbolicLink() || !stats.isDirectory()) {
|
|
@@ -18908,7 +19141,7 @@ async function ensureDestinationDirectory(root, path9) {
|
|
|
18908
19141
|
} catch (error) {
|
|
18909
19142
|
if (!isNotFoundError(error)) throw error;
|
|
18910
19143
|
try {
|
|
18911
|
-
await
|
|
19144
|
+
await mkdir7(current);
|
|
18912
19145
|
} catch (mkdirError) {
|
|
18913
19146
|
if (!isAlreadyExistsError(mkdirError)) throw mkdirError;
|
|
18914
19147
|
}
|
|
@@ -18941,7 +19174,7 @@ async function cloneDir(source, destination, options = {}) {
|
|
|
18941
19174
|
throw new CloneDestinationExistsError(destination);
|
|
18942
19175
|
}
|
|
18943
19176
|
const startedAt = Date.now();
|
|
18944
|
-
const parent =
|
|
19177
|
+
const parent = dirname9(destination);
|
|
18945
19178
|
let safeParent;
|
|
18946
19179
|
try {
|
|
18947
19180
|
if (options.destinationRoot) {
|
|
@@ -18957,10 +19190,10 @@ async function cloneDir(source, destination, options = {}) {
|
|
|
18957
19190
|
parent
|
|
18958
19191
|
);
|
|
18959
19192
|
} else {
|
|
18960
|
-
await
|
|
19193
|
+
await mkdir8(parent, { recursive: true });
|
|
18961
19194
|
}
|
|
18962
19195
|
const operationParent = safeParent?.path ?? parent;
|
|
18963
|
-
const operationDestination =
|
|
19196
|
+
const operationDestination = join12(operationParent, basename6(destination));
|
|
18964
19197
|
if (await destinationExists(operationDestination)) {
|
|
18965
19198
|
throw new CloneDestinationExistsError(destination);
|
|
18966
19199
|
}
|
|
@@ -18973,9 +19206,9 @@ async function cloneDir(source, destination, options = {}) {
|
|
|
18973
19206
|
try {
|
|
18974
19207
|
reservationPath = await reserveDestination(operationDestination);
|
|
18975
19208
|
temporaryRoot = await mkdtemp2(
|
|
18976
|
-
|
|
19209
|
+
join12(operationParent, `.${basename6(destination)}.gji-clone-`)
|
|
18977
19210
|
);
|
|
18978
|
-
const temporaryDestination =
|
|
19211
|
+
const temporaryDestination = join12(temporaryRoot, basename6(destination));
|
|
18979
19212
|
const copyDirectory = options.copyDirectory ?? (platformIsDarwin(platform3) ? runNativeCloneDirectory : async (source2, target) => {
|
|
18980
19213
|
if (!strategy) {
|
|
18981
19214
|
throw new CloneUnsupportedError(
|
|
@@ -19083,7 +19316,7 @@ async function directorySize(path9) {
|
|
|
19083
19316
|
if (entryCount > SIZE_ESTIMATE_MAX_ENTRIES || Date.now() - startedAt > SIZE_ESTIMATE_MAX_MS) {
|
|
19084
19317
|
throw new Error("directory size estimate exceeded its safety limit");
|
|
19085
19318
|
}
|
|
19086
|
-
const entryPath =
|
|
19319
|
+
const entryPath = join12(current, entry.name);
|
|
19087
19320
|
if (entry.isDirectory()) pending.push(entryPath);
|
|
19088
19321
|
else total += (await lstat3(entryPath)).size;
|
|
19089
19322
|
}
|
|
@@ -19128,10 +19361,10 @@ async function acquireCloneLock(lockPath, destination) {
|
|
|
19128
19361
|
const lockToken = randomUUID3();
|
|
19129
19362
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
19130
19363
|
try {
|
|
19131
|
-
await
|
|
19364
|
+
await mkdir8(lockPath);
|
|
19132
19365
|
try {
|
|
19133
|
-
await
|
|
19134
|
-
|
|
19366
|
+
await writeFile9(
|
|
19367
|
+
join12(lockPath, ownerFileName2(lockToken)),
|
|
19135
19368
|
`${lockToken}
|
|
19136
19369
|
`,
|
|
19137
19370
|
{
|
|
@@ -19164,10 +19397,10 @@ async function acquireCloneLock(lockPath, destination) {
|
|
|
19164
19397
|
throw error;
|
|
19165
19398
|
}
|
|
19166
19399
|
try {
|
|
19167
|
-
await
|
|
19400
|
+
await mkdir8(lockPath);
|
|
19168
19401
|
try {
|
|
19169
|
-
await
|
|
19170
|
-
|
|
19402
|
+
await writeFile9(
|
|
19403
|
+
join12(lockPath, ownerFileName2(lockToken)),
|
|
19171
19404
|
`${lockToken}
|
|
19172
19405
|
`,
|
|
19173
19406
|
{
|
|
@@ -19199,7 +19432,7 @@ async function lockFreshnessPath2(lockPath) {
|
|
|
19199
19432
|
try {
|
|
19200
19433
|
const entries = await readdir2(lockPath);
|
|
19201
19434
|
const owner = entries.find((entry) => entry.startsWith("owner-"));
|
|
19202
|
-
return owner ?
|
|
19435
|
+
return owner ? join12(lockPath, owner) : lockPath;
|
|
19203
19436
|
} catch (error) {
|
|
19204
19437
|
if (isNotFoundError(error)) throw error;
|
|
19205
19438
|
return lockPath;
|
|
@@ -19214,19 +19447,19 @@ async function publishCloneContents(temporaryDestination, destination, reservati
|
|
|
19214
19447
|
const temporaryEntries = await readdir2(temporaryDestination);
|
|
19215
19448
|
for (const entry of temporaryEntries) {
|
|
19216
19449
|
await publishCloneEntry(
|
|
19217
|
-
|
|
19218
|
-
|
|
19450
|
+
join12(temporaryDestination, entry),
|
|
19451
|
+
join12(destination, entry),
|
|
19219
19452
|
() => onEntryPublished(entry),
|
|
19220
19453
|
copyFileEntry
|
|
19221
19454
|
);
|
|
19222
19455
|
}
|
|
19223
|
-
await
|
|
19456
|
+
await unlink4(reservationPath);
|
|
19224
19457
|
}
|
|
19225
19458
|
async function publishCloneEntry(source, destination, onCreated, copyFileEntry) {
|
|
19226
19459
|
const sourceStats = await lstat3(source);
|
|
19227
19460
|
if (sourceStats.isDirectory()) {
|
|
19228
19461
|
try {
|
|
19229
|
-
await
|
|
19462
|
+
await mkdir8(destination);
|
|
19230
19463
|
} catch (error) {
|
|
19231
19464
|
if (isAlreadyExistsError(error)) {
|
|
19232
19465
|
throw new CloneDestinationExistsError(destination);
|
|
@@ -19236,8 +19469,8 @@ async function publishCloneEntry(source, destination, onCreated, copyFileEntry)
|
|
|
19236
19469
|
onCreated();
|
|
19237
19470
|
for (const entry of await readdir2(source)) {
|
|
19238
19471
|
await publishCloneEntry(
|
|
19239
|
-
|
|
19240
|
-
|
|
19472
|
+
join12(source, entry),
|
|
19473
|
+
join12(destination, entry),
|
|
19241
19474
|
() => void 0,
|
|
19242
19475
|
copyFileEntry
|
|
19243
19476
|
);
|
|
@@ -19288,19 +19521,19 @@ async function copyCloneMetadata(sourceStats, destination) {
|
|
|
19288
19521
|
}
|
|
19289
19522
|
async function reserveDestination(destination) {
|
|
19290
19523
|
try {
|
|
19291
|
-
await
|
|
19524
|
+
await mkdir8(destination);
|
|
19292
19525
|
} catch (error) {
|
|
19293
19526
|
if (isAlreadyExistsError(error)) {
|
|
19294
19527
|
throw new CloneDestinationExistsError(destination);
|
|
19295
19528
|
}
|
|
19296
19529
|
throw error;
|
|
19297
19530
|
}
|
|
19298
|
-
const reservationPath =
|
|
19531
|
+
const reservationPath = join12(
|
|
19299
19532
|
destination,
|
|
19300
19533
|
`.gji-clone-reservation-${randomUUID3()}`
|
|
19301
19534
|
);
|
|
19302
19535
|
try {
|
|
19303
|
-
await
|
|
19536
|
+
await writeFile9(reservationPath, "gji clone reservation\n", {
|
|
19304
19537
|
flag: "wx"
|
|
19305
19538
|
});
|
|
19306
19539
|
return reservationPath;
|
|
@@ -19340,8 +19573,8 @@ function startLockHeartbeat(lockPath, lockToken) {
|
|
|
19340
19573
|
}
|
|
19341
19574
|
async function refreshCloneLock(lockPath, lockToken) {
|
|
19342
19575
|
try {
|
|
19343
|
-
const ownerPath =
|
|
19344
|
-
await
|
|
19576
|
+
const ownerPath = join12(lockPath, ownerFileName2(lockToken));
|
|
19577
|
+
await readFile9(ownerPath, "utf8");
|
|
19345
19578
|
const now = /* @__PURE__ */ new Date();
|
|
19346
19579
|
await utimes2(ownerPath, now, now);
|
|
19347
19580
|
} catch {
|
|
@@ -19349,8 +19582,8 @@ async function refreshCloneLock(lockPath, lockToken) {
|
|
|
19349
19582
|
}
|
|
19350
19583
|
async function releaseCloneLock(lockPath, lockToken) {
|
|
19351
19584
|
try {
|
|
19352
|
-
const ownerPath =
|
|
19353
|
-
await
|
|
19585
|
+
const ownerPath = join12(lockPath, ownerFileName2(lockToken));
|
|
19586
|
+
await unlink4(ownerPath);
|
|
19354
19587
|
await rmdir2(lockPath);
|
|
19355
19588
|
} catch (error) {
|
|
19356
19589
|
if (!isNotFoundError(error) && !isDirectoryNotEmptyError(error))
|
|
@@ -19807,7 +20040,7 @@ function createBootstrapAdapters(checkUvRuntime, cargoBuildCommand) {
|
|
|
19807
20040
|
shell: false,
|
|
19808
20041
|
beforeRepair: async (target) => {
|
|
19809
20042
|
if (!target.existingBeforeBootstrap) {
|
|
19810
|
-
await rm5(
|
|
20043
|
+
await rm5(join13(target.targetPath, ".modules.yaml"), {
|
|
19811
20044
|
force: true
|
|
19812
20045
|
});
|
|
19813
20046
|
}
|
|
@@ -19891,9 +20124,9 @@ var LockfileBootstrapAdapter = class {
|
|
|
19891
20124
|
}
|
|
19892
20125
|
async detect(context) {
|
|
19893
20126
|
const detectionRoot = context.detectionRoot ?? context.sourceRoot;
|
|
19894
|
-
if (!await pathExists2(
|
|
19895
|
-
const sourcePath =
|
|
19896
|
-
const targetPath =
|
|
20127
|
+
if (!await pathExists2(join13(detectionRoot, this.lockfile))) return null;
|
|
20128
|
+
const sourcePath = join13(context.sourceRoot, this.relativePath);
|
|
20129
|
+
const targetPath = join13(context.worktreePath, this.relativePath);
|
|
19897
20130
|
const destinationInspection = await inspectDestination(
|
|
19898
20131
|
context.worktreePath,
|
|
19899
20132
|
targetPath
|
|
@@ -19974,13 +20207,13 @@ async function safeSourceDirectory(target) {
|
|
|
19974
20207
|
}
|
|
19975
20208
|
async function defaultCheckUvRuntime(target) {
|
|
19976
20209
|
try {
|
|
19977
|
-
const config = await
|
|
19978
|
-
|
|
20210
|
+
const config = await readFile10(
|
|
20211
|
+
join13(target.sourcePath, "pyvenv.cfg"),
|
|
19979
20212
|
"utf8"
|
|
19980
20213
|
);
|
|
19981
20214
|
const expected = config.match(/^version\s*=\s*(\d+\.\d+)/mu)?.[1];
|
|
19982
20215
|
if (!expected) return false;
|
|
19983
|
-
const sourceInterpreter =
|
|
20216
|
+
const sourceInterpreter = join13(
|
|
19984
20217
|
target.sourcePath,
|
|
19985
20218
|
process.platform === "win32" ? "Scripts/python.exe" : "bin/python"
|
|
19986
20219
|
);
|
|
@@ -20006,12 +20239,12 @@ async function isAllowedSourceRoot(repoRoot, sourceRoot) {
|
|
|
20006
20239
|
realpath3(sourceRoot)
|
|
20007
20240
|
]);
|
|
20008
20241
|
if (resolvedRepoRoot === resolvedSourceRoot) return true;
|
|
20009
|
-
const gitFile = await
|
|
20242
|
+
const gitFile = await readFile10(join13(resolvedSourceRoot, ".git"), "utf8");
|
|
20010
20243
|
const gitDirectory = gitFile.match(/^gitdir:\s*(.+)$/mu)?.[1];
|
|
20011
20244
|
if (!gitDirectory) return false;
|
|
20012
20245
|
return isWithin(
|
|
20013
|
-
|
|
20014
|
-
|
|
20246
|
+
join13(resolvedRepoRoot, ".git", "worktrees"),
|
|
20247
|
+
resolve9(resolvedSourceRoot, gitDirectory)
|
|
20015
20248
|
);
|
|
20016
20249
|
} catch {
|
|
20017
20250
|
return false;
|
|
@@ -20186,12 +20419,12 @@ function toErrorMessage3(error) {
|
|
|
20186
20419
|
|
|
20187
20420
|
// src/sync-plan.ts
|
|
20188
20421
|
import { lstat as lstat5, realpath as realpath4 } from "node:fs/promises";
|
|
20189
|
-
import { isAbsolute as isAbsolute4, join as
|
|
20422
|
+
import { isAbsolute as isAbsolute4, join as join14, relative as relative3, resolve as resolve10, sep as sep3 } from "node:path";
|
|
20190
20423
|
async function prepareSyncDirectoryPlan(repoRoot, worktreePath, directories) {
|
|
20191
20424
|
const normalizedDirectories = directories.map(validateSyncDirPattern).sort(directoryDepthAscending);
|
|
20192
20425
|
const plan = [];
|
|
20193
20426
|
for (const directory of normalizedDirectories) {
|
|
20194
|
-
const destination =
|
|
20427
|
+
const destination = join14(worktreePath, directory);
|
|
20195
20428
|
const destinationState = await inspectDestination(
|
|
20196
20429
|
worktreePath,
|
|
20197
20430
|
destination
|
|
@@ -20265,7 +20498,7 @@ async function estimateSyncDirectories(repoRoot, worktreePath, directories) {
|
|
|
20265
20498
|
return estimateSyncDirectoryPlan(plan);
|
|
20266
20499
|
}
|
|
20267
20500
|
async function resolveSyncDirectorySource(repoRoot, directory) {
|
|
20268
|
-
const source =
|
|
20501
|
+
const source = join14(repoRoot, directory);
|
|
20269
20502
|
let resolvedSource;
|
|
20270
20503
|
try {
|
|
20271
20504
|
resolvedSource = await realpath4(source);
|
|
@@ -20291,7 +20524,7 @@ function directoryDepthAscending(left, right) {
|
|
|
20291
20524
|
return left.split(/[\\/]+/u).length - right.split(/[\\/]+/u).length;
|
|
20292
20525
|
}
|
|
20293
20526
|
function isPathInside2(parent, child) {
|
|
20294
|
-
const relativePath = relative3(
|
|
20527
|
+
const relativePath = relative3(resolve10(parent), resolve10(child));
|
|
20295
20528
|
return relativePath !== "" && !isAbsolute4(relativePath) && relativePath !== ".." && !relativePath.startsWith(`..${sep3}`);
|
|
20296
20529
|
}
|
|
20297
20530
|
function toErrorMessage4(error) {
|
|
@@ -20306,12 +20539,12 @@ import { constants as constants7 } from "node:fs";
|
|
|
20306
20539
|
import { copyFile, lstat as lstat6, realpath as realpath5 } from "node:fs/promises";
|
|
20307
20540
|
import {
|
|
20308
20541
|
basename as basename7,
|
|
20309
|
-
dirname as
|
|
20542
|
+
dirname as dirname10,
|
|
20310
20543
|
isAbsolute as isAbsolute5,
|
|
20311
|
-
join as
|
|
20544
|
+
join as join15,
|
|
20312
20545
|
normalize as normalize2,
|
|
20313
20546
|
relative as relative4,
|
|
20314
|
-
resolve as
|
|
20547
|
+
resolve as resolve11,
|
|
20315
20548
|
sep as sep4
|
|
20316
20549
|
} from "node:path";
|
|
20317
20550
|
async function syncFiles(mainRoot, targetPath, patterns) {
|
|
@@ -20319,7 +20552,7 @@ async function syncFiles(mainRoot, targetPath, patterns) {
|
|
|
20319
20552
|
const normalized = validateSyncFilePattern(pattern);
|
|
20320
20553
|
const sourcePath = await resolveSyncFileSource(mainRoot, normalized);
|
|
20321
20554
|
if (!sourcePath) continue;
|
|
20322
|
-
const destPath =
|
|
20555
|
+
const destPath = join15(targetPath, normalized);
|
|
20323
20556
|
const existingDestination = await readDestinationEntry(destPath);
|
|
20324
20557
|
if (existingDestination?.isSymbolicLink()) {
|
|
20325
20558
|
throw new Error(`destination is a symbolic link: ${destPath}`);
|
|
@@ -20327,7 +20560,7 @@ async function syncFiles(mainRoot, targetPath, patterns) {
|
|
|
20327
20560
|
if (existingDestination) {
|
|
20328
20561
|
continue;
|
|
20329
20562
|
}
|
|
20330
|
-
const destinationParent =
|
|
20563
|
+
const destinationParent = dirname10(destPath);
|
|
20331
20564
|
const beforeCreate = await inspectDestination(
|
|
20332
20565
|
targetPath,
|
|
20333
20566
|
destinationParent
|
|
@@ -20340,7 +20573,7 @@ async function syncFiles(mainRoot, targetPath, patterns) {
|
|
|
20340
20573
|
destinationParent
|
|
20341
20574
|
);
|
|
20342
20575
|
try {
|
|
20343
|
-
const safeDestination =
|
|
20576
|
+
const safeDestination = join15(safeParent.path, basename7(destPath));
|
|
20344
20577
|
const safeExistingDestination = await readDestinationEntry(safeDestination);
|
|
20345
20578
|
if (safeExistingDestination?.isSymbolicLink()) {
|
|
20346
20579
|
throw new Error(`destination is a symbolic link: ${destPath}`);
|
|
@@ -20376,7 +20609,7 @@ async function resolveSyncFileSource(mainRoot, pattern) {
|
|
|
20376
20609
|
try {
|
|
20377
20610
|
[resolvedRoot, resolvedSource] = await Promise.all([
|
|
20378
20611
|
realpath5(mainRoot),
|
|
20379
|
-
realpath5(
|
|
20612
|
+
realpath5(join15(mainRoot, pattern))
|
|
20380
20613
|
]);
|
|
20381
20614
|
} catch (error) {
|
|
20382
20615
|
if (isNotFoundError(error)) return void 0;
|
|
@@ -20390,7 +20623,7 @@ async function resolveSyncFileSource(mainRoot, pattern) {
|
|
|
20390
20623
|
const sourceStats = await lstat6(resolvedSource);
|
|
20391
20624
|
if (!sourceStats.isFile()) {
|
|
20392
20625
|
throw new Error(
|
|
20393
|
-
`syncFiles: source is not a file: ${
|
|
20626
|
+
`syncFiles: source is not a file: ${join15(mainRoot, pattern)}`
|
|
20394
20627
|
);
|
|
20395
20628
|
}
|
|
20396
20629
|
return resolvedSource;
|
|
@@ -20404,13 +20637,13 @@ async function readDestinationEntry(path9) {
|
|
|
20404
20637
|
}
|
|
20405
20638
|
}
|
|
20406
20639
|
function isPathInside3(root, candidate) {
|
|
20407
|
-
const distance = relative4(
|
|
20640
|
+
const distance = relative4(resolve11(root), resolve11(candidate));
|
|
20408
20641
|
return distance === "" || !isAbsolute5(distance) && distance !== ".." && !distance.startsWith(`..${sep4}`);
|
|
20409
20642
|
}
|
|
20410
20643
|
|
|
20411
20644
|
// src/package-manager.ts
|
|
20412
20645
|
import { access as access8, readdir as readdir3 } from "node:fs/promises";
|
|
20413
|
-
import { join as
|
|
20646
|
+
import { join as join16 } from "node:path";
|
|
20414
20647
|
var ENTRIES = [
|
|
20415
20648
|
// JavaScript / TypeScript
|
|
20416
20649
|
{ name: "pnpm", signals: ["pnpm-lock.yaml"], command: "pnpm install" },
|
|
@@ -20540,7 +20773,7 @@ async function detectPackageManager(repoRoot) {
|
|
|
20540
20773
|
async function matchesExact(repoRoot, signals) {
|
|
20541
20774
|
for (const signal of signals) {
|
|
20542
20775
|
try {
|
|
20543
|
-
await access8(
|
|
20776
|
+
await access8(join16(repoRoot, signal));
|
|
20544
20777
|
return true;
|
|
20545
20778
|
} catch {
|
|
20546
20779
|
}
|
|
@@ -20911,13 +21144,15 @@ async function bootstrapWorktree(options) {
|
|
|
20911
21144
|
);
|
|
20912
21145
|
}
|
|
20913
21146
|
const hooks = extractHooks(options.config);
|
|
21147
|
+
const slot = await assignWorktreeSlot(options.repoRoot, options.worktreePath);
|
|
20914
21148
|
await runHook(
|
|
20915
21149
|
hooks["after-create"],
|
|
20916
21150
|
options.worktreePath,
|
|
20917
21151
|
{
|
|
20918
21152
|
branch: options.branch,
|
|
20919
21153
|
path: options.worktreePath,
|
|
20920
|
-
repo: basename8(options.repoRoot)
|
|
21154
|
+
repo: basename8(options.repoRoot),
|
|
21155
|
+
slot
|
|
20921
21156
|
},
|
|
20922
21157
|
options.reporter.write,
|
|
20923
21158
|
options.json ? () => void 0 : options.commandStdout ?? ((chunk) => process.stdout.write(chunk))
|
|
@@ -21113,7 +21348,7 @@ ${dryRunSyncDirs.map(({ dir, bytes }) => `Would clone ${dir} (${formatBytes(byte
|
|
|
21113
21348
|
}
|
|
21114
21349
|
return 1;
|
|
21115
21350
|
}
|
|
21116
|
-
await
|
|
21351
|
+
await mkdir9(dirname11(worktreePath), { recursive: true });
|
|
21117
21352
|
const branchAlreadyExists = await localBranchExists(
|
|
21118
21353
|
repository.repoRoot,
|
|
21119
21354
|
branchName
|
|
@@ -21277,26 +21512,31 @@ async function writeOutput(worktreePath, stdout2, outputEnv) {
|
|
|
21277
21512
|
}
|
|
21278
21513
|
|
|
21279
21514
|
// src/worktree-sources.ts
|
|
21515
|
+
var MAX_REPOSITORY_DISCOVERY_CONCURRENCY = 4;
|
|
21280
21516
|
async function listRegisteredWorktreeSources(cwd, onSkipped) {
|
|
21281
21517
|
const registry = await loadRegistry();
|
|
21282
21518
|
const currentRoot = await detectRepository(cwd).then((repository) => repository.currentRoot).catch(() => null);
|
|
21283
|
-
const results = await
|
|
21284
|
-
registry
|
|
21285
|
-
|
|
21286
|
-
|
|
21287
|
-
|
|
21519
|
+
const results = await mapWithConcurrency3(
|
|
21520
|
+
registry,
|
|
21521
|
+
MAX_REPOSITORY_DISCOVERY_CONCURRENCY,
|
|
21522
|
+
async (entry) => {
|
|
21523
|
+
try {
|
|
21524
|
+
const worktrees = await listWorktrees(entry.path);
|
|
21525
|
+
return { entry, worktrees };
|
|
21526
|
+
} catch {
|
|
21527
|
+
onSkipped?.(entry);
|
|
21528
|
+
return null;
|
|
21529
|
+
}
|
|
21530
|
+
}
|
|
21288
21531
|
);
|
|
21289
21532
|
const allItems = [];
|
|
21290
|
-
for (const
|
|
21291
|
-
if (result
|
|
21292
|
-
|
|
21293
|
-
continue;
|
|
21294
|
-
}
|
|
21295
|
-
const { repoName, repoRoot, worktrees } = result.value;
|
|
21533
|
+
for (const result of results) {
|
|
21534
|
+
if (result === null) continue;
|
|
21535
|
+
const { entry, worktrees } = result;
|
|
21296
21536
|
for (const worktree of worktrees) {
|
|
21297
21537
|
allItems.push({
|
|
21298
|
-
repoRoot,
|
|
21299
|
-
repoName,
|
|
21538
|
+
repoRoot: entry.path,
|
|
21539
|
+
repoName: entry.name,
|
|
21300
21540
|
worktree: {
|
|
21301
21541
|
...worktree,
|
|
21302
21542
|
isCurrent: currentRoot !== null && worktree.path === currentRoot
|
|
@@ -21306,6 +21546,44 @@ async function listRegisteredWorktreeSources(cwd, onSkipped) {
|
|
|
21306
21546
|
}
|
|
21307
21547
|
return allItems;
|
|
21308
21548
|
}
|
|
21549
|
+
async function listDiscoverableWorktreeSources(cwd, onSkipped) {
|
|
21550
|
+
const currentRepository = await detectRepository(cwd).catch(() => null);
|
|
21551
|
+
const registeredSources = await listRegisteredWorktreeSources(cwd, onSkipped);
|
|
21552
|
+
if (currentRepository === null) return dedupeSources(registeredSources);
|
|
21553
|
+
let currentSources = [];
|
|
21554
|
+
try {
|
|
21555
|
+
currentSources = (await listWorktrees(cwd)).map((worktree) => ({
|
|
21556
|
+
repoName: currentRepository.repoName,
|
|
21557
|
+
repoRoot: currentRepository.repoRoot,
|
|
21558
|
+
worktree
|
|
21559
|
+
}));
|
|
21560
|
+
} catch {
|
|
21561
|
+
}
|
|
21562
|
+
return dedupeSources([...currentSources, ...registeredSources]);
|
|
21563
|
+
}
|
|
21564
|
+
async function mapWithConcurrency3(items, limit, mapper) {
|
|
21565
|
+
const results = new Array(items.length);
|
|
21566
|
+
let nextIndex = 0;
|
|
21567
|
+
async function readNext() {
|
|
21568
|
+
for (; ; ) {
|
|
21569
|
+
const index = nextIndex++;
|
|
21570
|
+
if (index >= items.length) return;
|
|
21571
|
+
results[index] = await mapper(items[index]);
|
|
21572
|
+
}
|
|
21573
|
+
}
|
|
21574
|
+
await Promise.all(
|
|
21575
|
+
Array.from({ length: Math.min(limit, items.length) }, () => readNext())
|
|
21576
|
+
);
|
|
21577
|
+
return results;
|
|
21578
|
+
}
|
|
21579
|
+
function dedupeSources(sources) {
|
|
21580
|
+
const seen = /* @__PURE__ */ new Set();
|
|
21581
|
+
return sources.filter((source) => {
|
|
21582
|
+
if (seen.has(source.worktree.path)) return false;
|
|
21583
|
+
seen.add(source.worktree.path);
|
|
21584
|
+
return true;
|
|
21585
|
+
});
|
|
21586
|
+
}
|
|
21309
21587
|
|
|
21310
21588
|
// src/go-resolver.ts
|
|
21311
21589
|
async function resolveGoBranch(options) {
|
|
@@ -21487,8 +21765,8 @@ function normalizeRemoteUrl(value) {
|
|
|
21487
21765
|
|
|
21488
21766
|
// src/new.ts
|
|
21489
21767
|
import { execFile as execFile8 } from "node:child_process";
|
|
21490
|
-
import { access as access9, mkdir as
|
|
21491
|
-
import { dirname as
|
|
21768
|
+
import { access as access9, mkdir as mkdir10 } from "node:fs/promises";
|
|
21769
|
+
import { dirname as dirname12, resolve as resolve12 } from "node:path";
|
|
21492
21770
|
import { promisify as promisify9 } from "node:util";
|
|
21493
21771
|
var execFileAsync8 = promisify9(execFile8);
|
|
21494
21772
|
var NEW_OUTPUT_FILE_ENV = "GJI_NEW_OUTPUT_FILE";
|
|
@@ -21742,7 +22020,7 @@ ${dryRunSyncDirs.map(({ dir, bytes }) => `Would clone ${dir} (${formatBytes(byte
|
|
|
21742
22020
|
}
|
|
21743
22021
|
return 0;
|
|
21744
22022
|
}
|
|
21745
|
-
await
|
|
22023
|
+
await mkdir10(dirname12(worktreePath), { recursive: true });
|
|
21746
22024
|
const startPoint = options.fromCurrent && !options.detached ? await resolveCurrentWorktreeHead(options.cwd) : void 0;
|
|
21747
22025
|
const takeStartPoint = options.take ? await resolveCurrentWorktreeHead(options.cwd) : void 0;
|
|
21748
22026
|
const changedTakeFiles = options.take ? await listTakeFiles(options.cwd) : [];
|
|
@@ -21842,6 +22120,16 @@ ${dryRunSyncDirs.map(({ dir, bytes }) => `Would clone ${dir} (${formatBytes(byte
|
|
|
21842
22120
|
`could not apply taken changes; your changes are safe in stash: ${stashSha}`
|
|
21843
22121
|
);
|
|
21844
22122
|
}
|
|
22123
|
+
if (options.task !== void 0) {
|
|
22124
|
+
try {
|
|
22125
|
+
await writeTask(worktreePath, options.task);
|
|
22126
|
+
} catch (error) {
|
|
22127
|
+
options.stderr(
|
|
22128
|
+
`Warning: could not save task metadata: ${error instanceof Error ? error.message : String(error)}
|
|
22129
|
+
`
|
|
22130
|
+
);
|
|
22131
|
+
}
|
|
22132
|
+
}
|
|
21845
22133
|
const bootstrap = await bootstrapWorktree({
|
|
21846
22134
|
branch: worktreeName,
|
|
21847
22135
|
cloneDirectory,
|
|
@@ -22165,7 +22453,7 @@ async function detectInProgressGitState(cwd) {
|
|
|
22165
22453
|
["rev-parse", "--git-path", marker],
|
|
22166
22454
|
{ cwd }
|
|
22167
22455
|
);
|
|
22168
|
-
await access9(
|
|
22456
|
+
await access9(resolve12(cwd, stdout2.trim()));
|
|
22169
22457
|
return name;
|
|
22170
22458
|
} catch {
|
|
22171
22459
|
}
|
|
@@ -22579,10 +22867,16 @@ async function navigateToExistingWorktree(options, path9, worktree) {
|
|
|
22579
22867
|
{
|
|
22580
22868
|
branch: worktree?.branch ?? void 0,
|
|
22581
22869
|
path: path9,
|
|
22582
|
-
repo: basename9(repository.repoRoot)
|
|
22870
|
+
repo: basename9(repository.repoRoot),
|
|
22871
|
+
slot: await getWorktreeSlot(path9)
|
|
22583
22872
|
},
|
|
22584
22873
|
options.stderr
|
|
22585
22874
|
);
|
|
22875
|
+
if (!options.quiet) {
|
|
22876
|
+
const card = await renderContextCard(path9);
|
|
22877
|
+
if (card) options.stderr(`${card}
|
|
22878
|
+
`);
|
|
22879
|
+
}
|
|
22586
22880
|
await recordWorktreeUsage(path9, worktree?.branch ?? null);
|
|
22587
22881
|
await writeShellOutput(GO_OUTPUT_FILE_ENV, path9, options.stdout);
|
|
22588
22882
|
return 0;
|
|
@@ -22710,11 +23004,288 @@ async function runHistoryCommand(options) {
|
|
|
22710
23004
|
return 0;
|
|
22711
23005
|
}
|
|
22712
23006
|
|
|
23007
|
+
// src/hub.ts
|
|
23008
|
+
import { basename as basename10 } from "node:path";
|
|
23009
|
+
var MAX_HUB_REPOSITORY_CONCURRENCY = 4;
|
|
23010
|
+
var defaultDependencies = {
|
|
23011
|
+
queryRepositoryPullRequests: createPullRequestQuery().listOpenPullRequestsForRepository
|
|
23012
|
+
};
|
|
23013
|
+
async function buildHubData(cwd, dependencies = {}) {
|
|
23014
|
+
const queryRepositoryPullRequests = dependencies.queryRepositoryPullRequests ?? defaultDependencies.queryRepositoryPullRequests;
|
|
23015
|
+
const currentRepository = await detectRepository(cwd).catch(() => null);
|
|
23016
|
+
const history = await loadHistory();
|
|
23017
|
+
const lastUsedByPath = new Map(
|
|
23018
|
+
history.map((entry) => [entry.path, entry.timestamp])
|
|
23019
|
+
);
|
|
23020
|
+
const sources = await listDiscoverableWorktreeSources(cwd);
|
|
23021
|
+
const groups = /* @__PURE__ */ new Map();
|
|
23022
|
+
for (const source of sources) {
|
|
23023
|
+
if (!source.repoRoot) continue;
|
|
23024
|
+
const repoRoot = source.repoRoot;
|
|
23025
|
+
const existing = groups.get(repoRoot);
|
|
23026
|
+
if (existing) {
|
|
23027
|
+
existing.sources.push(source);
|
|
23028
|
+
} else {
|
|
23029
|
+
groups.set(repoRoot, {
|
|
23030
|
+
name: source.repoName || basename10(repoRoot),
|
|
23031
|
+
sources: [source]
|
|
23032
|
+
});
|
|
23033
|
+
}
|
|
23034
|
+
}
|
|
23035
|
+
const repositories = await mapWithConcurrency4(
|
|
23036
|
+
[...groups.entries()],
|
|
23037
|
+
MAX_HUB_REPOSITORY_CONCURRENCY,
|
|
23038
|
+
async ([root, group]) => {
|
|
23039
|
+
const infos = await readWorktreeInfos(
|
|
23040
|
+
group.sources.map((source) => source.worktree)
|
|
23041
|
+
);
|
|
23042
|
+
let pullRequests = [];
|
|
23043
|
+
try {
|
|
23044
|
+
pullRequests = await queryRepositoryPullRequests(root);
|
|
23045
|
+
} catch {
|
|
23046
|
+
}
|
|
23047
|
+
const worktrees = infos.map((info) => ({
|
|
23048
|
+
...info,
|
|
23049
|
+
lastUsedTimestamp: lastUsedByPath.get(info.path) ?? null,
|
|
23050
|
+
pullRequests: pullRequests.filter(
|
|
23051
|
+
(pullRequest) => pullRequest.sourceBranch === info.branch
|
|
23052
|
+
)
|
|
23053
|
+
}));
|
|
23054
|
+
return {
|
|
23055
|
+
current: currentRepository?.repoRoot === root,
|
|
23056
|
+
name: group.name,
|
|
23057
|
+
pullRequests,
|
|
23058
|
+
root,
|
|
23059
|
+
worktrees
|
|
23060
|
+
};
|
|
23061
|
+
}
|
|
23062
|
+
);
|
|
23063
|
+
repositories.sort((left, right) => {
|
|
23064
|
+
if (left.current !== right.current) return left.current ? -1 : 1;
|
|
23065
|
+
return left.name.localeCompare(right.name);
|
|
23066
|
+
});
|
|
23067
|
+
return {
|
|
23068
|
+
currentRepository: currentRepository?.repoRoot ?? null,
|
|
23069
|
+
repositories
|
|
23070
|
+
};
|
|
23071
|
+
}
|
|
23072
|
+
async function runHubCommand(options, dependencies = {}) {
|
|
23073
|
+
const data = await buildHubData(options.cwd, dependencies);
|
|
23074
|
+
if (options.json) {
|
|
23075
|
+
options.stdout(`${JSON.stringify(data, null, 2)}
|
|
23076
|
+
`);
|
|
23077
|
+
return 0;
|
|
23078
|
+
}
|
|
23079
|
+
options.stdout(
|
|
23080
|
+
`${formatHubOutput(
|
|
23081
|
+
data,
|
|
23082
|
+
options.columns ?? process.stdout.columns ?? 80,
|
|
23083
|
+
options.now ?? Date.now()
|
|
23084
|
+
)}
|
|
23085
|
+
`
|
|
23086
|
+
);
|
|
23087
|
+
return 0;
|
|
23088
|
+
}
|
|
23089
|
+
function formatHubOutput(data, columns = process.stdout.columns ?? 80, now = Date.now()) {
|
|
23090
|
+
if (data.repositories.length === 0) {
|
|
23091
|
+
return "No registered repositories. Run gji from a repository to add one.";
|
|
23092
|
+
}
|
|
23093
|
+
const lineWidth = Math.max(1, columns);
|
|
23094
|
+
const allWorktrees = data.repositories.flatMap(
|
|
23095
|
+
(repository) => repository.worktrees.map((worktree) => ({ repository, worktree }))
|
|
23096
|
+
);
|
|
23097
|
+
const attention = allWorktrees.filter(
|
|
23098
|
+
({ worktree }) => isActionable(worktree)
|
|
23099
|
+
);
|
|
23100
|
+
const current = allWorktrees.find(({ worktree }) => worktree.isCurrent) ?? null;
|
|
23101
|
+
const next = chooseNextWorktree(allWorktrees);
|
|
23102
|
+
const remainingAttention = attention.filter(
|
|
23103
|
+
(entry) => entry !== current && entry !== next
|
|
23104
|
+
);
|
|
23105
|
+
const quiet = allWorktrees.filter(
|
|
23106
|
+
(entry) => entry !== current && entry !== next && !attention.includes(entry)
|
|
23107
|
+
);
|
|
23108
|
+
const recentOther = [...quiet].sort(
|
|
23109
|
+
(left, right) => (right.worktree.lastCommitTimestamp ?? 0) - (left.worktree.lastCommitTimestamp ?? 0)
|
|
23110
|
+
).slice(0, 5);
|
|
23111
|
+
const hiddenCount = quiet.length - recentOther.length;
|
|
23112
|
+
const lines = [
|
|
23113
|
+
`GJI ${data.repositories.length} repositories \xB7 ${allWorktrees.length} worktrees${attention.length > 0 ? ` \xB7 ${attention.length} need attention` : ""}`,
|
|
23114
|
+
""
|
|
23115
|
+
];
|
|
23116
|
+
if (data.repositories.length > 1) {
|
|
23117
|
+
lines.push("REPOSITORIES");
|
|
23118
|
+
for (const repository of data.repositories) {
|
|
23119
|
+
lines.push(formatRepositorySummary(repository, lineWidth));
|
|
23120
|
+
}
|
|
23121
|
+
lines.push("");
|
|
23122
|
+
}
|
|
23123
|
+
if (current !== null) {
|
|
23124
|
+
lines.push("CURRENT");
|
|
23125
|
+
lines.push(...formatHubWorktree(current, lineWidth, "@", now));
|
|
23126
|
+
lines.push("");
|
|
23127
|
+
}
|
|
23128
|
+
if (next !== null && next !== current) {
|
|
23129
|
+
lines.push("NEXT");
|
|
23130
|
+
lines.push(...formatHubWorktree(next, lineWidth, "\u203A", now));
|
|
23131
|
+
lines.push("");
|
|
23132
|
+
}
|
|
23133
|
+
if (remainingAttention.length > 0) {
|
|
23134
|
+
lines.push("ATTENTION");
|
|
23135
|
+
for (const entry of remainingAttention) {
|
|
23136
|
+
lines.push(...formatHubWorktree(entry, lineWidth, "!", now));
|
|
23137
|
+
}
|
|
23138
|
+
lines.push("");
|
|
23139
|
+
}
|
|
23140
|
+
if (recentOther.length > 0) {
|
|
23141
|
+
lines.push("OTHER");
|
|
23142
|
+
for (const entry of recentOther) {
|
|
23143
|
+
lines.push(...formatHubWorktree(entry, lineWidth, " ", now));
|
|
23144
|
+
}
|
|
23145
|
+
if (hiddenCount > 0) {
|
|
23146
|
+
lines.push(
|
|
23147
|
+
` ${hiddenCount} quiet worktree${hiddenCount === 1 ? "" : "s"} hidden`
|
|
23148
|
+
);
|
|
23149
|
+
}
|
|
23150
|
+
lines.push("");
|
|
23151
|
+
}
|
|
23152
|
+
if (next === null && remainingAttention.length === 0 && recentOther.length === 0) {
|
|
23153
|
+
lines.push("No active worktrees.");
|
|
23154
|
+
}
|
|
23155
|
+
lines.push("Run `gji go <repo>/<branch>` to switch worktrees.");
|
|
23156
|
+
return lines.map(
|
|
23157
|
+
(line) => terminalWidth(line) > lineWidth ? middleEllipsize(line, lineWidth) : line
|
|
23158
|
+
).join("\n").trimEnd();
|
|
23159
|
+
}
|
|
23160
|
+
function formatRepositorySummary(repository, lineWidth) {
|
|
23161
|
+
const attentionCount = repository.worktrees.filter(isActionable).length;
|
|
23162
|
+
const attention = attentionCount === 0 ? "healthy" : `${attentionCount} attention`;
|
|
23163
|
+
const marker = repository.current ? "*" : " ";
|
|
23164
|
+
return middleEllipsize(
|
|
23165
|
+
`${marker} ${repository.name} \xB7 ${repository.worktrees.length} worktree${repository.worktrees.length === 1 ? "" : "s"} \xB7 ${attention}`,
|
|
23166
|
+
lineWidth
|
|
23167
|
+
);
|
|
23168
|
+
}
|
|
23169
|
+
function chooseNextWorktree(entries) {
|
|
23170
|
+
return [...entries].sort((left, right) => {
|
|
23171
|
+
const scoreDifference = nextWorktreeScore(right) - nextWorktreeScore(left);
|
|
23172
|
+
if (scoreDifference !== 0) return scoreDifference;
|
|
23173
|
+
const lastUsedDifference = (right.worktree.lastUsedTimestamp ?? 0) - (left.worktree.lastUsedTimestamp ?? 0);
|
|
23174
|
+
if (lastUsedDifference !== 0) return lastUsedDifference;
|
|
23175
|
+
return (right.worktree.lastCommitTimestamp ?? 0) - (left.worktree.lastCommitTimestamp ?? 0);
|
|
23176
|
+
})[0] ?? null;
|
|
23177
|
+
}
|
|
23178
|
+
function nextWorktreeScore(entry) {
|
|
23179
|
+
const { worktree } = entry;
|
|
23180
|
+
let score = worktree.isCurrent && (worktree.task !== null || isActionable(worktree)) ? 100 : 0;
|
|
23181
|
+
if (worktree.task !== null) score += 40;
|
|
23182
|
+
if (worktree.status === "dirty") score += 30;
|
|
23183
|
+
if (worktree.upstream.kind === "stale") score += 25;
|
|
23184
|
+
if (worktree.upstream.kind === "tracked" && worktree.upstream.behind > 0) {
|
|
23185
|
+
score += 25;
|
|
23186
|
+
}
|
|
23187
|
+
if (worktree.pullRequests.length > 0) score += 20;
|
|
23188
|
+
if (worktree.lastUsedTimestamp !== null && worktree.lastUsedTimestamp > 0) {
|
|
23189
|
+
score += 15;
|
|
23190
|
+
}
|
|
23191
|
+
return score;
|
|
23192
|
+
}
|
|
23193
|
+
function isActionable(worktree) {
|
|
23194
|
+
return worktree.status === "dirty" || worktree.upstream.kind === "stale" || worktree.upstream.kind === "tracked" && worktree.upstream.behind > 0 || worktree.pullRequests.length > 0;
|
|
23195
|
+
}
|
|
23196
|
+
function formatHubWorktree(entry, lineWidth, marker, now) {
|
|
23197
|
+
const { repository, worktree } = entry;
|
|
23198
|
+
const branch = worktree.branch ?? "(detached)";
|
|
23199
|
+
const status = [
|
|
23200
|
+
formatWorktreeHealth(worktree),
|
|
23201
|
+
formatWorktreeUpstream(worktree),
|
|
23202
|
+
worktree.pullRequests.length > 0 ? worktree.pullRequests.map(
|
|
23203
|
+
(pullRequest) => pullRequest.title ? `#${pullRequest.number} ${formatHumanText(pullRequest.title, 60)}` : `#${pullRequest.number}`
|
|
23204
|
+
).join(", ") : null,
|
|
23205
|
+
formatWorktreeActivity(worktree, now)
|
|
23206
|
+
].filter((value) => value !== null).join(" \xB7 ");
|
|
23207
|
+
const task = worktree.task === null ? null : `task: ${formatHumanText(worktree.task, 80)}`;
|
|
23208
|
+
const target = `${repository.name}/${branch}`;
|
|
23209
|
+
const lines = [`${marker} ${middleEllipsize(target, lineWidth - 2)}`];
|
|
23210
|
+
if (status.length > 0) {
|
|
23211
|
+
lines.push(` ${middleEllipsize(status, lineWidth - 2)}`);
|
|
23212
|
+
}
|
|
23213
|
+
if (task !== null) {
|
|
23214
|
+
lines.push(` ${middleEllipsize(task, lineWidth - 2)}`);
|
|
23215
|
+
}
|
|
23216
|
+
if (marker === "@" || marker === "\u203A" || marker === "!") {
|
|
23217
|
+
lines.push(` ${middleEllipsize(nextAction(worktree), lineWidth - 2)}`);
|
|
23218
|
+
}
|
|
23219
|
+
return lines;
|
|
23220
|
+
}
|
|
23221
|
+
function formatWorktreeHealth(worktree) {
|
|
23222
|
+
if (worktree.status === "dirty") return "! dirty";
|
|
23223
|
+
if (worktree.status === "unknown") return "? health unknown";
|
|
23224
|
+
return null;
|
|
23225
|
+
}
|
|
23226
|
+
function formatWorktreeUpstream(worktree) {
|
|
23227
|
+
if (worktree.upstream.kind === "stale") return "! stale upstream";
|
|
23228
|
+
if (worktree.upstream.kind === "tracked" && (worktree.upstream.behind > 0 || worktree.upstream.ahead > 0)) {
|
|
23229
|
+
return formatUpstreamState(worktree.upstream);
|
|
23230
|
+
}
|
|
23231
|
+
return null;
|
|
23232
|
+
}
|
|
23233
|
+
function formatWorktreeActivity(worktree, now) {
|
|
23234
|
+
if (worktree.isCurrent) return null;
|
|
23235
|
+
const timestamps = [
|
|
23236
|
+
worktree.lastUsedTimestamp ?? 0,
|
|
23237
|
+
(worktree.lastCommitTimestamp ?? 0) * 1e3
|
|
23238
|
+
].filter((timestamp) => timestamp > 0);
|
|
23239
|
+
if (timestamps.length === 0) return null;
|
|
23240
|
+
const latest = Math.max(...timestamps);
|
|
23241
|
+
const ageSeconds = Math.floor((now - latest) / 1e3);
|
|
23242
|
+
if (ageSeconds < 24 * 60 * 60) return null;
|
|
23243
|
+
return `inactive ${formatRelativeAge(
|
|
23244
|
+
Math.floor(latest / 1e3),
|
|
23245
|
+
Math.floor(now / 1e3)
|
|
23246
|
+
)}`;
|
|
23247
|
+
}
|
|
23248
|
+
function nextAction(worktree) {
|
|
23249
|
+
if (worktree.isCurrent && worktree.task !== null) {
|
|
23250
|
+
return "next: continue task";
|
|
23251
|
+
}
|
|
23252
|
+
if (worktree.status === "dirty") {
|
|
23253
|
+
return "next: switch \u2192 inspect changes";
|
|
23254
|
+
}
|
|
23255
|
+
if (worktree.upstream.kind === "stale" || worktree.upstream.kind === "tracked" && worktree.upstream.behind > 0) {
|
|
23256
|
+
return "next: switch \u2192 sync";
|
|
23257
|
+
}
|
|
23258
|
+
const pullRequest = worktree.pullRequests[0];
|
|
23259
|
+
if (pullRequest !== void 0) {
|
|
23260
|
+
return `next: switch \u2192 open PR '#${pullRequest.number}'`;
|
|
23261
|
+
}
|
|
23262
|
+
return "next: switch worktree";
|
|
23263
|
+
}
|
|
23264
|
+
function formatHumanText(value, maxLength) {
|
|
23265
|
+
const sanitized = sanitizeTerminalText(value);
|
|
23266
|
+
return sanitized.length <= maxLength ? sanitized : `${sanitized.slice(0, Math.max(0, maxLength - 1))}\u2026`;
|
|
23267
|
+
}
|
|
23268
|
+
async function mapWithConcurrency4(items, limit, mapper) {
|
|
23269
|
+
const results = new Array(items.length);
|
|
23270
|
+
let nextIndex = 0;
|
|
23271
|
+
async function readNext() {
|
|
23272
|
+
for (; ; ) {
|
|
23273
|
+
const index = nextIndex++;
|
|
23274
|
+
if (index >= items.length) return;
|
|
23275
|
+
results[index] = await mapper(items[index]);
|
|
23276
|
+
}
|
|
23277
|
+
}
|
|
23278
|
+
await Promise.all(
|
|
23279
|
+
Array.from({ length: Math.min(limit, items.length) }, () => readNext())
|
|
23280
|
+
);
|
|
23281
|
+
return results;
|
|
23282
|
+
}
|
|
23283
|
+
|
|
22713
23284
|
// src/init.ts
|
|
22714
23285
|
import { constants as constants8 } from "node:fs";
|
|
22715
|
-
import { access as access10, mkdir as
|
|
22716
|
-
import { homedir as
|
|
22717
|
-
import { dirname as
|
|
23286
|
+
import { access as access10, mkdir as mkdir11, readFile as readFile11, writeFile as writeFile10 } from "node:fs/promises";
|
|
23287
|
+
import { homedir as homedir9 } from "node:os";
|
|
23288
|
+
import { dirname as dirname13 } from "node:path";
|
|
22718
23289
|
var ZSH_COMPLETION_PATH_LINE = "fpath=(~/.zsh/completions $fpath)";
|
|
22719
23290
|
var SHELL_WRAPPED_COMMANDS = [
|
|
22720
23291
|
{
|
|
@@ -22784,7 +23355,7 @@ async function runOnboardingInitCommand(options) {
|
|
|
22784
23355
|
if (options.json || isHeadless() || !canRunOnboarding(options)) {
|
|
22785
23356
|
return writeNonInteractiveInitError(options);
|
|
22786
23357
|
}
|
|
22787
|
-
const home = options.home ??
|
|
23358
|
+
const home = options.home ?? homedir9();
|
|
22788
23359
|
const context = {
|
|
22789
23360
|
detectedShell: resolveSupportedShell(void 0, process.env.SHELL),
|
|
22790
23361
|
home
|
|
@@ -22838,8 +23409,8 @@ async function runDefaultOnboarding(context) {
|
|
|
22838
23409
|
const installCompletion = await promptForCompletion(shell, completionPath);
|
|
22839
23410
|
if (installCompletion === null) return abortDefaultOnboarding();
|
|
22840
23411
|
if (installCompletion) {
|
|
22841
|
-
await
|
|
22842
|
-
await
|
|
23412
|
+
await mkdir11(dirname13(completionPath), { recursive: true });
|
|
23413
|
+
await writeFile10(completionPath, renderShellCompletion(shell), "utf8");
|
|
22843
23414
|
}
|
|
22844
23415
|
if (shell === "zsh" && (completionAlreadyInstalled || installCompletion)) {
|
|
22845
23416
|
await ensureZshCompletionPath(rcPath);
|
|
@@ -22931,8 +23502,8 @@ async function applyOnboardingResult(result, home) {
|
|
|
22931
23502
|
const completionAlreadyInstalled = await fileExists(completionPath);
|
|
22932
23503
|
await applyShellIntegration(result.shell, result.shellIntegration, home);
|
|
22933
23504
|
if (result.installCompletion) {
|
|
22934
|
-
await
|
|
22935
|
-
await
|
|
23505
|
+
await mkdir11(dirname13(completionPath), { recursive: true });
|
|
23506
|
+
await writeFile10(
|
|
22936
23507
|
completionPath,
|
|
22937
23508
|
renderShellCompletion(result.shell),
|
|
22938
23509
|
"utf8"
|
|
@@ -22947,17 +23518,17 @@ async function applyOnboardingResult(result, home) {
|
|
|
22947
23518
|
}
|
|
22948
23519
|
async function installOnboardingShellIntegration(shell, home) {
|
|
22949
23520
|
const rcPath = resolveShellConfigPath(shell, home);
|
|
22950
|
-
await
|
|
23521
|
+
await mkdir11(dirname13(rcPath), { recursive: true });
|
|
22951
23522
|
const current = await readExistingConfig(rcPath);
|
|
22952
23523
|
if (shell === "fish") {
|
|
22953
|
-
await
|
|
23524
|
+
await writeFile10(
|
|
22954
23525
|
rcPath,
|
|
22955
23526
|
upsertShellIntegration(current, renderShellIntegration(shell)),
|
|
22956
23527
|
"utf8"
|
|
22957
23528
|
);
|
|
22958
23529
|
return;
|
|
22959
23530
|
}
|
|
22960
|
-
await
|
|
23531
|
+
await writeFile10(
|
|
22961
23532
|
rcPath,
|
|
22962
23533
|
upsertOnboardingShellIntegration(current, shell),
|
|
22963
23534
|
"utf8"
|
|
@@ -22991,7 +23562,7 @@ async function ensureZshCompletionPath(rcPath) {
|
|
|
22991
23562
|
const current = await readExistingConfig(rcPath);
|
|
22992
23563
|
const next = upsertZshCompletionPath(current);
|
|
22993
23564
|
if (next !== current) {
|
|
22994
|
-
await
|
|
23565
|
+
await writeFile10(rcPath, next, "utf8");
|
|
22995
23566
|
}
|
|
22996
23567
|
}
|
|
22997
23568
|
function upsertZshCompletionPath(existingConfig) {
|
|
@@ -23022,7 +23593,7 @@ async function fileExists(path9) {
|
|
|
23022
23593
|
}
|
|
23023
23594
|
async function runLegacyInitCommand(options) {
|
|
23024
23595
|
const shell = resolveSupportedShell(options.shell, process.env.SHELL);
|
|
23025
|
-
const home = options.home ??
|
|
23596
|
+
const home = options.home ?? homedir9();
|
|
23026
23597
|
if (!shell) {
|
|
23027
23598
|
options.stderr?.(
|
|
23028
23599
|
"Unable to detect a supported shell. Specify one explicitly: bash, fish, or zsh.\n"
|
|
@@ -23035,10 +23606,10 @@ async function runLegacyInitCommand(options) {
|
|
|
23035
23606
|
return 0;
|
|
23036
23607
|
}
|
|
23037
23608
|
const rcPath = resolveShellConfigPath(shell, home);
|
|
23038
|
-
await
|
|
23609
|
+
await mkdir11(dirname13(rcPath), { recursive: true });
|
|
23039
23610
|
const current = await readExistingConfig(rcPath);
|
|
23040
23611
|
const next = upsertShellIntegration(current, script);
|
|
23041
|
-
await
|
|
23612
|
+
await writeFile10(rcPath, next, "utf8");
|
|
23042
23613
|
options.stdout(`${rcPath}
|
|
23043
23614
|
`);
|
|
23044
23615
|
const { config: globalConfig } = await loadGlobalConfig(home);
|
|
@@ -23129,7 +23700,7 @@ async function saveWizardConfig(result, cwd, home) {
|
|
|
23129
23700
|
}
|
|
23130
23701
|
async function readExistingConfig(path9) {
|
|
23131
23702
|
try {
|
|
23132
|
-
return await
|
|
23703
|
+
return await readFile11(path9, "utf8");
|
|
23133
23704
|
} catch (error) {
|
|
23134
23705
|
if (isMissingFileError3(error)) {
|
|
23135
23706
|
return "";
|
|
@@ -23317,6 +23888,7 @@ function formatDetailedWorktreeTable(worktrees) {
|
|
|
23317
23888
|
lastCommit: formatLastCommit(worktree.lastCommitTimestamp),
|
|
23318
23889
|
path: worktree.path,
|
|
23319
23890
|
status: worktree.status,
|
|
23891
|
+
task: worktree.task,
|
|
23320
23892
|
upstream: formatUpstreamState(worktree.upstream)
|
|
23321
23893
|
}));
|
|
23322
23894
|
const branchWidth = Math.max(
|
|
@@ -23335,12 +23907,17 @@ function formatDetailedWorktreeTable(worktrees) {
|
|
|
23335
23907
|
"LAST".length,
|
|
23336
23908
|
...rows.map((row) => row.lastCommit.length)
|
|
23337
23909
|
);
|
|
23910
|
+
const hasTasks = rows.some((row) => row.task !== null);
|
|
23911
|
+
const taskWidth = Math.max(
|
|
23912
|
+
"TASK".length,
|
|
23913
|
+
...rows.map((row) => row.task?.slice(0, 40).length ?? 0)
|
|
23914
|
+
);
|
|
23338
23915
|
const lines = [
|
|
23339
|
-
" " + "BRANCH".padEnd(branchWidth, " ") + " " + "STATUS".padEnd(statusWidth, " ") + " " + "UPSTREAM".padEnd(upstreamWidth, " ") + " " + "LAST".padEnd(lastCommitWidth, " ") + " PATH"
|
|
23916
|
+
" " + "BRANCH".padEnd(branchWidth, " ") + " " + "STATUS".padEnd(statusWidth, " ") + " " + "UPSTREAM".padEnd(upstreamWidth, " ") + " " + "LAST".padEnd(lastCommitWidth, " ") + (hasTasks ? " " + "TASK".padEnd(taskWidth, " ") : "") + " PATH"
|
|
23340
23917
|
];
|
|
23341
23918
|
for (const row of rows) {
|
|
23342
23919
|
lines.push(
|
|
23343
|
-
`${row.isCurrent ? "*" : " "} ${row.branch.padEnd(branchWidth, " ")} ${row.status.padEnd(statusWidth, " ")} ${row.upstream.padEnd(upstreamWidth, " ")} ${row.lastCommit.padEnd(lastCommitWidth, " ")} ` + row.path
|
|
23920
|
+
`${row.isCurrent ? "*" : " "} ${row.branch.padEnd(branchWidth, " ")} ${row.status.padEnd(statusWidth, " ")} ${row.upstream.padEnd(upstreamWidth, " ")} ${row.lastCommit.padEnd(lastCommitWidth, " ")}` + (hasTasks ? ` ${(row.task?.slice(0, 40) ?? "").padEnd(taskWidth, " ")}` : "") + " " + row.path
|
|
23344
23921
|
);
|
|
23345
23922
|
}
|
|
23346
23923
|
return lines.join("\n");
|
|
@@ -23373,8 +23950,8 @@ function sortWorktrees(worktrees) {
|
|
|
23373
23950
|
|
|
23374
23951
|
// src/open.ts
|
|
23375
23952
|
import { execFile as execFile9 } from "node:child_process";
|
|
23376
|
-
import { access as access11, writeFile as
|
|
23377
|
-
import { join as
|
|
23953
|
+
import { access as access11, writeFile as writeFile11 } from "node:fs/promises";
|
|
23954
|
+
import { join as join17 } from "node:path";
|
|
23378
23955
|
import { promisify as promisify10 } from "node:util";
|
|
23379
23956
|
var execFileAsync9 = promisify10(execFile9);
|
|
23380
23957
|
function createOpenCommand(dependencies = {}) {
|
|
@@ -23541,14 +24118,14 @@ async function defaultPromptForEditor(editors) {
|
|
|
23541
24118
|
return choice;
|
|
23542
24119
|
}
|
|
23543
24120
|
async function ensureWorkspaceFile(worktreePath, repoName) {
|
|
23544
|
-
const workspacePath =
|
|
24121
|
+
const workspacePath = join17(worktreePath, `${repoName}.code-workspace`);
|
|
23545
24122
|
try {
|
|
23546
24123
|
await access11(workspacePath);
|
|
23547
24124
|
return workspacePath;
|
|
23548
24125
|
} catch {
|
|
23549
24126
|
}
|
|
23550
24127
|
const workspace = { folders: [{ path: "." }], settings: {} };
|
|
23551
|
-
await
|
|
24128
|
+
await writeFile11(
|
|
23552
24129
|
workspacePath,
|
|
23553
24130
|
`${JSON.stringify(workspace, null, 2)}
|
|
23554
24131
|
`,
|
|
@@ -23773,7 +24350,7 @@ async function openFromWorktreeSelector(repoRoot, repoName, options, promptForWo
|
|
|
23773
24350
|
queryPullRequests: async (_root, branch) => pullRequestsByBranch.get(branch) ?? []
|
|
23774
24351
|
});
|
|
23775
24352
|
const connectedEntries = entries.filter(
|
|
23776
|
-
(entry) => (entry.
|
|
24353
|
+
(entry) => (entry.pullRequests?.length ?? 0) > 0
|
|
23777
24354
|
);
|
|
23778
24355
|
if (connectedEntries.length === 0) {
|
|
23779
24356
|
if (lookupError !== null) {
|
|
@@ -23889,13 +24466,7 @@ async function openForBranch(repoRoot, branch, options, queryPullRequests, promp
|
|
|
23889
24466
|
);
|
|
23890
24467
|
}
|
|
23891
24468
|
async function openSelectedEntry(entry, options, promptForPullRequest, openInBrowser) {
|
|
23892
|
-
const
|
|
23893
|
-
const urls = entry.pullRequestUrls ?? [];
|
|
23894
|
-
const pullRequests = numbers.map((number, index) => ({
|
|
23895
|
-
number,
|
|
23896
|
-
sourceBranch: entry.branch ?? "",
|
|
23897
|
-
url: urls[index] ?? ""
|
|
23898
|
-
})).filter((pullRequest) => pullRequest.url.length > 0);
|
|
24469
|
+
const pullRequests = entry.pullRequests ?? [];
|
|
23899
24470
|
return openPullRequests(
|
|
23900
24471
|
pullRequests,
|
|
23901
24472
|
options,
|
|
@@ -23962,7 +24533,7 @@ function formatError(error) {
|
|
|
23962
24533
|
}
|
|
23963
24534
|
|
|
23964
24535
|
// src/remove.ts
|
|
23965
|
-
import { basename as
|
|
24536
|
+
import { basename as basename11 } from "node:path";
|
|
23966
24537
|
var REMOVE_OUTPUT_FILE_ENV = "GJI_REMOVE_OUTPUT_FILE";
|
|
23967
24538
|
function createRemoveCommand(dependencies = {}) {
|
|
23968
24539
|
const promptForWorktree2 = dependencies.promptForWorktree ?? defaultPromptForWorktree3;
|
|
@@ -24068,7 +24639,7 @@ function createRemoveCommand(dependencies = {}) {
|
|
|
24068
24639
|
{
|
|
24069
24640
|
branch: worktree.branch ?? void 0,
|
|
24070
24641
|
path: worktree.path,
|
|
24071
|
-
repo:
|
|
24642
|
+
repo: basename11(repository.repoRoot)
|
|
24072
24643
|
},
|
|
24073
24644
|
options.stderr
|
|
24074
24645
|
);
|
|
@@ -24096,6 +24667,7 @@ function createRemoveCommand(dependencies = {}) {
|
|
|
24096
24667
|
}
|
|
24097
24668
|
}
|
|
24098
24669
|
await finalizeUndoOperation(journal, [worktree]);
|
|
24670
|
+
await releaseWorktreeSlot(worktree.path);
|
|
24099
24671
|
if (worktree.branch) {
|
|
24100
24672
|
try {
|
|
24101
24673
|
await deleteBranch(repository.repoRoot, worktree.branch);
|
|
@@ -24219,7 +24791,8 @@ async function runHookCommand(options) {
|
|
|
24219
24791
|
{
|
|
24220
24792
|
branch: currentWorktree?.branch ?? void 0,
|
|
24221
24793
|
path: repository.currentRoot,
|
|
24222
|
-
repo: repository.repoName
|
|
24794
|
+
repo: repository.repoName,
|
|
24795
|
+
slot: await getWorktreeSlot(repository.currentRoot)
|
|
24223
24796
|
},
|
|
24224
24797
|
options.stderr
|
|
24225
24798
|
);
|
|
@@ -24284,12 +24857,18 @@ function formatStatusJson(repoRoot, currentRoot, rows) {
|
|
|
24284
24857
|
};
|
|
24285
24858
|
}
|
|
24286
24859
|
async function buildStatusRow(worktree) {
|
|
24287
|
-
const health = await
|
|
24860
|
+
const [health, slot, task] = await Promise.all([
|
|
24861
|
+
readWorktreeHealth(worktree.path),
|
|
24862
|
+
getWorktreeSlot(worktree.path),
|
|
24863
|
+
readTask(worktree.path)
|
|
24864
|
+
]);
|
|
24288
24865
|
return {
|
|
24289
24866
|
branch: worktree.branch,
|
|
24290
24867
|
current: worktree.isCurrent,
|
|
24291
24868
|
path: worktree.path,
|
|
24869
|
+
slot,
|
|
24292
24870
|
status: health.status,
|
|
24871
|
+
task: task?.task ?? null,
|
|
24293
24872
|
upstream: buildUpstreamState2(worktree.branch, health)
|
|
24294
24873
|
};
|
|
24295
24874
|
}
|
|
@@ -24458,11 +25037,11 @@ function resolveConfiguredString2(value) {
|
|
|
24458
25037
|
}
|
|
24459
25038
|
|
|
24460
25039
|
// src/sync-files-command.ts
|
|
24461
|
-
import { homedir as
|
|
24462
|
-
import { join as
|
|
25040
|
+
import { homedir as homedir10 } from "node:os";
|
|
25041
|
+
import { join as join18 } from "node:path";
|
|
24463
25042
|
async function runSyncFilesCommand(options) {
|
|
24464
25043
|
const repository = await detectRepository(options.cwd);
|
|
24465
|
-
const home = options.home ??
|
|
25044
|
+
const home = options.home ?? homedir10();
|
|
24466
25045
|
const loaded = await loadGlobalConfig(home);
|
|
24467
25046
|
const repoEntry = findRepoConfigEntry(
|
|
24468
25047
|
loaded.config,
|
|
@@ -24594,10 +25173,40 @@ function writeError(options, message) {
|
|
|
24594
25173
|
}
|
|
24595
25174
|
function expandTilde3(value, home) {
|
|
24596
25175
|
if (value === "~") return home;
|
|
24597
|
-
if (value.startsWith("~/")) return
|
|
25176
|
+
if (value.startsWith("~/")) return join18(home, value.slice(2));
|
|
24598
25177
|
return value;
|
|
24599
25178
|
}
|
|
24600
25179
|
|
|
25180
|
+
// src/task-command.ts
|
|
25181
|
+
async function runTaskCommand(options) {
|
|
25182
|
+
const repository = await detectRepository(options.cwd);
|
|
25183
|
+
if (options.clear && options.task !== void 0)
|
|
25184
|
+
return emitTaskError(options, "--clear cannot be combined with a task");
|
|
25185
|
+
if (options.clear) await clearTask(repository.currentRoot);
|
|
25186
|
+
else if (options.task !== void 0)
|
|
25187
|
+
await writeTask(repository.currentRoot, options.task);
|
|
25188
|
+
const task = await readTask(repository.currentRoot);
|
|
25189
|
+
if (options.json) {
|
|
25190
|
+
options.stdout(
|
|
25191
|
+
`${JSON.stringify({ task: task?.task ?? null }, null, 2)}
|
|
25192
|
+
`
|
|
25193
|
+
);
|
|
25194
|
+
} else if (task) {
|
|
25195
|
+
options.stdout(`${task.task}
|
|
25196
|
+
`);
|
|
25197
|
+
} else {
|
|
25198
|
+
options.stdout('no task set \u2014 set one: gji task "..."\n');
|
|
25199
|
+
}
|
|
25200
|
+
return 0;
|
|
25201
|
+
}
|
|
25202
|
+
function emitTaskError(options, message) {
|
|
25203
|
+
if (options.json) options.stderr(`${JSON.stringify({ error: message })}
|
|
25204
|
+
`);
|
|
25205
|
+
else options.stderr(`gji task: ${message}
|
|
25206
|
+
`);
|
|
25207
|
+
return 1;
|
|
25208
|
+
}
|
|
25209
|
+
|
|
24601
25210
|
// src/warp.ts
|
|
24602
25211
|
var WARP_OUTPUT_FILE_ENV = "GJI_WARP_OUTPUT_FILE";
|
|
24603
25212
|
async function runWarpCommand(options) {
|
|
@@ -24746,7 +25355,7 @@ async function runCli(argv, options = {}) {
|
|
|
24746
25355
|
const cwd = options.cwd ?? process.cwd();
|
|
24747
25356
|
const stdout2 = options.stdout ?? (() => void 0);
|
|
24748
25357
|
const stderr = options.stderr ?? (() => void 0);
|
|
24749
|
-
if (shouldRegisterCurrentRepo(argv)) {
|
|
25358
|
+
if (shouldRegisterCurrentRepo(argv) || isHubInvocation(argv)) {
|
|
24750
25359
|
await maybeRegisterCurrentRepo(cwd);
|
|
24751
25360
|
}
|
|
24752
25361
|
const program2 = createProgram();
|
|
@@ -24755,9 +25364,19 @@ async function runCli(argv, options = {}) {
|
|
|
24755
25364
|
writeOut: stdout2
|
|
24756
25365
|
});
|
|
24757
25366
|
program2.exitOverride();
|
|
24758
|
-
if (argv
|
|
24759
|
-
|
|
24760
|
-
|
|
25367
|
+
if (isHubInvocation(argv)) {
|
|
25368
|
+
const exitCode = await runHubCommand(
|
|
25369
|
+
{
|
|
25370
|
+
columns: options.columns,
|
|
25371
|
+
cwd,
|
|
25372
|
+
json: argv.includes("--json"),
|
|
25373
|
+
now: options.now,
|
|
25374
|
+
stderr,
|
|
25375
|
+
stdout: stdout2
|
|
25376
|
+
},
|
|
25377
|
+
options.hubDependencies
|
|
25378
|
+
);
|
|
25379
|
+
return { exitCode };
|
|
24761
25380
|
}
|
|
24762
25381
|
try {
|
|
24763
25382
|
attachCommandActions(program2, {
|
|
@@ -24808,6 +25427,9 @@ function shouldRegisterCurrentRepo(argv) {
|
|
|
24808
25427
|
"init"
|
|
24809
25428
|
].includes(command ?? "");
|
|
24810
25429
|
}
|
|
25430
|
+
function isHubInvocation(argv) {
|
|
25431
|
+
return argv.length === 0 || argv.length === 1 && argv[0] === "--json";
|
|
25432
|
+
}
|
|
24811
25433
|
async function maybeRegisterCurrentRepo(cwd) {
|
|
24812
25434
|
try {
|
|
24813
25435
|
const { repoRoot } = await detectRepository(cwd);
|
|
@@ -24827,7 +25449,7 @@ function registerCommands(program2) {
|
|
|
24827
25449
|
).option(
|
|
24828
25450
|
"--no-fetch",
|
|
24829
25451
|
"skip refreshing the remote default branch before creating the new branch"
|
|
24830
|
-
).option("--take", "move current uncommitted changes into the new worktree").option(
|
|
25452
|
+
).option("--take", "move current uncommitted changes into the new worktree").option("--task <description>", "record the purpose of the new worktree").option(
|
|
24831
25453
|
"--copy",
|
|
24832
25454
|
"copy current uncommitted changes instead of moving them (requires --take)"
|
|
24833
25455
|
).option("--open", "open the new worktree in an editor after creation").option(
|
|
@@ -24874,9 +25496,10 @@ function registerCommands(program2) {
|
|
|
24874
25496
|
"--workspace",
|
|
24875
25497
|
"generate a .code-workspace file before opening (VS Code / Cursor / Windsurf)"
|
|
24876
25498
|
).action(notImplemented("open"));
|
|
24877
|
-
program2.command("go [branch]").alias("jump").description("resolve and jump to a worktree path").option("--root", "navigate to the main repository root").option("--print", "print the resolved worktree path explicitly").option("--json", "emit JSON for an existing worktree destination").action(notImplemented("go"));
|
|
25499
|
+
program2.command("go [branch]").alias("jump").description("resolve and jump to a worktree path").option("--root", "navigate to the main repository root").option("--print", "print the resolved worktree path explicitly").option("--quiet", "suppress the context card").option("--json", "emit JSON for an existing worktree destination").action(notImplemented("go"));
|
|
24878
25500
|
program2.command("root").description("print the main repository root path").option("--print", "print the resolved repository root path explicitly").action(notImplemented("root"));
|
|
24879
25501
|
program2.command("status").description("summarize repository and worktree health").option("--json", "print repository and worktree health as JSON").action(notImplemented("status"));
|
|
25502
|
+
program2.command("task [task]").description("show or update the current worktree task").option("--clear", "clear the current worktree task").option("--json", "emit task metadata as JSON").action(notImplemented("task"));
|
|
24880
25503
|
program2.command("sync").description("fetch and update one or all worktrees").option("--all", "sync every worktree in the repository").option(
|
|
24881
25504
|
"--json",
|
|
24882
25505
|
"emit JSON on success or error instead of human-readable output"
|
|
@@ -24935,7 +25558,8 @@ function attachCommandActions(program2, options) {
|
|
|
24935
25558
|
noFetch: commandOptions2.fetch === false,
|
|
24936
25559
|
json: commandOptions2.json,
|
|
24937
25560
|
open: commandOptions2.open,
|
|
24938
|
-
take: commandOptions2.take
|
|
25561
|
+
take: commandOptions2.take,
|
|
25562
|
+
task: commandOptions2.task
|
|
24939
25563
|
});
|
|
24940
25564
|
if (exitCode !== 0) {
|
|
24941
25565
|
throw commanderExit(exitCode);
|
|
@@ -25094,6 +25718,7 @@ function attachCommandActions(program2, options) {
|
|
|
25094
25718
|
cwd: options.cwd,
|
|
25095
25719
|
json: commandOptions2.json,
|
|
25096
25720
|
print: commandOptions2.print,
|
|
25721
|
+
quiet: commandOptions2.quiet,
|
|
25097
25722
|
root: commandOptions2.root,
|
|
25098
25723
|
stderr: options.stderr,
|
|
25099
25724
|
stdout: options.stdout
|
|
@@ -25123,6 +25748,19 @@ function attachCommandActions(program2, options) {
|
|
|
25123
25748
|
throw commanderExit(exitCode);
|
|
25124
25749
|
}
|
|
25125
25750
|
});
|
|
25751
|
+
program2.commands.find((command) => command.name() === "task")?.action(
|
|
25752
|
+
async (task, commandOptions2) => {
|
|
25753
|
+
const exitCode = await runTaskCommand({
|
|
25754
|
+
clear: commandOptions2.clear,
|
|
25755
|
+
cwd: options.cwd,
|
|
25756
|
+
json: commandOptions2.json,
|
|
25757
|
+
stderr: options.stderr,
|
|
25758
|
+
task,
|
|
25759
|
+
stdout: options.stdout
|
|
25760
|
+
});
|
|
25761
|
+
if (exitCode !== 0) throw commanderExit(exitCode);
|
|
25762
|
+
}
|
|
25763
|
+
);
|
|
25126
25764
|
program2.commands.find((command) => command.name() === "sync")?.action(async (commandOptions2) => {
|
|
25127
25765
|
const exitCode = await runSyncCommand({
|
|
25128
25766
|
all: commandOptions2.all,
|
|
@@ -25354,7 +25992,7 @@ async function main() {
|
|
|
25354
25992
|
}
|
|
25355
25993
|
async function warnIfMissingShellIntegration() {
|
|
25356
25994
|
try {
|
|
25357
|
-
const { config } = await loadGlobalConfig(
|
|
25995
|
+
const { config } = await loadGlobalConfig(homedir11());
|
|
25358
25996
|
if (!config.shellIntegration) {
|
|
25359
25997
|
const setupCommand = isHeadless() ? legacyShellSetupCommand() : "gji init";
|
|
25360
25998
|
process.stderr.write(
|