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}