(
watcher: &GitWatcher,
_global_db_path: Option<&Path>,
last_gc: &mut Option<Instant>,
gc_period: Duration,
)
| 836 | } |
| 837 | |
| 838 | async fn tick( |
| 839 | watcher: &GitWatcher, |
| 840 | _global_db_path: Option<&Path>, |
| 841 | last_gc: &mut Option<Instant>, |
| 842 | gc_period: Duration, |
| 843 | ) { |
| 844 | let interval_secs = watcher |
| 845 | .inner |
| 846 | .config |
| 847 | .backstop_interval_mins |
| 848 | .saturating_mul(60); |
| 849 | let opts = TraceDecayOpenOptions::default(); |
| 850 | |
| 851 | // Snapshot registered projects; cover those the watcher isn't keeping |
| 852 | // fresh (stale/absent heartbeat) AND whose store is older than one |
| 853 | // interval. |
| 854 | let entries: Vec<(PathBuf, Arc<WatchState>)> = { |
| 855 | let projects = watcher.inner.projects.lock().await; |
| 856 | projects |
| 857 | .iter() |
| 858 | .map(|(root, state)| (root.clone(), Arc::clone(state))) |
| 859 | .collect() |
| 860 | }; |
| 861 | |
| 862 | let run_gc_now = last_gc.is_none_or(|t| t.elapsed() >= gc_period); |
| 863 | |
| 864 | for (root, state) in entries { |
| 865 | let snap = state.health.snapshot(); |
| 866 | if snap.heartbeat_stale() && store_is_stale(&root, &opts, interval_secs).await { |
| 867 | let _permit = watcher.inner.sync_semaphore.acquire().await; |
| 868 | if super::sync_project( |
| 869 | &root, |
| 870 | &opts, |
| 871 | watcher.inner.config.full_sync_escalation_files, |
| 872 | ) |
| 873 | .await |
| 874 | { |
| 875 | state.health.mark_synced(); |
| 876 | log_daemon_event( |
| 877 | "git_watch_synced", |
| 878 | &[ |
| 879 | ("project", root.display().to_string()), |
| 880 | ("action", "backstop".to_string()), |
| 881 | ], |
| 882 | ); |
| 883 | } |
| 884 | } |
| 885 | |
| 886 | if run_gc_now { |
| 887 | super::run_gc(&root, &opts, &watcher.inner.config).await; |
| 888 | } |
| 889 | } |
| 890 | |
| 891 | if run_gc_now { |
| 892 | *last_gc = Some(Instant::now()); |
| 893 | } |
| 894 | } |
| 895 |
no test coverage detected