@solaqua/gji 0.10.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 +89 -18
- 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 +101 -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 +5491 -1253
- 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 +13 -2
- package/dist/new.js +414 -39
- 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 +43 -12
- 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 +18 -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-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
|
|
@@ -112,8 +112,8 @@ gji open
|
|
|
112
112
|
gji open --select # choose another worktree interactively
|
|
113
113
|
gji open feature/dark-mode --editor code
|
|
114
114
|
|
|
115
|
-
#
|
|
116
|
-
gji
|
|
115
|
+
# finish a worktree when done
|
|
116
|
+
gji done feature/dark-mode
|
|
117
117
|
```
|
|
118
118
|
|
|
119
119
|
Worktrees land at a deterministic path so your editor bookmarks and scripts always know where to look:
|
|
@@ -135,7 +135,15 @@ gji pr 1234 # checkout PR locally
|
|
|
135
135
|
gji pr https://github.com/org/repo/pull/1234 # or paste the URL
|
|
136
136
|
|
|
137
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
|
|
138
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
|
|
139
147
|
|
|
140
148
|
gji status # health overview + ahead/behind counts
|
|
141
149
|
gji ls # list with status/upstream/last commit
|
|
@@ -146,7 +154,7 @@ gji sync --all # rebase every worktree
|
|
|
146
154
|
|
|
147
155
|
gji clean # interactive bulk cleanup
|
|
148
156
|
gji clean --stale # only target safe stale cleanup candidates
|
|
149
|
-
gji
|
|
157
|
+
gji done feature/auth-refactor # finish one worktree and its branch
|
|
150
158
|
|
|
151
159
|
gji trigger-hook afterCreate # re-run setup in the current worktree
|
|
152
160
|
```
|
|
@@ -231,24 +239,38 @@ path=$(gji root --print)
|
|
|
231
239
|
|
|
232
240
|
| Command | Description |
|
|
233
241
|
|---|---|
|
|
234
|
-
| `gji new [branch] [--from-current] [--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 |
|
|
235
245
|
| `gji pr <ref> [--json]` | fetch PR ref, create worktree, cd in |
|
|
236
246
|
| `gji pr open [branch|#N] [--select]` | open the current worktree PR, or choose a linked worktree with `--select` |
|
|
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 |
|
|
237
250
|
| `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 |
|
|
251
|
+
| `gji go [branch] [--root] [--print] [--json]` | resolve and jump to a worktree, branch, remote, or PR |
|
|
239
252
|
| `gji root [--print]` | jump to the main repo root |
|
|
240
253
|
| `gji status [--json]` | repo overview, worktree health, ahead/behind |
|
|
241
254
|
| `gji ls [--compact] [--json]` | list active worktrees |
|
|
242
255
|
| `gji sync [--all]` | fetch and rebase worktrees onto default branch |
|
|
243
256
|
| `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]` |
|
|
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 |
|
|
246
259
|
| `gji trigger-hook <hook>` | run a hook in the current worktree |
|
|
247
260
|
| `gji config [get\|set\|unset] [key] [value]` | manage global defaults |
|
|
248
261
|
| `gji init [shell]` | interactively set up onboarding, or print/install a shell wrapper |
|
|
249
262
|
| `gji doctor [--json] [--fix] [--yes]` | check installation and configuration health; optionally remove stale repository entries |
|
|
250
263
|
| `gji completion [shell]` | print shell completion definitions |
|
|
251
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
|
+
|
|
252
274
|
## Configuration
|
|
253
275
|
|
|
254
276
|
No setup required. Optional config lives in:
|
|
@@ -266,8 +288,11 @@ No setup required. Optional config lives in:
|
|
|
266
288
|
| `syncRemote` | remote for `gji sync` (default: `origin`) |
|
|
267
289
|
| `syncDefaultBranch` | branch to rebase onto (default: remote `HEAD`) |
|
|
268
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`) |
|
|
269
294
|
| `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` |
|
|
295
|
+
| `installSaveTarget` | `"local"` or `"global"` — where dependency policy and legacy **Always**/**Never** choices are persisted (default: `"local"`); set during `gji init <shell> --write` |
|
|
271
296
|
| `hooks` | lifecycle scripts (see [Hooks](#hooks)) |
|
|
272
297
|
| `repos` | per-repo overrides inside the global config (see below) |
|
|
273
298
|
|
|
@@ -276,7 +301,9 @@ No setup required. Optional config lives in:
|
|
|
276
301
|
"branchPrefix": "feature/",
|
|
277
302
|
"syncRemote": "upstream",
|
|
278
303
|
"syncDefaultBranch": "main",
|
|
279
|
-
"syncFiles": [".env.example", ".nvmrc"]
|
|
304
|
+
"syncFiles": [".env.example", ".nvmrc"],
|
|
305
|
+
"syncDirs": [".next"],
|
|
306
|
+
"dependencyBootstrap": "cow-then-repair"
|
|
280
307
|
}
|
|
281
308
|
```
|
|
282
309
|
|
|
@@ -304,6 +331,42 @@ This stores:
|
|
|
304
331
|
}
|
|
305
332
|
```
|
|
306
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
|
+
|
|
307
370
|
### Per-repo overrides in global config
|
|
308
371
|
|
|
309
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:
|
|
@@ -351,7 +414,7 @@ Run scripts automatically at key lifecycle moments:
|
|
|
351
414
|
|---|---|
|
|
352
415
|
| `afterCreate` | after `gji new` or `gji pr` creates a worktree |
|
|
353
416
|
| `afterEnter` | after `gji go` switches to a worktree |
|
|
354
|
-
| `beforeRemove` | before `gji
|
|
417
|
+
| `beforeRemove` | before `gji done` or `gji clean` deletes a worktree |
|
|
355
418
|
|
|
356
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.
|
|
357
420
|
|
|
@@ -410,7 +473,9 @@ This is useful after cloning on a new machine, recovering a broken worktree, or
|
|
|
410
473
|
|
|
411
474
|
## Install prompt
|
|
412
475
|
|
|
413
|
-
|
|
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:
|
|
414
479
|
|
|
415
480
|
```
|
|
416
481
|
Run `pnpm install` in the new worktree?
|
|
@@ -422,6 +487,8 @@ Run `pnpm install` in the new worktree?
|
|
|
422
487
|
|
|
423
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`.
|
|
424
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
|
+
|
|
425
492
|
## JSON output
|
|
426
493
|
|
|
427
494
|
Every mutating command supports `--json` for scripting and AI agent use. Success goes to stdout, errors go to stderr with exit code 1.
|
|
@@ -429,18 +496,22 @@ Every mutating command supports `--json` for scripting and AI agent use. Success
|
|
|
429
496
|
```sh
|
|
430
497
|
# create
|
|
431
498
|
gji new --json feature/dark-mode
|
|
432
|
-
# → { "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" } }
|
|
433
500
|
|
|
434
501
|
# fetch PR
|
|
435
502
|
gji pr --json 1234
|
|
436
|
-
# → { "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" } }
|
|
437
508
|
|
|
438
509
|
# detailed list
|
|
439
510
|
gji ls --json
|
|
440
511
|
# → [{ "branch": "...", "status": "clean", "upstream": { "kind": "tracked", ... }, ... }]
|
|
441
512
|
|
|
442
|
-
#
|
|
443
|
-
gji
|
|
513
|
+
# finish one worktree
|
|
514
|
+
gji done --json --force feature/dark-mode
|
|
444
515
|
# → { "branch": "feature/dark-mode", "path": "/…", "deleted": true }
|
|
445
516
|
|
|
446
517
|
# bulk clean
|
|
@@ -457,7 +528,7 @@ gji clean --stale --json --force
|
|
|
457
528
|
|
|
458
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.
|
|
459
530
|
|
|
460
|
-
`--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.
|
|
461
532
|
|
|
462
533
|
`gji ls --json` and `gji status --json` also produce structured output — see `gji status --json | jq` for the full schema.
|
|
463
534
|
|
|
@@ -465,7 +536,7 @@ gji clean --stale --json --force
|
|
|
465
536
|
|
|
466
537
|
```sh
|
|
467
538
|
GJI_NO_TUI=1 gji new feature/ci-branch
|
|
468
|
-
GJI_NO_TUI=1 gji
|
|
539
|
+
GJI_NO_TUI=1 gji done --force feature/ci-branch
|
|
469
540
|
GJI_NO_TUI=1 gji clean --force
|
|
470
541
|
```
|
|
471
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;
|