@solaqua/gji 0.5.0 → 0.6.1
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 +38 -4
- package/dist/cli.js +64 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.js +2 -1
- package/dist/editor.d.ts +8 -0
- package/dist/editor.js +17 -0
- package/dist/gji-bundle.mjs +1574 -994
- package/dist/go.js +22 -4
- package/dist/hooks.d.ts +5 -4
- package/dist/hooks.js +61 -9
- package/dist/init.js +26 -11
- package/dist/install-prompt.js +9 -1
- package/dist/new.d.ts +3 -0
- package/dist/new.js +33 -1
- package/dist/open.d.ts +20 -0
- package/dist/open.js +155 -0
- package/dist/repo-registry.d.ts +8 -0
- package/dist/repo-registry.js +52 -0
- package/dist/shell-completion.js +124 -89
- package/dist/warp.d.ts +20 -0
- package/dist/warp.js +196 -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 +7 -1
- package/man/man1/gji-open.1 +19 -0
- package/man/man1/gji-pr.1 +1 -1
- package/man/man1/gji-remove.1 +1 -1
- package/man/man1/gji-root.1 +1 -1
- package/man/man1/gji-status.1 +1 -1
- package/man/man1/gji-sync.1 +1 -1
- package/man/man1/gji-trigger-hook.1 +1 -1
- package/man/man1/gji-warp.1 +19 -0
- package/man/man1/gji.1 +11 -1
- package/package.json +4 -2
package/dist/shell-completion.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import { KNOWN_GLOBAL_CONFIG_KEYS } from './config.js';
|
|
1
2
|
const TOP_LEVEL_COMMANDS = [
|
|
2
3
|
{ name: 'new', description: 'create a new branch or detached linked worktree' },
|
|
3
4
|
{ name: 'init', description: 'print or install shell integration' },
|
|
4
5
|
{ name: 'completion', description: 'print shell completion definitions' },
|
|
5
6
|
{ name: 'pr', description: 'fetch a pull request into a linked worktree' },
|
|
7
|
+
{ name: 'back', description: 'navigate to the previously visited worktree' },
|
|
8
|
+
{ name: 'history', description: 'show navigation history' },
|
|
9
|
+
{ name: 'open', description: 'open the worktree in an editor' },
|
|
6
10
|
{ name: 'go', description: 'print or select a worktree path' },
|
|
11
|
+
{ name: 'jump', description: 'alias of go' },
|
|
7
12
|
{ name: 'root', description: 'print the main repository root path' },
|
|
8
13
|
{ name: 'status', description: 'summarize repository and worktree health' },
|
|
9
14
|
{ name: 'sync', description: 'fetch and update one or all worktrees' },
|
|
@@ -12,20 +17,12 @@ const TOP_LEVEL_COMMANDS = [
|
|
|
12
17
|
{ name: 'remove', description: 'remove a linked worktree and delete its branch when present' },
|
|
13
18
|
{ name: 'rm', description: 'alias of remove' },
|
|
14
19
|
{ name: 'trigger-hook', description: 'run a named hook in the current worktree' },
|
|
20
|
+
{ name: 'warp', description: 'jump to any worktree across all known repos' },
|
|
15
21
|
{ name: 'config', description: 'manage global config defaults' },
|
|
16
22
|
];
|
|
17
23
|
const SHELL_NAMES = ['bash', 'fish', 'zsh'];
|
|
18
24
|
const HOOK_NAMES = ['afterCreate', 'afterEnter', 'beforeRemove'];
|
|
19
|
-
const CONFIG_KEYS =
|
|
20
|
-
'branchPrefix',
|
|
21
|
-
'syncRemote',
|
|
22
|
-
'syncDefaultBranch',
|
|
23
|
-
'syncFiles',
|
|
24
|
-
'skipInstallPrompt',
|
|
25
|
-
'installSaveTarget',
|
|
26
|
-
'hooks',
|
|
27
|
-
'repos',
|
|
28
|
-
];
|
|
25
|
+
const CONFIG_KEYS = Array.from(KNOWN_GLOBAL_CONFIG_KEYS);
|
|
29
26
|
export function renderShellCompletion(shell) {
|
|
30
27
|
switch (shell) {
|
|
31
28
|
case 'bash':
|
|
@@ -59,7 +56,7 @@ _gji_completion() {
|
|
|
59
56
|
|
|
60
57
|
case "$command_name" in
|
|
61
58
|
new)
|
|
62
|
-
COMPREPLY=( $(compgen -W "--detached --dry-run --json --help" -- "$cur") )
|
|
59
|
+
COMPREPLY=( $(compgen -W "--detached --force --open --editor --dry-run --json --help" -- "$cur") )
|
|
63
60
|
;;
|
|
64
61
|
init)
|
|
65
62
|
COMPREPLY=( $(compgen -W "${shells} --write --help" -- "$cur") )
|
|
@@ -70,7 +67,16 @@ _gji_completion() {
|
|
|
70
67
|
pr)
|
|
71
68
|
COMPREPLY=( $(compgen -W "--dry-run --json --help" -- "$cur") )
|
|
72
69
|
;;
|
|
73
|
-
|
|
70
|
+
back)
|
|
71
|
+
COMPREPLY=( $(compgen -W "--print --help" -- "$cur") )
|
|
72
|
+
;;
|
|
73
|
+
history)
|
|
74
|
+
COMPREPLY=( $(compgen -W "--json --help" -- "$cur") )
|
|
75
|
+
;;
|
|
76
|
+
open)
|
|
77
|
+
COMPREPLY=( $(compgen -W "$(__gji_worktree_branches) --editor --save --workspace --help" -- "$cur") )
|
|
78
|
+
;;
|
|
79
|
+
go|jump)
|
|
74
80
|
COMPREPLY=( $(compgen -W "$(__gji_worktree_branches) --print --help" -- "$cur") )
|
|
75
81
|
;;
|
|
76
82
|
root)
|
|
@@ -94,6 +100,9 @@ _gji_completion() {
|
|
|
94
100
|
trigger-hook)
|
|
95
101
|
COMPREPLY=( $(compgen -W "${hooks} --help" -- "$cur") )
|
|
96
102
|
;;
|
|
103
|
+
warp)
|
|
104
|
+
COMPREPLY=( $(compgen -W "-n --new --print --json --help" -- "$cur") )
|
|
105
|
+
;;
|
|
97
106
|
config)
|
|
98
107
|
if [ "$COMP_CWORD" -eq 2 ]; then
|
|
99
108
|
COMPREPLY=( $(compgen -W "get set unset" -- "$cur") )
|
|
@@ -149,6 +158,9 @@ complete -c gji -f
|
|
|
149
158
|
${commandLines}
|
|
150
159
|
|
|
151
160
|
complete -c gji -n '__fish_seen_subcommand_from new' -l detached -d 'create a detached worktree without a branch'
|
|
161
|
+
complete -c gji -n '__fish_seen_subcommand_from new' -l force -d 'remove and recreate the worktree if the target path already exists'
|
|
162
|
+
complete -c gji -n '__fish_seen_subcommand_from new' -l open -d 'open the new worktree in an editor after creation'
|
|
163
|
+
complete -c gji -n '__fish_seen_subcommand_from new' -l editor -r -d 'editor CLI to use with --open (code, cursor, zed, …)'
|
|
152
164
|
complete -c gji -n '__fish_seen_subcommand_from new' -l dry-run -d 'show what would be created without executing any git commands or writing files'
|
|
153
165
|
complete -c gji -n '__fish_seen_subcommand_from new' -l json -d 'emit JSON on success or error instead of human-readable output'
|
|
154
166
|
|
|
@@ -162,8 +174,17 @@ complete -c gji -n '__fish_seen_subcommand_from completion' -a 'zsh' -d 'shell'
|
|
|
162
174
|
complete -c gji -n '__fish_seen_subcommand_from pr' -l dry-run -d 'show what would be created without executing any git commands or writing files'
|
|
163
175
|
complete -c gji -n '__fish_seen_subcommand_from pr' -l json -d 'emit JSON on success or error instead of human-readable output'
|
|
164
176
|
|
|
165
|
-
complete -c gji -n '__fish_seen_subcommand_from
|
|
166
|
-
|
|
177
|
+
complete -c gji -n '__fish_seen_subcommand_from back' -l print -d 'print the resolved worktree path explicitly'
|
|
178
|
+
|
|
179
|
+
complete -c gji -n '__fish_seen_subcommand_from history' -l json -d 'print history as JSON'
|
|
180
|
+
|
|
181
|
+
complete -c gji -n '__fish_seen_subcommand_from open' -l editor -r -d 'editor CLI to use (code, cursor, zed, windsurf, subl, …)'
|
|
182
|
+
complete -c gji -n '__fish_seen_subcommand_from open' -l save -d 'save the chosen editor to global config'
|
|
183
|
+
complete -c gji -n '__fish_seen_subcommand_from open' -l workspace -d 'generate a .code-workspace file before opening (VS Code / Cursor / Windsurf)'
|
|
184
|
+
complete -c gji -n '__fish_seen_subcommand_from open' -a '(__gji_worktree_branches)' -d 'worktree branch'
|
|
185
|
+
|
|
186
|
+
complete -c gji -n '__fish_seen_subcommand_from go jump' -l print -d 'print the resolved worktree path explicitly'
|
|
187
|
+
complete -c gji -n '__fish_seen_subcommand_from go jump' -a '(__gji_worktree_branches)' -d 'worktree branch'
|
|
167
188
|
|
|
168
189
|
complete -c gji -n '__fish_seen_subcommand_from root' -l print -d 'print the resolved repository root path explicitly'
|
|
169
190
|
|
|
@@ -187,6 +208,10 @@ complete -c gji -n '__fish_seen_subcommand_from remove rm' -a '(__gji_worktree_b
|
|
|
187
208
|
|
|
188
209
|
${hookLines}
|
|
189
210
|
|
|
211
|
+
complete -c gji -n '__fish_seen_subcommand_from warp' -s n -l new -d 'create a new worktree in a registered repo'
|
|
212
|
+
complete -c gji -n '__fish_seen_subcommand_from warp' -l print -d 'print the resolved worktree path without changing directory'
|
|
213
|
+
complete -c gji -n '__fish_seen_subcommand_from warp' -l json -d 'emit JSON on success or error instead of human-readable output'
|
|
214
|
+
|
|
190
215
|
complete -c gji -n '__fish_seen_subcommand_from config; and __gji_should_complete_config_action' -a 'get set unset' -d 'config action'
|
|
191
216
|
${configKeyLines}`;
|
|
192
217
|
}
|
|
@@ -195,89 +220,99 @@ function renderZshCompletion() {
|
|
|
195
220
|
const configKeys = CONFIG_KEYS.join(' ');
|
|
196
221
|
const shells = SHELL_NAMES.join(' ');
|
|
197
222
|
const hooks = HOOK_NAMES.join(' ');
|
|
198
|
-
return
|
|
223
|
+
return `#compdef gji
|
|
224
|
+
|
|
225
|
+
__gji_worktree_branches() {
|
|
199
226
|
command gji ls --compact 2>/dev/null | awk 'NR > 1 { branch = ($1 == "*" ? $2 : $1); if (branch != "(detached)") print branch }'
|
|
200
227
|
}
|
|
201
228
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
local -a commands worktree_branches
|
|
205
|
-
|
|
206
|
-
commands=(
|
|
207
|
-
${commandLines}
|
|
208
|
-
)
|
|
229
|
+
local context state line
|
|
230
|
+
local -a commands worktree_branches
|
|
209
231
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
fi
|
|
232
|
+
commands=(
|
|
233
|
+
${commandLines}
|
|
234
|
+
)
|
|
214
235
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
init)
|
|
220
|
-
_arguments '--write[write the integration to the shell config file]' '2:shell:(${shells})'
|
|
221
|
-
;;
|
|
222
|
-
completion)
|
|
223
|
-
_arguments '2:shell:(${shells})'
|
|
224
|
-
;;
|
|
225
|
-
pr)
|
|
226
|
-
_arguments '--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:ref: '
|
|
227
|
-
;;
|
|
228
|
-
go)
|
|
229
|
-
_arguments '--print[print the resolved worktree path explicitly]' '2:branch:->worktrees'
|
|
230
|
-
;;
|
|
231
|
-
root)
|
|
232
|
-
_arguments '--print[print the resolved repository root path explicitly]'
|
|
233
|
-
;;
|
|
234
|
-
status)
|
|
235
|
-
_arguments '--json[print repository and worktree health as JSON]'
|
|
236
|
-
;;
|
|
237
|
-
sync)
|
|
238
|
-
_arguments '--all[sync every worktree in the repository]' '--json[emit JSON on success or error instead of human-readable output]'
|
|
239
|
-
;;
|
|
240
|
-
ls)
|
|
241
|
-
_arguments '--compact[show only branch and path columns]' '--json[print active worktrees as JSON]'
|
|
242
|
-
;;
|
|
243
|
-
clean)
|
|
244
|
-
_arguments '(-f --force)'{-f,--force}'[bypass prompts, force-remove dirty worktrees, and force-delete unmerged branches]' '--stale[only target clean worktrees whose upstream is gone and branch is merged into the default branch]' '--dry-run[show what would be deleted without removing anything]' '--json[emit JSON on success or error instead of human-readable output]'
|
|
245
|
-
;;
|
|
246
|
-
remove|rm)
|
|
247
|
-
_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'
|
|
248
|
-
;;
|
|
249
|
-
trigger-hook)
|
|
250
|
-
_arguments "2:hook:(${hooks})"
|
|
251
|
-
;;
|
|
252
|
-
config)
|
|
253
|
-
if (( CURRENT == 3 )); then
|
|
254
|
-
_values 'config action' get set unset
|
|
255
|
-
return
|
|
256
|
-
fi
|
|
236
|
+
if (( CURRENT == 2 )); then
|
|
237
|
+
_describe 'command' commands
|
|
238
|
+
return
|
|
239
|
+
fi
|
|
257
240
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
241
|
+
case "\${words[2]}" in
|
|
242
|
+
new)
|
|
243
|
+
_arguments '--detached[create a detached worktree without a branch]' '--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, …)]: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: '
|
|
244
|
+
;;
|
|
245
|
+
init)
|
|
246
|
+
_arguments '--write[write the integration to the shell config file]' '2:shell:(${shells})'
|
|
247
|
+
;;
|
|
248
|
+
completion)
|
|
249
|
+
_arguments '2:shell:(${shells})'
|
|
250
|
+
;;
|
|
251
|
+
pr)
|
|
252
|
+
_arguments '--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:ref: '
|
|
253
|
+
;;
|
|
254
|
+
back)
|
|
255
|
+
_arguments '--print[print the resolved worktree path explicitly]' '2:steps: '
|
|
256
|
+
;;
|
|
257
|
+
history)
|
|
258
|
+
_arguments '--json[print history as JSON]'
|
|
259
|
+
;;
|
|
260
|
+
open)
|
|
261
|
+
_arguments '--editor[editor CLI to use (code, cursor, zed, windsurf, subl, …)]:editor:' '--save[save the chosen editor to global config]' '--workspace[generate a .code-workspace file before opening (VS Code / Cursor / Windsurf)]' '2:branch:->worktrees'
|
|
262
|
+
;;
|
|
263
|
+
go|jump)
|
|
264
|
+
_arguments '--print[print the resolved worktree path explicitly]' '2:branch:->worktrees'
|
|
265
|
+
;;
|
|
266
|
+
root)
|
|
267
|
+
_arguments '--print[print the resolved repository root path explicitly]'
|
|
268
|
+
;;
|
|
269
|
+
status)
|
|
270
|
+
_arguments '--json[print repository and worktree health as JSON]'
|
|
271
|
+
;;
|
|
272
|
+
sync)
|
|
273
|
+
_arguments '--all[sync every worktree in the repository]' '--json[emit JSON on success or error instead of human-readable output]'
|
|
274
|
+
;;
|
|
275
|
+
ls)
|
|
276
|
+
_arguments '--compact[show only branch and path columns]' '--json[print active worktrees as JSON]'
|
|
277
|
+
;;
|
|
278
|
+
clean)
|
|
279
|
+
_arguments '(-f --force)'{-f,--force}'[bypass prompts, force-remove dirty worktrees, and force-delete unmerged branches]' '--stale[only target clean worktrees whose upstream is gone and branch is merged into the default branch]' '--dry-run[show what would be deleted without removing anything]' '--json[emit JSON on success or error instead of human-readable output]'
|
|
280
|
+
;;
|
|
281
|
+
remove|rm)
|
|
282
|
+
_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'
|
|
283
|
+
;;
|
|
284
|
+
trigger-hook)
|
|
285
|
+
_arguments "2:hook:(${hooks})"
|
|
286
|
+
;;
|
|
287
|
+
warp)
|
|
288
|
+
_arguments '(-n --new)'{-n,--new}'[create a new worktree in a registered repo]:branch:' '--print[print the resolved worktree path without changing directory]' '--json[emit JSON on success or error instead of human-readable output]' '2:branch: '
|
|
289
|
+
;;
|
|
290
|
+
config)
|
|
291
|
+
if (( CURRENT == 3 )); then
|
|
292
|
+
_values 'config action' get set unset
|
|
293
|
+
return
|
|
294
|
+
fi
|
|
268
295
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
296
|
+
case "\${words[3]}" in
|
|
297
|
+
get|unset)
|
|
298
|
+
_arguments '3:key:->config_keys'
|
|
299
|
+
;;
|
|
300
|
+
set)
|
|
301
|
+
_arguments '3:key:->config_keys' '4:value: '
|
|
302
|
+
;;
|
|
303
|
+
esac
|
|
304
|
+
;;
|
|
305
|
+
esac
|
|
279
306
|
|
|
280
|
-
|
|
307
|
+
case "$state" in
|
|
308
|
+
worktrees)
|
|
309
|
+
worktree_branches=(\${(@f)$(__gji_worktree_branches)})
|
|
310
|
+
_describe 'worktree branch' worktree_branches
|
|
311
|
+
;;
|
|
312
|
+
config_keys)
|
|
313
|
+
_values 'config key' ${configKeys}
|
|
314
|
+
;;
|
|
315
|
+
esac`;
|
|
281
316
|
}
|
|
282
317
|
function escapeSingleQuotes(value) {
|
|
283
318
|
return value.replace(/'/g, `'\\''`);
|
package/dist/warp.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface WarpCommandOptions {
|
|
2
|
+
branch?: string;
|
|
3
|
+
cwd: string;
|
|
4
|
+
json?: boolean;
|
|
5
|
+
newWorktree?: boolean;
|
|
6
|
+
stderr: (chunk: string) => void;
|
|
7
|
+
stdout: (chunk: string) => void;
|
|
8
|
+
}
|
|
9
|
+
export declare function runWarpCommand(options: WarpCommandOptions): Promise<number>;
|
|
10
|
+
export interface WarpTarget {
|
|
11
|
+
branch: string | null;
|
|
12
|
+
path: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function resolveWarpTarget(options: {
|
|
15
|
+
branch?: string;
|
|
16
|
+
commandName?: string;
|
|
17
|
+
cwd: string;
|
|
18
|
+
json?: boolean;
|
|
19
|
+
stderr: (chunk: string) => void;
|
|
20
|
+
}): Promise<WarpTarget | null>;
|
package/dist/warp.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { isCancel, select } from '@clack/prompts';
|
|
2
|
+
import { readWorktreeHealth } from './git.js';
|
|
3
|
+
import { isHeadless } from './headless.js';
|
|
4
|
+
import { appendHistory } from './history.js';
|
|
5
|
+
import { runNewCommand } from './new.js';
|
|
6
|
+
import { loadRegistry } from './repo-registry.js';
|
|
7
|
+
import { listWorktrees } from './repo.js';
|
|
8
|
+
import { writeShellOutput } from './shell-handoff.js';
|
|
9
|
+
const WARP_OUTPUT_FILE_ENV = 'GJI_WARP_OUTPUT_FILE';
|
|
10
|
+
export async function runWarpCommand(options) {
|
|
11
|
+
if (options.newWorktree) {
|
|
12
|
+
const registry = await loadRegistry();
|
|
13
|
+
if (registry.length === 0) {
|
|
14
|
+
options.stderr('gji warp: no repos registered yet.\n' +
|
|
15
|
+
'Use any gji command in a repository to register it automatically.\n');
|
|
16
|
+
return 1;
|
|
17
|
+
}
|
|
18
|
+
return runWarpNew(options, registry);
|
|
19
|
+
}
|
|
20
|
+
return runWarpNavigate(options);
|
|
21
|
+
}
|
|
22
|
+
async function runWarpNavigate(options) {
|
|
23
|
+
if ((isHeadless() || options.json) && !options.branch) {
|
|
24
|
+
const message = 'branch argument is required';
|
|
25
|
+
if (options.json) {
|
|
26
|
+
options.stderr(`${JSON.stringify({ error: message }, null, 2)}\n`);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
options.stderr('gji warp: branch argument is required in non-interactive mode (GJI_NO_TUI=1)\n');
|
|
30
|
+
}
|
|
31
|
+
return 1;
|
|
32
|
+
}
|
|
33
|
+
const target = await resolveWarpTarget({ ...options, commandName: 'gji warp', json: options.json });
|
|
34
|
+
if (!target)
|
|
35
|
+
return 1;
|
|
36
|
+
if (options.json) {
|
|
37
|
+
// json callers use the output programmatically; skip history and shell handoff.
|
|
38
|
+
options.stdout(`${JSON.stringify({ branch: target.branch, path: target.path }, null, 2)}\n`);
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
|
41
|
+
appendHistory(target.path, target.branch).catch(() => undefined);
|
|
42
|
+
await writeShellOutput(WARP_OUTPUT_FILE_ENV, target.path, options.stdout);
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
45
|
+
async function runWarpNew(options, registry) {
|
|
46
|
+
let targetRepoRoot;
|
|
47
|
+
if (registry.length === 1) {
|
|
48
|
+
targetRepoRoot = registry[0].path;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
if (isHeadless()) {
|
|
52
|
+
options.stderr('gji warp: repo argument is required in non-interactive mode (GJI_NO_TUI=1)\n');
|
|
53
|
+
return 1;
|
|
54
|
+
}
|
|
55
|
+
const choice = await select({
|
|
56
|
+
message: 'Create worktree in which repo?',
|
|
57
|
+
options: registry.map((entry) => ({
|
|
58
|
+
value: entry.path,
|
|
59
|
+
label: entry.name,
|
|
60
|
+
hint: entry.path,
|
|
61
|
+
})),
|
|
62
|
+
});
|
|
63
|
+
if (isCancel(choice)) {
|
|
64
|
+
options.stderr('Aborted\n');
|
|
65
|
+
return 1;
|
|
66
|
+
}
|
|
67
|
+
targetRepoRoot = choice;
|
|
68
|
+
}
|
|
69
|
+
if (options.json) {
|
|
70
|
+
return runNewCommand({
|
|
71
|
+
branch: options.branch,
|
|
72
|
+
cwd: targetRepoRoot,
|
|
73
|
+
json: true,
|
|
74
|
+
stderr: options.stderr,
|
|
75
|
+
stdout: options.stdout,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
// runNewCommand writes the created path to options.stdout via writeShellOutput.
|
|
79
|
+
// Since GJI_NEW_OUTPUT_FILE is not set in the warp shell context, it falls
|
|
80
|
+
// through to our captured stdout, giving us the path to hand off.
|
|
81
|
+
let capturedPath = '';
|
|
82
|
+
const captureStdout = (chunk) => {
|
|
83
|
+
capturedPath = chunk.trim();
|
|
84
|
+
};
|
|
85
|
+
const exitCode = await runNewCommand({
|
|
86
|
+
branch: options.branch,
|
|
87
|
+
cwd: targetRepoRoot,
|
|
88
|
+
stderr: options.stderr,
|
|
89
|
+
stdout: captureStdout,
|
|
90
|
+
});
|
|
91
|
+
if (exitCode !== 0) {
|
|
92
|
+
return exitCode;
|
|
93
|
+
}
|
|
94
|
+
if (!capturedPath) {
|
|
95
|
+
options.stderr('gji warp: could not determine new worktree path\n');
|
|
96
|
+
return 1;
|
|
97
|
+
}
|
|
98
|
+
await writeShellOutput(WARP_OUTPUT_FILE_ENV, capturedPath, options.stdout);
|
|
99
|
+
return 0;
|
|
100
|
+
}
|
|
101
|
+
function findByQuery(items, query) {
|
|
102
|
+
const slashIdx = query.indexOf('/');
|
|
103
|
+
if (slashIdx !== -1) {
|
|
104
|
+
const repoQuery = query.slice(0, slashIdx);
|
|
105
|
+
const branchQuery = query.slice(slashIdx + 1);
|
|
106
|
+
const match = items.find((item) => item.repoName === repoQuery && item.worktree.branch === branchQuery);
|
|
107
|
+
if (match)
|
|
108
|
+
return match;
|
|
109
|
+
}
|
|
110
|
+
return items.find((item) => item.worktree.branch === query) ?? null;
|
|
111
|
+
}
|
|
112
|
+
export async function resolveWarpTarget(options) {
|
|
113
|
+
const cmd = options.commandName ?? 'gji';
|
|
114
|
+
const emitError = (message, hint) => {
|
|
115
|
+
if (options.json) {
|
|
116
|
+
options.stderr(`${JSON.stringify({ error: message }, null, 2)}\n`);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
options.stderr(`${cmd}: ${message}\n`);
|
|
120
|
+
if (hint)
|
|
121
|
+
options.stderr(hint);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
const registry = await loadRegistry();
|
|
125
|
+
if (registry.length === 0) {
|
|
126
|
+
emitError('not in a git repository and no repos registered yet.', 'Use any gji command inside a repository to register it.\n');
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
const results = await Promise.allSettled(registry.map(async (entry) => {
|
|
130
|
+
const worktrees = await listWorktrees(entry.path);
|
|
131
|
+
return { repoName: entry.name, worktrees };
|
|
132
|
+
}));
|
|
133
|
+
const allItems = [];
|
|
134
|
+
for (const result of results) {
|
|
135
|
+
if (result.status === 'rejected')
|
|
136
|
+
continue;
|
|
137
|
+
const { repoName, worktrees } = result.value;
|
|
138
|
+
for (const worktree of worktrees) {
|
|
139
|
+
allItems.push({ repoName, worktree });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (allItems.length === 0) {
|
|
143
|
+
emitError('no accessible worktrees found in any registered repo.');
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
if (options.branch) {
|
|
147
|
+
const match = findByQuery(allItems, options.branch);
|
|
148
|
+
if (!match) {
|
|
149
|
+
emitError(`no worktree found matching: ${options.branch}`);
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
return { branch: match.worktree.branch, path: match.worktree.path };
|
|
153
|
+
}
|
|
154
|
+
const path = await promptForWarpTarget(allItems);
|
|
155
|
+
if (!path) {
|
|
156
|
+
options.stderr('Aborted\n');
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
const chosen = allItems.find((item) => item.worktree.path === path);
|
|
160
|
+
return { branch: chosen?.worktree.branch ?? null, path };
|
|
161
|
+
}
|
|
162
|
+
async function promptForWarpTarget(items) {
|
|
163
|
+
const healthResults = await Promise.allSettled(items.map((item) => readWorktreeHealth(item.worktree.path)));
|
|
164
|
+
const choice = await select({
|
|
165
|
+
message: 'Warp to a worktree',
|
|
166
|
+
options: items.map((item, i) => {
|
|
167
|
+
const health = healthResults[i].status === 'fulfilled' ? healthResults[i].value : null;
|
|
168
|
+
const upstream = health ? formatHint(item.worktree.branch, health) : null;
|
|
169
|
+
const label = `${item.repoName} › ${item.worktree.branch ?? '(detached)'}`;
|
|
170
|
+
const pathHint = item.worktree.isCurrent
|
|
171
|
+
? `${item.worktree.path} (current)`
|
|
172
|
+
: item.worktree.path;
|
|
173
|
+
const hint = upstream ? `${upstream} · ${pathHint}` : pathHint;
|
|
174
|
+
return { hint, label, value: item.worktree.path };
|
|
175
|
+
}),
|
|
176
|
+
});
|
|
177
|
+
if (isCancel(choice)) {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
return choice;
|
|
181
|
+
}
|
|
182
|
+
function formatHint(branch, health) {
|
|
183
|
+
if (branch === null)
|
|
184
|
+
return null;
|
|
185
|
+
if (!health.hasUpstream)
|
|
186
|
+
return 'no upstream';
|
|
187
|
+
if (health.upstreamGone)
|
|
188
|
+
return 'upstream gone';
|
|
189
|
+
if (health.ahead === 0 && health.behind === 0)
|
|
190
|
+
return 'up to date';
|
|
191
|
+
if (health.ahead === 0)
|
|
192
|
+
return `behind ${health.behind}`;
|
|
193
|
+
if (health.behind === 0)
|
|
194
|
+
return `ahead ${health.ahead}`;
|
|
195
|
+
return `ahead ${health.ahead}, behind ${health.behind}`;
|
|
196
|
+
}
|
package/man/man1/gji-back.1
CHANGED
package/man/man1/gji-clean.1
CHANGED
package/man/man1/gji-config.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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.TH GJI\-NEW 1 "May 2026" "gji 0.
|
|
1
|
+
.TH GJI\-NEW 1 "May 2026" "gji 0.6.1" "User Commands"
|
|
2
2
|
.SH NAME
|
|
3
3
|
gji\-new \- create a new branch or detached linked worktree
|
|
4
4
|
.SH SYNOPSIS
|
|
@@ -13,6 +13,12 @@ remove and recreate the worktree if the target path already exists
|
|
|
13
13
|
.B \-\-detached
|
|
14
14
|
create a detached worktree without a branch
|
|
15
15
|
.TP
|
|
16
|
+
.B \-\-open
|
|
17
|
+
open the new worktree in an editor after creation
|
|
18
|
+
.TP
|
|
19
|
+
.B \-\-editor <cli>
|
|
20
|
+
editor CLI to use with \-\-open (code, cursor, zed, …)
|
|
21
|
+
.TP
|
|
16
22
|
.B \-\-dry\-run
|
|
17
23
|
show what would be created without executing any git commands or writing files
|
|
18
24
|
.TP
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
.TH GJI\-OPEN 1 "May 2026" "gji 0.6.1" "User Commands"
|
|
2
|
+
.SH NAME
|
|
3
|
+
gji\-open \- open the worktree in an editor
|
|
4
|
+
.SH SYNOPSIS
|
|
5
|
+
.B gji open [\fIoptions\fR] [options] [branch]
|
|
6
|
+
.SH DESCRIPTION
|
|
7
|
+
open the worktree in an editor
|
|
8
|
+
.SH OPTIONS
|
|
9
|
+
.TP
|
|
10
|
+
.B \-\-editor <cli>
|
|
11
|
+
editor CLI to use (code, cursor, zed, windsurf, subl, …)
|
|
12
|
+
.TP
|
|
13
|
+
.B \-\-save
|
|
14
|
+
save the chosen editor to global config
|
|
15
|
+
.TP
|
|
16
|
+
.B \-\-workspace
|
|
17
|
+
generate a .code\-workspace file before opening (VS Code / Cursor / Windsurf)
|
|
18
|
+
.SH "SEE ALSO"
|
|
19
|
+
.BR gji (1)
|
package/man/man1/gji-pr.1
CHANGED
package/man/man1/gji-remove.1
CHANGED
package/man/man1/gji-root.1
CHANGED
package/man/man1/gji-status.1
CHANGED
package/man/man1/gji-sync.1
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
.TH GJI\-WARP 1 "May 2026" "gji 0.6.1" "User Commands"
|
|
2
|
+
.SH NAME
|
|
3
|
+
gji\-warp \- jump to any worktree across all known repos
|
|
4
|
+
.SH SYNOPSIS
|
|
5
|
+
.B gji warp [\fIoptions\fR] [options] [branch]
|
|
6
|
+
.SH DESCRIPTION
|
|
7
|
+
jump to any worktree across all known repos
|
|
8
|
+
.SH OPTIONS
|
|
9
|
+
.TP
|
|
10
|
+
.B \-n, \-\-new [branch]
|
|
11
|
+
create a new worktree in a registered repo
|
|
12
|
+
.TP
|
|
13
|
+
.B \-\-print
|
|
14
|
+
print the resolved worktree path without changing directory
|
|
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)
|