vde-worktree 0.0.15 → 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 +14 -8
- package/README.md +13 -7
- package/completions/fish/vw.fish +12 -2
- package/completions/zsh/_vw +12 -2
- package/dist/index.mjs +222 -123
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -617,7 +617,8 @@ const hasStateDirectory = async (repoRoot) => {
|
|
|
617
617
|
const parseLifecycle = (content) => {
|
|
618
618
|
try {
|
|
619
619
|
const parsed = JSON.parse(content);
|
|
620
|
-
|
|
620
|
+
const isLastDivergedHeadValid = parsed.lastDivergedHead === null || typeof parsed.lastDivergedHead === "string" && parsed.lastDivergedHead.length > 0;
|
|
621
|
+
if (parsed.schemaVersion !== 2 || typeof parsed.branch !== "string" || typeof parsed.worktreeId !== "string" || typeof parsed.baseBranch !== "string" || typeof parsed.everDiverged !== "boolean" || isLastDivergedHeadValid !== true || typeof parsed.createdAt !== "string" || typeof parsed.updatedAt !== "string") return {
|
|
621
622
|
valid: false,
|
|
622
623
|
record: null
|
|
623
624
|
};
|
|
@@ -665,15 +666,17 @@ const readWorktreeMergeLifecycle = async ({ repoRoot, branch }) => {
|
|
|
665
666
|
};
|
|
666
667
|
}
|
|
667
668
|
};
|
|
668
|
-
const upsertWorktreeMergeLifecycle = async ({ repoRoot, branch, baseBranch,
|
|
669
|
+
const upsertWorktreeMergeLifecycle = async ({ repoRoot, branch, baseBranch, observedDivergedHead }) => {
|
|
670
|
+
const normalizedObservedHead = typeof observedDivergedHead === "string" && observedDivergedHead.length > 0 ? observedDivergedHead : null;
|
|
669
671
|
if (await hasStateDirectory(repoRoot) !== true) {
|
|
670
672
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
671
673
|
return {
|
|
672
|
-
schemaVersion:
|
|
674
|
+
schemaVersion: 2,
|
|
673
675
|
branch,
|
|
674
676
|
worktreeId: branchToWorktreeId(branch),
|
|
675
677
|
baseBranch,
|
|
676
|
-
|
|
678
|
+
everDiverged: normalizedObservedHead !== null,
|
|
679
|
+
lastDivergedHead: normalizedObservedHead,
|
|
677
680
|
createdAt: now,
|
|
678
681
|
updatedAt: now
|
|
679
682
|
};
|
|
@@ -682,14 +685,17 @@ const upsertWorktreeMergeLifecycle = async ({ repoRoot, branch, baseBranch, crea
|
|
|
682
685
|
repoRoot,
|
|
683
686
|
branch
|
|
684
687
|
});
|
|
685
|
-
if (current.valid && current.record !== null && current.record.baseBranch === baseBranch) return current.record;
|
|
688
|
+
if (current.valid && current.record !== null && current.record.baseBranch === baseBranch && normalizedObservedHead === null) return current.record;
|
|
686
689
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
690
|
+
const everDiverged = current.record?.everDiverged === true || normalizedObservedHead !== null;
|
|
691
|
+
const lastDivergedHead = normalizedObservedHead ?? current.record?.lastDivergedHead ?? null;
|
|
687
692
|
const next = {
|
|
688
|
-
schemaVersion:
|
|
693
|
+
schemaVersion: 2,
|
|
689
694
|
branch,
|
|
690
695
|
worktreeId: branchToWorktreeId(branch),
|
|
691
696
|
baseBranch,
|
|
692
|
-
|
|
697
|
+
everDiverged,
|
|
698
|
+
lastDivergedHead,
|
|
693
699
|
createdAt: current.record?.createdAt ?? now,
|
|
694
700
|
updatedAt: now
|
|
695
701
|
};
|
|
@@ -699,15 +705,17 @@ const upsertWorktreeMergeLifecycle = async ({ repoRoot, branch, baseBranch, crea
|
|
|
699
705
|
});
|
|
700
706
|
return next;
|
|
701
707
|
};
|
|
702
|
-
const moveWorktreeMergeLifecycle = async ({ repoRoot, fromBranch, toBranch, baseBranch,
|
|
708
|
+
const moveWorktreeMergeLifecycle = async ({ repoRoot, fromBranch, toBranch, baseBranch, observedDivergedHead }) => {
|
|
709
|
+
const normalizedObservedHead = typeof observedDivergedHead === "string" && observedDivergedHead.length > 0 ? observedDivergedHead : null;
|
|
703
710
|
if (await hasStateDirectory(repoRoot) !== true) {
|
|
704
711
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
705
712
|
return {
|
|
706
|
-
schemaVersion:
|
|
713
|
+
schemaVersion: 2,
|
|
707
714
|
branch: toBranch,
|
|
708
715
|
worktreeId: branchToWorktreeId(toBranch),
|
|
709
716
|
baseBranch,
|
|
710
|
-
|
|
717
|
+
everDiverged: normalizedObservedHead !== null,
|
|
718
|
+
lastDivergedHead: normalizedObservedHead,
|
|
711
719
|
createdAt: now,
|
|
712
720
|
updatedAt: now
|
|
713
721
|
};
|
|
@@ -718,12 +726,15 @@ const moveWorktreeMergeLifecycle = async ({ repoRoot, fromBranch, toBranch, base
|
|
|
718
726
|
});
|
|
719
727
|
const targetPath = lifecycleFilePath(repoRoot, toBranch);
|
|
720
728
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
729
|
+
const everDiverged = source.record?.everDiverged === true || normalizedObservedHead !== null;
|
|
730
|
+
const lastDivergedHead = normalizedObservedHead ?? source.record?.lastDivergedHead ?? null;
|
|
721
731
|
const next = {
|
|
722
|
-
schemaVersion:
|
|
732
|
+
schemaVersion: 2,
|
|
723
733
|
branch: toBranch,
|
|
724
734
|
worktreeId: branchToWorktreeId(toBranch),
|
|
725
735
|
baseBranch,
|
|
726
|
-
|
|
736
|
+
everDiverged,
|
|
737
|
+
lastDivergedHead,
|
|
727
738
|
createdAt: source.record?.createdAt ?? now,
|
|
728
739
|
updatedAt: now
|
|
729
740
|
};
|
|
@@ -842,43 +853,68 @@ const toTargetBranches = ({ branches, baseBranch }) => {
|
|
|
842
853
|
}
|
|
843
854
|
return [...uniqueBranches];
|
|
844
855
|
};
|
|
845
|
-
const
|
|
846
|
-
return new Map(branches.map((branch) => [branch,
|
|
856
|
+
const buildUnknownPrStatusMap = (branches) => {
|
|
857
|
+
return new Map(branches.map((branch) => [branch, "unknown"]));
|
|
858
|
+
};
|
|
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;
|
|
847
864
|
};
|
|
848
|
-
const
|
|
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 }) => {
|
|
849
874
|
try {
|
|
850
875
|
const parsed = JSON.parse(raw);
|
|
851
876
|
if (Array.isArray(parsed) !== true) return null;
|
|
877
|
+
const targetBranchSet = new Set(targetBranches);
|
|
852
878
|
const records = parsed;
|
|
853
|
-
const
|
|
854
|
-
for (const record of records) {
|
|
879
|
+
const latestByBranch = /* @__PURE__ */ new Map();
|
|
880
|
+
for (const [index, record] of records.entries()) {
|
|
855
881
|
if (typeof record?.headRefName !== "string" || record.headRefName.length === 0) continue;
|
|
856
|
-
if (
|
|
857
|
-
|
|
858
|
-
|
|
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");
|
|
859
896
|
}
|
|
860
|
-
return
|
|
897
|
+
return result;
|
|
861
898
|
} catch {
|
|
862
899
|
return null;
|
|
863
900
|
}
|
|
864
901
|
};
|
|
865
|
-
const
|
|
866
|
-
if (enabled !== true) return /* @__PURE__ */ new Map();
|
|
902
|
+
const resolvePrStatusByBranchBatch = async ({ repoRoot, baseBranch, branches, enabled = true, runGh = defaultRunGh }) => {
|
|
867
903
|
if (baseBranch === null) return /* @__PURE__ */ new Map();
|
|
868
904
|
const targetBranches = toTargetBranches({
|
|
869
905
|
branches,
|
|
870
906
|
baseBranch
|
|
871
907
|
});
|
|
872
908
|
if (targetBranches.length === 0) return /* @__PURE__ */ new Map();
|
|
909
|
+
if (enabled !== true) return buildUnknownPrStatusMap(targetBranches);
|
|
873
910
|
try {
|
|
874
|
-
const targetBranchSet = new Set(targetBranches);
|
|
875
911
|
const result = await runGh({
|
|
876
912
|
cwd: repoRoot,
|
|
877
913
|
args: [
|
|
878
914
|
"pr",
|
|
879
915
|
"list",
|
|
880
916
|
"--state",
|
|
881
|
-
"
|
|
917
|
+
"all",
|
|
882
918
|
"--base",
|
|
883
919
|
baseBranch,
|
|
884
920
|
"--search",
|
|
@@ -886,21 +922,19 @@ const resolveMergedByPrBatch = async ({ repoRoot, baseBranch, branches, enabled
|
|
|
886
922
|
"--limit",
|
|
887
923
|
"1000",
|
|
888
924
|
"--json",
|
|
889
|
-
"headRefName,mergedAt"
|
|
925
|
+
"headRefName,state,mergedAt,updatedAt"
|
|
890
926
|
]
|
|
891
927
|
});
|
|
892
|
-
if (result.exitCode !== 0) return
|
|
893
|
-
const
|
|
928
|
+
if (result.exitCode !== 0) return buildUnknownPrStatusMap(targetBranches);
|
|
929
|
+
const prStatusByBranch = parsePrStatusByBranch({
|
|
894
930
|
raw: result.stdout,
|
|
895
|
-
targetBranches
|
|
931
|
+
targetBranches
|
|
896
932
|
});
|
|
897
|
-
if (
|
|
898
|
-
return
|
|
899
|
-
return [branch, mergedBranches.has(branch)];
|
|
900
|
-
}));
|
|
933
|
+
if (prStatusByBranch === null) return buildUnknownPrStatusMap(targetBranches);
|
|
934
|
+
return prStatusByBranch;
|
|
901
935
|
} catch (error) {
|
|
902
|
-
if (error.code === "ENOENT") return
|
|
903
|
-
return
|
|
936
|
+
if (error.code === "ENOENT") return buildUnknownPrStatusMap(targetBranches);
|
|
937
|
+
return buildUnknownPrStatusMap(targetBranches);
|
|
904
938
|
}
|
|
905
939
|
};
|
|
906
940
|
|
|
@@ -1049,7 +1083,57 @@ const resolveLockState = async ({ repoRoot, branch }) => {
|
|
|
1049
1083
|
};
|
|
1050
1084
|
}
|
|
1051
1085
|
};
|
|
1052
|
-
const
|
|
1086
|
+
const WORK_REFLOG_MESSAGE_PATTERN = /^(commit(?: \([^)]*\))?|cherry-pick|revert|rebase \(pick\)|merge):/;
|
|
1087
|
+
const resolveLifecycleFromReflog = async ({ repoRoot, branch, baseBranch }) => {
|
|
1088
|
+
const reflog = await runGitCommand({
|
|
1089
|
+
cwd: repoRoot,
|
|
1090
|
+
args: [
|
|
1091
|
+
"reflog",
|
|
1092
|
+
"show",
|
|
1093
|
+
"--format=%H%x09%gs",
|
|
1094
|
+
branch
|
|
1095
|
+
],
|
|
1096
|
+
reject: false
|
|
1097
|
+
});
|
|
1098
|
+
if (reflog.exitCode !== 0) return {
|
|
1099
|
+
merged: null,
|
|
1100
|
+
divergedHead: null
|
|
1101
|
+
};
|
|
1102
|
+
let latestWorkHead = null;
|
|
1103
|
+
for (const line of reflog.stdout.split("\n")) {
|
|
1104
|
+
const trimmed = line.trim();
|
|
1105
|
+
if (trimmed.length === 0) continue;
|
|
1106
|
+
const separatorIndex = trimmed.indexOf(" ");
|
|
1107
|
+
if (separatorIndex <= 0) continue;
|
|
1108
|
+
const head = trimmed.slice(0, separatorIndex).trim();
|
|
1109
|
+
const message = trimmed.slice(separatorIndex + 1).trim();
|
|
1110
|
+
if (head.length === 0 || WORK_REFLOG_MESSAGE_PATTERN.test(message) !== true) continue;
|
|
1111
|
+
if (latestWorkHead === null) latestWorkHead = head;
|
|
1112
|
+
const result = await runGitCommand({
|
|
1113
|
+
cwd: repoRoot,
|
|
1114
|
+
args: [
|
|
1115
|
+
"merge-base",
|
|
1116
|
+
"--is-ancestor",
|
|
1117
|
+
head,
|
|
1118
|
+
baseBranch
|
|
1119
|
+
],
|
|
1120
|
+
reject: false
|
|
1121
|
+
});
|
|
1122
|
+
if (result.exitCode === 0) return {
|
|
1123
|
+
merged: true,
|
|
1124
|
+
divergedHead: head
|
|
1125
|
+
};
|
|
1126
|
+
if (result.exitCode !== 1) return {
|
|
1127
|
+
merged: null,
|
|
1128
|
+
divergedHead: latestWorkHead
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
return {
|
|
1132
|
+
merged: false,
|
|
1133
|
+
divergedHead: latestWorkHead
|
|
1134
|
+
};
|
|
1135
|
+
};
|
|
1136
|
+
const resolveMergedState = async ({ repoRoot, branch, head, baseBranch, prStatusByBranch }) => {
|
|
1053
1137
|
if (branch === null) return {
|
|
1054
1138
|
byAncestry: null,
|
|
1055
1139
|
byPR: null,
|
|
@@ -1070,17 +1154,49 @@ const resolveMergedState = async ({ repoRoot, branch, head, baseBranch, mergedBy
|
|
|
1070
1154
|
if (result.exitCode === 0) byAncestry = true;
|
|
1071
1155
|
else if (result.exitCode === 1) byAncestry = false;
|
|
1072
1156
|
}
|
|
1073
|
-
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;
|
|
1074
1161
|
let byLifecycle = null;
|
|
1075
1162
|
if (baseBranch !== null) {
|
|
1076
1163
|
const lifecycle = await upsertWorktreeMergeLifecycle({
|
|
1077
1164
|
repoRoot,
|
|
1078
1165
|
branch,
|
|
1079
1166
|
baseBranch,
|
|
1080
|
-
|
|
1167
|
+
observedDivergedHead: byAncestry === false ? head : null
|
|
1081
1168
|
});
|
|
1082
|
-
if (byAncestry ===
|
|
1083
|
-
else if (byAncestry ===
|
|
1169
|
+
if (byAncestry === false) byLifecycle = false;
|
|
1170
|
+
else if (byAncestry === true) if (lifecycle.everDiverged !== true || lifecycle.lastDivergedHead === null) if (byPR === true) byLifecycle = null;
|
|
1171
|
+
else {
|
|
1172
|
+
const probe = await resolveLifecycleFromReflog({
|
|
1173
|
+
repoRoot,
|
|
1174
|
+
branch,
|
|
1175
|
+
baseBranch
|
|
1176
|
+
});
|
|
1177
|
+
byLifecycle = probe.merged;
|
|
1178
|
+
if (probe.divergedHead !== null) await upsertWorktreeMergeLifecycle({
|
|
1179
|
+
repoRoot,
|
|
1180
|
+
branch,
|
|
1181
|
+
baseBranch,
|
|
1182
|
+
observedDivergedHead: probe.divergedHead
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
else {
|
|
1186
|
+
const lifecycleResult = await runGitCommand({
|
|
1187
|
+
cwd: repoRoot,
|
|
1188
|
+
args: [
|
|
1189
|
+
"merge-base",
|
|
1190
|
+
"--is-ancestor",
|
|
1191
|
+
lifecycle.lastDivergedHead,
|
|
1192
|
+
baseBranch
|
|
1193
|
+
],
|
|
1194
|
+
reject: false
|
|
1195
|
+
});
|
|
1196
|
+
if (lifecycleResult.exitCode === 0) byLifecycle = true;
|
|
1197
|
+
else if (lifecycleResult.exitCode === 1) byLifecycle = false;
|
|
1198
|
+
else byLifecycle = null;
|
|
1199
|
+
}
|
|
1084
1200
|
}
|
|
1085
1201
|
return {
|
|
1086
1202
|
byAncestry,
|
|
@@ -1092,6 +1208,10 @@ const resolveMergedState = async ({ repoRoot, branch, head, baseBranch, mergedBy
|
|
|
1092
1208
|
})
|
|
1093
1209
|
};
|
|
1094
1210
|
};
|
|
1211
|
+
const resolvePrState = ({ branch, baseBranch, prStatusByBranch }) => {
|
|
1212
|
+
if (branch === null || branch === baseBranch) return { status: null };
|
|
1213
|
+
return { status: prStatusByBranch.get(branch) ?? null };
|
|
1214
|
+
};
|
|
1095
1215
|
const resolveMergedOverall = ({ byAncestry, byPR, byLifecycle }) => {
|
|
1096
1216
|
if (byPR === true || byLifecycle === true) return true;
|
|
1097
1217
|
if (byAncestry === false) return false;
|
|
@@ -1138,7 +1258,7 @@ const resolveUpstreamState = async (worktreePath) => {
|
|
|
1138
1258
|
remote: upstreamRef.stdout.trim()
|
|
1139
1259
|
};
|
|
1140
1260
|
};
|
|
1141
|
-
const enrichWorktree = async ({ repoRoot, worktree, baseBranch,
|
|
1261
|
+
const enrichWorktree = async ({ repoRoot, worktree, baseBranch, prStatusByBranch }) => {
|
|
1142
1262
|
const [dirty, locked, merged, upstream] = await Promise.all([
|
|
1143
1263
|
resolveDirty(worktree.path),
|
|
1144
1264
|
resolveLockState({
|
|
@@ -1150,10 +1270,15 @@ const enrichWorktree = async ({ repoRoot, worktree, baseBranch, mergedByPrByBran
|
|
|
1150
1270
|
branch: worktree.branch,
|
|
1151
1271
|
head: worktree.head,
|
|
1152
1272
|
baseBranch,
|
|
1153
|
-
|
|
1273
|
+
prStatusByBranch
|
|
1154
1274
|
}),
|
|
1155
1275
|
resolveUpstreamState(worktree.path)
|
|
1156
1276
|
]);
|
|
1277
|
+
const pr = resolvePrState({
|
|
1278
|
+
branch: worktree.branch,
|
|
1279
|
+
baseBranch,
|
|
1280
|
+
prStatusByBranch
|
|
1281
|
+
});
|
|
1157
1282
|
return {
|
|
1158
1283
|
branch: worktree.branch,
|
|
1159
1284
|
path: worktree.path,
|
|
@@ -1161,6 +1286,7 @@ const enrichWorktree = async ({ repoRoot, worktree, baseBranch, mergedByPrByBran
|
|
|
1161
1286
|
dirty,
|
|
1162
1287
|
locked,
|
|
1163
1288
|
merged,
|
|
1289
|
+
pr,
|
|
1164
1290
|
upstream
|
|
1165
1291
|
};
|
|
1166
1292
|
};
|
|
@@ -1170,7 +1296,7 @@ const collectWorktreeSnapshot = async (repoRoot, { noGh = false } = {}) => {
|
|
|
1170
1296
|
listGitWorktrees(repoRoot),
|
|
1171
1297
|
resolveEnableGh(repoRoot)
|
|
1172
1298
|
]);
|
|
1173
|
-
const
|
|
1299
|
+
const prStatusByBranch = await resolvePrStatusByBranchBatch({
|
|
1174
1300
|
repoRoot,
|
|
1175
1301
|
baseBranch,
|
|
1176
1302
|
branches: worktrees.map((worktree) => worktree.branch),
|
|
@@ -1184,7 +1310,7 @@ const collectWorktreeSnapshot = async (repoRoot, { noGh = false } = {}) => {
|
|
|
1184
1310
|
repoRoot,
|
|
1185
1311
|
worktree,
|
|
1186
1312
|
baseBranch,
|
|
1187
|
-
|
|
1313
|
+
prStatusByBranch
|
|
1188
1314
|
});
|
|
1189
1315
|
}))
|
|
1190
1316
|
};
|
|
@@ -1440,9 +1566,9 @@ const colorizeListTableLine = ({ line, theme }) => {
|
|
|
1440
1566
|
const segments = line.split("│");
|
|
1441
1567
|
if (segments.length < 3) return line;
|
|
1442
1568
|
const cells = segments.slice(1, -1);
|
|
1443
|
-
if (cells.length !==
|
|
1569
|
+
if (cells.length !== 8) return line;
|
|
1444
1570
|
const headers = cells.map((cell) => cell.trim());
|
|
1445
|
-
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") {
|
|
1446
1572
|
const nextCells = cells.map((cell) => colorizeCellContent({
|
|
1447
1573
|
cell,
|
|
1448
1574
|
color: theme.header
|
|
@@ -1456,15 +1582,18 @@ const colorizeListTableLine = ({ line, theme }) => {
|
|
|
1456
1582
|
const branchCell = cells[0];
|
|
1457
1583
|
const dirtyCell = cells[1];
|
|
1458
1584
|
const mergedCell = cells[2];
|
|
1459
|
-
const
|
|
1460
|
-
const
|
|
1461
|
-
const
|
|
1462
|
-
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];
|
|
1463
1590
|
const branchColor = branchCell.includes("(detached)") === true ? theme.branchDetached : branchCell.trimStart().startsWith("*") ? theme.branchCurrent : theme.branch;
|
|
1464
1591
|
const dirtyTrimmed = dirtyCell.trim();
|
|
1465
1592
|
const dirtyColor = dirtyTrimmed === "dirty" ? theme.dirty : dirtyTrimmed === "clean" ? theme.clean : theme.value;
|
|
1466
1593
|
const mergedTrimmed = mergedCell.trim();
|
|
1467
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;
|
|
1468
1597
|
const lockedColor = lockedCell.trim() === "locked" ? theme.locked : theme.muted;
|
|
1469
1598
|
const aheadTrimmed = aheadCell.trim();
|
|
1470
1599
|
const aheadValue = Number.parseInt(aheadTrimmed, 10);
|
|
@@ -1485,6 +1614,10 @@ const colorizeListTableLine = ({ line, theme }) => {
|
|
|
1485
1614
|
cell: mergedCell,
|
|
1486
1615
|
color: mergedColor
|
|
1487
1616
|
}),
|
|
1617
|
+
colorizeCellContent({
|
|
1618
|
+
cell: prCell,
|
|
1619
|
+
color: prColor
|
|
1620
|
+
}),
|
|
1488
1621
|
colorizeCellContent({
|
|
1489
1622
|
cell: lockedCell,
|
|
1490
1623
|
color: lockedColor
|
|
@@ -1525,7 +1658,7 @@ const commandHelpEntries = [
|
|
|
1525
1658
|
name: "list",
|
|
1526
1659
|
usage: "vw list [--json]",
|
|
1527
1660
|
summary: "List worktrees with status metadata.",
|
|
1528
|
-
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."]
|
|
1529
1662
|
},
|
|
1530
1663
|
{
|
|
1531
1664
|
name: "status",
|
|
@@ -1897,22 +2030,6 @@ const resolveBaseBranch = async (repoRoot) => {
|
|
|
1897
2030
|
for (const candidate of ["main", "master"]) if (await doesGitRefExist(repoRoot, `refs/heads/${candidate}`)) return candidate;
|
|
1898
2031
|
throw createCliError("INVALID_ARGUMENT", { message: "Unable to resolve base branch. Configure vde-worktree.baseBranch." });
|
|
1899
2032
|
};
|
|
1900
|
-
const resolveBranchHead = async ({ repoRoot, branch }) => {
|
|
1901
|
-
const resolved = await runGitCommand({
|
|
1902
|
-
cwd: repoRoot,
|
|
1903
|
-
args: [
|
|
1904
|
-
"rev-parse",
|
|
1905
|
-
"--verify",
|
|
1906
|
-
branch
|
|
1907
|
-
],
|
|
1908
|
-
reject: false
|
|
1909
|
-
});
|
|
1910
|
-
if (resolved.exitCode !== 0 || resolved.stdout.trim().length === 0) throw createCliError("INVALID_ARGUMENT", {
|
|
1911
|
-
message: `Failed to resolve branch head: ${branch}`,
|
|
1912
|
-
details: { branch }
|
|
1913
|
-
});
|
|
1914
|
-
return resolved.stdout.trim();
|
|
1915
|
-
};
|
|
1916
2033
|
const ensureTargetPathWritable = async (targetPath) => {
|
|
1917
2034
|
try {
|
|
1918
2035
|
await access(targetPath, constants.F_OK);
|
|
@@ -2358,6 +2475,11 @@ const formatMergedColor = ({ mergedState, theme }) => {
|
|
|
2358
2475
|
if (normalized === "base") return theme.base(mergedState);
|
|
2359
2476
|
return theme.unknown(mergedState);
|
|
2360
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
|
+
};
|
|
2361
2483
|
const formatListUpstreamCount = (value) => {
|
|
2362
2484
|
if (value === null) return "-";
|
|
2363
2485
|
return String(value);
|
|
@@ -2519,7 +2641,7 @@ const renderGeneralHelpText = ({ version }) => {
|
|
|
2519
2641
|
" --json Output machine-readable JSON.",
|
|
2520
2642
|
" --verbose Enable verbose logs.",
|
|
2521
2643
|
" --no-hooks Disable hooks for this run (requires --allow-unsafe).",
|
|
2522
|
-
" --no-gh Disable GitHub CLI based PR
|
|
2644
|
+
" --no-gh Disable GitHub CLI based PR status checks for this run.",
|
|
2523
2645
|
" --allow-unsafe Explicitly allow unsafe behavior in non-TTY mode.",
|
|
2524
2646
|
" --hook-timeout-ms <ms> Override hook timeout.",
|
|
2525
2647
|
" --lock-timeout-ms <ms> Override repository lock timeout.",
|
|
@@ -2622,7 +2744,7 @@ const createCli = (options = {}) => {
|
|
|
2622
2744
|
},
|
|
2623
2745
|
gh: {
|
|
2624
2746
|
type: "boolean",
|
|
2625
|
-
description: "Enable GitHub CLI based PR
|
|
2747
|
+
description: "Enable GitHub CLI based PR status checks (disable with --no-gh)",
|
|
2626
2748
|
default: true
|
|
2627
2749
|
},
|
|
2628
2750
|
allowUnsafe: {
|
|
@@ -2956,6 +3078,7 @@ const createCli = (options = {}) => {
|
|
|
2956
3078
|
"branch",
|
|
2957
3079
|
"dirty",
|
|
2958
3080
|
"merged",
|
|
3081
|
+
"pr",
|
|
2959
3082
|
"locked",
|
|
2960
3083
|
"ahead",
|
|
2961
3084
|
"behind",
|
|
@@ -2966,11 +3089,17 @@ const createCli = (options = {}) => {
|
|
|
2966
3089
|
baseBranch: snapshot.baseBranch,
|
|
2967
3090
|
worktree
|
|
2968
3091
|
});
|
|
2969
|
-
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
|
+
});
|
|
2970
3098
|
return [
|
|
2971
3099
|
`${worktree.path === repoContext.currentWorktreeRoot ? "*" : " "} ${worktree.branch ?? "(detached)"}`,
|
|
2972
3100
|
worktree.dirty ? "dirty" : "clean",
|
|
2973
3101
|
mergedState,
|
|
3102
|
+
prState,
|
|
2974
3103
|
worktree.locked.value ? "locked" : "-",
|
|
2975
3104
|
formatListUpstreamCount(distanceFromBase.ahead),
|
|
2976
3105
|
formatListUpstreamCount(distanceFromBase.behind),
|
|
@@ -3095,10 +3224,7 @@ const createCli = (options = {}) => {
|
|
|
3095
3224
|
repoRoot,
|
|
3096
3225
|
branch,
|
|
3097
3226
|
baseBranch,
|
|
3098
|
-
|
|
3099
|
-
repoRoot,
|
|
3100
|
-
branch
|
|
3101
|
-
})
|
|
3227
|
+
observedDivergedHead: null
|
|
3102
3228
|
});
|
|
3103
3229
|
await runPostHook({
|
|
3104
3230
|
name: "new",
|
|
@@ -3133,18 +3259,12 @@ const createCli = (options = {}) => {
|
|
|
3133
3259
|
const snapshot = await collectWorktreeSnapshot$1(repoRoot);
|
|
3134
3260
|
const existing = snapshot.worktrees.find((worktree) => worktree.branch === branch);
|
|
3135
3261
|
if (existing !== void 0) {
|
|
3136
|
-
if (snapshot.baseBranch !== null) {
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
repoRoot,
|
|
3143
|
-
branch,
|
|
3144
|
-
baseBranch: snapshot.baseBranch,
|
|
3145
|
-
createdHead: branchHead
|
|
3146
|
-
});
|
|
3147
|
-
}
|
|
3262
|
+
if (snapshot.baseBranch !== null) await upsertWorktreeMergeLifecycle({
|
|
3263
|
+
repoRoot,
|
|
3264
|
+
branch,
|
|
3265
|
+
baseBranch: snapshot.baseBranch,
|
|
3266
|
+
observedDivergedHead: null
|
|
3267
|
+
});
|
|
3148
3268
|
return {
|
|
3149
3269
|
status: "existing",
|
|
3150
3270
|
branch,
|
|
@@ -3190,18 +3310,12 @@ const createCli = (options = {}) => {
|
|
|
3190
3310
|
]
|
|
3191
3311
|
});
|
|
3192
3312
|
}
|
|
3193
|
-
if (lifecycleBaseBranch !== null) {
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
repoRoot,
|
|
3200
|
-
branch,
|
|
3201
|
-
baseBranch: lifecycleBaseBranch,
|
|
3202
|
-
createdHead: branchHead
|
|
3203
|
-
});
|
|
3204
|
-
}
|
|
3313
|
+
if (lifecycleBaseBranch !== null) await upsertWorktreeMergeLifecycle({
|
|
3314
|
+
repoRoot,
|
|
3315
|
+
branch,
|
|
3316
|
+
baseBranch: lifecycleBaseBranch,
|
|
3317
|
+
observedDivergedHead: null
|
|
3318
|
+
});
|
|
3205
3319
|
await runPostHook({
|
|
3206
3320
|
name: "switch",
|
|
3207
3321
|
context: hookContext
|
|
@@ -3301,19 +3415,13 @@ const createCli = (options = {}) => {
|
|
|
3301
3415
|
newPath
|
|
3302
3416
|
]
|
|
3303
3417
|
});
|
|
3304
|
-
if (snapshot.baseBranch !== null) {
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
fromBranch: oldBranch,
|
|
3312
|
-
toBranch: newBranch,
|
|
3313
|
-
baseBranch: snapshot.baseBranch,
|
|
3314
|
-
createdHead: branchHead
|
|
3315
|
-
});
|
|
3316
|
-
}
|
|
3418
|
+
if (snapshot.baseBranch !== null) await moveWorktreeMergeLifecycle({
|
|
3419
|
+
repoRoot,
|
|
3420
|
+
fromBranch: oldBranch,
|
|
3421
|
+
toBranch: newBranch,
|
|
3422
|
+
baseBranch: snapshot.baseBranch,
|
|
3423
|
+
observedDivergedHead: null
|
|
3424
|
+
});
|
|
3317
3425
|
await runPostHook({
|
|
3318
3426
|
name: "mv",
|
|
3319
3427
|
context: hookContext
|
|
@@ -3582,10 +3690,7 @@ const createCli = (options = {}) => {
|
|
|
3582
3690
|
repoRoot,
|
|
3583
3691
|
branch,
|
|
3584
3692
|
baseBranch: lifecycleBaseBranch,
|
|
3585
|
-
|
|
3586
|
-
repoRoot,
|
|
3587
|
-
branch
|
|
3588
|
-
})
|
|
3693
|
+
observedDivergedHead: null
|
|
3589
3694
|
});
|
|
3590
3695
|
await runPostHook({
|
|
3591
3696
|
name: "get",
|
|
@@ -3612,10 +3717,7 @@ const createCli = (options = {}) => {
|
|
|
3612
3717
|
repoRoot,
|
|
3613
3718
|
branch,
|
|
3614
3719
|
baseBranch: lifecycleBaseBranch,
|
|
3615
|
-
|
|
3616
|
-
repoRoot,
|
|
3617
|
-
branch
|
|
3618
|
-
})
|
|
3720
|
+
observedDivergedHead: null
|
|
3619
3721
|
});
|
|
3620
3722
|
await runPostHook({
|
|
3621
3723
|
name: "get",
|
|
@@ -3727,10 +3829,7 @@ const createCli = (options = {}) => {
|
|
|
3727
3829
|
repoRoot,
|
|
3728
3830
|
branch,
|
|
3729
3831
|
baseBranch,
|
|
3730
|
-
|
|
3731
|
-
repoRoot,
|
|
3732
|
-
branch
|
|
3733
|
-
})
|
|
3832
|
+
observedDivergedHead: null
|
|
3734
3833
|
});
|
|
3735
3834
|
if (stashOid !== null) {
|
|
3736
3835
|
if ((await runGitCommand({
|