vde-worktree 0.0.16 → 0.0.17
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 +6 -5
- package/README.md +5 -4
- package/completions/fish/vw.fish +12 -2
- package/completions/zsh/_vw +12 -2
- package/dist/index.mjs +94 -39
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.ja.md
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
- Node.js 22+
|
|
23
23
|
- pnpm 10+
|
|
24
24
|
- `fzf`(`cd` に必須)
|
|
25
|
-
- `gh`(PR
|
|
25
|
+
- `gh`(PR 状態判定に任意)
|
|
26
26
|
|
|
27
27
|
## インストール / ビルド
|
|
28
28
|
|
|
@@ -111,7 +111,7 @@ autoload -Uz compinit && compinit
|
|
|
111
111
|
- `--verbose`: 詳細ログ
|
|
112
112
|
- `--no-hooks`: 今回のみ hook 無効化(`--allow-unsafe` 必須)
|
|
113
113
|
- `--allow-unsafe`: unsafe 操作の明示同意
|
|
114
|
-
- `--no-gh`: 今回の実行で `gh` による PR
|
|
114
|
+
- `--no-gh`: 今回の実行で `gh` による PR 状態判定を無効化
|
|
115
115
|
- `--hook-timeout-ms <ms>`: hook timeout 上書き
|
|
116
116
|
- `--lock-timeout-ms <ms>`: repo lock timeout 上書き
|
|
117
117
|
|
|
@@ -140,8 +140,8 @@ vw list --no-gh
|
|
|
140
140
|
機能:
|
|
141
141
|
|
|
142
142
|
- Git の porcelain 情報から worktree 一覧を取得
|
|
143
|
-
- branch/path/dirty/lock/merged/upstream を表示
|
|
144
|
-
- `--no-gh` 指定時は PR
|
|
143
|
+
- branch/path/dirty/lock/merged/PR/upstream を表示
|
|
144
|
+
- `--no-gh` 指定時は PR 状態判定をスキップ(`pr.status` は `unknown`、`merged.byPR` は `null`)
|
|
145
145
|
- 対話ターミナルでは Catppuccin 風の ANSI 色で表示
|
|
146
146
|
|
|
147
147
|
### `status`
|
|
@@ -424,6 +424,7 @@ vw completion zsh --install
|
|
|
424
424
|
- `merged.byAncestry`: ローカル履歴判定(`git merge-base --is-ancestor`)
|
|
425
425
|
- `merged.byPR`: GitHub PR merged 判定(`gh`)
|
|
426
426
|
- `merged.overall`: 最終判定
|
|
427
|
+
- `pr.status`: PR 状態(`none` / `open` / `merged` / `closed_unmerged` / `unknown`)
|
|
427
428
|
|
|
428
429
|
`overall` ポリシー:
|
|
429
430
|
|
|
@@ -436,7 +437,7 @@ vw completion zsh --install
|
|
|
436
437
|
- `byPR === false` または lifecycle が明示的に未取り込みなら `overall = false`
|
|
437
438
|
- それ以外は `overall = null`
|
|
438
439
|
|
|
439
|
-
`byPR` が `null` になる例:
|
|
440
|
+
`byPR` が `null` かつ `pr.status` が `unknown` になる例:
|
|
440
441
|
|
|
441
442
|
- `gh` 未導入
|
|
442
443
|
- `gh auth` 未設定
|
package/README.md
CHANGED
|
@@ -111,7 +111,7 @@ After `vw init`, the tool manages:
|
|
|
111
111
|
- `--verbose`: verbose logging
|
|
112
112
|
- `--no-hooks`: disable hooks for this run (requires `--allow-unsafe`)
|
|
113
113
|
- `--allow-unsafe`: explicit unsafe override
|
|
114
|
-
- `--no-gh`: disable GitHub CLI based PR
|
|
114
|
+
- `--no-gh`: disable GitHub CLI based PR status checks for this run
|
|
115
115
|
- `--hook-timeout-ms <ms>`: hook timeout override
|
|
116
116
|
- `--lock-timeout-ms <ms>`: repository lock timeout override
|
|
117
117
|
|
|
@@ -140,8 +140,8 @@ vw list --no-gh
|
|
|
140
140
|
What it does:
|
|
141
141
|
|
|
142
142
|
- Lists all worktrees from Git porcelain output
|
|
143
|
-
- Includes metadata such as branch, path, dirty, lock, merged, and upstream status
|
|
144
|
-
- With `--no-gh`, skips PR
|
|
143
|
+
- Includes metadata such as branch, path, dirty, lock, merged, PR status, and upstream status
|
|
144
|
+
- With `--no-gh`, skips PR status checks (`pr.status` becomes `unknown`, `merged.byPR` becomes `null`)
|
|
145
145
|
- In interactive terminal, uses Catppuccin-style ANSI colors
|
|
146
146
|
|
|
147
147
|
### `status`
|
|
@@ -424,6 +424,7 @@ Each worktree reports:
|
|
|
424
424
|
- `merged.byAncestry`: local ancestry check (`git merge-base --is-ancestor <branch> <baseBranch>`)
|
|
425
425
|
- `merged.byPR`: PR-based merged check via GitHub CLI
|
|
426
426
|
- `merged.overall`: final decision
|
|
427
|
+
- `pr.status`: PR state (`none` / `open` / `merged` / `closed_unmerged` / `unknown`)
|
|
427
428
|
|
|
428
429
|
Overall policy:
|
|
429
430
|
|
|
@@ -436,7 +437,7 @@ Overall policy:
|
|
|
436
437
|
- `byPR === false` or explicit lifecycle "not merged" evidence => `overall = false`
|
|
437
438
|
- otherwise `overall = null`
|
|
438
439
|
|
|
439
|
-
`byPR` becomes `null` when PR lookup is unavailable (for example: `gh` missing, auth missing, API error, `vde-worktree.enableGh=false`, or `--no-gh`).
|
|
440
|
+
`byPR` becomes `null` and `pr.status` becomes `unknown` when PR lookup is unavailable (for example: `gh` missing, auth missing, API error, `vde-worktree.enableGh=false`, or `--no-gh`).
|
|
440
441
|
|
|
441
442
|
## JSON Contract
|
|
442
443
|
|
package/completions/fish/vw.fish
CHANGED
|
@@ -68,6 +68,10 @@ const toFlag = (value) => {
|
|
|
68
68
|
if (value === false) return "no"
|
|
69
69
|
return "unknown"
|
|
70
70
|
}
|
|
71
|
+
const toPrStatus = (value) => {
|
|
72
|
+
if (typeof value !== "string" || value.length === 0) return "n/a"
|
|
73
|
+
return value
|
|
74
|
+
}
|
|
71
75
|
let payload
|
|
72
76
|
try {
|
|
73
77
|
payload = JSON.parse(fs.readFileSync(0, "utf8"))
|
|
@@ -78,10 +82,11 @@ const worktrees = Array.isArray(payload.worktrees) ? payload.worktrees : []
|
|
|
78
82
|
for (const worktree of worktrees) {
|
|
79
83
|
if (typeof worktree?.branch !== "string" || worktree.branch.length === 0) continue
|
|
80
84
|
const merged = toFlag(worktree?.merged?.overall)
|
|
85
|
+
const pr = toPrStatus(worktree?.pr?.status)
|
|
81
86
|
const dirty = worktree?.dirty === true ? "yes" : "no"
|
|
82
87
|
const locked = worktree?.locked?.value === true ? "yes" : "no"
|
|
83
88
|
const path = toDisplayPath(worktree?.path)
|
|
84
|
-
const summary = `merged=${merged} dirty=${dirty} locked=${locked}${path ? ` path=${path}` : ""}`
|
|
89
|
+
const summary = `merged=${merged} pr=${pr} dirty=${dirty} locked=${locked}${path ? ` path=${path}` : ""}`
|
|
85
90
|
const sanitized = summary.replace(/[\t\r\n]+/g, " ").trim()
|
|
86
91
|
process.stdout.write(`${worktree.branch}\t${sanitized}\n`)
|
|
87
92
|
}
|
|
@@ -101,6 +106,10 @@ const toFlag = (value) => {
|
|
|
101
106
|
if (value === false) return "no"
|
|
102
107
|
return "unknown"
|
|
103
108
|
}
|
|
109
|
+
const toPrStatus = (value) => {
|
|
110
|
+
if (typeof value !== "string" || value.length === 0) return "n/a"
|
|
111
|
+
return value
|
|
112
|
+
}
|
|
104
113
|
let payload
|
|
105
114
|
try {
|
|
106
115
|
payload = JSON.parse(fs.readFileSync(0, "utf8"))
|
|
@@ -118,9 +127,10 @@ for (const worktree of worktrees) {
|
|
|
118
127
|
const name = rel.split(path.sep).join("/")
|
|
119
128
|
const branch = typeof worktree?.branch === "string" && worktree.branch.length > 0 ? worktree.branch : "(detached)"
|
|
120
129
|
const merged = toFlag(worktree?.merged?.overall)
|
|
130
|
+
const pr = toPrStatus(worktree?.pr?.status)
|
|
121
131
|
const dirty = worktree?.dirty === true ? "yes" : "no"
|
|
122
132
|
const locked = worktree?.locked?.value === true ? "yes" : "no"
|
|
123
|
-
const summary = `branch=${branch} merged=${merged} dirty=${dirty} locked=${locked}`
|
|
133
|
+
const summary = `branch=${branch} merged=${merged} pr=${pr} dirty=${dirty} locked=${locked}`
|
|
124
134
|
const sanitized = summary.replace(/[\t\r\n]+/g, " ").trim()
|
|
125
135
|
process.stdout.write(`${name}\t${sanitized}\n`)
|
|
126
136
|
}
|
package/completions/zsh/_vw
CHANGED
|
@@ -45,6 +45,10 @@ const toFlag = (value) => {
|
|
|
45
45
|
if (value === false) return "no"
|
|
46
46
|
return "unknown"
|
|
47
47
|
}
|
|
48
|
+
const toPrStatus = (value) => {
|
|
49
|
+
if (typeof value !== "string" || value.length === 0) return "n/a"
|
|
50
|
+
return value
|
|
51
|
+
}
|
|
48
52
|
let payload
|
|
49
53
|
try {
|
|
50
54
|
payload = JSON.parse(fs.readFileSync(0, "utf8"))
|
|
@@ -55,10 +59,11 @@ const worktrees = Array.isArray(payload.worktrees) ? payload.worktrees : []
|
|
|
55
59
|
for (const worktree of worktrees) {
|
|
56
60
|
if (typeof worktree?.branch !== "string" || worktree.branch.length === 0) continue
|
|
57
61
|
const merged = toFlag(worktree?.merged?.overall)
|
|
62
|
+
const pr = toPrStatus(worktree?.pr?.status)
|
|
58
63
|
const dirty = worktree?.dirty === true ? "yes" : "no"
|
|
59
64
|
const locked = worktree?.locked?.value === true ? "yes" : "no"
|
|
60
65
|
const path = toDisplayPath(worktree?.path)
|
|
61
|
-
const summary = `merged=${merged} dirty=${dirty} locked=${locked}${path ? ` path=${path}` : ""}`
|
|
66
|
+
const summary = `merged=${merged} pr=${pr} dirty=${dirty} locked=${locked}${path ? ` path=${path}` : ""}`
|
|
62
67
|
const sanitized = summary.replace(/[\t\r\n]+/g, " ").trim()
|
|
63
68
|
process.stdout.write(`${worktree.branch}\t${sanitized}\n`)
|
|
64
69
|
}
|
|
@@ -78,6 +83,10 @@ const toFlag = (value) => {
|
|
|
78
83
|
if (value === false) return "no"
|
|
79
84
|
return "unknown"
|
|
80
85
|
}
|
|
86
|
+
const toPrStatus = (value) => {
|
|
87
|
+
if (typeof value !== "string" || value.length === 0) return "n/a"
|
|
88
|
+
return value
|
|
89
|
+
}
|
|
81
90
|
let payload
|
|
82
91
|
try {
|
|
83
92
|
payload = JSON.parse(fs.readFileSync(0, "utf8"))
|
|
@@ -95,9 +104,10 @@ for (const worktree of worktrees) {
|
|
|
95
104
|
const name = rel.split(path.sep).join("/")
|
|
96
105
|
const branch = typeof worktree?.branch === "string" && worktree.branch.length > 0 ? worktree.branch : "(detached)"
|
|
97
106
|
const merged = toFlag(worktree?.merged?.overall)
|
|
107
|
+
const pr = toPrStatus(worktree?.pr?.status)
|
|
98
108
|
const dirty = worktree?.dirty === true ? "yes" : "no"
|
|
99
109
|
const locked = worktree?.locked?.value === true ? "yes" : "no"
|
|
100
|
-
const summary = `branch=${branch} merged=${merged} dirty=${dirty} locked=${locked}`
|
|
110
|
+
const summary = `branch=${branch} merged=${merged} pr=${pr} dirty=${dirty} locked=${locked}`
|
|
101
111
|
const sanitized = summary.replace(/[\t\r\n]+/g, " ").trim()
|
|
102
112
|
process.stdout.write(`${name}\t${sanitized}\n`)
|
|
103
113
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -853,43 +853,68 @@ const toTargetBranches = ({ branches, baseBranch }) => {
|
|
|
853
853
|
}
|
|
854
854
|
return [...uniqueBranches];
|
|
855
855
|
};
|
|
856
|
-
const
|
|
857
|
-
return new Map(branches.map((branch) => [branch,
|
|
856
|
+
const buildUnknownPrStatusMap = (branches) => {
|
|
857
|
+
return new Map(branches.map((branch) => [branch, "unknown"]));
|
|
858
858
|
};
|
|
859
|
-
const
|
|
859
|
+
const parseUpdatedAtMillis = (value) => {
|
|
860
|
+
if (typeof value !== "string" || value.length === 0) return Number.NEGATIVE_INFINITY;
|
|
861
|
+
const parsed = Date.parse(value);
|
|
862
|
+
if (Number.isNaN(parsed)) return Number.NEGATIVE_INFINITY;
|
|
863
|
+
return parsed;
|
|
864
|
+
};
|
|
865
|
+
const toPrStatus = (record) => {
|
|
866
|
+
if (typeof record.mergedAt === "string" && record.mergedAt.length > 0) return "merged";
|
|
867
|
+
const state = typeof record.state === "string" ? record.state.toUpperCase() : "";
|
|
868
|
+
if (state === "MERGED") return "merged";
|
|
869
|
+
if (state === "OPEN") return "open";
|
|
870
|
+
if (state === "CLOSED") return "closed_unmerged";
|
|
871
|
+
return "unknown";
|
|
872
|
+
};
|
|
873
|
+
const parsePrStatusByBranch = ({ raw, targetBranches }) => {
|
|
860
874
|
try {
|
|
861
875
|
const parsed = JSON.parse(raw);
|
|
862
876
|
if (Array.isArray(parsed) !== true) return null;
|
|
877
|
+
const targetBranchSet = new Set(targetBranches);
|
|
863
878
|
const records = parsed;
|
|
864
|
-
const
|
|
865
|
-
for (const record of records) {
|
|
879
|
+
const latestByBranch = /* @__PURE__ */ new Map();
|
|
880
|
+
for (const [index, record] of records.entries()) {
|
|
866
881
|
if (typeof record?.headRefName !== "string" || record.headRefName.length === 0) continue;
|
|
867
|
-
if (
|
|
868
|
-
|
|
869
|
-
|
|
882
|
+
if (targetBranchSet.has(record.headRefName) !== true) continue;
|
|
883
|
+
const updatedAtMillis = parseUpdatedAtMillis(record.updatedAt);
|
|
884
|
+
const status = toPrStatus(record);
|
|
885
|
+
const current = latestByBranch.get(record.headRefName);
|
|
886
|
+
if (current === void 0 || updatedAtMillis > current.updatedAtMillis || updatedAtMillis === current.updatedAtMillis && index > current.index) latestByBranch.set(record.headRefName, {
|
|
887
|
+
updatedAtMillis,
|
|
888
|
+
index,
|
|
889
|
+
status
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
const result = /* @__PURE__ */ new Map();
|
|
893
|
+
for (const branch of targetBranches) {
|
|
894
|
+
const latest = latestByBranch.get(branch);
|
|
895
|
+
result.set(branch, latest?.status ?? "none");
|
|
870
896
|
}
|
|
871
|
-
return
|
|
897
|
+
return result;
|
|
872
898
|
} catch {
|
|
873
899
|
return null;
|
|
874
900
|
}
|
|
875
901
|
};
|
|
876
|
-
const
|
|
877
|
-
if (enabled !== true) return /* @__PURE__ */ new Map();
|
|
902
|
+
const resolvePrStatusByBranchBatch = async ({ repoRoot, baseBranch, branches, enabled = true, runGh = defaultRunGh }) => {
|
|
878
903
|
if (baseBranch === null) return /* @__PURE__ */ new Map();
|
|
879
904
|
const targetBranches = toTargetBranches({
|
|
880
905
|
branches,
|
|
881
906
|
baseBranch
|
|
882
907
|
});
|
|
883
908
|
if (targetBranches.length === 0) return /* @__PURE__ */ new Map();
|
|
909
|
+
if (enabled !== true) return buildUnknownPrStatusMap(targetBranches);
|
|
884
910
|
try {
|
|
885
|
-
const targetBranchSet = new Set(targetBranches);
|
|
886
911
|
const result = await runGh({
|
|
887
912
|
cwd: repoRoot,
|
|
888
913
|
args: [
|
|
889
914
|
"pr",
|
|
890
915
|
"list",
|
|
891
916
|
"--state",
|
|
892
|
-
"
|
|
917
|
+
"all",
|
|
893
918
|
"--base",
|
|
894
919
|
baseBranch,
|
|
895
920
|
"--search",
|
|
@@ -897,21 +922,19 @@ const resolveMergedByPrBatch = async ({ repoRoot, baseBranch, branches, enabled
|
|
|
897
922
|
"--limit",
|
|
898
923
|
"1000",
|
|
899
924
|
"--json",
|
|
900
|
-
"headRefName,mergedAt"
|
|
925
|
+
"headRefName,state,mergedAt,updatedAt"
|
|
901
926
|
]
|
|
902
927
|
});
|
|
903
|
-
if (result.exitCode !== 0) return
|
|
904
|
-
const
|
|
928
|
+
if (result.exitCode !== 0) return buildUnknownPrStatusMap(targetBranches);
|
|
929
|
+
const prStatusByBranch = parsePrStatusByBranch({
|
|
905
930
|
raw: result.stdout,
|
|
906
|
-
targetBranches
|
|
931
|
+
targetBranches
|
|
907
932
|
});
|
|
908
|
-
if (
|
|
909
|
-
return
|
|
910
|
-
return [branch, mergedBranches.has(branch)];
|
|
911
|
-
}));
|
|
933
|
+
if (prStatusByBranch === null) return buildUnknownPrStatusMap(targetBranches);
|
|
934
|
+
return prStatusByBranch;
|
|
912
935
|
} catch (error) {
|
|
913
|
-
if (error.code === "ENOENT") return
|
|
914
|
-
return
|
|
936
|
+
if (error.code === "ENOENT") return buildUnknownPrStatusMap(targetBranches);
|
|
937
|
+
return buildUnknownPrStatusMap(targetBranches);
|
|
915
938
|
}
|
|
916
939
|
};
|
|
917
940
|
|
|
@@ -1110,7 +1133,7 @@ const resolveLifecycleFromReflog = async ({ repoRoot, branch, baseBranch }) => {
|
|
|
1110
1133
|
divergedHead: latestWorkHead
|
|
1111
1134
|
};
|
|
1112
1135
|
};
|
|
1113
|
-
const resolveMergedState = async ({ repoRoot, branch, head, baseBranch,
|
|
1136
|
+
const resolveMergedState = async ({ repoRoot, branch, head, baseBranch, prStatusByBranch }) => {
|
|
1114
1137
|
if (branch === null) return {
|
|
1115
1138
|
byAncestry: null,
|
|
1116
1139
|
byPR: null,
|
|
@@ -1131,7 +1154,10 @@ const resolveMergedState = async ({ repoRoot, branch, head, baseBranch, mergedBy
|
|
|
1131
1154
|
if (result.exitCode === 0) byAncestry = true;
|
|
1132
1155
|
else if (result.exitCode === 1) byAncestry = false;
|
|
1133
1156
|
}
|
|
1134
|
-
const
|
|
1157
|
+
const prStatus = branch === baseBranch ? null : prStatusByBranch.get(branch) ?? null;
|
|
1158
|
+
let byPR = null;
|
|
1159
|
+
if (prStatus === "merged") byPR = true;
|
|
1160
|
+
else if (prStatus === "none" || prStatus === "open" || prStatus === "closed_unmerged") byPR = false;
|
|
1135
1161
|
let byLifecycle = null;
|
|
1136
1162
|
if (baseBranch !== null) {
|
|
1137
1163
|
const lifecycle = await upsertWorktreeMergeLifecycle({
|
|
@@ -1182,6 +1208,10 @@ const resolveMergedState = async ({ repoRoot, branch, head, baseBranch, mergedBy
|
|
|
1182
1208
|
})
|
|
1183
1209
|
};
|
|
1184
1210
|
};
|
|
1211
|
+
const resolvePrState = ({ branch, baseBranch, prStatusByBranch }) => {
|
|
1212
|
+
if (branch === null || branch === baseBranch) return { status: null };
|
|
1213
|
+
return { status: prStatusByBranch.get(branch) ?? null };
|
|
1214
|
+
};
|
|
1185
1215
|
const resolveMergedOverall = ({ byAncestry, byPR, byLifecycle }) => {
|
|
1186
1216
|
if (byPR === true || byLifecycle === true) return true;
|
|
1187
1217
|
if (byAncestry === false) return false;
|
|
@@ -1228,7 +1258,7 @@ const resolveUpstreamState = async (worktreePath) => {
|
|
|
1228
1258
|
remote: upstreamRef.stdout.trim()
|
|
1229
1259
|
};
|
|
1230
1260
|
};
|
|
1231
|
-
const enrichWorktree = async ({ repoRoot, worktree, baseBranch,
|
|
1261
|
+
const enrichWorktree = async ({ repoRoot, worktree, baseBranch, prStatusByBranch }) => {
|
|
1232
1262
|
const [dirty, locked, merged, upstream] = await Promise.all([
|
|
1233
1263
|
resolveDirty(worktree.path),
|
|
1234
1264
|
resolveLockState({
|
|
@@ -1240,10 +1270,15 @@ const enrichWorktree = async ({ repoRoot, worktree, baseBranch, mergedByPrByBran
|
|
|
1240
1270
|
branch: worktree.branch,
|
|
1241
1271
|
head: worktree.head,
|
|
1242
1272
|
baseBranch,
|
|
1243
|
-
|
|
1273
|
+
prStatusByBranch
|
|
1244
1274
|
}),
|
|
1245
1275
|
resolveUpstreamState(worktree.path)
|
|
1246
1276
|
]);
|
|
1277
|
+
const pr = resolvePrState({
|
|
1278
|
+
branch: worktree.branch,
|
|
1279
|
+
baseBranch,
|
|
1280
|
+
prStatusByBranch
|
|
1281
|
+
});
|
|
1247
1282
|
return {
|
|
1248
1283
|
branch: worktree.branch,
|
|
1249
1284
|
path: worktree.path,
|
|
@@ -1251,6 +1286,7 @@ const enrichWorktree = async ({ repoRoot, worktree, baseBranch, mergedByPrByBran
|
|
|
1251
1286
|
dirty,
|
|
1252
1287
|
locked,
|
|
1253
1288
|
merged,
|
|
1289
|
+
pr,
|
|
1254
1290
|
upstream
|
|
1255
1291
|
};
|
|
1256
1292
|
};
|
|
@@ -1260,7 +1296,7 @@ const collectWorktreeSnapshot = async (repoRoot, { noGh = false } = {}) => {
|
|
|
1260
1296
|
listGitWorktrees(repoRoot),
|
|
1261
1297
|
resolveEnableGh(repoRoot)
|
|
1262
1298
|
]);
|
|
1263
|
-
const
|
|
1299
|
+
const prStatusByBranch = await resolvePrStatusByBranchBatch({
|
|
1264
1300
|
repoRoot,
|
|
1265
1301
|
baseBranch,
|
|
1266
1302
|
branches: worktrees.map((worktree) => worktree.branch),
|
|
@@ -1274,7 +1310,7 @@ const collectWorktreeSnapshot = async (repoRoot, { noGh = false } = {}) => {
|
|
|
1274
1310
|
repoRoot,
|
|
1275
1311
|
worktree,
|
|
1276
1312
|
baseBranch,
|
|
1277
|
-
|
|
1313
|
+
prStatusByBranch
|
|
1278
1314
|
});
|
|
1279
1315
|
}))
|
|
1280
1316
|
};
|
|
@@ -1530,9 +1566,9 @@ const colorizeListTableLine = ({ line, theme }) => {
|
|
|
1530
1566
|
const segments = line.split("│");
|
|
1531
1567
|
if (segments.length < 3) return line;
|
|
1532
1568
|
const cells = segments.slice(1, -1);
|
|
1533
|
-
if (cells.length !==
|
|
1569
|
+
if (cells.length !== 8) return line;
|
|
1534
1570
|
const headers = cells.map((cell) => cell.trim());
|
|
1535
|
-
if (headers[0] === "branch" && headers[1] === "dirty" && headers[2] === "merged" && headers[3] === "
|
|
1571
|
+
if (headers[0] === "branch" && headers[1] === "dirty" && headers[2] === "merged" && headers[3] === "pr" && headers[4] === "locked" && headers[5] === "ahead" && headers[6] === "behind" && headers[7] === "path") {
|
|
1536
1572
|
const nextCells = cells.map((cell) => colorizeCellContent({
|
|
1537
1573
|
cell,
|
|
1538
1574
|
color: theme.header
|
|
@@ -1546,15 +1582,18 @@ const colorizeListTableLine = ({ line, theme }) => {
|
|
|
1546
1582
|
const branchCell = cells[0];
|
|
1547
1583
|
const dirtyCell = cells[1];
|
|
1548
1584
|
const mergedCell = cells[2];
|
|
1549
|
-
const
|
|
1550
|
-
const
|
|
1551
|
-
const
|
|
1552
|
-
const
|
|
1585
|
+
const prCell = cells[3];
|
|
1586
|
+
const lockedCell = cells[4];
|
|
1587
|
+
const aheadCell = cells[5];
|
|
1588
|
+
const behindCell = cells[6];
|
|
1589
|
+
const pathCell = cells[7];
|
|
1553
1590
|
const branchColor = branchCell.includes("(detached)") === true ? theme.branchDetached : branchCell.trimStart().startsWith("*") ? theme.branchCurrent : theme.branch;
|
|
1554
1591
|
const dirtyTrimmed = dirtyCell.trim();
|
|
1555
1592
|
const dirtyColor = dirtyTrimmed === "dirty" ? theme.dirty : dirtyTrimmed === "clean" ? theme.clean : theme.value;
|
|
1556
1593
|
const mergedTrimmed = mergedCell.trim();
|
|
1557
1594
|
const mergedColor = mergedTrimmed === "merged" ? theme.merged : mergedTrimmed === "unmerged" ? theme.unmerged : mergedTrimmed === "-" ? theme.base : theme.unknown;
|
|
1595
|
+
const prTrimmed = prCell.trim();
|
|
1596
|
+
const prColor = prTrimmed === "merged" ? theme.merged : prTrimmed === "open" ? theme.value : prTrimmed === "closed_unmerged" ? theme.unmerged : prTrimmed === "none" ? theme.muted : prTrimmed === "-" ? theme.base : theme.unknown;
|
|
1558
1597
|
const lockedColor = lockedCell.trim() === "locked" ? theme.locked : theme.muted;
|
|
1559
1598
|
const aheadTrimmed = aheadCell.trim();
|
|
1560
1599
|
const aheadValue = Number.parseInt(aheadTrimmed, 10);
|
|
@@ -1575,6 +1614,10 @@ const colorizeListTableLine = ({ line, theme }) => {
|
|
|
1575
1614
|
cell: mergedCell,
|
|
1576
1615
|
color: mergedColor
|
|
1577
1616
|
}),
|
|
1617
|
+
colorizeCellContent({
|
|
1618
|
+
cell: prCell,
|
|
1619
|
+
color: prColor
|
|
1620
|
+
}),
|
|
1578
1621
|
colorizeCellContent({
|
|
1579
1622
|
cell: lockedCell,
|
|
1580
1623
|
color: lockedColor
|
|
@@ -1615,7 +1658,7 @@ const commandHelpEntries = [
|
|
|
1615
1658
|
name: "list",
|
|
1616
1659
|
usage: "vw list [--json]",
|
|
1617
1660
|
summary: "List worktrees with status metadata.",
|
|
1618
|
-
details: ["Table output includes branch, path, dirty, lock, merged, and ahead/behind vs base branch.", "JSON output includes upstream metadata fields."]
|
|
1661
|
+
details: ["Table output includes branch, path, dirty, lock, merged, PR state, and ahead/behind vs base branch.", "JSON output includes PR and upstream metadata fields."]
|
|
1619
1662
|
},
|
|
1620
1663
|
{
|
|
1621
1664
|
name: "status",
|
|
@@ -2432,6 +2475,11 @@ const formatMergedColor = ({ mergedState, theme }) => {
|
|
|
2432
2475
|
if (normalized === "base") return theme.base(mergedState);
|
|
2433
2476
|
return theme.unknown(mergedState);
|
|
2434
2477
|
};
|
|
2478
|
+
const formatPrDisplayState = ({ prStatus, isBaseBranch }) => {
|
|
2479
|
+
if (isBaseBranch || prStatus === null) return "-";
|
|
2480
|
+
if (prStatus === "none" || prStatus === "open" || prStatus === "merged" || prStatus === "closed_unmerged" || prStatus === "unknown") return prStatus;
|
|
2481
|
+
return "unknown";
|
|
2482
|
+
};
|
|
2435
2483
|
const formatListUpstreamCount = (value) => {
|
|
2436
2484
|
if (value === null) return "-";
|
|
2437
2485
|
return String(value);
|
|
@@ -2593,7 +2641,7 @@ const renderGeneralHelpText = ({ version }) => {
|
|
|
2593
2641
|
" --json Output machine-readable JSON.",
|
|
2594
2642
|
" --verbose Enable verbose logs.",
|
|
2595
2643
|
" --no-hooks Disable hooks for this run (requires --allow-unsafe).",
|
|
2596
|
-
" --no-gh Disable GitHub CLI based PR
|
|
2644
|
+
" --no-gh Disable GitHub CLI based PR status checks for this run.",
|
|
2597
2645
|
" --allow-unsafe Explicitly allow unsafe behavior in non-TTY mode.",
|
|
2598
2646
|
" --hook-timeout-ms <ms> Override hook timeout.",
|
|
2599
2647
|
" --lock-timeout-ms <ms> Override repository lock timeout.",
|
|
@@ -2696,7 +2744,7 @@ const createCli = (options = {}) => {
|
|
|
2696
2744
|
},
|
|
2697
2745
|
gh: {
|
|
2698
2746
|
type: "boolean",
|
|
2699
|
-
description: "Enable GitHub CLI based PR
|
|
2747
|
+
description: "Enable GitHub CLI based PR status checks (disable with --no-gh)",
|
|
2700
2748
|
default: true
|
|
2701
2749
|
},
|
|
2702
2750
|
allowUnsafe: {
|
|
@@ -3030,6 +3078,7 @@ const createCli = (options = {}) => {
|
|
|
3030
3078
|
"branch",
|
|
3031
3079
|
"dirty",
|
|
3032
3080
|
"merged",
|
|
3081
|
+
"pr",
|
|
3033
3082
|
"locked",
|
|
3034
3083
|
"ahead",
|
|
3035
3084
|
"behind",
|
|
@@ -3040,11 +3089,17 @@ const createCli = (options = {}) => {
|
|
|
3040
3089
|
baseBranch: snapshot.baseBranch,
|
|
3041
3090
|
worktree
|
|
3042
3091
|
});
|
|
3043
|
-
const
|
|
3092
|
+
const isBaseBranch = worktree.branch !== null && snapshot.baseBranch !== null && worktree.branch === snapshot.baseBranch;
|
|
3093
|
+
const mergedState = isBaseBranch === true ? "-" : worktree.merged.overall === true ? "merged" : worktree.merged.overall === false ? "unmerged" : "unknown";
|
|
3094
|
+
const prState = formatPrDisplayState({
|
|
3095
|
+
prStatus: worktree.pr.status,
|
|
3096
|
+
isBaseBranch
|
|
3097
|
+
});
|
|
3044
3098
|
return [
|
|
3045
3099
|
`${worktree.path === repoContext.currentWorktreeRoot ? "*" : " "} ${worktree.branch ?? "(detached)"}`,
|
|
3046
3100
|
worktree.dirty ? "dirty" : "clean",
|
|
3047
3101
|
mergedState,
|
|
3102
|
+
prState,
|
|
3048
3103
|
worktree.locked.value ? "locked" : "-",
|
|
3049
3104
|
formatListUpstreamCount(distanceFromBase.ahead),
|
|
3050
3105
|
formatListUpstreamCount(distanceFromBase.behind),
|