From 230a023ebf4def157777ea84e3594484f58ec978 Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Tue, 29 Sep 2026 19:31:55 +0800 Subject: [PATCH] fix(terminal): stop the git retry check from redrawing every poll tick (#1015) The retry added in #1011 went through update_global on every 300ms poll while a pane's repo was unanswered, even when the probe was still in flight or throttled and nothing was claimed. Each mutable touch of the GitStatusCache global wakes its observers, so the window redrew about three times a second for as long as a probe was pending or failing. That is what a_commit_detail_reads_its_own_files_and_draws_them caught on Linux CI (2 idle frames where 0 are expected). Ask whether a retry is due read-only (GitStatusCache::probe_due) and only touch the global when one is. --- src/terminal/git_status.rs | 29 ++++++++++++++++++++--------- src/terminal/view.rs | 23 ++++++++++++++--------- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/src/terminal/git_status.rs b/src/terminal/git_status.rs index 24b75e80..04cf6db6 100644 --- a/src/terminal/git_status.rs +++ b/src/terminal/git_status.rs @@ -87,21 +87,32 @@ impl GitStatusCache { cwd: &Path, min_interval: Duration, ) -> bool { + if !self.probe_due(host, cwd, min_interval) { + return false; + } + let cwd = key(host, cwd); + let throttle = self.throttle_key(host, &cwd).to_path_buf(); + self.last_probe.insert(host, throttle, Instant::now()); + self.probes.begin((host, cwd.into_owned())); + true + } + + /// Whether [`GitStatusCache::begin_probe_throttled`] would start a probe, + /// asked without claiming anything. + /// + /// For a caller polling on a timer: reaching this cache through + /// `update_global` wakes everything observing it even when the answer is + /// "not yet", and a pane asking three times a second redraws the window + /// three times a second to be told no. + pub fn probe_due(&self, host: HostId, cwd: &Path, min_interval: Duration) -> bool { let cwd = key(host, cwd); if self.probes.is_pending(&(host, cwd.to_path_buf())) { return false; } - let throttle = self.throttle_key(host, &cwd).to_path_buf(); - if self + !self .last_probe - .get(host, throttle.as_path()) + .get(host, self.throttle_key(host, &cwd)) .is_some_and(|at| at.elapsed() < min_interval) - { - return false; - } - self.last_probe.insert(host, throttle, Instant::now()); - self.probes.begin((host, cwd.into_owned())); - true } /// `cwd` is already in the cache's own spelling — every caller of this one diff --git a/src/terminal/view.rs b/src/terminal/view.rs index de4727a8..acab626b 100644 --- a/src/terminal/view.rs +++ b/src/terminal/view.rs @@ -2168,17 +2168,22 @@ impl TerminalView { } /// This pane sits somewhere the repo cache cannot place yet — neither a - /// repository nor "not one". A probe in flight looks the same, and the - /// throttle turns the retry away; the case this exists for is a probe that - /// failed, which leaves the answer open instead of recording a wrong one, - /// and would otherwise wait for the cwd to change or a command to finish - /// before anyone asked again. - fn git_repo_unanswered(&self, cx: &App) -> bool { + /// repository nor "not one" — and a retry is due. Only a failed probe + /// leaves a cwd that way; without this it would wait for the cwd to change + /// or a command to finish before anyone asked again. + /// + /// Read-only on purpose: this runs on every poll tick, and the cache is a + /// global whose every mutable touch redraws the window — while a probe is + /// in flight or throttled, the answer has to be "no" at no cost. + fn git_retry_due(&self, cx: &App) -> bool { let Some(cwd) = self.git_status_cwd.as_deref() else { return false; }; - cx.try_global::() - .is_none_or(|cache| cache.known_repo_for(self.host_id, cwd).is_none()) + let Some(cache) = cx.try_global::() else { + return false; + }; + cache.known_repo_for(self.host_id, cwd).is_none() + && cache.probe_due(self.host_id, cwd, GIT_RETRY_GAP) } /// Plant the cwd the git-status poll would have found. For tests that @@ -4035,7 +4040,7 @@ impl TerminalView { self.refresh_git_status(cwd_now, GitRefresh::Edge, cx); } else if tool_activity { self.refresh_git_status(cwd_now, GitRefresh::Opportunistic, cx); - } else if self.git_repo_unanswered(cx) { + } else if self.git_retry_due(cx) { self.refresh_git_status(cwd_now, GitRefresh::Retry, cx); }