@solaqua/gji 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -120
- package/dist/cli/commands/back.d.ts +1 -1
- package/dist/cli/commands/back.js +1 -0
- package/dist/cli/commands/clean.d.ts +2 -1
- package/dist/cli/commands/clean.js +66 -18
- package/dist/cli/commands/done.js +3 -2
- package/dist/cli/commands/go.js +18 -5
- package/dist/cli/commands/init.js +0 -14
- package/dist/cli/commands/new.d.ts +2 -1
- package/dist/cli/commands/new.js +71 -63
- package/dist/cli/commands/open.js +1 -1
- package/dist/cli/commands/pr-open.js +2 -2
- package/dist/cli/commands/pr.js +17 -6
- package/dist/cli/commands/run-hook.js +4 -0
- package/dist/cli/commands/undo.js +10 -74
- package/dist/cli/dependencies.d.ts +5 -1
- package/dist/cli/dependencies.js +7 -2
- package/dist/cli/program.js +6 -56
- package/dist/cli/runtime/headless.d.ts +3 -3
- package/dist/cli/runtime/headless.js +6 -4
- package/dist/domain/worktree/path-filter.d.ts +13 -0
- package/dist/domain/worktree/path-filter.js +72 -0
- package/dist/domain/worktree/policy.d.ts +1 -0
- package/dist/domain/worktree/policy.js +1 -1
- package/dist/gji-bundle.mjs +738 -969
- package/dist/infrastructure/filesystem/locked-file.d.ts +4 -0
- package/dist/infrastructure/filesystem/locked-file.js +53 -0
- package/dist/infrastructure/git/runner.d.ts +7 -0
- package/dist/infrastructure/git/runner.js +35 -1
- package/dist/infrastructure/persistence/history.js +12 -10
- package/dist/infrastructure/persistence/slots.js +24 -21
- package/dist/infrastructure/process/hooks.js +2 -0
- package/dist/infrastructure/repository/registry.js +11 -41
- package/dist/infrastructure/worktree/lifecycle.d.ts +25 -0
- package/dist/infrastructure/worktree/lifecycle.js +120 -19
- package/dist/ports/git.d.ts +1 -0
- package/dist/ports/hooks.d.ts +7 -0
- package/dist/presentation/shell/completion.js +14 -32
- package/dist/presentation/terminal/context-card.js +1 -1
- package/man/man1/gji-back.1 +1 -1
- package/man/man1/gji-clean.1 +4 -1
- package/man/man1/gji-completion.1 +1 -1
- package/man/man1/gji-config.1 +1 -1
- package/man/man1/gji-doctor.1 +1 -1
- package/man/man1/gji-done.1 +1 -1
- package/man/man1/gji-go.1 +1 -1
- package/man/man1/gji-history.1 +1 -1
- package/man/man1/gji-init.1 +1 -1
- package/man/man1/gji-ls.1 +1 -1
- package/man/man1/gji-new.1 +1 -1
- package/man/man1/gji-open.1 +1 -1
- package/man/man1/gji-pr.1 +1 -1
- package/man/man1/gji-root.1 +1 -1
- package/man/man1/gji-run-hook.1 +3 -3
- package/man/man1/gji-status.1 +1 -1
- package/man/man1/gji-sync-files.1 +1 -1
- package/man/man1/gji-sync.1 +1 -1
- package/man/man1/gji-task.1 +1 -1
- package/man/man1/gji-undo.1 +1 -1
- package/man/man1/gji.1 +2 -12
- package/package.json +3 -2
- package/dist/cli/commands/remove.d.ts +0 -21
- package/dist/cli/commands/remove.js +0 -189
- package/dist/cli/commands/warp.d.ts +0 -28
- package/dist/cli/commands/warp.js +0 -123
- package/man/man1/gji-remove.1 +0 -19
- package/man/man1/gji-warp.1 +0 -16
package/man/man1/gji-back.1
CHANGED
package/man/man1/gji-clean.1
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.TH GJI\-CLEAN 1 "
|
|
1
|
+
.TH GJI\-CLEAN 1 "October 2026" "gji 0.16.0" "User Commands"
|
|
2
2
|
.SH NAME
|
|
3
3
|
gji\-clean \- interactively prune linked worktrees
|
|
4
4
|
.SH SYNOPSIS
|
|
@@ -13,6 +13,9 @@ bypass prompts, force\-remove dirty worktrees, and force\-delete unmerged branch
|
|
|
13
13
|
.B \-\-stale
|
|
14
14
|
only target clean worktrees whose upstream is gone and branch is merged into the default branch
|
|
15
15
|
.TP
|
|
16
|
+
.B \-\-path <pattern>
|
|
17
|
+
only target worktrees under a directory or matching a path glob (repeatable)
|
|
18
|
+
.TP
|
|
16
19
|
.B \-\-dry\-run
|
|
17
20
|
show what would be deleted without removing anything
|
|
18
21
|
.TP
|
package/man/man1/gji-config.1
CHANGED
package/man/man1/gji-doctor.1
CHANGED
package/man/man1/gji-done.1
CHANGED
package/man/man1/gji-go.1
CHANGED
package/man/man1/gji-history.1
CHANGED
package/man/man1/gji-init.1
CHANGED
package/man/man1/gji-ls.1
CHANGED
package/man/man1/gji-new.1
CHANGED
package/man/man1/gji-open.1
CHANGED
package/man/man1/gji-pr.1
CHANGED
package/man/man1/gji-root.1
CHANGED
package/man/man1/gji-run-hook.1
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
.TH GJI\-RUN\-HOOK 1 "
|
|
1
|
+
.TH GJI\-RUN\-HOOK 1 "October 2026" "gji 0.16.0" "User Commands"
|
|
2
2
|
.SH NAME
|
|
3
|
-
gji\-run\-hook \- run a named hook (after\-create, after\-enter, before\-remove) in the current worktree
|
|
3
|
+
gji\-run\-hook \- run a named hook (after\-branch\-create, after\-create, after\-enter, after\-switch, before\-remove) in the current worktree
|
|
4
4
|
.SH SYNOPSIS
|
|
5
5
|
.B gji run\-hook [options] <hook>
|
|
6
6
|
.SH DESCRIPTION
|
|
7
|
-
run a named hook (after\-create, after\-enter, before\-remove) in the current worktree
|
|
7
|
+
run a named hook (after\-branch\-create, after\-create, after\-enter, after\-switch, before\-remove) in the current worktree
|
|
8
8
|
.SH "SEE ALSO"
|
|
9
9
|
.BR gji (1)
|
package/man/man1/gji-status.1
CHANGED
package/man/man1/gji-sync.1
CHANGED
package/man/man1/gji-task.1
CHANGED
package/man/man1/gji-undo.1
CHANGED
package/man/man1/gji.1
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.TH GJI 1 "
|
|
1
|
+
.TH GJI 1 "October 2026" "gji 0.16.0" "User Commands"
|
|
2
2
|
.SH NAME
|
|
3
3
|
gji \- Context switching without the mess.
|
|
4
4
|
.SH SYNOPSIS
|
|
@@ -67,19 +67,11 @@ list active worktrees
|
|
|
67
67
|
.B clean [options]
|
|
68
68
|
interactively prune linked worktrees
|
|
69
69
|
.TP
|
|
70
|
-
.B remove [options] [branch]
|
|
71
|
-
deprecated: use gji done for one worktree or gji clean for bulk cleanup
|
|
72
|
-
.br
|
|
73
|
-
Alias: \fBrm\fR
|
|
74
|
-
.TP
|
|
75
70
|
.B run\-hook [options] <hook>
|
|
76
|
-
run a named hook (after\-create, after\-enter, before\-remove) in the current worktree
|
|
71
|
+
run a named hook (after\-branch\-create, after\-create, after\-enter, after\-switch, before\-remove) in the current worktree
|
|
77
72
|
.br
|
|
78
73
|
Alias: \fBtrigger\-hook\fR
|
|
79
74
|
.TP
|
|
80
|
-
.B warp [options] [branch]
|
|
81
|
-
deprecated: use gji go (press Tab for all known repos)
|
|
82
|
-
.TP
|
|
83
75
|
.B config [options] [command]
|
|
84
76
|
manage global config defaults
|
|
85
77
|
.SH OPTIONS
|
|
@@ -105,7 +97,5 @@ output the version number
|
|
|
105
97
|
.BR gji\-sync\-files (1),
|
|
106
98
|
.BR gji\-ls (1),
|
|
107
99
|
.BR gji\-clean (1),
|
|
108
|
-
.BR gji\-remove (1),
|
|
109
100
|
.BR gji\-run\-hook (1),
|
|
110
|
-
.BR gji\-warp (1),
|
|
111
101
|
.BR gji\-config (1)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solaqua/gji",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Git worktree CLI for
|
|
3
|
+
"version": "0.16.0",
|
|
4
|
+
"description": "Git worktree CLI for switching tasks without disturbing your work.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "sjquant",
|
|
7
7
|
"type": "module",
|
|
@@ -65,6 +65,7 @@
|
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"@biomejs/biome": "^2.4.15",
|
|
67
67
|
"@j178/prek": "^0.3.13",
|
|
68
|
+
"@resvg/resvg-js": "^2.6.2",
|
|
68
69
|
"@types/node": "^24.6.0",
|
|
69
70
|
"@vitest/coverage-v8": "3.2.4",
|
|
70
71
|
"esbuild": "^0.27.0",
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import type { WorktreeEntry } from "../../domain/worktree/types.js";
|
|
2
|
-
import { type WorktreePromptEntry } from "../../presentation/worktree/picker.js";
|
|
3
|
-
import { type CliRuntime } from "../dependencies.js";
|
|
4
|
-
export interface RemoveCommandOptions {
|
|
5
|
-
branch?: string;
|
|
6
|
-
cwd: string;
|
|
7
|
-
dryRun?: boolean;
|
|
8
|
-
force?: boolean;
|
|
9
|
-
json?: boolean;
|
|
10
|
-
runtime?: CliRuntime<"git" | "config" | "configStore" | "slots" | "hooks" | "repositoryContext" | "repositoryRegistry" | "worktrees" | "worktreeLifecycle" | "worktreeCatalog">;
|
|
11
|
-
stderr: (chunk: string) => void;
|
|
12
|
-
stdout: (chunk: string) => void;
|
|
13
|
-
}
|
|
14
|
-
export interface RemoveCommandDependencies {
|
|
15
|
-
confirmForceDeleteBranch: (branch: string) => Promise<boolean>;
|
|
16
|
-
confirmForceRemoveWorktree: (worktreePath: string) => Promise<boolean>;
|
|
17
|
-
confirmRemoval: (worktree: WorktreeEntry) => Promise<boolean>;
|
|
18
|
-
promptForWorktree: (worktrees: WorktreePromptEntry[]) => Promise<string | null>;
|
|
19
|
-
}
|
|
20
|
-
export declare function createRemoveCommand(dependencies?: Partial<RemoveCommandDependencies>): (options: RemoveCommandOptions) => Promise<number>;
|
|
21
|
-
export declare const runRemoveCommand: (options: RemoveCommandOptions) => Promise<number>;
|
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
import { basename } from "node:path";
|
|
2
|
-
import { confirm, isCancel } from "@clack/prompts";
|
|
3
|
-
import { loadLinkedWorktrees } from "../../application/worktree/catalog.js";
|
|
4
|
-
import { writeShellOutput } from "../../presentation/shell/handoff.js";
|
|
5
|
-
import { buildWorktreePromptEntries, promptForSingleWorktree, } from "../../presentation/worktree/picker.js";
|
|
6
|
-
import { defaultConfirmForceDeleteBranch, defaultConfirmForceRemoveWorktree, } from "../../presentation/worktree/prompts.js";
|
|
7
|
-
import { defaultCliDependencies } from "../dependencies.js";
|
|
8
|
-
import { isHeadless } from "../runtime/headless.js";
|
|
9
|
-
import { finalizeUndoOperation, recordUndoOperation } from "./undo.js";
|
|
10
|
-
const REMOVE_OUTPUT_FILE_ENV = "GJI_REMOVE_OUTPUT_FILE";
|
|
11
|
-
export function createRemoveCommand(dependencies = {}) {
|
|
12
|
-
const promptForWorktree = dependencies.promptForWorktree ?? defaultPromptForWorktree;
|
|
13
|
-
const confirmRemoval = dependencies.confirmRemoval ?? defaultConfirmRemoval;
|
|
14
|
-
const confirmForceRemoveWorktree = dependencies.confirmForceRemoveWorktree ??
|
|
15
|
-
defaultConfirmForceRemoveWorktree;
|
|
16
|
-
const confirmForceDeleteBranch = dependencies.confirmForceDeleteBranch ?? defaultConfirmForceDeleteBranch;
|
|
17
|
-
return async function runRemoveCommand(options) {
|
|
18
|
-
const runtime = options.runtime ?? defaultCliDependencies;
|
|
19
|
-
const { loadEffectiveConfig } = runtime.config;
|
|
20
|
-
const { releaseWorktreeSlot } = runtime.slots;
|
|
21
|
-
const { extractHooks, runHook } = runtime.hooks;
|
|
22
|
-
const sourceDependencies = {
|
|
23
|
-
...runtime.repositoryContext,
|
|
24
|
-
...runtime.repositoryRegistry,
|
|
25
|
-
...runtime.worktrees,
|
|
26
|
-
};
|
|
27
|
-
const { deleteBranch, forceDeleteBranch, forceRemoveWorktree, isBranchUnmergedError, isWorktreeForceRemovalError, removeWorktree, } = runtime.worktreeLifecycle;
|
|
28
|
-
const { linkedWorktrees, repository } = await loadLinkedWorktrees(options.cwd, sourceDependencies);
|
|
29
|
-
if (linkedWorktrees.length === 0) {
|
|
30
|
-
emitError(options, "No linked worktrees to finish");
|
|
31
|
-
return 1;
|
|
32
|
-
}
|
|
33
|
-
if (!options.branch && (options.json || isHeadless())) {
|
|
34
|
-
const message = "branch argument is required";
|
|
35
|
-
if (options.json) {
|
|
36
|
-
emitError(options, message);
|
|
37
|
-
}
|
|
38
|
-
else {
|
|
39
|
-
options.stderr(`gji remove: ${message} in non-interactive mode (GJI_NO_TUI=1)\n`);
|
|
40
|
-
}
|
|
41
|
-
return 1;
|
|
42
|
-
}
|
|
43
|
-
const selection = options.branch ??
|
|
44
|
-
(await promptForWorktree(await buildWorktreePromptEntries(linkedWorktrees.map((worktree) => ({
|
|
45
|
-
repoName: repository.repoName,
|
|
46
|
-
worktree,
|
|
47
|
-
})), { catalog: runtime.worktreeCatalog })));
|
|
48
|
-
if (!selection) {
|
|
49
|
-
options.stderr("Aborted\n");
|
|
50
|
-
return 1;
|
|
51
|
-
}
|
|
52
|
-
const worktree = linkedWorktrees.find((entry) => entry.branch === selection || entry.path === selection);
|
|
53
|
-
if (!worktree) {
|
|
54
|
-
emitError(options, `No linked worktree found for branch: ${selection}`);
|
|
55
|
-
return 1;
|
|
56
|
-
}
|
|
57
|
-
if (!options.dryRun && !options.force && (options.json || isHeadless())) {
|
|
58
|
-
const message = "--force is required";
|
|
59
|
-
if (options.json) {
|
|
60
|
-
emitError(options, message);
|
|
61
|
-
}
|
|
62
|
-
else {
|
|
63
|
-
options.stderr(`gji remove: ${message} in non-interactive mode (GJI_NO_TUI=1)\n`);
|
|
64
|
-
}
|
|
65
|
-
return 1;
|
|
66
|
-
}
|
|
67
|
-
if (!options.dryRun &&
|
|
68
|
-
!options.force &&
|
|
69
|
-
!(await confirmRemoval(worktree))) {
|
|
70
|
-
options.stderr("Aborted\n");
|
|
71
|
-
return 1;
|
|
72
|
-
}
|
|
73
|
-
if (options.dryRun) {
|
|
74
|
-
if (options.json) {
|
|
75
|
-
options.stdout(`${JSON.stringify({ branch: worktree.branch, path: worktree.path, dryRun: true }, null, 2)}\n`);
|
|
76
|
-
}
|
|
77
|
-
else {
|
|
78
|
-
const desc = worktree.branch
|
|
79
|
-
? `branch: ${worktree.branch}`
|
|
80
|
-
: "detached";
|
|
81
|
-
options.stdout(`Would remove worktree at ${worktree.path} (${desc})\n`);
|
|
82
|
-
}
|
|
83
|
-
return 0;
|
|
84
|
-
}
|
|
85
|
-
const config = await loadEffectiveConfig(repository.repoRoot, undefined, options.stderr);
|
|
86
|
-
const hooks = extractHooks(config);
|
|
87
|
-
let journal;
|
|
88
|
-
try {
|
|
89
|
-
journal = await recordUndoOperation("remove", repository.repoRoot, [worktree], undefined, runtime);
|
|
90
|
-
}
|
|
91
|
-
catch (error) {
|
|
92
|
-
emitError(options, `could not write undo journal; no worktree was removed: ${toMessage(error)}`);
|
|
93
|
-
return 1;
|
|
94
|
-
}
|
|
95
|
-
if (!journal) {
|
|
96
|
-
emitError(options, "could not capture undo state; no worktree was removed");
|
|
97
|
-
return 1;
|
|
98
|
-
}
|
|
99
|
-
await runHook(hooks["before-remove"], worktree.path, {
|
|
100
|
-
branch: worktree.branch ?? undefined,
|
|
101
|
-
path: worktree.path,
|
|
102
|
-
repo: basename(repository.repoRoot),
|
|
103
|
-
}, options.stderr);
|
|
104
|
-
try {
|
|
105
|
-
await removeWorktree(repository.repoRoot, worktree.path);
|
|
106
|
-
}
|
|
107
|
-
catch (error) {
|
|
108
|
-
if (!isWorktreeForceRemovalError(error)) {
|
|
109
|
-
await finalizeUndoOperation(journal, [], undefined, runtime.configStore.GLOBAL_CONFIG_DIRECTORY);
|
|
110
|
-
throw error;
|
|
111
|
-
}
|
|
112
|
-
if (!options.force &&
|
|
113
|
-
!(await confirmForceRemoveWorktree(worktree.path))) {
|
|
114
|
-
await finalizeUndoOperation(journal, [], undefined, runtime.configStore.GLOBAL_CONFIG_DIRECTORY);
|
|
115
|
-
options.stderr("Aborted\n");
|
|
116
|
-
return 1;
|
|
117
|
-
}
|
|
118
|
-
try {
|
|
119
|
-
await forceRemoveWorktree(repository.repoRoot, worktree.path);
|
|
120
|
-
}
|
|
121
|
-
catch (forceError) {
|
|
122
|
-
await finalizeUndoOperation(journal, [], undefined, runtime.configStore.GLOBAL_CONFIG_DIRECTORY);
|
|
123
|
-
emitError(options, `Failed to remove worktree at ${worktree.path}: ${toMessage(forceError)}`);
|
|
124
|
-
return 1;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
await finalizeUndoOperation(journal, [worktree], undefined, runtime.configStore.GLOBAL_CONFIG_DIRECTORY);
|
|
128
|
-
await releaseWorktreeSlot(worktree.path);
|
|
129
|
-
if (worktree.branch) {
|
|
130
|
-
try {
|
|
131
|
-
await deleteBranch(repository.repoRoot, worktree.branch);
|
|
132
|
-
}
|
|
133
|
-
catch (error) {
|
|
134
|
-
if (!isBranchUnmergedError(error)) {
|
|
135
|
-
throw error;
|
|
136
|
-
}
|
|
137
|
-
if (options.force ||
|
|
138
|
-
(await confirmForceDeleteBranch(worktree.branch))) {
|
|
139
|
-
try {
|
|
140
|
-
await forceDeleteBranch(repository.repoRoot, worktree.branch);
|
|
141
|
-
}
|
|
142
|
-
catch (forceError) {
|
|
143
|
-
options.stderr(`Failed to delete branch ${worktree.branch}: ${toMessage(forceError)}\n`);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
else {
|
|
147
|
-
options.stderr(`Branch ${worktree.branch} was not deleted (has unmerged commits)\n`);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
if (options.json) {
|
|
152
|
-
options.stdout(`${JSON.stringify({ branch: worktree.branch, path: worktree.path, deleted: true }, null, 2)}\n`);
|
|
153
|
-
}
|
|
154
|
-
else {
|
|
155
|
-
options.stderr("undo: gji undo\n");
|
|
156
|
-
await writeOutput(repository.repoRoot, options.stdout);
|
|
157
|
-
}
|
|
158
|
-
return 0;
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
export const runRemoveCommand = createRemoveCommand();
|
|
162
|
-
async function defaultPromptForWorktree(worktrees) {
|
|
163
|
-
return promptForSingleWorktree("Choose a worktree to finish", worktrees);
|
|
164
|
-
}
|
|
165
|
-
async function defaultConfirmRemoval(worktree) {
|
|
166
|
-
const choice = await confirm({
|
|
167
|
-
message: worktree.branch
|
|
168
|
-
? `Remove worktree and delete branch ${worktree.branch}?`
|
|
169
|
-
: `Remove detached worktree ${worktree.path}?`,
|
|
170
|
-
active: "Yes",
|
|
171
|
-
inactive: "No",
|
|
172
|
-
initialValue: true,
|
|
173
|
-
});
|
|
174
|
-
return !isCancel(choice) && choice;
|
|
175
|
-
}
|
|
176
|
-
async function writeOutput(repoRoot, stdout) {
|
|
177
|
-
await writeShellOutput(REMOVE_OUTPUT_FILE_ENV, repoRoot, stdout);
|
|
178
|
-
}
|
|
179
|
-
function emitError(options, message) {
|
|
180
|
-
if (options.json) {
|
|
181
|
-
options.stderr(`${JSON.stringify({ error: message }, null, 2)}\n`);
|
|
182
|
-
}
|
|
183
|
-
else {
|
|
184
|
-
options.stderr(`${message}\n`);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
function toMessage(error) {
|
|
188
|
-
return error instanceof Error ? error.message : String(error);
|
|
189
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { type NavigationRepository } from "../../presentation/terminal/navigation.js";
|
|
2
|
-
import { type QueryWorktreePullRequests } from "../../presentation/worktree/picker.js";
|
|
3
|
-
import { type CliRuntime } from "../dependencies.js";
|
|
4
|
-
export interface WarpCommandOptions {
|
|
5
|
-
branch?: string;
|
|
6
|
-
cwd: string;
|
|
7
|
-
json?: boolean;
|
|
8
|
-
queryPullRequests?: QueryWorktreePullRequests;
|
|
9
|
-
runtime?: CliRuntime<"history" | "repositoryContext" | "repositoryRegistry" | "worktrees" | "worktreeCatalog">;
|
|
10
|
-
stderr: (chunk: string) => void;
|
|
11
|
-
stdout: (chunk: string) => void;
|
|
12
|
-
}
|
|
13
|
-
export declare function runWarpCommand(options: WarpCommandOptions): Promise<number>;
|
|
14
|
-
export interface WarpTarget {
|
|
15
|
-
branch: string | null;
|
|
16
|
-
path: string;
|
|
17
|
-
repository: NavigationRepository;
|
|
18
|
-
}
|
|
19
|
-
export declare function resolveWarpTarget(options: {
|
|
20
|
-
branch?: string;
|
|
21
|
-
commandName?: string;
|
|
22
|
-
cwd: string;
|
|
23
|
-
excludeRepoRoot?: string;
|
|
24
|
-
json?: boolean;
|
|
25
|
-
queryPullRequests?: QueryWorktreePullRequests;
|
|
26
|
-
runtime?: CliRuntime<"history" | "repositoryContext" | "repositoryRegistry" | "worktrees" | "worktreeCatalog">;
|
|
27
|
-
stderr: (chunk: string) => void;
|
|
28
|
-
}): Promise<WarpTarget | null>;
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import { listRegisteredWorktreeSources } from "../../application/worktree/sources.js";
|
|
2
|
-
import { resolveWorktreeQuery } from "../../domain/worktree/matching.js";
|
|
3
|
-
import { writeShellOutput } from "../../presentation/shell/handoff.js";
|
|
4
|
-
import { createNavigationRepository, createNavigationTarget, } from "../../presentation/terminal/navigation.js";
|
|
5
|
-
import { buildWorktreePromptEntries, promptForSingleWorktree, } from "../../presentation/worktree/picker.js";
|
|
6
|
-
import { defaultCliDependencies, withPullRequestQueries, } from "../dependencies.js";
|
|
7
|
-
import { isHeadless } from "../runtime/headless.js";
|
|
8
|
-
const WARP_OUTPUT_FILE_ENV = "GJI_WARP_OUTPUT_FILE";
|
|
9
|
-
export async function runWarpCommand(options) {
|
|
10
|
-
return runWarpNavigate(options);
|
|
11
|
-
}
|
|
12
|
-
async function runWarpNavigate(options) {
|
|
13
|
-
const runtime = options.runtime ?? defaultCliDependencies;
|
|
14
|
-
const { recordWorktreeUsage } = runtime.history;
|
|
15
|
-
if ((isHeadless() || options.json) && !options.branch) {
|
|
16
|
-
const message = "branch argument is required";
|
|
17
|
-
if (options.json) {
|
|
18
|
-
options.stderr(`${JSON.stringify({ error: message }, null, 2)}\n`);
|
|
19
|
-
}
|
|
20
|
-
else {
|
|
21
|
-
options.stderr("gji warp: branch argument is required in non-interactive mode (GJI_NO_TUI=1)\n");
|
|
22
|
-
}
|
|
23
|
-
return 1;
|
|
24
|
-
}
|
|
25
|
-
const target = await resolveWarpTarget({
|
|
26
|
-
...options,
|
|
27
|
-
commandName: "gji warp",
|
|
28
|
-
json: options.json,
|
|
29
|
-
queryPullRequests: options.queryPullRequests,
|
|
30
|
-
runtime,
|
|
31
|
-
});
|
|
32
|
-
if (!target)
|
|
33
|
-
return 1;
|
|
34
|
-
if (options.json) {
|
|
35
|
-
// json callers use the output programmatically; skip history and shell handoff.
|
|
36
|
-
options.stdout(`${JSON.stringify(createNavigationTarget(target.repository, target.path, target.branch), null, 2)}\n`);
|
|
37
|
-
return 0;
|
|
38
|
-
}
|
|
39
|
-
await recordWorktreeUsage(target.path, target.branch);
|
|
40
|
-
await writeShellOutput(WARP_OUTPUT_FILE_ENV, target.path, options.stdout);
|
|
41
|
-
return 0;
|
|
42
|
-
}
|
|
43
|
-
export async function resolveWarpTarget(options) {
|
|
44
|
-
const cmd = options.commandName ?? "gji";
|
|
45
|
-
const runtime = options.runtime ?? defaultCliDependencies;
|
|
46
|
-
const { detectRepository } = runtime.repositoryContext;
|
|
47
|
-
const { loadRegistry } = runtime.repositoryRegistry;
|
|
48
|
-
const sourceDependencies = {
|
|
49
|
-
...runtime.repositoryContext,
|
|
50
|
-
...runtime.repositoryRegistry,
|
|
51
|
-
...runtime.worktrees,
|
|
52
|
-
};
|
|
53
|
-
const emitError = (message, hint) => {
|
|
54
|
-
if (options.json) {
|
|
55
|
-
options.stderr(`${JSON.stringify({ error: message }, null, 2)}\n`);
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
58
|
-
options.stderr(`${cmd}: ${message}\n`);
|
|
59
|
-
if (hint)
|
|
60
|
-
options.stderr(hint);
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
const registry = await loadRegistry();
|
|
64
|
-
if (registry.length === 0) {
|
|
65
|
-
emitError("not in a git repository and no repos registered yet.", "Use any gji command inside a repository to register it.\n");
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
const registered = await listRegisteredWorktreeSources({
|
|
69
|
-
cwd: options.cwd,
|
|
70
|
-
repositoryPort: sourceDependencies,
|
|
71
|
-
});
|
|
72
|
-
const skippedRegisteredRepos = registered.skipped.length;
|
|
73
|
-
const allItems = registered.sources.filter((item) => item.repoRoot !== options.excludeRepoRoot);
|
|
74
|
-
if (allItems.length === 0) {
|
|
75
|
-
emitError("no accessible worktrees found in any registered repo.", skippedRegisteredRepos > 0
|
|
76
|
-
? "Hint: Run 'gji doctor' to inspect stale repository entries.\n"
|
|
77
|
-
: undefined);
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
const promptSources = allItems.map((item) => ({
|
|
81
|
-
repoRoot: item.repoRoot,
|
|
82
|
-
repoName: item.repoName,
|
|
83
|
-
worktree: item.worktree,
|
|
84
|
-
}));
|
|
85
|
-
if (options.branch) {
|
|
86
|
-
const match = resolveWorktreeQuery(promptSources, options.branch);
|
|
87
|
-
if (!match) {
|
|
88
|
-
emitError(`no worktree found matching: ${options.branch}`, skippedRegisteredRepos > 0
|
|
89
|
-
? "Hint: Run 'gji doctor' to inspect stale repository entries.\n"
|
|
90
|
-
: undefined);
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
return {
|
|
94
|
-
branch: match.worktree.branch,
|
|
95
|
-
path: match.worktree.path,
|
|
96
|
-
repository: createNavigationRepository(match.repoName, match.repoRoot ??
|
|
97
|
-
(await detectRepository(match.worktree.path)).repoRoot),
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
const promptEntries = await buildWorktreePromptEntries(promptSources, {
|
|
101
|
-
metadata: "fast",
|
|
102
|
-
catalog: withPullRequestQueries(runtime, options.queryPullRequests),
|
|
103
|
-
});
|
|
104
|
-
const path = await promptForWarpTarget(promptEntries);
|
|
105
|
-
if (!path) {
|
|
106
|
-
options.stderr("Aborted\n");
|
|
107
|
-
return null;
|
|
108
|
-
}
|
|
109
|
-
const chosenSource = promptSources.find((item) => item.worktree.path === path);
|
|
110
|
-
if (chosenSource === undefined) {
|
|
111
|
-
emitError("selected worktree is no longer registered");
|
|
112
|
-
return null;
|
|
113
|
-
}
|
|
114
|
-
return {
|
|
115
|
-
branch: chosenSource.worktree.branch,
|
|
116
|
-
path,
|
|
117
|
-
repository: createNavigationRepository(chosenSource.repoName, chosenSource.repoRoot ??
|
|
118
|
-
(await detectRepository(chosenSource.worktree.path)).repoRoot),
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
async function promptForWarpTarget(items) {
|
|
122
|
-
return promptForSingleWorktree("Warp to a worktree", items);
|
|
123
|
-
}
|
package/man/man1/gji-remove.1
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
.TH GJI\-REMOVE 1 "August 2026" "gji 0.14.0" "User Commands"
|
|
2
|
-
.SH NAME
|
|
3
|
-
gji\-remove \- deprecated: use gji done for one worktree or gji clean for bulk cleanup
|
|
4
|
-
.SH SYNOPSIS
|
|
5
|
-
.B gji remove [\fIoptions\fR] [options] [branch]
|
|
6
|
-
.SH DESCRIPTION
|
|
7
|
-
deprecated: use gji done for one worktree or gji clean for bulk cleanup
|
|
8
|
-
.SH OPTIONS
|
|
9
|
-
.TP
|
|
10
|
-
.B \-f, \-\-force
|
|
11
|
-
bypass prompts, force\-remove a dirty worktree, and force\-delete an unmerged branch
|
|
12
|
-
.TP
|
|
13
|
-
.B \-\-dry\-run
|
|
14
|
-
show what would be deleted without removing anything
|
|
15
|
-
.TP
|
|
16
|
-
.B \-\-json
|
|
17
|
-
emit JSON on success or error instead of human\-readable output
|
|
18
|
-
.SH "SEE ALSO"
|
|
19
|
-
.BR gji (1)
|
package/man/man1/gji-warp.1
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
.TH GJI\-WARP 1 "August 2026" "gji 0.14.0" "User Commands"
|
|
2
|
-
.SH NAME
|
|
3
|
-
gji\-warp \- deprecated: use gji go (press Tab for all known repos)
|
|
4
|
-
.SH SYNOPSIS
|
|
5
|
-
.B gji warp [\fIoptions\fR] [options] [branch]
|
|
6
|
-
.SH DESCRIPTION
|
|
7
|
-
deprecated: use gji go (press Tab for all known repos)
|
|
8
|
-
.SH OPTIONS
|
|
9
|
-
.TP
|
|
10
|
-
.B \-\-print
|
|
11
|
-
print the resolved worktree path without changing directory
|
|
12
|
-
.TP
|
|
13
|
-
.B \-\-json
|
|
14
|
-
emit JSON on success or error instead of human\-readable output
|
|
15
|
-
.SH "SEE ALSO"
|
|
16
|
-
.BR gji (1)
|