@ogpoyraz/wtx 0.8.2 → 0.8.4
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/completions/_wtx.zsh +1 -0
- package/completions/wtx.bash +1 -1
- package/dist/cli.mjs +353 -91
- package/package.json +1 -1
package/completions/_wtx.zsh
CHANGED
|
@@ -137,6 +137,7 @@ _wtx() {
|
|
|
137
137
|
_arguments \
|
|
138
138
|
':pr-link:' \
|
|
139
139
|
{-r,--repo}'[Target specific repo(s)]:repo:_wtx_repos' \
|
|
140
|
+
{-f,--force}'[Override existing local branch/worktree]' \
|
|
140
141
|
{-q,--quiet}'[Suppress output]' \
|
|
141
142
|
'--verbose[Show git commands]' \
|
|
142
143
|
'--dry-run[Show what would happen]'
|
package/completions/wtx.bash
CHANGED
|
@@ -97,7 +97,7 @@ _wtx_completions() {
|
|
|
97
97
|
fetch) flags="--repo -q --quiet --verbose --dry-run --help" ;;
|
|
98
98
|
sync) flags="--repo -q --quiet --verbose --dry-run --help" ;;
|
|
99
99
|
deps) flags="-r --repo --install --symlink --json -q --quiet --verbose --dry-run --help" ;;
|
|
100
|
-
pull) flags="-r --repo -q --quiet --verbose --dry-run --help" ;;
|
|
100
|
+
pull) flags="-r --repo -f --force -q --quiet --verbose --dry-run --help" ;;
|
|
101
101
|
ls) flags="-r --repo --pr --json -q --quiet --verbose --dry-run --help" ;;
|
|
102
102
|
status) flags="-r --repo --base --json -q --quiet --verbose --dry-run --help" ;;
|
|
103
103
|
stack) flags="-r --repo --json -q --quiet --verbose --dry-run --help" ;;
|
package/dist/cli.mjs
CHANGED
|
@@ -26378,6 +26378,7 @@ var init_HelpOverlay = __esm(() => {
|
|
|
26378
26378
|
["n", "Create new worktree (pick dependency strategy)"],
|
|
26379
26379
|
["f", "Fetch main for selected repo(s)"],
|
|
26380
26380
|
["p", "Pull latest changes for selected branch(es)"],
|
|
26381
|
+
["P (shift+p)", "Pull PR by link (force-override if exists)"],
|
|
26381
26382
|
["b", "Rebase selected onto base (or main)"],
|
|
26382
26383
|
["s", "Sync selected (env files + hooks)"],
|
|
26383
26384
|
["i", "Install dependencies in selection (worktrees and main)"],
|
|
@@ -26558,7 +26559,7 @@ var init_ActionLogModal = __esm(() => {
|
|
|
26558
26559
|
});
|
|
26559
26560
|
|
|
26560
26561
|
// src/lib/history.ts
|
|
26561
|
-
import
|
|
26562
|
+
import fs32 from "fs";
|
|
26562
26563
|
import path36 from "path";
|
|
26563
26564
|
import os4 from "os";
|
|
26564
26565
|
function getHistoryDir() {
|
|
@@ -26572,24 +26573,24 @@ function rotateHistory(maxBytes = HISTORY_MAX_BYTES, keepLines = HISTORY_ROTATE_
|
|
|
26572
26573
|
const historyPath = getHistoryPath();
|
|
26573
26574
|
let size = 0;
|
|
26574
26575
|
try {
|
|
26575
|
-
size =
|
|
26576
|
+
size = fs32.statSync(historyPath).size;
|
|
26576
26577
|
} catch {
|
|
26577
26578
|
return;
|
|
26578
26579
|
}
|
|
26579
26580
|
if (size <= maxBytes)
|
|
26580
26581
|
return;
|
|
26581
|
-
const lines =
|
|
26582
|
+
const lines = fs32.readFileSync(historyPath, "utf-8").split(`
|
|
26582
26583
|
`).filter(Boolean);
|
|
26583
26584
|
const kept = lines.slice(-keepLines);
|
|
26584
26585
|
const tmpPath = `${historyPath}.tmp.${process.pid}`;
|
|
26585
26586
|
try {
|
|
26586
|
-
|
|
26587
|
+
fs32.writeFileSync(tmpPath, kept.length > 0 ? `${kept.join(`
|
|
26587
26588
|
`)}
|
|
26588
26589
|
` : "", "utf-8");
|
|
26589
|
-
|
|
26590
|
+
fs32.renameSync(tmpPath, historyPath);
|
|
26590
26591
|
} catch (err) {
|
|
26591
|
-
if (
|
|
26592
|
-
|
|
26592
|
+
if (fs32.existsSync(tmpPath)) {
|
|
26593
|
+
fs32.unlinkSync(tmpPath);
|
|
26593
26594
|
}
|
|
26594
26595
|
throw err;
|
|
26595
26596
|
}
|
|
@@ -26597,18 +26598,18 @@ function rotateHistory(maxBytes = HISTORY_MAX_BYTES, keepLines = HISTORY_ROTATE_
|
|
|
26597
26598
|
function appendHistory(entry) {
|
|
26598
26599
|
try {
|
|
26599
26600
|
const dir = getHistoryDir();
|
|
26600
|
-
if (!
|
|
26601
|
-
|
|
26601
|
+
if (!fs32.existsSync(dir)) {
|
|
26602
|
+
fs32.mkdirSync(dir, { recursive: true });
|
|
26602
26603
|
}
|
|
26603
26604
|
rotateHistory();
|
|
26604
|
-
|
|
26605
|
+
fs32.appendFileSync(getHistoryPath(), `${JSON.stringify(entry)}
|
|
26605
26606
|
`, "utf-8");
|
|
26606
26607
|
} catch {}
|
|
26607
26608
|
}
|
|
26608
26609
|
function readRecentHistory(limit = 50) {
|
|
26609
26610
|
let content;
|
|
26610
26611
|
try {
|
|
26611
|
-
content =
|
|
26612
|
+
content = fs32.readFileSync(getHistoryPath(), "utf-8");
|
|
26612
26613
|
} catch {
|
|
26613
26614
|
return [];
|
|
26614
26615
|
}
|
|
@@ -26739,7 +26740,7 @@ var init_HistoryOverlay = __esm(() => {
|
|
|
26739
26740
|
// src/tui/components/InputModal.tsx
|
|
26740
26741
|
import { useState as useState3 } from "react";
|
|
26741
26742
|
import { jsx as jsx9, jsxs as jsxs8 } from "@opentui/react/jsx-runtime";
|
|
26742
|
-
function InputModal({ title, placeholder, initialValue, errorMessage, onSubmit }) {
|
|
26743
|
+
function InputModal({ title, placeholder, initialValue, errorMessage: errorMessage2, onSubmit }) {
|
|
26743
26744
|
const [value, setValue] = useState3(initialValue ?? "");
|
|
26744
26745
|
return /* @__PURE__ */ jsx9(Overlay, {
|
|
26745
26746
|
title,
|
|
@@ -26754,10 +26755,10 @@ function InputModal({ title, placeholder, initialValue, errorMessage, onSubmit }
|
|
|
26754
26755
|
onInput: (val) => setValue(val),
|
|
26755
26756
|
onSubmit: () => onSubmit(value)
|
|
26756
26757
|
}),
|
|
26757
|
-
|
|
26758
|
+
errorMessage2 ? /* @__PURE__ */ jsx9("text", {
|
|
26758
26759
|
fg: tokens.error,
|
|
26759
26760
|
style: { marginTop: 1 },
|
|
26760
|
-
children:
|
|
26761
|
+
children: errorMessage2
|
|
26761
26762
|
}) : /* @__PURE__ */ jsx9("text", {
|
|
26762
26763
|
fg: tokens.dim,
|
|
26763
26764
|
style: { marginTop: 1 },
|
|
@@ -27278,6 +27279,7 @@ var init_useSpinnerFrame = __esm(() => {
|
|
|
27278
27279
|
|
|
27279
27280
|
// src/tui/components/App.tsx
|
|
27280
27281
|
import { useState as useState7, useEffect as useEffect6, useMemo, useRef as useRef4, useCallback as useCallback3 } from "react";
|
|
27282
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
27281
27283
|
import { useKeyboard as useKeyboard3, useRenderer, useSelectionHandler } from "@opentui/react";
|
|
27282
27284
|
import { jsx as jsx13, jsxs as jsxs12 } from "@opentui/react/jsx-runtime";
|
|
27283
27285
|
function getWorktreePathFor(repoName, branch) {
|
|
@@ -27304,6 +27306,9 @@ function App({ opts }) {
|
|
|
27304
27306
|
const [createBaseModal, setCreateBaseModal] = useState7(null);
|
|
27305
27307
|
const [createBaseError, setCreateBaseError] = useState7();
|
|
27306
27308
|
const [createDepsChoice, setCreateDepsChoice] = useState7(null);
|
|
27309
|
+
const [pullPrModal, setPullPrModal] = useState7(false);
|
|
27310
|
+
const [pullPrError, setPullPrError] = useState7();
|
|
27311
|
+
const [pullForceChoice, setPullForceChoice] = useState7(null);
|
|
27307
27312
|
const [renameModal, setRenameModal] = useState7(false);
|
|
27308
27313
|
const [renameError, setRenameError] = useState7();
|
|
27309
27314
|
const [configOpen, setConfigOpen] = useState7(false);
|
|
@@ -27487,6 +27492,22 @@ function App({ opts }) {
|
|
|
27487
27492
|
args.push("--deps", deps);
|
|
27488
27493
|
executeOp(op, args, [repoName]);
|
|
27489
27494
|
};
|
|
27495
|
+
const startPullPr = (link, repoName, force) => {
|
|
27496
|
+
const op = {
|
|
27497
|
+
id: nextOpId.current++,
|
|
27498
|
+
kind: "pull",
|
|
27499
|
+
repoNames: [repoName],
|
|
27500
|
+
label: `PR ${link.split("/").pop()}`,
|
|
27501
|
+
title: `Pull ${link}`,
|
|
27502
|
+
status: "queued",
|
|
27503
|
+
lines: []
|
|
27504
|
+
};
|
|
27505
|
+
setOps((prev) => [...prev, op]);
|
|
27506
|
+
const args = ["pull", link, "--repo", repoName];
|
|
27507
|
+
if (force)
|
|
27508
|
+
args.push("--force");
|
|
27509
|
+
executeOp(op, args, [repoName]);
|
|
27510
|
+
};
|
|
27490
27511
|
useKeyboard3((key) => {
|
|
27491
27512
|
if (isFiltering) {
|
|
27492
27513
|
if (key.name === "escape") {
|
|
@@ -27520,6 +27541,15 @@ function App({ opts }) {
|
|
|
27520
27541
|
}
|
|
27521
27542
|
if (createDepsChoice)
|
|
27522
27543
|
return;
|
|
27544
|
+
if (pullPrModal) {
|
|
27545
|
+
if (key.name === "escape") {
|
|
27546
|
+
setPullPrModal(false);
|
|
27547
|
+
setPullPrError(undefined);
|
|
27548
|
+
}
|
|
27549
|
+
return;
|
|
27550
|
+
}
|
|
27551
|
+
if (pullForceChoice)
|
|
27552
|
+
return;
|
|
27523
27553
|
if (renameModal) {
|
|
27524
27554
|
if (key.name === "escape") {
|
|
27525
27555
|
setRenameModal(false);
|
|
@@ -27544,7 +27574,8 @@ function App({ opts }) {
|
|
|
27544
27574
|
if (action === "confirm_remove") {
|
|
27545
27575
|
startBatchActions("remove", rows, (r) => {
|
|
27546
27576
|
const args = ["remove", r.branch, "--repo", r.repoName, "--yes"];
|
|
27547
|
-
|
|
27577
|
+
const needsForce = r.dirtyFiles.length > 0 || !existsSync4(r.path);
|
|
27578
|
+
if (needsForce)
|
|
27548
27579
|
args.push("--force");
|
|
27549
27580
|
return args;
|
|
27550
27581
|
});
|
|
@@ -27633,6 +27664,20 @@ function App({ opts }) {
|
|
|
27633
27664
|
setCreateError(undefined);
|
|
27634
27665
|
return;
|
|
27635
27666
|
}
|
|
27667
|
+
if (key.name === "P" || key.name === "p" && key.shift) {
|
|
27668
|
+
const repoName = selectedRow?.repoName ?? getSelectedRows()[0]?.repoName;
|
|
27669
|
+
if (!repoName) {
|
|
27670
|
+
flash("No repo selected");
|
|
27671
|
+
return;
|
|
27672
|
+
}
|
|
27673
|
+
if (busyRepos.has(repoName)) {
|
|
27674
|
+
flash(`${repoName} is busy`);
|
|
27675
|
+
return;
|
|
27676
|
+
}
|
|
27677
|
+
setPullPrModal(true);
|
|
27678
|
+
setPullPrError(undefined);
|
|
27679
|
+
return;
|
|
27680
|
+
}
|
|
27636
27681
|
if (key.name === "p") {
|
|
27637
27682
|
const targets = getSelectedRows();
|
|
27638
27683
|
if (targets.length === 0)
|
|
@@ -27938,6 +27983,47 @@ function App({ opts }) {
|
|
|
27938
27983
|
},
|
|
27939
27984
|
onCancel: () => setCreateDepsChoice(null)
|
|
27940
27985
|
}),
|
|
27986
|
+
pullPrModal && /* @__PURE__ */ jsx13(InputModal, {
|
|
27987
|
+
title: `Pull PR into ${selectedRow?.repoName ?? ""}`,
|
|
27988
|
+
placeholder: "https://github.com/owner/repo/pull/123",
|
|
27989
|
+
errorMessage: pullPrError,
|
|
27990
|
+
onSubmit: (value) => {
|
|
27991
|
+
const link = value.trim();
|
|
27992
|
+
if (!link) {
|
|
27993
|
+
setPullPrModal(false);
|
|
27994
|
+
return;
|
|
27995
|
+
}
|
|
27996
|
+
if (!link.includes("github.com") || !link.includes("/pull/")) {
|
|
27997
|
+
setPullPrError("Invalid PR link: expected https://github.com/{owner}/{repo}/pull/{N}");
|
|
27998
|
+
return;
|
|
27999
|
+
}
|
|
28000
|
+
const repoName = selectedRow?.repoName;
|
|
28001
|
+
if (!repoName) {
|
|
28002
|
+
setPullPrError("No repo selected");
|
|
28003
|
+
return;
|
|
28004
|
+
}
|
|
28005
|
+
if (busyRepos.has(repoName)) {
|
|
28006
|
+
setPullPrError(`${repoName} is busy`);
|
|
28007
|
+
return;
|
|
28008
|
+
}
|
|
28009
|
+
setPullPrModal(false);
|
|
28010
|
+
setPullPrError(undefined);
|
|
28011
|
+
setPullForceChoice({ link, repoName });
|
|
28012
|
+
}
|
|
28013
|
+
}),
|
|
28014
|
+
pullForceChoice && /* @__PURE__ */ jsx13(ChoiceModal, {
|
|
28015
|
+
title: `Pull ${pullForceChoice.link.split("/").pop()}?`,
|
|
28016
|
+
options: [
|
|
28017
|
+
{ value: "normal", label: "Pull (skip if exists)", desc: "Fails if branch already exists" },
|
|
28018
|
+
{ value: "force", label: "Force Override", desc: "Remove existing branch/worktree and recreate" }
|
|
28019
|
+
],
|
|
28020
|
+
onSubmit: (choice) => {
|
|
28021
|
+
const { link, repoName } = pullForceChoice;
|
|
28022
|
+
setPullForceChoice(null);
|
|
28023
|
+
startPullPr(link, repoName, choice === "force");
|
|
28024
|
+
},
|
|
28025
|
+
onCancel: () => setPullForceChoice(null)
|
|
28026
|
+
}),
|
|
27941
28027
|
renameModal && selectedRow && /* @__PURE__ */ jsx13(InputModal, {
|
|
27942
28028
|
title: `Rename branch ${selectedRow.branch}`,
|
|
27943
28029
|
initialValue: selectedRow.branch,
|
|
@@ -27971,7 +28057,7 @@ function App({ opts }) {
|
|
|
27971
28057
|
`Branch will be renamed and the checkout moved to:`,
|
|
27972
28058
|
getWorktreePathFor(modal.row.repoName, modal.to),
|
|
27973
28059
|
"",
|
|
27974
|
-
"
|
|
28060
|
+
"Uncommitted changes and synced files move with it.",
|
|
27975
28061
|
"Proceed?"
|
|
27976
28062
|
].join(`
|
|
27977
28063
|
`)
|
|
@@ -30642,7 +30728,7 @@ import path28 from "path";
|
|
|
30642
30728
|
init_forge();
|
|
30643
30729
|
init_stack();
|
|
30644
30730
|
function registerPullCommand(program2) {
|
|
30645
|
-
program2.command("pull <link>").description("Fetch a forge PR and create its worktree").option("-r, --repo <repos...>", "Target specific repo").action(async (link, options) => {
|
|
30731
|
+
program2.command("pull <link>").description("Fetch a forge PR and create its worktree").option("-r, --repo <repos...>", "Target specific repo").option("-f, --force", "Override existing local branch/worktree if it exists").action(async (link, options) => {
|
|
30646
30732
|
const globalOpts = program2.opts();
|
|
30647
30733
|
const config2 = loadConfig();
|
|
30648
30734
|
const repoFilter = parseRepoFlag(options.repo);
|
|
@@ -30753,12 +30839,45 @@ function registerPullCommand(program2) {
|
|
|
30753
30839
|
}
|
|
30754
30840
|
const wtPath = getWorktreePath(target, branch);
|
|
30755
30841
|
const worktrees = await getWorktreeList(target.mainPath);
|
|
30842
|
+
const existingWorktree = worktrees.find((w) => w.path === wtPath || w.branch === branch);
|
|
30756
30843
|
const localBranchFound = await localBranchExists(target.mainPath, branch, globalOpts);
|
|
30757
|
-
const dirExists =
|
|
30844
|
+
const dirExists = Boolean(existingWorktree) || fs22.existsSync(wtPath);
|
|
30758
30845
|
if (localBranchFound || dirExists) {
|
|
30759
|
-
|
|
30760
|
-
|
|
30761
|
-
|
|
30846
|
+
if (options.force) {
|
|
30847
|
+
stepWarning(`Branch '${branch}' already exists — --force: will override`, dirExists ? wtPath : "local branch exists");
|
|
30848
|
+
} else {
|
|
30849
|
+
stepWarning(`Branch '${branch}' already exists`, dirExists ? wtPath : "local branch exists");
|
|
30850
|
+
summaryWarning(`Nothing pulled — branch '${branch}' already exists`);
|
|
30851
|
+
return;
|
|
30852
|
+
}
|
|
30853
|
+
}
|
|
30854
|
+
if (options.force && (localBranchFound || dirExists)) {
|
|
30855
|
+
if (existingWorktree) {
|
|
30856
|
+
try {
|
|
30857
|
+
await gitExec(["-C", target.mainPath, "worktree", "remove", "--force", existingWorktree.path], { verbose: globalOpts.verbose, dryRun: globalOpts.dryRun });
|
|
30858
|
+
} catch (err) {
|
|
30859
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
30860
|
+
if (!message.includes("not a working tree")) {
|
|
30861
|
+
stepWarning("Worktree removal failed", message.split(`
|
|
30862
|
+
`)[0] ?? message);
|
|
30863
|
+
}
|
|
30864
|
+
}
|
|
30865
|
+
}
|
|
30866
|
+
if (!globalOpts.dryRun && fs22.existsSync(wtPath)) {
|
|
30867
|
+
fs22.rmSync(wtPath, { recursive: true, force: true });
|
|
30868
|
+
}
|
|
30869
|
+
if (localBranchFound) {
|
|
30870
|
+
try {
|
|
30871
|
+
await gitExec(["-C", target.mainPath, "branch", "-D", branch], { verbose: globalOpts.verbose, dryRun: globalOpts.dryRun });
|
|
30872
|
+
} catch (err) {
|
|
30873
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
30874
|
+
if (!message.includes("branch not found") && !message.includes("not found")) {
|
|
30875
|
+
stepWarning("Branch deletion failed", message.split(`
|
|
30876
|
+
`)[0] ?? message);
|
|
30877
|
+
}
|
|
30878
|
+
}
|
|
30879
|
+
}
|
|
30880
|
+
stepSuccess("Existing branch/worktree removed", branch);
|
|
30762
30881
|
}
|
|
30763
30882
|
const mainBranch = await resolveMainBranch(target, config2);
|
|
30764
30883
|
const baseRemote = await resolveBaseRemote(target.mainPath, mainBranch);
|
|
@@ -30792,7 +30911,8 @@ function registerPullCommand(program2) {
|
|
|
30792
30911
|
fs22.mkdirSync(path28.dirname(wtPath), { recursive: true });
|
|
30793
30912
|
}
|
|
30794
30913
|
try {
|
|
30795
|
-
|
|
30914
|
+
const createBranchFlag = options.force ? "-B" : "-b";
|
|
30915
|
+
await gitExec(["-C", target.mainPath, "worktree", "add", createBranchFlag, branch, wtPath, "FETCH_HEAD"], { verbose: globalOpts.verbose, dryRun: globalOpts.dryRun });
|
|
30796
30916
|
} catch (err) {
|
|
30797
30917
|
const msg = err.message || "";
|
|
30798
30918
|
if (msg.includes("already exists") || msg.includes("not a working tree")) {
|
|
@@ -30924,6 +31044,7 @@ init_log();
|
|
|
30924
31044
|
init_git();
|
|
30925
31045
|
init_resolver();
|
|
30926
31046
|
init_path_safety();
|
|
31047
|
+
import fs23 from "fs";
|
|
30927
31048
|
import path29 from "path";
|
|
30928
31049
|
|
|
30929
31050
|
// src/lib/prompts.ts
|
|
@@ -30968,6 +31089,69 @@ async function confirm(message, io) {
|
|
|
30968
31089
|
|
|
30969
31090
|
// src/commands/remove.ts
|
|
30970
31091
|
init_stack();
|
|
31092
|
+
function errorMessage(err) {
|
|
31093
|
+
return err instanceof Error ? err.message : String(err);
|
|
31094
|
+
}
|
|
31095
|
+
function isMissingWorktreePathError(message) {
|
|
31096
|
+
return message.includes("ENOENT") || message.includes("No such file") || message.includes("does not exist") || message.includes("not a git repository") || message.includes("cannot change to");
|
|
31097
|
+
}
|
|
31098
|
+
function isRecoverableWorktreeRemoveError(message) {
|
|
31099
|
+
return message.includes("not a working tree") || message.includes("already removed") || isMissingWorktreePathError(message);
|
|
31100
|
+
}
|
|
31101
|
+
function removeExistingPath(wtPath, opts) {
|
|
31102
|
+
if (!opts.dryRun && fs23.existsSync(wtPath)) {
|
|
31103
|
+
fs23.rmSync(wtPath, { recursive: true, force: true });
|
|
31104
|
+
}
|
|
31105
|
+
}
|
|
31106
|
+
async function removeLocalBranchIfExists(repoPath, branch, opts) {
|
|
31107
|
+
if (await localBranchExists(repoPath, branch, opts)) {
|
|
31108
|
+
await gitExec(["-C", repoPath, "branch", "-D", branch], opts);
|
|
31109
|
+
}
|
|
31110
|
+
}
|
|
31111
|
+
async function removeLocalBranchBestEffort(repoPath, branch, opts) {
|
|
31112
|
+
try {
|
|
31113
|
+
await removeLocalBranchIfExists(repoPath, branch, opts);
|
|
31114
|
+
} catch (err) {
|
|
31115
|
+
stepWarning("Local branch not removed", errorMessage(err));
|
|
31116
|
+
}
|
|
31117
|
+
}
|
|
31118
|
+
async function finishCleanup(repo, wtPath, branch, opts) {
|
|
31119
|
+
if (!opts.dryRun) {
|
|
31120
|
+
const removedDirs = cleanupEmptyParents(repo.wtRoot, repo.mainPath, wtPath);
|
|
31121
|
+
for (const dir of removedDirs) {
|
|
31122
|
+
stepSuccess("Cleaned up empty directory", path29.relative(repo.wtRoot, dir) + "/");
|
|
31123
|
+
}
|
|
31124
|
+
}
|
|
31125
|
+
try {
|
|
31126
|
+
await removeStackEntry(repo.mainPath, branch, opts);
|
|
31127
|
+
} catch (err) {
|
|
31128
|
+
stepWarning("Stack metadata not removed", errorMessage(err));
|
|
31129
|
+
}
|
|
31130
|
+
}
|
|
31131
|
+
async function confirmDeletionIfNeeded(repo, wtPath, options, globalOpts) {
|
|
31132
|
+
if (globalOpts.dryRun)
|
|
31133
|
+
return true;
|
|
31134
|
+
const interactive = isInteractive();
|
|
31135
|
+
const yesFlag = !!options.yes;
|
|
31136
|
+
const envYes = process.env.WTX_YES === "1";
|
|
31137
|
+
if (!canProceedDeletion({ interactive, yesFlag, envYes })) {
|
|
31138
|
+
stepError("Non-interactive terminal requires --yes flag or WTX_YES=1 for scripts");
|
|
31139
|
+
process.exit(1);
|
|
31140
|
+
}
|
|
31141
|
+
if (interactive && !yesFlag && !envYes) {
|
|
31142
|
+
const toClean = planEmptyParentRemoval(repo.wtRoot, repo.mainPath, wtPath);
|
|
31143
|
+
indented(`Will remove worktree: ${wtPath}`);
|
|
31144
|
+
for (const dir of toClean) {
|
|
31145
|
+
indented(`Will clean up empty dir: ${path29.relative(repo.wtRoot, dir)}/`);
|
|
31146
|
+
}
|
|
31147
|
+
const proceed = await confirm("Are you sure you want to delete these?");
|
|
31148
|
+
if (!proceed) {
|
|
31149
|
+
stepWarning("Skipped by user", wtPath);
|
|
31150
|
+
return false;
|
|
31151
|
+
}
|
|
31152
|
+
}
|
|
31153
|
+
return true;
|
|
31154
|
+
}
|
|
30971
31155
|
function registerRemoveCommand(program2) {
|
|
30972
31156
|
program2.command("remove <branch>").description("Remove worktree(s)").option("-r, --repo <repos...>", "Target specific repo(s)").option("-f, --force", "Force removal even if there are uncommitted changes").option("-y, --yes", "Skip confirmation prompt").action(async (branch, options) => {
|
|
30973
31157
|
const globalOpts = program2.opts();
|
|
@@ -30987,6 +31171,18 @@ function registerRemoveCommand(program2) {
|
|
|
30987
31171
|
const worktrees = await getWorktreeList(repo.mainPath);
|
|
30988
31172
|
const target = findWorktreeForBranch(worktrees, branch, repo.mainPath, candidatePath);
|
|
30989
31173
|
if (!target) {
|
|
31174
|
+
if (options.force && (fs23.existsSync(candidatePath) || await localBranchExists(repo.mainPath, branch, globalOpts))) {
|
|
31175
|
+
if (!await confirmDeletionIfNeeded(repo, candidatePath, options, globalOpts)) {
|
|
31176
|
+
skipCount++;
|
|
31177
|
+
continue;
|
|
31178
|
+
}
|
|
31179
|
+
removeExistingPath(candidatePath, globalOpts);
|
|
31180
|
+
await removeLocalBranchBestEffort(repo.mainPath, branch, globalOpts);
|
|
31181
|
+
await finishCleanup(repo, candidatePath, branch, globalOpts);
|
|
31182
|
+
stepSuccess("Worktree removed", candidatePath);
|
|
31183
|
+
successCount++;
|
|
31184
|
+
continue;
|
|
31185
|
+
}
|
|
30990
31186
|
stepWarning("No worktree found", `${branch} (skipped)`);
|
|
30991
31187
|
skipCount++;
|
|
30992
31188
|
continue;
|
|
@@ -31015,32 +31211,38 @@ function registerRemoveCommand(program2) {
|
|
|
31015
31211
|
continue;
|
|
31016
31212
|
}
|
|
31017
31213
|
} catch (err) {
|
|
31018
|
-
|
|
31214
|
+
const msg = errorMessage(err);
|
|
31215
|
+
if (isMissingWorktreePathError(msg) || !fs23.existsSync(wtPath)) {
|
|
31216
|
+
stepWarning("Worktree path missing", "continuing with removal cleanup");
|
|
31217
|
+
} else {
|
|
31218
|
+
stepError("Failed to check for uncommitted changes", msg);
|
|
31219
|
+
skipCount++;
|
|
31220
|
+
continue;
|
|
31221
|
+
}
|
|
31222
|
+
}
|
|
31223
|
+
}
|
|
31224
|
+
if (options.force && !globalOpts.dryRun && !fs23.existsSync(wtPath)) {
|
|
31225
|
+
if (!await confirmDeletionIfNeeded(repo, wtPath, options, globalOpts)) {
|
|
31019
31226
|
skipCount++;
|
|
31020
31227
|
continue;
|
|
31021
31228
|
}
|
|
31022
|
-
|
|
31023
|
-
|
|
31024
|
-
|
|
31025
|
-
|
|
31026
|
-
const envYes = process.env.WTX_YES === "1";
|
|
31027
|
-
if (!canProceedDeletion({ interactive, yesFlag, envYes })) {
|
|
31028
|
-
stepError("Non-interactive terminal requires --yes flag or WTX_YES=1 for scripts");
|
|
31029
|
-
process.exit(1);
|
|
31229
|
+
try {
|
|
31230
|
+
await gitExec(["-C", repo.mainPath, "worktree", "prune"], globalOpts);
|
|
31231
|
+
} catch (err) {
|
|
31232
|
+
stepWarning("Worktree prune failed", errorMessage(err));
|
|
31030
31233
|
}
|
|
31031
|
-
|
|
31032
|
-
|
|
31033
|
-
|
|
31034
|
-
|
|
31035
|
-
|
|
31036
|
-
}
|
|
31037
|
-
const proceed = await confirm("Are you sure you want to delete these?");
|
|
31038
|
-
if (!proceed) {
|
|
31039
|
-
stepWarning("Skipped by user", wtPath);
|
|
31040
|
-
skipCount++;
|
|
31041
|
-
continue;
|
|
31042
|
-
}
|
|
31234
|
+
await removeLocalBranchBestEffort(repo.mainPath, branch, globalOpts);
|
|
31235
|
+
await finishCleanup(repo, wtPath, branch, globalOpts);
|
|
31236
|
+
stepSuccess("Worktree removed", wtPath);
|
|
31237
|
+
if (children.length > 0) {
|
|
31238
|
+
stepWarning("Dependent base metadata retained", children.join(", "));
|
|
31043
31239
|
}
|
|
31240
|
+
successCount++;
|
|
31241
|
+
continue;
|
|
31242
|
+
}
|
|
31243
|
+
if (!await confirmDeletionIfNeeded(repo, wtPath, options, globalOpts)) {
|
|
31244
|
+
skipCount++;
|
|
31245
|
+
continue;
|
|
31044
31246
|
}
|
|
31045
31247
|
const args = ["-C", repo.mainPath, "worktree", "remove", wtPath];
|
|
31046
31248
|
if (options.force) {
|
|
@@ -31050,33 +31252,28 @@ function registerRemoveCommand(program2) {
|
|
|
31050
31252
|
await gitExec(args, globalOpts);
|
|
31051
31253
|
stepSuccess("Worktree removed", wtPath);
|
|
31052
31254
|
} catch (err) {
|
|
31053
|
-
const msg = err
|
|
31054
|
-
if (
|
|
31255
|
+
const msg = errorMessage(err);
|
|
31256
|
+
if (isRecoverableWorktreeRemoveError(msg)) {
|
|
31055
31257
|
stepWarning("Worktree already removed or invalid", msg);
|
|
31056
|
-
|
|
31057
|
-
|
|
31258
|
+
try {
|
|
31259
|
+
await gitExec(["-C", repo.mainPath, "worktree", "prune"], globalOpts);
|
|
31260
|
+
} catch (pruneErr) {
|
|
31261
|
+
stepWarning("Worktree prune failed", errorMessage(pruneErr));
|
|
31262
|
+
}
|
|
31263
|
+
removeExistingPath(wtPath, globalOpts);
|
|
31264
|
+
await removeLocalBranchBestEffort(repo.mainPath, branch, globalOpts);
|
|
31265
|
+
stepSuccess("Worktree removed", wtPath);
|
|
31058
31266
|
} else {
|
|
31059
31267
|
throw err;
|
|
31060
31268
|
}
|
|
31061
31269
|
}
|
|
31062
|
-
|
|
31063
|
-
const removedDirs = cleanupEmptyParents(repo.wtRoot, repo.mainPath, wtPath);
|
|
31064
|
-
for (const dir of removedDirs) {
|
|
31065
|
-
stepSuccess("Cleaned up empty directory", path29.relative(repo.wtRoot, dir) + "/");
|
|
31066
|
-
}
|
|
31067
|
-
}
|
|
31068
|
-
try {
|
|
31069
|
-
await removeStackEntry(repo.mainPath, branch, globalOpts);
|
|
31070
|
-
} catch (err) {
|
|
31071
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
31072
|
-
stepWarning("Stack metadata not removed", message);
|
|
31073
|
-
}
|
|
31270
|
+
await finishCleanup(repo, wtPath, branch, globalOpts);
|
|
31074
31271
|
if (children.length > 0) {
|
|
31075
31272
|
stepWarning("Dependent base metadata retained", children.join(", "));
|
|
31076
31273
|
}
|
|
31077
31274
|
successCount++;
|
|
31078
31275
|
} catch (err) {
|
|
31079
|
-
stepError("Failed to remove worktree", err
|
|
31276
|
+
stepError("Failed to remove worktree", errorMessage(err));
|
|
31080
31277
|
skipCount++;
|
|
31081
31278
|
}
|
|
31082
31279
|
}
|
|
@@ -31331,7 +31528,7 @@ init_source();
|
|
|
31331
31528
|
init_stack();
|
|
31332
31529
|
init_stack();
|
|
31333
31530
|
import path31 from "path";
|
|
31334
|
-
import
|
|
31531
|
+
import fs24 from "fs";
|
|
31335
31532
|
function buildLsJson(reposData) {
|
|
31336
31533
|
const result = [];
|
|
31337
31534
|
for (const repo of reposData) {
|
|
@@ -31441,7 +31638,7 @@ function registerLsCommand(program2) {
|
|
|
31441
31638
|
statusStr = source_default.blue("[main checkout]");
|
|
31442
31639
|
} else if (wt.isLocked) {
|
|
31443
31640
|
statusStr = source_default.red("locked \uD83D\uDD12");
|
|
31444
|
-
} else if (
|
|
31641
|
+
} else if (fs24.existsSync(wt.path)) {
|
|
31445
31642
|
try {
|
|
31446
31643
|
dirtyFiles = await getDirtyFiles(wt.path);
|
|
31447
31644
|
if (dirtyFiles.length > 0) {
|
|
@@ -31567,7 +31764,7 @@ end
|
|
|
31567
31764
|
init_config();
|
|
31568
31765
|
init_log();
|
|
31569
31766
|
init_git();
|
|
31570
|
-
import
|
|
31767
|
+
import fs25 from "fs";
|
|
31571
31768
|
init_resolver();
|
|
31572
31769
|
init_stack();
|
|
31573
31770
|
function registerRebaseCommand(program2) {
|
|
@@ -31582,7 +31779,7 @@ function registerRebaseCommand(program2) {
|
|
|
31582
31779
|
repoHeader(repo.name);
|
|
31583
31780
|
const mainBranch = await resolveMainBranch(repo, config2);
|
|
31584
31781
|
const wtPath = getWorktreePath(repo, branch);
|
|
31585
|
-
if (!
|
|
31782
|
+
if (!fs25.existsSync(wtPath)) {
|
|
31586
31783
|
stepError("No worktree found", `${wtPath} (skipped)`);
|
|
31587
31784
|
failCount++;
|
|
31588
31785
|
continue;
|
|
@@ -31708,7 +31905,7 @@ init_execa();
|
|
|
31708
31905
|
init_config();
|
|
31709
31906
|
init_log();
|
|
31710
31907
|
init_resolver();
|
|
31711
|
-
import
|
|
31908
|
+
import fs26 from "fs";
|
|
31712
31909
|
import path32 from "path";
|
|
31713
31910
|
function registerSyncCommand(program2) {
|
|
31714
31911
|
program2.command("sync <branch>").description("re-copy sync files + run post_sync").option("--repo <repos...>", "comma-separated list of repos to target").action(async (branch, _options, cmd) => {
|
|
@@ -31720,14 +31917,14 @@ function registerSyncCommand(program2) {
|
|
|
31720
31917
|
for (const repo of repos) {
|
|
31721
31918
|
repoHeader(repo.name);
|
|
31722
31919
|
const wtPath = getWorktreePath(repo, branch);
|
|
31723
|
-
if (!
|
|
31920
|
+
if (!fs26.existsSync(wtPath)) {
|
|
31724
31921
|
stepWarning("No worktree found", `${wtPath} (skipped)`);
|
|
31725
31922
|
continue;
|
|
31726
31923
|
}
|
|
31727
31924
|
try {
|
|
31728
31925
|
if (repo.config.sync_files) {
|
|
31729
31926
|
for (const file2 of repo.config.sync_files) {
|
|
31730
|
-
if (
|
|
31927
|
+
if (fs26.existsSync(path32.join(repo.mainPath, file2))) {
|
|
31731
31928
|
if (!opts.dryRun) {
|
|
31732
31929
|
syncEntry(repo.mainPath, wtPath, file2);
|
|
31733
31930
|
}
|
|
@@ -31780,14 +31977,14 @@ function registerSyncCommand(program2) {
|
|
|
31780
31977
|
}
|
|
31781
31978
|
}
|
|
31782
31979
|
const wtNodeModules = path32.join(wtPath, "node_modules");
|
|
31783
|
-
if (
|
|
31980
|
+
if (fs26.existsSync(wtNodeModules) && fs26.lstatSync(wtNodeModules).isSymbolicLink()) {
|
|
31784
31981
|
const lockfiles = ["yarn.lock", "package-lock.json", "pnpm-lock.yaml", "bun.lockb", "bun.lock"];
|
|
31785
31982
|
for (const lock of lockfiles) {
|
|
31786
31983
|
const mainLock = path32.join(repo.mainPath, lock);
|
|
31787
31984
|
const wtLock = path32.join(wtPath, lock);
|
|
31788
|
-
if (
|
|
31789
|
-
const mainContent =
|
|
31790
|
-
const wtContent =
|
|
31985
|
+
if (fs26.existsSync(mainLock) && fs26.existsSync(wtLock)) {
|
|
31986
|
+
const mainContent = fs26.readFileSync(mainLock);
|
|
31987
|
+
const wtContent = fs26.readFileSync(wtLock);
|
|
31791
31988
|
if (!mainContent.equals(wtContent)) {
|
|
31792
31989
|
stepWarning(`${lock} differs from main — node_modules is symlinked`);
|
|
31793
31990
|
indented(`Run: wtx deps ${branch} --repo ${repo.name} --install`);
|
|
@@ -31816,7 +32013,7 @@ init_config();
|
|
|
31816
32013
|
init_log();
|
|
31817
32014
|
init_resolver();
|
|
31818
32015
|
init_deps();
|
|
31819
|
-
import
|
|
32016
|
+
import fs27 from "fs";
|
|
31820
32017
|
function registerDepsCommand(program2) {
|
|
31821
32018
|
program2.command("deps [branch]").description("Manage node_modules strategy per worktree").option("-r, --repo <repos...>", "Target specific repo(s)").option("--install", "Switch to independent node_modules (run install)").option("--symlink", "Switch to legacy symlinked node_modules").option("--json", "Output machine-readable JSON state").action(async (branch, options) => {
|
|
31822
32019
|
const globalOpts = program2.opts();
|
|
@@ -31853,7 +32050,7 @@ function registerDepsCommand(program2) {
|
|
|
31853
32050
|
continue;
|
|
31854
32051
|
}
|
|
31855
32052
|
const wtPath = getWorktreePath(repo, branch);
|
|
31856
|
-
if (!
|
|
32053
|
+
if (!fs27.existsSync(wtPath)) {
|
|
31857
32054
|
if (options.json) {
|
|
31858
32055
|
jsonResults[repo.name] = { error: "No worktree found", branch };
|
|
31859
32056
|
} else {
|
|
@@ -31911,7 +32108,7 @@ function registerDepsCommand(program2) {
|
|
|
31911
32108
|
init_config();
|
|
31912
32109
|
init_log();
|
|
31913
32110
|
init_resolver();
|
|
31914
|
-
import
|
|
32111
|
+
import fs28 from "fs";
|
|
31915
32112
|
init_git();
|
|
31916
32113
|
async function resolveMainCheckoutPath(repoCtx, branch) {
|
|
31917
32114
|
try {
|
|
@@ -31936,7 +32133,7 @@ function registerOpenCommand(program2) {
|
|
|
31936
32133
|
for (const repo of repos) {
|
|
31937
32134
|
const wtPath = getWorktreePath(repo, branch);
|
|
31938
32135
|
let targetPath = wtPath;
|
|
31939
|
-
if (!
|
|
32136
|
+
if (!fs28.existsSync(wtPath)) {
|
|
31940
32137
|
const mainCheckoutPath = await resolveMainCheckoutPath(repo, branch);
|
|
31941
32138
|
if (!mainCheckoutPath) {
|
|
31942
32139
|
continue;
|
|
@@ -31972,7 +32169,7 @@ import path34 from "path";
|
|
|
31972
32169
|
init_git();
|
|
31973
32170
|
init_resolver();
|
|
31974
32171
|
init_path_safety();
|
|
31975
|
-
import
|
|
32172
|
+
import fs29 from "fs";
|
|
31976
32173
|
import path33 from "path";
|
|
31977
32174
|
async function getUpstream(wtPath) {
|
|
31978
32175
|
try {
|
|
@@ -31982,6 +32179,27 @@ async function getUpstream(wtPath) {
|
|
|
31982
32179
|
return null;
|
|
31983
32180
|
}
|
|
31984
32181
|
}
|
|
32182
|
+
async function getDirtyEntries(wtPath) {
|
|
32183
|
+
try {
|
|
32184
|
+
const out = await gitExec(["-C", wtPath, "status", "--porcelain"], {});
|
|
32185
|
+
return out.split(`
|
|
32186
|
+
`).map((line) => line.trimEnd()).filter((line) => line.length > 0);
|
|
32187
|
+
} catch {
|
|
32188
|
+
return [];
|
|
32189
|
+
}
|
|
32190
|
+
}
|
|
32191
|
+
function touchesEntry(porcelainLine, entry) {
|
|
32192
|
+
const entryPath = porcelainLine.slice(3);
|
|
32193
|
+
return entryPath === entry || entryPath.startsWith(`${entry}/`);
|
|
32194
|
+
}
|
|
32195
|
+
async function isTrackedInWorktree(wtPath, entry) {
|
|
32196
|
+
try {
|
|
32197
|
+
await gitExec(["-C", wtPath, "ls-files", "--error-unmatch", "--", entry], {});
|
|
32198
|
+
return true;
|
|
32199
|
+
} catch {
|
|
32200
|
+
return false;
|
|
32201
|
+
}
|
|
32202
|
+
}
|
|
31985
32203
|
async function planRename(repo, oldBranch, newBranch, opts) {
|
|
31986
32204
|
const list = await getWorktreeList(repo.mainPath);
|
|
31987
32205
|
const candidatePath = getWorktreePath(repo, oldBranch);
|
|
@@ -31993,7 +32211,7 @@ async function planRename(repo, oldBranch, newBranch, opts) {
|
|
|
31993
32211
|
throw new Error(`Worktree '${oldBranch}' is locked — unlock it before renaming`);
|
|
31994
32212
|
}
|
|
31995
32213
|
const newPath = `${repo.wtRoot}/${newBranch}`;
|
|
31996
|
-
if (
|
|
32214
|
+
if (fs29.existsSync(newPath)) {
|
|
31997
32215
|
throw new Error(`Target path already exists: ${newPath}`);
|
|
31998
32216
|
}
|
|
31999
32217
|
if (!opts.dryRun && await localBranchExists(repo.mainPath, newBranch, opts)) {
|
|
@@ -32005,6 +32223,7 @@ async function renameWorktree(params) {
|
|
|
32005
32223
|
const { repo, oldBranch, newBranch, opts } = params;
|
|
32006
32224
|
const planned = await planRename(repo, oldBranch, newBranch, opts);
|
|
32007
32225
|
const cleanedDirs = [];
|
|
32226
|
+
const dirtyBefore = await getDirtyEntries(planned.worktreePath);
|
|
32008
32227
|
if (opts.dryRun) {
|
|
32009
32228
|
return {
|
|
32010
32229
|
oldBranch,
|
|
@@ -32012,12 +32231,16 @@ async function renameWorktree(params) {
|
|
|
32012
32231
|
oldPath: planned.worktreePath,
|
|
32013
32232
|
newPath: planned.newPath,
|
|
32014
32233
|
upstream: planned.upstream,
|
|
32015
|
-
cleanedDirs
|
|
32234
|
+
cleanedDirs,
|
|
32235
|
+
dirtyFiles: dirtyBefore,
|
|
32236
|
+
lostDirtyFiles: [],
|
|
32237
|
+
resyncedFiles: [],
|
|
32238
|
+
keptLocalSyncFiles: []
|
|
32016
32239
|
};
|
|
32017
32240
|
}
|
|
32018
32241
|
await gitExec(["-C", planned.worktreePath, "branch", "-m", oldBranch, newBranch], opts);
|
|
32019
32242
|
try {
|
|
32020
|
-
|
|
32243
|
+
fs29.mkdirSync(path33.dirname(planned.newPath), { recursive: true });
|
|
32021
32244
|
await gitExec(["-C", repo.mainPath, "worktree", "move", planned.worktreePath, planned.newPath], opts);
|
|
32022
32245
|
} catch (err) {
|
|
32023
32246
|
try {
|
|
@@ -32030,13 +32253,40 @@ async function renameWorktree(params) {
|
|
|
32030
32253
|
for (const dir of cleanupEmptyParents(repo.wtRoot, repo.mainPath, planned.worktreePath)) {
|
|
32031
32254
|
cleanedDirs.push(dir);
|
|
32032
32255
|
}
|
|
32256
|
+
const dirtyAfter = await getDirtyEntries(planned.newPath);
|
|
32257
|
+
const lostDirtyFiles = dirtyBefore.filter((line) => !dirtyAfter.includes(line));
|
|
32258
|
+
const resyncedFiles = [];
|
|
32259
|
+
const keptLocalSyncFiles = [];
|
|
32260
|
+
for (const entry of repo.config.sync_files ?? []) {
|
|
32261
|
+
if (!fs29.existsSync(path33.join(repo.mainPath, entry)))
|
|
32262
|
+
continue;
|
|
32263
|
+
const destPath = path33.join(planned.newPath, entry);
|
|
32264
|
+
if (!fs29.existsSync(destPath)) {
|
|
32265
|
+
if (syncEntry(repo.mainPath, planned.newPath, entry)) {
|
|
32266
|
+
resyncedFiles.push(entry);
|
|
32267
|
+
}
|
|
32268
|
+
continue;
|
|
32269
|
+
}
|
|
32270
|
+
const tracked = await isTrackedInWorktree(planned.newPath, entry);
|
|
32271
|
+
if (tracked && !dirtyAfter.some((line) => touchesEntry(line, entry))) {
|
|
32272
|
+
if (syncEntry(repo.mainPath, planned.newPath, entry)) {
|
|
32273
|
+
resyncedFiles.push(entry);
|
|
32274
|
+
}
|
|
32275
|
+
continue;
|
|
32276
|
+
}
|
|
32277
|
+
keptLocalSyncFiles.push(entry);
|
|
32278
|
+
}
|
|
32033
32279
|
return {
|
|
32034
32280
|
oldBranch,
|
|
32035
32281
|
newBranch,
|
|
32036
32282
|
oldPath: planned.worktreePath,
|
|
32037
32283
|
newPath: planned.newPath,
|
|
32038
32284
|
upstream: planned.upstream,
|
|
32039
|
-
cleanedDirs
|
|
32285
|
+
cleanedDirs,
|
|
32286
|
+
dirtyFiles: dirtyAfter,
|
|
32287
|
+
lostDirtyFiles,
|
|
32288
|
+
resyncedFiles,
|
|
32289
|
+
keptLocalSyncFiles
|
|
32040
32290
|
};
|
|
32041
32291
|
}
|
|
32042
32292
|
|
|
@@ -32082,6 +32332,18 @@ function registerRenameCommand(program2) {
|
|
|
32082
32332
|
stepSuccess("Cleaned up empty directory", path34.relative(repo.wtRoot, dir) + "/");
|
|
32083
32333
|
}
|
|
32084
32334
|
stepSuccess("Renamed", `${outcome.oldPath} → ${outcome.newPath}`);
|
|
32335
|
+
if (outcome.dirtyFiles.length > 0) {
|
|
32336
|
+
stepSuccess("Carried uncommitted files", `${outcome.dirtyFiles.length} entr${outcome.dirtyFiles.length === 1 ? "y" : "ies"} moved with the checkout`);
|
|
32337
|
+
}
|
|
32338
|
+
if (outcome.lostDirtyFiles.length > 0) {
|
|
32339
|
+
stepError("Uncommitted changes missing after rename", outcome.lostDirtyFiles.join(", "));
|
|
32340
|
+
}
|
|
32341
|
+
if (outcome.resyncedFiles.length > 0) {
|
|
32342
|
+
stepSuccess("Synced files", outcome.resyncedFiles.join(", "));
|
|
32343
|
+
}
|
|
32344
|
+
for (const entry of outcome.keptLocalSyncFiles) {
|
|
32345
|
+
indented(`Kept local version of ${entry} — it has uncommitted edits`);
|
|
32346
|
+
}
|
|
32085
32347
|
if (outcome.upstream) {
|
|
32086
32348
|
indented(`Upstream still tracks '${outcome.upstream}' — after pushing run: git push -u origin ${newBranch}`);
|
|
32087
32349
|
}
|
|
@@ -32108,7 +32370,7 @@ init_resolver();
|
|
|
32108
32370
|
init_deps();
|
|
32109
32371
|
init_forge();
|
|
32110
32372
|
init_types2();
|
|
32111
|
-
import
|
|
32373
|
+
import fs30 from "fs";
|
|
32112
32374
|
init_owner();
|
|
32113
32375
|
init_stack();
|
|
32114
32376
|
init_source();
|
|
@@ -32158,7 +32420,7 @@ function registerStatusCommand(program2) {
|
|
|
32158
32420
|
const jsonOutputs = [];
|
|
32159
32421
|
for (const repo of repos) {
|
|
32160
32422
|
const wtPath = getWorktreePath(repo, branch);
|
|
32161
|
-
if (!
|
|
32423
|
+
if (!fs30.existsSync(wtPath)) {
|
|
32162
32424
|
continue;
|
|
32163
32425
|
}
|
|
32164
32426
|
found++;
|
|
@@ -32458,7 +32720,7 @@ function registerPrsCommand(program2) {
|
|
|
32458
32720
|
|
|
32459
32721
|
// src/commands/skill.ts
|
|
32460
32722
|
init_log();
|
|
32461
|
-
import
|
|
32723
|
+
import fs31 from "fs";
|
|
32462
32724
|
import path35 from "path";
|
|
32463
32725
|
import { URL as URL2 } from "url";
|
|
32464
32726
|
var __dirname2 = new URL2(".", import.meta.url).pathname;
|
|
@@ -32615,7 +32877,7 @@ function registerSkillCommand(program2) {
|
|
|
32615
32877
|
}
|
|
32616
32878
|
let projectRoot = path35.resolve(__dirname2, "..", "..");
|
|
32617
32879
|
let skillPath = path35.join(projectRoot, "skills", `${platform2}.md`);
|
|
32618
|
-
if (
|
|
32880
|
+
if (fs31.existsSync(skillPath)) {
|
|
32619
32881
|
process.stdout.write(skillPath + `
|
|
32620
32882
|
`);
|
|
32621
32883
|
} else {
|
|
@@ -32662,7 +32924,7 @@ init_deps();
|
|
|
32662
32924
|
import readline3 from "readline";
|
|
32663
32925
|
init_path_safety();
|
|
32664
32926
|
init_stack();
|
|
32665
|
-
import
|
|
32927
|
+
import fs33 from "fs";
|
|
32666
32928
|
async function runMcpServer(opts = {}) {
|
|
32667
32929
|
const input = opts.input ?? process.stdin;
|
|
32668
32930
|
const output = opts.output ?? process.stdout;
|
|
@@ -32845,7 +33107,7 @@ async function handleToolCall(name, args, config2, _opts) {
|
|
|
32845
33107
|
for (const wt of wts) {
|
|
32846
33108
|
if (!wt.path.startsWith(repo.wtRoot))
|
|
32847
33109
|
continue;
|
|
32848
|
-
const dirtyCount =
|
|
33110
|
+
const dirtyCount = fs33.existsSync(wt.path) ? (await getDirtyFiles(wt.path)).length : 0;
|
|
32849
33111
|
items.push({
|
|
32850
33112
|
repo: repo.name,
|
|
32851
33113
|
branch: wt.branch || null,
|
|
@@ -32873,7 +33135,7 @@ async function handleToolCall(name, args, config2, _opts) {
|
|
|
32873
33135
|
}
|
|
32874
33136
|
const repo = resolveRepos(config2, [args.repo])[0];
|
|
32875
33137
|
const wtPath = getWorktreePath(repo, args.branch);
|
|
32876
|
-
if (!
|
|
33138
|
+
if (!fs33.existsSync(wtPath)) {
|
|
32877
33139
|
throw { isToolError: true, message: `Worktree not found for branch ${args.branch}` };
|
|
32878
33140
|
}
|
|
32879
33141
|
const mainBranch = await resolveMainBranch(repo, config2);
|
|
@@ -32992,7 +33254,7 @@ async function handleToolCall(name, args, config2, _opts) {
|
|
|
32992
33254
|
if (children.length > 0 && args.force !== true) {
|
|
32993
33255
|
throw { isToolError: true, message: `Branch has dependent worktrees: ${children.join(", ")}. Use force:true to override.` };
|
|
32994
33256
|
}
|
|
32995
|
-
if (!args.force &&
|
|
33257
|
+
if (!args.force && fs33.existsSync(wtPath)) {
|
|
32996
33258
|
const dirty = await getDirtyFiles(wtPath);
|
|
32997
33259
|
if (dirty.length > 0) {
|
|
32998
33260
|
throw { isToolError: true, message: `Worktree is dirty. Use force:true to remove it.` };
|
|
@@ -33011,7 +33273,7 @@ async function handleToolCall(name, args, config2, _opts) {
|
|
|
33011
33273
|
}
|
|
33012
33274
|
const repo = resolveRepos(config2, [args.repo])[0];
|
|
33013
33275
|
const wtPath = getWorktreePath(repo, args.branch);
|
|
33014
|
-
if (!
|
|
33276
|
+
if (!fs33.existsSync(wtPath)) {
|
|
33015
33277
|
throw { isToolError: true, message: `Worktree not found for branch ${args.branch}` };
|
|
33016
33278
|
}
|
|
33017
33279
|
const mainBranch = await resolveMainBranch(repo, config2);
|
|
@@ -33233,7 +33495,7 @@ init_config();
|
|
|
33233
33495
|
init_resolver();
|
|
33234
33496
|
init_log();
|
|
33235
33497
|
init_history();
|
|
33236
|
-
import
|
|
33498
|
+
import fs34 from "fs";
|
|
33237
33499
|
var program2 = new Command;
|
|
33238
33500
|
program2.name("wtx").description("Multi-repo git worktree manager").version(VERSION).option("-q, --quiet", "Suppress progress indicators", false).option("--verbose", "Show git commands as they run", false).option("--dry-run", "Show what would happen", false);
|
|
33239
33501
|
var MUTATING_COMMANDS = new Set([
|
|
@@ -33337,7 +33599,7 @@ program2.command("_resolve-path <repo> <branch>", { hidden: true }).description(
|
|
|
33337
33599
|
}
|
|
33338
33600
|
const repo = repos[0];
|
|
33339
33601
|
const wtPath = getWorktreePath(repo, branch);
|
|
33340
|
-
if (!
|
|
33602
|
+
if (!fs34.existsSync(wtPath)) {
|
|
33341
33603
|
process.stderr.write(`✗ No worktree at ${wtPath}
|
|
33342
33604
|
`);
|
|
33343
33605
|
process.exit(1);
|