MooTool Next Compose Vault Git 提交/推送前刷新编辑器:`GitEditorFlushPolicy` 的 flush 语义与实现
桌面应用开发工具【免费下载链接】MooToolHandy tool set for developers项目地址https://gitcode.com/gh_mirrors/mo/MooTool点击查看免费下载导读本文深入剖析 MooTool Next Compose 版本next-compose模块中 Vault 仓库的 Git 操作前编辑器刷新机制在commit提交、push推送等写操作执行之前VaultGitDialog会强制调用onFlush()将 JSON Vault / 随手记QuickNote编辑器的未保存缓冲区写盘确保 Git 操作针对的是磁盘上的真实内容而非内存草稿。读完本文你将掌握GitVaultFlushAction枚举与GitEditorFlushPolicy的判定策略、各 Git 动作的 flush/刷新矩阵、与 Electron 版prepareGitAction的对齐关系以及相关的验证测试与演进历史。一、背景为什么 Git 操作前必须先刷新编辑器MooTool 的 VaultJSON 片段库与随手记采用编辑器缓冲区 磁盘文件双轨模型。用户在编辑器里输入的内容默认保存在内存中session.editor.text仅在自动保存250ms 空闲、用户显式保存或执行特定操作时才落盘。而 Vault Git 面板VaultGitDialog通过GitEngine直接执行真实的gitCLI 命令Git 本身只认磁盘文件git add暂存的是磁盘内容git diff、git status展示的是磁盘与 HEAD 的差异git commit打包的是暂存区staging area内容。因此如果用户在编辑器中修改了笔记但尚未落盘此时直接提交Git 看到的仍是被git add过的旧内容提交产物与用户所见缓冲区不一致——这正是 DIFF-184 要解决的问题在 commit / push 这类依赖工作区内容的操作执行前先通过onFlush()把缓冲区刷新到磁盘保证所见即所提交。关联文档DIFF-184Vault Git 提交/推送前刷新编辑器二、核心设计GitVaultFlushActionGitEditorFlushPolicy整个 flush 策略被集中封装在两个类型中位于领域层GitEditorFlushPolicy.kt2.1GitVaultFlushActionVault Git 操作的动作枚举enum class GitVaultFlushAction { Pull, Discard, AbortMerge, ResolveConflict, ContinueOperation, Commit, Push, }这个枚举覆盖了 Vault Git 面板的全部工作区动作。值得注意的是它只包含会改变工作区或依赖工作区内容的操作而fetch、init、配置 remotesetRemote等操作不携带GitVaultFlushAction调用时传入null因此不触发额外 flush。2.2shouldFlushEditor执行前是否需要刷新缓冲区fun shouldFlushEditor(action: GitVaultFlushAction?): Boolean when (action) { GitVaultFlushAction.Discard, GitVaultFlushAction.AbortMerge, GitVaultFlushAction.ResolveConflict - false GitVaultFlushAction.Pull, GitVaultFlushAction.ContinueOperation, GitVaultFlushAction.Commit, GitVaultFlushAction.Push - true null - false }判定规则可以整理成一张清晰的决策表操作执行前 flush 编辑器成功后台后刷新 Vault 树Commit提交✅ 是❌ 否Push推送✅ 是❌ 否Pull拉取✅ 是✅ 是ContinueOperation继续合并/变基✅ 是✅ 是Discard丢弃更改❌ 否✅ 是AbortMerge中止合并❌ 否✅ 是ResolveConflict解决冲突❌ 否✅ 是nullfetch / init / setRemote / 面板打开❌ 否❌ 否2.3refreshesVaultAfterSuccess操作成功后是否刷新 Vault 树fun refreshesVaultAfterSuccess(action: GitVaultFlushAction?): Boolean action in setOf( GitVaultFlushAction.Pull, GitVaultFlushAction.Discard, GitVaultFlushAction.AbortMerge, GitVaultFlushAction.ResolveConflict, GitVaultFlushAction.ContinueOperation, )这条规则回答了一个对称的问题哪些动作会改变磁盘上的 Vault 文件内容从而需要刷新打开中的编辑器文件。会刷新Pull远端覆盖本地、Discard丢弃本地改动、AbortMerge中止合并、ResolveConflict写入 ours/theirs 解析结果、ContinueOperation合并/变基推进后工作区被 Git 改写。这些动作都可能让当前打开文件的磁盘内容与缓冲区不一致因此成功后需调用onVaultRefresh()重新加载。不会刷新Commit与Push本身不改变文件内容只是打快照、上传提交成功后afterSuccess { onVaultRefresh() }刷新的是变更列表而非重新加载文件内容。从源码结构看这种刷新树/刷新文件的双通道设计正是为了让 Git 面板状态changes 列表、冲突数、ahead/behind与 Vault 树、打开文件始终保持一致。三、集成点VaultGitDialog.runAction中的 flush 时序flush 策略的真正落地在 Git 面板对话框 VaultGitDialog.kt 的runAction函数中fun runAction( workingTree: GitVaultFlushAction? null, afterSuccess: (() - Unit)? null, remoteOverrideAfterReload: String? null, block: () - GitActionResult, ) { scope.launch { busy true try { if (GitEditorFlushPolicy.shouldFlushEditor(workingTree)) { val flushed onFlush() if (flushed ! null) { notifyVaultGitFlushBlocked(container, flushed) returnlaunch } } val result withContext(Dispatchers.IO) { block() } if (result.success) { container.toastSuccess(container.t(git.done)) afterSuccess?.invoke() if (GitEditorFlushPolicy.refreshesVaultAfterSuccess(workingTree)) { onVaultRefresh() } } else { notifyVaultGitActionFailure(container, result) if (workingTree GitVaultFlushAction.Pull) { onVaultRefresh() } else { returnlaunch } } // ... 重新拉取 status / history同步 remote 字段触发 onGitStatusChanged() } catch (error: Exception) { notifyVaultGitPanelFailure(container, error) } finally { busy false } } }这里的关键时序flush → 执行 → 结果处理可以概括为前置 flush若shouldFlushEditor(workingTree)为true调用onFlush()。onFlush()返回String?返回null表示刷新成功或缓冲区本就干净无需写盘可继续执行 Git 操作返回非null表示刷新被阻塞如文件未命名、写盘失败此时通过notifyVaultGitFlushBlocked弹出错误 toast 并中止 Git 操作returnlaunch。后台执行block()在Dispatchers.IO线程池中执行真实 Git 命令GitEngine封装。成功处理toast 成功提示 →afterSuccess?.invoke()→ 若refreshesVaultAfterSuccess则onVaultRefresh()。失败处理弹出失败 toastPull操作失败时仍刷新 Vault因为 pull 可能已部分改变工作区其余动作直接中止。收尾重新拉取GitEngine.status(root)与GitEngine.history(root)同步 remote 字段并调用onGitStatusChanged()同步工具栏上的 Git 变更数角标对齐 Electron 的refreshGitChangeCount。3.1 各按钮的挂载方式面板中每个操作按钮都通过workingTree参数声明自己的动作类型VaultGitDialog.kt按钮workingTree参数底层 Git 操作提交commitGitVaultFlushAction.CommitGitEngine.commit(root, message, identity())推送pushGitVaultFlushAction.PushGitEngine.push(root, token token())拉取pullGitVaultFlushAction.PullGitEngine.pull(root, token token())继续continueGitVaultFlushAction.ContinueOperationGitEngine.continueOperation(root, identity())丢弃discardGitVaultFlushAction.DiscardGitEngine.discard(root, discardPath)中止abortGitVaultFlushAction.AbortMergeGitEngine.abortMerge(root)解决冲突ours/theirsGitVaultFlushAction.ResolveConflictGitEngine.resolveConflict(root, path, ours/theirs)拉取远端fetch不传nullGitEngine.fetch(root, token token())初始化init不传nullGitEngine.init(root, identity())保存/删除 remote不传nullGitEngine.setRemote(root, committed)fetch/init/setRemote不传workingTreeshouldFlushEditor(null)返回false因此不会在操作前打扰正在编辑的缓冲区——这与打开面板时load()不 flush共同构成了只在必要时刷新的克制原则。四、onFlush()的两个实际实现JSON Vault 与随手记VaultGitDialog本身不关心编辑器细节onFlush是外部传入的回调onFlush: () - String?。项目里两个调用方分别注入各自的刷新逻辑。4.1 JSON VaultjsonGitFlushBeforeAction入口在 JsonScreen.kt实现于 JsonVaultSession.ktinternal fun jsonGitFlushBeforeAction( container: AppContainer, session: JsonSession, monitor: VaultRevisionMonitor?, onConflict: (VaultConflictState) - Unit, ): String? { val editorHasUserDraft session.editor.text.isNotBlank() session.editor.text ! JsonSession.SAMPLE_JSON JsonVaultFooterPresentation.gitUntitledBlockKey(session.currentFile, editorHasUserDraft)?.let { return container.t(it) } if (JsonVaultFooterPresentation.gitFlushSkipsWhenClean(session.currentFile, session.isVaultEditorDirty())) { return null } return saveJsonVault(container, session, monitor, showToast false, onConflict onConflict) .fold( onSuccess { null }, onFailure { it.message ?: container.t(json.notice.failed) }, ) }它的三层守卫逻辑对齐 ElectronprepareGitAction未命名文件守卫若currentFile为空且编辑器里有非示例的用户草稿返回未命名提示文案阻止 Git 操作——因为一个未命名文档无法确定写盘目标。干净文档跳过已命名文件且editor.text savedText即缓冲区与磁盘一致例如刚经过 250ms 自动保存直接返回null不写盘避免多余 IO 与不必要的 toast 打断对应 DIFF-332。脏内容才写盘调用saveJsonVault(..., showToast false)静默写盘避免 Git 操作前出现重复的保存成功提示写盘失败则返回错误文案Git 操作随之中止。4.2 随手记QuickNotequickNoteGitFlushBeforeAction入口在 QuickNoteScreen.kt实现于 QuickNoteVaultContext.ktinternal fun quickNoteGitFlushBeforeAction( container: AppContainer, session: QuickNoteSession, vault: NoteVault, monitor: VaultRevisionMonitor?, onConflict: (VaultConflictState) - Unit, ): String? { QuickNoteVaultFooterPresentation.gitUntitledBlockKey( session.currentFile, session.editor.text, QuickNoteSession.SAMPLE, )?.let { return container.t(it) } if (QuickNoteVaultFooterPresentation.gitFlushSkipsWhenClean(session.currentFile, quickNoteDirty(session))) { return null } if (!quickNoteVaultSaveIfNeeded(container, session, vault, monitor, onConflict)) { return session.error.ifBlank { container.t(quickNote.saveFailed) } } return null }与 JSON 版本结构一致唯一差异在脏判定随手记使用quickNoteDirty(session)它会同时考虑正文与 metadata对应 DIFF-366而 JSON 侧使用session.isVaultEditorDirty()。两者共同保证了 Git flush 跳过干净文档 的语义在两条产品线上的一致性。4.3 与 Electron 版prepareGitAction的对齐Compose 的这份策略并非凭空设计而是与 Electron 版本逐点对齐。Electron 端 JSON Vault 面板的对照实现位于 JsonVaultPanel.tsxasync function prepareGitAction(action: VaultGitAction): Promiseboolean { if (!dirty) return true if (action pull || action continue-operation) return saveSelected() return true }对照结论对齐点pull/continue-operation都刷新编辑器!dirty缓冲区干净时放行不写盘。增强点Compose 独有Compose 在commit/push前也刷新编辑器Electron 只在 pull/continue 前保存。这是 DIFF-184 的关键增强——Git 提交/推送前强制 flush保证staged files 与编辑器缓冲区一致。GitEditorFlushPolicy源码注释明确记录了这一点Electron prepareGitAction saves before pull/continue only. Compose additionally flushes before commit/push so staged files match the editor buffer.共同边界discard/abort-merge/resolve-conflict在两侧都不刷新编辑器DIFF-110 对齐结论。五、被刷新之后GitEngine的真实 Git 命令链onFlush()保证磁盘内容就绪后VaultGitDialog.runAction里的block()才真正执行。底层由 GitEngine.kt 封装全部通过ProcessBuilder调用gitCLI并以root.toAbsolutePath()为 key 加锁串行化避免并发 Git 进程冲突。各操作对应的核心命令有源码依据GitEngine 方法执行的核心命令前置校验commit→commitLockedL467-L485git add --all后git commit -m message消息非空截断 300 字符、仓库已初始化、无进行中合并、存在变更pushL255-L265git push -u origin HEAD仓库已初始化、无进行中合并/冲突、存在 origin remotepullL242-L253git pull --no-rebase origin仓库已初始化、非 merging、无冲突、配置了 remotefetchL238-L240git fetch --prune origin需 origin remotediscardL267-L287未跟踪文件用git clean -f已跟踪文件用git restore --staged --worktree --sourceHEAD失败回退git resetgit checkout路径存在且在工作区变更中其中 commit 的身份参数值得注意identityArgsL487-L491private fun identityArgs(identity: GitIdentity): ListString listOf( -c, user.name${identity.name.ifBlank { MooTool Next Compose }}, -c, user.email${identity.email.ifBlank { next-composelocal }}, -c, commit.gpgsignfalse )即每个 commit 都以-c注入user.name/user.email从设置中的vault.gitUsername派生未配置时使用默认占位值并显式关闭 GPG 签名commit.gpgsignfalse避免本机全局 Git 配置干扰 Vault 提交的确定性。六、演进历史从无条件 flush到策略化按需 flush这一节整理 flush 语义在next-compose/docs/diff系列文档中的演进脉络理解它有助于把握当前设计的边界DIFF-110「Git 面板 P5 与 flush 语义」确立discard/abort-merge/resolve-conflict执行前不再调用onFlush对齐 ElectronprepareGitActionpull/continue-operation仍先 flush。参见 110-git-panel-p5-flush.md。DIFF-184「提交/推送前刷新编辑器」本文主体把 flush 面扩展至commit/push并将全部规则收敛到GitEditorFlushPolicyGitVaultFlushAction。原文档当时还提到打开面板时load()仍会 flush 一次。DIFF-198「Git 面板打开/刷新不再隐式 flush」后续修订撤销了load()中的无条件onFlush()——Electron 的load仅拉取 status/history不调用prepareGitActionCompose 对齐后打开面板、点刷新按钮都不再触发保存/toast只在真正执行 pull / continue / commit / push 时才 flush。参见 198-git-panel-load-no-flush.md。DIFF-332「JSON Vault Git flush 跳过已保存文件」与DIFF-366「随手记 Vault Git flush 跳过干净文档」进一步优化onFlush内部实现缓冲区与磁盘一致时跳过写盘、脏时才静默保存避免多余 IO 与重复 toast。参见 332-json-git-flush-skip-clean.md 与 366-quicknote-git-flush-skip-clean.md。最终形成的设计原则可以概括为flush 只发生在即将执行依赖磁盘内容的写操作之前且只在缓冲区确实脏时落盘不刷新编辑器的操作discard/abort/resolve与不依赖工作区内容的操作fetch/init/setRemote/面板打开一律不打扰用户。七、验证方式单元测试与全量桌面测试DIFF-184 给出的验证手段有两层均可在仓库中复现。7.1GitEditorFlushPolicyTest策略的单元测试位于 GitEditorFlushPolicyTest.kt包含三个用例Test fun flushBeforePullCommitPushNotBeforeDiscard() { assertTrue(GitEditorFlushPolicy.shouldFlushEditor(GitVaultFlushAction.Pull)) assertTrue(GitEditorFlushPolicy.shouldFlushEditor(GitVaultFlushAction.Commit)) assertTrue(GitEditorFlushPolicy.shouldFlushEditor(GitVaultFlushAction.Push)) assertFalse(GitEditorFlushPolicy.shouldFlushEditor(GitVaultFlushAction.Discard)) } Test fun vaultRefreshAfterPullNotAfterCommit() { assertTrue(GitEditorFlushPolicy.refreshesVaultAfterSuccess(GitVaultFlushAction.Pull)) assertFalse(GitEditorFlushPolicy.refreshesVaultAfterSuccess(GitVaultFlushAction.Commit)) assertFalse(GitEditorFlushPolicy.refreshesVaultAfterSuccess(GitVaultFlushAction.Push)) } Test fun fetchAndInitDoNotImplicitlyFlush() { assertFalse(GitEditorFlushPolicy.shouldFlushEditor(null)) }三个用例恰好覆盖了策略决策表的三个关键分支要 flush 的动作、成功后要刷新 Vault 的动作、不隐式 flush 的动作null构成对GitEditorFlushPolicy的完整行为契约。7.2 全量桌面测试./gradlew :composeApp:desktopTest --offline在 DIFF-184 落地时全量桌面测试为342/342 全部通过该数字为文档记录的当时结果。此外随手记侧的 flush 跳过逻辑还有独立的QuickNoteGitFlushTest见 366-quicknote-git-flush-skip-clean.md。八、总结GitEditorFlushPolicyGitVaultFlushAction是 MooTool Next Compose 版本 Vault Git 能力中一条精密的数据一致性防线。它用一张 8 行决策表解决了编辑器缓冲区 vs 磁盘工作区在不同 Git 操作前的同步问题提交/推送前强制 flushCompose 对 Electron 的增强保证 staged files 与编辑器所见一致拉取/继续操作前 flush对齐 Electron 行为丢弃/中止/解决冲突前不 flush因为这些操作本身在改写工作区fetch/init/setRemote/打开面板不 flush不打扰纯查看场景。配合 JSON 与随手记两侧的未命名守卫 干净跳过 静默写盘实现以及GitEngine的 CLI 封装与加锁串行化这套机制在保证 Git 操作正确性的同时把对编辑体验的打扰降到了最低。对希望理解 Vault 数据一致性设计或复刻类似编辑器 Git集成方案的开发者这份策略与源码就是一份可直接参考的完整范本。赞分享桌面应用开发工具【免费下载链接】MooToolHandy tool set for developers项目地址https://gitcode.com/gh_mirrors/mo/MooTool点击查看免费下载相关推荐为什么2025年设计师都在用的标题字体Bebas Neue开源方案全解析为什么2025年设计师都在用的标题字体Bebas Neue开源方案全解析 在数字设计领域标题字体的选择往往决定了作品的视觉冲击力和专业程度。Bebas Ne桌面应用开发工具MooTool Next ComposeVault Git 面板 P5 布局与 flush 语义对齐实践DIFF-110MooTool Next ComposeVault Git 面板 P5 布局与 flush 语义对齐实践DIFF 110 本文基于 MooTool Nex桌面应用开发工具MooTool Next Compose Vault Git 中止合并确认机制解析DIFF-108MooTool Next Compose Vault Git 中止合并确认机制解析DIFF 108 本文以 MooTool 仓库中 next compose桌面应用开发工具上一篇RedTeam-OffensiveSecurity深度解析红队实战技术架构与攻防对抗艺术下一篇SO-VITS-SVC 5.03步实现专业级AI歌声转换的完整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考