clawt 2.7.2 → 2.7.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/README.md +1 -1
- package/dist/index.js +27 -5
- package/docs/spec.md +2 -0
- package/package.json +1 -1
- package/src/commands/list.ts +8 -2
- package/src/commands/merge.ts +17 -2
- package/src/constants/messages.ts +6 -1
- package/src/utils/formatter.ts +12 -0
- package/src/utils/index.ts +2 -2
package/README.md
CHANGED
|
@@ -246,7 +246,7 @@ clawt list [--json]
|
|
|
246
246
|
| ---- | ---- | ---- |
|
|
247
247
|
| `--json` | 否 | 以 JSON 格式输出(仅包含 path 和 branch) |
|
|
248
248
|
|
|
249
|
-
列出当前项目在 `~/.clawt/worktrees/` 下的所有 worktree
|
|
249
|
+
列出当前项目在 `~/.clawt/worktrees/` 下的所有 worktree 及对应分支。文本模式下,如果某个 worktree 处于空闲状态(0 个提交、无变更、无未提交修改),其路径会以橙色高亮显示,方便快速识别可能需要清理或还未开始工作的 worktree。指定 `--json` 时以 JSON 格式输出,便于脚本解析。
|
|
250
250
|
|
|
251
251
|
```bash
|
|
252
252
|
# 文本格式输出(默认)
|
package/dist/index.js
CHANGED
|
@@ -55,7 +55,7 @@ var MESSAGES = {
|
|
|
55
55
|
/** merge 成功(无提交信息,目标 worktree 已提交过) */
|
|
56
56
|
MERGE_SUCCESS_NO_MESSAGE: (branch, pushed) => `\u2713 \u5206\u652F ${branch} \u5DF2\u6210\u529F\u5408\u5E76\u5230\u5F53\u524D\u5206\u652F${pushed ? "\n \u5DF2\u63A8\u9001\u5230\u8FDC\u7A0B\u4ED3\u5E93" : ""}`,
|
|
57
57
|
/** merge 冲突 */
|
|
58
|
-
MERGE_CONFLICT: "\u5408\u5E76\u5B58\u5728\u51B2\u7A81\uFF0C\u8BF7\u624B\u52A8\u5904\u7406",
|
|
58
|
+
MERGE_CONFLICT: "\u5408\u5E76\u5B58\u5728\u51B2\u7A81\uFF0C\u8BF7\u624B\u52A8\u5904\u7406\uFF1A\n \u89E3\u51B3\u51B2\u7A81\u540E\u6267\u884C git add . && git merge --continue",
|
|
59
59
|
/** merge 后清理 worktree 和分支成功 */
|
|
60
60
|
WORKTREE_CLEANED: (branch) => `\u2713 \u5DF2\u6E05\u7406 worktree \u548C\u5206\u652F: ${branch}`,
|
|
61
61
|
/** 请提供提交信息 */
|
|
@@ -166,7 +166,11 @@ ${branches.map((b) => ` - ${b}`).join("\n")}`,
|
|
|
166
166
|
/** sync 交互选择提示 */
|
|
167
167
|
SYNC_SELECT_BRANCH: "\u8BF7\u9009\u62E9\u8981\u540C\u6B65\u7684\u5206\u652F",
|
|
168
168
|
/** sync 模糊匹配到多个结果提示 */
|
|
169
|
-
SYNC_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A
|
|
169
|
+
SYNC_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A`,
|
|
170
|
+
/** merge 后 pull 冲突 */
|
|
171
|
+
PULL_CONFLICT: "\u81EA\u52A8 pull \u65F6\u53D1\u751F\u51B2\u7A81\uFF0Cmerge \u5DF2\u5B8C\u6210\u4F46\u8FDC\u7A0B\u540C\u6B65\u5931\u8D25\n \u8BF7\u624B\u52A8\u89E3\u51B3\u51B2\u7A81\uFF1A\n \u89E3\u51B3\u51B2\u7A81\u540E\u6267\u884C git add . && git commit\n \u7136\u540E\u6267\u884C git push \u63A8\u9001\u5230\u8FDC\u7A0B",
|
|
172
|
+
/** push 失败 */
|
|
173
|
+
PUSH_FAILED: "\u81EA\u52A8 push \u5931\u8D25\uFF0Cmerge \u548C pull \u5DF2\u5B8C\u6210\n \u8BF7\u624B\u52A8\u6267\u884C git push"
|
|
170
174
|
};
|
|
171
175
|
|
|
172
176
|
// src/constants/exitCodes.ts
|
|
@@ -499,6 +503,9 @@ function confirmDestructiveAction(dangerousCommand, description) {
|
|
|
499
503
|
printWarning(`\u5373\u5C06\u6267\u884C ${chalk.red.bold(dangerousCommand)}\uFF0C${description}`);
|
|
500
504
|
return confirmAction("\u662F\u5426\u7EE7\u7EED\uFF1F");
|
|
501
505
|
}
|
|
506
|
+
function isWorktreeIdle(status) {
|
|
507
|
+
return status.commitCount === 0 && status.insertions === 0 && status.deletions === 0 && !status.hasDirtyFiles;
|
|
508
|
+
}
|
|
502
509
|
function formatWorktreeStatus(status) {
|
|
503
510
|
const parts = [];
|
|
504
511
|
parts.push(chalk.yellow(`${status.commitCount} \u4E2A\u63D0\u4EA4`));
|
|
@@ -854,8 +861,10 @@ function printListAsText(projectName, worktrees) {
|
|
|
854
861
|
printInfo(` ${MESSAGES.NO_WORKTREES}`);
|
|
855
862
|
} else {
|
|
856
863
|
for (const wt of worktrees) {
|
|
857
|
-
printInfo(` ${wt.path} [${wt.branch}]`);
|
|
858
864
|
const status = getWorktreeStatus(wt);
|
|
865
|
+
const isIdle = status ? isWorktreeIdle(status) : false;
|
|
866
|
+
const pathDisplay = isIdle ? chalk2.hex("#FF8C00")(wt.path) : wt.path;
|
|
867
|
+
printInfo(` ${pathDisplay} [${wt.branch}]`);
|
|
859
868
|
if (status) {
|
|
860
869
|
printInfo(` ${formatWorktreeStatus(status)}`);
|
|
861
870
|
} else {
|
|
@@ -1425,8 +1434,21 @@ async function handleMerge(options) {
|
|
|
1425
1434
|
}
|
|
1426
1435
|
const autoPullPush = getConfigValue("autoPullPush");
|
|
1427
1436
|
if (autoPullPush) {
|
|
1428
|
-
|
|
1429
|
-
|
|
1437
|
+
try {
|
|
1438
|
+
gitPull(mainWorktreePath);
|
|
1439
|
+
} catch {
|
|
1440
|
+
if (hasMergeConflict(mainWorktreePath)) {
|
|
1441
|
+
printWarning(MESSAGES.PULL_CONFLICT);
|
|
1442
|
+
return;
|
|
1443
|
+
}
|
|
1444
|
+
throw new ClawtError(MESSAGES.PULL_CONFLICT);
|
|
1445
|
+
}
|
|
1446
|
+
try {
|
|
1447
|
+
gitPush(mainWorktreePath);
|
|
1448
|
+
} catch {
|
|
1449
|
+
printWarning(MESSAGES.PUSH_FAILED);
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
1430
1452
|
} else {
|
|
1431
1453
|
printInfo("\u5DF2\u8DF3\u8FC7\u81EA\u52A8 pull/push\uFF0C\u8BF7\u624B\u52A8\u6267\u884C git pull && git push");
|
|
1432
1454
|
}
|
package/docs/spec.md
CHANGED
package/package.json
CHANGED
package/src/commands/list.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
getProjectWorktrees,
|
|
10
10
|
getWorktreeStatus,
|
|
11
11
|
formatWorktreeStatus,
|
|
12
|
+
isWorktreeIdle,
|
|
12
13
|
printInfo,
|
|
13
14
|
} from '../utils/index.js';
|
|
14
15
|
// getWorktreeStatus 和 formatWorktreeStatus 仅在文本模式下使用
|
|
@@ -77,10 +78,15 @@ function printListAsText(projectName: string, worktrees: import('../types/index.
|
|
|
77
78
|
printInfo(` ${MESSAGES.NO_WORKTREES}`);
|
|
78
79
|
} else {
|
|
79
80
|
for (const wt of worktrees) {
|
|
80
|
-
printInfo(` ${wt.path} [${wt.branch}]`);
|
|
81
|
-
|
|
82
81
|
// 获取并展示 worktree 变更状态
|
|
83
82
|
const status = getWorktreeStatus(wt);
|
|
83
|
+
|
|
84
|
+
// 空闲状态的 worktree 路径用橙色高亮,提示用户关注
|
|
85
|
+
const isIdle = status ? isWorktreeIdle(status) : false;
|
|
86
|
+
const pathDisplay = isIdle ? chalk.hex('#FF8C00')(wt.path) : wt.path;
|
|
87
|
+
|
|
88
|
+
printInfo(` ${pathDisplay} [${wt.branch}]`);
|
|
89
|
+
|
|
84
90
|
if (status) {
|
|
85
91
|
printInfo(` ${formatWorktreeStatus(status)}`);
|
|
86
92
|
} else {
|
package/src/commands/merge.ts
CHANGED
|
@@ -196,8 +196,23 @@ async function handleMerge(options: MergeOptions): Promise<void> {
|
|
|
196
196
|
// 步骤 7:根据配置决定是否自动 pull 和 push
|
|
197
197
|
const autoPullPush = getConfigValue('autoPullPush');
|
|
198
198
|
if (autoPullPush) {
|
|
199
|
-
|
|
200
|
-
|
|
199
|
+
// pull 阶段:检测冲突并给出针对性引导
|
|
200
|
+
try {
|
|
201
|
+
gitPull(mainWorktreePath);
|
|
202
|
+
} catch {
|
|
203
|
+
if (hasMergeConflict(mainWorktreePath)) {
|
|
204
|
+
printWarning(MESSAGES.PULL_CONFLICT);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
throw new ClawtError(MESSAGES.PULL_CONFLICT);
|
|
208
|
+
}
|
|
209
|
+
// push 阶段
|
|
210
|
+
try {
|
|
211
|
+
gitPush(mainWorktreePath);
|
|
212
|
+
} catch {
|
|
213
|
+
printWarning(MESSAGES.PUSH_FAILED);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
201
216
|
} else {
|
|
202
217
|
printInfo('已跳过自动 pull/push,请手动执行 git pull && git push');
|
|
203
218
|
}
|
|
@@ -39,7 +39,7 @@ export const MESSAGES = {
|
|
|
39
39
|
MERGE_SUCCESS_NO_MESSAGE: (branch: string, pushed: boolean) =>
|
|
40
40
|
`✓ 分支 ${branch} 已成功合并到当前分支${pushed ? '\n 已推送到远程仓库' : ''}`,
|
|
41
41
|
/** merge 冲突 */
|
|
42
|
-
MERGE_CONFLICT: '
|
|
42
|
+
MERGE_CONFLICT: '合并存在冲突,请手动处理:\n 解决冲突后执行 git add . && git merge --continue',
|
|
43
43
|
/** merge 后清理 worktree 和分支成功 */
|
|
44
44
|
WORKTREE_CLEANED: (branch: string) => `✓ 已清理 worktree 和分支: ${branch}`,
|
|
45
45
|
/** 请提供提交信息 */
|
|
@@ -148,4 +148,9 @@ export const MESSAGES = {
|
|
|
148
148
|
SYNC_SELECT_BRANCH: '请选择要同步的分支',
|
|
149
149
|
/** sync 模糊匹配到多个结果提示 */
|
|
150
150
|
SYNC_MULTIPLE_MATCHES: (name: string) => `"${name}" 匹配到多个分支,请选择:`,
|
|
151
|
+
/** merge 后 pull 冲突 */
|
|
152
|
+
PULL_CONFLICT:
|
|
153
|
+
'自动 pull 时发生冲突,merge 已完成但远程同步失败\n 请手动解决冲突:\n 解决冲突后执行 git add . && git commit\n 然后执行 git push 推送到远程',
|
|
154
|
+
/** push 失败 */
|
|
155
|
+
PUSH_FAILED: '自动 push 失败,merge 和 pull 已完成\n 请手动执行 git push',
|
|
151
156
|
} as const;
|
package/src/utils/formatter.ts
CHANGED
|
@@ -78,6 +78,18 @@ export function confirmDestructiveAction(dangerousCommand: string, description:
|
|
|
78
78
|
return confirmAction('是否继续?');
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
/**
|
|
82
|
+
* 判断 worktree 是否处于空闲状态(0 个提交、无变更、无未提交修改)
|
|
83
|
+
* @param {WorktreeStatus} status - worktree 变更统计信息
|
|
84
|
+
* @returns {boolean} 是否空闲
|
|
85
|
+
*/
|
|
86
|
+
export function isWorktreeIdle(status: WorktreeStatus): boolean {
|
|
87
|
+
return status.commitCount === 0
|
|
88
|
+
&& status.insertions === 0
|
|
89
|
+
&& status.deletions === 0
|
|
90
|
+
&& !status.hasDirtyFiles;
|
|
91
|
+
}
|
|
92
|
+
|
|
81
93
|
/**
|
|
82
94
|
* 将 WorktreeStatus 格式化为带颜色的字符串
|
|
83
95
|
* @param {WorktreeStatus} status - worktree 变更统计信息
|
package/src/utils/index.ts
CHANGED
|
@@ -47,8 +47,8 @@ export {
|
|
|
47
47
|
export { sanitizeBranchName, generateBranchNames, validateBranchesNotExist } from './branch.js';
|
|
48
48
|
export { validateMainWorktree, validateGitInstalled, validateClaudeCodeInstalled } from './validation.js';
|
|
49
49
|
export { createWorktrees, getProjectWorktrees, getProjectWorktreeDir, cleanupWorktrees, getWorktreeStatus } from './worktree.js';
|
|
50
|
-
export { loadConfig, getConfigValue, ensureClawtDirs
|
|
51
|
-
export { printSuccess, printError, printWarning, printInfo, printSeparator, printDoubleSeparator, confirmAction, confirmDestructiveAction, formatWorktreeStatus } from './formatter.js';
|
|
50
|
+
export { loadConfig, writeDefaultConfig, getConfigValue, ensureClawtDirs } from './config.js';
|
|
51
|
+
export { printSuccess, printError, printWarning, printInfo, printSeparator, printDoubleSeparator, confirmAction, confirmDestructiveAction, formatWorktreeStatus, isWorktreeIdle } from './formatter.js';
|
|
52
52
|
export { ensureDir, removeEmptyDir } from './fs.js';
|
|
53
53
|
export { multilineInput } from './prompt.js';
|
|
54
54
|
export { launchInteractiveClaude } from './claude.js';
|