MooTool Next Compose DIFF-515 深度解析:Vault Git merge/rebase 状态 UI、push 引擎守卫与 HTTP/翻译代理运行时规范化

原创2026-10-15 15:46:151,634 阅读
文章标签:开发工具桌面应用MCP 服务

MooTool Next Compose DIFF-515 深度解析:Vault Git merge/rebase 状态 UI、push 引擎守卫与 HTTP/翻译代理运行时规范化

导读

本篇技术指南以 DIFF-515 变更说明 为骨架,解析 MooTool 新一代 Compose 桌面端在 Vault Git 工作流与网络代理两条链路上的运行时收口:一方面把 push 从"仅依赖 UI 禁用"升级为与 pull 同级的引擎层守卫,并在 VaultGitDialog 中呈现 merge/rebase 状态胶囊与变更计数;另一方面将 HTTP(F09)与翻译(F20,含单词本重译)的发送逻辑统一收敛到 toHttpProxyConfig() 映射,在发送前完成 host/凭据 trim 与端口合法性校验。读完你将对这两个切片的底层调用链、守卫策略与对应测试验证有完整、可复用的技术认知。


一、变更背景:从 parity-gap 到运行时收口

DIFF-515 承接 DIFF-514 的成果。DIFF-514 已为设置页的网络代理做了"失焦提交"语义与加载规范化(SettingsNetworkNormalize),但彼时 F09 HTTP 与 F20 翻译发送时仍直接读取 NetworkSettings 的原始字段,规范化只在设置保存/加载阶段生效,运行时未收口。

与此同时,Electron 版 parity-gap 仍将 Vault Git pull/push/rebase 工作流可感知性列为差距项:GitEngine.pull 在 merge/rebase 进行中会从引擎层拒绝,而 push 此前仅依赖 UI 禁用,缺少引擎层兜底。

DIFF-515 的定位正是补齐这两处运行时收口,可概括为:

切片 此前状态 DIFF-515 目标
Vault Git push 仅 UI 禁用 引擎层与 pull 同级拒绝
Vault Git 状态展示 进行中无明确提示 状态胶囊 + 上下文计数
HTTP/翻译发送代理 直接读原始字段 统一经 toHttpProxyConfig() 规范化

二、A03 Vault Git:push 引擎级守卫与状态呈现

2.1 GitEngine.push 与 pull 对齐的守卫策略

核心实现位于 GitEngine.kt。push 与 pull 采用了几乎对称的双层拒绝逻辑:

fun push(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 pushing")
    }
    if (current.conflicts > 0) {
        return@locked GitActionResult(false, "Resolve all conflicts before pushing")
    }
    requireRemote(root, isolateConfig) { run(listOf("push", "-u", "origin", "HEAD"), root, isolateConfig, token, REMOTE_TIMEOUT_MS) }
}
  • merge/rebase 进行中(current.merging):直接返回失败,文案含 "merge/rebase" 与 "pushing" 关键字,这正是文档要求对齐的拒绝语义;
  • 存在未解决冲突(current.conflicts > 0):同样拒绝,即使 merging 为 false(例如异常残留的冲突状态);
  • remote 缺失:requireRemote 兜底返回 "No origin remote is configured";
  • 满足条件后才真正执行 git push -u origin HEAD,走 REMOTE_TIMEOUT_MS = 120_000L 远程超时。

pull 的守卫与之对齐(GitEngine.kt pull 方法):同样拒绝 merging 与冲突状态,并校验 remote 后执行 git pull --no-rebase origin。

merging / operation 的来源在 status() 中可追溯到 Git 的底层状态判定:

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 与 .git 目录下的 rebase-merge / rebase-apply 哨兵文件识别进行中的操作,写入 GitStatus.operation 与 GitStatus.merging。

2.2 GitOperationPresentation:操作状态胶囊与上下文计数

GitOperationPresentation.kt 是纯函数式的展示层判断中心,全部方法对齐 Electron VaultGitDialog 语义:

fun inProgressMessageKey(operation: String, merging: Boolean): String? {
    if (!merging) return null
    return when (operation) {
        "merge" -> "git.operationMerge"
        "rebase" -> "git.operationRebase"
        else -> "git.operationInProgress"
    }
}

/** 对齐 DIFF-515:仅在 merge/rebase/冲突上下文显示变更计数。 */
fun showChangeCounts(merging: Boolean, conflicts: Int): Boolean =
    merging || conflicts > 0

关键点在于 showChangeCounts 的上下文限定:文档明确指出"仅 merge/rebase/冲突上下文,不恢复 DIFF-315 移除的常显 counts"。也就是说,git.counts(changes / conflicts 数量)不再是常态展示,只有当 merging 为 true 或存在未解决冲突时才出现,避免回归 DIFF-315 已删除的常显方案。

在 UI 侧,VaultGitDialog.kt 中按操作类型呈现:

  • GitOperationPresentation.inProgressMessageKey(...) 命中时渲染 MooStatusPill 状态胶囊,冲突时取 Error 色、无冲突时取 Valid 色(第 323-328 行);
  • showChangeCounts 命中时渲染 git.counts 文本,冲突数大于 0 时用 colors.danger 高亮(第 329-341 行);
  • 中止按钮文案按操作类型区分:git.abortRebase / git.abortMergeOnly / git.abortMerge,确认弹窗文案对应 git.confirmAbortRebase / git.confirmAbortMerge / git.confirmAbort。

此外展示层还明确了 pull/push 的启用条件(remotePresent && !merging):未解决冲突但非 merging 时面板按钮仍可点击,实际由引擎层拒绝——这正是 DIFF-515 强调"push 不再仅依赖 UI 禁用"的落地方式:UI 与引擎形成双层防线。

2.3 测试验证矩阵

DIFF-515 为 Vault Git 切片指定了两组测试:

  • GitRebaseGuardTest.kt 中的 pushBlockedWhileRebaseInProgress:构造 rebase 冲突夹具(base/feature/main 三分支制造冲突),断言 GitEngine.push 失败且文案同时包含 "merge" 与 "pushing";
  • GitOperationPresentationTest.kt:覆盖 inProgressMessageKey 在 none/merge/rebase/unknown 与 merging 组合下的文案键映射,以及 abort 文案、pull/push/commit/continue/abort 的启用与禁用边界。

补充证据:未解决冲突场景的 merge 变体由 GitPushGuardTest.kt 的 pushBlockedWhileMergeConflictsRemain 覆盖。所有 Git 引擎测试都通过 isolateConfig = true 隔离全局/系统 Git 配置,保证不受开发者本机配置干扰。


三、F09 HTTP / F20 翻译:代理运行时统一映射

3.1 NetworkSettings.toHttpProxyConfig() 规范化规则

DIFF-515 引入的运行时映射位于 NetworkRuntime.kt:

fun NetworkSettings.toHttpProxyConfig(): HttpProxyConfig {
    if (!proxyEnabled) return HttpProxyConfig()
    return HttpProxyConfig(
        enabled = true,
        host = proxyHost.trim(),
        port = SettingsNetworkNormalize.sanitizeProxyPort(proxyPort),
        username = proxyUsername.trim(),
        password = proxyPassword.trim(),
    )
}

规则明确:

  • proxyEnabled 关闭时直接返回未启用的空 HttpProxyConfig,不做任何 trim 副作用;
  • host / username / password 发送前统一 trim();
  • port 必须经过 SettingsNetworkNormalize.kt 的 sanitizeProxyPort 校验:空串保留为空,非整数或超出 1..65_535 均被清空,而非抛错或透传。
fun sanitizeProxyPort(value: String): String {
    val trimmed = value.trim()
    if (trimmed.isEmpty()) return ""
    return trimmed.toIntOrNull()?.takeIf { it in 1..65_535 }?.toString() ?: ""
}

这一约束与 DIFF-514 中 SettingsNetworkNormalize.apply 在加载/迁入时使用的端口规则完全一致(SettingsNetworkNormalize.kt 还限定了 host ≤256、用户名 ≤128、密码 ≤512 字符),使设置保存、配置加载、运行时发送三条路径共享同一套规范化语义。目标结构 HttpProxyConfig(enabled / host / port / username / password)定义在 HttpEngine.kt,默认均为空值。

3.2 消费链路:HttpScreen 与 TranslationScreen

映射的消费点统一替换了原先"直接读原始字段"的写法:

  • F09 HTTP:HttpScreen.kt 第 323 行在发送入口读取 val proxy = container.settings.value.network.toHttpProxyConfig(),随后把 proxy 与 draft、requestId、timeout 一并交给 HttpRequestPresentation.runSend(...) 在 Dispatchers.IO 上执行;
  • F20 翻译:TranslationScreen.kt 有两个消费点:
    • 常规翻译发送(第 147 行):val proxy = settings.network.toHttpProxyConfig() 进入 TranslationWiringPresentation.runTranslate(...);
    • 单词本重译(第 391 行):WordBookPane 的 onRetranslate 回调同样走 toHttpProxyConfig(),确保重译与首次翻译使用一致的规范化代理配置。

由此,DIFF-515 达成"HttpScreen / TranslationScreen(含单词本重译)统一走该映射"的验收条件。数据模型源在 Settings.kt 的 NetworkSettings:proxyEnabled / proxyHost / proxyPort / proxyUsername / proxyPassword,以及超时默认值 requestTimeoutMs = 30_000、translationTimeoutMs = 15_000。

3.3 验证:NetworkRuntimeTest

NetworkRuntimeTest.kt 用三个用例锁定映射行为:

用例 输入 期望
httpProxyConfigTrimsAndSanitizesPort host/port/user/password 均带空格 全部 trim,port " 7890 " → "7890"
httpProxyConfigClearsInvalidPortButKeepsEnabledFlag port "70000"(越界) enabled 仍为 true,port 清空为空串
httpProxyConfigDisabledWhenToggleOff proxyEnabled = false proxy.enabled = false

其中"非法端口清空但保留 enabled 标记"的语义值得注意:它避免了发送时因端口非法整体禁用代理,而是在运行时用清空端口的方式降级,端口合法性判断完全交给 sanitizeProxyPort 单一入口。


四、验证方式与运行前提

DIFF-515 指定的验证命令为:

./gradlew :composeApp:desktopTest --offline

运行前提:

  • JDK 21:仓库说明中明确验证环境为 JDK 21(见 next-compose/README.md 与文档);
  • --offline:使用本地 Gradle 依赖缓存离线执行,避免网络抖动影响结果;
  • Git 相关测试(GitRebaseGuardTest、GitPushGuardTest)通过 Assume.assumeTrue("git CLI not installed", ...) 在未安装 git CLI 的机器上自动跳过,属于有前置条件的集成型单测;NetworkRuntimeTest 为纯 JVM 单测,无外部依赖。

测试范围集中在 composeApp/src/desktopTest 下的三个类:GitOperationPresentationTest、NetworkRuntimeTest、GitRebaseGuardTest,分别覆盖展示层判断、代理映射、push/pull/commit/continue/abort 在 rebase 冲突下的引擎行为。


五、明确未做(Roadmap 边界)

DIFF-515 文档如实划定了本次切片的边界,避免对能力做出超范围承诺:

  • 产品主窗冲突 / Git merge / IME 界面截图证据;
  • 六套 CSS 皮肤切换;
  • P7 Windows / Linux 安装包验证;
  • runDistributable 烟雾测试;
  • 设置页 TextField 失焦提交链路延伸;
  • 其余 F-tool 大切片迁移。

这些属于后续迭代范围,未在本条变更中覆盖。对于同类迁移工作,acceptance.md、architecture.md 以及 diff 目录 下的其余切片文档可提供完整的产品一致性基线。


六、实现要点小结

  1. 双层防线设计:Vault Git 的 push/pull 在 UI 启用判定(GitOperationPresentation.pushEnabled)之外,必须存在引擎层守卫(GitEngine.push 的 merging/conflicts 拒绝),防止绕过 UI 的调用路径直接污染仓库状态;
  2. 操作状态判定:operation 由 MERGE_HEAD、rebase-merge、rebase-apply 哨兵推导,UI 状态胶囊与 abort/continue 文案键随操作类型分派;
  3. 计数上下文限定:git.counts 仅在 merge/rebase 或存在冲突时展示,避免回归常显方案;
  4. 运行时规范化:代理配置在发送前统一 trim 与端口校验(1–65535),与设置加载路径共享同一 SettingsNetworkNormalize 语义;关闭开关则返回空配置,不产生副作用;
  5. 测试锚点:展示层与引擎行为均有独立测试锁定,端口越界被"清空而非报错",rebase/merge 冲突下 push 文案必须同时含 merge 与 pushing 关键字。

以上实现均可回溯至 GitEngine.kt、GitOperationPresentation.kt、NetworkRuntime.kt 与对应的测试文件,可作为后续维护与横向迁移(如 Tauri/Flutter/原生版本)的参照基线。

登录后查看全文
MooTool