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); }