vde-worktree 0.0.1 → 0.0.3
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.ja.md +395 -0
- package/README.md +376 -28
- package/bin/vde-worktree +38 -0
- package/bin/vw +38 -0
- package/completions/fish/vw.fish +170 -0
- package/completions/zsh/_vw +287 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.mjs +3355 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +78 -6
package/README.md
CHANGED
|
@@ -1,45 +1,393 @@
|
|
|
1
1
|
# vde-worktree
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`vde-worktree` is a safe Git worktree manager designed for both humans and coding agents.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
It installs two command names:
|
|
6
|
+
- `vde-worktree`
|
|
7
|
+
- `vw` (alias)
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
Japanese documentation: [README.ja.md](./README.ja.md)
|
|
8
10
|
|
|
9
|
-
##
|
|
11
|
+
## Goals
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
- Keep all worktrees in one repo-local location: `.worktree/`
|
|
14
|
+
- Provide idempotent branch-to-worktree operations
|
|
15
|
+
- Prevent accidental destructive actions by default
|
|
16
|
+
- Expose stable JSON output for automation
|
|
17
|
+
- Support hook-driven customization
|
|
15
18
|
|
|
16
|
-
##
|
|
19
|
+
## Requirements
|
|
17
20
|
|
|
18
|
-
|
|
21
|
+
- Node.js 22+
|
|
22
|
+
- pnpm 10+
|
|
23
|
+
- `fzf` (required for `cd`)
|
|
24
|
+
- `gh` (optional, for PR-based merge status)
|
|
19
25
|
|
|
20
|
-
##
|
|
26
|
+
## Install / Build
|
|
21
27
|
|
|
22
|
-
|
|
28
|
+
Global install:
|
|
23
29
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
4. Use the configured workflow to publish your actual package
|
|
30
|
+
```bash
|
|
31
|
+
npm install -g vde-worktree
|
|
32
|
+
```
|
|
28
33
|
|
|
29
|
-
|
|
34
|
+
Local build:
|
|
30
35
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
- Exists only for administrative purposes
|
|
36
|
+
```bash
|
|
37
|
+
pnpm install
|
|
38
|
+
pnpm run build
|
|
39
|
+
```
|
|
36
40
|
|
|
37
|
-
|
|
41
|
+
Validate locally:
|
|
38
42
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
```bash
|
|
44
|
+
pnpm run ci
|
|
45
|
+
```
|
|
42
46
|
|
|
43
|
-
|
|
47
|
+
## Quick Start
|
|
44
48
|
|
|
45
|
-
|
|
49
|
+
```bash
|
|
50
|
+
vw init
|
|
51
|
+
vw switch feature/foo
|
|
52
|
+
cd "$(vw cd)"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`vw cd` prints the selected worktree path. It cannot change the parent shell directory by itself.
|
|
56
|
+
|
|
57
|
+
## Shell Completion
|
|
58
|
+
|
|
59
|
+
Generate from command:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
vw completion zsh
|
|
63
|
+
vw completion fish
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Install to default locations:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
vw completion zsh --install
|
|
70
|
+
vw completion fish --install
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Install to custom file path:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
vw completion zsh --install --path ~/.zsh/completions/_vw
|
|
77
|
+
vw completion fish --install --path ~/.config/fish/completions/vw.fish
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
For zsh, ensure completion path is loaded:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
fpath=(~/.zsh/completions $fpath)
|
|
84
|
+
autoload -Uz compinit && compinit
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Managed Directories
|
|
88
|
+
|
|
89
|
+
After `vw init`, the tool manages:
|
|
90
|
+
|
|
91
|
+
- `.worktree/` (worktree roots)
|
|
92
|
+
- `.vde/worktree/hooks/`
|
|
93
|
+
- `.vde/worktree/logs/`
|
|
94
|
+
- `.vde/worktree/locks/`
|
|
95
|
+
- `.vde/worktree/state/`
|
|
96
|
+
|
|
97
|
+
`init` updates `.git/info/exclude` idempotently.
|
|
98
|
+
|
|
99
|
+
## Global Behavior
|
|
100
|
+
|
|
101
|
+
- Most write commands require prior `init`.
|
|
102
|
+
- Write commands are protected by an internal repository lock.
|
|
103
|
+
- `--json` prints exactly one JSON object to stdout.
|
|
104
|
+
- Logs and warnings are written to stderr.
|
|
105
|
+
- Non-TTY unsafe overrides require `--allow-unsafe`.
|
|
106
|
+
|
|
107
|
+
## Global Options
|
|
108
|
+
|
|
109
|
+
- `--json`: machine-readable single-object output
|
|
110
|
+
- `--verbose`: verbose logging
|
|
111
|
+
- `--no-hooks`: disable hooks for this run (requires `--allow-unsafe`)
|
|
112
|
+
- `--allow-unsafe`: explicit unsafe override
|
|
113
|
+
- `--hook-timeout-ms <ms>`: hook timeout override
|
|
114
|
+
- `--lock-timeout-ms <ms>`: repository lock timeout override
|
|
115
|
+
|
|
116
|
+
## Command Guide
|
|
117
|
+
|
|
118
|
+
### `init`
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
vw init
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
What it does:
|
|
125
|
+
- Creates `.worktree/` and `.vde/worktree/*`
|
|
126
|
+
- Appends managed entries to `.git/info/exclude`
|
|
127
|
+
- Creates default hook templates
|
|
128
|
+
|
|
129
|
+
### `list`
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
vw list
|
|
133
|
+
vw list --json
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
What it does:
|
|
137
|
+
- Lists all worktrees from Git porcelain output
|
|
138
|
+
- Includes metadata such as branch, path, dirty, lock, merged, and upstream status
|
|
139
|
+
|
|
140
|
+
### `status`
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
vw status
|
|
144
|
+
vw status feature/foo
|
|
145
|
+
vw status --json
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
What it does:
|
|
149
|
+
- Shows one worktree state
|
|
150
|
+
- Without branch argument, resolves current worktree from current `cwd`
|
|
151
|
+
|
|
152
|
+
### `path`
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
vw path feature/foo
|
|
156
|
+
vw path feature/foo --json
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
What it does:
|
|
160
|
+
- Resolves and returns the absolute worktree path for the target branch
|
|
161
|
+
|
|
162
|
+
### `new`
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
vw new
|
|
166
|
+
vw new feature/foo
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
What it does:
|
|
170
|
+
- Creates a new branch + worktree under `.worktree/`
|
|
171
|
+
- Without argument, generates `wip-xxxxxx`
|
|
172
|
+
|
|
173
|
+
### `switch`
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
vw switch feature/foo
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
What it does:
|
|
180
|
+
- Idempotent branch entrypoint
|
|
181
|
+
- Reuses existing worktree if present, otherwise creates one
|
|
182
|
+
|
|
183
|
+
### `mv`
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
vw mv feature/new-name
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
What it does:
|
|
190
|
+
- Renames current non-primary worktree branch and moves its directory
|
|
191
|
+
- Requires branch checkout (not detached HEAD)
|
|
192
|
+
|
|
193
|
+
### `del`
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
vw del
|
|
197
|
+
vw del feature/foo
|
|
198
|
+
vw del feature/foo --force-unmerged --allow-unpushed --allow-unsafe
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
What it does:
|
|
202
|
+
- Removes worktree and branch safely
|
|
203
|
+
- By default, rejects dirty, locked, unmerged/unknown, or unpushed/unknown states
|
|
204
|
+
|
|
205
|
+
Useful force flags:
|
|
206
|
+
- `--force-dirty`
|
|
207
|
+
- `--allow-unpushed`
|
|
208
|
+
- `--force-unmerged`
|
|
209
|
+
- `--force-locked`
|
|
210
|
+
- `--force` (enables all force flags)
|
|
211
|
+
|
|
212
|
+
### `gone`
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
vw gone
|
|
216
|
+
vw gone --apply
|
|
217
|
+
vw gone --json
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
What it does:
|
|
221
|
+
- Bulk cleanup candidate finder/remover
|
|
222
|
+
- Default mode is dry-run
|
|
223
|
+
- `--apply` actually deletes eligible branches/worktrees
|
|
224
|
+
|
|
225
|
+
### `get`
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
vw get origin/feature/foo
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
What it does:
|
|
232
|
+
- Fetches remote branch
|
|
233
|
+
- Creates tracking local branch when missing
|
|
234
|
+
- Creates/reuses local worktree
|
|
235
|
+
|
|
236
|
+
### `extract`
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
vw extract --current
|
|
240
|
+
vw extract --current --stash
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
What it does:
|
|
244
|
+
- Extracts current primary worktree branch into `.worktree/`
|
|
245
|
+
- Switches primary worktree back to base branch
|
|
246
|
+
- `--stash` allows extraction when primary is dirty
|
|
247
|
+
|
|
248
|
+
Current limitation:
|
|
249
|
+
- Implementation currently supports primary worktree extraction flow.
|
|
250
|
+
|
|
251
|
+
### `use`
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
vw use feature/foo
|
|
255
|
+
vw use feature/foo --allow-agent --allow-unsafe
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
What it does:
|
|
259
|
+
- Checks out the target branch in the primary worktree
|
|
260
|
+
- Intended for human workflows where primary context must be fixed
|
|
261
|
+
|
|
262
|
+
Safety:
|
|
263
|
+
- Rejects dirty primary worktree
|
|
264
|
+
- In non-TTY mode, requires `--allow-agent` and `--allow-unsafe`
|
|
265
|
+
|
|
266
|
+
### `exec`
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
vw exec feature/foo -- pnpm test
|
|
270
|
+
vw exec feature/foo --json -- pnpm test
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
What it does:
|
|
274
|
+
- Executes command inside the target branch worktree path
|
|
275
|
+
- Does not use shell expansion
|
|
276
|
+
|
|
277
|
+
Exit behavior:
|
|
278
|
+
- Child success => `0`
|
|
279
|
+
- Child failure => `21` (`CHILD_PROCESS_FAILED` in JSON mode)
|
|
280
|
+
|
|
281
|
+
### `invoke`
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
vw invoke post-switch
|
|
285
|
+
vw invoke pre-new -- --arg1 --arg2
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
What it does:
|
|
289
|
+
- Manually invokes `pre-*` / `post-*` hook scripts
|
|
290
|
+
- Useful for debugging hook behavior
|
|
291
|
+
|
|
292
|
+
### `copy`
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
vw copy .envrc .claude/settings.local.json
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
What it does:
|
|
299
|
+
- Copies repo-relative files/dirs from repo root into target worktree
|
|
300
|
+
- Primarily intended for hook usage with `WT_WORKTREE_PATH`
|
|
301
|
+
|
|
302
|
+
### `link`
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
vw link .envrc
|
|
306
|
+
vw link .envrc --no-fallback
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
What it does:
|
|
310
|
+
- Creates symlink in target worktree pointing to repo-root file
|
|
311
|
+
- On Windows, can fallback to copy unless `--no-fallback`
|
|
312
|
+
|
|
313
|
+
### `lock` / `unlock`
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
vw lock feature/foo --owner codex --reason "agent in progress"
|
|
317
|
+
vw unlock feature/foo --owner codex
|
|
318
|
+
vw unlock feature/foo --force
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
What they do:
|
|
322
|
+
- `lock` writes lock metadata under `.vde/worktree/locks/`
|
|
323
|
+
- `unlock` clears lock, enforcing owner match unless `--force`
|
|
324
|
+
|
|
325
|
+
### `cd`
|
|
326
|
+
|
|
327
|
+
```bash
|
|
328
|
+
cd "$(vw cd)"
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
What it does:
|
|
332
|
+
- Interactive worktree picker via `fzf`
|
|
333
|
+
- Prints selected absolute path to stdout
|
|
334
|
+
|
|
335
|
+
### `completion`
|
|
336
|
+
|
|
337
|
+
```bash
|
|
338
|
+
vw completion zsh
|
|
339
|
+
vw completion fish
|
|
340
|
+
vw completion zsh --install
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
What it does:
|
|
344
|
+
- Prints completion script for zsh/fish
|
|
345
|
+
- With `--install`, writes completion file to shell default path or `--path`
|
|
346
|
+
|
|
347
|
+
## Merge Status (Local + PR)
|
|
348
|
+
|
|
349
|
+
Each worktree reports:
|
|
350
|
+
|
|
351
|
+
- `merged.byAncestry`: local ancestry check (`git merge-base --is-ancestor <branch> <baseBranch>`)
|
|
352
|
+
- `merged.byPR`: PR-based merged check via GitHub CLI
|
|
353
|
+
- `merged.overall`: final decision
|
|
354
|
+
|
|
355
|
+
Overall policy:
|
|
356
|
+
|
|
357
|
+
- `byPR === true` => `overall = true`
|
|
358
|
+
- `byPR === false` => `overall = false`
|
|
359
|
+
- `byPR === null` => fallback to `byAncestry`
|
|
360
|
+
|
|
361
|
+
`byPR` becomes `null` when PR lookup is unavailable (for example: `gh` missing, auth missing, API error, or `vde-worktree.enableGh=false`).
|
|
362
|
+
|
|
363
|
+
## JSON Contract
|
|
364
|
+
|
|
365
|
+
With `--json`, stdout always emits exactly one JSON object.
|
|
366
|
+
|
|
367
|
+
Common success fields:
|
|
368
|
+
- `schemaVersion`
|
|
369
|
+
- `command`
|
|
370
|
+
- `status`
|
|
371
|
+
- `repoRoot`
|
|
372
|
+
|
|
373
|
+
Error shape:
|
|
374
|
+
- `status: "error"`
|
|
375
|
+
- `code`
|
|
376
|
+
- `message`
|
|
377
|
+
- `details`
|
|
378
|
+
|
|
379
|
+
## Configuration Keys
|
|
380
|
+
|
|
381
|
+
Configured via `git config`:
|
|
382
|
+
|
|
383
|
+
- `vde-worktree.baseBranch`
|
|
384
|
+
- `vde-worktree.baseRemote`
|
|
385
|
+
- `vde-worktree.enableGh`
|
|
386
|
+
- `vde-worktree.hooksEnabled`
|
|
387
|
+
- `vde-worktree.hookTimeoutMs`
|
|
388
|
+
- `vde-worktree.lockTimeoutMs`
|
|
389
|
+
- `vde-worktree.staleLockTTLSeconds`
|
|
390
|
+
|
|
391
|
+
## Current Scope
|
|
392
|
+
|
|
393
|
+
- Ink-based `tui` is not implemented yet.
|
package/bin/vde-worktree
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const isModuleNotFoundFor = (error, entryFile) => {
|
|
4
|
+
if (typeof error !== "object" || error === null) {
|
|
5
|
+
return false
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const withMeta = /** @type {{ code?: string; url?: string; message?: string }} */ (error)
|
|
9
|
+
if (withMeta.code !== "ERR_MODULE_NOT_FOUND") {
|
|
10
|
+
return false
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const expected = `dist/${entryFile}`
|
|
14
|
+
if (typeof withMeta.url === "string" && (withMeta.url.includes(expected) || withMeta.url.includes(`dist\\${entryFile}`))) {
|
|
15
|
+
return true
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return typeof withMeta.message === "string" && withMeta.message.includes(expected)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
await import("../dist/index.mjs")
|
|
23
|
+
} catch (error) {
|
|
24
|
+
if (!isModuleNotFoundFor(error, "index.mjs")) {
|
|
25
|
+
throw error
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
await import("../dist/index.js")
|
|
30
|
+
} catch (fallbackError) {
|
|
31
|
+
if (isModuleNotFoundFor(fallbackError, "index.js")) {
|
|
32
|
+
console.error('Failed to load vde-worktree entrypoint from dist. Run "pnpm run build" and try again.')
|
|
33
|
+
process.exit(1)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
throw fallbackError
|
|
37
|
+
}
|
|
38
|
+
}
|
package/bin/vw
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const isModuleNotFoundFor = (error, entryFile) => {
|
|
4
|
+
if (typeof error !== "object" || error === null) {
|
|
5
|
+
return false
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const withMeta = /** @type {{ code?: string; url?: string; message?: string }} */ (error)
|
|
9
|
+
if (withMeta.code !== "ERR_MODULE_NOT_FOUND") {
|
|
10
|
+
return false
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const expected = `dist/${entryFile}`
|
|
14
|
+
if (typeof withMeta.url === "string" && (withMeta.url.includes(expected) || withMeta.url.includes(`dist\\${entryFile}`))) {
|
|
15
|
+
return true
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return typeof withMeta.message === "string" && withMeta.message.includes(expected)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
await import("../dist/index.mjs")
|
|
23
|
+
} catch (error) {
|
|
24
|
+
if (!isModuleNotFoundFor(error, "index.mjs")) {
|
|
25
|
+
throw error
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
await import("../dist/index.js")
|
|
30
|
+
} catch (fallbackError) {
|
|
31
|
+
if (isModuleNotFoundFor(fallbackError, "index.js")) {
|
|
32
|
+
console.error('Failed to load vde-worktree entrypoint from dist. Run "pnpm run build" and try again.')
|
|
33
|
+
process.exit(1)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
throw fallbackError
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
function __vw_worktree_branches
|
|
2
|
+
command git rev-parse --is-inside-work-tree >/dev/null 2>/dev/null; or return 0
|
|
3
|
+
command git worktree list --porcelain 2>/dev/null \
|
|
4
|
+
| string match -r '^branch refs/heads/.+$' \
|
|
5
|
+
| string replace 'branch refs/heads/' '' \
|
|
6
|
+
| sort -u
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
function __vw_current_bin
|
|
10
|
+
set -l tokens (commandline -opc)
|
|
11
|
+
if test (count $tokens) -ge 1
|
|
12
|
+
set -l candidate $tokens[1]
|
|
13
|
+
if command -sq $candidate
|
|
14
|
+
echo $candidate
|
|
15
|
+
return
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
if command -sq vw
|
|
20
|
+
echo vw
|
|
21
|
+
return
|
|
22
|
+
end
|
|
23
|
+
if command -sq vde-worktree
|
|
24
|
+
echo vde-worktree
|
|
25
|
+
return
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
function __vw_worktree_candidates_with_meta
|
|
30
|
+
command git rev-parse --is-inside-work-tree >/dev/null 2>/dev/null; or return 0
|
|
31
|
+
set -l vw_bin (__vw_current_bin)
|
|
32
|
+
test -n "$vw_bin"; or return 0
|
|
33
|
+
|
|
34
|
+
command $vw_bin list --json 2>/dev/null | command node -e '
|
|
35
|
+
const fs = require("fs")
|
|
36
|
+
const home = process.env.HOME || ""
|
|
37
|
+
const toDisplayPath = (path) => {
|
|
38
|
+
if (typeof path !== "string" || path.length === 0) return ""
|
|
39
|
+
if (home.length === 0) return path
|
|
40
|
+
if (path === home) return "~"
|
|
41
|
+
if (path.startsWith(`${home}/`)) return `~${path.slice(home.length)}`
|
|
42
|
+
return path
|
|
43
|
+
}
|
|
44
|
+
const toFlag = (value) => {
|
|
45
|
+
if (value === true) return "yes"
|
|
46
|
+
if (value === false) return "no"
|
|
47
|
+
return "unknown"
|
|
48
|
+
}
|
|
49
|
+
let payload
|
|
50
|
+
try {
|
|
51
|
+
payload = JSON.parse(fs.readFileSync(0, "utf8"))
|
|
52
|
+
} catch {
|
|
53
|
+
process.exit(0)
|
|
54
|
+
}
|
|
55
|
+
const worktrees = Array.isArray(payload.worktrees) ? payload.worktrees : []
|
|
56
|
+
for (const worktree of worktrees) {
|
|
57
|
+
if (typeof worktree?.branch !== "string" || worktree.branch.length === 0) continue
|
|
58
|
+
const merged = toFlag(worktree?.merged?.overall)
|
|
59
|
+
const dirty = worktree?.dirty === true ? "yes" : "no"
|
|
60
|
+
const locked = worktree?.locked?.value === true ? "yes" : "no"
|
|
61
|
+
const path = toDisplayPath(worktree?.path)
|
|
62
|
+
const summary = `merged=${merged} dirty=${dirty} locked=${locked}${path ? ` path=${path}` : ""}`
|
|
63
|
+
const sanitized = summary.replace(/[\t\r\n]+/g, " ").trim()
|
|
64
|
+
process.stdout.write(`${worktree.branch}\t${sanitized}\n`)
|
|
65
|
+
}
|
|
66
|
+
' 2>/dev/null
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
function __vw_local_branches
|
|
70
|
+
command git rev-parse --is-inside-work-tree >/dev/null 2>/dev/null; or return 0
|
|
71
|
+
command git for-each-ref --format='%(refname:short)' refs/heads 2>/dev/null | sort -u
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
function __vw_switch_branches
|
|
75
|
+
__vw_worktree_branches
|
|
76
|
+
__vw_local_branches
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
function __vw_remote_branches
|
|
80
|
+
command git rev-parse --is-inside-work-tree >/dev/null 2>/dev/null; or return 0
|
|
81
|
+
command git for-each-ref --format='%(refname:short)' refs/remotes 2>/dev/null \
|
|
82
|
+
| string match -rv '.*/HEAD$' \
|
|
83
|
+
| sort -u
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
function __vw_hook_names
|
|
87
|
+
command git rev-parse --is-inside-work-tree >/dev/null 2>/dev/null; or return 0
|
|
88
|
+
set -l repo_root (command git rev-parse --show-toplevel 2>/dev/null); or return 0
|
|
89
|
+
if test -d "$repo_root/.vde/worktree/hooks"
|
|
90
|
+
command ls -1 "$repo_root/.vde/worktree/hooks" 2>/dev/null | string match -r '^(pre|post)-' | sort -u
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
set -l __vw_commands init list status path new switch mv del gone get extract use exec invoke copy link lock unlock cd completion help
|
|
95
|
+
|
|
96
|
+
for __vw_bin in vw vde-worktree
|
|
97
|
+
complete -c $__vw_bin -f -n "not __fish_seen_subcommand_from $__vw_commands" -a init -d "Initialize directories, hooks, and managed exclude entries"
|
|
98
|
+
complete -c $__vw_bin -f -n "not __fish_seen_subcommand_from $__vw_commands" -a list -d "List worktrees with status metadata"
|
|
99
|
+
complete -c $__vw_bin -f -n "not __fish_seen_subcommand_from $__vw_commands" -a status -d "Show a single worktree status"
|
|
100
|
+
complete -c $__vw_bin -f -n "not __fish_seen_subcommand_from $__vw_commands" -a path -d "Print absolute worktree path for branch"
|
|
101
|
+
complete -c $__vw_bin -f -n "not __fish_seen_subcommand_from $__vw_commands" -a new -d "Create branch + worktree under .worktree"
|
|
102
|
+
complete -c $__vw_bin -f -n "not __fish_seen_subcommand_from $__vw_commands" -a switch -d "Idempotent branch entrypoint"
|
|
103
|
+
complete -c $__vw_bin -f -n "not __fish_seen_subcommand_from $__vw_commands" -a mv -d "Rename current non-primary worktree branch"
|
|
104
|
+
complete -c $__vw_bin -f -n "not __fish_seen_subcommand_from $__vw_commands" -a del -d "Delete worktree + branch with safety checks"
|
|
105
|
+
complete -c $__vw_bin -f -n "not __fish_seen_subcommand_from $__vw_commands" -a gone -d "Bulk cleanup by safety-filtered candidate selection"
|
|
106
|
+
complete -c $__vw_bin -f -n "not __fish_seen_subcommand_from $__vw_commands" -a get -d "Fetch remote branch and attach worktree"
|
|
107
|
+
complete -c $__vw_bin -f -n "not __fish_seen_subcommand_from $__vw_commands" -a extract -d "Extract current primary branch into .worktree"
|
|
108
|
+
complete -c $__vw_bin -f -n "not __fish_seen_subcommand_from $__vw_commands" -a use -d "Checkout target branch in primary worktree"
|
|
109
|
+
complete -c $__vw_bin -f -n "not __fish_seen_subcommand_from $__vw_commands" -a exec -d "Run command in target branch worktree"
|
|
110
|
+
complete -c $__vw_bin -f -n "not __fish_seen_subcommand_from $__vw_commands" -a invoke -d "Manually run hook script"
|
|
111
|
+
complete -c $__vw_bin -f -n "not __fish_seen_subcommand_from $__vw_commands" -a copy -d "Copy repo-root files/dirs to target worktree"
|
|
112
|
+
complete -c $__vw_bin -f -n "not __fish_seen_subcommand_from $__vw_commands" -a link -d "Create symlink from target worktree to repo-root file"
|
|
113
|
+
complete -c $__vw_bin -f -n "not __fish_seen_subcommand_from $__vw_commands" -a lock -d "Create or update lock metadata"
|
|
114
|
+
complete -c $__vw_bin -f -n "not __fish_seen_subcommand_from $__vw_commands" -a unlock -d "Remove lock metadata"
|
|
115
|
+
complete -c $__vw_bin -f -n "not __fish_seen_subcommand_from $__vw_commands" -a cd -d "Interactive fzf picker"
|
|
116
|
+
complete -c $__vw_bin -f -n "not __fish_seen_subcommand_from $__vw_commands" -a completion -d "Print or install shell completion scripts"
|
|
117
|
+
complete -c $__vw_bin -f -n "not __fish_seen_subcommand_from $__vw_commands" -a help -d "Show help"
|
|
118
|
+
|
|
119
|
+
complete -c $__vw_bin -l json -d "Output machine-readable JSON"
|
|
120
|
+
complete -c $__vw_bin -l verbose -d "Enable verbose logs"
|
|
121
|
+
complete -c $__vw_bin -l no-hooks -d "Disable hooks for this run (requires --allow-unsafe)"
|
|
122
|
+
complete -c $__vw_bin -l allow-unsafe -d "Explicit unsafe override in non-TTY mode"
|
|
123
|
+
complete -c $__vw_bin -l strict-post-hooks -d "Fail when post hooks fail"
|
|
124
|
+
complete -c $__vw_bin -l hook-timeout-ms -r -d "Override hook timeout"
|
|
125
|
+
complete -c $__vw_bin -l lock-timeout-ms -r -d "Override lock timeout"
|
|
126
|
+
complete -c $__vw_bin -s h -l help -d "Show help"
|
|
127
|
+
complete -c $__vw_bin -s v -l version -d "Show version"
|
|
128
|
+
|
|
129
|
+
complete -c $__vw_bin -n "__fish_seen_subcommand_from status" -a "(__vw_worktree_candidates_with_meta)"
|
|
130
|
+
complete -c $__vw_bin -n "__fish_seen_subcommand_from path" -a "(__vw_worktree_candidates_with_meta)"
|
|
131
|
+
complete -c $__vw_bin -n "__fish_seen_subcommand_from switch" -a "(__vw_switch_branches)"
|
|
132
|
+
complete -c $__vw_bin -n "__fish_seen_subcommand_from mv" -a "(__vw_local_branches)"
|
|
133
|
+
complete -c $__vw_bin -n "__fish_seen_subcommand_from del" -a "(__vw_worktree_candidates_with_meta)"
|
|
134
|
+
complete -c $__vw_bin -n "__fish_seen_subcommand_from get" -a "(__vw_remote_branches)"
|
|
135
|
+
complete -c $__vw_bin -n "__fish_seen_subcommand_from use" -a "(__vw_switch_branches)"
|
|
136
|
+
complete -c $__vw_bin -n "__fish_seen_subcommand_from exec" -a "(__vw_worktree_candidates_with_meta)"
|
|
137
|
+
complete -c $__vw_bin -n "__fish_seen_subcommand_from invoke" -a "(__vw_hook_names)"
|
|
138
|
+
complete -c $__vw_bin -n "__fish_seen_subcommand_from lock" -a "(__vw_worktree_candidates_with_meta)"
|
|
139
|
+
complete -c $__vw_bin -n "__fish_seen_subcommand_from unlock" -a "(__vw_worktree_candidates_with_meta)"
|
|
140
|
+
complete -c $__vw_bin -n "__fish_seen_subcommand_from help" -a "$__vw_commands"
|
|
141
|
+
|
|
142
|
+
complete -c $__vw_bin -n "__fish_seen_subcommand_from del" -l force-dirty -d "Allow dirty worktree for del"
|
|
143
|
+
complete -c $__vw_bin -n "__fish_seen_subcommand_from del" -l allow-unpushed -d "Allow unpushed commits for del"
|
|
144
|
+
complete -c $__vw_bin -n "__fish_seen_subcommand_from del" -l force-unmerged -d "Allow unmerged worktree for del"
|
|
145
|
+
complete -c $__vw_bin -n "__fish_seen_subcommand_from del" -l force-locked -d "Allow deleting locked worktree"
|
|
146
|
+
complete -c $__vw_bin -n "__fish_seen_subcommand_from del" -l force -d "Enable all del force flags"
|
|
147
|
+
|
|
148
|
+
complete -c $__vw_bin -n "__fish_seen_subcommand_from gone" -l apply -d "Apply deletion"
|
|
149
|
+
complete -c $__vw_bin -n "__fish_seen_subcommand_from gone" -l dry-run -d "Dry-run mode"
|
|
150
|
+
|
|
151
|
+
complete -c $__vw_bin -n "__fish_seen_subcommand_from extract" -l current -d "Extract current worktree branch"
|
|
152
|
+
complete -c $__vw_bin -n "__fish_seen_subcommand_from extract" -l from -r -d "Path used by extract --from"
|
|
153
|
+
complete -c $__vw_bin -n "__fish_seen_subcommand_from extract" -l stash -d "Allow stash when dirty"
|
|
154
|
+
|
|
155
|
+
complete -c $__vw_bin -n "__fish_seen_subcommand_from use" -l allow-agent -d "Allow non-TTY execution for use"
|
|
156
|
+
complete -c $__vw_bin -n "__fish_seen_subcommand_from use" -l allow-unsafe -d "Allow unsafe behavior in non-TTY mode"
|
|
157
|
+
|
|
158
|
+
complete -c $__vw_bin -n "__fish_seen_subcommand_from link" -l no-fallback -d "Disable copy fallback when symlink fails"
|
|
159
|
+
complete -c $__vw_bin -n "__fish_seen_subcommand_from lock" -l owner -r -d "Lock owner"
|
|
160
|
+
complete -c $__vw_bin -n "__fish_seen_subcommand_from lock" -l reason -r -d "Lock reason"
|
|
161
|
+
complete -c $__vw_bin -n "__fish_seen_subcommand_from unlock" -l owner -r -d "Unlock owner"
|
|
162
|
+
complete -c $__vw_bin -n "__fish_seen_subcommand_from unlock" -l force -d "Force unlock"
|
|
163
|
+
|
|
164
|
+
complete -c $__vw_bin -n "__fish_seen_subcommand_from cd" -l prompt -r -d "Custom fzf prompt"
|
|
165
|
+
complete -c $__vw_bin -n "__fish_seen_subcommand_from cd" -l fzf-arg -r -d "Extra argument passed to fzf"
|
|
166
|
+
|
|
167
|
+
complete -c $__vw_bin -n "__fish_seen_subcommand_from completion" -a "zsh fish" -d "Shell name"
|
|
168
|
+
complete -c $__vw_bin -n "__fish_seen_subcommand_from completion" -l install -d "Install completion file"
|
|
169
|
+
complete -c $__vw_bin -n "__fish_seen_subcommand_from completion" -l path -r -d "Install destination file path"
|
|
170
|
+
end
|