返回 DeepSeek-TUI-2026
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 repo.prune_older_than(max_age)
28 }
29
30 #[cfg(test)]
31 mod tests {
32 use super::*;
33 use crate::test_support::lock_test_env;
34 use std::sync::MutexGuard;
35 use tempfile::tempdir;
36
37 /// Same guard shape as in `repo::tests` — pins HOME for the lifetime
38 /// of one test under the process-wide env mutex.
39 struct ScopedHome {
40 prev: Option<std::ffi::OsString>,
41 _guard: MutexGuard<'static, ()>,
42 }
43 impl Drop for ScopedHome {
44 fn drop(&mut self) {
45 // SAFETY: process-wide lock still held.
46 unsafe {
47 match self.prev.take() {
48 Some(v) => std::env::set_var("HOME", v),
49 None => std::env::remove_var("HOME"),
50 }
51 }
52 }
53 }
54 fn scoped_home(home: &std::path::Path) -> ScopedHome {
55 let guard = lock_test_env();
56 let prev = std::env::var_os("HOME");
57 // SAFETY: serialised by the global env lock.
58 unsafe {
59 std::env::set_var("HOME", home);
60 }
61 ScopedHome {
62 prev,
63 _guard: guard,
64 }
65 }
66
67 #[test]
68 fn prune_no_repo_returns_zero() {
69 let tmp = tempdir().unwrap();
70 let _home = scoped_home(tmp.path());
71 let removed = prune_older_than(tmp.path(), DEFAULT_MAX_AGE).unwrap();
72 assert_eq!(removed, 0);
73 }
74
75 #[test]
76 fn prune_with_existing_repo_zero_age_clears_all() {
77 let tmp = tempdir().unwrap();
78 let _home = scoped_home(tmp.path());
79 let workspace = tmp.path().join("ws");
80 std::fs::create_dir_all(&workspace).unwrap();
81 let repo = SnapshotRepo::open_or_init(&workspace).unwrap();
82 std::fs::write(workspace.join("f.txt"), "x").unwrap();
83 repo.snapshot("turn:0").unwrap();
84
85 // Same-second flake guard: see `repo::tests`.
86 std::thread::sleep(Duration::from_millis(1100));
87
88 let removed = prune_older_than(&workspace, Duration::from_secs(0)).unwrap();
89 assert!(removed >= 1);
90 }
91 }
92
92 lines RUST