gix_fs/dir/
remove.rs

1//!
2use std::path::{Path, PathBuf};
3
4/// A special iterator which communicates its operation through results where…
5///
6/// * `Some(Ok(removed_directory))` is yielded once or more success, followed by `None`
7/// * `Some(Err(std::io::Error))` is yielded exactly once on failure.
8pub struct Iter<'a> {
9    cursor: Option<&'a Path>,
10    boundary: &'a Path,
11}
12
13/// Construction
14impl<'a> Iter<'a> {
15    /// Create a new instance that deletes `target` but will stop at `boundary`, without deleting the latter.
16    /// Returns an error if `boundary` doesn't contain `target`
17    ///
18    /// **Note** that we don't canonicalize the path for performance reasons.
19    pub fn new(target: &'a Path, boundary: &'a Path) -> std::io::Result<Self> {
20        if !target.starts_with(boundary) {
21            return Err(std::io::Error::new(
22                std::io::ErrorKind::InvalidInput,
23                format!("Removal target {target:?} must be contained in boundary {boundary:?}"),
24            ));
25        }
26        let cursor = if target == boundary {
27            None
28        } else if target.exists() {
29            Some(target)
30        } else {
31            None
32        };
33        Ok(Iter { cursor, boundary })
34    }
35}
36
37impl<'a> Iterator for Iter<'a> {
38    type Item = std::io::Result<&'a Path>;
39
40    fn next(&mut self) -> Option<Self::Item> {
41        match self.cursor.take() {
42            Some(dir) => {
43                let next = match std::fs::remove_dir(dir) {
44                    Ok(()) => Some(Ok(dir)),
45                    Err(err) => match err.kind() {
46                        std::io::ErrorKind::NotFound => Some(Ok(dir)),
47                        _other_error_kind => return Some(Err(err)),
48                    },
49                };
50                self.cursor = match dir.parent() {
51                    Some(parent) => (parent != self.boundary).then_some(parent),
52                    None => {
53                        unreachable!("directory {:?} ran out of parents, this really shouldn't happen before hitting the boundary {:?}", dir, self.boundary)
54                    }
55                };
56                next
57            }
58            None => None,
59        }
60    }
61}
62
63/// Delete all empty directories from `delete_dir` upward and until (not including) the `boundary_dir`.
64///
65/// Note that `boundary_dir` must contain `delete_dir` or an error is returned, otherwise `delete_dir` is returned on success.
66pub fn empty_upward_until_boundary<'a>(delete_dir: &'a Path, boundary_dir: &'a Path) -> std::io::Result<&'a Path> {
67    for item in Iter::new(delete_dir, boundary_dir)? {
68        match item {
69            Ok(_dir) => continue,
70            Err(err) => return Err(err),
71        }
72    }
73    Ok(delete_dir)
74}
75
76/// Delete all empty directories reachable from `delete_dir` from empty leaves moving upward to and including `delete_dir`.
77///
78/// If any encountered directory contains a file the entire operation is aborted.
79/// Please note that this is inherently racy and no attempts are made to counter that, which will allow creators to win
80/// as long as they retry.
81pub fn empty_depth_first(delete_dir: PathBuf) -> std::io::Result<()> {
82    if let Ok(()) = std::fs::remove_dir(&delete_dir) {
83        return Ok(());
84    }
85
86    let mut stack = vec![delete_dir];
87    let mut next_to_push = Vec::new();
88    while let Some(dir_to_delete) = stack.pop() {
89        let mut num_entries = 0;
90        for entry in std::fs::read_dir(&dir_to_delete)? {
91            num_entries += 1;
92            let entry = entry?;
93            if entry.file_type()?.is_dir() {
94                next_to_push.push(entry.path());
95            } else {
96                return Err(std::io::Error::new(std::io::ErrorKind::Other, "Directory not empty"));
97            }
98        }
99        if num_entries == 0 {
100            std::fs::remove_dir(&dir_to_delete)?;
101        } else {
102            stack.push(dir_to_delete);
103            stack.append(&mut next_to_push);
104        }
105    }
106    Ok(())
107}