clawt 3.1.3 → 3.2.1
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/dist/index.js +191 -130
- package/dist/postinstall.js +7 -1
- package/docs/spec.md +1 -0
- package/docs/validate.md +51 -10
- package/package.json +1 -1
- package/src/commands/remove.ts +13 -0
- package/src/commands/validate.ts +38 -230
- package/src/constants/messages/remove.ts +6 -0
- package/src/constants/messages/validate.ts +3 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/validate-core.ts +174 -0
- package/src/utils/validate-runner.ts +105 -0
- package/src/utils/validate-snapshot.ts +29 -9
- package/tests/unit/commands/remove.test.ts +9 -0
- package/tests/unit/commands/validate.test.ts +79 -289
- package/tests/unit/utils/validate-snapshot.test.ts +8 -3
package/dist/index.js
CHANGED
|
@@ -180,6 +180,8 @@ var VALIDATE_MESSAGES = {
|
|
|
180
180
|
\u6682\u5B58\u533A = \u4E0A\u6B21\u5FEB\u7167\uFF0C\u5DE5\u4F5C\u76EE\u5F55 = \u6700\u65B0\u53D8\u66F4`,
|
|
181
181
|
/** 增量 validate 降级为全量模式提示 */
|
|
182
182
|
INCREMENTAL_VALIDATE_FALLBACK: "\u589E\u91CF\u5BF9\u6BD4\u5931\u8D25\uFF0C\u5DF2\u964D\u7EA7\u4E3A\u5168\u91CF\u6A21\u5F0F",
|
|
183
|
+
/** 增量 validate 检测到目标 worktree 无新变更 */
|
|
184
|
+
INCREMENTAL_VALIDATE_NO_CHANGES: (branch) => `\u5206\u652F ${branch} \u81EA\u4E0A\u6B21 validate \u4EE5\u6765\u6CA1\u6709\u65B0\u7684\u53D8\u66F4\uFF0C\u5DF2\u6062\u590D\u5230\u4E0A\u6B21\u9A8C\u8BC1\u72B6\u6001`,
|
|
183
185
|
/** validate 状态已清理 */
|
|
184
186
|
VALIDATE_CLEANED: (branch) => `\u2713 \u5206\u652F ${branch} \u7684 validate \u72B6\u6001\u5DF2\u6E05\u7406`,
|
|
185
187
|
/** validate patch apply 失败,提示用户同步主分支 */
|
|
@@ -297,7 +299,11 @@ ${failures.map((f) => ` \u2717 ${f.path}: ${f.error}`).join("\n")}`,
|
|
|
297
299
|
/** 用户选择保留本地分支 */
|
|
298
300
|
REMOVE_BRANCHES_KEPT: "\u5DF2\u4FDD\u7559\u672C\u5730\u5206\u652F\uFF0C\u53EF\u7A0D\u540E\u4F7F\u7528 git branch -D <\u5206\u652F\u540D> \u624B\u52A8\u5220\u9664",
|
|
299
301
|
/** 确认删除本地分支和验证分支 */
|
|
300
|
-
REMOVE_CONFIRM_DELETE_BRANCHES: "\u662F\u5426\u540C\u65F6\u5220\u9664\u5BF9\u5E94\u7684\u672C\u5730\u5206\u652F\u548C\u9A8C\u8BC1\u5206\u652F\uFF1F"
|
|
302
|
+
REMOVE_CONFIRM_DELETE_BRANCHES: "\u662F\u5426\u540C\u65F6\u5220\u9664\u5BF9\u5E94\u7684\u672C\u5730\u5206\u652F\u548C\u9A8C\u8BC1\u5206\u652F\uFF1F",
|
|
303
|
+
/** 待移除的 worktree 的分支是主 worktree 当前所在分支 */
|
|
304
|
+
REMOVE_BRANCH_IS_CURRENT: (branch) => `\u65E0\u6CD5\u79FB\u9664\uFF1A\u5206\u652F ${branch} \u662F\u4E3B worktree \u5F53\u524D\u6240\u5728\u5206\u652F\uFF0C\u8BF7\u5148\u5207\u6362\u5230\u5176\u4ED6\u5206\u652F\u540E\u518D\u79FB\u9664`,
|
|
305
|
+
/** 待移除的 worktree 对应的验证分支是主 worktree 当前所在分支 */
|
|
306
|
+
REMOVE_VALIDATE_BRANCH_IS_CURRENT: (branch, validateBranch) => `\u65E0\u6CD5\u79FB\u9664\uFF1A\u5206\u652F ${branch} \u7684\u9A8C\u8BC1\u5206\u652F ${validateBranch} \u662F\u4E3B worktree \u5F53\u524D\u6240\u5728\u5206\u652F\uFF0C\u8BF7\u5148\u5207\u6362\u5230\u5176\u4ED6\u5206\u652F\u540E\u518D\u79FB\u9664`
|
|
301
307
|
};
|
|
302
308
|
|
|
303
309
|
// src/constants/messages/reset.ts
|
|
@@ -1672,6 +1678,9 @@ function getSnapshotPath(projectName, branchName) {
|
|
|
1672
1678
|
function getSnapshotHeadPath(projectName, branchName) {
|
|
1673
1679
|
return join6(VALIDATE_SNAPSHOTS_DIR, projectName, `${branchName}.head`);
|
|
1674
1680
|
}
|
|
1681
|
+
function getSnapshotStagedPath(projectName, branchName) {
|
|
1682
|
+
return join6(VALIDATE_SNAPSHOTS_DIR, projectName, `${branchName}.staged`);
|
|
1683
|
+
}
|
|
1675
1684
|
function hasSnapshot(projectName, branchName) {
|
|
1676
1685
|
return existsSync8(getSnapshotPath(projectName, branchName));
|
|
1677
1686
|
}
|
|
@@ -1684,23 +1693,28 @@ function getSnapshotModifiedTime(projectName, branchName) {
|
|
|
1684
1693
|
function readSnapshot(projectName, branchName) {
|
|
1685
1694
|
const snapshotPath = getSnapshotPath(projectName, branchName);
|
|
1686
1695
|
const headPath = getSnapshotHeadPath(projectName, branchName);
|
|
1696
|
+
const stagedPath = getSnapshotStagedPath(projectName, branchName);
|
|
1687
1697
|
logger.debug(`\u8BFB\u53D6 validate \u5FEB\u7167: ${snapshotPath}`);
|
|
1688
1698
|
const treeHash = existsSync8(snapshotPath) ? readFileSync3(snapshotPath, "utf-8").trim() : "";
|
|
1689
1699
|
const headCommitHash = existsSync8(headPath) ? readFileSync3(headPath, "utf-8").trim() : "";
|
|
1690
|
-
|
|
1700
|
+
const stagedTreeHash = existsSync8(stagedPath) ? readFileSync3(stagedPath, "utf-8").trim() : "";
|
|
1701
|
+
return { treeHash, headCommitHash, stagedTreeHash };
|
|
1691
1702
|
}
|
|
1692
|
-
function writeSnapshot(projectName, branchName, treeHash, headCommitHash) {
|
|
1703
|
+
function writeSnapshot(projectName, branchName, treeHash, headCommitHash, stagedTreeHash = "") {
|
|
1693
1704
|
const snapshotPath = getSnapshotPath(projectName, branchName);
|
|
1694
1705
|
const headPath = getSnapshotHeadPath(projectName, branchName);
|
|
1706
|
+
const stagedPath = getSnapshotStagedPath(projectName, branchName);
|
|
1695
1707
|
const snapshotDir = join6(VALIDATE_SNAPSHOTS_DIR, projectName);
|
|
1696
1708
|
ensureDir(snapshotDir);
|
|
1697
1709
|
writeFileSync3(snapshotPath, treeHash, "utf-8");
|
|
1698
1710
|
writeFileSync3(headPath, headCommitHash, "utf-8");
|
|
1699
|
-
|
|
1711
|
+
writeFileSync3(stagedPath, stagedTreeHash, "utf-8");
|
|
1712
|
+
logger.info(`\u5DF2\u4FDD\u5B58 validate \u5FEB\u7167: ${snapshotPath}, ${headPath}, ${stagedPath}`);
|
|
1700
1713
|
}
|
|
1701
1714
|
function removeSnapshot(projectName, branchName) {
|
|
1702
1715
|
const snapshotPath = getSnapshotPath(projectName, branchName);
|
|
1703
1716
|
const headPath = getSnapshotHeadPath(projectName, branchName);
|
|
1717
|
+
const stagedPath = getSnapshotStagedPath(projectName, branchName);
|
|
1704
1718
|
if (existsSync8(snapshotPath)) {
|
|
1705
1719
|
unlinkSync(snapshotPath);
|
|
1706
1720
|
logger.info(`\u5DF2\u5220\u9664 validate \u5FEB\u7167: ${snapshotPath}`);
|
|
@@ -1709,6 +1723,10 @@ function removeSnapshot(projectName, branchName) {
|
|
|
1709
1723
|
unlinkSync(headPath);
|
|
1710
1724
|
logger.info(`\u5DF2\u5220\u9664 validate \u5FEB\u7167: ${headPath}`);
|
|
1711
1725
|
}
|
|
1726
|
+
if (existsSync8(stagedPath)) {
|
|
1727
|
+
unlinkSync(stagedPath);
|
|
1728
|
+
logger.info(`\u5DF2\u5220\u9664 validate \u5FEB\u7167: ${stagedPath}`);
|
|
1729
|
+
}
|
|
1712
1730
|
}
|
|
1713
1731
|
function getProjectSnapshotBranches(projectName) {
|
|
1714
1732
|
const projectDir = join6(VALIDATE_SNAPSHOTS_DIR, projectName);
|
|
@@ -2968,6 +2986,145 @@ async function checkForUpdates(currentVersion) {
|
|
|
2968
2986
|
}
|
|
2969
2987
|
}
|
|
2970
2988
|
|
|
2989
|
+
// src/utils/validate-runner.ts
|
|
2990
|
+
function executeSingleCommand(command, mainWorktreePath) {
|
|
2991
|
+
printInfo(MESSAGES.VALIDATE_RUN_START(command));
|
|
2992
|
+
printSeparator();
|
|
2993
|
+
const result = runCommandInherited(command, { cwd: mainWorktreePath });
|
|
2994
|
+
printSeparator();
|
|
2995
|
+
if (result.error) {
|
|
2996
|
+
printError(MESSAGES.VALIDATE_RUN_ERROR(command, result.error.message));
|
|
2997
|
+
return;
|
|
2998
|
+
}
|
|
2999
|
+
const exitCode = result.status ?? 1;
|
|
3000
|
+
if (exitCode === 0) {
|
|
3001
|
+
printSuccess(MESSAGES.VALIDATE_RUN_SUCCESS(command));
|
|
3002
|
+
} else {
|
|
3003
|
+
printError(MESSAGES.VALIDATE_RUN_FAILED(command, exitCode));
|
|
3004
|
+
}
|
|
3005
|
+
}
|
|
3006
|
+
function reportParallelResults(results) {
|
|
3007
|
+
printSeparator();
|
|
3008
|
+
const successCount = results.filter((r) => r.exitCode === 0 && !r.error).length;
|
|
3009
|
+
const failedCount = results.length - successCount;
|
|
3010
|
+
for (const result of results) {
|
|
3011
|
+
if (result.error) {
|
|
3012
|
+
printError(MESSAGES.VALIDATE_PARALLEL_CMD_ERROR(result.command, result.error));
|
|
3013
|
+
} else if (result.exitCode === 0) {
|
|
3014
|
+
printSuccess(MESSAGES.VALIDATE_PARALLEL_CMD_SUCCESS(result.command));
|
|
3015
|
+
} else {
|
|
3016
|
+
printError(MESSAGES.VALIDATE_PARALLEL_CMD_FAILED(result.command, result.exitCode));
|
|
3017
|
+
}
|
|
3018
|
+
}
|
|
3019
|
+
if (failedCount === 0) {
|
|
3020
|
+
printSuccess(MESSAGES.VALIDATE_PARALLEL_RUN_ALL_SUCCESS(results.length));
|
|
3021
|
+
} else {
|
|
3022
|
+
printError(MESSAGES.VALIDATE_PARALLEL_RUN_SUMMARY(successCount, failedCount));
|
|
3023
|
+
}
|
|
3024
|
+
}
|
|
3025
|
+
async function executeParallelCommands(commands, mainWorktreePath) {
|
|
3026
|
+
printInfo(MESSAGES.VALIDATE_PARALLEL_RUN_START(commands.length));
|
|
3027
|
+
for (let i = 0; i < commands.length; i++) {
|
|
3028
|
+
printInfo(MESSAGES.VALIDATE_PARALLEL_CMD_START(i + 1, commands.length, commands[i]));
|
|
3029
|
+
}
|
|
3030
|
+
printSeparator();
|
|
3031
|
+
const results = await runParallelCommands(commands, { cwd: mainWorktreePath });
|
|
3032
|
+
reportParallelResults(results);
|
|
3033
|
+
}
|
|
3034
|
+
async function executeRunCommand(command, mainWorktreePath) {
|
|
3035
|
+
printInfo("");
|
|
3036
|
+
const commands = parseParallelCommands(command);
|
|
3037
|
+
if (commands.length <= 1) {
|
|
3038
|
+
executeSingleCommand(commands[0] || command, mainWorktreePath);
|
|
3039
|
+
} else {
|
|
3040
|
+
await executeParallelCommands(commands, mainWorktreePath);
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
3043
|
+
|
|
3044
|
+
// src/utils/validate-core.ts
|
|
3045
|
+
function migrateChangesViaPatch(targetWorktreePath, mainWorktreePath, branchName, hasUncommitted) {
|
|
3046
|
+
let didTempCommit = false;
|
|
3047
|
+
try {
|
|
3048
|
+
if (hasUncommitted) {
|
|
3049
|
+
gitAddAll(targetWorktreePath);
|
|
3050
|
+
gitCommit("clawt:temp-commit-for-validate", targetWorktreePath);
|
|
3051
|
+
didTempCommit = true;
|
|
3052
|
+
}
|
|
3053
|
+
const patch = gitDiffBinaryAgainstBranch(branchName, mainWorktreePath);
|
|
3054
|
+
if (patch.length > 0) {
|
|
3055
|
+
try {
|
|
3056
|
+
gitApplyFromStdin(patch, mainWorktreePath);
|
|
3057
|
+
} catch (error) {
|
|
3058
|
+
logger.warn(`patch apply \u5931\u8D25: ${error}`);
|
|
3059
|
+
printWarning(MESSAGES.VALIDATE_PATCH_APPLY_FAILED(branchName));
|
|
3060
|
+
return { success: false };
|
|
3061
|
+
}
|
|
3062
|
+
}
|
|
3063
|
+
return { success: true };
|
|
3064
|
+
} finally {
|
|
3065
|
+
if (didTempCommit) {
|
|
3066
|
+
try {
|
|
3067
|
+
gitResetSoft(1, targetWorktreePath);
|
|
3068
|
+
} catch (error) {
|
|
3069
|
+
logger.error(`\u64A4\u9500\u4E34\u65F6 commit \u5931\u8D25: ${error}`);
|
|
3070
|
+
}
|
|
3071
|
+
try {
|
|
3072
|
+
gitRestoreStaged(targetWorktreePath);
|
|
3073
|
+
} catch (error) {
|
|
3074
|
+
logger.error(`\u6062\u590D\u6682\u5B58\u533A\u5931\u8D25: ${error}`);
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
3079
|
+
function computeCurrentTreeHash(mainWorktreePath) {
|
|
3080
|
+
gitAddAll(mainWorktreePath);
|
|
3081
|
+
const treeHash = gitWriteTree(mainWorktreePath);
|
|
3082
|
+
gitRestoreStaged(mainWorktreePath);
|
|
3083
|
+
return treeHash;
|
|
3084
|
+
}
|
|
3085
|
+
function saveCurrentSnapshotTree(mainWorktreePath, projectName, branchName, stagedTreeHash = "") {
|
|
3086
|
+
gitAddAll(mainWorktreePath);
|
|
3087
|
+
const treeHash = gitWriteTree(mainWorktreePath);
|
|
3088
|
+
gitRestoreStaged(mainWorktreePath);
|
|
3089
|
+
const headCommitHash = getHeadCommitHash(mainWorktreePath);
|
|
3090
|
+
writeSnapshot(projectName, branchName, treeHash, headCommitHash, stagedTreeHash);
|
|
3091
|
+
return treeHash;
|
|
3092
|
+
}
|
|
3093
|
+
function loadOldSnapshotToStage(oldTreeHash, oldHeadCommitHash, currentHeadCommitHash, mainWorktreePath) {
|
|
3094
|
+
try {
|
|
3095
|
+
if (oldHeadCommitHash && oldHeadCommitHash !== currentHeadCommitHash) {
|
|
3096
|
+
const oldHeadTreeHash = getCommitTreeHash(oldHeadCommitHash, mainWorktreePath);
|
|
3097
|
+
const oldChangePatch = gitDiffTree(oldHeadTreeHash, oldTreeHash, mainWorktreePath);
|
|
3098
|
+
if (oldChangePatch.length > 0 && gitApplyCachedCheck(oldChangePatch, mainWorktreePath)) {
|
|
3099
|
+
gitApplyCachedFromStdin(oldChangePatch, mainWorktreePath);
|
|
3100
|
+
const stagedTreeHash = gitWriteTree(mainWorktreePath);
|
|
3101
|
+
return { success: true, stagedTreeHash };
|
|
3102
|
+
} else if (oldChangePatch.length > 0) {
|
|
3103
|
+
logger.warn("\u65E7\u53D8\u66F4 patch \u4E0E\u5F53\u524D HEAD \u51B2\u7A81\uFF0C\u964D\u7EA7\u4E3A\u5168\u91CF\u6A21\u5F0F");
|
|
3104
|
+
return { success: false, stagedTreeHash: "" };
|
|
3105
|
+
}
|
|
3106
|
+
return { success: true, stagedTreeHash: "" };
|
|
3107
|
+
} else {
|
|
3108
|
+
gitReadTree(oldTreeHash, mainWorktreePath);
|
|
3109
|
+
return { success: true, stagedTreeHash: oldTreeHash };
|
|
3110
|
+
}
|
|
3111
|
+
} catch (error) {
|
|
3112
|
+
logger.warn(`\u589E\u91CF read-tree \u5931\u8D25: ${error}`);
|
|
3113
|
+
return { success: false, stagedTreeHash: "" };
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
function switchToValidateBranch(branchName, mainWorktreePath) {
|
|
3117
|
+
const validateBranchName = getValidateBranchName(branchName);
|
|
3118
|
+
if (!checkBranchExists(validateBranchName)) {
|
|
3119
|
+
throw new ClawtError(MESSAGES.VALIDATE_BRANCH_NOT_FOUND(validateBranchName, branchName));
|
|
3120
|
+
}
|
|
3121
|
+
const currentBranch = getCurrentBranch(mainWorktreePath);
|
|
3122
|
+
if (currentBranch !== validateBranchName) {
|
|
3123
|
+
gitCheckout(validateBranchName, mainWorktreePath);
|
|
3124
|
+
}
|
|
3125
|
+
return validateBranchName;
|
|
3126
|
+
}
|
|
3127
|
+
|
|
2971
3128
|
// src/utils/interactive-panel.ts
|
|
2972
3129
|
import { createInterface as createInterface2 } from "readline";
|
|
2973
3130
|
|
|
@@ -3658,6 +3815,16 @@ async function handleRemove(options) {
|
|
|
3658
3815
|
printInfo(MESSAGES.NO_WORKTREES);
|
|
3659
3816
|
return;
|
|
3660
3817
|
}
|
|
3818
|
+
const currentBranch = getCurrentBranch();
|
|
3819
|
+
for (const wt of worktreesToRemove) {
|
|
3820
|
+
if (wt.branch === currentBranch) {
|
|
3821
|
+
throw new ClawtError(MESSAGES.REMOVE_BRANCH_IS_CURRENT(wt.branch));
|
|
3822
|
+
}
|
|
3823
|
+
const validateBranch = getValidateBranchName(wt.branch);
|
|
3824
|
+
if (validateBranch === currentBranch) {
|
|
3825
|
+
throw new ClawtError(MESSAGES.REMOVE_VALIDATE_BRANCH_IS_CURRENT(wt.branch, validateBranch));
|
|
3826
|
+
}
|
|
3827
|
+
}
|
|
3661
3828
|
printInfo("\u5373\u5C06\u79FB\u9664\u4EE5\u4E0B worktree \u53CA\u672C\u5730\u5206\u652F\uFF1A\n");
|
|
3662
3829
|
worktreesToRemove.forEach((wt, index) => {
|
|
3663
3830
|
printInfo(` ${index + 1}. ${wt.path} \u2192 \u5206\u652F: ${wt.branch} \u9A8C\u8BC1\u5206\u652F: ${getValidateBranchName(wt.branch)}`);
|
|
@@ -3934,40 +4101,6 @@ function registerValidateCommand(program2) {
|
|
|
3934
4101
|
async function handleDirtyMainWorktree(mainWorktreePath) {
|
|
3935
4102
|
await handleDirtyWorkingDir(mainWorktreePath);
|
|
3936
4103
|
}
|
|
3937
|
-
function migrateChangesViaPatch(targetWorktreePath, mainWorktreePath, branchName, hasUncommitted) {
|
|
3938
|
-
let didTempCommit = false;
|
|
3939
|
-
try {
|
|
3940
|
-
if (hasUncommitted) {
|
|
3941
|
-
gitAddAll(targetWorktreePath);
|
|
3942
|
-
gitCommit("clawt:temp-commit-for-validate", targetWorktreePath);
|
|
3943
|
-
didTempCommit = true;
|
|
3944
|
-
}
|
|
3945
|
-
const patch = gitDiffBinaryAgainstBranch(branchName, mainWorktreePath);
|
|
3946
|
-
if (patch.length > 0) {
|
|
3947
|
-
try {
|
|
3948
|
-
gitApplyFromStdin(patch, mainWorktreePath);
|
|
3949
|
-
} catch (error) {
|
|
3950
|
-
logger.warn(`patch apply \u5931\u8D25: ${error}`);
|
|
3951
|
-
printWarning(MESSAGES.VALIDATE_PATCH_APPLY_FAILED(branchName));
|
|
3952
|
-
return { success: false };
|
|
3953
|
-
}
|
|
3954
|
-
}
|
|
3955
|
-
return { success: true };
|
|
3956
|
-
} finally {
|
|
3957
|
-
if (didTempCommit) {
|
|
3958
|
-
try {
|
|
3959
|
-
gitResetSoft(1, targetWorktreePath);
|
|
3960
|
-
} catch (error) {
|
|
3961
|
-
logger.error(`\u64A4\u9500\u4E34\u65F6 commit \u5931\u8D25: ${error}`);
|
|
3962
|
-
}
|
|
3963
|
-
try {
|
|
3964
|
-
gitRestoreStaged(targetWorktreePath);
|
|
3965
|
-
} catch (error) {
|
|
3966
|
-
logger.error(`\u6062\u590D\u6682\u5B58\u533A\u5931\u8D25: ${error}`);
|
|
3967
|
-
}
|
|
3968
|
-
}
|
|
3969
|
-
}
|
|
3970
|
-
}
|
|
3971
4104
|
async function handlePatchApplyFailure(targetWorktreePath, branchName) {
|
|
3972
4105
|
const confirmed = await confirmAction(MESSAGES.VALIDATE_CONFIRM_AUTO_SYNC(branchName));
|
|
3973
4106
|
if (!confirmed) {
|
|
@@ -3977,14 +4110,6 @@ async function handlePatchApplyFailure(targetWorktreePath, branchName) {
|
|
|
3977
4110
|
printInfo(MESSAGES.VALIDATE_AUTO_SYNC_START(branchName));
|
|
3978
4111
|
const syncResult = await executeSyncForBranch(targetWorktreePath, branchName);
|
|
3979
4112
|
}
|
|
3980
|
-
function saveCurrentSnapshotTree(mainWorktreePath, projectName, branchName) {
|
|
3981
|
-
gitAddAll(mainWorktreePath);
|
|
3982
|
-
const treeHash = gitWriteTree(mainWorktreePath);
|
|
3983
|
-
gitRestoreStaged(mainWorktreePath);
|
|
3984
|
-
const headCommitHash = getHeadCommitHash(mainWorktreePath);
|
|
3985
|
-
writeSnapshot(projectName, branchName, treeHash, headCommitHash);
|
|
3986
|
-
return treeHash;
|
|
3987
|
-
}
|
|
3988
4113
|
async function handleValidateClean(options) {
|
|
3989
4114
|
validateMainWorktree();
|
|
3990
4115
|
requireProjectConfig();
|
|
@@ -4013,11 +4138,7 @@ async function handleValidateClean(options) {
|
|
|
4013
4138
|
printSuccess(MESSAGES.VALIDATE_CLEANED(branchName));
|
|
4014
4139
|
}
|
|
4015
4140
|
async function handleFirstValidate(targetWorktreePath, mainWorktreePath, projectName, branchName, hasUncommitted) {
|
|
4016
|
-
const validateBranchName =
|
|
4017
|
-
if (!checkBranchExists(validateBranchName)) {
|
|
4018
|
-
throw new ClawtError(MESSAGES.VALIDATE_BRANCH_NOT_FOUND(validateBranchName, branchName));
|
|
4019
|
-
}
|
|
4020
|
-
gitCheckout(validateBranchName, mainWorktreePath);
|
|
4141
|
+
const validateBranchName = switchToValidateBranch(branchName, mainWorktreePath);
|
|
4021
4142
|
const result = migrateChangesViaPatch(targetWorktreePath, mainWorktreePath, branchName, hasUncommitted);
|
|
4022
4143
|
if (!result.success) {
|
|
4023
4144
|
await ensureOnMainWorkBranch(mainWorktreePath);
|
|
@@ -4028,102 +4149,42 @@ async function handleFirstValidate(targetWorktreePath, mainWorktreePath, project
|
|
|
4028
4149
|
printSuccess(MESSAGES.VALIDATE_SUCCESS_WITH_BRANCH(branchName, validateBranchName));
|
|
4029
4150
|
}
|
|
4030
4151
|
async function handleIncrementalValidate(targetWorktreePath, mainWorktreePath, projectName, branchName, hasUncommitted) {
|
|
4031
|
-
const { treeHash: oldTreeHash, headCommitHash: oldHeadCommitHash } = readSnapshot(projectName, branchName);
|
|
4152
|
+
const { treeHash: oldTreeHash, headCommitHash: oldHeadCommitHash, stagedTreeHash: oldStagedTreeHash } = readSnapshot(projectName, branchName);
|
|
4032
4153
|
if (!isWorkingDirClean(mainWorktreePath)) {
|
|
4033
4154
|
gitResetHard(mainWorktreePath);
|
|
4034
4155
|
gitCleanForce(mainWorktreePath);
|
|
4035
4156
|
}
|
|
4036
|
-
const validateBranchName =
|
|
4037
|
-
if (!checkBranchExists(validateBranchName)) {
|
|
4038
|
-
throw new ClawtError(MESSAGES.VALIDATE_BRANCH_NOT_FOUND(validateBranchName, branchName));
|
|
4039
|
-
}
|
|
4040
|
-
const currentBranch = getCurrentBranch(mainWorktreePath);
|
|
4041
|
-
if (currentBranch !== validateBranchName) {
|
|
4042
|
-
gitCheckout(validateBranchName, mainWorktreePath);
|
|
4043
|
-
}
|
|
4157
|
+
const validateBranchName = switchToValidateBranch(branchName, mainWorktreePath);
|
|
4044
4158
|
const result = migrateChangesViaPatch(targetWorktreePath, mainWorktreePath, branchName, hasUncommitted);
|
|
4045
4159
|
if (!result.success) {
|
|
4046
4160
|
await ensureOnMainWorkBranch(mainWorktreePath);
|
|
4047
4161
|
await handlePatchApplyFailure(targetWorktreePath, branchName);
|
|
4048
4162
|
return;
|
|
4049
4163
|
}
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
logger.warn("\u65E7\u53D8\u66F4 patch \u4E0E\u5F53\u524D HEAD \u51B2\u7A81\uFF0C\u964D\u7EA7\u4E3A\u5168\u91CF\u6A21\u5F0F");
|
|
4060
|
-
printWarning(MESSAGES.INCREMENTAL_VALIDATE_FALLBACK);
|
|
4061
|
-
printSuccess(MESSAGES.VALIDATE_SUCCESS_WITH_BRANCH(branchName, validateBranchName));
|
|
4062
|
-
return;
|
|
4164
|
+
const newTreeHash = computeCurrentTreeHash(mainWorktreePath);
|
|
4165
|
+
const currentHeadCommitHash = getHeadCommitHash(mainWorktreePath);
|
|
4166
|
+
const hasNewChanges = newTreeHash !== oldTreeHash || oldHeadCommitHash && oldHeadCommitHash !== currentHeadCommitHash;
|
|
4167
|
+
if (!hasNewChanges) {
|
|
4168
|
+
if (oldStagedTreeHash) {
|
|
4169
|
+
try {
|
|
4170
|
+
gitReadTree(oldStagedTreeHash, mainWorktreePath);
|
|
4171
|
+
} catch (error) {
|
|
4172
|
+
logger.warn(`\u6062\u590D\u6682\u5B58\u533A\u5931\u8D25: ${error}`);
|
|
4063
4173
|
}
|
|
4064
|
-
} else {
|
|
4065
|
-
gitReadTree(oldTreeHash, mainWorktreePath);
|
|
4066
4174
|
}
|
|
4067
|
-
|
|
4068
|
-
logger.warn(`\u589E\u91CF read-tree \u5931\u8D25: ${error}`);
|
|
4069
|
-
printWarning(MESSAGES.INCREMENTAL_VALIDATE_FALLBACK);
|
|
4175
|
+
printInfo(MESSAGES.INCREMENTAL_VALIDATE_NO_CHANGES(branchName));
|
|
4070
4176
|
printSuccess(MESSAGES.VALIDATE_SUCCESS_WITH_BRANCH(branchName, validateBranchName));
|
|
4071
4177
|
return;
|
|
4072
4178
|
}
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
const result = runCommandInherited(command, { cwd: mainWorktreePath });
|
|
4079
|
-
printSeparator();
|
|
4080
|
-
if (result.error) {
|
|
4081
|
-
printError(MESSAGES.VALIDATE_RUN_ERROR(command, result.error.message));
|
|
4179
|
+
const stageResult = loadOldSnapshotToStage(oldTreeHash, oldHeadCommitHash, currentHeadCommitHash, mainWorktreePath);
|
|
4180
|
+
if (!stageResult.success) {
|
|
4181
|
+
printWarning(MESSAGES.INCREMENTAL_VALIDATE_FALLBACK);
|
|
4182
|
+
writeSnapshot(projectName, branchName, newTreeHash, currentHeadCommitHash, "");
|
|
4183
|
+
printSuccess(MESSAGES.VALIDATE_SUCCESS_WITH_BRANCH(branchName, validateBranchName));
|
|
4082
4184
|
return;
|
|
4083
4185
|
}
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
printSuccess(MESSAGES.VALIDATE_RUN_SUCCESS(command));
|
|
4087
|
-
} else {
|
|
4088
|
-
printError(MESSAGES.VALIDATE_RUN_FAILED(command, exitCode));
|
|
4089
|
-
}
|
|
4090
|
-
}
|
|
4091
|
-
function reportParallelResults(results) {
|
|
4092
|
-
printSeparator();
|
|
4093
|
-
const successCount = results.filter((r) => r.exitCode === 0 && !r.error).length;
|
|
4094
|
-
const failedCount = results.length - successCount;
|
|
4095
|
-
for (const result of results) {
|
|
4096
|
-
if (result.error) {
|
|
4097
|
-
printError(MESSAGES.VALIDATE_PARALLEL_CMD_ERROR(result.command, result.error));
|
|
4098
|
-
} else if (result.exitCode === 0) {
|
|
4099
|
-
printSuccess(MESSAGES.VALIDATE_PARALLEL_CMD_SUCCESS(result.command));
|
|
4100
|
-
} else {
|
|
4101
|
-
printError(MESSAGES.VALIDATE_PARALLEL_CMD_FAILED(result.command, result.exitCode));
|
|
4102
|
-
}
|
|
4103
|
-
}
|
|
4104
|
-
if (failedCount === 0) {
|
|
4105
|
-
printSuccess(MESSAGES.VALIDATE_PARALLEL_RUN_ALL_SUCCESS(results.length));
|
|
4106
|
-
} else {
|
|
4107
|
-
printError(MESSAGES.VALIDATE_PARALLEL_RUN_SUMMARY(successCount, failedCount));
|
|
4108
|
-
}
|
|
4109
|
-
}
|
|
4110
|
-
async function executeParallelCommands(commands, mainWorktreePath) {
|
|
4111
|
-
printInfo(MESSAGES.VALIDATE_PARALLEL_RUN_START(commands.length));
|
|
4112
|
-
for (let i = 0; i < commands.length; i++) {
|
|
4113
|
-
printInfo(MESSAGES.VALIDATE_PARALLEL_CMD_START(i + 1, commands.length, commands[i]));
|
|
4114
|
-
}
|
|
4115
|
-
printSeparator();
|
|
4116
|
-
const results = await runParallelCommands(commands, { cwd: mainWorktreePath });
|
|
4117
|
-
reportParallelResults(results);
|
|
4118
|
-
}
|
|
4119
|
-
async function executeRunCommand(command, mainWorktreePath) {
|
|
4120
|
-
printInfo("");
|
|
4121
|
-
const commands = parseParallelCommands(command);
|
|
4122
|
-
if (commands.length <= 1) {
|
|
4123
|
-
executeSingleCommand(commands[0] || command, mainWorktreePath);
|
|
4124
|
-
} else {
|
|
4125
|
-
await executeParallelCommands(commands, mainWorktreePath);
|
|
4126
|
-
}
|
|
4186
|
+
writeSnapshot(projectName, branchName, newTreeHash, currentHeadCommitHash, stageResult.stagedTreeHash);
|
|
4187
|
+
printSuccess(MESSAGES.INCREMENTAL_VALIDATE_SUCCESS(branchName));
|
|
4127
4188
|
}
|
|
4128
4189
|
function resolveRunCommand(optionRun) {
|
|
4129
4190
|
if (optionRun) {
|
package/dist/postinstall.js
CHANGED
|
@@ -171,6 +171,8 @@ var VALIDATE_MESSAGES = {
|
|
|
171
171
|
\u6682\u5B58\u533A = \u4E0A\u6B21\u5FEB\u7167\uFF0C\u5DE5\u4F5C\u76EE\u5F55 = \u6700\u65B0\u53D8\u66F4`,
|
|
172
172
|
/** 增量 validate 降级为全量模式提示 */
|
|
173
173
|
INCREMENTAL_VALIDATE_FALLBACK: "\u589E\u91CF\u5BF9\u6BD4\u5931\u8D25\uFF0C\u5DF2\u964D\u7EA7\u4E3A\u5168\u91CF\u6A21\u5F0F",
|
|
174
|
+
/** 增量 validate 检测到目标 worktree 无新变更 */
|
|
175
|
+
INCREMENTAL_VALIDATE_NO_CHANGES: (branch) => `\u5206\u652F ${branch} \u81EA\u4E0A\u6B21 validate \u4EE5\u6765\u6CA1\u6709\u65B0\u7684\u53D8\u66F4\uFF0C\u5DF2\u6062\u590D\u5230\u4E0A\u6B21\u9A8C\u8BC1\u72B6\u6001`,
|
|
174
176
|
/** validate 状态已清理 */
|
|
175
177
|
VALIDATE_CLEANED: (branch) => `\u2713 \u5206\u652F ${branch} \u7684 validate \u72B6\u6001\u5DF2\u6E05\u7406`,
|
|
176
178
|
/** validate patch apply 失败,提示用户同步主分支 */
|
|
@@ -288,7 +290,11 @@ ${failures.map((f) => ` \u2717 ${f.path}: ${f.error}`).join("\n")}`,
|
|
|
288
290
|
/** 用户选择保留本地分支 */
|
|
289
291
|
REMOVE_BRANCHES_KEPT: "\u5DF2\u4FDD\u7559\u672C\u5730\u5206\u652F\uFF0C\u53EF\u7A0D\u540E\u4F7F\u7528 git branch -D <\u5206\u652F\u540D> \u624B\u52A8\u5220\u9664",
|
|
290
292
|
/** 确认删除本地分支和验证分支 */
|
|
291
|
-
REMOVE_CONFIRM_DELETE_BRANCHES: "\u662F\u5426\u540C\u65F6\u5220\u9664\u5BF9\u5E94\u7684\u672C\u5730\u5206\u652F\u548C\u9A8C\u8BC1\u5206\u652F\uFF1F"
|
|
293
|
+
REMOVE_CONFIRM_DELETE_BRANCHES: "\u662F\u5426\u540C\u65F6\u5220\u9664\u5BF9\u5E94\u7684\u672C\u5730\u5206\u652F\u548C\u9A8C\u8BC1\u5206\u652F\uFF1F",
|
|
294
|
+
/** 待移除的 worktree 的分支是主 worktree 当前所在分支 */
|
|
295
|
+
REMOVE_BRANCH_IS_CURRENT: (branch) => `\u65E0\u6CD5\u79FB\u9664\uFF1A\u5206\u652F ${branch} \u662F\u4E3B worktree \u5F53\u524D\u6240\u5728\u5206\u652F\uFF0C\u8BF7\u5148\u5207\u6362\u5230\u5176\u4ED6\u5206\u652F\u540E\u518D\u79FB\u9664`,
|
|
296
|
+
/** 待移除的 worktree 对应的验证分支是主 worktree 当前所在分支 */
|
|
297
|
+
REMOVE_VALIDATE_BRANCH_IS_CURRENT: (branch, validateBranch) => `\u65E0\u6CD5\u79FB\u9664\uFF1A\u5206\u652F ${branch} \u7684\u9A8C\u8BC1\u5206\u652F ${validateBranch} \u662F\u4E3B worktree \u5F53\u524D\u6240\u5728\u5206\u652F\uFF0C\u8BF7\u5148\u5207\u6362\u5230\u5176\u4ED6\u5206\u652F\u540E\u518D\u79FB\u9664`
|
|
292
298
|
};
|
|
293
299
|
|
|
294
300
|
// src/constants/messages/reset.ts
|
package/docs/spec.md
CHANGED
|
@@ -254,6 +254,7 @@ async function interactiveConfigEditor<T extends object>(
|
|
|
254
254
|
│ └── <project-name>/ # 以项目名分组
|
|
255
255
|
│ ├── <branchName>.tree # 每个分支一个 tree hash 快照文件(存储 git tree 对象的 hash)
|
|
256
256
|
│ ├── <branchName>.head # 每个分支一个 HEAD commit hash 快照文件(存储快照时验证分支的 HEAD commit hash)
|
|
257
|
+
│ ├── <branchName>.staged # 每个分支一个 staged tree hash 快照文件(存储 validate 结束时暂存区对应的 tree hash,用于无变更时恢复)
|
|
257
258
|
│ └── ...
|
|
258
259
|
├── projects/<project-name>/ # 项目级配置目录
|
|
259
260
|
│ └── config.json # 项目级配置(含 clawtMainWorkBranch)
|
package/docs/validate.md
CHANGED
|
@@ -30,7 +30,7 @@ validate 不再在主工作分支上直接 apply patch,而是先切换到目
|
|
|
30
30
|
|
|
31
31
|
**快照机制:**
|
|
32
32
|
|
|
33
|
-
validate 命令引入了**快照(snapshot)机制**来支持增量对比。每次 validate 执行成功后,会将当前全量变更通过 `git write-tree` 保存为 git tree 对象,并将 tree hash 记录到文件(`~/.clawt/validate-snapshots/<project>/<branchName>.tree`),同时将验证分支的 HEAD commit hash 记录到文件(`~/.clawt/validate-snapshots/<project>/<branchName>.head`),用于增量 validate
|
|
33
|
+
validate 命令引入了**快照(snapshot)机制**来支持增量对比。每次 validate 执行成功后,会将当前全量变更通过 `git write-tree` 保存为 git tree 对象,并将 tree hash 记录到文件(`~/.clawt/validate-snapshots/<project>/<branchName>.tree`),同时将验证分支的 HEAD commit hash 记录到文件(`~/.clawt/validate-snapshots/<project>/<branchName>.head`),以及 validate 结束时暂存区对应的 tree hash 记录到文件(`~/.clawt/validate-snapshots/<project>/<branchName>.staged`),用于增量 validate 时对齐基准和无变更恢复。当再次执行 validate 时,先计算当前变更的 tree hash 与旧快照对比:如果没有新变更(tree hash 和 HEAD 均未变化),直接通过 `git read-tree` 恢复上次 validate 结束时的暂存区状态,跳过后续步骤;如果有新变更,则继续执行暂存区载入流程——如果验证分支 HEAD 未变化(正常情况),通过 `git read-tree` 将上次快照的 tree 对象载入暂存区;如果验证分支 HEAD 已变化(sync 后重建了验证分支),则将旧变更 patch(旧 tree 相对于旧 HEAD 的差异)重放到当前 HEAD 暂存区上,避免新旧 tree 基准不同导致 diff 混入 HEAD 变化的内容。最终用户可通过 `git diff` 查看两次 validate 之间的增量差异。
|
|
34
34
|
|
|
35
35
|
**运行流程:**
|
|
36
36
|
|
|
@@ -289,7 +289,7 @@ clawt validate -b feature-scheme-1 -r "pnpm test & pnpm build"
|
|
|
289
289
|
|
|
290
290
|
##### 步骤 1:读取旧快照
|
|
291
291
|
|
|
292
|
-
在清空主 worktree 之前,读取上次保存的快照 tree hash
|
|
292
|
+
在清空主 worktree 之前,读取上次保存的快照 tree hash、当时的 HEAD commit hash 和暂存区 tree hash(`stagedTreeHash`)。
|
|
293
293
|
|
|
294
294
|
##### 步骤 2:确保主 worktree 干净
|
|
295
295
|
|
|
@@ -307,9 +307,27 @@ git checkout clawt-validate-<branchName>
|
|
|
307
307
|
|
|
308
308
|
通过 patch 方式从目标分支获取最新全量变更(流程同首次 validate 的步骤 4)。如果 patch apply 失败,同样进入自动 sync 交互流程(见首次 validate 的 [patch apply 失败后的自动 sync 流程](#patch-apply-失败后的自动-sync-流程)),validate 流程提前结束。
|
|
309
309
|
|
|
310
|
-
##### 步骤 5
|
|
310
|
+
##### 步骤 5:检测是否有新变更
|
|
311
311
|
|
|
312
|
-
|
|
312
|
+
计算当前工作目录变更的 tree hash(`git add . → git write-tree → git restore --staged .`),并与旧快照的 tree hash 及 HEAD commit hash 对比:
|
|
313
|
+
|
|
314
|
+
```bash
|
|
315
|
+
# 计算当前变更的 tree hash
|
|
316
|
+
git add .
|
|
317
|
+
git write-tree # → newTreeHash
|
|
318
|
+
git restore --staged .
|
|
319
|
+
|
|
320
|
+
# 获取当前 HEAD
|
|
321
|
+
git rev-parse HEAD # → currentHeadCommitHash
|
|
322
|
+
|
|
323
|
+
# 判断是否有新变更
|
|
324
|
+
hasNewChanges = (newTreeHash !== oldTreeHash) || (oldHeadCommitHash !== currentHeadCommitHash)
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
- **无新变更**(tree hash 和 HEAD 均未变化)→ 不更新快照,直接通过 `git read-tree <oldStagedTreeHash>` 恢复上次 validate 结束时的暂存区状态,输出提示后返回
|
|
328
|
+
- **有新变更** → 继续步骤 6
|
|
329
|
+
|
|
330
|
+
> 无变更检测避免了重复 validate 时不必要的快照更新和暂存区重载操作。恢复上次暂存区状态后,用户看到的 `git diff` 结果与上次 validate 结束时完全一致。
|
|
313
331
|
|
|
314
332
|
##### 步骤 6:将旧变更状态载入暂存区
|
|
315
333
|
|
|
@@ -323,8 +341,8 @@ git checkout clawt-validate-<branchName>
|
|
|
323
341
|
git read-tree <旧 tree hash>
|
|
324
342
|
```
|
|
325
343
|
|
|
326
|
-
- **读取成功** →
|
|
327
|
-
- **读取失败**(tree 对象可能被 git gc 回收)→
|
|
344
|
+
- **读取成功** → 记录 `newStagedTreeHash = oldTreeHash`,结果:暂存区=上次快照,工作目录=最新全量变更(用户可通过 `git diff` 查看增量差异)
|
|
345
|
+
- **读取失败**(tree 对象可能被 git gc 回收)→ 降级为全量模式,写入快照(`stagedTreeHash` 为空)后返回,暂存区保持为空,等同于首次 validate 的结果
|
|
328
346
|
|
|
329
347
|
> 这是最常见的路径。相比重构前,正常情况不再需要处理 HEAD 变化的复杂逻辑,代码路径更简单、更可靠。
|
|
330
348
|
|
|
@@ -344,25 +362,48 @@ git apply --cached --check < patch
|
|
|
344
362
|
|
|
345
363
|
# 无冲突:apply --cached 到当前 HEAD 暂存区
|
|
346
364
|
git apply --cached < patch
|
|
365
|
+
|
|
366
|
+
# 记录暂存区的 tree hash
|
|
367
|
+
git write-tree # → newStagedTreeHash
|
|
347
368
|
```
|
|
348
369
|
|
|
349
370
|
- **patch 为空**(旧变更为空)→ 暂存区保持干净
|
|
350
|
-
- **无冲突** → apply --cached 到当前 HEAD
|
|
351
|
-
- **有冲突** →
|
|
371
|
+
- **无冲突** → apply --cached 到当前 HEAD 暂存区,通过 `git write-tree` 记录 `newStagedTreeHash`,结果与正常情况一致
|
|
372
|
+
- **有冲突** → 降级为全量模式(暂存区保持为空),写入快照(`stagedTreeHash` 为空)后返回
|
|
352
373
|
|
|
353
|
-
##### 步骤 7
|
|
374
|
+
##### 步骤 7:写入新快照
|
|
375
|
+
|
|
376
|
+
将步骤 5 计算的 `newTreeHash`、当前 HEAD commit hash 和步骤 6 记录的 `newStagedTreeHash` 写入快照文件,供下次 validate 使用:
|
|
377
|
+
|
|
378
|
+
```bash
|
|
379
|
+
# 写入 ~/.clawt/validate-snapshots/<project>/<branchName>.tree
|
|
380
|
+
echo <newTreeHash>
|
|
381
|
+
|
|
382
|
+
# 写入 ~/.clawt/validate-snapshots/<project>/<branchName>.head
|
|
383
|
+
echo <currentHeadCommitHash>
|
|
384
|
+
|
|
385
|
+
# 写入 ~/.clawt/validate-snapshots/<project>/<branchName>.staged
|
|
386
|
+
echo <newStagedTreeHash>
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
> `stagedTreeHash` 记录了 validate 结束时暂存区的完整状态。下次 validate 如果检测到无新变更,可直接通过此值恢复暂存区,避免重复执行 read-tree 或 apply --cached 流程。
|
|
390
|
+
|
|
391
|
+
##### 步骤 8:输出成功提示
|
|
354
392
|
|
|
355
393
|
```
|
|
356
394
|
# 增量模式成功
|
|
357
395
|
✓ 已将分支 feature-scheme-1 的最新变更应用到主 worktree(增量模式)
|
|
358
396
|
暂存区 = 上次快照,工作目录 = 最新变更
|
|
359
397
|
|
|
398
|
+
# 增量无变更
|
|
399
|
+
分支 feature-scheme-1 自上次 validate 以来没有新的变更,已恢复到上次验证状态
|
|
400
|
+
|
|
360
401
|
# 增量降级为全量
|
|
361
402
|
✓ 已切换到验证分支 clawt-validate-feature-scheme-1 并应用分支 feature-scheme-1 的变更
|
|
362
403
|
可以开始验证了
|
|
363
404
|
```
|
|
364
405
|
|
|
365
|
-
##### 步骤
|
|
406
|
+
##### 步骤 9:执行 `--run` 命令(可选)
|
|
366
407
|
|
|
367
408
|
与首次 validate 的步骤 7 相同,增量 validate 成功后也会执行 `-r, --run` 指定的命令(或从项目配置 `validateRunCommand` 读取的默认命令)。
|
|
368
409
|
|
package/package.json
CHANGED
package/src/commands/remove.ts
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
getValidateBranchName,
|
|
25
25
|
deleteValidateBranch,
|
|
26
26
|
requireProjectConfig,
|
|
27
|
+
getCurrentBranch,
|
|
27
28
|
} from '../utils/index.js';
|
|
28
29
|
import type { WorktreeMultiResolveMessages } from '../utils/index.js';
|
|
29
30
|
|
|
@@ -77,6 +78,18 @@ async function handleRemove(options: RemoveOptions): Promise<void> {
|
|
|
77
78
|
return;
|
|
78
79
|
}
|
|
79
80
|
|
|
81
|
+
// 检查待移除的分支是否是主 worktree 当前所在分支
|
|
82
|
+
const currentBranch = getCurrentBranch();
|
|
83
|
+
for (const wt of worktreesToRemove) {
|
|
84
|
+
if (wt.branch === currentBranch) {
|
|
85
|
+
throw new ClawtError(MESSAGES.REMOVE_BRANCH_IS_CURRENT(wt.branch));
|
|
86
|
+
}
|
|
87
|
+
const validateBranch = getValidateBranchName(wt.branch);
|
|
88
|
+
if (validateBranch === currentBranch) {
|
|
89
|
+
throw new ClawtError(MESSAGES.REMOVE_VALIDATE_BRANCH_IS_CURRENT(wt.branch, validateBranch));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
80
93
|
// 列出即将移除的 worktree
|
|
81
94
|
printInfo('即将移除以下 worktree 及本地分支:\n');
|
|
82
95
|
worktreesToRemove.forEach((wt, index) => {
|