git_branch_stash/
stack.rs1pub use super::Snapshot;
2
3#[derive(Clone, Debug, PartialEq, Eq)]
5pub struct Stack {
6 pub name: String,
7 root: std::path::PathBuf,
8 capacity: Option<usize>,
9}
10
11impl Stack {
12 pub const DEFAULT_STACK: &'static str = "recent";
13 const EXT: &'static str = "bak";
14
15 pub fn new(name: &str, repo: &crate::git::GitRepo) -> Self {
17 let root = stack_root(repo.raw().path(), name);
18 let name = name.to_owned();
19 Self {
20 name,
21 root,
22 capacity: None,
23 }
24 }
25
26 pub fn all(repo: &crate::git::GitRepo) -> impl Iterator<Item = Self> {
28 let root = stacks_root(repo.raw().path());
29 let mut stacks: Vec<_> = std::fs::read_dir(root)
30 .into_iter()
31 .flatten()
32 .filter_map(|e| {
33 let e = e.ok()?;
34 let e = e.file_type().ok()?.is_dir().then_some(e)?;
35 let p = e.path();
36 let stack_name = p.file_name()?.to_str()?.to_owned();
37 let stack_root = stack_root(repo.raw().path(), &stack_name);
38 Some(Self {
39 name: stack_name,
40 root: stack_root,
41 capacity: None,
42 })
43 })
44 .collect();
45 if !stacks.iter().any(|v| v.name == Self::DEFAULT_STACK) {
46 stacks.insert(0, Self::new(Self::DEFAULT_STACK, repo));
47 }
48 stacks.into_iter()
49 }
50
51 pub fn capacity(&mut self, capacity: Option<usize>) {
53 self.capacity = capacity;
54 }
55
56 pub fn iter(&self) -> impl DoubleEndedIterator<Item = std::path::PathBuf> {
58 let mut elements: Vec<(usize, std::path::PathBuf)> = std::fs::read_dir(&self.root)
59 .into_iter()
60 .flatten()
61 .filter_map(|e| {
62 let e = e.ok()?;
63 let e = e.file_type().ok()?.is_file().then_some(e)?;
64 let p = e.path();
65 let p = (p.extension()? == Self::EXT).then_some(p)?;
66 let index = p.file_stem()?.to_str()?.parse::<usize>().ok()?;
67 Some((index, p))
68 })
69 .collect();
70 elements.sort_unstable();
71 elements.into_iter().map(|(_, p)| p)
72 }
73
74 pub fn push(&mut self, snapshot: Snapshot) -> Result<std::path::PathBuf, std::io::Error> {
76 let elems: Vec<_> = self.iter().collect();
77 let last_path = elems.iter().last();
78 let next_index = match last_path {
79 Some(last_path) => {
80 let current_index = last_path
81 .file_stem()
82 .unwrap()
83 .to_str()
84 .unwrap()
85 .parse::<usize>()
86 .unwrap();
87 current_index + 1
88 }
89 None => 0,
90 };
91 let last = last_path.and_then(|p| Snapshot::load(p).ok());
92 if last.as_ref() == Some(&snapshot) {
93 let last_path = last_path.unwrap().to_owned();
94 log::trace!("Reusing snapshot {}", last_path.display());
95 return Ok(last_path);
96 }
97
98 std::fs::create_dir_all(&self.root)?;
99 let new_path = self.root.join(format!("{}.{}", next_index, Self::EXT));
100 snapshot.save(&new_path)?;
101 log::trace!("Backed up as {}", new_path.display());
102
103 if let Some(capacity) = self.capacity {
104 let len = elems.len();
105 if capacity < len {
106 let remove = len - capacity;
107 log::debug!("Too many snapshots, clearing {} oldest", remove);
108 for snapshot_path in &elems[0..remove] {
109 if let Err(err) = std::fs::remove_file(snapshot_path) {
110 log::debug!("Failed to remove {}: {}", snapshot_path.display(), err);
111 } else {
112 log::trace!("Removed {}", snapshot_path.display());
113 }
114 }
115 }
116 }
117
118 Ok(new_path)
119 }
120
121 pub fn clear(&mut self) {
123 let _ = std::fs::remove_dir_all(&self.root);
124 }
125
126 pub fn pop(&mut self) -> Option<std::path::PathBuf> {
128 let mut elems: Vec<_> = self.iter().collect();
129 let last = elems.pop()?;
130 std::fs::remove_file(&last).ok()?;
131 Some(last)
132 }
133
134 pub fn peek(&mut self) -> Option<std::path::PathBuf> {
136 self.iter().last()
137 }
138}
139
140fn stacks_root(repo: &std::path::Path) -> std::path::PathBuf {
141 repo.join("branch-stash")
142}
143
144fn stack_root(repo: &std::path::Path, stack: &str) -> std::path::PathBuf {
145 repo.join("branch-stash").join(stack)
146}