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 Some {
17 abs_base_path: Arc<Path>,
18 ignore: Arc<Gitignore>,
19 parent: Arc<IgnoreStackEntry>,
20 },
21 All,
22}
23
24impl IgnoreStack {
25 pub fn none() -> Self {
26 Self {
27 repo_root: None,
28 top: Arc::new(IgnoreStackEntry::None),
29 }
30 }
31
32 pub fn all() -> Self {
33 Self {
34 repo_root: None,
35 top: Arc::new(IgnoreStackEntry::All),
36 }
37 }
38
39 pub fn global(ignore: Arc<Gitignore>) -> Self {
40 Self {
41 repo_root: None,
42 top: Arc::new(IgnoreStackEntry::Global { ignore }),
43 }
44 }
45
46 pub fn append(self, abs_base_path: Arc<Path>, ignore: Arc<Gitignore>) -> Self {
47 let top = match self.top.as_ref() {
48 IgnoreStackEntry::All => self.top.clone(),
49 _ => Arc::new(IgnoreStackEntry::Some {
50 abs_base_path,
51 ignore,
52 parent: self.top.clone(),
53 }),
54 };
55 Self {
56 repo_root: self.repo_root,
57 top,
58 }
59 }
60
61 pub fn is_abs_path_ignored(&self, abs_path: &Path, is_dir: bool) -> bool {
62 if is_dir && abs_path.file_name() == Some(OsStr::new(".git")) {
63 return true;
64 }
65
66 match self.top.as_ref() {
67 IgnoreStackEntry::None => false,
68 IgnoreStackEntry::All => true,
69 IgnoreStackEntry::Global { ignore } => {
70 let combined_path;
71 let abs_path = if let Some(repo_root) = self.repo_root.as_ref() {
72 combined_path = ignore.path().join(
73 abs_path
74 .strip_prefix(repo_root)
75 .expect("repo root should be a parent of matched path"),
76 );
77 &combined_path
78 } else {
79 abs_path
80 };
81 match ignore.matched(abs_path, is_dir) {
82 ignore::Match::None => false,
83 ignore::Match::Ignore(_) => true,
84 ignore::Match::Whitelist(_) => false,
85 }
86 }
87 IgnoreStackEntry::Some {
88 abs_base_path,
89 ignore,
90 parent: prev,
91 } => match ignore.matched(abs_path.strip_prefix(abs_base_path).unwrap(), is_dir) {
92 ignore::Match::None => IgnoreStack {
93 repo_root: self.repo_root.clone(),
94 top: prev.clone(),
95 }
96 .is_abs_path_ignored(abs_path, is_dir),
97 ignore::Match::Ignore(_) => true,
98 ignore::Match::Whitelist(_) => false,
99 },
100 }
101 }
102}