fix(git): keep git status fresh when git metadata changes externally - #53
Merged
Conversation
Yager-42
marked this pull request as draft
August 11, 2026 04:57
Yager-42
force-pushed
the
feature/git-watch-context
branch
from
August 11, 2026 05:16
78837e4 to
260c0f1
Compare
Yager-42
marked this pull request as ready for review
August 11, 2026 05:33
2 tasks
Collaborator
|
这边个人文档不建议合入分支呢 |
Yager-42
force-pushed
the
feature/git-watch-context
branch
from
August 12, 2026 00:51
260c0f1 to
b7e0f98
Compare
1lck
reviewed
Aug 12, 2026
Owner
|
本 PR 新增了公开命令 |
Add git.watchContext command to the Rust core that resolves the repository root, git directory, and git common directory as canonical absolute paths. The Swift bridge exposes these through GitService and the directory watcher now observes the repository and Git metadata roots in addition to the workspace root. DirectoryChangeBatch routes filesystem events to workspace snapshot refreshes or Git status refreshes so worktree changes refresh Git without rescanning the whole tree, and Git metadata changes do not reload the workspace. Recovery events rebuild the watcher and rescan when watched roots change, and Git refreshes are coalesced through a pending-flag state machine that keeps the final request when a refresh is already running. Projects now start observing the workspace immediately, resolve the Git watch context afterward, and refresh Git unconditionally once loaded, which also picks up a repository initialized after the project was opened. The app re-resolves the watch context for every open project when it becomes active again.
Add real-repository observation tests covering linked worktrees, submodules, separate git directories, git init discovery, and git-only event routing. Unit tests cover watch-root deduplication, event classification, recovery state machines, and refresh coalescing. Rust integration tests exercise the git.watchContext command contract across ordinary, separate-dir, worktree, and submodule repositories.
watchRootsChanged/requiresFullRescan batches returned early and dropped their workspacePaths, so a merged FSEvents batch could skip document and snapshot refresh after recovery. Merge batch paths first, process them after a non-full recovery, and cover the watchRootsChanged + workspacePaths combination with a regression test.
git.watchContext is public surface shared by both products. Document its { root } request, the null response outside a repository, and the absolute repositoryRoot, gitDirectory, and gitCommonDirectory fields.
Yager-42
force-pushed
the
feature/git-watch-context
branch
from
August 12, 2026 11:43
7fa4439 to
d22ffca
Compare
1lck
approved these changes
Aug 12, 2026
1lck
left a comment
Owner
There was a problem hiding this comment.
Reviewed. The remaining full-rescan document refresh edge case is accepted as non-blocking for this PR.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #22
问题与根本原因
报告的现象:在终端执行
git commit/push后,Lithe 的 Git 面板停留在旧状态,直到手动刷新。issue已指出直接原因:外部提交改变
.git/index、HEAD和refs,而这些文件系统事件被过滤掉了。深入排查后,这个现象背后有四个架构层面的根本原因,本 PR 全部解决:
1. 监听根不覆盖 Git 元数据。 原有 watcher 只监听工作区根目录,而 Git 元数据经常位于工作区之外:linked worktree
的 gitdir/common dir、separate git-dir、以及位于父仓库
.git/modules/下的 submodule git 目录。即使.git在工作区内,其事件也会被隐藏路径规则过滤。→ 新增共享 Rust 命令
git.watchContext,解析并规范化仓库根目录、绝对 git目录和 git common 目录(覆盖普通仓库、linked worktree、submodule、separate
git-dir,以及从嵌套子目录打开的仓库);
MacDirectoryWatcher现在监听多个逻辑根,并自动去重物理根。2. 事件分类是单一的"工作区变化"模型。 旧实现把一切外部变化都当作工作区变化处理:一次微小的 Git 元数据写入(如
index更新)要么被过滤导致 Git 从不刷新,要么(若不过滤)触发不必要的整树重扫。→ 引入结构化DirectoryChangeBatch分类:Git 元数据路径只触发 Git 状态刷新;可见工作区路径触发快照刷新;仓库范围内的隐藏路径(如
dist/)只刷新Git;恢复类事件(
RootChanged、MustScanSubDirs、事件 ID 回绕、逻辑根被删除或重命名)重建 watcher 并整树重扫。3. Git 刷新没有独立的事件通道,刷新期间到达的请求会被丢弃。 高频 Git 活动(外部 commit 后紧跟 index
写入)会让新请求在刷新进行中丢失,Git 操作冻结期间到达的事件也没有补偿。→
WorkspaceFeatureModel新增专用的 Git刷新状态机:pending 标记合并突发事件,刷新进行中到达的请求随后重放(
GitFeatureModel同步支持);
begin/endGitOperationFreeze期间累积的事件在结束时一次性 flush,按"恢复 > 工作区路径 > 仅Git"的优先级顺序处理。
4. 初始化与生命周期缺陷。 watcher
只监听打开时已知的根,因此项目打开后才初始化的仓库永远不会被发现,从后台恢复后监听也可能失效。→
启动顺序改为:先启动工作区监听并加载快照,再解析 Git context 并无条件刷新一次
Git(由此发现打开后创建的仓库);应用回到前台时,每个打开的项目都会重新解析 context、重建 watcher 并刷新 Git。
另修复一个实现层面的隐患:FSEventStream 回调直接持有 watcher 实例的引用,watcher 重建时存在
use-after-free(完整测试套件曾因此随机崩溃)——回调改为持有弱引用的
CallbackContext,其生命周期由 stream 的retain/release 管理。
边界(明确不包含)
覆盖包含打开路径的单一仓库,包括其 worktree/submodule 元数据联动。
git.watchContext契约(共享层已就绪),平台 watcher 按分层架构在
windows/下另行实现。.git内部逐文件映射:.git下的任何变化都被视为"Git 状态可能已改变",通过git status快照重算,不做增量 diff(
git status自带缓存,成本可控)。RootChanged标志会触发整树重扫作为兜底,但不保证实时。
GitFeatureModel仍消费git.status快照;本 PR 只改变"何时刷新"。验证
swift test --disable-sandbox(133 个测试,含 linked worktree / submodule / separate git-dir / git init 发现 /仅 Git 事件路由的真实仓库观察测试)
swift buildcargo test --manifest-path rust/lithe-core/Cargo.toml(36 个单元 + 5 个git.watchContext集成测试)scripts/verify-rust-core.shcargo fmt --manifest-path rust/lithe-core/Cargo.toml --checkgit diff --checkscripts/verify-git-graph.sh/verify-shared-contracts.sh/verify-windows-boundaries.sh已知基线问题
scripts/verify-core.sh引用了已被移除的Sources/Lithe/Core/Terminal/TerminalBuffer.swift(SwiftTerm迁移时删除),且
scripts/verify-service-boundaries.sh报告Sources/Lithe/Core/RustWorkspaceOperations.swift中既有的
FileManager用法。两者在干净的preview/0.2.0上均可复现,与本 PR 无关。Result
以下四组截图按机制各取一个代表场景,覆盖本次修改的四种核心机制;其余同机制场景(外部 checkout/stash/merge、separate git-dir、直接打开 submodule、打开仓库子目录)由自动化测试覆盖(见测试清单,全部通过):
.git/index、HEAD、refs(即 issue [Bug] 在终端执行 git commit/push 后,Lithe 没有感知 .git 状态变化 #22原文)。旧实现中这些路径被隐藏规则过滤,现在监听解析出的 Git 目录,元数据变化只触发 Git 状态刷新;
位于打开的工作区之外(
.git/worktrees/<name>/),旧实现监听范围不覆盖。现在用git rev-parse解析出绝对 git-dir 与common-dir 作为额外监听根,外部
git add后面板自动更新;状态)时不重扫工作区。
DirectoryChangeBatch把事件分为 Git 元数据/仓库内隐藏路径/可见工作区/恢复类四类路由;git init,面板自动从 No Git切换为仓库状态。打开时先监听工作区、加载快照后无条件刷新一次 Git,
.git创建事件触发重建 watcher。Issue #22 场景:外部 commit 后 Git 面板自动刷新
在外部终端执行
git add+git commit前后,Changes 面板的变化(未手动刷新):截图 1(外部提交前,面板显示 README.md 已修改):
截图 2(外部提交后,面板自动变为无改动):
截图 3:
linked worktree 场景:index 在外部,git add 后面板自动从 Modified 变 Staged
工作区打开在 worktree 目录,
git rev-parse --git-path index显示 index 位于工作区之外;执行git add写入外部index 后面板自动刷新:
截图 1(git add 前,面板显示 Modified):
截图 2(git add 后,面板自动变为 Staged,附 rev-parse 输出证据):
父仓库 + submodule 场景:外部 commit 使 submodule HEAD 前进,父仓库面板自动刷新
外部在 submodule 内执行
git commit使其 HEAD 前进,父仓库的 Changes 面板自动刷新并显示 submodule已更新,无需手动刷新:
截图 1(提交前,父仓库面板无变化):
截图 2(外部提交后,面板显示 sub 更新):
截图 3:
打开项目后 git init 场景:外部初始化仓库后,面板自动发现
工作区打开的是一个普通目录(显示 No Git);外部终端执行
git init、git add、git commit后,面板自动发现新仓库并切换为正常 Git 状态,无需重开项目:
截图 1(git init 前,面板显示 No Git):
截图 2(git init 并提交后,面板显示正常仓库状态):
截图 3-5: