vde-worktree 0.0.6 → 0.0.8
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 +2 -0
- package/README.md +2 -0
- package/completions/fish/vw.fish +31 -1
- package/completions/zsh/_vw +44 -2
- package/dist/index.mjs +55 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.ja.md
CHANGED
|
@@ -267,6 +267,7 @@ vw extract --current --stash
|
|
|
267
267
|
|
|
268
268
|
```bash
|
|
269
269
|
vw use feature/foo
|
|
270
|
+
vw use feature/foo --allow-shared
|
|
270
271
|
vw use feature/foo --allow-agent --allow-unsafe
|
|
271
272
|
```
|
|
272
273
|
|
|
@@ -278,6 +279,7 @@ vw use feature/foo --allow-agent --allow-unsafe
|
|
|
278
279
|
安全条件:
|
|
279
280
|
|
|
280
281
|
- primary が dirty なら拒否
|
|
282
|
+
- 対象 branch が他 worktree で使用中なら `--allow-shared` が必要(指定時は警告を表示)
|
|
281
283
|
- 非TTYでは `--allow-agent` と `--allow-unsafe` の両方が必要
|
|
282
284
|
|
|
283
285
|
### `exec`
|
package/README.md
CHANGED
|
@@ -267,6 +267,7 @@ Current limitation:
|
|
|
267
267
|
|
|
268
268
|
```bash
|
|
269
269
|
vw use feature/foo
|
|
270
|
+
vw use feature/foo --allow-shared
|
|
270
271
|
vw use feature/foo --allow-agent --allow-unsafe
|
|
271
272
|
```
|
|
272
273
|
|
|
@@ -278,6 +279,7 @@ What it does:
|
|
|
278
279
|
Safety:
|
|
279
280
|
|
|
280
281
|
- Rejects dirty primary worktree
|
|
282
|
+
- If target branch is attached by another worktree, requires `--allow-shared` and prints a warning
|
|
281
283
|
- In non-TTY mode, requires `--allow-agent` and `--allow-unsafe`
|
|
282
284
|
|
|
283
285
|
### `exec`
|
package/completions/fish/vw.fish
CHANGED
|
@@ -6,6 +6,28 @@ function __vw_worktree_branches
|
|
|
6
6
|
| sort -u
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
+
function __vw_default_branch
|
|
10
|
+
command git rev-parse --is-inside-work-tree >/dev/null 2>/dev/null; or return 0
|
|
11
|
+
|
|
12
|
+
set -l configured (command git config --get vde-worktree.baseBranch 2>/dev/null)
|
|
13
|
+
if test -n "$configured"
|
|
14
|
+
echo $configured
|
|
15
|
+
return
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
command git show-ref --verify --quiet refs/heads/main >/dev/null 2>/dev/null
|
|
19
|
+
and begin
|
|
20
|
+
echo main
|
|
21
|
+
return
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
command git show-ref --verify --quiet refs/heads/master >/dev/null 2>/dev/null
|
|
25
|
+
and begin
|
|
26
|
+
echo master
|
|
27
|
+
return
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
9
31
|
function __vw_current_bin
|
|
10
32
|
set -l tokens (commandline -opc)
|
|
11
33
|
if test (count $tokens) -ge 1
|
|
@@ -66,6 +88,13 @@ for (const worktree of worktrees) {
|
|
|
66
88
|
' 2>/dev/null
|
|
67
89
|
end
|
|
68
90
|
|
|
91
|
+
function __vw_use_candidates_with_meta
|
|
92
|
+
begin
|
|
93
|
+
__vw_worktree_branches
|
|
94
|
+
__vw_default_branch
|
|
95
|
+
end | sort -u
|
|
96
|
+
end
|
|
97
|
+
|
|
69
98
|
function __vw_local_branches
|
|
70
99
|
command git rev-parse --is-inside-work-tree >/dev/null 2>/dev/null; or return 0
|
|
71
100
|
command git for-each-ref --format='%(refname:short)' refs/heads 2>/dev/null | sort -u
|
|
@@ -132,7 +161,7 @@ for __vw_bin in vw vde-worktree
|
|
|
132
161
|
complete -c $__vw_bin -n "__fish_seen_subcommand_from mv" -a "(__vw_local_branches)"
|
|
133
162
|
complete -c $__vw_bin -n "__fish_seen_subcommand_from del" -a "(__vw_worktree_candidates_with_meta)"
|
|
134
163
|
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 "(
|
|
164
|
+
complete -c $__vw_bin -n "__fish_seen_subcommand_from use" -a "(__vw_use_candidates_with_meta)"
|
|
136
165
|
complete -c $__vw_bin -n "__fish_seen_subcommand_from exec" -a "(__vw_worktree_candidates_with_meta)"
|
|
137
166
|
complete -c $__vw_bin -n "__fish_seen_subcommand_from invoke" -a "(__vw_hook_names)"
|
|
138
167
|
complete -c $__vw_bin -n "__fish_seen_subcommand_from lock" -a "(__vw_worktree_candidates_with_meta)"
|
|
@@ -153,6 +182,7 @@ for __vw_bin in vw vde-worktree
|
|
|
153
182
|
complete -c $__vw_bin -n "__fish_seen_subcommand_from extract" -l stash -d "Allow stash when dirty"
|
|
154
183
|
|
|
155
184
|
complete -c $__vw_bin -n "__fish_seen_subcommand_from use" -l allow-agent -d "Allow non-TTY execution for use"
|
|
185
|
+
complete -c $__vw_bin -n "__fish_seen_subcommand_from use" -l allow-shared -d "Allow checkout when branch is attached by another worktree"
|
|
156
186
|
complete -c $__vw_bin -n "__fish_seen_subcommand_from use" -l allow-unsafe -d "Allow unsafe behavior in non-TTY mode"
|
|
157
187
|
|
|
158
188
|
complete -c $__vw_bin -n "__fish_seen_subcommand_from link" -l no-fallback -d "Disable copy fallback when symlink fails"
|
package/completions/zsh/_vw
CHANGED
|
@@ -7,6 +7,24 @@ _vw_worktree_branches_raw() {
|
|
|
7
7
|
| command sort -u
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
_vw_default_branch_raw() {
|
|
11
|
+
command git rev-parse --is-inside-work-tree >/dev/null 2>&1 || return 0
|
|
12
|
+
local configured
|
|
13
|
+
configured="$(command git config --get vde-worktree.baseBranch 2>/dev/null)"
|
|
14
|
+
if [[ -n "${configured}" ]]; then
|
|
15
|
+
print -r -- "${configured}"
|
|
16
|
+
return 0
|
|
17
|
+
fi
|
|
18
|
+
if command git show-ref --verify --quiet refs/heads/main 2>/dev/null; then
|
|
19
|
+
print -r -- "main"
|
|
20
|
+
return 0
|
|
21
|
+
fi
|
|
22
|
+
if command git show-ref --verify --quiet refs/heads/master 2>/dev/null; then
|
|
23
|
+
print -r -- "master"
|
|
24
|
+
return 0
|
|
25
|
+
fi
|
|
26
|
+
}
|
|
27
|
+
|
|
10
28
|
_vw_worktree_candidate_rows_raw() {
|
|
11
29
|
command git rev-parse --is-inside-work-tree >/dev/null 2>&1 || return 0
|
|
12
30
|
local vw_bin="${words[1]:-vw}"
|
|
@@ -110,6 +128,25 @@ _vw_complete_worktree_branches_with_meta() {
|
|
|
110
128
|
_vw_complete_worktree_branches
|
|
111
129
|
}
|
|
112
130
|
|
|
131
|
+
_vw_complete_use_branches() {
|
|
132
|
+
local -a branches
|
|
133
|
+
local default_branch
|
|
134
|
+
branches=("${(@f)$(_vw_worktree_branches_raw)}")
|
|
135
|
+
default_branch="$(_vw_default_branch_raw | command head -n 1)"
|
|
136
|
+
if [[ -n "${default_branch}" ]]; then
|
|
137
|
+
branches+=("${default_branch}")
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
branches=("${(@u)branches}")
|
|
141
|
+
|
|
142
|
+
if (( ${#branches} > 0 )); then
|
|
143
|
+
_vw_describe_values "branch" "${branches[@]}"
|
|
144
|
+
return 0
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
_vw_complete_worktree_branches
|
|
148
|
+
}
|
|
149
|
+
|
|
113
150
|
_vw_complete_local_branches() {
|
|
114
151
|
local -a values
|
|
115
152
|
values=("${(@f)$(_vw_local_branches_raw)}")
|
|
@@ -118,7 +155,11 @@ _vw_complete_local_branches() {
|
|
|
118
155
|
|
|
119
156
|
_vw_complete_switch_branches() {
|
|
120
157
|
local -a values
|
|
121
|
-
values=(
|
|
158
|
+
values=(
|
|
159
|
+
"${(@f)$(_vw_worktree_branches_raw)}"
|
|
160
|
+
"${(@f)$(_vw_local_branches_raw)}"
|
|
161
|
+
)
|
|
162
|
+
values=("${(@u)values}")
|
|
122
163
|
_vw_describe_values "branch" "${values[@]}"
|
|
123
164
|
}
|
|
124
165
|
|
|
@@ -231,7 +272,8 @@ _vw() {
|
|
|
231
272
|
;;
|
|
232
273
|
use)
|
|
233
274
|
_arguments \
|
|
234
|
-
"1:branch:
|
|
275
|
+
"1:branch:_vw_complete_use_branches" \
|
|
276
|
+
"--allow-shared[Allow checkout when branch is attached by another worktree]" \
|
|
235
277
|
"--allow-agent[Allow non-TTY execution for use]" \
|
|
236
278
|
"--allow-unsafe[Allow unsafe behavior in non-TTY mode]"
|
|
237
279
|
;;
|
package/dist/index.mjs
CHANGED
|
@@ -10,6 +10,7 @@ import { parseArgs } from "citty";
|
|
|
10
10
|
import { execa } from "execa";
|
|
11
11
|
import stringWidth from "string-width";
|
|
12
12
|
import { getBorderCharacters, table } from "table";
|
|
13
|
+
import { createHash } from "node:crypto";
|
|
13
14
|
|
|
14
15
|
//#region src/core/constants.ts
|
|
15
16
|
const SCHEMA_VERSION = 1;
|
|
@@ -170,6 +171,8 @@ const doesGitRefExist = async (cwd, ref) => {
|
|
|
170
171
|
//#endregion
|
|
171
172
|
//#region src/core/paths.ts
|
|
172
173
|
const GIT_DIR_NAME = ".git";
|
|
174
|
+
const WORKTREE_ID_HASH_LENGTH = 12;
|
|
175
|
+
const WORKTREE_ID_SLUG_MAX_LENGTH = 48;
|
|
173
176
|
const resolveRepoRootFromCommonDir = ({ currentWorktreeRoot, gitCommonDir }) => {
|
|
174
177
|
if (gitCommonDir.endsWith(`/${GIT_DIR_NAME}`)) return dirname(gitCommonDir);
|
|
175
178
|
if (gitCommonDir.endsWith(`\\${GIT_DIR_NAME}`)) return dirname(gitCommonDir);
|
|
@@ -224,10 +227,14 @@ const getStateDirectoryPath = (repoRoot) => {
|
|
|
224
227
|
return join(getWorktreeMetaRootPath(repoRoot), "state");
|
|
225
228
|
};
|
|
226
229
|
const branchToWorktreeId = (branch) => {
|
|
227
|
-
return
|
|
230
|
+
return `${branch.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, WORKTREE_ID_SLUG_MAX_LENGTH) || "branch"}--${createHash("sha256").update(branch).digest("hex").slice(0, WORKTREE_ID_HASH_LENGTH)}`;
|
|
228
231
|
};
|
|
229
232
|
const branchToWorktreePath = (repoRoot, branch) => {
|
|
230
|
-
|
|
233
|
+
const worktreeRoot = getWorktreeRootPath(repoRoot);
|
|
234
|
+
return ensurePathInsideRepo({
|
|
235
|
+
repoRoot: worktreeRoot,
|
|
236
|
+
path: join(worktreeRoot, ...branch.split("/"))
|
|
237
|
+
});
|
|
231
238
|
};
|
|
232
239
|
const ensurePathInsideRepo = ({ repoRoot, path }) => {
|
|
233
240
|
const rel = relative(repoRoot, path);
|
|
@@ -1378,9 +1385,14 @@ const commandHelpEntries = [
|
|
|
1378
1385
|
},
|
|
1379
1386
|
{
|
|
1380
1387
|
name: "use",
|
|
1381
|
-
usage: "vw use <branch> [--allow-agent --allow-unsafe]",
|
|
1388
|
+
usage: "vw use <branch> [--allow-shared] [--allow-agent --allow-unsafe]",
|
|
1382
1389
|
summary: "Checkout target branch in primary worktree.",
|
|
1383
|
-
details: ["Non-TTY execution requires --allow-agent and --allow-unsafe."]
|
|
1390
|
+
details: ["If target branch is attached by another worktree, --allow-shared is required.", "Non-TTY execution requires --allow-agent and --allow-unsafe."],
|
|
1391
|
+
options: [
|
|
1392
|
+
"--allow-shared",
|
|
1393
|
+
"--allow-agent",
|
|
1394
|
+
"--allow-unsafe"
|
|
1395
|
+
]
|
|
1384
1396
|
},
|
|
1385
1397
|
{
|
|
1386
1398
|
name: "exec",
|
|
@@ -1654,6 +1666,7 @@ const ensureTargetPathWritable = async (targetPath) => {
|
|
|
1654
1666
|
try {
|
|
1655
1667
|
await access(targetPath, constants.F_OK);
|
|
1656
1668
|
} catch {
|
|
1669
|
+
await mkdir(dirname(targetPath), { recursive: true });
|
|
1657
1670
|
return;
|
|
1658
1671
|
}
|
|
1659
1672
|
if ((await readdir(targetPath)).length > 0) throw createCliError("TARGET_PATH_NOT_EMPTY", {
|
|
@@ -2140,6 +2153,10 @@ const createCli = (options = {}) => {
|
|
|
2140
2153
|
type: "boolean",
|
|
2141
2154
|
description: "Allow non-TTY execution for use command"
|
|
2142
2155
|
},
|
|
2156
|
+
allowShared: {
|
|
2157
|
+
type: "boolean",
|
|
2158
|
+
description: "Allow use checkout when target branch is attached by another worktree"
|
|
2159
|
+
},
|
|
2143
2160
|
reason: {
|
|
2144
2161
|
type: "string",
|
|
2145
2162
|
valueHint: "text",
|
|
@@ -3175,6 +3192,7 @@ const createCli = (options = {}) => {
|
|
|
3175
3192
|
max: 1
|
|
3176
3193
|
});
|
|
3177
3194
|
const branch = commandArgs[0];
|
|
3195
|
+
const allowShared = parsedArgs.allowShared === true;
|
|
3178
3196
|
if (runtime.isInteractive !== true) {
|
|
3179
3197
|
if (parsedArgs.allowAgent !== true) throw createCliError("UNSAFE_FLAG_REQUIRED", { message: "UNSAFE_FLAG_REQUIRED: use in non-TTY requires --allow-agent" });
|
|
3180
3198
|
ensureUnsafeForNonTty({
|
|
@@ -3191,6 +3209,38 @@ const createCli = (options = {}) => {
|
|
|
3191
3209
|
message: "use requires clean primary worktree",
|
|
3192
3210
|
details: { repoRoot }
|
|
3193
3211
|
});
|
|
3212
|
+
const branchCheckedOutInOtherWorktree = (await collectWorktreeSnapshot(repoRoot)).worktrees.find((worktree) => {
|
|
3213
|
+
return worktree.branch === branch && worktree.path !== repoRoot;
|
|
3214
|
+
});
|
|
3215
|
+
if (branchCheckedOutInOtherWorktree !== void 0 && allowShared !== true) throw createCliError("BRANCH_IN_USE", {
|
|
3216
|
+
message: [
|
|
3217
|
+
`branch '${branch}' is already checked out in another worktree.`,
|
|
3218
|
+
` path: ${branchCheckedOutInOtherWorktree.path}`,
|
|
3219
|
+
"",
|
|
3220
|
+
"To continue (unsafe), re-run with:",
|
|
3221
|
+
` vw use ${branch} --allow-shared`,
|
|
3222
|
+
"",
|
|
3223
|
+
"Risk:",
|
|
3224
|
+
" multiple worktrees will share the same branch."
|
|
3225
|
+
].join("\n"),
|
|
3226
|
+
details: {
|
|
3227
|
+
branch,
|
|
3228
|
+
path: branchCheckedOutInOtherWorktree.path,
|
|
3229
|
+
hint: "re-run with --allow-shared to continue",
|
|
3230
|
+
risk: "unsafe: multiple worktrees will share the same branch"
|
|
3231
|
+
}
|
|
3232
|
+
});
|
|
3233
|
+
if (branchCheckedOutInOtherWorktree !== void 0 && allowShared === true) stderr([
|
|
3234
|
+
"warning: --allow-shared enabled.",
|
|
3235
|
+
` branch: ${branch}`,
|
|
3236
|
+
` path: ${branchCheckedOutInOtherWorktree.path}`,
|
|
3237
|
+
" risk (unsafe): multiple worktrees will share the same branch."
|
|
3238
|
+
].join("\n"));
|
|
3239
|
+
const checkoutArgs = branchCheckedOutInOtherWorktree ? [
|
|
3240
|
+
"checkout",
|
|
3241
|
+
"--ignore-other-worktrees",
|
|
3242
|
+
branch
|
|
3243
|
+
] : ["checkout", branch];
|
|
3194
3244
|
const hookContext = createHookContext({
|
|
3195
3245
|
runtime,
|
|
3196
3246
|
repoRoot,
|
|
@@ -3205,7 +3255,7 @@ const createCli = (options = {}) => {
|
|
|
3205
3255
|
});
|
|
3206
3256
|
await runGitCommand({
|
|
3207
3257
|
cwd: repoRoot,
|
|
3208
|
-
args:
|
|
3258
|
+
args: checkoutArgs
|
|
3209
3259
|
});
|
|
3210
3260
|
await runPostHook({
|
|
3211
3261
|
name: "use",
|