☰
MooTool Next Compose 冲突期 Git 操作护栏:merge/rebase 进行中 fetch 可用而 pull 禁用的实现与证据链(DIFF-619)
2026/10/16 2:29:00 网站建设 项目流程
  • 桌面应用
  • 开发工具

【免费下载链接】MooTool

Handy tool set for developers

项目地址:https://gitcode.com/gh_mirrors/mo/MooTool
点击查看免费下载

导读

本文以 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-609merge §B push/pull 禁用204-compose-git-merge-push-disabled-tab-focus.png
DIFF-616rebase §C push/pull 禁用211-compose-git-rebase-push-pull-disabled-tab-focus.png
DIFF-617rebase resolve 提示 + 完成后 pull 恢复走查 hint
DIFF-618merge/rebase 冲突期提交禁用212/213
DIFF-619冲突期 fetch 仍可用、pull 禁用214

DIFF-619 明确不重复618 的提交禁用链,也不重复211 的 push/pull 禁用链,聚焦于"fetch 例外可用"这一独特行为,并强调"仅要求 remote"这一前置条件。

核心行为规则:fetch 只依赖 remote,pull 依赖冲突状态

DIFF-619 的范围描述如下(原文语义):

A03:对齐 ElectronVaultGitDialog: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 = remotePresent
  • pullEnabled/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)

参数含义与取值约束:

参数类型说明
persistedRemoteString已持久化的originremote URL,空白即视为无 remote
mergingBoolean由GitStatus.merging传入,表示 merge/rebase 进行中
busyBoolean面板忙碌态,任一 Git 操作执行期间为true,此时所有按钮禁用
conflictsInt未解决冲突计数,> 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 的核心流程:

  1. 创建 bare 远端 + upstream + local 三个临时仓库,upstream提交{"side":"base"}后 push 到 bare;
  2. localclone 后,upstream提交{"side":"remote"}并 push,local提交{"side":"local"}制造分叉;
  3. GitEngine.pull(local)触发冲突,GitEngine.status断言operation == "merge"、merging == true、conflicts >= 1;
  4. 冲突态断言矩阵:
    • pushActionEnabled→false;
    • pullActionEnabled→false;
    • commitActionEnabled→false;
    • fetchActionEnabled(status.remote, busy = false)→true,随后val fetched = GitEngine.fetch(local, isolateConfig = true)断言fetched.success——真实引擎调用验证冲突期 fetch 成功;
  5. 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 完成后
fetchActionEnabledtruetruetrue(不恢复概念,全程可用)
GitEngine.fetch真实调用successsuccess—
pullActionEnabledfalsefalsetrue(恢复)
pushActionEnabledfalsefalsetrue(恢复)
commitActionEnabledfalsefalse—
mergeContinueActionEnabledfalsefalsetrue(冲突清零后)

证据帧 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同构):

  1. 在隔离 JSON Vault 数据目录下创建 bare 远端(_evidence_git_rebase_remote.git)与 upstream 克隆(_evidence_git_rebase_upstream_clone);
  2. git init -b main初始化 Vault 仓库,提交conflict.json(内容{"side":"base"})并 push 建立origin;
  3. upstream 提交{"side":"remote"}并 push;
  4. 本地提交{"side":"local"}后执行git pull origin main --rebase制造变基冲突;
  5. 通过mootool_evidence_assert_git_rebase_conflict断言验证:存在.git/rebase-merge或.git/rebase-apply目录,且git diff --name-only --diff-filter=U非空;
  6. 输出产品走查提示(自动选中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

项目地址:https://gitcode.com/gh_mirrors/mo/MooTool
点击查看免费下载

相关推荐

上一篇:零训练AI换脸神器:roop-unleashed完整入门指南
下一篇:5分钟零基础上手:roop-unleashed AI换脸工具终极指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询