@solaqua/gji 0.8.1 → 0.10.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 +29 -33
- package/dist/browser.d.ts +7 -0
- package/dist/browser.js +37 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +63 -4
- package/dist/doctor.d.ts +28 -0
- package/dist/doctor.js +454 -0
- package/dist/gji-bundle.mjs +2507 -359
- 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/new.d.ts +1 -0
- package/dist/new.js +24 -1
- package/dist/open.d.ts +3 -1
- package/dist/open.js +16 -3
- 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/repo-registry.d.ts +5 -0
- package/dist/repo-registry.js +87 -14
- package/dist/shell-completion.js +284 -14
- 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-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 +4 -1
- package/man/man1/gji-open.1 +4 -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=()
|
|
@@ -69,16 +137,40 @@ _gji_completion() {
|
|
|
69
137
|
|
|
70
138
|
case "$command_name" in
|
|
71
139
|
new)
|
|
72
|
-
COMPREPLY=( $(compgen -W "--detached --force --open --editor --dry-run --json --help" -- "$cur") )
|
|
140
|
+
COMPREPLY=( $(compgen -W "--detached --from-current --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") )
|
|
@@ -87,7 +179,7 @@ _gji_completion() {
|
|
|
87
179
|
COMPREPLY=( $(compgen -W "--json --help" -- "$cur") )
|
|
88
180
|
;;
|
|
89
181
|
open)
|
|
90
|
-
COMPREPLY=( $(compgen -W "$(__gji_worktree_branches) --editor --save --workspace --help" -- "$cur") )
|
|
182
|
+
COMPREPLY=( $(compgen -W "$(__gji_worktree_branches) --editor --select --save --workspace --help" -- "$cur") )
|
|
91
183
|
;;
|
|
92
184
|
go|jump)
|
|
93
185
|
COMPREPLY=( $(compgen -W "$(__gji_worktree_branches) --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
|
|
@@ -177,6 +364,7 @@ complete -c gji -f
|
|
|
177
364
|
${commandLines}
|
|
178
365
|
|
|
179
366
|
complete -c gji -n '__fish_seen_subcommand_from new' -l detached -d 'create a detached worktree without a branch'
|
|
367
|
+
complete -c gji -n '__fish_seen_subcommand_from new' -l from-current -d 'base the new branch on the current worktree instead of the main worktree'
|
|
180
368
|
complete -c gji -n '__fish_seen_subcommand_from new' -l force -d 'remove and recreate the worktree if the target path already exists'
|
|
181
369
|
complete -c gji -n '__fish_seen_subcommand_from new' -l open -d 'open the new worktree in an editor after creation'
|
|
182
370
|
complete -c gji -n '__fish_seen_subcommand_from new' -l editor -r -d 'editor CLI to use with --open (code, cursor, zed, …)'
|
|
@@ -184,20 +372,29 @@ complete -c gji -n '__fish_seen_subcommand_from new' -l dry-run -d 'show what wo
|
|
|
184
372
|
complete -c gji -n '__fish_seen_subcommand_from new' -l json -d 'emit JSON on success or error instead of human-readable output'
|
|
185
373
|
|
|
186
374
|
complete -c gji -n '__fish_seen_subcommand_from init' -l write -d 'write the integration to the shell config file'
|
|
375
|
+
complete -c gji -n '__fish_seen_subcommand_from init' -l json -d 'emit a JSON error in non-interactive mode'
|
|
187
376
|
${shellLines}
|
|
188
377
|
|
|
378
|
+
complete -c gji -n '__fish_seen_subcommand_from doctor' -l fix -d 'apply safe automatic fixes after showing the plan'
|
|
379
|
+
complete -c gji -n '__fish_seen_subcommand_from doctor' -l yes -d 'apply --fix without prompting'
|
|
380
|
+
complete -c gji -n '__fish_seen_subcommand_from doctor' -l json -d 'emit diagnostic checks as JSON'
|
|
381
|
+
|
|
189
382
|
complete -c gji -n '__fish_seen_subcommand_from completion' -a 'bash' -d 'shell'
|
|
190
383
|
complete -c gji -n '__fish_seen_subcommand_from completion' -a 'fish' -d 'shell'
|
|
191
384
|
complete -c gji -n '__fish_seen_subcommand_from completion' -a 'zsh' -d 'shell'
|
|
192
385
|
|
|
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'
|
|
386
|
+
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'
|
|
387
|
+
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'
|
|
388
|
+
complete -c gji -n '__fish_seen_subcommand_from pr' -a 'open' -d 'open a pull request in the default browser'
|
|
389
|
+
complete -c gji -n '__gji_should_complete_pr_select' -l select -d 'choose a pull request from any linked worktree'
|
|
390
|
+
complete -c gji -n '__gji_should_complete_pr_target' -a '(__gji_pr_targets)' -d 'branch or PR number'
|
|
195
391
|
|
|
196
392
|
complete -c gji -n '__fish_seen_subcommand_from back' -l print -d 'print the resolved worktree path explicitly'
|
|
197
393
|
|
|
198
394
|
complete -c gji -n '__fish_seen_subcommand_from history' -l json -d 'print history as JSON'
|
|
199
395
|
|
|
200
396
|
complete -c gji -n '__fish_seen_subcommand_from open' -l editor -r -d 'editor CLI to use (code, cursor, zed, windsurf, subl, …)'
|
|
397
|
+
complete -c gji -n '__fish_seen_subcommand_from open' -l select -d 'choose a worktree with the interactive selector'
|
|
201
398
|
complete -c gji -n '__fish_seen_subcommand_from open' -l save -d 'save the chosen editor to global config'
|
|
202
399
|
complete -c gji -n '__fish_seen_subcommand_from open' -l workspace -d 'generate a .code-workspace file before opening (VS Code / Cursor / Windsurf)'
|
|
203
400
|
complete -c gji -n '__fish_seen_subcommand_from open' -a '(__gji_worktree_branches)' -d 'worktree branch'
|
|
@@ -251,30 +448,99 @@ __gji_worktree_branches() {
|
|
|
251
448
|
command gji ls --compact 2>/dev/null | awk 'NR > 1 { branch = ($1 == "*" ? $2 : $1); if (branch != "(detached)") print branch }'
|
|
252
449
|
}
|
|
253
450
|
|
|
451
|
+
typeset -g __gji_pr_targets_cache=()
|
|
452
|
+
typeset -g __gji_pr_targets_cache_at=0
|
|
453
|
+
typeset -g __gji_pr_targets_cache_loaded=0
|
|
454
|
+
typeset -g __gji_pr_targets_cache_repo=""
|
|
455
|
+
|
|
456
|
+
__gji_run_bounded() {
|
|
457
|
+
local output_file pid status attempt
|
|
458
|
+
output_file=$(mktemp "\${TMPDIR:-/tmp}/gji-completion.XXXXXX") || return 124
|
|
459
|
+
"$@" >"$output_file" 2>/dev/null &
|
|
460
|
+
pid=$!
|
|
461
|
+
for ((attempt = 0; attempt < 20; attempt++)); do
|
|
462
|
+
if ! kill -0 "$pid" 2>/dev/null; then
|
|
463
|
+
wait "$pid"
|
|
464
|
+
status=$?
|
|
465
|
+
cat "$output_file"
|
|
466
|
+
rm -f "$output_file"
|
|
467
|
+
return "$status"
|
|
468
|
+
fi
|
|
469
|
+
sleep 0.1
|
|
470
|
+
done
|
|
471
|
+
kill "$pid" 2>/dev/null
|
|
472
|
+
wait "$pid" 2>/dev/null
|
|
473
|
+
rm -f "$output_file"
|
|
474
|
+
return 124
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
__gji_pr_targets() {
|
|
478
|
+
local now repo_key
|
|
479
|
+
local -a branches targets
|
|
480
|
+
now=$(date +%s)
|
|
481
|
+
repo_key=$(command git rev-parse --show-toplevel 2>/dev/null)
|
|
482
|
+
if (( __gji_pr_targets_cache_loaded == 1 && "\${repo_key}" == "\${__gji_pr_targets_cache_repo}" && now - __gji_pr_targets_cache_at < 30 )); then
|
|
483
|
+
print -rl -- "\${__gji_pr_targets_cache[@]}"
|
|
484
|
+
return
|
|
485
|
+
fi
|
|
486
|
+
|
|
487
|
+
branches=("\${(@f)$(__gji_worktree_branches)}")
|
|
488
|
+
targets=()
|
|
489
|
+
if [[ -n "\${repo_key}" && $+commands[gh] -eq 1 ]]; then
|
|
490
|
+
targets=("\${(@f)$(__gji_run_bounded env GH_PROMPT_DISABLED=1 gh pr list --state open --json number --limit 100 |
|
|
491
|
+
tr '{},' '\\n' | sed -n 's/.*"number":[[:space:]]*\\([0-9][0-9]*\\).*/\\1/p')}")
|
|
492
|
+
elif [[ -n "\${repo_key}" && $+commands[glab] -eq 1 ]]; then
|
|
493
|
+
targets=("\${(@f)$(__gji_run_bounded env GLAB_NON_INTERACTIVE=1 glab mr list --state opened --output json --per-page 100 |
|
|
494
|
+
tr '{},' '\\n' | sed -n 's/.*"iid":[[:space:]]*\\([0-9][0-9]*\\).*/\\1/p')}")
|
|
495
|
+
elif [[ -n "\${repo_key}" && $+commands[bb] -eq 1 ]]; then
|
|
496
|
+
targets=("\${(@f)$(__gji_run_bounded bb pr list --state OPEN --format json |
|
|
497
|
+
tr '{},' '\\n' | sed -n 's/.*"id":[[:space:]]*\\([0-9][0-9]*\\).*/\\1/p')}")
|
|
498
|
+
fi
|
|
499
|
+
|
|
500
|
+
__gji_pr_targets_cache=("\${branches[@]}" "\${targets[@]}")
|
|
501
|
+
__gji_pr_targets_cache_at="$now"
|
|
502
|
+
__gji_pr_targets_cache_loaded=1
|
|
503
|
+
__gji_pr_targets_cache_repo="$repo_key"
|
|
504
|
+
print -rl -- "\${__gji_pr_targets_cache[@]}"
|
|
505
|
+
}
|
|
506
|
+
|
|
254
507
|
local context state line
|
|
255
|
-
local -a
|
|
508
|
+
local -a command_entries worktree_branches
|
|
256
509
|
|
|
257
|
-
|
|
510
|
+
command_entries=(
|
|
258
511
|
${commandLines}
|
|
259
512
|
)
|
|
260
513
|
|
|
261
514
|
if (( CURRENT == 2 )); then
|
|
262
|
-
_describe 'command'
|
|
515
|
+
_describe 'command' command_entries
|
|
263
516
|
return
|
|
264
517
|
fi
|
|
265
518
|
|
|
266
519
|
case "\${words[2]}" in
|
|
267
520
|
new)
|
|
268
|
-
_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: '
|
|
521
|
+
_arguments '--detached[create a detached worktree without a branch]' '--from-current[base the new branch on the current worktree instead of the main worktree]' '--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
522
|
;;
|
|
270
523
|
init)
|
|
271
|
-
_arguments '--write[write the integration to the shell config file]' '2:shell:(${shells})'
|
|
524
|
+
_arguments '--write[write the integration to the shell config file]' '--json[emit a JSON error in non-interactive mode]' '2:shell:(${shells})'
|
|
525
|
+
;;
|
|
526
|
+
doctor)
|
|
527
|
+
_arguments '--fix[apply safe automatic fixes after showing the plan]' '--yes[apply --fix without prompting]' '--json[emit diagnostic checks as JSON]'
|
|
272
528
|
;;
|
|
273
529
|
completion)
|
|
274
530
|
_arguments '2:shell:(${shells})'
|
|
275
531
|
;;
|
|
276
532
|
pr)
|
|
277
|
-
|
|
533
|
+
if [[ "\${words[3]}" == "open" ]]; then
|
|
534
|
+
if (( \${words[(I)--select]} )); then
|
|
535
|
+
_arguments
|
|
536
|
+
elif [[ -n "\${words[4]}" && "\${words[4]}" != -* ]]; then
|
|
537
|
+
_arguments
|
|
538
|
+
else
|
|
539
|
+
_arguments '--select[choose a pull request from any linked worktree]' '4:branch or PR number:->pr_targets'
|
|
540
|
+
fi
|
|
541
|
+
else
|
|
542
|
+
_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)'
|
|
543
|
+
fi
|
|
278
544
|
;;
|
|
279
545
|
back)
|
|
280
546
|
_arguments '--print[print the resolved worktree path explicitly]' '2:steps: '
|
|
@@ -283,7 +549,7 @@ case "\${words[2]}" in
|
|
|
283
549
|
_arguments '--json[print history as JSON]'
|
|
284
550
|
;;
|
|
285
551
|
open)
|
|
286
|
-
_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'
|
|
552
|
+
_arguments '--editor[editor CLI to use (code, cursor, zed, windsurf, subl, …)]:editor:' '--select[choose a worktree with the interactive selector]' '--save[save the chosen editor to global config]' '--workspace[generate a .code-workspace file before opening (VS Code / Cursor / Windsurf)]' '2:branch:->worktrees'
|
|
287
553
|
;;
|
|
288
554
|
go|jump)
|
|
289
555
|
_arguments '--print[print the resolved worktree path explicitly]' '2:branch:->worktrees'
|
|
@@ -337,6 +603,10 @@ case "$state" in
|
|
|
337
603
|
worktree_branches=(\${(@f)$(__gji_worktree_branches)})
|
|
338
604
|
_describe 'worktree branch' worktree_branches
|
|
339
605
|
;;
|
|
606
|
+
pr_targets)
|
|
607
|
+
worktree_branches=(\${(@f)$(__gji_pr_targets)})
|
|
608
|
+
_describe 'branch or PR number' worktree_branches
|
|
609
|
+
;;
|
|
340
610
|
config_keys)
|
|
341
611
|
_values 'config key' ${configKeys}
|
|
342
612
|
;;
|
|
@@ -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");
|
|
@@ -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;
|