- 桌面应用
- 开发工具
【免费下载链接】MooTool
Handy tool set for developers
导读
本文以 MooTool Next Compose(next-compose子工程)的 DIFF-619 变更切片为线索,解析桌面端 Git 保险库面板在 merge/rebase 冲突期间的远程操作行为:fetch 只要配置了 remote 即可用,而 pull 在 merge/rebase 进行中强制禁用。文章从展示层判定函数(GitVaultRemotePresentation/GitOperationPresentation)、引擎层守卫(GitEngine.pull/fetch)到 UI 接线(VaultGitDialog)逐层还原实现,并给出GitMergeProductEvidenceFlowTest、GitRebaseProductEvidenceFlowTest等测试证据流以及证据帧214的生成方式,读者可据此复现验证命令并理解整套冲突期操作矩阵。
变更背景:DIFF-619 在整个冲突期证据链中的位置
MooTool Next Compose 的 Git 保险库(JSON Vault)面板迭代采用「DIFF 变更切片」方式推进,每个切片锁定一类冲突期操作行为并配套独立证据帧。DIFF-619 的基线是 DIFF-618 变更切片(工作区),后者已锁定冲突未清时commit禁用(帧 212/213),而 619 在相同冲突态下继续细分远程操作:
| DIFF 切片 | 锁定行为 | 关键证据帧 |
|---|---|---|
| DIFF-609 | merge §B push/pull 禁用 | 204-compose-git-merge-push-disabled-tab-focus.png |
| DIFF-616 | rebase §C push/pull 禁用 | 211-compose-git-rebase-push-pull-disabled-tab-focus.png |
| DIFF-617 | rebase resolve 提示 + 完成后 pull 恢复 | 走查 hint |
| DIFF-618 | merge/rebase 冲突期提交禁用 | 212/213 |
| DIFF-619 | 冲突期 fetch 仍可用、pull 禁用 | 214 |
DIFF-619 明确不重复618 的提交禁用链,也不重复211 的 push/pull 禁用链,聚焦于"fetch 例外可用"这一独特行为,并强调"仅要求 remote"这一前置条件。
核心行为规则:fetch 只依赖 remote,pull 依赖冲突状态
DIFF-619 的范围描述如下(原文语义):
A03:对齐 Electron
VaultGitDialog:fetch在 merge/rebase 进行中仍可用(仅要求 remote);pull在merging时禁用。
- fetch:在 merge/rebase 冲突期间保持可用,唯一前提是仓库配置了 remote(
status.remote非空); - pull:只要
status.merging == true即禁用,即使 remote 已配置。
这与用户的直觉相反(许多人认为冲突期间一切远程操作都应暂停),其设计意图在于:fetch 只从远端下载引用、不会触碰工作区与暂存区,因此即使冲突未解决也不会破坏冲突现场,反而可以帮助开发者查看上游最新状态;而 pull 会尝试合并远端变更,在冲突未清时继续 pull 只会叠加新的冲突,故必须在 UI 与引擎两层同时拦截。
展示层实现:判定函数与 busy 门控
冲突期操作是否可用的判定集中在commonMain的两个 presentation 对象中,分别承担"文案"与"可用性"职责。
GitOperationPresentation:基础判定
GitOperationPresentation.kt 提供基础判定:
fun pullEnabled(remotePresent: Boolean, merging: Boolean): Boolean = remotePresent && !merging fun pushEnabled(remotePresent: Boolean, merging: Boolean): Boolean = remotePresent && !merging fun fetchEnabled(remotePresent: Boolean): Boolean = remotePresentpullEnabled/pushEnabled要求remote 存在且非 merging(merge/rebase 进行中一律禁用);fetchEnabled只要求 remote 存在,不关心 merging 状态——这就是"冲突期 fetch 可用"的判定源头。
GitVaultRemotePresentation:busy 门控 + 冲突数约束
GitVaultRemotePresentation.kt 在基础判定之上叠加busy(操作进行中)与conflicts(未解决冲突数):
fun fetchActionEnabled(persistedRemote: String, busy: Boolean): Boolean = !busy && fetchEnabled(persistedRemote) fun pullActionEnabled( persistedRemote: String, merging: Boolean, busy: Boolean, conflicts: Int = 0, ): Boolean = !busy && pullEnabled(persistedRemote, merging, conflicts)参数含义与取值约束:
| 参数 | 类型 | 说明 |
|---|---|---|
persistedRemote | String | 已持久化的originremote URL,空白即视为无 remote |
merging | Boolean | 由GitStatus.merging传入,表示 merge/rebase 进行中 |
busy | Boolean | 面板忙碌态,任一 Git 操作执行期间为true,此时所有按钮禁用 |
conflicts | Int | 未解决冲突计数,> 0时 pull/push 禁用(fetch 不受影响) |
单元测试 GitVaultRemotePresentationTest.kt 对此有直接断言:
fetchActionEnabled(remote, busy = false)为true;fetchActionEnabled(remote, busy = true)为false(busy 门控);fetchActionEnabled("", busy = false)为false(无 remote 禁用);pullActionEnabled(remote, merging = true, busy = false)为false(冲突期 pull 禁用)。
引擎层守卫:GitEngine.pull 与 fetch 的"双重防线"
UI 层禁用只是第一道防线,真正的硬性约束在GitEngine引擎层。即使绕过 UI 直接调用引擎,冲突期 pull 依然会被拒绝,而 fetch 放行。
pull:merge/rebase 与冲突双重拦截
GitEngine.kt 中pull的执行顺序刻意将状态守卫放在 remote 校验之前:
fun pull(root: Path, isolateConfig: Boolean = false, token: String = ""): GitActionResult = locked(root) { val current = status(root, isolateConfig) if (!current.repository) return@locked GitActionResult(false, "Git repository is not initialized") if (current.merging) return@locked GitActionResult(false, "Finish or abort the current merge/rebase before pulling") if (current.conflicts > 0) { return@locked GitActionResult(false, "Resolve all conflicts before pulling") } if (current.remote.isBlank()) return@locked GitActionResult(false, "No origin remote is configured") val result = run(listOf("pull", "--no-rebase", "origin"), root, isolateConfig, token, REMOTE_TIMEOUT_MS) ... }current.merging时立即返回失败,错误信息为 "Finish or abort the current merge/rebase before pulling";current.conflicts > 0时返回 "Resolve all conflicts before pulling";- merge/rebase 校验先于 remote 校验,这一点由 GitPullGuardTest.kt 专门锁定:测试
mergeInProgressRejectedBeforeMissingRemoteMessage断言 merge 进行中时 pull 的失败信息包含 "merge" 而不包含 "remote",证明守卫优先级正确。
fetch:仅要求 remote
而fetch的实现(GitEngine.kt#L238-L240)只做一件事:
fun fetch(root: Path, isolateConfig: Boolean = false, token: String = ""): GitActionResult = locked(root) { requireRemote(root, isolateConfig) { run(listOf("fetch", "--prune", "origin"), root, isolateConfig, token, REMOTE_TIMEOUT_MS) } }requireRemote(同文件 L500-L507)仅校验"仓库已初始化 + origin remote 已配置",随后执行git fetch --prune origin,完全没有 merging / conflicts 守卫——这与展示层fetchEnabled只依赖 remote 的规则完全对齐,构成"UI 判定 = 引擎行为"的一致性。
status:如何识别 merge/rebase 进行中
GitStatus.merging与operation字段由 GitEngine.status 判定:
val merge = run(listOf("rev-parse", "-q", "--verify", "MERGE_HEAD"), root, isolateConfig) val rebaseMerge = gitPathExists(root, "rebase-merge", isolateConfig) val rebaseApply = gitPathExists(root, "rebase-apply", isolateConfig) val operation = when { rebaseMerge || rebaseApply -> "rebase" merge.exitCode == 0 -> "merge" else -> "none" }即:存在MERGE_HEAD判定为 merge,存在.git/rebase-merge或.git/rebase-apply目录判定为 rebase,其余为none;merging = operation != "none"。冲突数则由status --porcelain=v1 -z --branch --untracked-files=all解析中U/AA/DD标记统计。
UI 接线:VaultGitDialog 工具栏按钮的 enabled 表达式
VaultGitDialog.kt 中工具栏三个远程按钮直接引用上述判定函数:
// fetch —— 冲突期仍可用(仅要求 remote) MooButton( container.t("git.fetch"), p5Toolbar = true, enabled = GitVaultRemotePresentation.fetchActionEnabled(status.remote, busy), onClick = { runAction { GitEngine.fetch(root, token = token()) } }, leading = { GitPanelIcon(GitPanelIconKind.CloudDownload, colors.textBody) }, ) // pull —— merging 时禁用 MooButton( container.t("git.pull"), p5Toolbar = true, enabled = GitVaultRemotePresentation.pullActionEnabled( status.remote, status.merging, busy, conflicts = status.conflicts, ), onClick = { runAction(workingTree = GitVaultFlushAction.Pull) { GitEngine.pull(root, token = token()) } }, ) // push —— 同样 merging/conflicts 时禁用 MooButton( container.t("git.push"), p5Toolbar = true, enabled = GitVaultRemotePresentation.pushActionEnabled( status.remote, status.merging, busy, conflicts = status.conflicts, ), ... )同面板内commit按钮(L664-L674)则使用GitOperationPresentation.commitActionEnabled(busy, merging, conflicts, hasChanges, messageTrimmed),!merging && conflicts == 0 && hasChanges && message.trim()四条件同时满足才可用——这正是 DIFF-618 锁定的提交禁用链,与 619 的 fetch 可用形成鲜明对照:同一冲突态下,commit/pull/push 三类操作按不同规则分别放行或拦截,而 fetch 是唯一保持可用的远程操作。
此外工具栏还依据GitOperationPresentation.inProgressMessageKey(status.operation, status.merging)渲染状态胶囊(merge 显示git.operationMerge,rebase 显示git.operationRebase),冲突数在showChangeCounts为真时以 danger 色显示,与按钮状态一起构成冲突期的完整视觉反馈。
测试证据流:从制造冲突到 fetch 真实成功
DIFF-619 指定的证据流测试是GitMergeProductEvidenceFlowTest与GitRebaseProductEvidenceFlowTest,两者在冲突态下断言fetchActionEnabled == true,并调用真实GitEngine.fetch验证成功。
merge 证据流(GitMergeProductEvidenceFlowTest)
GitMergeProductEvidenceFlowTest.kt 的核心流程:
- 创建 bare 远端 + upstream + local 三个临时仓库,
upstream提交{"side":"base"}后 push 到 bare; localclone 后,upstream提交{"side":"remote"}并 push,local提交{"side":"local"}制造分叉;GitEngine.pull(local)触发冲突,GitEngine.status断言operation == "merge"、merging == true、conflicts >= 1;- 冲突态断言矩阵:
pushActionEnabled→false;pullActionEnabled→false;commitActionEnabled→false;fetchActionEnabled(status.remote, busy = false)→true,随后val fetched = GitEngine.fetch(local, isolateConfig = true)断言fetched.success——真实引擎调用验证冲突期 fetch 成功;
resolveConflict(local, "conflict.json", "theirs")清零冲突 →mergeContinueActionEnabled恢复 →continueOperation完成 merge → 断言operation == "none"且 push/pull 重新可用。
rebase 证据流(GitRebaseProductEvidenceFlowTest)
GitRebaseProductEvidenceFlowTest.kt 流程与 merge 版基本对称,差异在于:
- 冲突制造改用
git pull origin <branch> --rebase(失败即进入 rebase 冲突态); GitEngine.status断言operation == "rebase";- 冲突态下同样断言
fetchActionEnabled == true并真实调用GitEngine.fetch(local, isolateConfig = true)验证成功; - resolve 时方向相反:rebase 冲突中
ours为 upstream(remote)、theirs为 replay 的本地提交,故使用"ours"(源码注释明确说明对齐git checkout --ours); continueOperation在 rebase 分支走git rebase --continue(引擎实现见 GitEngine.kt#L426-L436),merge 分支走git commit --no-edit。
两个测试文件均通过GitIdentity("MooTool Evidence", "mootool-evidence@local")隔离身份、isolateConfig = true隔离全局配置执行,并在finally中删除临时目录。
冲突态断言汇总
| 断言点 | merge 冲突态 | rebase 冲突态 | merge/rebase 完成后 |
|---|---|---|---|
fetchActionEnabled | true | true | true(不恢复概念,全程可用) |
GitEngine.fetch真实调用 | success | success | — |
pullActionEnabled | false | false | true(恢复) |
pushActionEnabled | false | false | true(恢复) |
commitActionEnabled | false | false | — |
mergeContinueActionEnabled | false | false | true(冲突清零后) |
证据帧 214:UI 捕获测试生成"fetch 可用 / pull 禁用"帧
除了引擎层证据流,DIFF-619 还通过 GitVaultFetchDuringConflictCaptureTest.kt 生成 UI 证据帧,输出文件为:
next-compose/docs/evidence/2026-09-15-inspector-screencapture/windows/214-compose-git-rebase-fetch-enabled-pull-disabled-tab-focus.png
该测试使用 Compose UI 测试框架(runDesktopComposeUiTest(width = 560, height = 120))渲染与VaultGitDialog顶栏一致的控件组合:
MooStatusPill(zh.t("git.operationRebase"), kind = MooStatusKind.Error) // 变基进行中胶囊(错误色) MooButton(zh.t("git.fetch"), p5Toolbar = true, enabled = GitVaultRemotePresentation.fetchActionEnabled(remote, busy = false)) // fetch 启用 MooButton(zh.t("git.pull"), p5Toolbar = true, enabled = GitVaultRemotePresentation.pullActionEnabled( persistedRemote = remote, merging = true, busy = false)) // pull 禁用随后onRoot().captureToImage().toAwtImage()捕获整根节点并写入指定路径,并断言 PNG 文件大小 > 500 字节(保证帧非空)。测试文件名与 DIFF 文档中的帧编号214一一对应,与211(push/pull 禁用)、212/213(提交禁用)共同构成冲突期操作矩阵的完整证据帧序列。
配合证据帧的脚本侧提示见 product-evidence-common.sh 的mootool_evidence_print_git_rebase_product_hint:其中明确登记"rebase 进行中 push/pull 应禁用、fetch 仍可用(GitVaultRemotePresentation;Compose 帧 211/214)",同时说明冲突未清时「提交」应禁用(帧 213 /commitEnabled),产品走查(产品主窗 §C)仍需手工截图登记至docs/evidence/2026-09-17-vault-conflict-product-window/results.md。
证据仓库制备脚本:如何手动复现 rebase 冲突态
如需手动走查产品窗的 rebase 冲突态,可运行证据制备脚本:
next-compose/scripts/prepare-git-rebase-conflict-evidence.sh
脚本核心步骤(与GitRebaseProductEvidenceFlowTest同构):
- 在隔离 JSON Vault 数据目录下创建 bare 远端(
_evidence_git_rebase_remote.git)与 upstream 克隆(_evidence_git_rebase_upstream_clone); git init -b main初始化 Vault 仓库,提交conflict.json(内容{"side":"base"})并 push 建立origin;- upstream 提交
{"side":"remote"}并 push; - 本地提交
{"side":"local"}后执行git pull origin main --rebase制造变基冲突; - 通过
mootool_evidence_assert_git_rebase_conflict断言验证:存在.git/rebase-merge或.git/rebase-apply目录,且git diff --name-only --diff-filter=U非空; - 输出产品走查提示(自动选中
conflict.json、ours/theirs 解决、continue 完成变基)。
脚本通过GIT_CONFIG_GLOBAL=/dev/null、GIT_CONFIG_SYSTEM=/dev/null及isolateConfig语义隔离全局 git 配置,确保复现结果确定性。merge 冲突态对应脚本为prepare-git-merge-conflict-evidence.sh(断言MERGE_HEAD存在)。
本地验证方式
DIFF-619 指定的验证命令(JDK 21 环境,离线模式):
./gradlew :composeApp:desktopTest --offline --no-daemon该命令会执行desktopTest全部测试,覆盖上述GitMergeProductEvidenceFlowTest、GitRebaseProductEvidenceFlowTest、GitVaultFetchDuringConflictCaptureTest及GitVaultRemotePresentationTest、GitPullGuardTest等守卫测试。若目标环境已安装 git CLI,assumeGit()前置条件满足后,证据流测试会真实走完整流程(含GitEngine.fetch真实执行);未安装 git 时相关测试自动跳过(Assume)。
总结:冲突期操作矩阵的完整设计
DIFF-619 并非孤立的一条规则,而是 MooTool Next Compose Git 保险库"冲突期操作矩阵"的一部分,从GitOperationPresentation基础判定 →GitVaultRemotePresentationbusy/conflicts 门控 →GitEngine引擎守卫 →VaultGitDialogUI 接线,四层规则保持严格一致:
- fetch:只要求 remote,冲突期可用(DIFF-619 锁定);
- pull:remote +
!merging+conflicts == 0,冲突期禁用(DIFF-609/616/619 锁定); - push:remote +
!merging+conflicts == 0,冲突期禁用(DIFF-609/616 锁定); - commit:
!merging+conflicts == 0+ 有变更 + 消息非空,冲突期禁用(DIFF-618 锁定); - resolve / continue / abort:随冲突数动态启用(冲突清零后 continue 恢复)。
每条规则都配备引擎层真实执行的证据流测试 + Compose UI 捕获证据帧,使"冲突期 fetch 可用、pull 禁用"这一看似反直觉的设计具备了可复现、可审计的完整证据链。
- 桌面应用
- 开发工具
【免费下载链接】MooTool
Handy tool set for developers
相关推荐
opensre 交互式 Shell 动作工具实现指南:`tools/interactive_shell` 包架构、子进程解耦与依赖边界
opensre 交互式 Shell 动作工具实现指南: tools/interactive_shell 包架构、子进程解耦与依赖边界 opensre(开源 AI
桌面应用开发工具Kornia 端到端应用实战指南:用 PyTorch 组合图像增强、匹配、拼接与视觉提示
Kornia 端到端应用实战指南:用 PyTorch 组合图像增强、匹配、拼接与视觉提示 本文是 Kornia 官方文档 applications https:
桌面应用开发工具MooTool Next Compose 的 Git Rebase 冲突解析与 Continue 集成:从 `resolveConflict` 到「继续合并 / Rebase」的完整链路
MooTool Next Compose 的 Git Rebase 冲突解析与 Continue 集成:从 resolveConflict 到「继续合并 / R
桌面应用开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考