1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
use std::rc::Rc;
use std::sync::Arc;
/// Resolved gitignore for a directory.
pub struct DirGitIgnores {
current: Option<Rc<ignore::gitignore::Gitignore>>,
parent: Option<Rc<DirGitIgnores>>,
}
impl DirGitIgnores {
pub fn is_ignored(&self, path: &Path, is_dir: bool) -> bool {
let mut is_ignored = false;
if let Some(parent) = &self.parent {
is_ignored = parent.is_ignored(path, is_dir);
}
if let Some(current) = &self.current {
match current.matched(path, is_dir) {
ignore::Match::None => {}
ignore::Match::Ignore(_) => {
is_ignored = true;
}
ignore::Match::Whitelist(_) => {
is_ignored = false;
}
}
}
is_ignored
}
}
/// Resolves gitignores in a directory tree taking into account
/// ancestor gitignores that may be found in a directory.
pub struct GitIgnoreTree {
fs: Arc<dyn deno_runtime::deno_fs::FileSystem>,
ignores: HashMap<PathBuf, Option<Rc<DirGitIgnores>>>,
}
impl GitIgnoreTree {
pub fn new(fs: Arc<dyn deno_runtime::deno_fs::FileSystem>) -> Self {
Self {
fs,
ignores: Default::default(),
}
}
pub fn get_resolved_git_ignore(
&mut self,
dir_path: &Path,
) -> Option<Rc<DirGitIgnores>> {
self.get_resolved_git_ignore_inner(dir_path, None)
}
fn get_resolved_git_ignore_inner(
&mut self,
dir_path: &Path,
maybe_parent: Option<&Path>,
) -> Option<Rc<DirGitIgnores>> {
let maybe_resolved = self.ignores.get(dir_path).cloned();
if let Some(resolved) = maybe_resolved {
resolved
} else {
let resolved = self.resolve_gitignore_in_dir(dir_path, maybe_parent);
self.ignores.insert(dir_path.to_owned(), resolved.clone());
resolved
}
}
fn resolve_gitignore_in_dir(
&mut self,
dir_path: &Path,
maybe_parent: Option<&Path>,
) -> Option<Rc<DirGitIgnores>> {
if let Some(parent) = maybe_parent {
// stop searching if the parent dir had a .git directory in it
if self.fs.exists_sync(&parent.join(".git")) {
return None;
}
}
let parent = dir_path.parent().and_then(|parent| {
self.get_resolved_git_ignore_inner(parent, Some(dir_path))
});
let current = self
.fs
.read_text_file_sync(&dir_path.join(".gitignore"))
.ok()
.and_then(|text| {
let mut builder = ignore::gitignore::GitignoreBuilder::new(dir_path);
for line in text.lines() {
builder.add_line(None, line).ok()?;
}
let gitignore = builder.build().ok()?;
Some(Rc::new(gitignore))
});
if parent.is_none() && current.is_none() {
None
} else {
Some(Rc::new(DirGitIgnores { current, parent }))
}
}
}
#[cfg(test)]
mod test {
use deno_runtime::deno_fs::InMemoryFs;
use super::*;
#[test]
fn git_ignore_tree() {
let fs = InMemoryFs::default();
fs.setup_text_files(vec![
("/.gitignore".into(), "file.txt".into()),
("/sub_dir/.gitignore".into(), "data.txt".into()),
(
"/sub_dir/sub_dir/.gitignore".into(),
"!file.txt\nignore.txt".into(),
),
]);
let mut ignore_tree = GitIgnoreTree::new(Arc::new(fs));
let mut run_test = |path: &str, expected: bool| {
let path = PathBuf::from(path);
let gitignore = ignore_tree
.get_resolved_git_ignore(path.parent().unwrap())
.unwrap();
assert_eq!(
gitignore.is_ignored(&path, /* is_dir */ false),
expected,
"Path: {}",
path.display()
);
};
run_test("/file.txt", true);
run_test("/other.txt", false);
run_test("/data.txt", false);
run_test("/sub_dir/file.txt", true);
run_test("/sub_dir/other.txt", false);
run_test("/sub_dir/data.txt", true);
run_test("/sub_dir/sub_dir/file.txt", false); // unignored up here
run_test("/sub_dir/sub_dir/sub_dir/file.txt", false);
run_test("/sub_dir/sub_dir/sub_dir/ignore.txt", true);
run_test("/sub_dir/sub_dir/ignore.txt", true);
run_test("/sub_dir/ignore.txt", false);
run_test("/ignore.txt", false);
}
}
|