clawt 1.4.0 → 2.0.0
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 +3 -3
- package/dist/index.js +9 -26
- package/docs/spec.md +2 -3
- package/package.json +1 -1
- package/src/commands/remove.ts +2 -17
- package/src/types/command.ts +0 -2
- package/src/types/worktree.ts +2 -2
- package/src/utils/git.ts +7 -18
- package/src/utils/worktree.ts +1 -1
package/README.md
CHANGED
|
@@ -168,11 +168,11 @@ clawt remove [options]
|
|
|
168
168
|
# 移除当前项目下所有 worktree
|
|
169
169
|
clawt remove --all
|
|
170
170
|
|
|
171
|
-
# 移除指定分支名下的所有 worktree
|
|
171
|
+
# 移除指定分支名下的所有 worktree(匹配 feature-scheme 和 feature-scheme-*)
|
|
172
172
|
clawt remove -b feature-scheme
|
|
173
173
|
|
|
174
|
-
#
|
|
175
|
-
clawt remove -b feature-scheme
|
|
174
|
+
# 移除单个 worktree(直接写完整分支名)
|
|
175
|
+
clawt remove -b feature-scheme-2
|
|
176
176
|
```
|
|
177
177
|
|
|
178
178
|
移除时会询问是否同时删除对应的本地分支。
|
package/dist/index.js
CHANGED
|
@@ -325,15 +325,9 @@ function parseShortStat(output) {
|
|
|
325
325
|
}
|
|
326
326
|
return { insertions, deletions };
|
|
327
327
|
}
|
|
328
|
-
function getDiffStat(
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
const uncommittedOutput = execCommand("git diff --shortstat HEAD", { cwd: worktreePath });
|
|
332
|
-
const uncommitted = parseShortStat(uncommittedOutput);
|
|
333
|
-
return {
|
|
334
|
-
insertions: committed.insertions + uncommitted.insertions,
|
|
335
|
-
deletions: committed.deletions + uncommitted.deletions
|
|
336
|
-
};
|
|
328
|
+
function getDiffStat(worktreePath) {
|
|
329
|
+
const output = execCommand("git diff --shortstat HEAD", { cwd: worktreePath });
|
|
330
|
+
return parseShortStat(output);
|
|
337
331
|
}
|
|
338
332
|
function gitDiffCachedBinary(cwd) {
|
|
339
333
|
logger.debug(`\u6267\u884C\u547D\u4EE4: git diff --cached --binary${cwd ? ` (cwd: ${cwd})` : ""}`);
|
|
@@ -545,7 +539,7 @@ function cleanupWorktrees(worktrees) {
|
|
|
545
539
|
function getWorktreeStatus(worktree) {
|
|
546
540
|
try {
|
|
547
541
|
const commitCount = getCommitCountAhead(worktree.branch);
|
|
548
|
-
const { insertions, deletions } = getDiffStat(worktree.
|
|
542
|
+
const { insertions, deletions } = getDiffStat(worktree.path);
|
|
549
543
|
const hasDirtyFiles = !isWorkingDirClean(worktree.path);
|
|
550
544
|
return { commitCount, insertions, deletions, hasDirtyFiles };
|
|
551
545
|
} catch (error) {
|
|
@@ -718,14 +712,12 @@ function handleCreate(options) {
|
|
|
718
712
|
}
|
|
719
713
|
|
|
720
714
|
// src/commands/remove.ts
|
|
721
|
-
import { join as join4 } from "path";
|
|
722
715
|
function registerRemoveCommand(program2) {
|
|
723
|
-
program2.command("remove").description("\u79FB\u9664 worktree\uFF08\u652F\u6301\u5355\u4E2A/\u6279\u91CF/\u5168\u90E8\uFF09").option("--all", "\u79FB\u9664\u5F53\u524D\u9879\u76EE\u4E0B\u6240\u6709 worktree").option("-b, --branch <branchName>", "\u6307\u5B9A\u5206\u652F\u540D
|
|
716
|
+
program2.command("remove").description("\u79FB\u9664 worktree\uFF08\u652F\u6301\u5355\u4E2A/\u6279\u91CF/\u5168\u90E8\uFF09").option("--all", "\u79FB\u9664\u5F53\u524D\u9879\u76EE\u4E0B\u6240\u6709 worktree").option("-b, --branch <branchName>", "\u6307\u5B9A\u5206\u652F\u540D\uFF08\u5B8C\u6574\u5206\u652F\u540D\u7CBE\u786E\u5339\u914D\uFF09").action(async (options) => {
|
|
724
717
|
await handleRemove(options);
|
|
725
718
|
});
|
|
726
719
|
}
|
|
727
720
|
function resolveWorktreesToRemove(options) {
|
|
728
|
-
const projectDir = getProjectWorktreeDir();
|
|
729
721
|
const allWorktrees = getProjectWorktrees();
|
|
730
722
|
if (options.all) {
|
|
731
723
|
return allWorktrees;
|
|
@@ -733,15 +725,6 @@ function resolveWorktreesToRemove(options) {
|
|
|
733
725
|
if (!options.branch) {
|
|
734
726
|
throw new ClawtError("\u8BF7\u6307\u5B9A --all \u6216 -b <branchName> \u53C2\u6570");
|
|
735
727
|
}
|
|
736
|
-
if (options.index !== void 0) {
|
|
737
|
-
const targetName = `${options.branch}-${options.index}`;
|
|
738
|
-
const targetPath = join4(projectDir, targetName);
|
|
739
|
-
const found = allWorktrees.find((wt) => wt.path === targetPath);
|
|
740
|
-
if (!found) {
|
|
741
|
-
throw new ClawtError(MESSAGES.WORKTREE_NOT_FOUND(targetName));
|
|
742
|
-
}
|
|
743
|
-
return [found];
|
|
744
|
-
}
|
|
745
728
|
const matched = allWorktrees.filter(
|
|
746
729
|
(wt) => wt.branch === options.branch || wt.branch.startsWith(`${options.branch}-`)
|
|
747
730
|
);
|
|
@@ -975,7 +958,7 @@ async function handleResume(options) {
|
|
|
975
958
|
}
|
|
976
959
|
|
|
977
960
|
// src/commands/validate.ts
|
|
978
|
-
import { join as
|
|
961
|
+
import { join as join4 } from "path";
|
|
979
962
|
import { existsSync as existsSync6 } from "fs";
|
|
980
963
|
import Enquirer2 from "enquirer";
|
|
981
964
|
function registerValidateCommand(program2) {
|
|
@@ -1096,7 +1079,7 @@ async function handleValidate(options) {
|
|
|
1096
1079
|
const projectName = getProjectName();
|
|
1097
1080
|
const mainWorktreePath = getGitTopLevel();
|
|
1098
1081
|
const projectDir = getProjectWorktreeDir();
|
|
1099
|
-
const targetWorktreePath =
|
|
1082
|
+
const targetWorktreePath = join4(projectDir, options.branch);
|
|
1100
1083
|
logger.info(`validate \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${options.branch}`);
|
|
1101
1084
|
if (!existsSync6(targetWorktreePath)) {
|
|
1102
1085
|
throw new ClawtError(MESSAGES.WORKTREE_NOT_FOUND(options.branch));
|
|
@@ -1131,7 +1114,7 @@ async function handleValidate(options) {
|
|
|
1131
1114
|
}
|
|
1132
1115
|
|
|
1133
1116
|
// src/commands/merge.ts
|
|
1134
|
-
import { join as
|
|
1117
|
+
import { join as join5 } from "path";
|
|
1135
1118
|
import { existsSync as existsSync7 } from "fs";
|
|
1136
1119
|
function registerMergeCommand(program2) {
|
|
1137
1120
|
program2.command("merge").description("\u5408\u5E76\u67D0\u4E2A\u5DF2\u9A8C\u8BC1\u7684 worktree \u5206\u652F\u5230\u4E3B worktree").requiredOption("-b, --branch <branchName>", "\u8981\u5408\u5E76\u7684\u5206\u652F\u540D").option("-m, --message <message>", "\u63D0\u4EA4\u4FE1\u606F\uFF08\u5DE5\u4F5C\u533A\u6709\u4FEE\u6539\u65F6\u5FC5\u586B\uFF09").action(async (options) => {
|
|
@@ -1154,7 +1137,7 @@ async function handleMerge(options) {
|
|
|
1154
1137
|
validateMainWorktree();
|
|
1155
1138
|
const mainWorktreePath = getGitTopLevel();
|
|
1156
1139
|
const projectDir = getProjectWorktreeDir();
|
|
1157
|
-
const targetWorktreePath =
|
|
1140
|
+
const targetWorktreePath = join5(projectDir, options.branch);
|
|
1158
1141
|
logger.info(`merge \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${options.branch}\uFF0C\u63D0\u4EA4\u4FE1\u606F: ${options.message ?? "(\u672A\u63D0\u4F9B)"}`);
|
|
1159
1142
|
if (!existsSync7(targetWorktreePath)) {
|
|
1160
1143
|
throw new ClawtError(MESSAGES.WORKTREE_NOT_FOUND(options.branch));
|
package/docs/spec.md
CHANGED
|
@@ -522,8 +522,7 @@ clawt remove [options]
|
|
|
522
522
|
| 参数 | 说明 |
|
|
523
523
|
| --------- | ---------------------------------------------------------- |
|
|
524
524
|
| `--all` | 移除当前项目 (`~/.clawt/worktrees/<project>/`) 下所有 worktree |
|
|
525
|
-
| `-b <branchName>` |
|
|
526
|
-
| `-b <branchName> -i <index>` | 移除指定 branchName 的某一个 worktree (如 `branchName-2`) |
|
|
525
|
+
| `-b <branchName>` | 移除匹配 branchName 或 branchName-* 的 worktree |
|
|
527
526
|
|
|
528
527
|
**三种移除粒度:**
|
|
529
528
|
|
|
@@ -531,7 +530,7 @@ clawt remove [options]
|
|
|
531
530
|
| ---- | ---------------------------------------- | ------------------------------------------------------------- |
|
|
532
531
|
| 全部 | `clawt remove --all` | `~/.clawt/worktrees/<project>/` 下所有 worktree |
|
|
533
532
|
| 分支 | `clawt remove -b feature-scheme` | `~/.clawt/worktrees/<project>/feature-scheme-*` 的所有 worktree |
|
|
534
|
-
| 单个 | `clawt remove -b feature-scheme
|
|
533
|
+
| 单个 | `clawt remove -b feature-scheme-2` | 仅移除 `feature-scheme-2` 对应的 worktree(完整分支名精确匹配) |
|
|
535
534
|
|
|
536
535
|
**运行流程:**
|
|
537
536
|
|
package/package.json
CHANGED
package/src/commands/remove.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { existsSync, readdirSync } from 'node:fs';
|
|
4
2
|
import { logger } from '../logger/index.js';
|
|
5
3
|
import { ClawtError } from '../errors/index.js';
|
|
6
4
|
import { MESSAGES } from '../constants/index.js';
|
|
@@ -29,8 +27,7 @@ export function registerRemoveCommand(program: Command): void {
|
|
|
29
27
|
.command('remove')
|
|
30
28
|
.description('移除 worktree(支持单个/批量/全部)')
|
|
31
29
|
.option('--all', '移除当前项目下所有 worktree')
|
|
32
|
-
.option('-b, --branch <branchName>', '
|
|
33
|
-
.option('-i, --index <index>', '指定索引(配合 -b 使用)')
|
|
30
|
+
.option('-b, --branch <branchName>', '指定分支名(完整分支名精确匹配)')
|
|
34
31
|
.action(async (options: RemoveOptions) => {
|
|
35
32
|
await handleRemove(options);
|
|
36
33
|
});
|
|
@@ -42,7 +39,6 @@ export function registerRemoveCommand(program: Command): void {
|
|
|
42
39
|
* @returns {Array<{path: string, branch: string}>} 待移除的 worktree 列表
|
|
43
40
|
*/
|
|
44
41
|
function resolveWorktreesToRemove(options: RemoveOptions): Array<{ path: string; branch: string }> {
|
|
45
|
-
const projectDir = getProjectWorktreeDir();
|
|
46
42
|
const allWorktrees = getProjectWorktrees();
|
|
47
43
|
|
|
48
44
|
if (options.all) {
|
|
@@ -53,23 +49,12 @@ function resolveWorktreesToRemove(options: RemoveOptions): Array<{ path: string;
|
|
|
53
49
|
throw new ClawtError('请指定 --all 或 -b <branchName> 参数');
|
|
54
50
|
}
|
|
55
51
|
|
|
56
|
-
if (options.index !== undefined) {
|
|
57
|
-
// 单个移除:branchName-<index>
|
|
58
|
-
const targetName = `${options.branch}-${options.index}`;
|
|
59
|
-
const targetPath = join(projectDir, targetName);
|
|
60
|
-
const found = allWorktrees.find((wt) => wt.path === targetPath);
|
|
61
|
-
if (!found) {
|
|
62
|
-
throw new ClawtError(MESSAGES.WORKTREE_NOT_FOUND(targetName));
|
|
63
|
-
}
|
|
64
|
-
return [found];
|
|
65
|
-
}
|
|
66
|
-
|
|
67
52
|
// 分支级移除:匹配 branchName 或 branchName-*
|
|
68
53
|
const matched = allWorktrees.filter(
|
|
69
54
|
(wt) => wt.branch === options.branch || wt.branch.startsWith(`${options.branch}-`),
|
|
70
55
|
);
|
|
71
56
|
if (matched.length === 0) {
|
|
72
|
-
throw new ClawtError(MESSAGES.WORKTREE_NOT_FOUND(options.branch
|
|
57
|
+
throw new ClawtError(MESSAGES.WORKTREE_NOT_FOUND(options.branch));
|
|
73
58
|
}
|
|
74
59
|
return matched;
|
|
75
60
|
}
|
package/src/types/command.ts
CHANGED
package/src/types/worktree.ts
CHANGED
|
@@ -10,9 +10,9 @@ export interface WorktreeInfo {
|
|
|
10
10
|
export interface WorktreeStatus {
|
|
11
11
|
/** 相对于主分支的新增提交数 */
|
|
12
12
|
commitCount: number;
|
|
13
|
-
/**
|
|
13
|
+
/** 工作区和暂存区的新增行数 */
|
|
14
14
|
insertions: number;
|
|
15
|
-
/**
|
|
15
|
+
/** 工作区和暂存区的删除行数 */
|
|
16
16
|
deletions: number;
|
|
17
17
|
/** 工作区是否有未提交修改 */
|
|
18
18
|
hasDirtyFiles: boolean;
|
package/src/utils/git.ts
CHANGED
|
@@ -286,25 +286,14 @@ function parseShortStat(output: string): { insertions: number; deletions: number
|
|
|
286
286
|
}
|
|
287
287
|
|
|
288
288
|
/**
|
|
289
|
-
*
|
|
290
|
-
* @param {string} branchName - 目标分支名
|
|
289
|
+
* 获取 worktree 中工作区和暂存区的变更统计
|
|
291
290
|
* @param {string} worktreePath - worktree 目录路径
|
|
292
|
-
* @
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
const committed = parseShortStat(committedOutput);
|
|
299
|
-
|
|
300
|
-
// 未提交的变更(在 worktree 内执行)
|
|
301
|
-
const uncommittedOutput = execCommand('git diff --shortstat HEAD', { cwd: worktreePath });
|
|
302
|
-
const uncommitted = parseShortStat(uncommittedOutput);
|
|
303
|
-
|
|
304
|
-
return {
|
|
305
|
-
insertions: committed.insertions + uncommitted.insertions,
|
|
306
|
-
deletions: committed.deletions + uncommitted.deletions,
|
|
307
|
-
};
|
|
291
|
+
* @returns {{ insertions: number; deletions: number }} 新增和删除行数
|
|
292
|
+
*/
|
|
293
|
+
export function getDiffStat(worktreePath: string): { insertions: number; deletions: number } {
|
|
294
|
+
// 工作区和暂存区相对于 HEAD 的变更
|
|
295
|
+
const output = execCommand('git diff --shortstat HEAD', { cwd: worktreePath });
|
|
296
|
+
return parseShortStat(output);
|
|
308
297
|
}
|
|
309
298
|
|
|
310
299
|
/**
|
package/src/utils/worktree.ts
CHANGED
|
@@ -115,7 +115,7 @@ export function cleanupWorktrees(worktrees: WorktreeInfo[]): void {
|
|
|
115
115
|
export function getWorktreeStatus(worktree: WorktreeInfo): WorktreeStatus | null {
|
|
116
116
|
try {
|
|
117
117
|
const commitCount = getCommitCountAhead(worktree.branch);
|
|
118
|
-
const { insertions, deletions } = getDiffStat(worktree.
|
|
118
|
+
const { insertions, deletions } = getDiffStat(worktree.path);
|
|
119
119
|
const hasDirtyFiles = !isWorkingDirClean(worktree.path);
|
|
120
120
|
|
|
121
121
|
return { commitCount, insertions, deletions, hasDirtyFiles };
|