1use std::{collections::HashSet, fs, path::Path};
8
9use crate::{
10 object::ChangeId,
11 store::{Result, atomic::write_file_atomic},
12};
13
14pub struct ShallowInfo {
16 path: std::path::PathBuf,
17 shallow_states: HashSet<ChangeId>,
18}
19
20impl ShallowInfo {
21 pub fn load(heddle_dir: &Path) -> Result<Self> {
23 let path = heddle_dir.join("shallow");
24 let shallow_states = if path.exists() {
25 let contents = fs::read_to_string(&path)?;
26 contents
27 .lines()
28 .filter_map(|line| {
29 let line = line.trim();
30 if line.is_empty() || line.starts_with('#') {
31 None
32 } else {
33 ChangeId::parse(line).ok()
34 }
35 })
36 .collect()
37 } else {
38 HashSet::new()
39 };
40
41 Ok(Self {
42 path,
43 shallow_states,
44 })
45 }
46
47 pub fn is_shallow(&self, id: &ChangeId) -> bool {
49 self.shallow_states.contains(id)
50 }
51
52 pub fn shallow_states(&self) -> &HashSet<ChangeId> {
54 &self.shallow_states
55 }
56
57 pub fn add_shallow(&mut self, id: ChangeId) -> Result<()> {
59 if self.shallow_states.insert(id) {
60 self.save()?;
61 }
62 Ok(())
63 }
64
65 pub fn remove_shallow(&mut self, id: &ChangeId) -> Result<()> {
67 if self.shallow_states.remove(id) {
68 self.save()?;
69 }
70 Ok(())
71 }
72
73 fn save(&self) -> Result<()> {
75 let contents: String = self
76 .shallow_states
77 .iter()
78 .map(|id| format!("{}\n", id.to_string_full()))
79 .collect();
80
81 write_file_atomic(&self.path, contents.as_bytes())?;
82 Ok(())
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use tempfile::TempDir;
89
90 use super::*;
91
92 #[test]
93 fn test_shallow_save_rewrites_file_without_temp_residue() {
94 let temp_dir = TempDir::new().unwrap();
95 let heddle_dir = temp_dir.path().join(".heddle");
96 fs::create_dir_all(&heddle_dir).unwrap();
97
98 let mut shallow = ShallowInfo::load(&heddle_dir).unwrap();
99 let id = ChangeId::generate();
100 shallow.add_shallow(id).unwrap();
101
102 let reloaded = ShallowInfo::load(&heddle_dir).unwrap();
103 assert!(reloaded.is_shallow(&id));
104
105 let temp_entries = fs::read_dir(&heddle_dir)
106 .unwrap()
107 .filter_map(|entry| entry.ok())
108 .filter(|entry| entry.file_name().to_string_lossy().contains(".tmp-"))
109 .count();
110 assert_eq!(temp_entries, 0);
111 }
112}