@solaqua/gji 0.8.0 → 0.9.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 +24 -30
- package/dist/browser.d.ts +7 -0
- package/dist/browser.js +37 -0
- package/dist/clean.js +34 -19
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +59 -4
- package/dist/doctor.d.ts +28 -0
- package/dist/doctor.js +454 -0
- package/dist/gji-bundle.mjs +2514 -378
- package/dist/go.d.ts +2 -1
- package/dist/go.js +4 -1
- package/dist/index.js +12 -6
- package/dist/init.d.ts +15 -0
- package/dist/init.js +232 -16
- package/dist/open.d.ts +2 -1
- package/dist/open.js +2 -1
- package/dist/pr-open.d.ts +19 -0
- package/dist/pr-open.js +290 -0
- package/dist/pull-requests.d.ts +33 -0
- package/dist/pull-requests.js +384 -0
- package/dist/remove.js +2 -2
- package/dist/repo-registry.d.ts +5 -0
- package/dist/repo-registry.js +87 -14
- package/dist/shell-completion.js +278 -10
- package/dist/shell-setup.d.ts +9 -0
- package/dist/shell-setup.js +56 -0
- package/dist/warp.d.ts +3 -0
- package/dist/warp.js +8 -3
- package/dist/worktree-management.d.ts +2 -1
- package/dist/worktree-management.js +23 -7
- package/dist/worktree-picker.d.ts +12 -1
- package/dist/worktree-picker.js +84 -6
- package/man/man1/gji-back.1 +1 -1
- package/man/man1/gji-clean.1 +1 -1
- package/man/man1/gji-completion.1 +1 -1
- package/man/man1/gji-config.1 +1 -1
- package/man/man1/gji-doctor.1 +19 -0
- package/man/man1/gji-go.1 +1 -1
- package/man/man1/gji-history.1 +1 -1
- package/man/man1/gji-init.1 +6 -3
- 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 +5 -1
- package/man/man1/gji-remove.1 +1 -1
- package/man/man1/gji-root.1 +1 -1
- package/man/man1/gji-run-hook.1 +1 -1
- package/man/man1/gji-status.1 +1 -1
- package/man/man1/gji-sync-files.1 +1 -1
- package/man/man1/gji-sync.1 +1 -1
- package/man/man1/gji-warp.1 +1 -1
- package/man/man1/gji.1 +6 -2
- package/package.json +1 -1
package/dist/shell-completion.js
CHANGED
|
@@ -4,7 +4,14 @@ const TOP_LEVEL_COMMANDS = [
|
|
|
4
4
|
name: "new",
|
|
5
5
|
description: "create a new branch or detached linked worktree",
|
|
6
6
|
},
|
|
7
|
-
{
|
|
7
|
+
{
|
|
8
|
+
name: "init",
|
|
9
|
+
description: "set up shell integration interactively or print a shell wrapper",
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
name: "doctor",
|
|
13
|
+
description: "check gji installation and configuration health",
|
|
14
|
+
},
|
|
8
15
|
{ name: "completion", description: "print shell completion definitions" },
|
|
9
16
|
{ name: "pr", description: "fetch a pull request into a linked worktree" },
|
|
10
17
|
{ name: "back", description: "navigate to the previously visited worktree" },
|
|
@@ -55,6 +62,67 @@ function renderBashCompletion() {
|
|
|
55
62
|
command gji ls --compact 2>/dev/null | awk 'NR > 1 { branch = ($1 == "*" ? $2 : $1); if (branch != "(detached)") print branch }'
|
|
56
63
|
}
|
|
57
64
|
|
|
65
|
+
__gji_pr_targets_cache=""
|
|
66
|
+
__gji_pr_targets_cache_at=0
|
|
67
|
+
__gji_pr_targets_cache_loaded=0
|
|
68
|
+
__gji_pr_targets_cache_repo=""
|
|
69
|
+
|
|
70
|
+
__gji_run_bounded() {
|
|
71
|
+
local output_file pid status attempt
|
|
72
|
+
output_file=$(mktemp "\${TMPDIR:-/tmp}/gji-completion.XXXXXX") || return 124
|
|
73
|
+
"$@" >"$output_file" 2>/dev/null &
|
|
74
|
+
pid=$!
|
|
75
|
+
for ((attempt = 0; attempt < 20; attempt++)); do
|
|
76
|
+
if ! kill -0 "$pid" 2>/dev/null; then
|
|
77
|
+
wait "$pid"
|
|
78
|
+
status=$?
|
|
79
|
+
cat "$output_file"
|
|
80
|
+
rm -f "$output_file"
|
|
81
|
+
return "$status"
|
|
82
|
+
fi
|
|
83
|
+
sleep 0.1
|
|
84
|
+
done
|
|
85
|
+
kill "$pid" 2>/dev/null
|
|
86
|
+
wait "$pid" 2>/dev/null
|
|
87
|
+
rm -f "$output_file"
|
|
88
|
+
return 124
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
__gji_pr_targets() {
|
|
92
|
+
local now repo_key branches targets
|
|
93
|
+
now=$(date +%s)
|
|
94
|
+
repo_key=$(command git rev-parse --show-toplevel 2>/dev/null)
|
|
95
|
+
if [ "$__gji_pr_targets_cache_loaded" -eq 1 ] && [ "$repo_key" = "$__gji_pr_targets_cache_repo" ] && [ "$((now - __gji_pr_targets_cache_at))" -lt 30 ]; then
|
|
96
|
+
printf '%s\n' "$__gji_pr_targets_cache"
|
|
97
|
+
return
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
branches="$(__gji_worktree_branches)"
|
|
101
|
+
targets=""
|
|
102
|
+
if [ -n "$repo_key" ] && command -v gh >/dev/null 2>&1; then
|
|
103
|
+
targets=$(__gji_run_bounded env GH_PROMPT_DISABLED=1 gh pr list --state open --json number --limit 100 |
|
|
104
|
+
tr '{},' '\\n' | sed -n 's/.*"number":[[:space:]]*\\([0-9][0-9]*\\).*/\\1/p')
|
|
105
|
+
elif [ -n "$repo_key" ] && command -v glab >/dev/null 2>&1; then
|
|
106
|
+
targets=$(__gji_run_bounded env GLAB_NON_INTERACTIVE=1 glab mr list --state opened --output json --per-page 100 |
|
|
107
|
+
tr '{},' '\\n' | sed -n 's/.*"iid":[[:space:]]*\\([0-9][0-9]*\\).*/\\1/p')
|
|
108
|
+
elif [ -n "$repo_key" ] && command -v bb >/dev/null 2>&1; then
|
|
109
|
+
targets=$(__gji_run_bounded bb pr list --state OPEN --format json |
|
|
110
|
+
tr '{},' '\\n' | sed -n 's/.*"id":[[:space:]]*\\([0-9][0-9]*\\).*/\\1/p')
|
|
111
|
+
fi
|
|
112
|
+
|
|
113
|
+
__gji_pr_targets_cache="$branches"
|
|
114
|
+
if [ -n "$targets" ]; then
|
|
115
|
+
if [ -n "$__gji_pr_targets_cache" ]; then
|
|
116
|
+
__gji_pr_targets_cache+=$'\n'
|
|
117
|
+
fi
|
|
118
|
+
__gji_pr_targets_cache+="$targets"
|
|
119
|
+
fi
|
|
120
|
+
__gji_pr_targets_cache_at="$now"
|
|
121
|
+
__gji_pr_targets_cache_loaded=1
|
|
122
|
+
__gji_pr_targets_cache_repo="$repo_key"
|
|
123
|
+
printf '%s\n' "$__gji_pr_targets_cache"
|
|
124
|
+
}
|
|
125
|
+
|
|
58
126
|
_gji_completion() {
|
|
59
127
|
local cur command_name
|
|
60
128
|
COMPREPLY=()
|
|
@@ -72,13 +140,37 @@ _gji_completion() {
|
|
|
72
140
|
COMPREPLY=( $(compgen -W "--detached --force --open --editor --dry-run --json --help" -- "$cur") )
|
|
73
141
|
;;
|
|
74
142
|
init)
|
|
75
|
-
COMPREPLY=( $(compgen -W "${shells} --write --help" -- "$cur") )
|
|
143
|
+
COMPREPLY=( $(compgen -W "${shells} --write --json --help" -- "$cur") )
|
|
144
|
+
;;
|
|
145
|
+
doctor)
|
|
146
|
+
COMPREPLY=( $(compgen -W "--fix --yes --json --help" -- "$cur") )
|
|
76
147
|
;;
|
|
77
148
|
completion)
|
|
78
149
|
COMPREPLY=( $(compgen -W "${shells} --help" -- "$cur") )
|
|
79
150
|
;;
|
|
80
151
|
pr)
|
|
81
|
-
|
|
152
|
+
if [ "$COMP_CWORD" -eq 2 ]; then
|
|
153
|
+
COMPREPLY=( $(compgen -W "open --dry-run --json --help" -- "$cur") )
|
|
154
|
+
elif [ "\${COMP_WORDS[2]}" = "open" ]; then
|
|
155
|
+
local has_select=0 has_target=0 word
|
|
156
|
+
for word in "\${COMP_WORDS[@]:3:$((COMP_CWORD - 3))}"; do
|
|
157
|
+
if [ "$word" = "--select" ]; then has_select=1; fi
|
|
158
|
+
if [[ "$word" != -* && "$word" != "" ]]; then has_target=1; fi
|
|
159
|
+
done
|
|
160
|
+
if [[ "$cur" == -* ]]; then
|
|
161
|
+
if [[ "$has_select" -eq 1 || "$has_target" -eq 1 ]]; then
|
|
162
|
+
COMPREPLY=( $(compgen -W "--help" -- "$cur") )
|
|
163
|
+
else
|
|
164
|
+
COMPREPLY=( $(compgen -W "--select --help" -- "$cur") )
|
|
165
|
+
fi
|
|
166
|
+
elif [[ "$has_select" -eq 1 || "$has_target" -eq 1 ]]; then
|
|
167
|
+
COMPREPLY=()
|
|
168
|
+
else
|
|
169
|
+
COMPREPLY=( $(compgen -W "$(__gji_pr_targets) --select --help" -- "$cur") )
|
|
170
|
+
fi
|
|
171
|
+
else
|
|
172
|
+
COMPREPLY=( $(compgen -W "--dry-run --json --help" -- "$cur") )
|
|
173
|
+
fi
|
|
82
174
|
;;
|
|
83
175
|
back)
|
|
84
176
|
COMPREPLY=( $(compgen -W "--print --help" -- "$cur") )
|
|
@@ -155,6 +247,101 @@ function renderFishCompletion() {
|
|
|
155
247
|
command gji ls --compact 2>/dev/null | awk 'NR > 1 { branch = ($1 == "*" ? $2 : $1); if (branch != "(detached)") print branch }'
|
|
156
248
|
end
|
|
157
249
|
|
|
250
|
+
set -g __gji_pr_targets_cache
|
|
251
|
+
set -g __gji_pr_targets_cache_at 0
|
|
252
|
+
set -g __gji_pr_targets_cache_loaded 0
|
|
253
|
+
set -g __gji_pr_targets_cache_repo
|
|
254
|
+
|
|
255
|
+
function __gji_run_bounded
|
|
256
|
+
set -l output_file (mktemp "$TMPDIR/gji-completion.XXXXXX" 2>/dev/null)
|
|
257
|
+
if test -z "$output_file"
|
|
258
|
+
set output_file (mktemp "/tmp/gji-completion.XXXXXX" 2>/dev/null)
|
|
259
|
+
end
|
|
260
|
+
if test -z "$output_file"
|
|
261
|
+
return 124
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
command $argv >$output_file 2>/dev/null &
|
|
265
|
+
set -l pid $last_pid
|
|
266
|
+
for attempt in (seq 1 20)
|
|
267
|
+
if not kill -0 $pid 2>/dev/null
|
|
268
|
+
wait $pid
|
|
269
|
+
set -l exit_status $status
|
|
270
|
+
command cat $output_file
|
|
271
|
+
command rm -f $output_file
|
|
272
|
+
return $exit_status
|
|
273
|
+
end
|
|
274
|
+
sleep 0.1
|
|
275
|
+
end
|
|
276
|
+
kill $pid 2>/dev/null
|
|
277
|
+
wait $pid 2>/dev/null
|
|
278
|
+
command rm -f $output_file
|
|
279
|
+
return 124
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
function __gji_pr_targets
|
|
283
|
+
set -l now (date +%s)
|
|
284
|
+
set -l repo_key (command git rev-parse --show-toplevel 2>/dev/null)
|
|
285
|
+
if test $__gji_pr_targets_cache_loaded -eq 1; and test "$repo_key" = "$__gji_pr_targets_cache_repo"; and test (math $now - $__gji_pr_targets_cache_at) -lt 30
|
|
286
|
+
printf '%s\n' $__gji_pr_targets_cache
|
|
287
|
+
return
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
set -l branches (__gji_worktree_branches)
|
|
291
|
+
set -l targets
|
|
292
|
+
if test -n "$repo_key"; and command -q gh
|
|
293
|
+
set targets (__gji_run_bounded env GH_PROMPT_DISABLED=1 gh pr list --state open --json number --limit 100 |
|
|
294
|
+
string replace -a -r '[{},]' '\\n' |
|
|
295
|
+
string match -r '"number"[[:space:]]*:[[:space:]]*[0-9]+' | string replace -r '.*:[[:space:]]*' '')
|
|
296
|
+
else if test -n "$repo_key"; and command -q glab
|
|
297
|
+
set targets (__gji_run_bounded env GLAB_NON_INTERACTIVE=1 glab mr list --state opened --output json --per-page 100 |
|
|
298
|
+
string replace -a -r '[{},]' '\\n' |
|
|
299
|
+
string match -r '"iid"[[:space:]]*:[[:space:]]*[0-9]+' | string replace -r '.*:[[:space:]]*' '')
|
|
300
|
+
else if test -n "$repo_key"; and command -q bb
|
|
301
|
+
set targets (__gji_run_bounded bb pr list --state OPEN --format json |
|
|
302
|
+
string replace -a -r '[{},]' '\\n' |
|
|
303
|
+
string match -r '"id"[[:space:]]*:[[:space:]]*[0-9]+' | string replace -r '.*:[[:space:]]*' '')
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
set -g __gji_pr_targets_cache $branches $targets
|
|
307
|
+
set -g __gji_pr_targets_cache_at $now
|
|
308
|
+
set -g __gji_pr_targets_cache_loaded 1
|
|
309
|
+
set -g __gji_pr_targets_cache_repo $repo_key
|
|
310
|
+
printf '%s\n' $__gji_pr_targets_cache
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
function __gji_should_complete_pr_select
|
|
314
|
+
set -l tokens (commandline -opc)
|
|
315
|
+
if test (count $tokens) -lt 3; or test $tokens[2] != pr; or test $tokens[3] != open
|
|
316
|
+
return 1
|
|
317
|
+
end
|
|
318
|
+
for token in $tokens[4..-1]
|
|
319
|
+
if test $token = --select; or not string match -q -- '-*' $token
|
|
320
|
+
return 1
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
return 0
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
function __gji_should_complete_pr_target
|
|
327
|
+
set -l tokens (commandline -opc)
|
|
328
|
+
if test (count $tokens) -lt 3; or test $tokens[2] != pr; or test $tokens[3] != open
|
|
329
|
+
return 1
|
|
330
|
+
end
|
|
331
|
+
if string match -q -- '-*' (commandline -ct)
|
|
332
|
+
return 1
|
|
333
|
+
end
|
|
334
|
+
for token in $tokens[4..-1]
|
|
335
|
+
if test $token = --select
|
|
336
|
+
return 1
|
|
337
|
+
end
|
|
338
|
+
if not string match -q -- '-*' $token
|
|
339
|
+
return 1
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
return 0
|
|
343
|
+
end
|
|
344
|
+
|
|
158
345
|
function __gji_should_complete_config_action
|
|
159
346
|
set -l tokens (commandline -opc)
|
|
160
347
|
test (count $tokens) -eq 2
|
|
@@ -184,14 +371,22 @@ complete -c gji -n '__fish_seen_subcommand_from new' -l dry-run -d 'show what wo
|
|
|
184
371
|
complete -c gji -n '__fish_seen_subcommand_from new' -l json -d 'emit JSON on success or error instead of human-readable output'
|
|
185
372
|
|
|
186
373
|
complete -c gji -n '__fish_seen_subcommand_from init' -l write -d 'write the integration to the shell config file'
|
|
374
|
+
complete -c gji -n '__fish_seen_subcommand_from init' -l json -d 'emit a JSON error in non-interactive mode'
|
|
187
375
|
${shellLines}
|
|
188
376
|
|
|
377
|
+
complete -c gji -n '__fish_seen_subcommand_from doctor' -l fix -d 'apply safe automatic fixes after showing the plan'
|
|
378
|
+
complete -c gji -n '__fish_seen_subcommand_from doctor' -l yes -d 'apply --fix without prompting'
|
|
379
|
+
complete -c gji -n '__fish_seen_subcommand_from doctor' -l json -d 'emit diagnostic checks as JSON'
|
|
380
|
+
|
|
189
381
|
complete -c gji -n '__fish_seen_subcommand_from completion' -a 'bash' -d 'shell'
|
|
190
382
|
complete -c gji -n '__fish_seen_subcommand_from completion' -a 'fish' -d 'shell'
|
|
191
383
|
complete -c gji -n '__fish_seen_subcommand_from completion' -a 'zsh' -d 'shell'
|
|
192
384
|
|
|
193
|
-
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'
|
|
194
|
-
complete -c gji -n '__fish_seen_subcommand_from pr' -l json -d 'emit JSON on success or error instead of human-readable output'
|
|
385
|
+
complete -c gji -n '__fish_seen_subcommand_from pr; and test (commandline -opc)[3] != open' -l dry-run -d 'show what would be created without executing any git commands or writing files'
|
|
386
|
+
complete -c gji -n '__fish_seen_subcommand_from pr; and test (commandline -opc)[3] != open' -l json -d 'emit JSON on success or error instead of human-readable output'
|
|
387
|
+
complete -c gji -n '__fish_seen_subcommand_from pr' -a 'open' -d 'open a pull request in the default browser'
|
|
388
|
+
complete -c gji -n '__gji_should_complete_pr_select' -l select -d 'choose a pull request from any linked worktree'
|
|
389
|
+
complete -c gji -n '__gji_should_complete_pr_target' -a '(__gji_pr_targets)' -d 'branch or PR number'
|
|
195
390
|
|
|
196
391
|
complete -c gji -n '__fish_seen_subcommand_from back' -l print -d 'print the resolved worktree path explicitly'
|
|
197
392
|
|
|
@@ -251,15 +446,71 @@ __gji_worktree_branches() {
|
|
|
251
446
|
command gji ls --compact 2>/dev/null | awk 'NR > 1 { branch = ($1 == "*" ? $2 : $1); if (branch != "(detached)") print branch }'
|
|
252
447
|
}
|
|
253
448
|
|
|
449
|
+
typeset -g __gji_pr_targets_cache=()
|
|
450
|
+
typeset -g __gji_pr_targets_cache_at=0
|
|
451
|
+
typeset -g __gji_pr_targets_cache_loaded=0
|
|
452
|
+
typeset -g __gji_pr_targets_cache_repo=""
|
|
453
|
+
|
|
454
|
+
__gji_run_bounded() {
|
|
455
|
+
local output_file pid status attempt
|
|
456
|
+
output_file=$(mktemp "\${TMPDIR:-/tmp}/gji-completion.XXXXXX") || return 124
|
|
457
|
+
"$@" >"$output_file" 2>/dev/null &
|
|
458
|
+
pid=$!
|
|
459
|
+
for ((attempt = 0; attempt < 20; attempt++)); do
|
|
460
|
+
if ! kill -0 "$pid" 2>/dev/null; then
|
|
461
|
+
wait "$pid"
|
|
462
|
+
status=$?
|
|
463
|
+
cat "$output_file"
|
|
464
|
+
rm -f "$output_file"
|
|
465
|
+
return "$status"
|
|
466
|
+
fi
|
|
467
|
+
sleep 0.1
|
|
468
|
+
done
|
|
469
|
+
kill "$pid" 2>/dev/null
|
|
470
|
+
wait "$pid" 2>/dev/null
|
|
471
|
+
rm -f "$output_file"
|
|
472
|
+
return 124
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
__gji_pr_targets() {
|
|
476
|
+
local now repo_key
|
|
477
|
+
local -a branches targets
|
|
478
|
+
now=$(date +%s)
|
|
479
|
+
repo_key=$(command git rev-parse --show-toplevel 2>/dev/null)
|
|
480
|
+
if (( __gji_pr_targets_cache_loaded == 1 && "\${repo_key}" == "\${__gji_pr_targets_cache_repo}" && now - __gji_pr_targets_cache_at < 30 )); then
|
|
481
|
+
print -rl -- "\${__gji_pr_targets_cache[@]}"
|
|
482
|
+
return
|
|
483
|
+
fi
|
|
484
|
+
|
|
485
|
+
branches=("\${(@f)$(__gji_worktree_branches)}")
|
|
486
|
+
targets=()
|
|
487
|
+
if [[ -n "\${repo_key}" && $+commands[gh] -eq 1 ]]; then
|
|
488
|
+
targets=("\${(@f)$(__gji_run_bounded env GH_PROMPT_DISABLED=1 gh pr list --state open --json number --limit 100 |
|
|
489
|
+
tr '{},' '\\n' | sed -n 's/.*"number":[[:space:]]*\\([0-9][0-9]*\\).*/\\1/p')}")
|
|
490
|
+
elif [[ -n "\${repo_key}" && $+commands[glab] -eq 1 ]]; then
|
|
491
|
+
targets=("\${(@f)$(__gji_run_bounded env GLAB_NON_INTERACTIVE=1 glab mr list --state opened --output json --per-page 100 |
|
|
492
|
+
tr '{},' '\\n' | sed -n 's/.*"iid":[[:space:]]*\\([0-9][0-9]*\\).*/\\1/p')}")
|
|
493
|
+
elif [[ -n "\${repo_key}" && $+commands[bb] -eq 1 ]]; then
|
|
494
|
+
targets=("\${(@f)$(__gji_run_bounded bb pr list --state OPEN --format json |
|
|
495
|
+
tr '{},' '\\n' | sed -n 's/.*"id":[[:space:]]*\\([0-9][0-9]*\\).*/\\1/p')}")
|
|
496
|
+
fi
|
|
497
|
+
|
|
498
|
+
__gji_pr_targets_cache=("\${branches[@]}" "\${targets[@]}")
|
|
499
|
+
__gji_pr_targets_cache_at="$now"
|
|
500
|
+
__gji_pr_targets_cache_loaded=1
|
|
501
|
+
__gji_pr_targets_cache_repo="$repo_key"
|
|
502
|
+
print -rl -- "\${__gji_pr_targets_cache[@]}"
|
|
503
|
+
}
|
|
504
|
+
|
|
254
505
|
local context state line
|
|
255
|
-
local -a
|
|
506
|
+
local -a command_entries worktree_branches
|
|
256
507
|
|
|
257
|
-
|
|
508
|
+
command_entries=(
|
|
258
509
|
${commandLines}
|
|
259
510
|
)
|
|
260
511
|
|
|
261
512
|
if (( CURRENT == 2 )); then
|
|
262
|
-
_describe 'command'
|
|
513
|
+
_describe 'command' command_entries
|
|
263
514
|
return
|
|
264
515
|
fi
|
|
265
516
|
|
|
@@ -268,13 +519,26 @@ case "\${words[2]}" in
|
|
|
268
519
|
_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: '
|
|
269
520
|
;;
|
|
270
521
|
init)
|
|
271
|
-
_arguments '--write[write the integration to the shell config file]' '2:shell:(${shells})'
|
|
522
|
+
_arguments '--write[write the integration to the shell config file]' '--json[emit a JSON error in non-interactive mode]' '2:shell:(${shells})'
|
|
523
|
+
;;
|
|
524
|
+
doctor)
|
|
525
|
+
_arguments '--fix[apply safe automatic fixes after showing the plan]' '--yes[apply --fix without prompting]' '--json[emit diagnostic checks as JSON]'
|
|
272
526
|
;;
|
|
273
527
|
completion)
|
|
274
528
|
_arguments '2:shell:(${shells})'
|
|
275
529
|
;;
|
|
276
530
|
pr)
|
|
277
|
-
|
|
531
|
+
if [[ "\${words[3]}" == "open" ]]; then
|
|
532
|
+
if (( \${words[(I)--select]} )); then
|
|
533
|
+
_arguments
|
|
534
|
+
elif [[ -n "\${words[4]}" && "\${words[4]}" != -* ]]; then
|
|
535
|
+
_arguments
|
|
536
|
+
else
|
|
537
|
+
_arguments '--select[choose a pull request from any linked worktree]' '4:branch or PR number:->pr_targets'
|
|
538
|
+
fi
|
|
539
|
+
else
|
|
540
|
+
_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:(open)'
|
|
541
|
+
fi
|
|
278
542
|
;;
|
|
279
543
|
back)
|
|
280
544
|
_arguments '--print[print the resolved worktree path explicitly]' '2:steps: '
|
|
@@ -337,6 +601,10 @@ case "$state" in
|
|
|
337
601
|
worktree_branches=(\${(@f)$(__gji_worktree_branches)})
|
|
338
602
|
_describe 'worktree branch' worktree_branches
|
|
339
603
|
;;
|
|
604
|
+
pr_targets)
|
|
605
|
+
worktree_branches=(\${(@f)$(__gji_pr_targets)})
|
|
606
|
+
_describe 'branch or PR number' worktree_branches
|
|
607
|
+
;;
|
|
340
608
|
config_keys)
|
|
341
609
|
_values 'config key' ${configKeys}
|
|
342
610
|
;;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SupportedShell } from "./shell.js";
|
|
2
|
+
export declare const SHELL_INTEGRATION_START_MARKER = "# >>> gji init >>>";
|
|
3
|
+
export declare const SHELL_INTEGRATION_END_MARKER = "# <<< gji init <<<";
|
|
4
|
+
export declare const ONBOARDING_SHELL_INTEGRATION_START_MARKER = "# >>> gji shell integration >>>";
|
|
5
|
+
export declare const ONBOARDING_SHELL_INTEGRATION_END_MARKER = "# <<< gji shell integration <<<";
|
|
6
|
+
export declare function executableExists(command: string): Promise<boolean>;
|
|
7
|
+
export declare function resolveShellConfigPath(shell: SupportedShell, home: string): string;
|
|
8
|
+
export declare function resolveCompletionPath(shell: SupportedShell, home: string): string;
|
|
9
|
+
export declare function hasShellIntegration(config: string, shell: SupportedShell): boolean;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { access } from "node:fs/promises";
|
|
3
|
+
import { delimiter, join } from "node:path";
|
|
4
|
+
export const SHELL_INTEGRATION_START_MARKER = "# >>> gji init >>>";
|
|
5
|
+
export const SHELL_INTEGRATION_END_MARKER = "# <<< gji init <<<";
|
|
6
|
+
export const ONBOARDING_SHELL_INTEGRATION_START_MARKER = "# >>> gji shell integration >>>";
|
|
7
|
+
export const ONBOARDING_SHELL_INTEGRATION_END_MARKER = "# <<< gji shell integration <<<";
|
|
8
|
+
export async function executableExists(command) {
|
|
9
|
+
const candidates = command.includes("/")
|
|
10
|
+
? [command]
|
|
11
|
+
: (process.env.PATH ?? "")
|
|
12
|
+
.split(delimiter)
|
|
13
|
+
.filter(Boolean)
|
|
14
|
+
.map((directory) => join(directory, command));
|
|
15
|
+
for (const path of candidates) {
|
|
16
|
+
try {
|
|
17
|
+
await access(path, constants.X_OK);
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// Continue until a matching executable is found.
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
export function resolveShellConfigPath(shell, home) {
|
|
27
|
+
switch (shell) {
|
|
28
|
+
case "bash":
|
|
29
|
+
return join(home, ".bashrc");
|
|
30
|
+
case "fish":
|
|
31
|
+
return join(home, ".config", "fish", "config.fish");
|
|
32
|
+
case "zsh":
|
|
33
|
+
return join(home, ".zshrc");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export function resolveCompletionPath(shell, home) {
|
|
37
|
+
switch (shell) {
|
|
38
|
+
case "bash":
|
|
39
|
+
return join(home, ".local", "share", "bash-completion", "completions", "gji");
|
|
40
|
+
case "fish":
|
|
41
|
+
return join(home, ".config", "fish", "completions", "gji.fish");
|
|
42
|
+
case "zsh":
|
|
43
|
+
return join(home, ".zsh", "completions", "_gji");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export function hasShellIntegration(config, shell) {
|
|
47
|
+
if ((config.includes(SHELL_INTEGRATION_START_MARKER) &&
|
|
48
|
+
config.includes(SHELL_INTEGRATION_END_MARKER)) ||
|
|
49
|
+
(config.includes(ONBOARDING_SHELL_INTEGRATION_START_MARKER) &&
|
|
50
|
+
config.includes(ONBOARDING_SHELL_INTEGRATION_END_MARKER))) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
if (shell === "fish")
|
|
54
|
+
return false;
|
|
55
|
+
return new RegExp(`^[\\t ]*eval\\s+["']?\\$\\(\\s*gji\\s+init\\s+${shell}\\s*\\)["']?\\s*(?:#.*)?$`, "m").test(config);
|
|
56
|
+
}
|
package/dist/warp.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { type QueryWorktreePullRequests } from "./worktree-picker.js";
|
|
1
2
|
export interface WarpCommandOptions {
|
|
2
3
|
branch?: string;
|
|
3
4
|
cwd: string;
|
|
4
5
|
json?: boolean;
|
|
5
6
|
newWorktree?: boolean;
|
|
7
|
+
queryPullRequests?: QueryWorktreePullRequests;
|
|
6
8
|
stderr: (chunk: string) => void;
|
|
7
9
|
stdout: (chunk: string) => void;
|
|
8
10
|
}
|
|
@@ -16,5 +18,6 @@ export declare function resolveWarpTarget(options: {
|
|
|
16
18
|
commandName?: string;
|
|
17
19
|
cwd: string;
|
|
18
20
|
json?: boolean;
|
|
21
|
+
queryPullRequests?: QueryWorktreePullRequests;
|
|
19
22
|
stderr: (chunk: string) => void;
|
|
20
23
|
}): Promise<WarpTarget | null>;
|
package/dist/warp.js
CHANGED
|
@@ -36,6 +36,7 @@ async function runWarpNavigate(options) {
|
|
|
36
36
|
...options,
|
|
37
37
|
commandName: "gji warp",
|
|
38
38
|
json: options.json,
|
|
39
|
+
queryPullRequests: options.queryPullRequests,
|
|
39
40
|
});
|
|
40
41
|
if (!target)
|
|
41
42
|
return 1;
|
|
@@ -152,15 +153,16 @@ export async function resolveWarpTarget(options) {
|
|
|
152
153
|
}
|
|
153
154
|
const results = await Promise.allSettled(registry.map(async (entry) => {
|
|
154
155
|
const worktrees = await listWorktrees(entry.path);
|
|
155
|
-
return { repoName: entry.name, worktrees };
|
|
156
|
+
return { repoName: entry.name, repoRoot: entry.path, worktrees };
|
|
156
157
|
}));
|
|
157
158
|
const allItems = [];
|
|
158
159
|
for (const result of results) {
|
|
159
160
|
if (result.status === "rejected")
|
|
160
161
|
continue;
|
|
161
|
-
const { repoName, worktrees } = result.value;
|
|
162
|
+
const { repoName, repoRoot, worktrees } = result.value;
|
|
162
163
|
for (const worktree of worktrees) {
|
|
163
164
|
allItems.push({
|
|
165
|
+
repoRoot,
|
|
164
166
|
repoName,
|
|
165
167
|
worktree: {
|
|
166
168
|
...worktree,
|
|
@@ -174,6 +176,7 @@ export async function resolveWarpTarget(options) {
|
|
|
174
176
|
return null;
|
|
175
177
|
}
|
|
176
178
|
const promptSources = allItems.map((item) => ({
|
|
179
|
+
repoRoot: item.repoRoot,
|
|
177
180
|
repoName: item.repoName,
|
|
178
181
|
worktree: item.worktree,
|
|
179
182
|
}));
|
|
@@ -185,7 +188,9 @@ export async function resolveWarpTarget(options) {
|
|
|
185
188
|
}
|
|
186
189
|
return { branch: match.worktree.branch, path: match.worktree.path };
|
|
187
190
|
}
|
|
188
|
-
const promptEntries = await buildWorktreePromptEntries(promptSources
|
|
191
|
+
const promptEntries = await buildWorktreePromptEntries(promptSources, {
|
|
192
|
+
queryPullRequests: options.queryPullRequests,
|
|
193
|
+
});
|
|
189
194
|
const path = await promptForWarpTarget(promptEntries);
|
|
190
195
|
if (!path) {
|
|
191
196
|
options.stderr("Aborted\n");
|
|
@@ -8,5 +8,6 @@ export declare function removeWorktree(repoRoot: string, worktreePath: string):
|
|
|
8
8
|
export declare function forceRemoveWorktree(repoRoot: string, worktreePath: string): Promise<void>;
|
|
9
9
|
export declare function deleteBranch(repoRoot: string, branch: string): Promise<void>;
|
|
10
10
|
export declare function forceDeleteBranch(repoRoot: string, branch: string): Promise<void>;
|
|
11
|
-
export declare function
|
|
11
|
+
export declare function isWorktreeForceRemovalError(error: unknown): boolean;
|
|
12
|
+
export declare function isWorktreeDeletionError(error: unknown): boolean;
|
|
12
13
|
export declare function isBranchUnmergedError(error: unknown): boolean;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
|
+
import { rm } from "node:fs/promises";
|
|
2
3
|
import { promisify } from "node:util";
|
|
3
4
|
import { detectRepository, listWorktrees, } from "./repo.js";
|
|
4
5
|
const execFileAsync = promisify(execFile);
|
|
@@ -19,10 +20,19 @@ export async function removeWorktree(repoRoot, worktreePath) {
|
|
|
19
20
|
});
|
|
20
21
|
}
|
|
21
22
|
export async function forceRemoveWorktree(repoRoot, worktreePath) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
try {
|
|
24
|
+
await execFileAsync("git", ["worktree", "remove", "--force", worktreePath], {
|
|
25
|
+
cwd: repoRoot,
|
|
26
|
+
env: GIT_ENV,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
const worktreeIsStillRegistered = (await listWorktrees(repoRoot)).some((worktree) => worktree.path === worktreePath);
|
|
31
|
+
if (worktreeIsStillRegistered) {
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
await rm(worktreePath, { force: true, recursive: true });
|
|
35
|
+
}
|
|
26
36
|
}
|
|
27
37
|
export async function deleteBranch(repoRoot, branch) {
|
|
28
38
|
await execFileAsync("git", ["branch", "-d", branch], {
|
|
@@ -36,9 +46,15 @@ export async function forceDeleteBranch(repoRoot, branch) {
|
|
|
36
46
|
env: GIT_ENV,
|
|
37
47
|
});
|
|
38
48
|
}
|
|
39
|
-
export function
|
|
40
|
-
|
|
41
|
-
|
|
49
|
+
export function isWorktreeForceRemovalError(error) {
|
|
50
|
+
if (!hasStderr(error)) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
return (error.stderr.includes("contains modified or untracked files") ||
|
|
54
|
+
isWorktreeDeletionError(error));
|
|
55
|
+
}
|
|
56
|
+
export function isWorktreeDeletionError(error) {
|
|
57
|
+
return hasStderr(error) && error.stderr.includes("failed to delete");
|
|
42
58
|
}
|
|
43
59
|
export function isBranchUnmergedError(error) {
|
|
44
60
|
return hasStderr(error) && error.stderr.includes("is not fully merged");
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { Readable, Writable } from "node:stream";
|
|
2
|
+
import { type PullRequestInfo } from "./pull-requests.js";
|
|
2
3
|
import type { WorktreeEntry } from "./repo.js";
|
|
3
4
|
export interface WorktreePromptSource {
|
|
5
|
+
repoRoot?: string;
|
|
4
6
|
repoName: string;
|
|
5
7
|
worktree: WorktreeEntry;
|
|
6
8
|
}
|
|
@@ -8,8 +10,16 @@ export interface WorktreePromptEntry extends WorktreeEntry {
|
|
|
8
10
|
group: "recent" | "other";
|
|
9
11
|
label: string;
|
|
10
12
|
metadata?: string | null;
|
|
13
|
+
pullRequestNumbers?: number[];
|
|
14
|
+
pullRequestUrls?: string[];
|
|
11
15
|
repoName: string;
|
|
12
16
|
}
|
|
17
|
+
export type QueryWorktreePullRequests = (repoRoot: string, sourceBranch: string) => Promise<PullRequestInfo[]>;
|
|
18
|
+
export type QueryRepositoryPullRequests = (repoRoot: string) => Promise<PullRequestInfo[]>;
|
|
19
|
+
export interface BuildWorktreePromptEntriesDependencies {
|
|
20
|
+
queryPullRequests?: QueryWorktreePullRequests;
|
|
21
|
+
queryRepositoryPullRequests?: QueryRepositoryPullRequests;
|
|
22
|
+
}
|
|
13
23
|
export interface WorktreePickerIO {
|
|
14
24
|
input?: Readable & {
|
|
15
25
|
isTTY?: boolean;
|
|
@@ -20,7 +30,8 @@ export interface WorktreePickerIO {
|
|
|
20
30
|
rows?: number;
|
|
21
31
|
};
|
|
22
32
|
}
|
|
23
|
-
export declare function buildWorktreePromptEntries(sources: WorktreePromptSource[]): Promise<WorktreePromptEntry[]>;
|
|
33
|
+
export declare function buildWorktreePromptEntries(sources: WorktreePromptSource[], dependencies?: BuildWorktreePromptEntriesDependencies): Promise<WorktreePromptEntry[]>;
|
|
24
34
|
export declare function resolveWorktreeQuery(sources: WorktreePromptSource[], query: string): WorktreePromptSource | null;
|
|
25
35
|
export declare function promptForSingleWorktree(message: string, worktrees: WorktreePromptEntry[], io?: WorktreePickerIO): Promise<string | null>;
|
|
26
36
|
export declare function promptForMultipleWorktrees(message: string, worktrees: WorktreePromptEntry[], io?: WorktreePickerIO): Promise<string[] | null>;
|
|
37
|
+
export declare function formatWorktreeBranchLabel(branch: string | null, pullRequestNumbers?: number[]): string;
|