- 桌面应用
- 开发工具
【免费下载链接】MooTool
Handy tool set for developers
MooTool 是一个面向开发者的跨平台工具箱,其 Compose 桌面版(next-compose)正在将 Electron 版的 Vault Git 版本管理能力逐项迁移并对齐。electron-next-vaultGitService-vitest.md 正是这一对账工作的核心登记表,逐条列出 ElectronvaultGitService.integration.test.ts覆盖的 Git 行为及其在 Compose 侧GitEngineTest等测试中的对应验证。本文将以这份登记表为骨架,结合 GitEngine.kt 的引擎实现与两侧测试源码,讲清楚 Vault Git 的初始化、提交、冲突处理、检查点、锁恢复、远端管理与并发安全等机制,帮助你理解这套「以测试对齐替代人工比对」的跨实现迁移方法,以及每项行为背后的工程细节。
一、这份对照登记表是什么
登记表的结构非常简单:每一行描述一个「caseId」,从三个维度记录一个行为点的对齐情况:
| 维度 | 说明 |
|---|---|
| sourceProduct | 行为来源产品,绝大多数为 MooTool Next Electron |
| sourceFile | 源实现/测试所在文件,主要是next/src/shared/vaultGitService.integration.test.ts |
| Compose 验证 | Compose 侧对应的测试类与方法,附带 DIFF 文档编号 |
登记表覆盖约 35 个行为点,可以归类为以下几组:
- 基础生命周期:
init、commit、history、diff(含提交级与工作区级); - 冲突处理:
pull导致分叉合并冲突、merge/rebase的resolve、abort、continue; - 操作守卫(Guard):merge/rebase 期间禁止 commit/pull/push、未解决冲突禁止 continue/commit/pull/push;
- 自动化检查点(automatic checkpoint):首次使用自动初始化、配置远端后自动 push、冲突期跳过检查点;
- 健壮性与安全:非安全路径拒绝、嵌套 Vault 不被父仓库吸收、
index.lock陈旧锁恢复、并发提交序列化、Unicode 路径、提交说明截断 300 字符、远端 URL 校验与空 URL 移除; - fetch/push 语义:
fetch --prune、分叉后的 ahead/behind 报告、非快进 push 拒绝; - UI 呈现:
VaultGitDialog冲突行「使用本地/远端」Tab 帧、顶栏分支图标与操作钮的 Lucide 对齐。
每个 Compose 验证项都挂着一个 DIFF 编号(如 DIFF-442),DIFF 文档是「行为差异描述 + 修复方案 + 验证结果」的完整记录,两者配合构成了可追溯的对齐证据链。
二、核心引擎:GitEngine 的职责划分
Compose 侧的对齐验证全部落在GitEngine这个单例对象上,源码位于 GitEngine.kt。它的设计值得先看清楚:
2.1 与 Electron VaultGitService 的镜像关系
Electron 侧实现是VaultGitService(next/electron/main/vaultGitService.ts),对外暴露三类接口:
status()/history()/diff():只读查询;action(input):以{ action, path, message, remote, strategy }为参数的统一动作入口,内部 switch 到init、configure-remote、commit、fetch、pull、push、discard、abort-merge、resolve-conflict、continue-operation;automaticCheckpoint(message):检查点专用流程。
Compose 侧GitEngine则以顶层函数形式提供status、init、commit、history、diff、fileDiffs、setRemote、fetch、pull、push、discard、abortMerge、resolveConflict、continueOperation、automaticCheckpoint等方法。两条实现的命令参数几乎一致——例如status都用git status --porcelain=v1 -z --branch --untracked-files=all,fetch 都用git fetch --prune origin,push 都用git push -u origin HEAD,pull 都用git pull --no-rebase origin。这保证了「同一行为、同一命令、同一预期」的可比性。
2.2 数据模型与关键常量
GitEngine定义了清晰的数据结构(见 GitEngine.kt 顶部):
GitStatus:available、repository、version、branch、remote、ahead、behind、changes、conflicts、merging、operation(取值为merge/rebase/none);GitChange:path、originalPath、status、conflict(conflict由UU/AA/DD等状态判定);GitCommitInfo:hash、shortHash、author、date、message;GitFileDiff:path、originalPath、status、before、after、preview(Text/Binary/TooLarge三态);GitActionResult:统一的success+message返回。
关键常量定义了行为边界:普通命令超时TIMEOUT_MS = 30_000L,远端操作超时REMOTE_TIMEOUT_MS = 120_000L,diff 预览最大512 * 1024字节,提交说明截断上限300字符,陈旧index.lock判定阈值5分钟。defaultGitignore默认为.DS_Store、.idea/、.vscode/、*.tmp、.migrated-from-db。
2.3 执行器:进程级隔离与超时控制
GitEngine.run是命令执行的统一出口(GitEngine.kt),它的职责包括:
- 设置
GIT_TERMINAL_PROMPT=0禁止交互式提示,设置GIT_OPTIONAL_LOCKS=0; isolateConfig = true时通过GIT_CONFIG_GLOBAL/GIT_CONFIG_SYSTEM指向空设备,避免用户全局 Git 配置影响测试结果(这也是所有对齐测试统一传isolateConfig = true的原因);- token 非空时写入临时
GIT_ASKPASS脚本,通过环境变量MOOTOOL_COMPOSE_GIT_TOKEN向脚本传值; - 用两个线程异步排空 stdout/stderr,防止管道写满造成死锁;超时后
destroyForcibly()并返回退出码 124。
locked(root)则用ConcurrentHashMap<Path, Any>配合synchronized保证同一 Vault 目录下所有 Git 操作串行执行——这是并发提交对齐用例的直接实现基础。
三、逐项解析:登记表中的核心行为点
下面按登记表的分组,逐项说明行为语义、Electron 测试与 Compose 验证的对应关系,以及源码依据。
3.1 初始化、提交与历史(init / commit / diff / history)
登记表第一行init-commit-diff-history对齐的是 Electron「initializes, reports changes, commits, shows history, and builds a diff」这条完整链路。两端测试都覆盖了以下断言:
- 未初始化时
status().repository === false; init后写入默认.gitignore(必须包含.DS_Store),工作区出现.DS_Store也不进入变更列表(Compose 对应GitEngineTest.initWritesGitignoreAndIgnoresDsStore);- 未跟踪文件在
status.changes中以??呈现; commit后history首条即为本次提交,author 非空;diff({ commit })返回该提交的文件级前后内容,diff({ path })返回工作区相对 HEAD 的前后内容。
Compose 侧对应的mirrorsElectronVaultGitInitCommitDiffHistory(GitEngineTest.kt)把这条链路完整重演了一遍:提交sample.json两版内容,验证fileDiffs的before/after分别等于旧值与新值,并且新文件的before为空、preview为Text。
实现层面,GitEngine.commit内部会先把提交说明trim().take(300)截断,空说明返回失败,merge/rebase 进行中直接拒绝,无变更返回No changes to commit;随后执行git add --all与git commit -m。提交命令通过identityArgs携带-c user.name、-c user.email、-c commit.gpgsign=false,与 Electron 的ensureIdentity()逻辑对应。
3.2 提交说明 300 字符截断
commit-message-300对应 Electron 的说明截断行为。Compose 的truncatesCommitMessageToThreeHundredCharacters直接传入 320 个m提交,再断言history返回的 message 恰好为 300 字符。实现依据是commitLocked中的message.trim().take(300)(GitEngine.kt)。
3.3 冲突处理:pull、merge、rebase 的完整闭环
登记表围绕冲突操作列出多条:pull-merge-conflict-resolve、rebase-continue、merge-abort/continue。
分叉 pull 产生合并冲突:pullLeavesMergeConflictWhenHistoriesDiverge构造 bare 仓库、upstream 与 local 三方环境:upstream 提交Base后 push,local clone,随后两边各自修改同一文件并提交,此时GitEngine.pull必然失败;status显示operation == "merge"、merging == true、conflicts >= 1。接下来用resolveConflict(root, path, "theirs")解决冲突,status.conflicts归零,continueOperation成功,最终operation == "none"。Electron 侧对应的「resolves and aborts real merge conflicts」测试路径完全一致(vaultGitService.integration.test.ts)。
merge 的 abort 与 continue:resolvesAndAbortsMergeConflicts验证解决冲突后可abortMerge干净退出;continuesMergeAfterConflictResolved验证解决后continueOperation能正常收尾。实现上abortMerge先试git merge --abort,失败再试git rebase --abort(GitEngine.kt);continueOperation在operation == "rebase"时执行rebase --continue(附带-c core.editor=true),否则执行commit --no-edit,但冲突未清一律拒绝(GitEngine.kt)。
rebase 的 continue:continuesRebaseAfterConflictResolved复现了 rebase 冲突→resolveConflict("theirs")→continueOperation的流程,验证operation从rebase回到none。这与 Electron 的「detects and continues a rebase after conflicts are resolved」用例逐条对应。
resolve-conflict 的两种策略:resolveConflict只接受ours/theirs两个策略,内部执行git checkout --ours/--theirs -- <path>后git add;对未处于冲突状态的文件返回Git path is not conflicted(GitEngine.kt)。
3.4 操作守卫:冲突期禁止 commit / pull / push / continue
登记表中出现了一组形态相同的守卫行为,这是「操作顺序正确性」的对齐重点:
| caseId | 行为 | Compose 验证类 |
|---|---|---|
| commit-during-merge | merge 中禁止 commit | rejectsCommitWhileMergeInProgress |
| commit-unresolved-conflicts | 未解决冲突禁止 commit | GitCommitGuardTest.commitBlockedWhileMergeConflictsRemain |
| pull-during-merge | merge 中禁止 pull | GitPullGuardTest.mergeInProgressRejectedBeforeMissingRemoteMessage |
| pull-unresolved-conflicts | 未解决冲突禁止 pull | GitPullGuardTest.pullBlockedWhileMergeConflictsRemain |
| continue-unresolved-conflicts | 冲突未解决禁止 continue | GitContinueGuardTest/GitRebaseGuardTest |
| pull-during-rebase | rebase 中禁止 pull | GitRebaseGuardTest.pullBlockedWhileRebaseInProgress |
| commit-during-rebase-conflicts | rebase 冲突未清禁止 commit | GitRebaseGuardTest.commitBlockedWhileRebaseConflictsRemain |
| push-unresolved-conflicts | 冲突未清禁止 push | GitPushGuardTest.pushBlockedWhileMergeConflictsRemain |
| abort-rebase-unresolved | rebase 冲突态允许 abort | GitRebaseGuardTest.abortEndsRebaseWithUnresolvedConflicts |
从GitEngine.pull的实现可以看到守卫顺序(GitEngine.kt):先检查repository,再检查merging(返回Finish or abort the current merge/rebase before pulling),随后检查conflicts > 0(返回Resolve all conflicts before pulling),最后才检查 remote。GitPullGuardTest.mergeInProgressRejectedBeforeMissingRemoteMessage专门锁定「merge 进行中时,错误提示必须命中 merge 语义且不能出现 remote 字样」,即守卫优先级高于远端校验。
push的守卫链与之类似(GitEngine.kt):merge/rebase 进行中或存在冲突都直接拒绝。这些 guard 测试的 fixture 模式相同:构造base/other/base-branch三方修改同一文件,git merge other必失败制造冲突态,然后调用目标动作断言失败文案。
3.5 自动检查点(automatic checkpoint)
automatic-checkpoint-init/push与automatic-checkpoint-conflict-skip两条,对应 ElectronautomaticCheckpoint()与 ComposeautomaticCheckpoint()(GitEngine.kt)的完整语义:
- 首次使用自动初始化:目录还不是仓库时,
automaticCheckpoint内部调用init,并以「Initial MooTool Vault setup」作为首个提交说明。对应用例automaticCheckpointInitializesRepositoryOnFirstUse与 Electron「initializes a new Vault when creating its first automatic checkpoint」; - 配置远端后自动 push:有未提交变更或
ahead > 0且remote已配置时,检查点提交完成后自动git push -u origin HEAD。对应用例automaticCheckpointPushesWhenRemoteConfigured会去 bare 仓库执行git log --all断言提交确实到达远端,且status().ahead == 0; - 冲突期跳过:
merging || conflicts > 0时返回「Merge/rebase in progress; checkpoint skipped」,并保持 HEAD 不变。resolvesAndAbortsMergeConflicts中断言冲突期间的检查点调用后 history 首个 hash 与冲突前一致。
这个流程保证了「随手记/JSON 笔记自动保存到 Git」不会在合并中途制造脏提交——这正是登记表标注对齐 ElectronautomaticCheckpoint的核心价值。
3.6 安全与路径规则
- rejects-unsafe-paths:
fileDiffsMatchesElectronPathRules验证../outside抛出IllegalArgumentException("Invalid Git path"),而valid..name.json(合法但无变更)返回空列表。实现依据是normalizeGitPath(GitEngine.kt):拒绝绝对路径、含\u0000、长度超 512 的路径,并拒绝任意一段为./../空串的相对路径; - parent-repo-not-adopted:
rejectsParentRepositoryUntilVaultRootIsInitialized构造「父目录已 Git init、子目录为 Vault」的嵌套场景,断言status(vault)先报告repository == false,init(vault)之后才成为独立仓库,且父仓库的git status能继续看到outside-unrelated.txt——即嵌套 Vault 不会被父仓库的git add --all吞并。这一点由status()中rev-parse --show-toplevel与root.toRealPath()的比对逻辑保障(GitEngine.kt); - unicode-paths:
unicodePathsWorkForStatusDiffDiscardAndRename用中文文件名验证 status/diff/discard/rename 全链路,重命名后status.changes需同时给出path与originalPath。
3.7 index.lock 恢复与并发提交
这是GitEngine.run最精细的一段逻辑(GitEngine.kt),对应登记表三条用例:
- stale-index-lock:命令因
unable to create ... index.lock ... file exists失败后,先sleep(250ms)重试一次;仍失败则走quarantineStaleIndexLock。隔离流程为:锁文件存在且最后修改时间距今超过 5 分钟(STALE_INDEX_LOCK_MS)→ 用lsof -t确认无进程占用 → 再等100ms确认 mtime 稳定 → 把锁文件改名为index.lock.mootool-stale-<时间戳>-<pid>→ 重试命令 →finally删除隔离文件。对应用例repairsStaleIndexLockAndRetriesCommit在锁文件 mtime 设为 10 分钟前的前提下,断言提交成功、锁文件消失、.git下没有残留index.lock.mootool-stale-*; - recent-index-lock:锁文件刚创建(不足 5 分钟)时不做隔离,
commit返回失败且文案包含index.lock。对应用例recentIndexLockBlocksCommit; - held-index-lock:锁文件虽陈旧但被本进程以文件句柄占用时,
lsof -t检测到占用,不做隔离,提交保持失败。对应用例doesNotQuarantineIndexLockHeldOpenByThisProcess(该用例依赖平台lsof,macOS 为/usr/sbin/lsof,Linux 为lsof,Electron 侧同样用skipIf(!lockInspectorAvailable)做条件跳过)。
并发提交序列化:serializesConcurrentCommitsForSameVaultRoot用双线程同时发起两条不同 message 的提交,断言两个结果都成功,但git log --pretty=%s -- concurrent.txt只能看到一条 message——说明后到的提交看到的是已经干净的工作区,不会产生叠加提交。Electron 侧同名用例使用「两个独立VaultGitService实例并行Promise.all」,验证的是repositoryOperationQueues按目录的队列化;Compose 侧locks的synchronized达到同一效果。
3.8 fetch 与 push 的远端语义
登记表后半段集中对齐远端操作:
- push-non-ff:
pushFailsWhenRemoteIsAheadWithoutPull让两个 clone 先后 push 到同一 bare 仓库,第二个 push 必须失败,且文案匹配rejected/non-fast-forward/fetch first之一。这对应 Electron 侧「rejects non-fast-forward push」语义; - fetch-prune-behind:
fetchUpdatesBehindWithoutPullingWorkingTree验证git fetch --prune origin只更新远端追踪引用(status.behind从 0 变为 ≥1),但工作区文件v2.txt依然不存在,随后pull才真正落盘。对应 Electron「fetch updates behind without pulling the working tree」; - fetch-prune-stale-ref:
fetchPruneRemovesStaleRemoteTrackingBranch先让客户端 fetch 到origin/feature,远端删除该分支后再次 fetch,git branch -r不再出现origin/feature——验证 prune 清理陈旧追踪分支; - fetch-diverged-ahead-behind:
fetchReportsAheadAndBehindWhenBranchesDiverge验证分叉后 fetch 能同时更新 ahead 与 behind,且不触碰工作区; - fetch 未配置远端:
fetchFailsWhenRemoteIsNotConfigured断言失败文案包含remote; - configure-remote-remove:
configureRemoteRemoveClearsOriginFromStatus与configureRemoteRemoveWhenAlreadyRemovedIsIdempotent验证空 URL 删除origin的幂等语义:首次删除返回成功且status.remote == "",二次删除返回「Remote is already removed」。Electron 侧对应「removes a configured remote when an empty URL is saved」; - 远端 URL 校验:
rejectsUnsafeGitRemotes验证javascript:alert(1)、/etc/passwd这类 URL 被拒绝(normalizeGitRemote只放行https?://、ssh://、git://、git@、file://前缀,见 GitEngine.kt),合法file://URL 可配置,空字符串可移除。
3.9 UI 呈现层的对齐
登记表末行与底注说明 UI 层也有明确的对照物:
git-ui-abort-copy:merge/rebase 中止与确认文案、continue/counts/resolve/pull 的显隐与 busy 状态,由GitOperationPresentationTest锁定(参见 DIFF-520);merge-conflict-ui-hint:未解决冲突的说明文案与变更行冲突<em>高亮,由GitMergeConflictPresentationTest与VaultGitDialog锁定;- 底注补充了两条 UI 对照:
VaultGitDialog冲突行「使用本地/远端」的 Compose Tab 帧编号 142(见 DIFF-437),以及顶栏分支图标与操作钮的 Lucide 图标对齐(见 DIFF-540)。
四、跨实现对齐的验证方法论
这份登记表本身就是一个值得借鉴的工程实践:用一张 caseId 表把两个实现的行为一一钉死,再用同名/同语义测试双向锁定。要点有三:
- 统一 fixture 模式:冲突类测试几乎都遵循「base 提交 →
checkout -b other修改提交 → 切回 base 修改提交 →merge/rebase制造冲突」的固定剧本,两侧代码逐行对应,降低了环境差异带来的噪声; - 双保险验证:引擎层断言(
GitEngineTest/GitEngine*GuardTest)与呈现层断言(Git*PresentationTest)分离,行为正确性与 UI 文案/显隐分开验证; - 可追溯的差异记录:每个行为点挂在 DIFF 文档上(如 DIFF-443、DIFF-475),DIFF 文档写明背景、行为差异、修复方案与
./gradlew :composeApp:desktopTest --offline的通过统计,形成从需求到测试再到验证的完整闭环。
五、如何在当前仓库中验证这些行为
所有 Compose 侧测试都位于 desktopTest 目录下,主要文件包括:
GitEngineTest.kt:生命周期、冲突、fetch/push、锁恢复、并发、Unicode、路径规则的引擎级测试;GitCommitGuardTest.kt/GitPullGuardTest.kt/GitPushGuardTest.kt/GitRebaseGuardTest.kt:各类冲突态守卫测试;GitMergeConflictPresentationTest.kt/GitOperationPresentationTest.kt:UI 呈现测试。
Electron 侧的源测试是 next/src/shared/vaultGitService.integration.test.ts,实现是 next/electron/main/vaultGitService.ts。运行验证的方式:
# 在 next-compose 目录下运行 Compose 桌面测试(需要本机安装 git CLI) ./gradlew :composeApp:desktopTest --offline需要注意的适用前提:冲突/锁/远端相关用例通过assumeGit()或Assume.assumeTrue在未安装 git 时自动跳过;held-index-lock用例额外要求lsof可用,Electron 侧同样按平台条件跳过,不属于失败。
六、小结
Vault Git 是 MooTool 把「随手记、JSON 笔记等本地数据放进一个可版本化的目录」的关键基础设施。本文基于 electron-next-vaultGitService-vitest.md 这份对照登记表,梳理了从init/commit/diff基础链路,到 merge/rebase 冲突闭环、操作守卫、自动检查点、index.lock恢复、并发序列化、fetch/push 远端语义,再到 UI 呈现的完整行为清单,并用 GitEngine.kt 的实现逐项印证。对正在做跨实现功能迁移的团队来说,这套「行为登记表 + 同名测试对 + DIFF 追溯」的方法本身,就比任何一条具体行为都更有长期价值。
- 桌面应用
- 开发工具
【免费下载链接】MooTool
Handy tool set for developers
相关推荐
MooTool Vault Git 面板文案对齐实战:Compose `git.*` 与 Electron `json.git.*` 全量对照解析
MooTool Vault Git 面板文案对齐实战:Compose git. 与 Electron json.git. 全量对照解析 导读 在 MooTool
桌面应用开发工具MooTool Compose 版 Vault Git 角标即时刷新:基于 onGitStatusChanged 回调对齐 Electron 行为的实现解析
MooTool Compose 版 Vault Git 角标即时刷新:基于 onGitStatusChanged 回调对齐 Electron 行为的实现解析 导
桌面应用开发工具MooTool Vault Git 自动检查点:DIFF-412 活动记录与 Compose/Electron 行为对齐实践
MooTool Vault Git 自动检查点:DIFF 412 活动记录与 Compose/Electron 行为对齐实践 本文基于 next compose
桌面应用开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考