clawt 3.8.4 → 3.8.6
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 +16 -15
- package/dist/postinstall.js +2 -0
- package/docs/merge.md +1 -1
- package/docs/sync.md +1 -1
- package/package.json +1 -1
- package/src/commands/home.ts +1 -1
- package/src/commands/merge.ts +2 -2
- package/src/commands/sync.ts +6 -4
- package/src/constants/git.ts +1 -1
- package/src/constants/index.ts +1 -1
- package/src/constants/messages/common.ts +2 -0
- package/src/utils/git-core.ts +11 -1
- package/src/utils/index.ts +1 -0
- package/src/utils/validation.ts +11 -13
- package/tests/unit/commands/merge.test.ts +1 -1
- package/tests/unit/commands/sync.test.ts +7 -2
- package/tests/unit/constants/git.test.ts +7 -7
- package/tests/unit/constants/messages.test.ts +1 -0
- package/tests/unit/utils/git.test.ts +18 -0
- package/tests/unit/utils/validation.test.ts +13 -6
package/dist/index.js
CHANGED
|
@@ -26,6 +26,8 @@ var VALIDATE_BRANCH_PREFIX = "clawt-validate-";
|
|
|
26
26
|
var COMMON_MESSAGES = {
|
|
27
27
|
/** 不在主 worktree 根目录 */
|
|
28
28
|
NOT_MAIN_WORKTREE: "\u8BF7\u5728\u4E3B worktree \u7684\u6839\u76EE\u5F55\u4E0B\u6267\u884C clawt",
|
|
29
|
+
/** 不在 git 仓库中 */
|
|
30
|
+
NOT_GIT_REPO: "\u5F53\u524D\u76EE\u5F55\u4E0D\u662F git \u4ED3\u5E93\uFF0C\u8BF7\u5148\u6267\u884C git init \u5E76\u63D0\u4EA4\u540E\uFF0C\u518D\u6267\u884C clawt init \u521D\u59CB\u5316\u9879\u76EE",
|
|
29
31
|
/** Git 未安装 */
|
|
30
32
|
GIT_NOT_INSTALLED: "Git \u672A\u5B89\u88C5\u6216\u4E0D\u5728 PATH \u4E2D\uFF0C\u8BF7\u5148\u5B89\u88C5 Git",
|
|
31
33
|
/** Claude Code CLI 未安装 */
|
|
@@ -794,7 +796,7 @@ var PROJECT_DEFAULT_CONFIG = deriveDefaultConfig2(PROJECT_CONFIG_DEFINITIONS);
|
|
|
794
796
|
var PROJECT_CONFIG_DESCRIPTIONS = deriveConfigDescriptions2(PROJECT_CONFIG_DEFINITIONS);
|
|
795
797
|
|
|
796
798
|
// src/constants/git.ts
|
|
797
|
-
var
|
|
799
|
+
var AUTO_SAVE_COMMIT_MESSAGE_PREFIX = "clawt: auto-save before merging";
|
|
798
800
|
var EXEC_MAX_BUFFER = 200 * 1024 * 1024;
|
|
799
801
|
|
|
800
802
|
// src/constants/logger.ts
|
|
@@ -1288,6 +1290,9 @@ function gitAddFiles(files, cwd) {
|
|
|
1288
1290
|
function gitMergeContinue(cwd) {
|
|
1289
1291
|
execCommand("GIT_EDITOR=true git merge --continue", { cwd });
|
|
1290
1292
|
}
|
|
1293
|
+
function buildAutoSaveCommitMessage(mainBranch, branch) {
|
|
1294
|
+
return `${AUTO_SAVE_COMMIT_MESSAGE_PREFIX} ${mainBranch} into ${branch}`;
|
|
1295
|
+
}
|
|
1291
1296
|
|
|
1292
1297
|
// src/utils/git-branch.ts
|
|
1293
1298
|
function checkBranchExists(branchName, cwd) {
|
|
@@ -1735,15 +1740,11 @@ async function ensureOnMainWorkBranch(cwd) {
|
|
|
1735
1740
|
|
|
1736
1741
|
// src/utils/validation.ts
|
|
1737
1742
|
function validateMainWorktree() {
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
} catch (error) {
|
|
1744
|
-
if (error instanceof ClawtError) {
|
|
1745
|
-
throw error;
|
|
1746
|
-
}
|
|
1743
|
+
if (!isInsideGitRepo()) {
|
|
1744
|
+
throw new ClawtError(MESSAGES.NOT_GIT_REPO);
|
|
1745
|
+
}
|
|
1746
|
+
const gitCommonDir = getGitCommonDir();
|
|
1747
|
+
if (gitCommonDir !== ".git") {
|
|
1747
1748
|
throw new ClawtError(MESSAGES.NOT_MAIN_WORKTREE);
|
|
1748
1749
|
}
|
|
1749
1750
|
}
|
|
@@ -4814,9 +4815,9 @@ var SYNC_RESOLVE_MESSAGES = {
|
|
|
4814
4815
|
multipleMatches: MESSAGES.SYNC_MULTIPLE_MATCHES,
|
|
4815
4816
|
noMatch: MESSAGES.SYNC_NO_MATCH
|
|
4816
4817
|
};
|
|
4817
|
-
function autoSaveChanges(worktreePath, branch) {
|
|
4818
|
+
function autoSaveChanges(worktreePath, branch, mainBranch) {
|
|
4818
4819
|
gitAddAll(worktreePath);
|
|
4819
|
-
gitCommit(
|
|
4820
|
+
gitCommit(buildAutoSaveCommitMessage(mainBranch, branch), worktreePath);
|
|
4820
4821
|
printInfo(MESSAGES.SYNC_AUTO_COMMITTED(branch));
|
|
4821
4822
|
logger.info(`\u5DF2\u81EA\u52A8\u4FDD\u5B58 ${branch} \u5206\u652F\u7684\u672A\u63D0\u4EA4\u53D8\u66F4`);
|
|
4822
4823
|
}
|
|
@@ -4835,7 +4836,7 @@ async function executeSyncForBranch(targetWorktreePath, branch) {
|
|
|
4835
4836
|
const mainWorktreePath = getGitTopLevel();
|
|
4836
4837
|
const mainBranch = getMainWorkBranch();
|
|
4837
4838
|
if (!isWorkingDirClean(targetWorktreePath)) {
|
|
4838
|
-
autoSaveChanges(targetWorktreePath, branch);
|
|
4839
|
+
autoSaveChanges(targetWorktreePath, branch, mainBranch);
|
|
4839
4840
|
}
|
|
4840
4841
|
printInfo(MESSAGES.SYNC_MERGING(branch, mainBranch));
|
|
4841
4842
|
const hasConflict = mergeMainBranch(targetWorktreePath, mainBranch);
|
|
@@ -5085,7 +5086,7 @@ function commitSquash(message, worktreePath, branchName) {
|
|
|
5085
5086
|
printSuccess(MESSAGES.MERGE_SQUASH_COMMITTED(branchName));
|
|
5086
5087
|
}
|
|
5087
5088
|
async function handleSquashIfNeeded(targetWorktreePath, mainWorktreePath, branchName, commitMessage) {
|
|
5088
|
-
if (!hasCommitWithMessage(branchName,
|
|
5089
|
+
if (!hasCommitWithMessage(branchName, AUTO_SAVE_COMMIT_MESSAGE_PREFIX, mainWorktreePath)) {
|
|
5089
5090
|
return false;
|
|
5090
5091
|
}
|
|
5091
5092
|
const shouldSquash = await confirmAction(MESSAGES.MERGE_SQUASH_PROMPT, false);
|
|
@@ -6044,7 +6045,7 @@ function registerHomeCommand(program2) {
|
|
|
6044
6045
|
}
|
|
6045
6046
|
async function handleHome() {
|
|
6046
6047
|
if (!isInsideGitRepo()) {
|
|
6047
|
-
printError(MESSAGES.
|
|
6048
|
+
printError(MESSAGES.NOT_GIT_REPO);
|
|
6048
6049
|
return;
|
|
6049
6050
|
}
|
|
6050
6051
|
if (!isMainWorktree()) {
|
package/dist/postinstall.js
CHANGED
|
@@ -17,6 +17,8 @@ var PROJECTS_CONFIG_DIR = join(CLAWT_HOME, "projects");
|
|
|
17
17
|
var COMMON_MESSAGES = {
|
|
18
18
|
/** 不在主 worktree 根目录 */
|
|
19
19
|
NOT_MAIN_WORKTREE: "\u8BF7\u5728\u4E3B worktree \u7684\u6839\u76EE\u5F55\u4E0B\u6267\u884C clawt",
|
|
20
|
+
/** 不在 git 仓库中 */
|
|
21
|
+
NOT_GIT_REPO: "\u5F53\u524D\u76EE\u5F55\u4E0D\u662F git \u4ED3\u5E93\uFF0C\u8BF7\u5148\u6267\u884C git init \u5E76\u63D0\u4EA4\u540E\uFF0C\u518D\u6267\u884C clawt init \u521D\u59CB\u5316\u9879\u76EE",
|
|
20
22
|
/** Git 未安装 */
|
|
21
23
|
GIT_NOT_INSTALLED: "Git \u672A\u5B89\u88C5\u6216\u4E0D\u5728 PATH \u4E2D\uFF0C\u8BF7\u5148\u5B89\u88C5 Git",
|
|
22
24
|
/** Claude Code CLI 未安装 */
|
package/docs/merge.md
CHANGED
|
@@ -42,7 +42,7 @@ clawt merge [-m <commitMessage>]
|
|
|
42
42
|
- 提示 `主 worktree 有未提交的更改,请先处理`,退出
|
|
43
43
|
- 无更改 → 继续
|
|
44
44
|
6. **Squash 检测与执行(auto-save 临时提交压缩)**
|
|
45
|
-
- 通过 `git log HEAD..<branchName> --format=%s` 检查目标分支是否存在以 `
|
|
45
|
+
- 通过 `git log HEAD..<branchName> --format=%s` 检查目标分支是否存在以 `AUTO_SAVE_COMMIT_MESSAGE_PREFIX`(`clawt: auto-save before merging`)为前缀的 commit
|
|
46
46
|
- **不存在** → 跳过,进入步骤 7
|
|
47
47
|
- **存在** → 提示用户是否将所有提交压缩为一个:
|
|
48
48
|
```
|
package/docs/sync.md
CHANGED
|
@@ -61,7 +61,7 @@ export interface SyncResult {
|
|
|
61
61
|
|
|
62
62
|
1. **获取主 worktree 路径和主分支名**:通过 `getGitTopLevel()` 获取主 worktree 路径(后续传给 `rebuildValidateBranch`),通过项目级配置 `clawtMainWorkBranch` 获取主工作分支名(不再通过 `getCurrentBranch` 动态获取,因为在新架构下主 worktree 可能处于验证分支上)
|
|
63
63
|
2. **自动保存未提交变更**:检查目标 worktree 是否有未提交修改
|
|
64
|
-
- 有修改 → 自动执行 `git add . && git commit -m "<
|
|
64
|
+
- 有修改 → 自动执行 `git add . && git commit -m "<message>"` 保存变更(commit message 由 `buildAutoSaveCommitMessage(mainBranch, branch)` 函数动态生成,格式为 `clawt: auto-save before merging {mainBranch} into {branch}`,前缀部分由常量 `AUTO_SAVE_COMMIT_MESSAGE_PREFIX` 定义,值为 `clawt: auto-save before merging`,同时用于 merge 命令的 squash 检测)
|
|
65
65
|
- 无修改 → 跳过
|
|
66
66
|
3. **在目标 worktree 中合并主分支**:
|
|
67
67
|
```bash
|
package/package.json
CHANGED
package/src/commands/home.ts
CHANGED
|
@@ -37,7 +37,7 @@ export function registerHomeCommand(program: Command): void {
|
|
|
37
37
|
async function handleHome(): Promise<void> {
|
|
38
38
|
// 场景 1:不在 git 仓库中,直接报错
|
|
39
39
|
if (!isInsideGitRepo()) {
|
|
40
|
-
printError(MESSAGES.
|
|
40
|
+
printError(MESSAGES.NOT_GIT_REPO);
|
|
41
41
|
return;
|
|
42
42
|
}
|
|
43
43
|
|
package/src/commands/merge.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
2
|
import { logger } from '../logger/index.js';
|
|
3
3
|
import { ClawtError } from '../errors/index.js';
|
|
4
|
-
import { MESSAGES,
|
|
4
|
+
import { MESSAGES, AUTO_SAVE_COMMIT_MESSAGE_PREFIX } from '../constants/index.js';
|
|
5
5
|
import type { MergeOptions } from '../types/index.js';
|
|
6
6
|
import { PRE_CHECK_MERGE } from '../constants/index.js';
|
|
7
7
|
import {
|
|
@@ -92,7 +92,7 @@ async function handleSquashIfNeeded(
|
|
|
92
92
|
commitMessage?: string,
|
|
93
93
|
): Promise<boolean> {
|
|
94
94
|
// 检查目标分支是否存在 auto-save commit
|
|
95
|
-
if (!hasCommitWithMessage(branchName,
|
|
95
|
+
if (!hasCommitWithMessage(branchName, AUTO_SAVE_COMMIT_MESSAGE_PREFIX, mainWorktreePath)) {
|
|
96
96
|
return false;
|
|
97
97
|
}
|
|
98
98
|
|
package/src/commands/sync.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
2
|
import { logger } from '../logger/index.js';
|
|
3
3
|
import { ClawtError } from '../errors/index.js';
|
|
4
|
-
import { MESSAGES
|
|
4
|
+
import { MESSAGES } from '../constants/index.js';
|
|
5
5
|
import type { SyncOptions } from '../types/index.js';
|
|
6
6
|
import { PRE_CHECK_SYNC } from '../constants/index.js';
|
|
7
7
|
import {
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
getMainWorkBranch,
|
|
21
21
|
rebuildValidateBranch,
|
|
22
22
|
getValidateBranchName,
|
|
23
|
+
buildAutoSaveCommitMessage,
|
|
23
24
|
} from '../utils/index.js';
|
|
24
25
|
import type { WorktreeResolveMessages } from '../utils/index.js';
|
|
25
26
|
|
|
@@ -49,10 +50,11 @@ const SYNC_RESOLVE_MESSAGES: WorktreeResolveMessages = {
|
|
|
49
50
|
* 自动保存目标 worktree 中的未提交变更
|
|
50
51
|
* @param {string} worktreePath - 目标 worktree 路径
|
|
51
52
|
* @param {string} branch - 分支名
|
|
53
|
+
* @param {string} mainBranch - 主分支名
|
|
52
54
|
*/
|
|
53
|
-
function autoSaveChanges(worktreePath: string, branch: string): void {
|
|
55
|
+
function autoSaveChanges(worktreePath: string, branch: string, mainBranch: string): void {
|
|
54
56
|
gitAddAll(worktreePath);
|
|
55
|
-
gitCommit(
|
|
57
|
+
gitCommit(buildAutoSaveCommitMessage(mainBranch, branch), worktreePath);
|
|
56
58
|
printInfo(MESSAGES.SYNC_AUTO_COMMITTED(branch));
|
|
57
59
|
logger.info(`已自动保存 ${branch} 分支的未提交变更`);
|
|
58
60
|
}
|
|
@@ -99,7 +101,7 @@ export async function executeSyncForBranch(targetWorktreePath: string, branch: s
|
|
|
99
101
|
|
|
100
102
|
// 检查目标 worktree 是否有未提交变更,有则自动保存
|
|
101
103
|
if (!isWorkingDirClean(targetWorktreePath)) {
|
|
102
|
-
autoSaveChanges(targetWorktreePath, branch);
|
|
104
|
+
autoSaveChanges(targetWorktreePath, branch, mainBranch);
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
// 在目标 worktree 中合并主分支
|
package/src/constants/git.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** sync 自动保存的 commit message 前缀,用于检测 auto-save 提交 */
|
|
2
|
-
export const
|
|
2
|
+
export const AUTO_SAVE_COMMIT_MESSAGE_PREFIX = 'clawt: auto-save before merging';
|
|
3
3
|
|
|
4
4
|
/** execSync 最大缓冲区大小(200MB),防止大分支 diff 时触发 ENOBUFS 错误 */
|
|
5
5
|
export const EXEC_MAX_BUFFER = 200 * 1024 * 1024;
|
package/src/constants/index.ts
CHANGED
|
@@ -7,7 +7,7 @@ export { EXIT_CODES } from './exitCodes.js';
|
|
|
7
7
|
export { ENABLE_BRACKETED_PASTE, DISABLE_BRACKETED_PASTE, PASTE_THRESHOLD_MS, VALID_TERMINAL_APPS, ITERM2_APP_PATH } from './terminal.js';
|
|
8
8
|
export { DEFAULT_CONFIG, CONFIG_DESCRIPTIONS, CONFIG_DEFINITIONS, APPEND_SYSTEM_PROMPT } from './config.js';
|
|
9
9
|
export { PROJECT_CONFIG_DEFINITIONS, PROJECT_DEFAULT_CONFIG, PROJECT_CONFIG_DESCRIPTIONS } from './project-config.js';
|
|
10
|
-
export {
|
|
10
|
+
export { AUTO_SAVE_COMMIT_MESSAGE_PREFIX } from './git.js';
|
|
11
11
|
export { DEBUG_LOG_PREFIX, DEBUG_TIMESTAMP_FORMAT } from './logger.js';
|
|
12
12
|
export { UPDATE_CHECK_INTERVAL_MS, NPM_REGISTRY_URL, NPM_REGISTRY_TIMEOUT_MS, PACKAGE_NAME } from './update.js';
|
|
13
13
|
export {
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
export const COMMON_MESSAGES = {
|
|
3
3
|
/** 不在主 worktree 根目录 */
|
|
4
4
|
NOT_MAIN_WORKTREE: '请在主 worktree 的根目录下执行 clawt',
|
|
5
|
+
/** 不在 git 仓库中 */
|
|
6
|
+
NOT_GIT_REPO: '当前目录不是 git 仓库,请先执行 git init 并提交后,再执行 clawt init 初始化项目',
|
|
5
7
|
/** Git 未安装 */
|
|
6
8
|
GIT_NOT_INSTALLED: 'Git 未安装或不在 PATH 中,请先安装 Git',
|
|
7
9
|
/** Claude Code CLI 未安装 */
|
package/src/utils/git-core.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { basename } from 'node:path';
|
|
|
2
2
|
import { execSync, execFileSync } from 'node:child_process';
|
|
3
3
|
import { execCommand, execCommandWithInput } from './shell.js';
|
|
4
4
|
import { logger } from '../logger/index.js';
|
|
5
|
-
import { EXEC_MAX_BUFFER } from '../constants/git.js';
|
|
5
|
+
import { EXEC_MAX_BUFFER, AUTO_SAVE_COMMIT_MESSAGE_PREFIX } from '../constants/git.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* 获取 git common dir(用于判断是否为主 worktree)
|
|
@@ -459,3 +459,13 @@ export function gitMergeContinue(cwd?: string): void {
|
|
|
459
459
|
export function gitMergeAbort(cwd?: string): void {
|
|
460
460
|
execCommand('git merge --abort', { cwd });
|
|
461
461
|
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* 生成完整的 auto-save commit message
|
|
465
|
+
* @param {string} mainBranch - 主分支名(被合并的源分支)
|
|
466
|
+
* @param {string} branch - 目标分支名(合并到的分支)
|
|
467
|
+
* @returns {string} 完整的 commit message
|
|
468
|
+
*/
|
|
469
|
+
export function buildAutoSaveCommitMessage(mainBranch: string, branch: string): string {
|
|
470
|
+
return `${AUTO_SAVE_COMMIT_MESSAGE_PREFIX} ${mainBranch} into ${branch}`;
|
|
471
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -56,6 +56,7 @@ export {
|
|
|
56
56
|
gitAddFiles,
|
|
57
57
|
gitMergeContinue,
|
|
58
58
|
gitMergeAbort,
|
|
59
|
+
buildAutoSaveCommitMessage,
|
|
59
60
|
} from './git.js';
|
|
60
61
|
export { sanitizeBranchName, generateBranchNames, validateBranchesNotExist } from './branch.js';
|
|
61
62
|
export { validateMainWorktree, validateGitInstalled, validateClaudeCodeInstalled, validateHeadExists, validateWorkingDirClean, runPreChecks } from './validation.js';
|
package/src/utils/validation.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { MESSAGES } from '../constants/index.js';
|
|
2
2
|
import { ClawtError } from '../errors/index.js';
|
|
3
3
|
import { execCommand } from './shell.js';
|
|
4
|
-
import { getGitCommonDir, isWorkingDirClean } from './git.js';
|
|
4
|
+
import { getGitCommonDir, isInsideGitRepo, isWorkingDirClean } from './git.js';
|
|
5
5
|
import { requireProjectConfig, guardMainWorkBranchExists } from './project-config.js';
|
|
6
6
|
import { ensureOnMainWorkBranch } from './validate-branch.js';
|
|
7
7
|
|
|
@@ -25,20 +25,18 @@ export interface PreCheckOptions {
|
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* 校验当前目录是否为主 worktree 的根目录
|
|
28
|
-
*
|
|
29
|
-
* @throws {ClawtError}
|
|
28
|
+
* 先判断是否在 git 仓库中,再判断是否为主 worktree
|
|
29
|
+
* @throws {ClawtError} 不在 git 仓库时抛出 NOT_GIT_REPO
|
|
30
|
+
* @throws {ClawtError} 不在主 worktree 根目录时抛出 NOT_MAIN_WORKTREE
|
|
30
31
|
*/
|
|
31
32
|
export function validateMainWorktree(): void {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
throw error;
|
|
40
|
-
}
|
|
41
|
-
// git 命令执行失败,可能不在 git 仓库中
|
|
33
|
+
// 先检查是否在 git 仓库中
|
|
34
|
+
if (!isInsideGitRepo()) {
|
|
35
|
+
throw new ClawtError(MESSAGES.NOT_GIT_REPO);
|
|
36
|
+
}
|
|
37
|
+
// 再检查是否在主 worktree 根目录
|
|
38
|
+
const gitCommonDir = getGitCommonDir();
|
|
39
|
+
if (gitCommonDir !== '.git') {
|
|
42
40
|
throw new ClawtError(MESSAGES.NOT_MAIN_WORKTREE);
|
|
43
41
|
}
|
|
44
42
|
}
|
|
@@ -41,7 +41,7 @@ vi.mock('../../../src/constants/index.js', async (importOriginal) => {
|
|
|
41
41
|
MERGE_SUCCESS_NO_MESSAGE: (branch: string, autoPullPush: boolean) => `合并成功: ${branch}`,
|
|
42
42
|
WORKTREE_CLEANED: (branch: string) => `已清理: ${branch}`,
|
|
43
43
|
},
|
|
44
|
-
|
|
44
|
+
AUTO_SAVE_COMMIT_MESSAGE_PREFIX: 'clawt: auto-save before merging',
|
|
45
45
|
};
|
|
46
46
|
});
|
|
47
47
|
|
|
@@ -30,7 +30,7 @@ vi.mock('../../../src/constants/index.js', async (importOriginal) => {
|
|
|
30
30
|
SYNC_SUCCESS: (branch: string, mainBranch: string) => `✓ 已将 ${mainBranch} 同步到 ${branch}`,
|
|
31
31
|
SYNC_VALIDATE_BRANCH_REBUILT: (validateBranch: string) => `验证分支 ${validateBranch} 已重建`,
|
|
32
32
|
},
|
|
33
|
-
|
|
33
|
+
AUTO_SAVE_COMMIT_MESSAGE_PREFIX: 'clawt: auto-save before merging',
|
|
34
34
|
};
|
|
35
35
|
});
|
|
36
36
|
|
|
@@ -54,6 +54,8 @@ vi.mock('../../../src/utils/index.js', () => ({
|
|
|
54
54
|
getValidateBranchName: vi.fn((name: string) => `clawt-validate-${name}`),
|
|
55
55
|
guardMainWorkBranch: vi.fn().mockResolvedValue(undefined),
|
|
56
56
|
guardMainWorkBranchExists: vi.fn(),
|
|
57
|
+
buildAutoSaveCommitMessage: (mainBranch: string, branch: string) =>
|
|
58
|
+
`clawt: auto-save before merging ${mainBranch} into ${branch}`,
|
|
57
59
|
}));
|
|
58
60
|
|
|
59
61
|
import { registerSyncCommand } from '../../../src/commands/sync.js';
|
|
@@ -135,7 +137,10 @@ describe('handleSync', () => {
|
|
|
135
137
|
await program.parseAsync(['sync', '-b', 'feature'], { from: 'user' });
|
|
136
138
|
|
|
137
139
|
expect(mockedGitAddAll).toHaveBeenCalledWith('/path/feature');
|
|
138
|
-
expect(mockedGitCommit).toHaveBeenCalledWith(
|
|
140
|
+
expect(mockedGitCommit).toHaveBeenCalledWith(
|
|
141
|
+
'clawt: auto-save before merging main into feature',
|
|
142
|
+
'/path/feature',
|
|
143
|
+
);
|
|
139
144
|
expect(mockedGitMerge).toHaveBeenCalled();
|
|
140
145
|
});
|
|
141
146
|
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import { AUTO_SAVE_COMMIT_MESSAGE_PREFIX } from '../../../src/constants/git.js';
|
|
3
3
|
|
|
4
|
-
describe('
|
|
5
|
-
it('
|
|
6
|
-
expect(
|
|
4
|
+
describe('AUTO_SAVE_COMMIT_MESSAGE_PREFIX', () => {
|
|
5
|
+
it('值为预期的前缀', () => {
|
|
6
|
+
expect(AUTO_SAVE_COMMIT_MESSAGE_PREFIX).toBe('clawt: auto-save before merging');
|
|
7
7
|
});
|
|
8
8
|
|
|
9
9
|
it('是非空字符串', () => {
|
|
10
|
-
expect(typeof
|
|
11
|
-
expect(
|
|
10
|
+
expect(typeof AUTO_SAVE_COMMIT_MESSAGE_PREFIX).toBe('string');
|
|
11
|
+
expect(AUTO_SAVE_COMMIT_MESSAGE_PREFIX.length).toBeGreaterThan(0);
|
|
12
12
|
});
|
|
13
13
|
|
|
14
14
|
it('符合 conventional commit 格式', () => {
|
|
15
|
-
expect(
|
|
15
|
+
expect(AUTO_SAVE_COMMIT_MESSAGE_PREFIX).toMatch(/^[a-z]+:\s.+$/);
|
|
16
16
|
});
|
|
17
17
|
});
|
|
@@ -64,6 +64,7 @@ import {
|
|
|
64
64
|
gitDiffTree,
|
|
65
65
|
gitApplyCachedCheck,
|
|
66
66
|
getBranchCreatedAt,
|
|
67
|
+
buildAutoSaveCommitMessage,
|
|
67
68
|
} from '../../../src/utils/git.js';
|
|
68
69
|
|
|
69
70
|
const mockedExecCommand = vi.mocked(execCommand);
|
|
@@ -610,3 +611,20 @@ describe('getBranchCreatedAt', () => {
|
|
|
610
611
|
);
|
|
611
612
|
});
|
|
612
613
|
});
|
|
614
|
+
|
|
615
|
+
describe('buildAutoSaveCommitMessage', () => {
|
|
616
|
+
it('生成包含分支信息的完整 commit message', () => {
|
|
617
|
+
const result = buildAutoSaveCommitMessage('main', 'feature/login');
|
|
618
|
+
expect(result).toBe('clawt: auto-save before merging main into feature/login');
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
it('生成的 message 以前缀常量开头', () => {
|
|
622
|
+
const result = buildAutoSaveCommitMessage('main', 'feat-xyz');
|
|
623
|
+
expect(result.startsWith('clawt: auto-save before merging')).toBe(true);
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
it('符合 conventional commit 格式', () => {
|
|
627
|
+
const result = buildAutoSaveCommitMessage('develop', 'hotfix');
|
|
628
|
+
expect(result).toMatch(/^[a-z]+:\s.+$/);
|
|
629
|
+
});
|
|
630
|
+
});
|
|
@@ -5,9 +5,10 @@ vi.mock('../../../src/utils/shell.js', () => ({
|
|
|
5
5
|
execCommand: vi.fn(),
|
|
6
6
|
}));
|
|
7
7
|
|
|
8
|
-
// mock git(validateMainWorktree 依赖 getGitCommonDir)
|
|
8
|
+
// mock git(validateMainWorktree 依赖 getGitCommonDir 和 isInsideGitRepo)
|
|
9
9
|
vi.mock('../../../src/utils/git.js', () => ({
|
|
10
10
|
getGitCommonDir: vi.fn(),
|
|
11
|
+
isInsideGitRepo: vi.fn(),
|
|
11
12
|
}));
|
|
12
13
|
|
|
13
14
|
// mock project-config(runPreChecks 依赖 requireProjectConfig 和 guardMainWorkBranchExists)
|
|
@@ -22,30 +23,35 @@ vi.mock('../../../src/logger/index.js', () => ({
|
|
|
22
23
|
}));
|
|
23
24
|
|
|
24
25
|
import { execCommand } from '../../../src/utils/shell.js';
|
|
25
|
-
import { getGitCommonDir } from '../../../src/utils/git.js';
|
|
26
|
+
import { getGitCommonDir, isInsideGitRepo } from '../../../src/utils/git.js';
|
|
26
27
|
import { validateMainWorktree, validateGitInstalled, validateClaudeCodeInstalled, validateHeadExists, runPreChecks } from '../../../src/utils/validation.js';
|
|
27
28
|
import { requireProjectConfig, guardMainWorkBranchExists } from '../../../src/utils/project-config.js';
|
|
28
29
|
import { ClawtError } from '../../../src/errors/index.js';
|
|
29
30
|
|
|
30
31
|
const mockedExecCommand = vi.mocked(execCommand);
|
|
31
32
|
const mockedGetGitCommonDir = vi.mocked(getGitCommonDir);
|
|
33
|
+
const mockedIsInsideGitRepo = vi.mocked(isInsideGitRepo);
|
|
32
34
|
const mockedRequireProjectConfig = vi.mocked(requireProjectConfig);
|
|
33
35
|
const mockedGuardMainWorkBranchExists = vi.mocked(guardMainWorkBranchExists);
|
|
34
36
|
|
|
35
37
|
describe('validateMainWorktree', () => {
|
|
36
|
-
it('
|
|
38
|
+
it('在主 worktree 根目录时正常通过', () => {
|
|
39
|
+
mockedIsInsideGitRepo.mockReturnValue(true);
|
|
37
40
|
mockedGetGitCommonDir.mockReturnValue('.git');
|
|
38
41
|
expect(() => validateMainWorktree()).not.toThrow();
|
|
39
42
|
});
|
|
40
43
|
|
|
41
|
-
it('
|
|
44
|
+
it('在子 worktree 中时抛出 NOT_MAIN_WORKTREE 错误', () => {
|
|
45
|
+
mockedIsInsideGitRepo.mockReturnValue(true);
|
|
42
46
|
mockedGetGitCommonDir.mockReturnValue('/path/to/.git');
|
|
43
47
|
expect(() => validateMainWorktree()).toThrow(ClawtError);
|
|
48
|
+
expect(() => validateMainWorktree()).toThrow('请在主 worktree 的根目录下执行 clawt');
|
|
44
49
|
});
|
|
45
50
|
|
|
46
|
-
it('
|
|
47
|
-
|
|
51
|
+
it('不在 git 仓库中时抛出 NOT_GIT_REPO 错误', () => {
|
|
52
|
+
mockedIsInsideGitRepo.mockReturnValue(false);
|
|
48
53
|
expect(() => validateMainWorktree()).toThrow(ClawtError);
|
|
54
|
+
expect(() => validateMainWorktree()).toThrow('当前目录不是 git 仓库');
|
|
49
55
|
});
|
|
50
56
|
});
|
|
51
57
|
|
|
@@ -87,6 +93,7 @@ describe('validateHeadExists', () => {
|
|
|
87
93
|
|
|
88
94
|
describe('runPreChecks', () => {
|
|
89
95
|
it('按选项组合调用对应校验', () => {
|
|
96
|
+
mockedIsInsideGitRepo.mockReturnValue(true);
|
|
90
97
|
mockedGetGitCommonDir.mockReturnValue('.git');
|
|
91
98
|
mockedExecCommand.mockReturnValue('abc1234');
|
|
92
99
|
mockedRequireProjectConfig.mockReturnValue({ clawtMainWorkBranch: 'main' });
|