worktree: Avoid dropping git repositories during watcher rescans - #59976
Merged
ChristopherBiscardi merged 2 commits intoJun 29, 2026
Merged
Conversation
When the filesystem watcher loses sync (common on macOS under heavy churn, e.g. a dev build constantly rewriting `target/` and `.git/`), it forces a recursive rescan of the worktree root. That rescan removes the whole subtree from the snapshot before re-scanning it, and `remove_path_from_snapshot` was unconditionally pruning the worktree's git repositories along with it. Because the scanner publishes intermediate snapshots while it works, a snapshot could be published in the window after the repository was pruned but before `.git` was re-scanned. The `GitStore` would see the repository as removed, tear it down, and then re-create it with a fresh `RepositoryId` once it reappeared — churning the id on every watcher overflow and spamming `RepositoryUpdated` events to all consumers (git panel, git graph, blame, etc.). This change makes `remove_path_from_snapshot` prune git repositories only when the path was genuinely removed (`metadata == Ok(None)`), not during a recursive refresh where the subtree is about to be re-scanned. Stale repositories are still reaped authoritatively against the filesystem in `update_git_repositories`.
Collaborator
Author
|
Here a quick before/after profiling summary:
The fix roughly halves the |
ChristopherBiscardi
approved these changes
Jun 29, 2026
ChristopherBiscardi
left a comment
Contributor
There was a problem hiding this comment.
Thank you! This is a great PR. The screenrecordings, code comments, and benchmarking is very much appreciated.
This was referenced Jul 4, 2026
5 tasks
pull Bot
pushed a commit
to Jaleel-zhu/zed
that referenced
this pull request
Jul 24, 2026
…ed-industries#61541) # Objective When the OS file watcher loses sync (e.g. its event queue overflows under heavy fs churn), it drops pending events and reports a single `Rescan` event for the watched root. Git changes hidden behind such a rescan were silently lost, leaving the git panel and branch indicator stale until something else touched `.git`. Contributes to zed-industries#13176. May fix zed-industries#60102, though that report predates zed-industries#60660 and may already be addressed by it on nightly (see Related PRs below). Two bugs caused this, and they masked each other: 1. **A rescan never triggered a git reload.** The `Rescan` event's path is the worktree root, not something inside `.git`, so it never populated `dot_git_abs_paths` in `process_events` — and since `.git` is excluded from entry scanning, the rescan produced no `.git` events either. The dropped git changes were simply never picked up. 2. **Re-scanning reset `git_dir_scan_id` to 0.** The snapshot diff detects git changes by comparing scan ids, so re-inserting the repository with a fresh id could wipe out a bump made earlier in the same scan cycle, swallowing the corresponding `UpdatedGitRepositories` signal. The masking is why these fixes land together rather than as two PRs: the root-rescan case in `test_dot_git_dir_event_does_not_suppress_children` only passed on `main` because the buggy scan-id reset made the snapshot diff fire spuriously. Fixing either bug alone turns that (currently green) test red. ## Solution - `process_events`: a `Rescan` event now schedules a git state reload for every repository whose git directory (`dot_git`, `common_dir`, or `repository_dir`) lies under the rescanned path, covering linked worktrees and gitfile repositories, including git dirs watched outside the worktree root. - `insert_git_repository_for_path`: carry the existing `git_dir_scan_id` forward when re-inserting a repository instead of resetting it to 0. Deliberately *not* bumped either: re-insertion is snapshot bookkeeping, not evidence of a git change — bumping would trigger spurious full reloads on non-lossy paths (explicit refreshes, path-prefix scans). Only `update_git_repositories` claims that git state changed. - `changed_repos`: a `debug_assert` enforcing that `git_dir_scan_id` never regresses, so future violations of this invariant fail loudly in tests instead of manifesting as a stale git panel. ## Testing New fault-injection infrastructure and tests: - `FakeFs::simulate_watcher_overflow` models the kernel's watch queue overflowing: buffered (undelivered) events are discarded and replaced by a single `Rescan` for the given root, mirroring FSEvents `kFSEventStreamEventFlagMustScanSubDirs`, inotify `IN_Q_OVERFLOW`, and Windows `ERROR_NOTIFY_ENUM_DIR`. - `test_watcher_overflow_rescan_reloads_git_state`: a git change whose events are lost to an overflow must still be picked up via the rescan (reproduces bug 1; fails on `main`). - `test_git_update_in_same_batch_as_rescan_is_not_lost`: a git event processed in the same batch as a rescan must not lose its scan-id bump to the repository re-insertion (reproduces bug 2; fails on `main`). - `test_random_git_updates_with_watcher_overflows`: randomized property test (100 iterations) asserting that every git state change is eventually signaled via `UpdatedGitRepositories` under random event batching, delays, and overflows. Fails on `main` within the first few seeds. - `test_random_worktree_changes` now also injects watcher overflows, extending its existing convergence property to rescan reconciliation of worktree entries (this already passed; the injection guards it going forward). Full `worktree` and `fs` suites pass. Verified the directed tests exercise the intended code paths via trace logging (the same-batch test hits `update_git_repositories` stamping followed by re-insertion, distinct from the overflow test where no `.git` event arrives at all). ## Related PRs The recent stale-git-state reports trace back to three distinct mechanisms that share one symptom. This PR addresses the third: - **Events never generated** — zed-industries#60660 (merged, in this PR's base): Linux's non-recursive watcher missed nested `refs/` directories, so external commits/fetches produced no events at all. That PR (together with zed-industries#60590, which explicitly rescans after Zed-initiated reset/fetch and touches only `git_store.rs`) fixed zed-industries#60348. No overlap with this PR; a merge against current `main` is clean, and the refs-watching tests are disjoint from the overflow/rescan tests added here. - **Events coalesced** — zed-industries#59876 (open, complementary): FSEvents can merge `.git` child events into a bare `.git` `Changed` event; the signal arrives, in a shape Zed ignores. @RemcoSmitsDev's review comment there describes this PR's failure mode and calls the two fixes complementary; this is effectively the follow-up promised in that comment. Both PRs touch the same region of `process_events`, so whichever lands second needs a trivial rebase, and zed-industries#59876 flips the bare-`.git` expectation in `test_dot_git_dir_event_does_not_suppress_children` Case 2, which this PR preserves. - **Events dropped** — this PR: the watcher generated events but lost them to a queue overflow, and the resulting `Rescan` did not reach the git reload path. zed-industries#59976 and zed-industries#60098 (merged) reduced how often this happens; this PR makes git state recover correctly when it does. ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - Fixed the git panel and branch indicator showing stale state after heavy file-system activity caused the file watcher to lose events
0arm
pushed a commit
to 0arm/zed
that referenced
this pull request
Jul 26, 2026
…ed-industries#61541) # Objective When the OS file watcher loses sync (e.g. its event queue overflows under heavy fs churn), it drops pending events and reports a single `Rescan` event for the watched root. Git changes hidden behind such a rescan were silently lost, leaving the git panel and branch indicator stale until something else touched `.git`. Contributes to zed-industries#13176. May fix zed-industries#60102, though that report predates zed-industries#60660 and may already be addressed by it on nightly (see Related PRs below). Two bugs caused this, and they masked each other: 1. **A rescan never triggered a git reload.** The `Rescan` event's path is the worktree root, not something inside `.git`, so it never populated `dot_git_abs_paths` in `process_events` — and since `.git` is excluded from entry scanning, the rescan produced no `.git` events either. The dropped git changes were simply never picked up. 2. **Re-scanning reset `git_dir_scan_id` to 0.** The snapshot diff detects git changes by comparing scan ids, so re-inserting the repository with a fresh id could wipe out a bump made earlier in the same scan cycle, swallowing the corresponding `UpdatedGitRepositories` signal. The masking is why these fixes land together rather than as two PRs: the root-rescan case in `test_dot_git_dir_event_does_not_suppress_children` only passed on `main` because the buggy scan-id reset made the snapshot diff fire spuriously. Fixing either bug alone turns that (currently green) test red. ## Solution - `process_events`: a `Rescan` event now schedules a git state reload for every repository whose git directory (`dot_git`, `common_dir`, or `repository_dir`) lies under the rescanned path, covering linked worktrees and gitfile repositories, including git dirs watched outside the worktree root. - `insert_git_repository_for_path`: carry the existing `git_dir_scan_id` forward when re-inserting a repository instead of resetting it to 0. Deliberately *not* bumped either: re-insertion is snapshot bookkeeping, not evidence of a git change — bumping would trigger spurious full reloads on non-lossy paths (explicit refreshes, path-prefix scans). Only `update_git_repositories` claims that git state changed. - `changed_repos`: a `debug_assert` enforcing that `git_dir_scan_id` never regresses, so future violations of this invariant fail loudly in tests instead of manifesting as a stale git panel. ## Testing New fault-injection infrastructure and tests: - `FakeFs::simulate_watcher_overflow` models the kernel's watch queue overflowing: buffered (undelivered) events are discarded and replaced by a single `Rescan` for the given root, mirroring FSEvents `kFSEventStreamEventFlagMustScanSubDirs`, inotify `IN_Q_OVERFLOW`, and Windows `ERROR_NOTIFY_ENUM_DIR`. - `test_watcher_overflow_rescan_reloads_git_state`: a git change whose events are lost to an overflow must still be picked up via the rescan (reproduces bug 1; fails on `main`). - `test_git_update_in_same_batch_as_rescan_is_not_lost`: a git event processed in the same batch as a rescan must not lose its scan-id bump to the repository re-insertion (reproduces bug 2; fails on `main`). - `test_random_git_updates_with_watcher_overflows`: randomized property test (100 iterations) asserting that every git state change is eventually signaled via `UpdatedGitRepositories` under random event batching, delays, and overflows. Fails on `main` within the first few seeds. - `test_random_worktree_changes` now also injects watcher overflows, extending its existing convergence property to rescan reconciliation of worktree entries (this already passed; the injection guards it going forward). Full `worktree` and `fs` suites pass. Verified the directed tests exercise the intended code paths via trace logging (the same-batch test hits `update_git_repositories` stamping followed by re-insertion, distinct from the overflow test where no `.git` event arrives at all). ## Related PRs The recent stale-git-state reports trace back to three distinct mechanisms that share one symptom. This PR addresses the third: - **Events never generated** — zed-industries#60660 (merged, in this PR's base): Linux's non-recursive watcher missed nested `refs/` directories, so external commits/fetches produced no events at all. That PR (together with zed-industries#60590, which explicitly rescans after Zed-initiated reset/fetch and touches only `git_store.rs`) fixed zed-industries#60348. No overlap with this PR; a merge against current `main` is clean, and the refs-watching tests are disjoint from the overflow/rescan tests added here. - **Events coalesced** — zed-industries#59876 (open, complementary): FSEvents can merge `.git` child events into a bare `.git` `Changed` event; the signal arrives, in a shape Zed ignores. @RemcoSmitsDev's review comment there describes this PR's failure mode and calls the two fixes complementary; this is effectively the follow-up promised in that comment. Both PRs touch the same region of `process_events`, so whichever lands second needs a trivial rebase, and zed-industries#59876 flips the bare-`.git` expectation in `test_dot_git_dir_event_does_not_suppress_children` Case 2, which this PR preserves. - **Events dropped** — this PR: the watcher generated events but lost them to a queue overflow, and the resulting `Rescan` did not reach the git reload path. zed-industries#59976 and zed-industries#60098 (merged) reduced how often this happens; this PR makes git state recover correctly when it does. ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - Fixed the git panel and branch indicator showing stale state after heavy file-system activity caused the file watcher to lose events
jolutz
pushed a commit
to jolutz/zed
that referenced
this pull request
Aug 8, 2026
…-industries#59976) Related to zed-industries#59610. # Objective When the filesystem watcher loses sync (common on macOS under heavy churn, e.g. a dev build constantly rewriting `target/` and `.git/`), it forces a recursive rescan of the worktree root. That rescan removes the whole subtree from the snapshot before re-scanning it, and `remove_path_from_snapshot` was unconditionally pruning the worktree's git repositories along with it. Because the scanner publishes intermediate snapshots while it works, a snapshot could be published in the window after the repository was pruned but before `.git` was re-scanned. The `GitStore` would see the repository as removed, tear it down, and then re-create it with a fresh `RepositoryId` once it reappeared — churning the id on every watcher overflow and spamming `RepositoryUpdated` events to all consumers (git panel, git graph, blame, etc.). Here some debug logs, **See** that the **repoId** inside the git graph event is different and is **8** now, even though I only have one repository and didn't do any git related updates. ``` 2026-06-26T18:38:44+02:00 WARN [fs::fs_watcher] filesystem watcher lost sync for Native; scheduling rescans for 11 registrations 2026-06-26T18:38:44+02:00 WARN [fs::fs_watcher] filesystem watcher lost sync for many files, not logging more 2026-06-26T18:38:44+02:00 WARN [fs::fs_watcher] filesystem watcher lost sync for many files, not logging more 2026-06-26T18:38:44+02:00 WARN [fs::fs_watcher] filesystem watcher lost sync for many files, not logging more 2026-06-26T18:38:44+02:00 WARN [fs::fs_watcher] filesystem watcher lost sync for many files, not logging more 2026-06-26T18:38:44+02:00 WARN [fs::fs_watcher] filesystem watcher lost sync for many files, not logging more 2026-06-26T18:38:44+02:00 WARN [fs::fs_watcher] filesystem watcher lost sync for many files, not logging more 2026-06-26T18:38:44+02:00 WARN [fs::fs_watcher] filesystem watcher lost sync for many files, not logging more 2026-06-26T18:38:44+02:00 WARN [fs::fs_watcher] filesystem watcher lost sync for many files, not logging more 2026-06-26T18:38:44+02:00 WARN [fs::fs_watcher] filesystem watcher lost sync for many files, not logging more 2026-06-26T18:38:44+02:00 WARN [fs::fs_watcher] filesystem watcher lost sync for many files, not logging more 2026-06-26T18:38:44+02:00 WARN [fs::fs_watcher] filesystem watcher lost sync for many files, not logging more 2026-06-26T18:38:44+02:00 INFO [git::repository] opening git repository at "/Users/remcosmits/Documents/code/zed/.git" using git binary "/opt/homebrew/bin/git" 2026-06-26T18:38:44+02:00 ERROR [crates/git_ui/src/git_panel.rs:3892] oneshot canceled 2026-06-26T18:38:44+02:00 ERROR [crates/git_ui/src/git_panel.rs:3892] oneshot canceled 2026-06-26T18:38:44+02:00 ERROR [crates/git_ui/src/git_panel.rs:3892] oneshot canceled 2026-06-26T18:38:44+02:00 ERROR [crates/git_ui/src/git_panel.rs:3892] oneshot canceled 2026-06-26T18:38:44+02:00 ERROR [crates/zed/src/main.rs:1991] Is a directory (os error 21) 2026-06-26T18:38:44+02:00 INFO [project::prettier_store] Prettier config file ".prettierrc" changed, reloading prettier instances for worktree 1 2026-06-26T18:38:45+02:00 INFO [project::prettier_store] Prettier config file ".prettierrc" changed, reloading prettier instances for worktree 1 2026-06-26T18:38:45+02:00 INFO [git::repository] opening git repository at "/Users/remcosmits/Documents/code/zed/.git" using git binary "/opt/homebrew/bin/git" 2026-06-26T18:38:45+02:00 ERROR [crates/git_ui/src/git_panel.rs:3892] oneshot canceled 2026-06-26T18:38:45+02:00 ERROR [crates/git_ui/src/git_panel.rs:3892] oneshot canceled [crates/git_ui/src/git_graph.rs:1468:21] &this.repo_id = RepositoryId( 1, ) [crates/git_ui/src/git_graph.rs:1468:21] "other repo id" = "other repo id" [crates/git_ui/src/git_graph.rs:1468:21] &updated_repo_id = RepositoryId( 8, ) [crates/git_ui/src/git_graph.rs:1468:21] &this.repo_id = RepositoryId( 1, ) [crates/git_ui/src/git_graph.rs:1468:21] "other repo id" = "other repo id" [crates/git_ui/src/git_graph.rs:1468:21] &updated_repo_id = RepositoryId( 8, ) [crates/git_ui/src/git_graph.rs:1468:21] &this.repo_id = RepositoryId( 1, ) [crates/git_ui/src/git_graph.rs:1468:21] "other repo id" = "other repo id" [crates/git_ui/src/git_graph.rs:1468:21] &updated_repo_id = RepositoryId( 8, ) [crates/git_ui/src/git_graph.rs:1468:21] &this.repo_id = RepositoryId( 1, ) ``` ## Solution This change makes `remove_path_from_snapshot` prune git repositories only when the path was genuinely removed (`metadata == Ok(None)`), not during a recursive refresh where the subtree is about to be re-scanned. Stale repositories are still reaped authoritatively against the filesystem in `update_git_repositories`. ## Testing It seems to be that I'm the only that can reproduce this issue @Anthony-Eid tried reproducing this but couldn't. **Note**: that I dindn't write a regression test since this is a async timing issue that I couldn't figure out to write a test for. Steps how I can reproduce this: 1. Run `cargo run` 2. Open `Zed` as only project 3. Open the git graph 4. Open a rust file e.g. `git_graph.rs` 5. CTRL-C inside the terminal to kill Zed 6. Run `cargo run` see that you have git graph open and a rust file, note that cargo check is doing it's thing. 7. See within a few seconds that Zed's UI flickers (especially the project panel & nav bar with the selected repo & branch) ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [ ] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable ## Showcase **Note**: This PR only fixes that we don't send as many git store events, so this results in not having to update the nav bar where the selected repo & branch on every event (that is duplicated in a way). Result of that we now also fixed a rare case that the git graph commit entries where gone, this was because we incremented the **repoId** and the graph couldn't find the repo anymore with the id that it has stored on the gitGraph struct itself. **Before** See that the nav bar with selected repo & branch flickers and the git graph commits are empty. https://github.com/user-attachments/assets/0957be7f-e732-4fd0-a950-29c496b6407d See that the git panel entries flicker and we have an temporary empty panel. https://github.com/user-attachments/assets/3b70e45c-c720-4009-aefa-696986d731b4 **After** See that the nav bar with selected repo & branch does not flicker anymore and that the git graph keeps showing the commits. https://github.com/user-attachments/assets/6e8a74f6-4a8e-4b28-ad30-574c2f33c6d9 See that the git panel does not show an empy state and does not flicker anymore. https://github.com/user-attachments/assets/1df7aacf-b337-4b81-b195-e67d8554d9da --- cc @Anthony-Eid Since we where debugging this yesterday. Release Notes: - Fixed git repositories being repeatedly torn down and re-created when the filesystem watcher forced a rescan, which caused redundant git status refreshes and UI churn.
jolutz
pushed a commit
to jolutz/zed
that referenced
this pull request
Aug 8, 2026
…ed-industries#61541) # Objective When the OS file watcher loses sync (e.g. its event queue overflows under heavy fs churn), it drops pending events and reports a single `Rescan` event for the watched root. Git changes hidden behind such a rescan were silently lost, leaving the git panel and branch indicator stale until something else touched `.git`. Contributes to zed-industries#13176. May fix zed-industries#60102, though that report predates zed-industries#60660 and may already be addressed by it on nightly (see Related PRs below). Two bugs caused this, and they masked each other: 1. **A rescan never triggered a git reload.** The `Rescan` event's path is the worktree root, not something inside `.git`, so it never populated `dot_git_abs_paths` in `process_events` — and since `.git` is excluded from entry scanning, the rescan produced no `.git` events either. The dropped git changes were simply never picked up. 2. **Re-scanning reset `git_dir_scan_id` to 0.** The snapshot diff detects git changes by comparing scan ids, so re-inserting the repository with a fresh id could wipe out a bump made earlier in the same scan cycle, swallowing the corresponding `UpdatedGitRepositories` signal. The masking is why these fixes land together rather than as two PRs: the root-rescan case in `test_dot_git_dir_event_does_not_suppress_children` only passed on `main` because the buggy scan-id reset made the snapshot diff fire spuriously. Fixing either bug alone turns that (currently green) test red. ## Solution - `process_events`: a `Rescan` event now schedules a git state reload for every repository whose git directory (`dot_git`, `common_dir`, or `repository_dir`) lies under the rescanned path, covering linked worktrees and gitfile repositories, including git dirs watched outside the worktree root. - `insert_git_repository_for_path`: carry the existing `git_dir_scan_id` forward when re-inserting a repository instead of resetting it to 0. Deliberately *not* bumped either: re-insertion is snapshot bookkeeping, not evidence of a git change — bumping would trigger spurious full reloads on non-lossy paths (explicit refreshes, path-prefix scans). Only `update_git_repositories` claims that git state changed. - `changed_repos`: a `debug_assert` enforcing that `git_dir_scan_id` never regresses, so future violations of this invariant fail loudly in tests instead of manifesting as a stale git panel. ## Testing New fault-injection infrastructure and tests: - `FakeFs::simulate_watcher_overflow` models the kernel's watch queue overflowing: buffered (undelivered) events are discarded and replaced by a single `Rescan` for the given root, mirroring FSEvents `kFSEventStreamEventFlagMustScanSubDirs`, inotify `IN_Q_OVERFLOW`, and Windows `ERROR_NOTIFY_ENUM_DIR`. - `test_watcher_overflow_rescan_reloads_git_state`: a git change whose events are lost to an overflow must still be picked up via the rescan (reproduces bug 1; fails on `main`). - `test_git_update_in_same_batch_as_rescan_is_not_lost`: a git event processed in the same batch as a rescan must not lose its scan-id bump to the repository re-insertion (reproduces bug 2; fails on `main`). - `test_random_git_updates_with_watcher_overflows`: randomized property test (100 iterations) asserting that every git state change is eventually signaled via `UpdatedGitRepositories` under random event batching, delays, and overflows. Fails on `main` within the first few seeds. - `test_random_worktree_changes` now also injects watcher overflows, extending its existing convergence property to rescan reconciliation of worktree entries (this already passed; the injection guards it going forward). Full `worktree` and `fs` suites pass. Verified the directed tests exercise the intended code paths via trace logging (the same-batch test hits `update_git_repositories` stamping followed by re-insertion, distinct from the overflow test where no `.git` event arrives at all). ## Related PRs The recent stale-git-state reports trace back to three distinct mechanisms that share one symptom. This PR addresses the third: - **Events never generated** — zed-industries#60660 (merged, in this PR's base): Linux's non-recursive watcher missed nested `refs/` directories, so external commits/fetches produced no events at all. That PR (together with zed-industries#60590, which explicitly rescans after Zed-initiated reset/fetch and touches only `git_store.rs`) fixed zed-industries#60348. No overlap with this PR; a merge against current `main` is clean, and the refs-watching tests are disjoint from the overflow/rescan tests added here. - **Events coalesced** — zed-industries#59876 (open, complementary): FSEvents can merge `.git` child events into a bare `.git` `Changed` event; the signal arrives, in a shape Zed ignores. @RemcoSmitsDev's review comment there describes this PR's failure mode and calls the two fixes complementary; this is effectively the follow-up promised in that comment. Both PRs touch the same region of `process_events`, so whichever lands second needs a trivial rebase, and zed-industries#59876 flips the bare-`.git` expectation in `test_dot_git_dir_event_does_not_suppress_children` Case 2, which this PR preserves. - **Events dropped** — this PR: the watcher generated events but lost them to a queue overflow, and the resulting `Rescan` did not reach the git reload path. zed-industries#59976 and zed-industries#60098 (merged) reduced how often this happens; this PR makes git state recover correctly when it does. ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - Fixed the git panel and branch indicator showing stale state after heavy file-system activity caused the file watcher to lose events
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.
Related to #59610.
Objective
When the filesystem watcher loses sync (common on macOS under heavy churn, e.g. a dev build constantly rewriting
target/and.git/), it forces a recursive rescan of the worktree root. That rescan removes the whole subtree from the snapshot before re-scanning it, andremove_path_from_snapshotwas unconditionally pruning the worktree's git repositories along with it.Because the scanner publishes intermediate snapshots while it works, a snapshot could be published in the window after the repository was pruned but before
.gitwas re-scanned. TheGitStorewould see the repository as removed, tear it down, and then re-create it with a freshRepositoryIdonce it reappeared — churning the id on every watcher overflow and spammingRepositoryUpdatedevents to all consumers (git panel, git graph, blame, etc.).Here some debug logs,
See that the repoId inside the git graph event is different and is 8 now, even though I only have one repository and didn't do any git related updates.
Solution
This change makes
remove_path_from_snapshotprune git repositories only when the path was genuinely removed (metadata == Ok(None)), not during a recursive refresh where the subtree is about to be re-scanned. Stale repositories are still reaped authoritatively against the filesystem inupdate_git_repositories.Testing
It seems to be that I'm the only that can reproduce this issue @Anthony-Eid tried reproducing this but couldn't.
Note: that I dindn't write a regression test since this is a async timing issue that I couldn't figure out to write a test for.
Steps how I can reproduce this:
cargo runZedas only projectgit_graph.rscargo runsee that you have git graph open and a rust file, note that cargo check is doing it's thing.Self-Review Checklist:
Showcase
Note: This PR only fixes that we don't send as many git store events, so this results in not having to update the nav bar where the selected repo & branch on every event (that is duplicated in a way). Result of that we now also fixed a rare case that the git graph commit entries where gone, this was because we incremented the repoId and the graph couldn't find the repo anymore with the id that it has stored on the gitGraph struct itself.
Before
See that the nav bar with selected repo & branch flickers and the git graph commits are empty.
Screen.Recording.2026-06-26.at.18.18.18.mov
See that the git panel entries flicker and we have an temporary empty panel.
Screen.Recording.2026-06-27.at.11.49.37.mov
After
See that the nav bar with selected repo & branch does not flicker anymore and that the git graph keeps showing the commits.
Screen.Recording.2026-06-26.at.20.03.30.mov
See that the git panel does not show an empy state and does not flicker anymore.
Screen.Recording.2026-06-27.at.11.52.36.mov
cc @Anthony-Eid Since we where debugging this yesterday.
Release Notes: