@solaqua/gji 0.10.0 → 0.12.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 +83 -426
- package/dist/back.d.ts +3 -0
- package/dist/back.js +19 -7
- package/dist/bootstrap-output.d.ts +3 -0
- package/dist/bootstrap-output.js +27 -0
- package/dist/bootstrap-preview.d.ts +10 -0
- package/dist/bootstrap-preview.js +21 -0
- package/dist/clean.js +27 -3
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +140 -22
- package/dist/clone-failure-store.d.ts +18 -0
- package/dist/clone-failure-store.js +230 -0
- package/dist/command-runner.d.ts +6 -0
- package/dist/command-runner.js +38 -0
- package/dist/completion.js +7 -2
- package/dist/config-command.d.ts +1 -0
- package/dist/config-command.js +7 -1
- package/dist/config.d.ts +26 -1
- package/dist/config.js +110 -6
- package/dist/context-card.d.ts +1 -0
- package/dist/context-card.js +25 -0
- package/dist/dependency-bootstrap-prompt.d.ts +24 -0
- package/dist/dependency-bootstrap-prompt.js +103 -0
- package/dist/dependency-bootstrap.d.ts +129 -0
- package/dist/dependency-bootstrap.js +566 -0
- package/dist/dir-clone.d.ts +33 -0
- package/dist/dir-clone.js +505 -0
- package/dist/doctor.js +21 -0
- package/dist/done.d.ts +10 -0
- package/dist/done.js +210 -0
- package/dist/file-sync.js +71 -22
- package/dist/format-bytes.d.ts +1 -0
- package/dist/format-bytes.js +14 -0
- package/dist/fs-utils.d.ts +4 -0
- package/dist/fs-utils.js +26 -0
- package/dist/git.d.ts +5 -0
- package/dist/git.js +50 -0
- package/dist/gji-bundle.mjs +6189 -1313
- package/dist/go-resolver.d.ts +35 -0
- package/dist/go-resolver.js +185 -0
- package/dist/go.d.ts +6 -2
- package/dist/go.js +270 -62
- package/dist/hooks.d.ts +2 -1
- package/dist/hooks.js +20 -8
- package/dist/hub.d.ts +31 -0
- package/dist/hub.js +287 -0
- package/dist/init.js +30 -22
- package/dist/install-prompt.d.ts +1 -0
- package/dist/install-prompt.js +3 -25
- package/dist/ls.js +9 -1
- package/dist/navigation-output.d.ts +11 -0
- package/dist/navigation-output.js +6 -0
- package/dist/new.d.ts +14 -2
- package/dist/new.js +422 -38
- package/dist/pr-open.js +2 -10
- package/dist/pr.d.ts +6 -2
- package/dist/pr.js +135 -30
- package/dist/pull-requests.d.ts +1 -0
- package/dist/pull-requests.js +32 -3
- package/dist/remove.js +22 -0
- package/dist/repo.d.ts +4 -0
- package/dist/repo.js +61 -6
- package/dist/run-hook.js +2 -0
- package/dist/safe-destination.d.ts +15 -0
- package/dist/safe-destination.js +166 -0
- package/dist/shell-completion.js +52 -13
- package/dist/slots.d.ts +8 -0
- package/dist/slots.js +61 -0
- package/dist/status.d.ts +2 -0
- package/dist/status.js +9 -1
- package/dist/sync-directories.d.ts +33 -0
- package/dist/sync-directories.js +97 -0
- package/dist/sync-plan.d.ts +16 -0
- package/dist/sync-plan.js +128 -0
- package/dist/sync.js +7 -13
- package/dist/task-command.d.ts +9 -0
- package/dist/task-command.js +29 -0
- package/dist/task.d.ts +9 -0
- package/dist/task.js +37 -0
- package/dist/terminal-text.d.ts +4 -0
- package/dist/terminal-text.js +72 -0
- package/dist/undo.d.ts +36 -0
- package/dist/undo.js +392 -0
- package/dist/warp.d.ts +3 -1
- package/dist/warp.js +32 -125
- package/dist/worktree-bootstrap.d.ts +35 -0
- package/dist/worktree-bootstrap.js +96 -0
- package/dist/worktree-info.d.ts +6 -1
- package/dist/worktree-info.js +14 -4
- package/dist/worktree-management.d.ts +1 -0
- package/dist/worktree-management.js +23 -4
- package/dist/worktree-picker.d.ts +24 -11
- package/dist/worktree-picker.js +176 -95
- package/dist/worktree-prompts.js +1 -1
- package/dist/worktree-source.d.ts +6 -0
- package/dist/worktree-source.js +1 -0
- package/dist/worktree-sources.d.ts +4 -0
- package/dist/worktree-sources.js +77 -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-doctor.1 +1 -1
- package/man/man1/gji-done.1 +19 -0
- package/man/man1/gji-go.1 +12 -3
- 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 +21 -3
- package/man/man1/gji-open.1 +1 -1
- package/man/man1/gji-pr.1 +7 -1
- package/man/man1/gji-remove.1 +3 -3
- 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-task.1 +16 -0
- package/man/man1/gji-undo.1 +16 -0
- package/man/man1/gji-warp.1 +3 -6
- package/man/man1/gji.1 +17 -5
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -1,487 +1,144 @@
|
|
|
1
|
-
# gji — Git
|
|
1
|
+
# gji — Switch Git contexts without stashing
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Start, review, and switch between Git tasks without disturbing the work already open.
|
|
4
4
|
|
|
5
|
-
`gji`
|
|
5
|
+
`gji` makes Git worktrees feel like normal shell navigation. Each task gets its
|
|
6
|
+
own directory, dependencies, editor context, and terminal — so moving to a
|
|
7
|
+
pull request or experiment does not require stashing, checking out, or
|
|
8
|
+
reinstalling.
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
## Start your next task
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
gji new feature/payment-refactor # new branch + worktree, cd in
|
|
11
|
-
gji pr 1234 # review PR in isolation, cd in
|
|
12
|
-
gji pr open # open the PR for the current worktree
|
|
13
|
-
gji go main # jump back, shell changes directory
|
|
14
|
-
gji remove feature/payment-refactor
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
## Before / After
|
|
18
|
-
|
|
19
|
-
<table>
|
|
20
|
-
<tr>
|
|
21
|
-
<td width="50%" valign="top">
|
|
22
|
-
<strong>Before</strong><br />
|
|
23
|
-
<img src=".github/assets/readme-before.gif" alt="Traditional branch review flow with git stash, branch switching, reinstalling dependencies, and a merge conflict on stash pop." />
|
|
24
|
-
</td>
|
|
25
|
-
<td width="50%" valign="top">
|
|
26
|
-
<strong>After</strong><br />
|
|
27
|
-
<img src=".github/assets/readme-after.gif" alt="gji creating an isolated pull request worktree from the terminal in a few commands." />
|
|
28
|
-
</td>
|
|
29
|
-
</tr>
|
|
30
|
-
</table>
|
|
31
|
-
|
|
32
|
-
Maintainer note: `pnpm generate:readme-demos` currently expects macOS, `zsh`, Google Chrome, `asciinema`, and `ffmpeg`.
|
|
33
|
-
|
|
34
|
-
---
|
|
35
|
-
|
|
36
|
-
**If `gji` has saved you from a `git stash` spiral, a ⭐ on [GitHub](https://github.com/sjquant/gji) means a lot — it helps other developers find this tool.**
|
|
37
|
-
|
|
38
|
-
---
|
|
39
|
-
|
|
40
|
-
## The problem
|
|
41
|
-
|
|
42
|
-
You are deep in a feature branch. A colleague asks for a quick review. You:
|
|
43
|
-
|
|
44
|
-
1. stash your changes
|
|
45
|
-
2. checkout their branch
|
|
46
|
-
3. wait for `npm install` to finish
|
|
47
|
-
4. review
|
|
48
|
-
5. checkout back
|
|
49
|
-
6. pop your stash
|
|
50
|
-
7. realize something is broken
|
|
51
|
-
|
|
52
|
-
**Or you use `gji`, run `gji pr 1234`, and let the fresh worktree boot itself.**
|
|
53
|
-
|
|
54
|
-
## Why it matters more now
|
|
55
|
-
|
|
56
|
-
AI increases the amount of parallel work around a codebase.
|
|
57
|
-
|
|
58
|
-
It is increasingly normal to have:
|
|
59
|
-
|
|
60
|
-
1. your own branch open
|
|
61
|
-
2. another branch for review
|
|
62
|
-
3. a scratch space for testing an AI-generated change
|
|
63
|
-
4. a separate worktree for validating a risky migration or refactor
|
|
64
|
-
|
|
65
|
-
That makes Git worktrees more important, because a single shared checkout becomes the bottleneck. `gji` turns worktrees into a daily workflow instead of a Git power-user feature.
|
|
66
|
-
|
|
67
|
-
## Install
|
|
12
|
+
Run `gji new` and let the interactive flow guide you into a fresh worktree:
|
|
68
13
|
|
|
69
14
|
```sh
|
|
70
|
-
|
|
15
|
+
gji new
|
|
71
16
|
```
|
|
72
17
|
|
|
73
|
-
|
|
18
|
+
Choose a task name and setup options, then continue working in the new
|
|
19
|
+
directory. For repeatable workflows, you can still provide a branch directly:
|
|
74
20
|
|
|
75
21
|
```sh
|
|
76
|
-
gji
|
|
77
|
-
# restart your shell, or source the rc file shown by the wizard
|
|
78
|
-
gji doctor
|
|
22
|
+
gji new feature/payment-refactor
|
|
79
23
|
```
|
|
80
24
|
|
|
81
|
-
The
|
|
25
|
+
## The problem
|
|
82
26
|
|
|
83
|
-
|
|
84
|
-
|
|
27
|
+
You are halfway through a feature when someone asks you to review a pull
|
|
28
|
+
request. Without worktrees, a quick review often becomes:
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
stash → checkout → reinstall → review → checkout back → stash pop
|
|
85
32
|
```
|
|
86
33
|
|
|
87
|
-
|
|
34
|
+
With `gji`, the review gets its own workspace and your current task stays
|
|
35
|
+
untouched:
|
|
88
36
|
|
|
89
37
|
```sh
|
|
90
|
-
# start a new task
|
|
91
|
-
gji new feature/dark-mode
|
|
92
|
-
|
|
93
|
-
# start a task and open it straight in your editor
|
|
94
|
-
gji new feature/dark-mode --open --editor cursor
|
|
95
|
-
|
|
96
|
-
# review a pull request
|
|
97
38
|
gji pr 1234
|
|
98
|
-
gji pr open # open the PR for the current worktree
|
|
99
|
-
gji pr open --select # choose a PR from any linked worktree
|
|
100
|
-
gji pr open feature/auth-refactor # open the PR for a branch
|
|
101
|
-
gji pr open '#1234' # open an open PR directly
|
|
102
|
-
|
|
103
|
-
# see what's open
|
|
104
|
-
gji status
|
|
105
|
-
|
|
106
|
-
# jump between worktrees
|
|
107
|
-
gji go feature/dark-mode
|
|
108
|
-
gji go main
|
|
109
|
-
|
|
110
|
-
# open the current worktree in an editor
|
|
111
|
-
gji open
|
|
112
|
-
gji open --select # choose another worktree interactively
|
|
113
|
-
gji open feature/dark-mode --editor code
|
|
114
|
-
|
|
115
|
-
# clean up when done
|
|
116
|
-
gji remove feature/dark-mode
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
Worktrees land at a deterministic path so your editor bookmarks and scripts always know where to look:
|
|
120
|
-
|
|
121
|
-
```
|
|
122
|
-
../worktrees/<repo>/<branch>
|
|
123
39
|
```
|
|
124
40
|
|
|
125
|
-
|
|
41
|
+
<table>
|
|
42
|
+
<tr>
|
|
43
|
+
<td width="50%" valign="top">
|
|
44
|
+
<strong>Before</strong><br />
|
|
45
|
+
<img src=".github/assets/readme-before.gif" alt="Traditional branch switching with stash and reinstall steps" />
|
|
46
|
+
</td>
|
|
47
|
+
<td width="50%" valign="top">
|
|
48
|
+
<strong>After</strong><br />
|
|
49
|
+
<img src=".github/assets/readme-after.gif" alt="Creating an isolated pull request worktree with gji" />
|
|
50
|
+
</td>
|
|
51
|
+
</tr>
|
|
52
|
+
</table>
|
|
126
53
|
|
|
127
|
-
##
|
|
54
|
+
## Remember four commands
|
|
128
55
|
|
|
129
56
|
```sh
|
|
130
|
-
gji new
|
|
131
|
-
gji
|
|
132
|
-
gji
|
|
133
|
-
|
|
134
|
-
gji pr 1234 # checkout PR locally
|
|
135
|
-
gji pr https://github.com/org/repo/pull/1234 # or paste the URL
|
|
136
|
-
|
|
137
|
-
gji go feature/auth-refactor # jump to a worktree
|
|
138
|
-
gji root # jump to repo root
|
|
139
|
-
|
|
140
|
-
gji status # health overview + ahead/behind counts
|
|
141
|
-
gji ls # list with status/upstream/last commit
|
|
142
|
-
gji ls --compact # branch/path only
|
|
143
|
-
|
|
144
|
-
gji sync # rebase current worktree onto default branch
|
|
145
|
-
gji sync --all # rebase every worktree
|
|
146
|
-
|
|
147
|
-
gji clean # interactive bulk cleanup
|
|
148
|
-
gji clean --stale # only target safe stale cleanup candidates
|
|
149
|
-
gji remove feature/auth-refactor # remove one worktree and its branch
|
|
150
|
-
|
|
151
|
-
gji trigger-hook afterCreate # re-run setup in the current worktree
|
|
57
|
+
gji new # interactively start a task
|
|
58
|
+
gji pr 1234 # open a pull request in isolation
|
|
59
|
+
gji go main # jump to another worktree
|
|
60
|
+
gji done feature/payment-refactor # finish and clean up a task
|
|
152
61
|
```
|
|
153
62
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
`gji` sits between raw Git primitives and larger Git or repository tools:
|
|
157
|
-
|
|
158
|
-
- **vs raw `git worktree`**: same underlying capability, but with branch-first commands, shell handoff, PR checkout, hooks, sync, and cleanup built into the workflow
|
|
159
|
-
- **vs `lazygit`**: `lazygit` is a broad Git UI; `gji` is narrower and faster for opening, jumping between, and removing isolated branch directories
|
|
160
|
-
- **vs `ghq`**: `ghq` organizes where repositories live; `gji` organizes which branch, PR, or worktree you should be in once you are inside one
|
|
161
|
-
|
|
162
|
-
Use `gji` when your bottleneck is repeated context switching between features, reviews, and maintenance work without disturbing what is already open.
|
|
163
|
-
|
|
164
|
-
It is especially useful when those contexts are happening in parallel across both human and AI-assisted work.
|
|
165
|
-
|
|
166
|
-
See the full comparison in [website/docs/comparison.mdx](./website/docs/comparison.mdx).
|
|
167
|
-
|
|
168
|
-
## Shell setup
|
|
63
|
+
With shell integration enabled, each command moves your shell into the
|
|
64
|
+
selected worktree automatically.
|
|
169
65
|
|
|
170
|
-
|
|
66
|
+
## Install
|
|
171
67
|
|
|
172
68
|
```sh
|
|
69
|
+
npm install -g @solaqua/gji
|
|
173
70
|
gji init
|
|
174
|
-
gji doctor
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
`gji init` is interactive. For dotfiles or CI, the explicit shell commands remain available and preserve their existing output:
|
|
178
|
-
|
|
179
|
-
```sh
|
|
180
|
-
# zsh
|
|
181
|
-
echo 'eval "$(gji init zsh)"' >> ~/.zshrc
|
|
182
|
-
|
|
183
|
-
# bash
|
|
184
|
-
echo 'eval "$(gji init bash)"' >> ~/.bashrc
|
|
185
|
-
|
|
186
|
-
# fish
|
|
187
|
-
gji init fish --write
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
Install completions separately so your shell rc stays small:
|
|
191
|
-
|
|
192
|
-
```sh
|
|
193
|
-
# zsh
|
|
194
|
-
mkdir -p ~/.zsh/completions
|
|
195
|
-
gji completion zsh > ~/.zsh/completions/_gji
|
|
196
|
-
# add this before running compinit in ~/.zshrc
|
|
197
|
-
fpath=(~/.zsh/completions $fpath)
|
|
198
|
-
|
|
199
|
-
# bash
|
|
200
|
-
mkdir -p ~/.local/share/bash-completion/completions
|
|
201
|
-
gji completion bash > ~/.local/share/bash-completion/completions/gji
|
|
202
|
-
|
|
203
|
-
# fish
|
|
204
|
-
mkdir -p ~/.config/fish/completions
|
|
205
|
-
gji completion fish > ~/.config/fish/completions/gji.fish
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
After a reinstall or upgrade, refresh both the wrapper and the completion file:
|
|
209
|
-
|
|
210
|
-
```sh
|
|
211
|
-
# zsh
|
|
212
|
-
eval "$(gji init zsh)"
|
|
213
|
-
gji completion zsh > ~/.zsh/completions/_gji
|
|
214
|
-
# if zsh is already running, refresh completion discovery too
|
|
215
|
-
autoload -Uz compinit && compinit
|
|
216
|
-
|
|
217
|
-
# fish
|
|
218
|
-
gji init fish --write
|
|
219
|
-
gji completion fish > ~/.config/fish/completions/gji.fish
|
|
220
|
-
source ~/.config/fish/config.fish
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
For scripts that need the raw path, use `--print`:
|
|
224
|
-
|
|
225
|
-
```sh
|
|
226
|
-
path=$(gji go --print feature/dark-mode)
|
|
227
|
-
path=$(gji root --print)
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
## Commands
|
|
231
|
-
|
|
232
|
-
| Command | Description |
|
|
233
|
-
|---|---|
|
|
234
|
-
| `gji new [branch] [--from-current] [--detached] [--open] [--editor <cli>] [--json]` | create branch + worktree, optionally based on the current worktree |
|
|
235
|
-
| `gji pr <ref> [--json]` | fetch PR ref, create worktree, cd in |
|
|
236
|
-
| `gji pr open [branch|#N] [--select]` | open the current worktree PR, or choose a linked worktree with `--select` |
|
|
237
|
-
| `gji open [branch] [--select] [--editor <cli>] [--save] [--workspace]` | open the current or selected worktree in an editor |
|
|
238
|
-
| `gji go [branch] [--print]` | jump to a worktree |
|
|
239
|
-
| `gji root [--print]` | jump to the main repo root |
|
|
240
|
-
| `gji status [--json]` | repo overview, worktree health, ahead/behind |
|
|
241
|
-
| `gji ls [--compact] [--json]` | list active worktrees |
|
|
242
|
-
| `gji sync [--all]` | fetch and rebase worktrees onto default branch |
|
|
243
|
-
| `gji sync-files [list\|add\|remove] [paths...]` | manage local files copied into new worktrees |
|
|
244
|
-
| `gji clean [--stale] [--force] [--json]` | interactively prune linked worktrees |
|
|
245
|
-
| `gji remove [branch] [--force] [--json]` | remove a worktree and its branch |
|
|
246
|
-
| `gji trigger-hook <hook>` | run a hook in the current worktree |
|
|
247
|
-
| `gji config [get\|set\|unset] [key] [value]` | manage global defaults |
|
|
248
|
-
| `gji init [shell]` | interactively set up onboarding, or print/install a shell wrapper |
|
|
249
|
-
| `gji doctor [--json] [--fix] [--yes]` | check installation and configuration health; optionally remove stale repository entries |
|
|
250
|
-
| `gji completion [shell]` | print shell completion definitions |
|
|
251
|
-
|
|
252
|
-
## Configuration
|
|
253
|
-
|
|
254
|
-
No setup required. Optional config lives in:
|
|
255
|
-
|
|
256
|
-
- `~/.config/gji/config.json` — global defaults
|
|
257
|
-
- `.gji.json` — repo-local overrides (takes precedence)
|
|
258
|
-
|
|
259
|
-
### Available keys
|
|
260
|
-
|
|
261
|
-
| Key | Description |
|
|
262
|
-
|---|---|
|
|
263
|
-
| `branchPrefix` | prefix added to new branch names (e.g. `"feature/"`) |
|
|
264
|
-
| `editor` | default editor CLI for `gji open` and `gji new --open` (e.g. `"cursor"`, `"code"`, `"zed"`); set automatically with `gji open --save` |
|
|
265
|
-
| `worktreePath` | base directory for new worktrees (absolute or `~/…`); overrides the default `../worktrees/<repo>/` layout |
|
|
266
|
-
| `syncRemote` | remote for `gji sync` (default: `origin`) |
|
|
267
|
-
| `syncDefaultBranch` | branch to rebase onto (default: remote `HEAD`) |
|
|
268
|
-
| `syncFiles` | files to copy from main worktree into each new worktree; use global per-repo config for private files |
|
|
269
|
-
| `skipInstallPrompt` | `true` to disable the auto-install prompt permanently |
|
|
270
|
-
| `installSaveTarget` | `"local"` or `"global"` — where **Always**/**Never** choices are persisted (default: `"local"`); set during `gji init <shell> --write` |
|
|
271
|
-
| `hooks` | lifecycle scripts (see [Hooks](#hooks)) |
|
|
272
|
-
| `repos` | per-repo overrides inside the global config (see below) |
|
|
273
|
-
|
|
274
|
-
```json
|
|
275
|
-
{
|
|
276
|
-
"branchPrefix": "feature/",
|
|
277
|
-
"syncRemote": "upstream",
|
|
278
|
-
"syncDefaultBranch": "main",
|
|
279
|
-
"syncFiles": [".env.example", ".nvmrc"]
|
|
280
|
-
}
|
|
281
71
|
```
|
|
282
72
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
For private files, prefer the `sync-files` command. It writes to your global per-repo config so secret filenames do not need to be committed to `.gji.json`:
|
|
73
|
+
`gji init` interactively installs shell integration, completions, and an
|
|
74
|
+
available editor. Restart your shell, or source the rc file shown by the
|
|
75
|
+
wizard, then run:
|
|
288
76
|
|
|
289
77
|
```sh
|
|
290
|
-
gji
|
|
291
|
-
gji sync-files list
|
|
292
|
-
gji sync-files remove .npmrc
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
This stores:
|
|
296
|
-
|
|
297
|
-
```json
|
|
298
|
-
{
|
|
299
|
-
"repos": {
|
|
300
|
-
"/home/me/code/my-repo": {
|
|
301
|
-
"syncFiles": [".env.local"]
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
### Per-repo overrides in global config
|
|
308
|
-
|
|
309
|
-
If you work across many repositories, you can scope config to a specific repo inside `~/.config/gji/config.json` without adding a `.gji.json` to that repo:
|
|
310
|
-
|
|
311
|
-
```json
|
|
312
|
-
{
|
|
313
|
-
"branchPrefix": "feature/",
|
|
314
|
-
"repos": {
|
|
315
|
-
"/home/me/code/my-repo": {
|
|
316
|
-
"branchPrefix": "fix/",
|
|
317
|
-
"hooks": {
|
|
318
|
-
"afterCreate": "npm install"
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
}
|
|
78
|
+
gji doctor
|
|
323
79
|
```
|
|
324
80
|
|
|
325
|
-
|
|
81
|
+
Without shell integration, commands still print paths for scripts and other
|
|
82
|
+
non-interactive use. See the [installation guide](https://gji.solaqua.dev/docs/installation)
|
|
83
|
+
for explicit shell setup.
|
|
326
84
|
|
|
327
|
-
|
|
85
|
+
## A typical day
|
|
328
86
|
|
|
329
87
|
```sh
|
|
330
|
-
|
|
331
|
-
gji
|
|
332
|
-
gji config set branchPrefix feature/
|
|
333
|
-
gji config unset branchPrefix
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
## Hooks
|
|
337
|
-
|
|
338
|
-
Run scripts automatically at key lifecycle moments:
|
|
339
|
-
|
|
340
|
-
```json
|
|
341
|
-
{
|
|
342
|
-
"hooks": {
|
|
343
|
-
"afterCreate": ["pnpm", "install"],
|
|
344
|
-
"afterEnter": ["printf", "switched to %s\n", "{{branch}}"],
|
|
345
|
-
"beforeRemove": "pnpm run cleanup"
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
```
|
|
88
|
+
# Start feature work
|
|
89
|
+
gji new
|
|
349
90
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
| `afterCreate` | after `gji new` or `gji pr` creates a worktree |
|
|
353
|
-
| `afterEnter` | after `gji go` switches to a worktree |
|
|
354
|
-
| `beforeRemove` | before `gji remove` deletes a worktree |
|
|
91
|
+
# Check what is active
|
|
92
|
+
gji
|
|
355
93
|
|
|
356
|
-
|
|
94
|
+
# Review a PR without changing the current task
|
|
95
|
+
gji pr 1234
|
|
357
96
|
|
|
358
|
-
|
|
97
|
+
# Move between tasks
|
|
98
|
+
gji go feature/payment-refactor
|
|
99
|
+
gji back
|
|
359
100
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
"afterCreate": ["pnpm", "install"],
|
|
364
|
-
"afterEnter": ["printf", "switched to %s at %s\n", "{{branch}}", "{{path}}"]
|
|
365
|
-
}
|
|
366
|
-
}
|
|
101
|
+
# Keep worktrees current and remove finished work
|
|
102
|
+
gji sync
|
|
103
|
+
gji done feature/payment-refactor
|
|
367
104
|
```
|
|
368
105
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
Template values are interpolated before the shell parses string hooks, so avoid putting `{{branch}}`, `{{path}}`, or `{{repo}}` directly into shell strings. For shell-string hooks, the safer pattern is to use the environment variables and double-quote each expansion:
|
|
106
|
+
Worktrees use a predictable location by default:
|
|
372
107
|
|
|
373
|
-
```
|
|
374
|
-
|
|
375
|
-
"hooks": {
|
|
376
|
-
"afterCreate": "pnpm install && printf 'ready: %s\n' \"$GJI_PATH\""
|
|
377
|
-
}
|
|
378
|
-
}
|
|
108
|
+
```text
|
|
109
|
+
../worktrees/<repo>/<branch>
|
|
379
110
|
```
|
|
380
111
|
|
|
381
|
-
|
|
112
|
+
That keeps editor bookmarks, scripts, and terminal navigation stable.
|
|
382
113
|
|
|
383
|
-
|
|
114
|
+
## More capabilities
|
|
384
115
|
|
|
385
|
-
|
|
386
|
-
// ~/.config/gji/config.json
|
|
387
|
-
{ "hooks": { "afterCreate": "nvm use", "afterEnter": "echo hi" } }
|
|
116
|
+
Use the documentation when you need to go beyond the core workflow:
|
|
388
117
|
|
|
389
|
-
|
|
390
|
-
|
|
118
|
+
- [Daily workflow](https://gji.solaqua.dev/docs/daily-workflow) — navigation, PRs, sync, and cleanup
|
|
119
|
+
- [Commands](https://gji.solaqua.dev/docs/commands) — complete command reference
|
|
120
|
+
- [Shell integration](https://gji.solaqua.dev/docs/shell-integration) — zsh, bash, and fish setup
|
|
121
|
+
- [Configuration](https://gji.solaqua.dev/docs/configuration) — paths, defaults, and local files
|
|
122
|
+
- [Hooks](https://gji.solaqua.dev/docs/hooks) — automate setup and cleanup
|
|
123
|
+
- [Sync and cleanup](https://gji.solaqua.dev/docs/sync-and-cleanup) — maintain active worktrees
|
|
124
|
+
- [Comparison](https://gji.solaqua.dev/docs/comparison) — `gji` vs `git worktree`, `lazygit`, and `ghq`
|
|
125
|
+
- [FAQ and troubleshooting](https://gji.solaqua.dev/docs/faq) — common questions and fixes
|
|
391
126
|
|
|
392
|
-
|
|
393
|
-
{ "hooks": { "beforeRemove": "echo bye" } }
|
|
394
|
-
|
|
395
|
-
// effective
|
|
396
|
-
{ "hooks": { "afterCreate": "npm install", "afterEnter": "echo hi", "beforeRemove": "echo bye" } }
|
|
397
|
-
```
|
|
398
|
-
|
|
399
|
-
### Triggering hooks manually
|
|
400
|
-
|
|
401
|
-
Run any hook in the current worktree on demand:
|
|
127
|
+
`gji` also supports JSON output for scripts and AI-assisted workflows:
|
|
402
128
|
|
|
403
129
|
```sh
|
|
404
|
-
gji
|
|
405
|
-
gji trigger-hook afterEnter # re-run the enter script
|
|
406
|
-
gji trigger-hook beforeRemove # dry-run the cleanup script
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
This is useful after cloning on a new machine, recovering a broken worktree, or letting an AI agent bootstrap an already-existing worktree without needing interactive prompts.
|
|
410
|
-
|
|
411
|
-
## Install prompt
|
|
412
|
-
|
|
413
|
-
When `gji new` or `gji pr` creates a worktree, `gji` detects the project's package manager from its lockfile and offers to run the install command:
|
|
414
|
-
|
|
415
|
-
```
|
|
416
|
-
Run `pnpm install` in the new worktree?
|
|
417
|
-
› Yes run once
|
|
418
|
-
No skip this time
|
|
419
|
-
Always save as afterCreate hook
|
|
420
|
-
Never disable this prompt for this repo
|
|
421
|
-
```
|
|
422
|
-
|
|
423
|
-
**Always** saves `hooks.afterCreate`; **Never** writes `skipInstallPrompt: true`. Where they are saved depends on `installSaveTarget` (see [Available keys](#available-keys)) — defaults to `.gji.json`.
|
|
424
|
-
|
|
425
|
-
## JSON output
|
|
426
|
-
|
|
427
|
-
Every mutating command supports `--json` for scripting and AI agent use. Success goes to stdout, errors go to stderr with exit code 1.
|
|
428
|
-
|
|
429
|
-
```sh
|
|
430
|
-
# create
|
|
130
|
+
gji --json
|
|
431
131
|
gji new --json feature/dark-mode
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
# fetch PR
|
|
435
|
-
gji pr --json 1234
|
|
436
|
-
# → { "branch": "pr/1234", "path": "/…/worktrees/repo/pr/1234" }
|
|
437
|
-
|
|
438
|
-
# detailed list
|
|
439
|
-
gji ls --json
|
|
440
|
-
# → [{ "branch": "...", "status": "clean", "upstream": { "kind": "tracked", ... }, ... }]
|
|
441
|
-
|
|
442
|
-
# remove
|
|
443
|
-
gji remove --json --force feature/dark-mode
|
|
444
|
-
# → { "branch": "feature/dark-mode", "path": "/…", "deleted": true }
|
|
445
|
-
|
|
446
|
-
# bulk clean
|
|
447
|
-
gji clean --json --force
|
|
448
|
-
# → { "removed": [{ "branch": "...", "path": "..." }, …] }
|
|
449
|
-
|
|
450
|
-
# stale-only clean
|
|
451
|
-
gji clean --stale --json --force
|
|
452
|
-
# → { "removed": [{ "branch": "...", "path": "..." }, …] }
|
|
453
|
-
|
|
454
|
-
# error shape (any command)
|
|
455
|
-
# stderr → { "error": "branch argument is required" }
|
|
132
|
+
gji go --json feature/dark-mode
|
|
456
133
|
```
|
|
457
134
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
`--json` suppresses all interactive prompts. `--force` is required for `remove` and `clean` in JSON mode. `branch` is `null` for detached worktrees.
|
|
461
|
-
|
|
462
|
-
`gji ls --json` and `gji status --json` also produce structured output — see `gji status --json | jq` for the full schema.
|
|
135
|
+
Read the [full documentation](https://gji.solaqua.dev) for configuration,
|
|
136
|
+
dependency bootstrap, hooks, automation, and the complete CLI reference.
|
|
463
137
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
```sh
|
|
467
|
-
GJI_NO_TUI=1 gji new feature/ci-branch
|
|
468
|
-
GJI_NO_TUI=1 gji remove --force feature/ci-branch
|
|
469
|
-
GJI_NO_TUI=1 gji clean --force
|
|
470
|
-
```
|
|
471
|
-
|
|
472
|
-
`GJI_NO_TUI=1` disables all prompts. Commands that need confirmation require their non-interactive approval flag (`--force` for cleanup commands, `--yes` for `doctor --fix`). `--json` implies the same behaviour.
|
|
473
|
-
|
|
474
|
-
`gji pr open --select` requires an interactive terminal; plain `gji pr open` opens the PR for the current worktree without prompting.
|
|
475
|
-
If the current worktree has no open PR or has multiple open PRs in headless mode, pass `gji pr open <branch|#N>` explicitly.
|
|
476
|
-
|
|
477
|
-
Update notifications are also suppressed automatically in non-interactive and `--json` runs. Users can opt out explicitly with `NO_UPDATE_NOTIFIER=1` or `--no-update-notifier`.
|
|
478
|
-
|
|
479
|
-
## Notes
|
|
138
|
+
---
|
|
480
139
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
- `gji pr` fetches from `origin` using the first matching forge ref namespace: GitHub `refs/pull/<number>/head`, GitLab `refs/merge-requests/<number>/head`, then Bitbucket `refs/pull-requests/<number>/from`
|
|
484
|
-
- `gji pr open` reads open PRs from the `origin` forge (GitHub, GitLab, or Bitbucket), preferring an installed authenticated provider CLI and falling back to its public API
|
|
140
|
+
If `gji` has saved you from a `git stash` spiral, a ⭐ on
|
|
141
|
+
[GitHub](https://github.com/sjquant/gji) helps other developers find it.
|
|
485
142
|
|
|
486
143
|
## License
|
|
487
144
|
|
package/dist/back.d.ts
CHANGED
|
@@ -3,7 +3,10 @@ export declare const BACK_OUTPUT_FILE_ENV = "GJI_BACK_OUTPUT_FILE";
|
|
|
3
3
|
export interface BackCommandOptions {
|
|
4
4
|
cwd: string;
|
|
5
5
|
home?: string;
|
|
6
|
+
json?: boolean;
|
|
6
7
|
n?: number;
|
|
8
|
+
commandName?: string;
|
|
9
|
+
outputEnv?: string;
|
|
7
10
|
print?: boolean;
|
|
8
11
|
stderr: (chunk: string) => void;
|
|
9
12
|
stdout: (chunk: string) => void;
|
package/dist/back.js
CHANGED
|
@@ -3,6 +3,7 @@ import { basename } from "node:path";
|
|
|
3
3
|
import { loadEffectiveConfig } from "./config.js";
|
|
4
4
|
import { appendHistory, loadHistory } from "./history.js";
|
|
5
5
|
import { extractHooks, runHook } from "./hooks.js";
|
|
6
|
+
import { createNavigationRepository, createNavigationTarget, } from "./navigation-output.js";
|
|
6
7
|
import { detectRepository } from "./repo.js";
|
|
7
8
|
import { writeShellOutput } from "./shell-handoff.js";
|
|
8
9
|
export const BACK_OUTPUT_FILE_ENV = "GJI_BACK_OUTPUT_FILE";
|
|
@@ -31,10 +32,23 @@ export async function runBackCommand(options) {
|
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
if (!target) {
|
|
34
|
-
options.
|
|
35
|
-
options.
|
|
35
|
+
const commandName = options.commandName ?? "gji back";
|
|
36
|
+
if (options.json) {
|
|
37
|
+
options.stderr(`${JSON.stringify({ error: "no previous worktree in history" }, null, 2)}\n`);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
options.stderr(`${commandName}: no previous worktree in history\n`);
|
|
41
|
+
}
|
|
42
|
+
if (!options.json) {
|
|
43
|
+
options.stderr("Hint: Use 'gji go', 'gji new', or 'gji pr' to navigate between worktrees\n");
|
|
44
|
+
}
|
|
36
45
|
return 1;
|
|
37
46
|
}
|
|
47
|
+
if (options.json) {
|
|
48
|
+
const repository = await detectRepository(target.path);
|
|
49
|
+
options.stdout(`${JSON.stringify(createNavigationTarget(createNavigationRepository(repository.repoName, repository.repoRoot), target.path, target.branch), null, 2)}\n`);
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
38
52
|
try {
|
|
39
53
|
const repository = await detectRepository(target.path);
|
|
40
54
|
const config = await loadEffectiveConfig(repository.repoRoot, options.home, options.stderr);
|
|
@@ -49,21 +63,19 @@ export async function runBackCommand(options) {
|
|
|
49
63
|
// Not in a git repo or hooks unavailable — proceed without hook
|
|
50
64
|
}
|
|
51
65
|
await appendHistory(target.path, target.branch, options.home);
|
|
52
|
-
await writeShellOutput(BACK_OUTPUT_FILE_ENV, target.path, options.stdout);
|
|
66
|
+
await writeShellOutput(options.outputEnv ?? BACK_OUTPUT_FILE_ENV, target.path, options.stdout);
|
|
53
67
|
return 0;
|
|
54
68
|
}
|
|
55
69
|
export function formatHistoryList(history, cwd) {
|
|
56
70
|
const branchWidth = Math.max("BRANCH".length, ...history.map((e) => (e.branch ?? "(detached)").length));
|
|
57
|
-
const lines = [
|
|
58
|
-
" " + "BRANCH".padEnd(branchWidth) + " WHEN PATH",
|
|
59
|
-
];
|
|
71
|
+
const lines = [` ${"BRANCH".padEnd(branchWidth)} WHEN PATH`];
|
|
60
72
|
for (const entry of history) {
|
|
61
73
|
const isCurrent = entry.path === cwd;
|
|
62
74
|
const branch = (entry.branch ?? "(detached)").padEnd(branchWidth);
|
|
63
75
|
const when = formatAge(entry.timestamp).padEnd(10);
|
|
64
76
|
lines.push(`${isCurrent ? "*" : " "} ${branch} ${when} ${entry.path}`);
|
|
65
77
|
}
|
|
66
|
-
return lines.join("\n")
|
|
78
|
+
return `${lines.join("\n")}\n`;
|
|
67
79
|
}
|
|
68
80
|
export function formatAge(timestamp) {
|
|
69
81
|
const seconds = Math.floor((Date.now() - timestamp) / 1000);
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { DependencyBootstrapReporter } from "./dependency-bootstrap.js";
|
|
2
|
+
import type { SyncDirectoryReporter } from "./sync-directories.js";
|
|
3
|
+
export declare function createBootstrapReporter(write: (chunk: string) => void, json: boolean, measureCloneSize?: boolean): SyncDirectoryReporter & DependencyBootstrapReporter;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { formatBytes } from "./format-bytes.js";
|
|
2
|
+
export function createBootstrapReporter(write, json, measureCloneSize = false) {
|
|
3
|
+
return {
|
|
4
|
+
emitCachedFailureWarnings: !json,
|
|
5
|
+
measureCloneSize: measureCloneSize && !json,
|
|
6
|
+
write,
|
|
7
|
+
cloned: (directory) => {
|
|
8
|
+
if (json)
|
|
9
|
+
return;
|
|
10
|
+
write(`⚡ cloned ${directory.dir} (${formatBytes(directory.bytes)} → ${formatDuration(directory.ms)})\n`);
|
|
11
|
+
},
|
|
12
|
+
skipped: (directory) => {
|
|
13
|
+
if (json)
|
|
14
|
+
return;
|
|
15
|
+
write(`gji: skipped ${directory.dir} — ${directory.reason}\n`);
|
|
16
|
+
},
|
|
17
|
+
dependency: (event) => {
|
|
18
|
+
if (json)
|
|
19
|
+
return;
|
|
20
|
+
const target = event.target ? ` ${event.target}` : "";
|
|
21
|
+
write(`gji: ${event.state}${target} — ${event.message}\n`);
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function formatDuration(ms) {
|
|
26
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
27
|
+
}
|