返回 CodeWhale
prune.rs
根目录 / crates / tui / src / snapshot / prune.rs
1 //! Boot-time snapshot pruning.
2 //!
3 //! Called from `session_manager` once per session start. Failure is
4 //! never fatal — old snapshots taking disk space is annoying but not
5 //! correctness-breaking, so we log and move on.
6
7 use std::io;
8 use std::path::Path;
9 use std::time::Duration;
10
11 use super::paths::snapshot_git_dir;
12 use super::repo::SnapshotRepo;
13
14 /// Default snapshot retention window: 7 days.
15 pub const DEFAULT_MAX_AGE: Duration = Duration::from_secs(7 * 24 * 60 * 60);
16
17 /// Prune snapshots older than `max_age` for the given workspace.
18 ///
19 /// If no snapshot repo exists yet (first run) this is a cheap no-op.
20 /// Returns the number of snapshots removed.
21 pub fn prune_older_than(workspace: &Path, max_age: Duration) -> io::Result<usize> {
22 let git_dir = snapshot_git_dir(workspace);
23 if !git_dir.exists() {
24 return Ok(0);
25 }
26 let repo = SnapshotRepo::open_or_init(workspace)?;
27 let removed = repo.prune_older_than(max_age)?;
28 // `git prune --expire=now` walks the whole object store — seconds on a
29 // large snapshot repo. Nothing removed means nothing newly unreachable,
30 // so skip the walk entirely on the common boot path (#3757).
31 if removed > 0 {
32 repo.prune_unreachable_objects()?;
33 }
34 Ok(removed)
35 }
36
37 #[cfg(test)]
38 mod tests {
39 use super::*;
40 use crate::test_support::lock_test_env;
41 use tempfile::tempdir;
42
43 /// Same guard shape as in `repo::tests` — pins HOME for the lifetime
44 /// of one test under the process-wide env mutex.
45 struct ScopedHome {
46 prev: Option<std::ffi::OsString>,
47 _guard: crate::test_support::TestEnvLock,
48 }
49 impl Drop for ScopedHome {
50 fn drop(&mut self) {
51 // SAFETY: process-wide lock still held.
52 unsafe {
53 match self.prev.take() {
54 Some(v) => std::env::set_var("HOME", v),
55 None => std::env::remove_var("HOME"),
56 }
57 }
58 }
59 }
60 fn scoped_home(home: &std::path::Path) -> ScopedHome {
61 let guard = lock_test_env();
62 let prev = std::env::var_os("HOME");
63 // SAFETY: serialised by the global env lock.
64 unsafe {
65 std::env::set_var("HOME", home);
66 }
67 ScopedHome {
68 prev,
69 _guard: guard,
70 }
71 }
72
73 #[test]
74 fn prune_no_repo_returns_zero() {
75 let tmp = tempdir().unwrap();
76 let _home = scoped_home(tmp.path());
77 let removed = prune_older_than(tmp.path(), DEFAULT_MAX_AGE).unwrap();
78 assert_eq!(removed, 0);
79 }
80
81 #[test]
82 fn prune_with_existing_repo_zero_age_clears_all() {
83 let tmp = tempdir().unwrap();
84 let _home = scoped_home(tmp.path());
85 let workspace = tmp.path().join("ws");
86 std::fs::create_dir_all(&workspace).unwrap();
87 let repo = SnapshotRepo::open_or_init(&workspace).unwrap();
88 std::fs::write(workspace.join("f.txt"), "x").unwrap();
89 repo.snapshot("turn:0").unwrap();
90
91 // Same-second flake guard: see `repo::tests`.
92 std::thread::sleep(Duration::from_millis(1100));
93
94 let removed = prune_older_than(&workspace, Duration::from_secs(0)).unwrap();
95 assert!(removed >= 1);
96 }
97 }
98
98 lines RUST