mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-11-06 00:15:06 +00:00
fix(sidebar): retry negative repository probes for idle panes (#1157)
This commit is contained in:
@@ -115,6 +115,13 @@ impl GitStatusCache {
|
||||
.is_some_and(|at| at.elapsed() < min_interval)
|
||||
}
|
||||
|
||||
/// Recheck unresolved and negative answers: repository metadata can be
|
||||
/// temporarily unreadable, or a repository can be created in an idle cwd.
|
||||
pub fn retry_due(&self, host: HostId, cwd: &Path, min_interval: Duration) -> bool {
|
||||
!matches!(self.roots.get(host, &*key(host, cwd)), Some(Some(_)))
|
||||
&& self.probe_due(host, cwd, min_interval)
|
||||
}
|
||||
|
||||
/// `cwd` is already in the cache's own spelling — every caller of this one
|
||||
/// has been past [`key`].
|
||||
fn throttle_key<'a>(&'a self, host: HostId, cwd: &'a Path) -> &'a Path {
|
||||
@@ -195,7 +202,9 @@ impl GitStatusCache {
|
||||
let rerun = !self.probes.finish(&(host, cwd.to_path_buf()));
|
||||
let throttle = match &snapshot {
|
||||
Some(snap) => snap.root.clone(),
|
||||
None => self.throttle_key(host, &cwd).to_path_buf(),
|
||||
// A negative result removes the cwd-to-root mapping below, so
|
||||
// subsequent retries are throttled by cwd, not the old root.
|
||||
None => cwd.to_path_buf(),
|
||||
};
|
||||
self.last_probe.insert(host, throttle, Instant::now());
|
||||
match snapshot {
|
||||
@@ -430,6 +439,54 @@ mod tests {
|
||||
assert!(cache.status_for(L, b).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_negative_probe_retries_and_recovers_without_a_cwd_change() {
|
||||
let mut cache = GitStatusCache::default();
|
||||
let cwd = Path::new("/repo/src");
|
||||
let gap = Duration::from_secs(10);
|
||||
cache.finish_probe(L, cwd, Some(snap("/repo", "main", None)));
|
||||
assert!(!cache.retry_due(L, cwd, Duration::ZERO));
|
||||
|
||||
// Discovery temporarily cannot recognize the repository.
|
||||
cache.begin_probe(L, cwd);
|
||||
cache.finish_probe(L, cwd, None);
|
||||
assert_eq!(cache.known_repo_for(L, cwd), Some(None));
|
||||
assert!(
|
||||
!cache.retry_due(L, cwd, gap),
|
||||
"negative answers are throttled by cwd"
|
||||
);
|
||||
cache
|
||||
.last_probe
|
||||
.insert(L, cwd.to_path_buf(), Instant::now() - gap);
|
||||
assert!(cache.retry_due(L, cwd, gap));
|
||||
assert!(cache.begin_probe_throttled(L, cwd, gap));
|
||||
assert!(
|
||||
!cache.retry_due(L, cwd, Duration::ZERO),
|
||||
"only one retry in flight"
|
||||
);
|
||||
|
||||
cache.finish_probe(L, cwd, Some(snap("/repo", "main", None)));
|
||||
assert_eq!(
|
||||
cache.known_repo_for(L, cwd),
|
||||
Some(Some(PathBuf::from("/repo")))
|
||||
);
|
||||
assert!(!cache.retry_due(L, cwd, Duration::ZERO));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_plain_directory_is_rechecked_after_the_retry_interval() {
|
||||
let mut cache = GitStatusCache::default();
|
||||
let cwd = Path::new("/plain");
|
||||
let gap = Duration::from_secs(10);
|
||||
assert!(cache.retry_due(L, cwd, gap));
|
||||
cache.finish_probe(L, cwd, None);
|
||||
assert!(!cache.retry_due(L, cwd, gap));
|
||||
cache
|
||||
.last_probe
|
||||
.insert(L, cwd.to_path_buf(), Instant::now() - gap);
|
||||
assert!(cache.retry_due(L, cwd, gap));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_failed_probe_leaves_what_was_known() {
|
||||
let mut cache = GitStatusCache::default();
|
||||
|
||||
+7
-12
@@ -679,11 +679,9 @@ const INTEGRATION_NOTICE_TIMEOUT: std::time::Duration = std::time::Duration::fro
|
||||
|
||||
const OPPORTUNISTIC_GIT_GAP: std::time::Duration = std::time::Duration::from_millis(1500);
|
||||
|
||||
/// How long a pane whose repository is still unanswered waits before asking
|
||||
/// again. Only a probe that failed leaves it that way, and whatever broke it
|
||||
/// (a remote link mid-reconnect, a `git` that would not start) is gone in
|
||||
/// seconds, not milliseconds — nor worth a request every poll tick while it
|
||||
/// lasts.
|
||||
/// How long a pane with an unknown or negative repository answer waits
|
||||
/// before asking again. Discovery can miss temporarily unavailable metadata;
|
||||
/// retry without issuing a request on every poll tick.
|
||||
const GIT_RETRY_GAP: std::time::Duration = std::time::Duration::from_secs(10);
|
||||
|
||||
/// How long a title the program set has to stand before the tab adopts it.
|
||||
@@ -733,7 +731,7 @@ enum GitRefresh {
|
||||
Edge,
|
||||
Opportunistic,
|
||||
/// Nothing happened; the cache just has no answer for this pane's cwd
|
||||
/// yet, because the last probe failed. Throttled by [`GIT_RETRY_GAP`].
|
||||
/// yet, or the last probe found no repository. Throttled by [`GIT_RETRY_GAP`].
|
||||
Retry,
|
||||
}
|
||||
|
||||
@@ -2274,10 +2272,8 @@ impl TerminalView {
|
||||
self.git_status_cwd.as_deref()
|
||||
}
|
||||
|
||||
/// This pane sits somewhere the repo cache cannot place yet — neither a
|
||||
/// 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.
|
||||
/// Retry unknown and negative repo answers. A transient discovery failure
|
||||
/// must not leave an idle pane in Ungrouped until it moves or restarts.
|
||||
///
|
||||
/// 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
|
||||
@@ -2289,8 +2285,7 @@ impl TerminalView {
|
||||
let Some(cache) = cx.try_global::<crate::terminal::git_status::GitStatusCache>() else {
|
||||
return false;
|
||||
};
|
||||
cache.known_repo_for(self.host_id, cwd).is_none()
|
||||
&& cache.probe_due(self.host_id, cwd, GIT_RETRY_GAP)
|
||||
cache.retry_due(self.host_id, cwd, GIT_RETRY_GAP)
|
||||
}
|
||||
|
||||
/// Plant the cwd the git-status poll would have found. For tests that
|
||||
|
||||
Reference in New Issue
Block a user