ignore.rs

  1use ignore::gitignore::Gitignore;
  2use std::{ffi::OsStr, path::Path, sync::Arc};
  3
  4#[derive(Clone, Debug)]
  5pub struct IgnoreStack {
  6    pub repo_root: Option<Arc<Path>>,
  7    pub top: Arc<IgnoreStackEntry>,
  8}
  9
 10#[derive(Debug)]
 11pub enum IgnoreStackEntry {
 12    None,
 13    Global {
 14        ignore: Arc<Gitignore>,
 15    },
 16    RepoExclude {
 17        ignore: Arc<Gitignore>,
 18        parent: Arc<IgnoreStackEntry>,
 19    },
 20    Some {
 21        abs_base_path: Arc<Path>,
 22        ignore: Arc<Gitignore>,
 23        parent: Arc<IgnoreStackEntry>,
 24    },
 25    All,
 26}
 27
 28#[derive(Debug)]
 29pub enum IgnoreKind {
 30    Gitignore(Arc<Path>),
 31    RepoExclude,
 32}
 33
 34impl IgnoreStack {
 35    pub fn none() -> Self {
 36        Self {
 37            repo_root: None,
 38            top: Arc::new(IgnoreStackEntry::None),
 39        }
 40    }
 41
 42    pub fn all() -> Self {
 43        Self {
 44            repo_root: None,
 45            top: Arc::new(IgnoreStackEntry::All),
 46        }
 47    }
 48
 49    pub fn global(ignore: Arc<Gitignore>) -> Self {
 50        Self {
 51            repo_root: None,
 52            top: Arc::new(IgnoreStackEntry::Global { ignore }),
 53        }
 54    }
 55
 56    pub fn append(self, kind: IgnoreKind, ignore: Arc<Gitignore>) -> Self {
 57        let top = match self.top.as_ref() {
 58            IgnoreStackEntry::All => self.top.clone(),
 59            _ => Arc::new(match kind {
 60                IgnoreKind::Gitignore(abs_base_path) => IgnoreStackEntry::Some {
 61                    abs_base_path,
 62                    ignore,
 63                    parent: self.top.clone(),
 64                },
 65                IgnoreKind::RepoExclude => IgnoreStackEntry::RepoExclude {
 66                    ignore,
 67                    parent: self.top.clone(),
 68                },
 69            }),
 70        };
 71        Self {
 72            repo_root: self.repo_root,
 73            top,
 74        }
 75    }
 76
 77    pub fn is_abs_path_ignored(&self, abs_path: &Path, is_dir: bool) -> bool {
 78        if is_dir && abs_path.file_name() == Some(OsStr::new(".git")) {
 79            return true;
 80        }
 81
 82        match self.top.as_ref() {
 83            IgnoreStackEntry::None => false,
 84            IgnoreStackEntry::All => true,
 85            IgnoreStackEntry::Global { ignore } => {
 86                let combined_path;
 87                let abs_path = if let Some(repo_root) = self.repo_root.as_ref() {
 88                    combined_path = ignore.path().join(
 89                        abs_path
 90                            .strip_prefix(repo_root)
 91                            .expect("repo root should be a parent of matched path"),
 92                    );
 93                    &combined_path
 94                } else {
 95                    abs_path
 96                };
 97                match ignore.matched(abs_path, is_dir) {
 98                    ignore::Match::None => false,
 99                    ignore::Match::Ignore(_) => true,
100                    ignore::Match::Whitelist(_) => false,
101                }
102            }
103            IgnoreStackEntry::RepoExclude { ignore, parent } => {
104                match ignore.matched(abs_path, is_dir) {
105                    ignore::Match::None => IgnoreStack {
106                        repo_root: self.repo_root.clone(),
107                        top: parent.clone(),
108                    }
109                    .is_abs_path_ignored(abs_path, is_dir),
110                    ignore::Match::Ignore(_) => true,
111                    ignore::Match::Whitelist(_) => false,
112                }
113            }
114            IgnoreStackEntry::Some {
115                abs_base_path,
116                ignore,
117                parent: prev,
118            } => match ignore.matched(abs_path.strip_prefix(abs_base_path).unwrap(), is_dir) {
119                ignore::Match::None => IgnoreStack {
120                    repo_root: self.repo_root.clone(),
121                    top: prev.clone(),
122                }
123                .is_abs_path_ignored(abs_path, is_dir),
124                ignore::Match::Ignore(_) => true,
125                ignore::Match::Whitelist(_) => false,
126            },
127        }
128    }
129}