close
Skip to content

Refresh git state on bare .git events - #59876

Merged
eholk merged 2 commits into
zed-industries:mainfrom
gb-jos:fix/git-refresh-on-dot-git-events
Jul 23, 2026
Merged

Refresh git state on bare .git events#59876
eholk merged 2 commits into
zed-industries:mainfrom
gb-jos:fix/git-refresh-on-dot-git-events

Conversation

@gb-jos

@gb-jos gb-jos commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Objective

Fix stale Git state in Zed when repository metadata changes are reported only as a bare .git directory event.

On macOS, file watcher events can be coalesced such that Git operations only surface as a Changed event for the .git directory itself, rather than individual events for files like .git/index or .git/HEAD. Zed previously ignored bare .git directory events before scheduling a Git metadata refresh, which could leave the Git panel showing stale changes or an outdated history even though git status / git log reflected the latest state.

Solution

Treat meaningful bare .git directory events as Git repository updates before skipping them from normal worktree scanning.

This preserves the existing behavior of not scanning .git as regular project content, while still notifying the Git repository tracking path that repository metadata may have changed. As a result, Git state such as HEAD and file statuses are refreshed when .git itself is the only watcher event.

Updated the existing worktree test expectations so bare .git events now trigger UpdatedGitRepositories, while skipped files like .git/index.lock still do not.

Added a test covering the full project/Git path:

  • repo initially has old HEAD and a modified file status
  • fake Git state is updated to represent a commit
  • only a bare .git Changed event is emitted
  • Zed refreshes the repository snapshot, observes the new HEAD, and clears the stale file status

Testing

The new tests cover the full project/Git path scenario described above. It fails without the fix and passes with the fix. Unfortunately, i was unable to reproduce the issue deterministically enough to test it end to end.

Self-Review Checklist:

  • I've reviewed my own diff for quality, security, and reliability
  • Unsafe blocks (if any) have justifying comments
  • The content adheres to Zed's UI standards (UX/UI and icon guidelines)
  • Tests cover the new/changed behavior
  • Performance impact has been considered and is acceptable

Release Notes:

  • Fixed stale Git panel state after repository metadata changes.

@cla-bot cla-bot Bot added the cla-signed The user has signed the Contributor License Agreement label Jun 25, 2026
@gb-jos
gb-jos force-pushed the fix/git-refresh-on-dot-git-events branch from 3497969 to 4322fcf Compare June 25, 2026 04:33
@yara-blue yara-blue added the area:integrations/git Git integration feedback label Jun 25, 2026

@mariogeiger mariogeiger left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's so short, it cannot be wrong (just trolling here)

@RemcoSmitsDev

Copy link
Copy Markdown
Collaborator

Hey, great work! This looks like a correct fix for the event-coalescing case (a bare .git Changed event being dropped before triggering a reload).

However, I want to flag that it doesn't cover a related failure mode I can still reproduce with this PR applied: when the FSEvents queue overflows, the watcher emits a Rescan event for the worktree root (filesystem watcher lost sync ... scheduling rescan in the log) instead of any .git events. That event never reaches the .git branch in process_events, so dot_git_abs_paths stays empty and the git state is never reloaded. The panel and branch indicator stay stale indefinitely until something else touches .git. There's also a second wrinkle: the forced rescan re-inserts the repository and resets git_dir_scan_id to 0, which can mask the reload signal.

This is what's happening in #60102, #60348 and #13176 (comment) (that last comment has good repro steps). I can trigger it 100% of the time by putting Zed under heavy fs-event pressure so the watcher loses sync (run a git branch switch + generate ~150k file events, git add ., resume). Happy to share the script.

Not a blocker for this PR. The two fixes are complementary. I'm preparing a PR for the rescan path (reload git state for repositories under a rescanned path + preserve the scan id across re-insertion) that fixes the git UI being out of sync.

…t-git-events

# Conflicts:
#	crates/worktree/src/worktree.rs
eholk
eholk approved these changes Jul 23, 2026
@eholk
eholk enabled auto-merge July 23, 2026 23:31
@eholk
eholk added this pull request to the merge queue Jul 23, 2026
Merged via the queue into zed-industries:main with commit 137c981 Jul 23, 2026
36 checks passed
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
InfyniteHeap pushed a commit to InfyniteHeap/zed that referenced this pull request Jul 25, 2026
…nts (zed-industries#61636)

Fixes an infinite git-rescan loop on Windows introduced by zed-industries#59876.

On Windows, creating or deleting a file directly inside .git updates the
directory's last-write time, so ReadDirectoryChangesW reports a bare
.git Changed event alongside the file's own event. Since zed-industries#59876, bare
.git events schedule a git rescan (to cope with coalesced FSEvents on
macOS), so every filtered-out lock-file event still triggered a rescan
through its paired bare event. Because a rescan's own `git diff
--numstat HEAD` can take .git/index.lock (even under
--no-optional-locks, e.g. in jj-colocated repos whose index never
refreshes clean), each rescan re-triggered the next one, spawning ~9 git
processes per cycle, ~4 cycles/sec, indefinitely.

Bare .git events are now deferred while processing an event batch and
only trigger a rescan when the batch contains no filtered event for the
same git dir that explains the directory change. A standalone bare .git
event (the macOS coalescing case zed-industries#59876 addressed) still triggers a
rescan, as does any batch containing a meaningful .git change.


Release Notes:

- N/A or Added/Fixed/Improved ...
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
0arm pushed a commit to 0arm/zed that referenced this pull request Jul 26, 2026
…nts (zed-industries#61636)

Fixes an infinite git-rescan loop on Windows introduced by zed-industries#59876.

On Windows, creating or deleting a file directly inside .git updates the
directory's last-write time, so ReadDirectoryChangesW reports a bare
.git Changed event alongside the file's own event. Since zed-industries#59876, bare
.git events schedule a git rescan (to cope with coalesced FSEvents on
macOS), so every filtered-out lock-file event still triggered a rescan
through its paired bare event. Because a rescan's own `git diff
--numstat HEAD` can take .git/index.lock (even under
--no-optional-locks, e.g. in jj-colocated repos whose index never
refreshes clean), each rescan re-triggered the next one, spawning ~9 git
processes per cycle, ~4 cycles/sec, indefinitely.

Bare .git events are now deferred while processing an event batch and
only trigger a rescan when the batch contains no filtered event for the
same git dir that explains the directory change. A standalone bare .git
event (the macOS coalescing case zed-industries#59876 addressed) still triggers a
rescan, as does any batch containing a meaningful .git change.


Release Notes:

- N/A or Added/Fixed/Improved ...
jolutz pushed a commit to jolutz/zed that referenced this pull request Aug 8, 2026
# Objective

Fix stale Git state in Zed when repository metadata changes are reported
only as a bare `.git` directory event.

On macOS, file watcher events can be coalesced such that Git operations
only surface as a `Changed` event for the `.git` directory itself,
rather than individual events for files like `.git/index` or
`.git/HEAD`. Zed previously ignored bare `.git` directory events before
scheduling a Git metadata refresh, which could leave the Git panel
showing stale changes or an outdated history even though `git status` /
`git log` reflected the latest state.

## Solution

Treat meaningful bare `.git` directory events as Git repository updates
before skipping them from normal worktree scanning.

This preserves the existing behavior of not scanning `.git` as regular
project content, while still notifying the Git repository tracking path
that repository metadata may have changed. As a result, Git state such
as `HEAD` and file statuses are refreshed when `.git` itself is the only
watcher event.

Updated the existing worktree test expectations so bare `.git` events
now trigger `UpdatedGitRepositories`, while skipped files like
`.git/index.lock` still do not.

Added a test covering the full project/Git path:

- repo initially has old `HEAD` and a modified file status
- fake Git state is updated to represent a commit
- only a bare `.git Changed` event is emitted
- Zed refreshes the repository snapshot, observes the new `HEAD`, and
clears the stale file status

## Testing

The new tests cover the full project/Git path scenario described above.
It fails without the fix and passes with the fix. Unfortunately, i was
unable to reproduce the issue deterministically enough to test it end to
end.

## 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 stale Git panel state after repository metadata changes.

Co-authored-by: Eric Holk <eric@zed.dev>
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
jolutz pushed a commit to jolutz/zed that referenced this pull request Aug 8, 2026
…nts (zed-industries#61636)

Fixes an infinite git-rescan loop on Windows introduced by zed-industries#59876.

On Windows, creating or deleting a file directly inside .git updates the
directory's last-write time, so ReadDirectoryChangesW reports a bare
.git Changed event alongside the file's own event. Since zed-industries#59876, bare
.git events schedule a git rescan (to cope with coalesced FSEvents on
macOS), so every filtered-out lock-file event still triggered a rescan
through its paired bare event. Because a rescan's own `git diff
--numstat HEAD` can take .git/index.lock (even under
--no-optional-locks, e.g. in jj-colocated repos whose index never
refreshes clean), each rescan re-triggered the next one, spawning ~9 git
processes per cycle, ~4 cycles/sec, indefinitely.

Bare .git events are now deferred while processing an event batch and
only trigger a rescan when the batch contains no filtered event for the
same git dir that explains the directory change. A standalone bare .git
event (the macOS coalescing case zed-industries#59876 addressed) still triggers a
rescan, as does any batch containing a meaningful .git change.


Release Notes:

- N/A or Added/Fixed/Improved ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:integrations/git Git integration feedback cla-signed The user has signed the Contributor License Agreement

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

5 participants