@solaqua/gji 0.7.0 → 0.7.2
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/dist/back.js +1 -1
- package/dist/clean.d.ts +2 -1
- package/dist/clean.js +8 -16
- package/dist/cli.js +7 -6
- package/dist/gji-bundle.mjs +568 -273
- package/dist/go.d.ts +2 -4
- package/dist/go.js +19 -48
- package/dist/history.d.ts +1 -0
- package/dist/history.js +12 -5
- package/dist/hooks.d.ts +3 -3
- package/dist/hooks.js +3 -3
- package/dist/init.d.ts +3 -3
- package/dist/init.js +12 -12
- package/dist/install-prompt.js +22 -21
- package/dist/new.js +72 -10
- package/dist/open.d.ts +2 -2
- package/dist/open.js +22 -18
- package/dist/pr.js +4 -4
- package/dist/remove.d.ts +3 -2
- package/dist/remove.js +8 -13
- package/dist/repo.d.ts +0 -1
- package/dist/repo.js +0 -9
- package/dist/run-hook.d.ts +6 -0
- package/dist/{trigger-hook.js → run-hook.js} +13 -7
- package/dist/shell-completion.js +5 -5
- package/dist/warp.js +24 -53
- package/dist/worktree-info.d.ts +0 -1
- package/dist/worktree-info.js +17 -11
- package/dist/worktree-picker.d.ts +14 -0
- package/dist/worktree-picker.js +228 -0
- package/man/man1/gji-back.1 +1 -1
- package/man/man1/gji-clean.1 +1 -1
- package/man/man1/gji-completion.1 +1 -1
- package/man/man1/gji-config.1 +1 -1
- package/man/man1/gji-go.1 +1 -1
- package/man/man1/gji-history.1 +1 -1
- package/man/man1/gji-init.1 +1 -1
- package/man/man1/gji-ls.1 +1 -1
- package/man/man1/gji-new.1 +1 -1
- package/man/man1/gji-open.1 +1 -1
- package/man/man1/gji-pr.1 +1 -1
- package/man/man1/gji-remove.1 +1 -1
- package/man/man1/gji-root.1 +1 -1
- package/man/man1/gji-run-hook.1 +9 -0
- 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-warp.1 +1 -1
- package/man/man1/gji.1 +6 -4
- package/package.json +3 -7
- package/dist/trigger-hook.d.ts +0 -6
- package/man/man1/gji-trigger-hook.1 +0 -9
package/dist/gji-bundle.mjs
CHANGED
|
@@ -13074,15 +13074,21 @@ async function loadHistory(home = homedir2()) {
|
|
|
13074
13074
|
async function appendHistory(path9, branch, home = homedir2()) {
|
|
13075
13075
|
const historyPath = HISTORY_FILE_PATH(home);
|
|
13076
13076
|
const existing = await loadHistory(home);
|
|
13077
|
-
if (existing.length > 0 && existing[0].path === path9) {
|
|
13078
|
-
return;
|
|
13079
|
-
}
|
|
13080
13077
|
const entry = { branch, path: path9, timestamp: Date.now() };
|
|
13081
|
-
const next = [
|
|
13078
|
+
const next = [
|
|
13079
|
+
entry,
|
|
13080
|
+
...existing.filter((existingEntry) => existingEntry.path !== path9)
|
|
13081
|
+
].slice(0, MAX_HISTORY_ENTRIES);
|
|
13082
13082
|
await mkdir2(dirname2(historyPath), { recursive: true });
|
|
13083
13083
|
await writeFile2(historyPath, `${JSON.stringify(next, null, 2)}
|
|
13084
13084
|
`, "utf8");
|
|
13085
13085
|
}
|
|
13086
|
+
async function recordWorktreeUsage(path9, branch, home = homedir2()) {
|
|
13087
|
+
try {
|
|
13088
|
+
await appendHistory(path9, branch, home);
|
|
13089
|
+
} catch {
|
|
13090
|
+
}
|
|
13091
|
+
}
|
|
13086
13092
|
function isHistoryEntry(value) {
|
|
13087
13093
|
return typeof value === "object" && value !== null && "path" in value && typeof value.path === "string" && "timestamp" in value && typeof value.timestamp === "number";
|
|
13088
13094
|
}
|
|
@@ -13176,9 +13182,13 @@ function extractHooks(config) {
|
|
|
13176
13182
|
}
|
|
13177
13183
|
const hooks = raw;
|
|
13178
13184
|
return {
|
|
13179
|
-
|
|
13180
|
-
|
|
13181
|
-
|
|
13185
|
+
"after-create": parseHookCommand(
|
|
13186
|
+
hooks["after-create"] ?? hooks.afterCreate
|
|
13187
|
+
),
|
|
13188
|
+
"after-enter": parseHookCommand(hooks["after-enter"] ?? hooks.afterEnter),
|
|
13189
|
+
"before-remove": parseHookCommand(
|
|
13190
|
+
hooks["before-remove"] ?? hooks.beforeRemove
|
|
13191
|
+
)
|
|
13182
13192
|
};
|
|
13183
13193
|
}
|
|
13184
13194
|
function parseHookCommand(value) {
|
|
@@ -13377,13 +13387,6 @@ async function listWorktrees(cwd) {
|
|
|
13377
13387
|
};
|
|
13378
13388
|
});
|
|
13379
13389
|
}
|
|
13380
|
-
function sortByCurrentFirst(worktrees) {
|
|
13381
|
-
return [...worktrees].sort((a, b3) => {
|
|
13382
|
-
if (a.isCurrent && !b3.isCurrent) return -1;
|
|
13383
|
-
if (!a.isCurrent && b3.isCurrent) return 1;
|
|
13384
|
-
return 0;
|
|
13385
|
-
});
|
|
13386
|
-
}
|
|
13387
13390
|
function findPorcelainValue(block, key) {
|
|
13388
13391
|
const value = findOptionalPorcelainValue(block, key);
|
|
13389
13392
|
if (!value) {
|
|
@@ -13450,7 +13453,7 @@ async function runBackCommand(options) {
|
|
|
13450
13453
|
);
|
|
13451
13454
|
const hooks = extractHooks(config);
|
|
13452
13455
|
await runHook(
|
|
13453
|
-
hooks
|
|
13456
|
+
hooks["after-enter"],
|
|
13454
13457
|
target.path,
|
|
13455
13458
|
{
|
|
13456
13459
|
branch: target.branch ?? void 0,
|
|
@@ -13817,43 +13820,63 @@ var dD = class extends x {
|
|
|
13817
13820
|
});
|
|
13818
13821
|
}
|
|
13819
13822
|
};
|
|
13823
|
+
var mD = Object.defineProperty;
|
|
13824
|
+
var bD = (e2, u2, t) => u2 in e2 ? mD(e2, u2, { enumerable: true, configurable: true, writable: true, value: t }) : e2[u2] = t;
|
|
13825
|
+
var Z = (e2, u2, t) => (bD(e2, typeof u2 != "symbol" ? u2 + "" : u2, t), t);
|
|
13826
|
+
var q = (e2, u2, t) => {
|
|
13827
|
+
if (!u2.has(e2)) throw TypeError("Cannot " + t);
|
|
13828
|
+
};
|
|
13829
|
+
var T = (e2, u2, t) => (q(e2, u2, "read from private field"), t ? t.call(e2) : u2.get(e2));
|
|
13830
|
+
var wD = (e2, u2, t) => {
|
|
13831
|
+
if (u2.has(e2)) throw TypeError("Cannot add the same private member more than once");
|
|
13832
|
+
u2 instanceof WeakSet ? u2.add(e2) : u2.set(e2, t);
|
|
13833
|
+
};
|
|
13834
|
+
var yD = (e2, u2, t, F2) => (q(e2, u2, "write to private field"), F2 ? F2.call(e2, t) : u2.set(e2, t), t);
|
|
13820
13835
|
var A;
|
|
13821
|
-
|
|
13822
|
-
var kD = Object.defineProperty;
|
|
13823
|
-
var $D = (e2, u2, t) => u2 in e2 ? kD(e2, u2, { enumerable: true, configurable: true, writable: true, value: t }) : e2[u2] = t;
|
|
13824
|
-
var H = (e2, u2, t) => ($D(e2, typeof u2 != "symbol" ? u2 + "" : u2, t), t);
|
|
13825
|
-
var SD = class extends x {
|
|
13836
|
+
var _D = class extends x {
|
|
13826
13837
|
constructor(u2) {
|
|
13827
|
-
super(u2, false),
|
|
13828
|
-
|
|
13829
|
-
}), this.on("cursor", (
|
|
13830
|
-
switch (
|
|
13838
|
+
super(u2, false), Z(this, "options"), Z(this, "cursor", 0), wD(this, A, void 0);
|
|
13839
|
+
const { options: t } = u2;
|
|
13840
|
+
yD(this, A, u2.selectableGroups !== false), this.options = Object.entries(t).flatMap(([F2, s]) => [{ value: F2, group: true, label: F2 }, ...s.map((i) => ({ ...i, group: F2 }))]), this.value = [...u2.initialValues ?? []], this.cursor = Math.max(this.options.findIndex(({ value: F2 }) => F2 === u2.cursorAt), T(this, A) ? 0 : 1), this.on("cursor", (F2) => {
|
|
13841
|
+
switch (F2) {
|
|
13831
13842
|
case "left":
|
|
13832
|
-
case "up":
|
|
13843
|
+
case "up": {
|
|
13833
13844
|
this.cursor = this.cursor === 0 ? this.options.length - 1 : this.cursor - 1;
|
|
13845
|
+
const s = this.options[this.cursor]?.group === true;
|
|
13846
|
+
!T(this, A) && s && (this.cursor = this.cursor === 0 ? this.options.length - 1 : this.cursor - 1);
|
|
13834
13847
|
break;
|
|
13848
|
+
}
|
|
13835
13849
|
case "down":
|
|
13836
|
-
case "right":
|
|
13850
|
+
case "right": {
|
|
13837
13851
|
this.cursor = this.cursor === this.options.length - 1 ? 0 : this.cursor + 1;
|
|
13852
|
+
const s = this.options[this.cursor]?.group === true;
|
|
13853
|
+
!T(this, A) && s && (this.cursor = this.cursor === this.options.length - 1 ? 0 : this.cursor + 1);
|
|
13838
13854
|
break;
|
|
13855
|
+
}
|
|
13839
13856
|
case "space":
|
|
13840
13857
|
this.toggleValue();
|
|
13841
13858
|
break;
|
|
13842
13859
|
}
|
|
13843
13860
|
});
|
|
13844
13861
|
}
|
|
13845
|
-
|
|
13846
|
-
return this.options
|
|
13862
|
+
getGroupItems(u2) {
|
|
13863
|
+
return this.options.filter((t) => t.group === u2);
|
|
13847
13864
|
}
|
|
13848
|
-
|
|
13849
|
-
|
|
13850
|
-
this.value = u2 ? [] : this.options.map((t) => t.value);
|
|
13865
|
+
isGroupSelected(u2) {
|
|
13866
|
+
return this.getGroupItems(u2).every((t) => this.value.includes(t.value));
|
|
13851
13867
|
}
|
|
13852
13868
|
toggleValue() {
|
|
13853
|
-
const u2 = this.
|
|
13854
|
-
|
|
13869
|
+
const u2 = this.options[this.cursor];
|
|
13870
|
+
if (u2.group === true) {
|
|
13871
|
+
const t = u2.value, F2 = this.getGroupItems(t);
|
|
13872
|
+
this.isGroupSelected(t) ? this.value = this.value.filter((s) => F2.findIndex((i) => i.value === s) === -1) : this.value = [...this.value, ...F2.map((s) => s.value)], this.value = Array.from(new Set(this.value));
|
|
13873
|
+
} else {
|
|
13874
|
+
const t = this.value.includes(u2.value);
|
|
13875
|
+
this.value = t ? this.value.filter((F2) => F2 !== u2.value) : [...this.value, u2.value];
|
|
13876
|
+
}
|
|
13855
13877
|
}
|
|
13856
13878
|
};
|
|
13879
|
+
A = /* @__PURE__ */ new WeakMap();
|
|
13857
13880
|
var OD = Object.defineProperty;
|
|
13858
13881
|
var PD = (e2, u2, t) => u2 in e2 ? OD(e2, u2, { enumerable: true, configurable: true, writable: true, value: t }) : e2[u2] = t;
|
|
13859
13882
|
var J = (e2, u2, t) => (PD(e2, typeof u2 != "symbol" ? u2 + "" : u2, t), t);
|
|
@@ -13916,14 +13939,14 @@ var d2 = u("\u2514", "\u2014");
|
|
|
13916
13939
|
var k2 = u("\u25CF", ">");
|
|
13917
13940
|
var P2 = u("\u25CB", " ");
|
|
13918
13941
|
var A2 = u("\u25FB", "[\u2022]");
|
|
13919
|
-
var
|
|
13942
|
+
var T2 = u("\u25FC", "[+]");
|
|
13920
13943
|
var F = u("\u25FB", "[ ]");
|
|
13921
13944
|
var $e = u("\u25AA", "\u2022");
|
|
13922
13945
|
var _2 = u("\u2500", "-");
|
|
13923
13946
|
var me = u("\u256E", "+");
|
|
13924
13947
|
var de = u("\u251C", "+");
|
|
13925
13948
|
var pe = u("\u256F", "+");
|
|
13926
|
-
var
|
|
13949
|
+
var q2 = u("\u25CF", "\u2022");
|
|
13927
13950
|
var D = u("\u25C6", "*");
|
|
13928
13951
|
var U = u("\u25B2", "!");
|
|
13929
13952
|
var K2 = u("\u25A0", "x");
|
|
@@ -14022,40 +14045,54 @@ ${import_picocolors2.default.cyan(d2)}
|
|
|
14022
14045
|
}
|
|
14023
14046
|
} }).prompt();
|
|
14024
14047
|
};
|
|
14025
|
-
var
|
|
14026
|
-
const n = (
|
|
14027
|
-
const
|
|
14028
|
-
|
|
14048
|
+
var be = (t) => {
|
|
14049
|
+
const { selectableGroups: n = true } = t, r2 = (i, s, c = []) => {
|
|
14050
|
+
const a = i.label ?? String(i.value), l2 = typeof i.group == "string", $2 = l2 && (c[c.indexOf(i) + 1] ?? { group: true }), g = l2 && $2.group === true, p2 = l2 ? n ? `${g ? d2 : o} ` : " " : "";
|
|
14051
|
+
if (s === "active") return `${import_picocolors2.default.dim(p2)}${import_picocolors2.default.cyan(A2)} ${a} ${i.hint ? import_picocolors2.default.dim(`(${i.hint})`) : ""}`;
|
|
14052
|
+
if (s === "group-active") return `${p2}${import_picocolors2.default.cyan(A2)} ${import_picocolors2.default.dim(a)}`;
|
|
14053
|
+
if (s === "group-active-selected") return `${p2}${import_picocolors2.default.green(T2)} ${import_picocolors2.default.dim(a)}`;
|
|
14054
|
+
if (s === "selected") {
|
|
14055
|
+
const f = l2 || n ? import_picocolors2.default.green(T2) : "";
|
|
14056
|
+
return `${import_picocolors2.default.dim(p2)}${f} ${import_picocolors2.default.dim(a)} ${i.hint ? import_picocolors2.default.dim(`(${i.hint})`) : ""}`;
|
|
14057
|
+
}
|
|
14058
|
+
if (s === "cancelled") return `${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(a))}`;
|
|
14059
|
+
if (s === "active-selected") return `${import_picocolors2.default.dim(p2)}${import_picocolors2.default.green(T2)} ${a} ${i.hint ? import_picocolors2.default.dim(`(${i.hint})`) : ""}`;
|
|
14060
|
+
if (s === "submitted") return `${import_picocolors2.default.dim(a)}`;
|
|
14061
|
+
const v2 = l2 || n ? import_picocolors2.default.dim(F) : "";
|
|
14062
|
+
return `${import_picocolors2.default.dim(p2)}${v2} ${import_picocolors2.default.dim(a)}`;
|
|
14029
14063
|
};
|
|
14030
|
-
return new
|
|
14031
|
-
if (this.required &&
|
|
14064
|
+
return new _D({ options: t.options, initialValues: t.initialValues, required: t.required ?? true, cursorAt: t.cursorAt, selectableGroups: n, validate(i) {
|
|
14065
|
+
if (this.required && i.length === 0) return `Please select at least one option.
|
|
14032
14066
|
${import_picocolors2.default.reset(import_picocolors2.default.dim(`Press ${import_picocolors2.default.gray(import_picocolors2.default.bgWhite(import_picocolors2.default.inverse(" space ")))} to select, ${import_picocolors2.default.gray(import_picocolors2.default.bgWhite(import_picocolors2.default.inverse(" enter ")))} to submit`))}`;
|
|
14033
14067
|
}, render() {
|
|
14034
|
-
const
|
|
14068
|
+
const i = `${import_picocolors2.default.gray(o)}
|
|
14035
14069
|
${b2(this.state)} ${t.message}
|
|
14036
|
-
|
|
14037
|
-
const a = this.value.includes(s.value);
|
|
14038
|
-
return c && a ? n(s, "active-selected") : a ? n(s, "selected") : n(s, c ? "active" : "inactive");
|
|
14039
|
-
};
|
|
14070
|
+
`;
|
|
14040
14071
|
switch (this.state) {
|
|
14041
14072
|
case "submit":
|
|
14042
|
-
return `${
|
|
14073
|
+
return `${i}${import_picocolors2.default.gray(o)} ${this.options.filter(({ value: s }) => this.value.includes(s)).map((s) => r2(s, "submitted")).join(import_picocolors2.default.dim(", "))}`;
|
|
14043
14074
|
case "cancel": {
|
|
14044
|
-
const s = this.options.filter(({ value: c }) => this.value.includes(c)).map((c) =>
|
|
14045
|
-
return `${
|
|
14075
|
+
const s = this.options.filter(({ value: c }) => this.value.includes(c)).map((c) => r2(c, "cancelled")).join(import_picocolors2.default.dim(", "));
|
|
14076
|
+
return `${i}${import_picocolors2.default.gray(o)} ${s.trim() ? `${s}
|
|
14046
14077
|
${import_picocolors2.default.gray(o)}` : ""}`;
|
|
14047
14078
|
}
|
|
14048
14079
|
case "error": {
|
|
14049
14080
|
const s = this.error.split(`
|
|
14050
14081
|
`).map((c, a) => a === 0 ? `${import_picocolors2.default.yellow(d2)} ${import_picocolors2.default.yellow(c)}` : ` ${c}`).join(`
|
|
14051
14082
|
`);
|
|
14052
|
-
return `${
|
|
14083
|
+
return `${i}${import_picocolors2.default.yellow(o)} ${this.options.map((c, a, l2) => {
|
|
14084
|
+
const $2 = this.value.includes(c.value) || c.group === true && this.isGroupSelected(`${c.value}`), g = a === this.cursor;
|
|
14085
|
+
return !g && typeof c.group == "string" && this.options[this.cursor].value === c.group ? r2(c, $2 ? "group-active-selected" : "group-active", l2) : g && $2 ? r2(c, "active-selected", l2) : $2 ? r2(c, "selected", l2) : r2(c, g ? "active" : "inactive", l2);
|
|
14086
|
+
}).join(`
|
|
14053
14087
|
${import_picocolors2.default.yellow(o)} `)}
|
|
14054
14088
|
${s}
|
|
14055
14089
|
`;
|
|
14056
14090
|
}
|
|
14057
14091
|
default:
|
|
14058
|
-
return `${
|
|
14092
|
+
return `${i}${import_picocolors2.default.cyan(o)} ${this.options.map((s, c, a) => {
|
|
14093
|
+
const l2 = this.value.includes(s.value) || s.group === true && this.isGroupSelected(`${s.value}`), $2 = c === this.cursor;
|
|
14094
|
+
return !$2 && typeof s.group == "string" && this.options[this.cursor].value === s.group ? r2(s, l2 ? "group-active-selected" : "group-active", a) : $2 && l2 ? r2(s, "active-selected", a) : l2 ? r2(s, "selected", a) : r2(s, $2 ? "active" : "inactive", a);
|
|
14095
|
+
}).join(`
|
|
14059
14096
|
${import_picocolors2.default.cyan(o)} `)}
|
|
14060
14097
|
${import_picocolors2.default.cyan(d2)}
|
|
14061
14098
|
`;
|
|
@@ -14080,8 +14117,29 @@ function isHeadless() {
|
|
|
14080
14117
|
}
|
|
14081
14118
|
|
|
14082
14119
|
// src/worktree-info.ts
|
|
14120
|
+
var MAX_WORKTREE_INFO_READ_CONCURRENCY = 8;
|
|
14083
14121
|
async function readWorktreeInfos(worktrees) {
|
|
14084
|
-
return
|
|
14122
|
+
return mapWithConcurrency(
|
|
14123
|
+
worktrees,
|
|
14124
|
+
MAX_WORKTREE_INFO_READ_CONCURRENCY,
|
|
14125
|
+
readWorktreeInfo
|
|
14126
|
+
);
|
|
14127
|
+
}
|
|
14128
|
+
async function mapWithConcurrency(items, limit, mapper) {
|
|
14129
|
+
const results = new Array(items.length);
|
|
14130
|
+
let nextIndex = 0;
|
|
14131
|
+
async function readNext() {
|
|
14132
|
+
for (; ; ) {
|
|
14133
|
+
const index = nextIndex;
|
|
14134
|
+
nextIndex += 1;
|
|
14135
|
+
if (index >= items.length) return;
|
|
14136
|
+
results[index] = await mapper(items[index]);
|
|
14137
|
+
}
|
|
14138
|
+
}
|
|
14139
|
+
await Promise.all(
|
|
14140
|
+
Array.from({ length: Math.min(limit, items.length) }, () => readNext())
|
|
14141
|
+
);
|
|
14142
|
+
return results;
|
|
14085
14143
|
}
|
|
14086
14144
|
async function readWorktreeInfo(worktree) {
|
|
14087
14145
|
const [healthResult, lastCommitResult] = await Promise.allSettled([
|
|
@@ -14125,16 +14183,6 @@ function serializeWorktreeInfo(info) {
|
|
|
14125
14183
|
upstream: info.upstream
|
|
14126
14184
|
};
|
|
14127
14185
|
}
|
|
14128
|
-
function formatWorktreeHint(info) {
|
|
14129
|
-
const details = [
|
|
14130
|
-
`status: ${info.status}`,
|
|
14131
|
-
`upstream: ${formatUpstreamState(info.upstream)}`
|
|
14132
|
-
];
|
|
14133
|
-
if (info.lastCommitTimestamp !== null) {
|
|
14134
|
-
details.push(`last: ${formatRelativeAge(info.lastCommitTimestamp)}`);
|
|
14135
|
-
}
|
|
14136
|
-
return `${info.path} (${details.join(", ")})`;
|
|
14137
|
-
}
|
|
14138
14186
|
function formatUpstreamState(upstream) {
|
|
14139
14187
|
if (upstream.kind === "detached") {
|
|
14140
14188
|
return "n/a";
|
|
@@ -14235,6 +14283,225 @@ function hasStderr(error) {
|
|
|
14235
14283
|
return error instanceof Error && "stderr" in error && typeof error.stderr === "string";
|
|
14236
14284
|
}
|
|
14237
14285
|
|
|
14286
|
+
// src/worktree-picker.ts
|
|
14287
|
+
async function buildWorktreePromptEntries(sources) {
|
|
14288
|
+
const [history, infos] = await Promise.all([
|
|
14289
|
+
loadHistory(),
|
|
14290
|
+
readWorktreeInfos(sources.map((source) => source.worktree))
|
|
14291
|
+
]);
|
|
14292
|
+
const historyByPath = new Map(history.map((entry) => [entry.path, entry]));
|
|
14293
|
+
const entries = sources.map(
|
|
14294
|
+
(source, index) => buildWorktreePromptEntry(
|
|
14295
|
+
source,
|
|
14296
|
+
infos[index],
|
|
14297
|
+
historyByPath.get(source.worktree.path)?.timestamp ?? null,
|
|
14298
|
+
Date.now()
|
|
14299
|
+
)
|
|
14300
|
+
);
|
|
14301
|
+
return entries.sort(comparePromptEntries).map(
|
|
14302
|
+
({ lastActivityTimestamp: _lastActivityTimestamp, ...entry }) => entry
|
|
14303
|
+
);
|
|
14304
|
+
}
|
|
14305
|
+
function resolveWorktreeQuery(sources, query) {
|
|
14306
|
+
const normalizedQuery = normalizeQuery(query);
|
|
14307
|
+
if (normalizedQuery === null) return null;
|
|
14308
|
+
const matches = findWorktreePromptSourceMatches(sources, normalizedQuery);
|
|
14309
|
+
if (isAmbiguousRepoOnlyQuery(matches, normalizedQuery)) return null;
|
|
14310
|
+
return matches[0]?.source ?? null;
|
|
14311
|
+
}
|
|
14312
|
+
function findWorktreePromptSourceMatches(sources, normalizedQuery) {
|
|
14313
|
+
return sources.flatMap((source) => {
|
|
14314
|
+
const matchScore = scoreWorktreeMatch(
|
|
14315
|
+
{
|
|
14316
|
+
...source.worktree,
|
|
14317
|
+
repoName: source.repoName
|
|
14318
|
+
},
|
|
14319
|
+
normalizedQuery
|
|
14320
|
+
);
|
|
14321
|
+
return matchScore === null ? [] : [{ matchScore, source }];
|
|
14322
|
+
}).sort(compareQueryMatches);
|
|
14323
|
+
}
|
|
14324
|
+
function isAmbiguousRepoOnlyQuery(matches, query) {
|
|
14325
|
+
if (matches[0]?.matchScore === 1e3) return false;
|
|
14326
|
+
return matches.filter((match) => match.source.repoName.toLowerCase() === query).length > 1;
|
|
14327
|
+
}
|
|
14328
|
+
async function promptForSingleWorktree(message, worktrees) {
|
|
14329
|
+
const choice = await ve({
|
|
14330
|
+
message,
|
|
14331
|
+
options: worktrees.map((worktree) => ({
|
|
14332
|
+
label: worktree.label,
|
|
14333
|
+
value: worktree.path
|
|
14334
|
+
})),
|
|
14335
|
+
maxItems: 12
|
|
14336
|
+
});
|
|
14337
|
+
return pD(choice) ? null : choice;
|
|
14338
|
+
}
|
|
14339
|
+
async function promptForMultipleWorktrees(message, worktrees) {
|
|
14340
|
+
const choice = await be({
|
|
14341
|
+
message,
|
|
14342
|
+
options: groupPromptEntries(worktrees),
|
|
14343
|
+
required: true,
|
|
14344
|
+
selectableGroups: false
|
|
14345
|
+
});
|
|
14346
|
+
return pD(choice) ? null : choice;
|
|
14347
|
+
}
|
|
14348
|
+
function compareQueryMatches(a, b3) {
|
|
14349
|
+
if (a.matchScore !== b3.matchScore) {
|
|
14350
|
+
return b3.matchScore - a.matchScore;
|
|
14351
|
+
}
|
|
14352
|
+
if (a.source.worktree.isCurrent && !b3.source.worktree.isCurrent) return -1;
|
|
14353
|
+
if (!a.source.worktree.isCurrent && b3.source.worktree.isCurrent) return 1;
|
|
14354
|
+
return a.source.repoName.localeCompare(b3.source.repoName) || (a.source.worktree.branch ?? "").localeCompare(
|
|
14355
|
+
b3.source.worktree.branch ?? ""
|
|
14356
|
+
) || a.source.worktree.path.localeCompare(b3.source.worktree.path);
|
|
14357
|
+
}
|
|
14358
|
+
function groupPromptEntries(worktrees) {
|
|
14359
|
+
const groups = {};
|
|
14360
|
+
for (const worktree of worktrees) {
|
|
14361
|
+
const group = worktree.group === "recent" ? "Recent worktrees" : "Other worktrees";
|
|
14362
|
+
groups[group] ??= [];
|
|
14363
|
+
groups[group].push({
|
|
14364
|
+
label: worktree.label,
|
|
14365
|
+
value: worktree.path
|
|
14366
|
+
});
|
|
14367
|
+
}
|
|
14368
|
+
return groups;
|
|
14369
|
+
}
|
|
14370
|
+
function buildWorktreePromptEntry(source, info, lastUsedTimestamp, now) {
|
|
14371
|
+
const lastWorkedTimestamp = info.lastCommitTimestamp === null ? null : info.lastCommitTimestamp * 1e3;
|
|
14372
|
+
const lastActivityTimestamp = lastUsedTimestamp ?? lastWorkedTimestamp;
|
|
14373
|
+
const lastActivityType = lastUsedTimestamp !== null ? "used" : lastWorkedTimestamp !== null ? "worked" : null;
|
|
14374
|
+
const branch = source.worktree.branch ?? "(detached)";
|
|
14375
|
+
const badges = buildStatusBadges(info);
|
|
14376
|
+
const recency = formatPromptRecency(
|
|
14377
|
+
lastActivityTimestamp,
|
|
14378
|
+
lastActivityType,
|
|
14379
|
+
now
|
|
14380
|
+
);
|
|
14381
|
+
const status = badges.length > 0 ? badges.map((badge) => `[${badge}]`).join(" ") : null;
|
|
14382
|
+
const path9 = middleEllipsize(source.worktree.path, 76);
|
|
14383
|
+
const label = [
|
|
14384
|
+
middleEllipsize(source.repoName, 22),
|
|
14385
|
+
middleEllipsize(branch, 34),
|
|
14386
|
+
status,
|
|
14387
|
+
recency,
|
|
14388
|
+
path9
|
|
14389
|
+
].filter((part) => part !== null && part.length > 0).join(" \xB7 ");
|
|
14390
|
+
return {
|
|
14391
|
+
...source.worktree,
|
|
14392
|
+
group: lastUsedTimestamp !== null ? "recent" : "other",
|
|
14393
|
+
label,
|
|
14394
|
+
lastActivityTimestamp,
|
|
14395
|
+
repoName: source.repoName
|
|
14396
|
+
};
|
|
14397
|
+
}
|
|
14398
|
+
function buildStatusBadges(info) {
|
|
14399
|
+
const badges = [];
|
|
14400
|
+
if (info.isCurrent) {
|
|
14401
|
+
badges.push("current");
|
|
14402
|
+
}
|
|
14403
|
+
if (info.branch === null) {
|
|
14404
|
+
badges.push("detached");
|
|
14405
|
+
}
|
|
14406
|
+
if (info.status === "dirty") {
|
|
14407
|
+
badges.push("dirty");
|
|
14408
|
+
}
|
|
14409
|
+
if (info.upstream.kind === "stale") {
|
|
14410
|
+
badges.push("stale", "gone");
|
|
14411
|
+
}
|
|
14412
|
+
if (isUpToDate(info.upstream)) {
|
|
14413
|
+
badges.push("up to date");
|
|
14414
|
+
}
|
|
14415
|
+
return badges;
|
|
14416
|
+
}
|
|
14417
|
+
function isUpToDate(upstream) {
|
|
14418
|
+
return upstream.kind === "tracked" && upstream.ahead === 0 && upstream.behind === 0;
|
|
14419
|
+
}
|
|
14420
|
+
function formatPromptRecency(timestamp, type, now) {
|
|
14421
|
+
if (timestamp === null || type === null) {
|
|
14422
|
+
return "last used: never";
|
|
14423
|
+
}
|
|
14424
|
+
const label = type === "used" ? "last used" : "last worked";
|
|
14425
|
+
return `${label}: ${formatPickerAge(timestamp, now)}`;
|
|
14426
|
+
}
|
|
14427
|
+
function formatPickerAge(timestamp, now) {
|
|
14428
|
+
const ageSeconds = Math.max(0, Math.floor((now - timestamp) / 1e3));
|
|
14429
|
+
if (ageSeconds < 60) {
|
|
14430
|
+
return "now";
|
|
14431
|
+
}
|
|
14432
|
+
if (ageSeconds < 60 * 60) {
|
|
14433
|
+
return `${Math.floor(ageSeconds / 60)}m ago`;
|
|
14434
|
+
}
|
|
14435
|
+
if (ageSeconds < 24 * 60 * 60) {
|
|
14436
|
+
return `${Math.floor(ageSeconds / (60 * 60))}h ago`;
|
|
14437
|
+
}
|
|
14438
|
+
if (isYesterday(timestamp, now)) {
|
|
14439
|
+
return "yesterday";
|
|
14440
|
+
}
|
|
14441
|
+
return new Intl.DateTimeFormat("en-US", {
|
|
14442
|
+
day: "numeric",
|
|
14443
|
+
month: "short"
|
|
14444
|
+
}).format(new Date(timestamp));
|
|
14445
|
+
}
|
|
14446
|
+
function isYesterday(timestamp, now) {
|
|
14447
|
+
const date = new Date(timestamp);
|
|
14448
|
+
const yesterday = new Date(now);
|
|
14449
|
+
yesterday.setDate(yesterday.getDate() - 1);
|
|
14450
|
+
return date.getFullYear() === yesterday.getFullYear() && date.getMonth() === yesterday.getMonth() && date.getDate() === yesterday.getDate();
|
|
14451
|
+
}
|
|
14452
|
+
function buildSearchText(repoName, worktree) {
|
|
14453
|
+
return [
|
|
14454
|
+
repoName,
|
|
14455
|
+
worktree.branch ?? "detached",
|
|
14456
|
+
worktree.path,
|
|
14457
|
+
`${repoName}/${worktree.branch ?? "detached"}`
|
|
14458
|
+
].join(" ").toLowerCase();
|
|
14459
|
+
}
|
|
14460
|
+
function normalizeQuery(query) {
|
|
14461
|
+
const normalized = query?.trim().toLowerCase();
|
|
14462
|
+
return normalized && normalized.length > 0 ? normalized : null;
|
|
14463
|
+
}
|
|
14464
|
+
function comparePromptEntries(a, b3) {
|
|
14465
|
+
if (a.isCurrent && !b3.isCurrent) return -1;
|
|
14466
|
+
if (!a.isCurrent && b3.isCurrent) return 1;
|
|
14467
|
+
if (a.group !== b3.group) {
|
|
14468
|
+
return groupRank(a.group) - groupRank(b3.group);
|
|
14469
|
+
}
|
|
14470
|
+
const aRecent = a.lastActivityTimestamp ?? 0;
|
|
14471
|
+
const bRecent = b3.lastActivityTimestamp ?? 0;
|
|
14472
|
+
if (aRecent !== bRecent) {
|
|
14473
|
+
return bRecent - aRecent;
|
|
14474
|
+
}
|
|
14475
|
+
return a.repoName.localeCompare(b3.repoName) || (a.branch ?? "").localeCompare(b3.branch ?? "") || a.path.localeCompare(b3.path);
|
|
14476
|
+
}
|
|
14477
|
+
function groupRank(group) {
|
|
14478
|
+
return group === "recent" ? 0 : 1;
|
|
14479
|
+
}
|
|
14480
|
+
function scoreWorktreeMatch(entry, query) {
|
|
14481
|
+
const branch = entry.branch ?? "detached";
|
|
14482
|
+
const exactCandidates = [
|
|
14483
|
+
branch,
|
|
14484
|
+
entry.path,
|
|
14485
|
+
`${entry.repoName}/${branch}`
|
|
14486
|
+
].map((candidate) => candidate.toLowerCase());
|
|
14487
|
+
if (exactCandidates.includes(query)) {
|
|
14488
|
+
return 1e3;
|
|
14489
|
+
}
|
|
14490
|
+
return buildSearchText(entry.repoName, entry).includes(query) ? 1 : null;
|
|
14491
|
+
}
|
|
14492
|
+
function middleEllipsize(value, maxLength) {
|
|
14493
|
+
if (value.length <= maxLength) {
|
|
14494
|
+
return value;
|
|
14495
|
+
}
|
|
14496
|
+
if (maxLength <= 1) {
|
|
14497
|
+
return "\u2026";
|
|
14498
|
+
}
|
|
14499
|
+
const keep = maxLength - 1;
|
|
14500
|
+
const start = Math.ceil(keep / 2);
|
|
14501
|
+
const end = Math.floor(keep / 2);
|
|
14502
|
+
return `${value.slice(0, start)}\u2026${value.slice(value.length - end)}`;
|
|
14503
|
+
}
|
|
14504
|
+
|
|
14238
14505
|
// src/worktree-prompts.ts
|
|
14239
14506
|
async function defaultConfirmForceRemoveWorktree(worktreePath) {
|
|
14240
14507
|
const choice = await ye({
|
|
@@ -14295,7 +14562,14 @@ function createCleanCommand(dependencies = {}) {
|
|
|
14295
14562
|
return 1;
|
|
14296
14563
|
}
|
|
14297
14564
|
const shouldSelectAll = options.force || options.dryRun && (options.stale || options.json || isHeadless());
|
|
14298
|
-
const selections = shouldSelectAll ? cleanupCandidates.map((w2) => w2.path) : await promptForWorktrees(
|
|
14565
|
+
const selections = shouldSelectAll ? cleanupCandidates.map((w2) => w2.path) : await promptForWorktrees(
|
|
14566
|
+
await buildWorktreePromptEntries(
|
|
14567
|
+
cleanupCandidates.map((worktree) => ({
|
|
14568
|
+
repoName: repository.repoName,
|
|
14569
|
+
worktree
|
|
14570
|
+
}))
|
|
14571
|
+
)
|
|
14572
|
+
);
|
|
14299
14573
|
if (!selections || selections.length === 0) {
|
|
14300
14574
|
options.stderr("Aborted\n");
|
|
14301
14575
|
return 1;
|
|
@@ -14530,19 +14804,7 @@ function toMessage(error) {
|
|
|
14530
14804
|
return error instanceof Error ? error.message : String(error);
|
|
14531
14805
|
}
|
|
14532
14806
|
async function defaultPromptForWorktrees(worktrees) {
|
|
14533
|
-
|
|
14534
|
-
const choice = await fe({
|
|
14535
|
-
message: "Choose worktrees to clean",
|
|
14536
|
-
options: worktrees.map((worktree, i) => {
|
|
14537
|
-
return {
|
|
14538
|
-
hint: formatWorktreeHint(infos[i]),
|
|
14539
|
-
label: worktree.branch ?? "(detached)",
|
|
14540
|
-
value: worktree.path
|
|
14541
|
-
};
|
|
14542
|
-
}),
|
|
14543
|
-
required: true
|
|
14544
|
-
});
|
|
14545
|
-
return pD(choice) ? null : choice;
|
|
14807
|
+
return promptForMultipleWorktrees("Choose worktrees to clean", worktrees);
|
|
14546
14808
|
}
|
|
14547
14809
|
async function defaultConfirmRemoval(worktrees) {
|
|
14548
14810
|
const branchCount = worktrees.filter(
|
|
@@ -14623,14 +14885,14 @@ var TOP_LEVEL_COMMANDS = [
|
|
|
14623
14885
|
},
|
|
14624
14886
|
{ name: "rm", description: "alias of remove" },
|
|
14625
14887
|
{
|
|
14626
|
-
name: "
|
|
14888
|
+
name: "run-hook",
|
|
14627
14889
|
description: "run a named hook in the current worktree"
|
|
14628
14890
|
},
|
|
14629
14891
|
{ name: "warp", description: "jump to any worktree across all known repos" },
|
|
14630
14892
|
{ name: "config", description: "manage global config defaults" }
|
|
14631
14893
|
];
|
|
14632
14894
|
var SHELL_NAMES = ["bash", "fish", "zsh"];
|
|
14633
|
-
var HOOK_NAMES = ["
|
|
14895
|
+
var HOOK_NAMES = ["after-create", "after-enter", "before-remove"];
|
|
14634
14896
|
var CONFIG_KEYS = Array.from(KNOWN_GLOBAL_CONFIG_KEYS);
|
|
14635
14897
|
function renderShellCompletion(shell) {
|
|
14636
14898
|
switch (shell) {
|
|
@@ -14714,7 +14976,7 @@ _gji_completion() {
|
|
|
14714
14976
|
remove|rm)
|
|
14715
14977
|
COMPREPLY=( $(compgen -W "$(__gji_worktree_branches) -f --force --dry-run --json --help" -- "$cur") )
|
|
14716
14978
|
;;
|
|
14717
|
-
|
|
14979
|
+
run-hook)
|
|
14718
14980
|
COMPREPLY=( $(compgen -W "${hooks} --help" -- "$cur") )
|
|
14719
14981
|
;;
|
|
14720
14982
|
warp)
|
|
@@ -14752,7 +15014,7 @@ function renderFishCompletion() {
|
|
|
14752
15014
|
(shell) => `complete -c gji -n '__fish_seen_subcommand_from init' -a '${shell}' -d 'shell'`
|
|
14753
15015
|
).join("\n");
|
|
14754
15016
|
const hookLines = HOOK_NAMES.map(
|
|
14755
|
-
(hook) => `complete -c gji -n '__fish_seen_subcommand_from
|
|
15017
|
+
(hook) => `complete -c gji -n '__fish_seen_subcommand_from run-hook' -a '${hook}' -d 'hook'`
|
|
14756
15018
|
).join("\n");
|
|
14757
15019
|
const configKeyLines = CONFIG_KEYS.map(
|
|
14758
15020
|
(key) => `complete -c gji -n '__gji_should_complete_config_key' -a '${key}' -d 'config key'`
|
|
@@ -14917,7 +15179,7 @@ case "\${words[2]}" in
|
|
|
14917
15179
|
remove|rm)
|
|
14918
15180
|
_arguments '(-f --force)'{-f,--force}'[bypass prompts, force-remove a dirty worktree, and force-delete an unmerged branch]' '--dry-run[show what would be deleted without removing anything]' '--json[emit JSON on success or error instead of human-readable output]' '2:branch:->worktrees'
|
|
14919
15181
|
;;
|
|
14920
|
-
|
|
15182
|
+
run-hook)
|
|
14921
15183
|
_arguments "2:hook:(${hooks})"
|
|
14922
15184
|
;;
|
|
14923
15185
|
warp)
|
|
@@ -15303,8 +15565,7 @@ async function maybeRunInstallPrompt(worktreePath, repoRoot, config, stderr, dep
|
|
|
15303
15565
|
if (isHeadless() || nonInteractive) {
|
|
15304
15566
|
return;
|
|
15305
15567
|
}
|
|
15306
|
-
|
|
15307
|
-
if (isConfiguredHookCommand(hooks?.afterCreate)) {
|
|
15568
|
+
if (extractHooks(config)["after-create"]) {
|
|
15308
15569
|
return;
|
|
15309
15570
|
}
|
|
15310
15571
|
if (config.skipInstallPrompt === true) {
|
|
@@ -15337,17 +15598,28 @@ async function maybeRunInstallPrompt(worktreePath, repoRoot, config, stderr, dep
|
|
|
15337
15598
|
if (choice === "always") {
|
|
15338
15599
|
try {
|
|
15339
15600
|
if (saveGlobal) {
|
|
15340
|
-
const
|
|
15601
|
+
const existingRaw = await loadExistingGlobalRepoHooks(repoRoot);
|
|
15602
|
+
const existing = extractHooks({ hooks: existingRaw });
|
|
15341
15603
|
await writeGlobalKey(repoRoot, "hooks", {
|
|
15342
|
-
...
|
|
15343
|
-
|
|
15604
|
+
...existing["after-enter"] !== void 0 && {
|
|
15605
|
+
"after-enter": existing["after-enter"]
|
|
15606
|
+
},
|
|
15607
|
+
...existing["before-remove"] !== void 0 && {
|
|
15608
|
+
"before-remove": existing["before-remove"]
|
|
15609
|
+
},
|
|
15610
|
+
"after-create": pm.installCommand
|
|
15344
15611
|
});
|
|
15345
15612
|
} else {
|
|
15346
15613
|
const { config: localConfig } = await loadConfig(repoRoot);
|
|
15347
|
-
const
|
|
15614
|
+
const existing = extractHooks(localConfig);
|
|
15348
15615
|
await writeKey(repoRoot, "hooks", {
|
|
15349
|
-
...
|
|
15350
|
-
|
|
15616
|
+
...existing["after-enter"] !== void 0 && {
|
|
15617
|
+
"after-enter": existing["after-enter"]
|
|
15618
|
+
},
|
|
15619
|
+
...existing["before-remove"] !== void 0 && {
|
|
15620
|
+
"before-remove": existing["before-remove"]
|
|
15621
|
+
},
|
|
15622
|
+
"after-create": pm.installCommand
|
|
15351
15623
|
});
|
|
15352
15624
|
}
|
|
15353
15625
|
} catch (error) {
|
|
@@ -15412,7 +15684,7 @@ async function defaultPromptForInstallChoice(pm) {
|
|
|
15412
15684
|
options: [
|
|
15413
15685
|
{ value: "yes", label: "Yes", hint: "run once" },
|
|
15414
15686
|
{ value: "no", label: "No", hint: "skip this time" },
|
|
15415
|
-
{ value: "always", label: "Always", hint: "save as
|
|
15687
|
+
{ value: "always", label: "Always", hint: "save as after-create hook" },
|
|
15416
15688
|
{
|
|
15417
15689
|
value: "never",
|
|
15418
15690
|
label: "Never",
|
|
@@ -15428,10 +15700,6 @@ async function defaultPromptForInstallChoice(pm) {
|
|
|
15428
15700
|
function isPlainObject2(value) {
|
|
15429
15701
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15430
15702
|
}
|
|
15431
|
-
function isConfiguredHookCommand(value) {
|
|
15432
|
-
if (typeof value === "string") return value.length > 0;
|
|
15433
|
-
return Array.isArray(value) && value.length > 0 && value[0] !== "" && value.every((item) => typeof item === "string");
|
|
15434
|
-
}
|
|
15435
15703
|
|
|
15436
15704
|
// src/new.ts
|
|
15437
15705
|
var execFileAsync3 = promisify4(execFile3);
|
|
@@ -15549,7 +15817,7 @@ function createNewCommand(dependencies = {}) {
|
|
|
15549
15817
|
`
|
|
15550
15818
|
);
|
|
15551
15819
|
options.stderr(
|
|
15552
|
-
`Hint: Use 'gji
|
|
15820
|
+
`Hint: Use 'gji run-hook after-create' inside the worktree to re-run setup hooks
|
|
15553
15821
|
`
|
|
15554
15822
|
);
|
|
15555
15823
|
}
|
|
@@ -15557,7 +15825,7 @@ function createNewCommand(dependencies = {}) {
|
|
|
15557
15825
|
} else {
|
|
15558
15826
|
const choice = await prompt(worktreePath);
|
|
15559
15827
|
if (choice === "reuse") {
|
|
15560
|
-
|
|
15828
|
+
await recordWorktreeUsage(worktreePath, worktreeName);
|
|
15561
15829
|
await writeOutput(worktreePath, options.stdout);
|
|
15562
15830
|
return 0;
|
|
15563
15831
|
}
|
|
@@ -15610,7 +15878,7 @@ function createNewCommand(dependencies = {}) {
|
|
|
15610
15878
|
);
|
|
15611
15879
|
const hooks = extractHooks(config);
|
|
15612
15880
|
await runHook(
|
|
15613
|
-
hooks
|
|
15881
|
+
hooks["after-create"],
|
|
15614
15882
|
worktreePath,
|
|
15615
15883
|
{
|
|
15616
15884
|
branch: worktreeName,
|
|
@@ -15625,7 +15893,7 @@ function createNewCommand(dependencies = {}) {
|
|
|
15625
15893
|
`
|
|
15626
15894
|
);
|
|
15627
15895
|
} else {
|
|
15628
|
-
await
|
|
15896
|
+
await recordWorktreeUsage(worktreePath, worktreeName);
|
|
15629
15897
|
await writeOutput(worktreePath, options.stdout);
|
|
15630
15898
|
}
|
|
15631
15899
|
if (options.open) {
|
|
@@ -15662,7 +15930,32 @@ function generateBranchPlaceholder(random = Math.random) {
|
|
|
15662
15930
|
"newton",
|
|
15663
15931
|
"lovelace",
|
|
15664
15932
|
"nietzsche",
|
|
15665
|
-
"kafka"
|
|
15933
|
+
"kafka",
|
|
15934
|
+
"sappho",
|
|
15935
|
+
"aristotle",
|
|
15936
|
+
"pythagoras",
|
|
15937
|
+
"artemis",
|
|
15938
|
+
"apollo",
|
|
15939
|
+
"minerva",
|
|
15940
|
+
"persephone",
|
|
15941
|
+
"icarus",
|
|
15942
|
+
"odysseus",
|
|
15943
|
+
"murasaki",
|
|
15944
|
+
"shakespeare",
|
|
15945
|
+
"frida",
|
|
15946
|
+
"davinci",
|
|
15947
|
+
"kepler",
|
|
15948
|
+
"copernicus",
|
|
15949
|
+
"faraday",
|
|
15950
|
+
"noether",
|
|
15951
|
+
"hopper",
|
|
15952
|
+
"boole",
|
|
15953
|
+
"shannon",
|
|
15954
|
+
"gauss",
|
|
15955
|
+
"ramanujan",
|
|
15956
|
+
"austen",
|
|
15957
|
+
"borges",
|
|
15958
|
+
"zeno"
|
|
15666
15959
|
];
|
|
15667
15960
|
const antics = [
|
|
15668
15961
|
"borrowed-a-bike",
|
|
@@ -15679,9 +15972,50 @@ function generateBranchPlaceholder(random = Math.random) {
|
|
|
15679
15972
|
"watered-the-plants",
|
|
15680
15973
|
"washed-the-dishes",
|
|
15681
15974
|
"folded-the-laundry",
|
|
15682
|
-
"took-a-nap"
|
|
15975
|
+
"took-a-nap",
|
|
15976
|
+
"lost-a-sock",
|
|
15977
|
+
"patched-the-boat",
|
|
15978
|
+
"alphabetized-the-spoons",
|
|
15979
|
+
"argued-with-the-calendar",
|
|
15980
|
+
"misplaced-the-moon",
|
|
15981
|
+
"painted-the-fence",
|
|
15982
|
+
"overcooked-the-rice",
|
|
15983
|
+
"packed-the-snacks",
|
|
15984
|
+
"dropped-the-spoon",
|
|
15985
|
+
"hid-the-remote",
|
|
15986
|
+
"untangled-the-cables",
|
|
15987
|
+
"rebooted-the-kettle",
|
|
15988
|
+
"indexed-the-attic",
|
|
15989
|
+
"forgot-the-password",
|
|
15990
|
+
"sorted-the-buttons",
|
|
15991
|
+
"mopped-the-ceiling",
|
|
15992
|
+
"polished-the-doorknob",
|
|
15993
|
+
"misread-the-map",
|
|
15994
|
+
"reheated-the-tea",
|
|
15995
|
+
"fixed-the-squeak",
|
|
15996
|
+
"labeled-the-drawer",
|
|
15997
|
+
"stacked-the-chairs",
|
|
15998
|
+
"overslept-the-standup",
|
|
15999
|
+
"claimed-the-last-bagel",
|
|
16000
|
+
"debugged-the-toaster"
|
|
15683
16001
|
];
|
|
15684
|
-
|
|
16002
|
+
const root = pickRandom(roots, random);
|
|
16003
|
+
const antic = pickRandom(antics, random);
|
|
16004
|
+
const suffix = generateBranchPlaceholderSuffix(random);
|
|
16005
|
+
return `${root}-${antic}-${suffix}`;
|
|
16006
|
+
}
|
|
16007
|
+
function pickRandom(values, random) {
|
|
16008
|
+
const index = Math.floor(random() * values.length);
|
|
16009
|
+
return values[Math.min(index, values.length - 1)];
|
|
16010
|
+
}
|
|
16011
|
+
function generateBranchPlaceholderSuffix(random) {
|
|
16012
|
+
const characters = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
16013
|
+
let suffix = "";
|
|
16014
|
+
for (let index = 0; index < 3; index += 1) {
|
|
16015
|
+
const characterIndex = Math.floor(random() * characters.length);
|
|
16016
|
+
suffix += characters[Math.min(characterIndex, characters.length - 1)];
|
|
16017
|
+
}
|
|
16018
|
+
return suffix;
|
|
15685
16019
|
}
|
|
15686
16020
|
function applyConfiguredBranchPrefix(branch, branchPrefix) {
|
|
15687
16021
|
if (typeof branchPrefix !== "string" || branchPrefix.length === 0) {
|
|
@@ -15722,10 +16056,6 @@ async function defaultPromptForBranch(placeholder) {
|
|
|
15722
16056
|
}
|
|
15723
16057
|
return choice.trim();
|
|
15724
16058
|
}
|
|
15725
|
-
function pickRandom(values, random) {
|
|
15726
|
-
const index = Math.floor(random() * values.length);
|
|
15727
|
-
return values[Math.min(index, values.length - 1)];
|
|
15728
|
-
}
|
|
15729
16059
|
async function localBranchExists(repoRoot, branchName) {
|
|
15730
16060
|
try {
|
|
15731
16061
|
await execFileAsync3(
|
|
@@ -15882,7 +16212,7 @@ async function runWarpNavigate(options) {
|
|
|
15882
16212
|
);
|
|
15883
16213
|
return 0;
|
|
15884
16214
|
}
|
|
15885
|
-
|
|
16215
|
+
await recordWorktreeUsage(target.path, target.branch);
|
|
15886
16216
|
await writeShellOutput(WARP_OUTPUT_FILE_ENV, target.path, options.stdout);
|
|
15887
16217
|
return 0;
|
|
15888
16218
|
}
|
|
@@ -15965,18 +16295,6 @@ async function canonicalizeRepoPath2(repoPath) {
|
|
|
15965
16295
|
return resolve5(repoPath);
|
|
15966
16296
|
}
|
|
15967
16297
|
}
|
|
15968
|
-
function findByQuery(items, query) {
|
|
15969
|
-
const slashIdx = query.indexOf("/");
|
|
15970
|
-
if (slashIdx !== -1) {
|
|
15971
|
-
const repoQuery = query.slice(0, slashIdx);
|
|
15972
|
-
const branchQuery = query.slice(slashIdx + 1);
|
|
15973
|
-
const match = items.find(
|
|
15974
|
-
(item) => item.repoName === repoQuery && item.worktree.branch === branchQuery
|
|
15975
|
-
);
|
|
15976
|
-
if (match) return match;
|
|
15977
|
-
}
|
|
15978
|
-
return items.find((item) => item.worktree.branch === query) ?? null;
|
|
15979
|
-
}
|
|
15980
16298
|
async function resolveWarpTarget(options) {
|
|
15981
16299
|
const cmd = options.commandName ?? "gji";
|
|
15982
16300
|
const emitError4 = (message, hint) => {
|
|
@@ -15990,6 +16308,7 @@ async function resolveWarpTarget(options) {
|
|
|
15990
16308
|
}
|
|
15991
16309
|
};
|
|
15992
16310
|
const registry = await loadRegistry();
|
|
16311
|
+
const currentRoot = await detectRepository(options.cwd).then((repository) => repository.currentRoot).catch(() => null);
|
|
15993
16312
|
if (registry.length === 0) {
|
|
15994
16313
|
emitError4(
|
|
15995
16314
|
"not in a git repository and no repos registered yet.",
|
|
@@ -16008,57 +16327,42 @@ async function resolveWarpTarget(options) {
|
|
|
16008
16327
|
if (result.status === "rejected") continue;
|
|
16009
16328
|
const { repoName, worktrees } = result.value;
|
|
16010
16329
|
for (const worktree of worktrees) {
|
|
16011
|
-
allItems.push({
|
|
16330
|
+
allItems.push({
|
|
16331
|
+
repoName,
|
|
16332
|
+
worktree: {
|
|
16333
|
+
...worktree,
|
|
16334
|
+
isCurrent: currentRoot !== null && worktree.path === currentRoot
|
|
16335
|
+
}
|
|
16336
|
+
});
|
|
16012
16337
|
}
|
|
16013
16338
|
}
|
|
16014
16339
|
if (allItems.length === 0) {
|
|
16015
16340
|
emitError4("no accessible worktrees found in any registered repo.");
|
|
16016
16341
|
return null;
|
|
16017
16342
|
}
|
|
16343
|
+
const promptSources = allItems.map((item) => ({
|
|
16344
|
+
repoName: item.repoName,
|
|
16345
|
+
worktree: item.worktree
|
|
16346
|
+
}));
|
|
16018
16347
|
if (options.branch) {
|
|
16019
|
-
const match =
|
|
16348
|
+
const match = resolveWorktreeQuery(promptSources, options.branch);
|
|
16020
16349
|
if (!match) {
|
|
16021
16350
|
emitError4(`no worktree found matching: ${options.branch}`);
|
|
16022
16351
|
return null;
|
|
16023
16352
|
}
|
|
16024
16353
|
return { branch: match.worktree.branch, path: match.worktree.path };
|
|
16025
16354
|
}
|
|
16026
|
-
const
|
|
16355
|
+
const promptEntries = await buildWorktreePromptEntries(promptSources);
|
|
16356
|
+
const path9 = await promptForWarpTarget(promptEntries);
|
|
16027
16357
|
if (!path9) {
|
|
16028
16358
|
options.stderr("Aborted\n");
|
|
16029
16359
|
return null;
|
|
16030
16360
|
}
|
|
16031
|
-
const chosen =
|
|
16032
|
-
return { branch: chosen?.
|
|
16361
|
+
const chosen = promptEntries.find((item) => item.path === path9);
|
|
16362
|
+
return { branch: chosen?.branch ?? null, path: path9 };
|
|
16033
16363
|
}
|
|
16034
16364
|
async function promptForWarpTarget(items) {
|
|
16035
|
-
|
|
16036
|
-
items.map((item) => readWorktreeHealth(item.worktree.path))
|
|
16037
|
-
);
|
|
16038
|
-
const choice = await ve({
|
|
16039
|
-
message: "Warp to a worktree",
|
|
16040
|
-
options: items.map((item, i) => {
|
|
16041
|
-
const health = healthResults[i].status === "fulfilled" ? healthResults[i].value : null;
|
|
16042
|
-
const upstream = health ? formatHint(item.worktree.branch, health) : null;
|
|
16043
|
-
const label = `${item.repoName} \u203A ${item.worktree.branch ?? "(detached)"}`;
|
|
16044
|
-
const pathHint = item.worktree.isCurrent ? `${item.worktree.path} (current)` : item.worktree.path;
|
|
16045
|
-
const hint = upstream ? `${upstream} \xB7 ${pathHint}` : pathHint;
|
|
16046
|
-
return { hint, label, value: item.worktree.path };
|
|
16047
|
-
})
|
|
16048
|
-
});
|
|
16049
|
-
if (pD(choice)) {
|
|
16050
|
-
return null;
|
|
16051
|
-
}
|
|
16052
|
-
return choice;
|
|
16053
|
-
}
|
|
16054
|
-
function formatHint(branch, health) {
|
|
16055
|
-
if (branch === null) return null;
|
|
16056
|
-
if (!health.hasUpstream) return "no upstream";
|
|
16057
|
-
if (health.upstreamGone) return "upstream gone";
|
|
16058
|
-
if (health.ahead === 0 && health.behind === 0) return "up to date";
|
|
16059
|
-
if (health.ahead === 0) return `behind ${health.behind}`;
|
|
16060
|
-
if (health.behind === 0) return `ahead ${health.ahead}`;
|
|
16061
|
-
return `ahead ${health.ahead}, behind ${health.behind}`;
|
|
16365
|
+
return promptForSingleWorktree("Warp to a worktree", items);
|
|
16062
16366
|
}
|
|
16063
16367
|
|
|
16064
16368
|
// src/go.ts
|
|
@@ -16085,7 +16389,7 @@ function createGoCommand(dependencies = {}) {
|
|
|
16085
16389
|
commandName: "gji go"
|
|
16086
16390
|
});
|
|
16087
16391
|
if (!target) return 1;
|
|
16088
|
-
|
|
16392
|
+
await recordWorktreeUsage(target.path, target.branch);
|
|
16089
16393
|
await writeShellOutput(GO_OUTPUT_FILE_ENV, target.path, options.stdout);
|
|
16090
16394
|
return 0;
|
|
16091
16395
|
}
|
|
@@ -16095,8 +16399,14 @@ function createGoCommand(dependencies = {}) {
|
|
|
16095
16399
|
);
|
|
16096
16400
|
return 1;
|
|
16097
16401
|
}
|
|
16098
|
-
const
|
|
16099
|
-
|
|
16402
|
+
const promptSources = worktrees.map((worktree) => ({
|
|
16403
|
+
repoName: repository.repoName,
|
|
16404
|
+
worktree
|
|
16405
|
+
}));
|
|
16406
|
+
const promptEntries = options.branch ? [] : await buildWorktreePromptEntries(promptSources);
|
|
16407
|
+
const queried = options.branch ? resolveWorktreeQuery(promptSources, options.branch) : null;
|
|
16408
|
+
const prompted = options.branch ? null : await prompt(promptEntries);
|
|
16409
|
+
const resolvedPath = options.branch ? queried?.worktree.path : prompted ?? void 0;
|
|
16100
16410
|
if (!resolvedPath) {
|
|
16101
16411
|
if (options.branch) {
|
|
16102
16412
|
options.stderr(`No worktree found for branch: ${options.branch}
|
|
@@ -16116,7 +16426,7 @@ function createGoCommand(dependencies = {}) {
|
|
|
16116
16426
|
);
|
|
16117
16427
|
const hooks = extractHooks(config);
|
|
16118
16428
|
await runHook(
|
|
16119
|
-
hooks
|
|
16429
|
+
hooks["after-enter"],
|
|
16120
16430
|
resolvedPath,
|
|
16121
16431
|
{
|
|
16122
16432
|
branch: chosenWorktree?.branch ?? void 0,
|
|
@@ -16125,44 +16435,14 @@ function createGoCommand(dependencies = {}) {
|
|
|
16125
16435
|
},
|
|
16126
16436
|
options.stderr
|
|
16127
16437
|
);
|
|
16128
|
-
|
|
16129
|
-
() => void 0
|
|
16130
|
-
);
|
|
16438
|
+
await recordWorktreeUsage(resolvedPath, chosenWorktree?.branch ?? null);
|
|
16131
16439
|
await writeShellOutput(GO_OUTPUT_FILE_ENV, resolvedPath, options.stdout);
|
|
16132
16440
|
return 0;
|
|
16133
16441
|
};
|
|
16134
16442
|
}
|
|
16135
16443
|
var runGoCommand = createGoCommand();
|
|
16136
16444
|
async function promptForWorktree(worktrees) {
|
|
16137
|
-
|
|
16138
|
-
worktrees.map((w2) => readWorktreeHealth(w2.path))
|
|
16139
|
-
);
|
|
16140
|
-
const choice = await ve({
|
|
16141
|
-
message: "Choose a worktree",
|
|
16142
|
-
options: worktrees.map((worktree, i) => {
|
|
16143
|
-
const health = healthResults[i].status === "fulfilled" ? healthResults[i].value : null;
|
|
16144
|
-
const pathHint = worktree.isCurrent ? `${worktree.path} (current)` : worktree.path;
|
|
16145
|
-
const upstream = health ? formatUpstreamHint(worktree.branch, health) : null;
|
|
16146
|
-
return {
|
|
16147
|
-
value: worktree.path,
|
|
16148
|
-
label: worktree.branch ?? "(detached)",
|
|
16149
|
-
hint: upstream ? `${upstream} \xB7 ${pathHint}` : pathHint
|
|
16150
|
-
};
|
|
16151
|
-
})
|
|
16152
|
-
});
|
|
16153
|
-
if (pD(choice)) {
|
|
16154
|
-
return null;
|
|
16155
|
-
}
|
|
16156
|
-
return choice;
|
|
16157
|
-
}
|
|
16158
|
-
function formatUpstreamHint(branch, health) {
|
|
16159
|
-
if (branch === null) return null;
|
|
16160
|
-
if (!health.hasUpstream) return "no upstream";
|
|
16161
|
-
if (health.upstreamGone) return "upstream gone";
|
|
16162
|
-
if (health.ahead === 0 && health.behind === 0) return "up to date";
|
|
16163
|
-
if (health.ahead === 0) return `behind ${health.behind}`;
|
|
16164
|
-
if (health.behind === 0) return `ahead ${health.ahead}`;
|
|
16165
|
-
return `ahead ${health.ahead}, behind ${health.behind}`;
|
|
16445
|
+
return promptForSingleWorktree("Choose a worktree", worktrees);
|
|
16166
16446
|
}
|
|
16167
16447
|
|
|
16168
16448
|
// src/history-command.ts
|
|
@@ -16329,10 +16609,12 @@ async function saveWizardConfig(result, cwd, home) {
|
|
|
16329
16609
|
if (result.branchPrefix) values.branchPrefix = result.branchPrefix;
|
|
16330
16610
|
if (result.worktreePath) values.worktreePath = result.worktreePath;
|
|
16331
16611
|
const hooks = {};
|
|
16332
|
-
if (result.hooks?.
|
|
16333
|
-
|
|
16334
|
-
if (result.hooks?.
|
|
16335
|
-
hooks
|
|
16612
|
+
if (result.hooks?.["after-create"])
|
|
16613
|
+
hooks["after-create"] = result.hooks["after-create"];
|
|
16614
|
+
if (result.hooks?.["after-enter"])
|
|
16615
|
+
hooks["after-enter"] = result.hooks["after-enter"];
|
|
16616
|
+
if (result.hooks?.["before-remove"])
|
|
16617
|
+
hooks["before-remove"] = result.hooks["before-remove"];
|
|
16336
16618
|
if (Object.keys(hooks).length > 0) values.hooks = hooks;
|
|
16337
16619
|
if (Object.keys(values).length === 0) return;
|
|
16338
16620
|
if (result.installSaveTarget === "local") {
|
|
@@ -16463,7 +16745,7 @@ async function defaultPromptForSetup() {
|
|
|
16463
16745
|
return null;
|
|
16464
16746
|
}
|
|
16465
16747
|
const afterCreate = await he({
|
|
16466
|
-
message: "
|
|
16748
|
+
message: "after-create hook \u2014 run after creating a worktree?",
|
|
16467
16749
|
placeholder: "e.g. pnpm install \u2014 leave blank to skip"
|
|
16468
16750
|
});
|
|
16469
16751
|
if (pD(afterCreate)) {
|
|
@@ -16471,7 +16753,7 @@ async function defaultPromptForSetup() {
|
|
|
16471
16753
|
return null;
|
|
16472
16754
|
}
|
|
16473
16755
|
const afterEnter = await he({
|
|
16474
|
-
message: "
|
|
16756
|
+
message: "after-enter hook \u2014 run after entering a worktree?",
|
|
16475
16757
|
placeholder: "e.g. nvm use \u2014 leave blank to skip"
|
|
16476
16758
|
});
|
|
16477
16759
|
if (pD(afterEnter)) {
|
|
@@ -16479,7 +16761,7 @@ async function defaultPromptForSetup() {
|
|
|
16479
16761
|
return null;
|
|
16480
16762
|
}
|
|
16481
16763
|
const beforeRemove = await he({
|
|
16482
|
-
message: "
|
|
16764
|
+
message: "before-remove hook \u2014 run before removing a worktree?",
|
|
16483
16765
|
placeholder: "leave blank to skip"
|
|
16484
16766
|
});
|
|
16485
16767
|
if (pD(beforeRemove)) {
|
|
@@ -16488,9 +16770,9 @@ async function defaultPromptForSetup() {
|
|
|
16488
16770
|
}
|
|
16489
16771
|
Se("Setup complete!");
|
|
16490
16772
|
const hooks = {};
|
|
16491
|
-
if (afterCreate) hooks
|
|
16492
|
-
if (afterEnter) hooks
|
|
16493
|
-
if (beforeRemove) hooks
|
|
16773
|
+
if (afterCreate) hooks["after-create"] = afterCreate;
|
|
16774
|
+
if (afterEnter) hooks["after-enter"] = afterEnter;
|
|
16775
|
+
if (beforeRemove) hooks["before-remove"] = beforeRemove;
|
|
16494
16776
|
return {
|
|
16495
16777
|
branchPrefix: branchPrefix || void 0,
|
|
16496
16778
|
hooks: Object.keys(hooks).length > 0 ? hooks : void 0,
|
|
@@ -16611,27 +16893,43 @@ function createOpenCommand(dependencies = {}) {
|
|
|
16611
16893
|
detectRepository(options.cwd)
|
|
16612
16894
|
]);
|
|
16613
16895
|
let targetPath;
|
|
16896
|
+
let targetWorktree;
|
|
16614
16897
|
if (options.branch) {
|
|
16615
|
-
const
|
|
16616
|
-
|
|
16898
|
+
const match = resolveWorktreeQuery(
|
|
16899
|
+
worktrees.map((worktree) => ({
|
|
16900
|
+
repoName: repository.repoName,
|
|
16901
|
+
worktree
|
|
16902
|
+
})),
|
|
16903
|
+
options.branch
|
|
16904
|
+
);
|
|
16905
|
+
if (!match) {
|
|
16617
16906
|
options.stderr(
|
|
16618
|
-
`gji open: no worktree found
|
|
16907
|
+
`gji open: no worktree found matching: ${options.branch}
|
|
16619
16908
|
`
|
|
16620
16909
|
);
|
|
16621
16910
|
options.stderr(`Hint: Use 'gji ls' to see available worktrees
|
|
16622
16911
|
`);
|
|
16623
16912
|
return 1;
|
|
16624
16913
|
}
|
|
16625
|
-
targetPath =
|
|
16914
|
+
targetPath = match.worktree.path;
|
|
16915
|
+
targetWorktree = match.worktree;
|
|
16626
16916
|
} else if (isHeadless()) {
|
|
16627
|
-
|
|
16917
|
+
targetWorktree = worktrees.find((w2) => w2.isCurrent);
|
|
16918
|
+
targetPath = targetWorktree?.path ?? options.cwd;
|
|
16628
16919
|
} else {
|
|
16629
|
-
const
|
|
16920
|
+
const entries = await buildWorktreePromptEntries(
|
|
16921
|
+
worktrees.map((worktree) => ({
|
|
16922
|
+
repoName: repository.repoName,
|
|
16923
|
+
worktree
|
|
16924
|
+
}))
|
|
16925
|
+
);
|
|
16926
|
+
const chosen = await promptForWorktree2(entries);
|
|
16630
16927
|
if (!chosen) {
|
|
16631
16928
|
options.stderr("Aborted\n");
|
|
16632
16929
|
return 1;
|
|
16633
16930
|
}
|
|
16634
16931
|
targetPath = chosen;
|
|
16932
|
+
targetWorktree = worktrees.find((w2) => w2.path === chosen);
|
|
16635
16933
|
}
|
|
16636
16934
|
const config = await loadEffectiveConfig(
|
|
16637
16935
|
repository.repoRoot,
|
|
@@ -16696,6 +16994,7 @@ function createOpenCommand(dependencies = {}) {
|
|
|
16696
16994
|
return 1;
|
|
16697
16995
|
}
|
|
16698
16996
|
const displayName = editorDef?.name ?? editorCli;
|
|
16997
|
+
await recordWorktreeUsage(targetPath, targetWorktree?.branch ?? null);
|
|
16699
16998
|
options.stdout(`Opened ${targetPath} in ${displayName}
|
|
16700
16999
|
`);
|
|
16701
17000
|
return 0;
|
|
@@ -16720,16 +17019,7 @@ async function isCommandAvailable(command) {
|
|
|
16720
17019
|
}
|
|
16721
17020
|
}
|
|
16722
17021
|
async function defaultPromptForWorktree(worktrees) {
|
|
16723
|
-
|
|
16724
|
-
message: "Choose a worktree to open",
|
|
16725
|
-
options: worktrees.map((w2) => ({
|
|
16726
|
-
value: w2.path,
|
|
16727
|
-
label: w2.branch ?? "(detached)",
|
|
16728
|
-
hint: w2.isCurrent ? `${w2.path} (current)` : w2.path
|
|
16729
|
-
}))
|
|
16730
|
-
});
|
|
16731
|
-
if (pD(choice)) return null;
|
|
16732
|
-
return choice;
|
|
17022
|
+
return promptForSingleWorktree("Choose a worktree to open", worktrees);
|
|
16733
17023
|
}
|
|
16734
17024
|
async function defaultPromptForEditor(editors) {
|
|
16735
17025
|
const choice = await ve({
|
|
@@ -16823,7 +17113,7 @@ function createPrCommand(dependencies = {}) {
|
|
|
16823
17113
|
}
|
|
16824
17114
|
const choice = await prompt(worktreePath);
|
|
16825
17115
|
if (choice === "reuse") {
|
|
16826
|
-
|
|
17116
|
+
await recordWorktreeUsage(worktreePath, branchName);
|
|
16827
17117
|
await writeOutput2(worktreePath, options.stdout);
|
|
16828
17118
|
return 0;
|
|
16829
17119
|
}
|
|
@@ -16899,7 +17189,7 @@ function createPrCommand(dependencies = {}) {
|
|
|
16899
17189
|
);
|
|
16900
17190
|
const hooks = extractHooks(config);
|
|
16901
17191
|
await runHook(
|
|
16902
|
-
hooks
|
|
17192
|
+
hooks["after-create"],
|
|
16903
17193
|
worktreePath,
|
|
16904
17194
|
{
|
|
16905
17195
|
branch: branchName,
|
|
@@ -16914,7 +17204,7 @@ function createPrCommand(dependencies = {}) {
|
|
|
16914
17204
|
`
|
|
16915
17205
|
);
|
|
16916
17206
|
} else {
|
|
16917
|
-
await
|
|
17207
|
+
await recordWorktreeUsage(worktreePath, branchName);
|
|
16918
17208
|
await writeOutput2(worktreePath, options.stdout);
|
|
16919
17209
|
}
|
|
16920
17210
|
return 0;
|
|
@@ -17014,7 +17304,14 @@ function createRemoveCommand(dependencies = {}) {
|
|
|
17014
17304
|
}
|
|
17015
17305
|
return 1;
|
|
17016
17306
|
}
|
|
17017
|
-
const selection = options.branch ?? await promptForWorktree2(
|
|
17307
|
+
const selection = options.branch ?? await promptForWorktree2(
|
|
17308
|
+
await buildWorktreePromptEntries(
|
|
17309
|
+
linkedWorktrees.map((worktree2) => ({
|
|
17310
|
+
repoName: repository.repoName,
|
|
17311
|
+
worktree: worktree2
|
|
17312
|
+
}))
|
|
17313
|
+
)
|
|
17314
|
+
);
|
|
17018
17315
|
if (!selection) {
|
|
17019
17316
|
options.stderr("Aborted\n");
|
|
17020
17317
|
return 1;
|
|
@@ -17062,7 +17359,7 @@ function createRemoveCommand(dependencies = {}) {
|
|
|
17062
17359
|
);
|
|
17063
17360
|
const hooks = extractHooks(config);
|
|
17064
17361
|
await runHook(
|
|
17065
|
-
hooks
|
|
17362
|
+
hooks["before-remove"],
|
|
17066
17363
|
worktree.path,
|
|
17067
17364
|
{
|
|
17068
17365
|
branch: worktree.branch ?? void 0,
|
|
@@ -17128,15 +17425,7 @@ function createRemoveCommand(dependencies = {}) {
|
|
|
17128
17425
|
}
|
|
17129
17426
|
var runRemoveCommand = createRemoveCommand();
|
|
17130
17427
|
async function defaultPromptForWorktree2(worktrees) {
|
|
17131
|
-
|
|
17132
|
-
message: "Choose a worktree to finish",
|
|
17133
|
-
options: worktrees.map((worktree) => ({
|
|
17134
|
-
hint: worktree.isCurrent ? `${worktree.path} (current)` : worktree.path,
|
|
17135
|
-
label: worktree.branch ?? "(detached)",
|
|
17136
|
-
value: worktree.path
|
|
17137
|
-
}))
|
|
17138
|
-
});
|
|
17139
|
-
return pD(choice) ? null : choice;
|
|
17428
|
+
return promptForSingleWorktree("Choose a worktree to finish", worktrees);
|
|
17140
17429
|
}
|
|
17141
17430
|
async function defaultConfirmRemoval2(worktree) {
|
|
17142
17431
|
const choice = await ye({
|
|
@@ -17180,6 +17469,54 @@ async function runRootCommand(options) {
|
|
|
17180
17469
|
return 0;
|
|
17181
17470
|
}
|
|
17182
17471
|
|
|
17472
|
+
// src/run-hook.ts
|
|
17473
|
+
var VALID_HOOKS = [
|
|
17474
|
+
"after-create",
|
|
17475
|
+
"after-enter",
|
|
17476
|
+
"before-remove"
|
|
17477
|
+
];
|
|
17478
|
+
var CAMEL_ALIASES = {
|
|
17479
|
+
afterCreate: "after-create",
|
|
17480
|
+
afterEnter: "after-enter",
|
|
17481
|
+
beforeRemove: "before-remove"
|
|
17482
|
+
};
|
|
17483
|
+
function isValidHook(hook) {
|
|
17484
|
+
return VALID_HOOKS.includes(hook);
|
|
17485
|
+
}
|
|
17486
|
+
async function runHookCommand(options) {
|
|
17487
|
+
const normalized = CAMEL_ALIASES[options.hook] ?? options.hook;
|
|
17488
|
+
if (!isValidHook(normalized)) {
|
|
17489
|
+
options.stderr(
|
|
17490
|
+
`gji run-hook: unknown hook '${options.hook}'. Valid hooks: ${VALID_HOOKS.join(", ")}
|
|
17491
|
+
`
|
|
17492
|
+
);
|
|
17493
|
+
return 1;
|
|
17494
|
+
}
|
|
17495
|
+
const hookName = normalized;
|
|
17496
|
+
const repository = await detectRepository(options.cwd);
|
|
17497
|
+
const config = await loadEffectiveConfig(
|
|
17498
|
+
repository.repoRoot,
|
|
17499
|
+
void 0,
|
|
17500
|
+
options.stderr
|
|
17501
|
+
);
|
|
17502
|
+
const hooks = extractHooks(config);
|
|
17503
|
+
const worktrees = await listWorktrees(options.cwd);
|
|
17504
|
+
const currentWorktree = worktrees.find(
|
|
17505
|
+
(w2) => w2.path === repository.currentRoot
|
|
17506
|
+
);
|
|
17507
|
+
await runHook(
|
|
17508
|
+
hooks[hookName],
|
|
17509
|
+
repository.currentRoot,
|
|
17510
|
+
{
|
|
17511
|
+
branch: currentWorktree?.branch ?? void 0,
|
|
17512
|
+
path: repository.currentRoot,
|
|
17513
|
+
repo: repository.repoName
|
|
17514
|
+
},
|
|
17515
|
+
options.stderr
|
|
17516
|
+
);
|
|
17517
|
+
return 0;
|
|
17518
|
+
}
|
|
17519
|
+
|
|
17183
17520
|
// src/status.ts
|
|
17184
17521
|
async function runStatusCommand(options) {
|
|
17185
17522
|
const repository = await detectRepository(options.cwd);
|
|
@@ -17552,48 +17889,6 @@ function expandTilde2(value, home) {
|
|
|
17552
17889
|
return value;
|
|
17553
17890
|
}
|
|
17554
17891
|
|
|
17555
|
-
// src/trigger-hook.ts
|
|
17556
|
-
var VALID_HOOKS = [
|
|
17557
|
-
"afterCreate",
|
|
17558
|
-
"afterEnter",
|
|
17559
|
-
"beforeRemove"
|
|
17560
|
-
];
|
|
17561
|
-
function isValidHook(hook) {
|
|
17562
|
-
return VALID_HOOKS.includes(hook);
|
|
17563
|
-
}
|
|
17564
|
-
async function runTriggerHookCommand(options) {
|
|
17565
|
-
if (!isValidHook(options.hook)) {
|
|
17566
|
-
options.stderr(
|
|
17567
|
-
`gji trigger-hook: unknown hook '${options.hook}'. Valid hooks: ${VALID_HOOKS.join(", ")}
|
|
17568
|
-
`
|
|
17569
|
-
);
|
|
17570
|
-
return 1;
|
|
17571
|
-
}
|
|
17572
|
-
const hookName = options.hook;
|
|
17573
|
-
const repository = await detectRepository(options.cwd);
|
|
17574
|
-
const config = await loadEffectiveConfig(
|
|
17575
|
-
repository.repoRoot,
|
|
17576
|
-
void 0,
|
|
17577
|
-
options.stderr
|
|
17578
|
-
);
|
|
17579
|
-
const hooks = extractHooks(config);
|
|
17580
|
-
const worktrees = await listWorktrees(options.cwd);
|
|
17581
|
-
const currentWorktree = worktrees.find(
|
|
17582
|
-
(w2) => w2.path === repository.currentRoot
|
|
17583
|
-
);
|
|
17584
|
-
await runHook(
|
|
17585
|
-
hooks[hookName],
|
|
17586
|
-
repository.currentRoot,
|
|
17587
|
-
{
|
|
17588
|
-
branch: currentWorktree?.branch ?? void 0,
|
|
17589
|
-
path: repository.currentRoot,
|
|
17590
|
-
repo: repository.repoName
|
|
17591
|
-
},
|
|
17592
|
-
options.stderr
|
|
17593
|
-
);
|
|
17594
|
-
return 0;
|
|
17595
|
-
}
|
|
17596
|
-
|
|
17597
17892
|
// src/cli.ts
|
|
17598
17893
|
function createProgram() {
|
|
17599
17894
|
const program2 = new Command();
|
|
@@ -17724,9 +18019,9 @@ function registerCommands(program2) {
|
|
|
17724
18019
|
"--json",
|
|
17725
18020
|
"emit JSON on success or error instead of human-readable output"
|
|
17726
18021
|
).action(notImplemented("remove"));
|
|
17727
|
-
program2.command("
|
|
17728
|
-
"run a named hook (
|
|
17729
|
-
).action(notImplemented("
|
|
18022
|
+
program2.command("run-hook <hook>").alias("trigger-hook").description(
|
|
18023
|
+
"run a named hook (after-create, after-enter, before-remove) in the current worktree"
|
|
18024
|
+
).action(notImplemented("run-hook"));
|
|
17730
18025
|
program2.command("warp [branch]").description("jump to any worktree across all known repos").option("-n, --new [branch]", "create a new worktree in a registered repo").option(
|
|
17731
18026
|
"--print",
|
|
17732
18027
|
"print the resolved worktree path without changing directory"
|
|
@@ -17984,8 +18279,8 @@ function attachCommandActions(program2, options) {
|
|
|
17984
18279
|
}
|
|
17985
18280
|
};
|
|
17986
18281
|
program2.commands.find((command) => command.name() === "remove")?.action(runRemovalCommand);
|
|
17987
|
-
program2.commands.find((command) => command.name() === "
|
|
17988
|
-
const exitCode = await
|
|
18282
|
+
program2.commands.find((command) => command.name() === "run-hook")?.action(async (hook) => {
|
|
18283
|
+
const exitCode = await runHookCommand({
|
|
17989
18284
|
cwd: options.cwd,
|
|
17990
18285
|
hook,
|
|
17991
18286
|
stderr: options.stderr
|