@solaqua/gji 0.9.0 → 0.11.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 +93 -20
- 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 +25 -3
- package/dist/cli.js +105 -19
- 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/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/done.d.ts +10 -0
- package/dist/done.js +208 -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 +5489 -1206
- package/dist/go-resolver.d.ts +35 -0
- package/dist/go-resolver.js +185 -0
- package/dist/go.d.ts +5 -2
- package/dist/go.js +262 -62
- package/dist/hooks.d.ts +1 -1
- package/dist/hooks.js +13 -7
- package/dist/init.js +30 -22
- package/dist/install-prompt.d.ts +1 -0
- package/dist/install-prompt.js +3 -25
- 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 +434 -36
- package/dist/open.d.ts +1 -0
- package/dist/open.js +14 -2
- package/dist/pr.d.ts +6 -2
- package/dist/pr.js +135 -30
- package/dist/remove.js +20 -0
- package/dist/repo.d.ts +3 -0
- package/dist/repo.js +57 -6
- package/dist/safe-destination.d.ts +15 -0
- package/dist/safe-destination.js +166 -0
- package/dist/shell-completion.js +47 -14
- 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/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 +93 -0
- package/dist/worktree-management.d.ts +1 -0
- package/dist/worktree-management.js +23 -4
- package/dist/worktree-picker.d.ts +21 -8
- package/dist/worktree-picker.js +107 -15
- 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 +3 -0
- package/dist/worktree-sources.js +31 -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 +9 -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 +4 -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-undo.1 +16 -0
- package/man/man1/gji-warp.1 +3 -6
- package/man/man1/gji.1 +13 -5
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ gji new feature/payment-refactor # new branch + worktree, cd in
|
|
|
11
11
|
gji pr 1234 # review PR in isolation, cd in
|
|
12
12
|
gji pr open # open the PR for the current worktree
|
|
13
13
|
gji go main # jump back, shell changes directory
|
|
14
|
-
gji
|
|
14
|
+
gji done feature/payment-refactor
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
## Before / After
|
|
@@ -107,12 +107,13 @@ gji status
|
|
|
107
107
|
gji go feature/dark-mode
|
|
108
108
|
gji go main
|
|
109
109
|
|
|
110
|
-
# open
|
|
110
|
+
# open the current worktree in an editor
|
|
111
111
|
gji open
|
|
112
|
+
gji open --select # choose another worktree interactively
|
|
112
113
|
gji open feature/dark-mode --editor code
|
|
113
114
|
|
|
114
|
-
#
|
|
115
|
-
gji
|
|
115
|
+
# finish a worktree when done
|
|
116
|
+
gji done feature/dark-mode
|
|
116
117
|
```
|
|
117
118
|
|
|
118
119
|
Worktrees land at a deterministic path so your editor bookmarks and scripts always know where to look:
|
|
@@ -127,13 +128,22 @@ Set `worktreePath` in your config to use a different base (e.g. `"~/worktrees"`
|
|
|
127
128
|
|
|
128
129
|
```sh
|
|
129
130
|
gji new feature/auth-refactor # new branch + worktree
|
|
131
|
+
gji new feature/auth-tests --from-current # branch from the current worktree
|
|
130
132
|
gji new --detached # scratch space, auto-named
|
|
131
133
|
|
|
132
134
|
gji pr 1234 # checkout PR locally
|
|
133
135
|
gji pr https://github.com/org/repo/pull/1234 # or paste the URL
|
|
134
136
|
|
|
135
137
|
gji go feature/auth-refactor # jump to a worktree
|
|
138
|
+
gji go teammate-branch # open an existing local or remote branch
|
|
139
|
+
gji go - # return to the previous worktree
|
|
140
|
+
gji go --root # return to the main repository checkout
|
|
136
141
|
gji root # jump to repo root
|
|
142
|
+
gji warp repo-a/main # compatibility spelling for cross-repo navigation
|
|
143
|
+
gji back # return through navigation history
|
|
144
|
+
gji history # inspect recent navigation
|
|
145
|
+
|
|
146
|
+
# with no branch, the chooser starts in the current repo; press Tab for all repos
|
|
137
147
|
|
|
138
148
|
gji status # health overview + ahead/behind counts
|
|
139
149
|
gji ls # list with status/upstream/last commit
|
|
@@ -144,7 +154,7 @@ gji sync --all # rebase every worktree
|
|
|
144
154
|
|
|
145
155
|
gji clean # interactive bulk cleanup
|
|
146
156
|
gji clean --stale # only target safe stale cleanup candidates
|
|
147
|
-
gji
|
|
157
|
+
gji done feature/auth-refactor # finish one worktree and its branch
|
|
148
158
|
|
|
149
159
|
gji trigger-hook afterCreate # re-run setup in the current worktree
|
|
150
160
|
```
|
|
@@ -229,24 +239,38 @@ path=$(gji root --print)
|
|
|
229
239
|
|
|
230
240
|
| Command | Description |
|
|
231
241
|
|---|---|
|
|
232
|
-
| `gji new [branch] [--detached] [--open] [--editor <cli>] [--json]` | create branch + worktree
|
|
242
|
+
| `gji new [branch] [--from-current] [--detached] [--no-fetch] [--take] [--copy] [--force] [--open] [--editor <cli>] [--dry-run] [--json]` | create branch + worktree from a refreshed remote base by default; `--no-fetch` opts out |
|
|
243
|
+
| `gji done [branch] [--force] [--keep-branch] [--json]` | safely finish a linked worktree and return |
|
|
244
|
+
| `gji undo [id] [--list] [--json]` | restore a journaled cleanup without overwriting work |
|
|
233
245
|
| `gji pr <ref> [--json]` | fetch PR ref, create worktree, cd in |
|
|
234
246
|
| `gji pr open [branch|#N] [--select]` | open the current worktree PR, or choose a linked worktree with `--select` |
|
|
235
|
-
| `gji
|
|
236
|
-
| `gji
|
|
247
|
+
| `gji back [n] [--print]` | return to a previous worktree from navigation history |
|
|
248
|
+
| `gji history [--json]` | show navigation history |
|
|
249
|
+
| `gji warp [branch] [--print] [--json]` | compatibility spelling for cross-repository navigation |
|
|
250
|
+
| `gji open [branch] [--select] [--editor <cli>] [--save] [--workspace]` | open the current or selected worktree in an editor |
|
|
251
|
+
| `gji go [branch] [--root] [--print] [--json]` | resolve and jump to a worktree, branch, remote, or PR |
|
|
237
252
|
| `gji root [--print]` | jump to the main repo root |
|
|
238
253
|
| `gji status [--json]` | repo overview, worktree health, ahead/behind |
|
|
239
254
|
| `gji ls [--compact] [--json]` | list active worktrees |
|
|
240
255
|
| `gji sync [--all]` | fetch and rebase worktrees onto default branch |
|
|
241
256
|
| `gji sync-files [list\|add\|remove] [paths...]` | manage local files copied into new worktrees |
|
|
242
|
-
| `gji clean [--stale] [--force] [--json]` | interactively prune linked worktrees |
|
|
243
|
-
| `gji remove [branch] [--force] [--json]` |
|
|
257
|
+
| `gji clean [--stale] [--force] [--dry-run] [--json]` | interactively prune linked worktrees |
|
|
258
|
+
| `gji remove [branch] [--force] [--dry-run] [--json]` (`rm`) | **deprecated**; use `gji done` for one worktree or `gji clean` for bulk cleanup |
|
|
244
259
|
| `gji trigger-hook <hook>` | run a hook in the current worktree |
|
|
245
260
|
| `gji config [get\|set\|unset] [key] [value]` | manage global defaults |
|
|
246
261
|
| `gji init [shell]` | interactively set up onboarding, or print/install a shell wrapper |
|
|
247
262
|
| `gji doctor [--json] [--fix] [--yes]` | check installation and configuration health; optionally remove stale repository entries |
|
|
248
263
|
| `gji completion [shell]` | print shell completion definitions |
|
|
249
264
|
|
|
265
|
+
The repository registry records projects that gji has visited so `go`, `warp`,
|
|
266
|
+
and the chooser can resolve worktrees across repositories. It is advisory
|
|
267
|
+
metadata: missing paths are skipped and `gji doctor --fix` can remove stale
|
|
268
|
+
entries.
|
|
269
|
+
|
|
270
|
+
`gji remove` and its `rm` alias remain available during the deprecation window,
|
|
271
|
+
but print a migration warning in human-readable mode. Use `gji done <branch>`
|
|
272
|
+
to finish one linked worktree or `gji clean` to prune several worktrees.
|
|
273
|
+
|
|
250
274
|
## Configuration
|
|
251
275
|
|
|
252
276
|
No setup required. Optional config lives in:
|
|
@@ -264,8 +288,11 @@ No setup required. Optional config lives in:
|
|
|
264
288
|
| `syncRemote` | remote for `gji sync` (default: `origin`) |
|
|
265
289
|
| `syncDefaultBranch` | branch to rebase onto (default: remote `HEAD`) |
|
|
266
290
|
| `syncFiles` | files to copy from main worktree into each new worktree; use global per-repo config for private files |
|
|
291
|
+
| `syncDirs` | arbitrary directories to clone with filesystem copy-on-write before sync files |
|
|
292
|
+
| `dependencyBootstrap` | dependency/build-state policy: `off`, `cow-then-repair`, or `install-only` |
|
|
293
|
+
| `dependencyBuildCommand` | optional Cargo repair command used by `dependencyBootstrap` (default: `cargo check`) |
|
|
267
294
|
| `skipInstallPrompt` | `true` to disable the auto-install prompt permanently |
|
|
268
|
-
| `installSaveTarget` | `"local"` or `"global"` — where **Always**/**Never** choices are persisted (default: `"local"`); set during `gji init <shell> --write` |
|
|
295
|
+
| `installSaveTarget` | `"local"` or `"global"` — where dependency policy and legacy **Always**/**Never** choices are persisted (default: `"local"`); set during `gji init <shell> --write` |
|
|
269
296
|
| `hooks` | lifecycle scripts (see [Hooks](#hooks)) |
|
|
270
297
|
| `repos` | per-repo overrides inside the global config (see below) |
|
|
271
298
|
|
|
@@ -274,7 +301,9 @@ No setup required. Optional config lives in:
|
|
|
274
301
|
"branchPrefix": "feature/",
|
|
275
302
|
"syncRemote": "upstream",
|
|
276
303
|
"syncDefaultBranch": "main",
|
|
277
|
-
"syncFiles": [".env.example", ".nvmrc"]
|
|
304
|
+
"syncFiles": [".env.example", ".nvmrc"],
|
|
305
|
+
"syncDirs": [".next"],
|
|
306
|
+
"dependencyBootstrap": "cow-then-repair"
|
|
278
307
|
}
|
|
279
308
|
```
|
|
280
309
|
|
|
@@ -302,6 +331,42 @@ This stores:
|
|
|
302
331
|
}
|
|
303
332
|
```
|
|
304
333
|
|
|
334
|
+
### Instant directory bootstrap
|
|
335
|
+
|
|
336
|
+
Use `syncDirs` for advanced, arbitrary directories that should be available immediately in each new worktree. Dependency adapters discover their own project-local targets, so you do not need to list `node_modules`, `.venv`, or `target` here:
|
|
337
|
+
|
|
338
|
+
```json
|
|
339
|
+
{
|
|
340
|
+
"syncDirs": [".next", ".cache"]
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
`gji new` clones these directories with APFS copy-on-write on macOS or mandatory reflinks on Linux, before `syncFiles`. It never falls back to a slow ordinary copy. Unsupported filesystems, external symlink targets, missing sources, and existing destinations are skipped safely; failed CoW attempts are cached in `~/.config/gji/state.json` so repeated worktree creation does not keep waiting on the same unsupported filesystem. `syncDirs` is generic: it does not know or special-case package managers.
|
|
345
|
+
|
|
346
|
+
Paths are relative to the repository root. Absolute paths, `..` segments, and `.git` paths are rejected in all three config layers.
|
|
347
|
+
|
|
348
|
+
The human output includes clone timing; dry-run can provide source-size estimates:
|
|
349
|
+
|
|
350
|
+
```text
|
|
351
|
+
⚡ cloned .next (size unavailable → 1.2s)
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
Use `dependencyBootstrap` when a package manager or build cache needs a reusable seed followed by authoritative repair:
|
|
355
|
+
|
|
356
|
+
```json
|
|
357
|
+
{
|
|
358
|
+
"dependencyBootstrap": "cow-then-repair"
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
`cow-then-repair` supports pnpm (`node_modules` + `pnpm install --frozen-lockfile`), Yarn (`node_modules` + `yarn install --immutable`), uv (`.venv` + `uv sync --locked`), Cargo (`target` + the configured `dependencyBuildCommand`, or `cargo check`), and Bundler (`vendor/bundle` + `BUNDLE_PATH=vendor/bundle bundle install`). npm is install-only (`npm ci`) because it can delete an existing dependency tree; it never seeds `node_modules`. Only project-local targets are eligible, so global Ruby gems and package-manager caches are never cloned. CoW failure never triggers ordinary copying: repair runs from an empty target instead. The lifecycle is `CoW seed → syncFiles → repair/install → after-create`; a sync-file failure stops repair, install prompts, and hooks. A successful dependency seed is reported as `reused and repaired`, not as an install skip.
|
|
363
|
+
|
|
364
|
+
When a supported lockfile is detected and no `dependencyBootstrap` policy is configured, interactive `gji new` and `gji pr` ask which policy to persist: **Reuse and repair (recommended)**, **Install fresh each time**, or **Skip dependency setup**. The prompt explains the detected tool—for example, pnpm reuses local `node_modules` through CoW and then runs `pnpm install --frozen-lockfile`. The choice is saved locally or in the per-repo global config according to `installSaveTarget`. Headless, JSON, and dry-run commands never prompt and retain the safe `off` default.
|
|
365
|
+
|
|
366
|
+
Ecosystems dominated by global caches, such as Gradle, Maven, and Go, are not seeded until a safe project-local target and deterministic repair rule are available. Future adapters can add Composer, Poetry/PDM, Mix, Dart/Flutter, or .NET without changing `syncDirs`.
|
|
367
|
+
|
|
368
|
+
Use `gji new --dry-run` to see the directories and estimated sizes without creating anything. `gji new --json` adds a `cloned` array and structured `dependencyBootstrap` events, including machine-readable reasons for skips and failures. If bootstrap fails, the JSON error includes the created worktree path; text mode prints the same path and a cleanup hint. The benchmark target for a 2 GB dependency tree on supported APFS/Btrfs or XFS filesystems is under 5 seconds; benchmark your repository locally because filesystem and storage behavior determine the result.
|
|
369
|
+
|
|
305
370
|
### Per-repo overrides in global config
|
|
306
371
|
|
|
307
372
|
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:
|
|
@@ -349,7 +414,7 @@ Run scripts automatically at key lifecycle moments:
|
|
|
349
414
|
|---|---|
|
|
350
415
|
| `afterCreate` | after `gji new` or `gji pr` creates a worktree |
|
|
351
416
|
| `afterEnter` | after `gji go` switches to a worktree |
|
|
352
|
-
| `beforeRemove` | before `gji
|
|
417
|
+
| `beforeRemove` | before `gji done` or `gji clean` deletes a worktree |
|
|
353
418
|
|
|
354
419
|
Hooks receive `{{branch}}`, `{{path}}`, `{{repo}}` as template variables and `GJI_BRANCH`, `GJI_PATH`, `GJI_REPO` as environment variables. A failing hook emits a warning but never aborts the command.
|
|
355
420
|
|
|
@@ -408,7 +473,9 @@ This is useful after cloning on a new machine, recovering a broken worktree, or
|
|
|
408
473
|
|
|
409
474
|
## Install prompt
|
|
410
475
|
|
|
411
|
-
|
|
476
|
+
For supported lockfiles, the dependency policy prompt above is the primary setup choice. Keep `after-create` hooks for project-specific work such as `pnpm run generate`, code generation, local-service setup, or environment-specific commands; hooks are not a second dependency-bootstrap configuration.
|
|
477
|
+
|
|
478
|
+
Projects without a supported dependency adapter can still use the legacy one-shot install prompt:
|
|
412
479
|
|
|
413
480
|
```
|
|
414
481
|
Run `pnpm install` in the new worktree?
|
|
@@ -420,6 +487,8 @@ Run `pnpm install` in the new worktree?
|
|
|
420
487
|
|
|
421
488
|
**Always** saves `hooks.afterCreate`; **Never** writes `skipInstallPrompt: true`. Where they are saved depends on `installSaveTarget` (see [Available keys](#available-keys)) — defaults to `.gji.json`.
|
|
422
489
|
|
|
490
|
+
`syncDirs` remains generic and never suppresses installation or repair. To automate supported dependency/build setup, use `dependencyBootstrap`; the adapter always runs its lockfile/build repair after `syncFiles`. Explicit policies in global defaults, per-repo global config, or `.gji.json` always win over prompting.
|
|
491
|
+
|
|
423
492
|
## JSON output
|
|
424
493
|
|
|
425
494
|
Every mutating command supports `--json` for scripting and AI agent use. Success goes to stdout, errors go to stderr with exit code 1.
|
|
@@ -427,18 +496,22 @@ Every mutating command supports `--json` for scripting and AI agent use. Success
|
|
|
427
496
|
```sh
|
|
428
497
|
# create
|
|
429
498
|
gji new --json feature/dark-mode
|
|
430
|
-
# → { "branch": "feature/dark-mode", "path": "/…/worktrees/repo/feature/dark-mode" }
|
|
499
|
+
# → { "branch": "feature/dark-mode", "path": "/…/worktrees/repo/feature/dark-mode", "repository": { "name": "repo", "root": "/…/repo" } }
|
|
431
500
|
|
|
432
501
|
# fetch PR
|
|
433
502
|
gji pr --json 1234
|
|
434
|
-
# → { "branch": "pr/1234", "path": "/…/worktrees/repo/pr/1234" }
|
|
503
|
+
# → { "branch": "pr/1234", "path": "/…/worktrees/repo/pr/1234", "repository": { "name": "repo", "root": "/…/repo" } }
|
|
504
|
+
|
|
505
|
+
# resolve an existing destination without changing directories
|
|
506
|
+
gji go --json feature/auth-refactor
|
|
507
|
+
# → { "branch": "feature/auth-refactor", "path": "/…/worktrees/repo/feature/auth-refactor", "repository": { "name": "repo", "root": "/…/repo" } }
|
|
435
508
|
|
|
436
509
|
# detailed list
|
|
437
510
|
gji ls --json
|
|
438
511
|
# → [{ "branch": "...", "status": "clean", "upstream": { "kind": "tracked", ... }, ... }]
|
|
439
512
|
|
|
440
|
-
#
|
|
441
|
-
gji
|
|
513
|
+
# finish one worktree
|
|
514
|
+
gji done --json --force feature/dark-mode
|
|
442
515
|
# → { "branch": "feature/dark-mode", "path": "/…", "deleted": true }
|
|
443
516
|
|
|
444
517
|
# bulk clean
|
|
@@ -455,7 +528,7 @@ gji clean --stale --json --force
|
|
|
455
528
|
|
|
456
529
|
`gji clean --stale` limits cleanup to clean branch worktrees whose upstream is gone and whose branch is already merged into the configured or remote default branch.
|
|
457
530
|
|
|
458
|
-
`--json` suppresses all interactive prompts. `--force` is required for `
|
|
531
|
+
`--json` suppresses all interactive prompts. Navigation results include a `repository` object with the stable repository `root` and display `name`. `--force` is required for `done` and `clean` in JSON mode. `branch` is `null` for detached worktrees.
|
|
459
532
|
|
|
460
533
|
`gji ls --json` and `gji status --json` also produce structured output — see `gji status --json | jq` for the full schema.
|
|
461
534
|
|
|
@@ -463,7 +536,7 @@ gji clean --stale --json --force
|
|
|
463
536
|
|
|
464
537
|
```sh
|
|
465
538
|
GJI_NO_TUI=1 gji new feature/ci-branch
|
|
466
|
-
GJI_NO_TUI=1 gji
|
|
539
|
+
GJI_NO_TUI=1 gji done --force feature/ci-branch
|
|
467
540
|
GJI_NO_TUI=1 gji clean --force
|
|
468
541
|
```
|
|
469
542
|
|
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
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { DependencyBootstrapMode } from "./config.js";
|
|
2
|
+
import { type BootstrapTarget, type DependencyBootstrapPreview } from "./dependency-bootstrap.js";
|
|
3
|
+
export declare function createDependencyBootstrapPreview(mode: DependencyBootstrapMode, context: {
|
|
4
|
+
repoRoot: string;
|
|
5
|
+
currentRoot?: string;
|
|
6
|
+
worktreePath: string;
|
|
7
|
+
cargoBuildCommand?: string;
|
|
8
|
+
checkUvRuntime?: (target: BootstrapTarget) => Promise<boolean>;
|
|
9
|
+
}): Promise<DependencyBootstrapPreview>;
|
|
10
|
+
export declare function formatDependencyBootstrapPreview(preview: DependencyBootstrapPreview | undefined): string;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { prepareDependencyBootstrap, previewDependencyBootstrap, } from "./dependency-bootstrap.js";
|
|
2
|
+
export async function createDependencyBootstrapPreview(mode, context) {
|
|
3
|
+
return previewDependencyBootstrap(await prepareDependencyBootstrap(mode, context));
|
|
4
|
+
}
|
|
5
|
+
export function formatDependencyBootstrapPreview(preview) {
|
|
6
|
+
if (!preview)
|
|
7
|
+
return "";
|
|
8
|
+
return preview.targets
|
|
9
|
+
.map(({ adapter, target, strategy }) => `Would ${formatBootstrapStrategy(strategy)} ${target} with ${adapter}\n`)
|
|
10
|
+
.join("");
|
|
11
|
+
}
|
|
12
|
+
function formatBootstrapStrategy(strategy) {
|
|
13
|
+
switch (strategy) {
|
|
14
|
+
case "cow-then-repair":
|
|
15
|
+
return "seed and repair";
|
|
16
|
+
case "repair-only":
|
|
17
|
+
return "repair";
|
|
18
|
+
case "install-only":
|
|
19
|
+
return "install";
|
|
20
|
+
}
|
|
21
|
+
}
|
package/dist/clean.js
CHANGED
|
@@ -2,8 +2,9 @@ import { confirm, isCancel } from "@clack/prompts";
|
|
|
2
2
|
import { loadEffectiveConfig } from "./config.js";
|
|
3
3
|
import { isBranchMergedInto, readWorktreeHealth, resolveRemoteDefaultBranch, runGit, } from "./git.js";
|
|
4
4
|
import { isHeadless } from "./headless.js";
|
|
5
|
+
import { finalizeUndoOperation, recordUndoOperation } from "./undo.js";
|
|
5
6
|
import { formatLastCommit, formatUpstreamState, readWorktreeInfos, serializeWorktreeInfo, } from "./worktree-info.js";
|
|
6
|
-
import { deleteBranch, forceDeleteBranch, forceRemoveWorktree, isBranchUnmergedError, isWorktreeDeletionError, isWorktreeForceRemovalError, loadLinkedWorktrees, removeWorktree, } from "./worktree-management.js";
|
|
7
|
+
import { deleteBranch, forceDeleteBranch, forceRemoveWorktree, isBranchUnmergedError, isSubmoduleWorktreeRemovalError, isWorktreeDeletionError, isWorktreeForceRemovalError, loadLinkedWorktrees, removeWorktree, } from "./worktree-management.js";
|
|
7
8
|
import { buildWorktreePromptEntries, promptForMultipleWorktrees, } from "./worktree-picker.js";
|
|
8
9
|
import { defaultConfirmForceDeleteBranch, defaultConfirmForceRemoveWorktree, } from "./worktree-prompts.js";
|
|
9
10
|
export function createCleanCommand(dependencies = {}) {
|
|
@@ -79,6 +80,18 @@ export function createCleanCommand(dependencies = {}) {
|
|
|
79
80
|
}
|
|
80
81
|
const removedWorktrees = [];
|
|
81
82
|
const failures = [];
|
|
83
|
+
let journal;
|
|
84
|
+
try {
|
|
85
|
+
journal = await recordUndoOperation("clean", repository.repoRoot, selectedWorktrees);
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
emitError(options, `could not write undo journal; no worktrees were removed: ${toMessage(error)}`);
|
|
89
|
+
return 1;
|
|
90
|
+
}
|
|
91
|
+
if (!journal) {
|
|
92
|
+
emitError(options, "could not capture undo state; no worktrees were removed");
|
|
93
|
+
return 1;
|
|
94
|
+
}
|
|
82
95
|
for (const worktree of selectedWorktrees) {
|
|
83
96
|
if (options.stale &&
|
|
84
97
|
!(await isStaleCleanupCandidate(repository.repoRoot, worktree, staleBaseRef))) {
|
|
@@ -97,7 +110,9 @@ export function createCleanCommand(dependencies = {}) {
|
|
|
97
110
|
});
|
|
98
111
|
continue;
|
|
99
112
|
}
|
|
100
|
-
if (options.stale &&
|
|
113
|
+
if (options.stale &&
|
|
114
|
+
!isWorktreeDeletionError(error) &&
|
|
115
|
+
!isSubmoduleWorktreeRemovalError(error)) {
|
|
101
116
|
options.stderr(`Skipped ${worktree.path}: no longer a safe stale cleanup candidate\n`);
|
|
102
117
|
continue;
|
|
103
118
|
}
|
|
@@ -129,7 +144,12 @@ export function createCleanCommand(dependencies = {}) {
|
|
|
129
144
|
}
|
|
130
145
|
catch (error) {
|
|
131
146
|
if (!isBranchUnmergedError(error)) {
|
|
132
|
-
|
|
147
|
+
failures.push({
|
|
148
|
+
branch: worktree.branch,
|
|
149
|
+
message: toMessage(error),
|
|
150
|
+
path: worktree.path,
|
|
151
|
+
});
|
|
152
|
+
continue;
|
|
133
153
|
}
|
|
134
154
|
if (options.force ||
|
|
135
155
|
(await confirmForceDeleteBranch(worktree.branch))) {
|
|
@@ -146,6 +166,7 @@ export function createCleanCommand(dependencies = {}) {
|
|
|
146
166
|
}
|
|
147
167
|
}
|
|
148
168
|
}
|
|
169
|
+
await finalizeUndoOperation(journal, removedWorktrees);
|
|
149
170
|
if (options.json) {
|
|
150
171
|
const removed = removedWorktrees.map((worktree) => {
|
|
151
172
|
const info = selectedInfoByPath.get(worktree.path);
|
|
@@ -160,6 +181,7 @@ export function createCleanCommand(dependencies = {}) {
|
|
|
160
181
|
reportCleanFailures(failures, options.stderr);
|
|
161
182
|
}
|
|
162
183
|
else {
|
|
184
|
+
options.stderr("undo: gji undo\n");
|
|
163
185
|
options.stdout(`${repository.repoRoot}\n`);
|
|
164
186
|
}
|
|
165
187
|
return failures.length === 0 ? 0 : 1;
|