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
use crate::{
cancellation::{CancellationFlag, Outcome},
spelled_path::{FileRoot, SpelledPath},
};
use ignore::{WalkBuilder, overrides::OverrideBuilder};
use std::path::Path;
// Configure a walk of the file tree which follows directory symlinks, includes hidden
// entries, and honors ignore files within the tree while excluding VCS metadata.
pub fn file_tree_walker(file_root: &Path) -> WalkBuilder {
// Exclude VCS metadata, which never needs links.
let mut overrides = OverrideBuilder::new(file_root);
overrides
.add("!.git/")
.expect("The static .git override should be valid.")
.add("!.hg/")
.expect("The static .hg override should be valid.");
let overrides = overrides
.build()
.expect("The static overrides should compile.");
// Consult ignore files only within the file tree, whether or not it's a Git repository.
let mut walker_builder = WalkBuilder::new(file_root);
walker_builder
.current_dir(file_root)
.follow_links(true)
.hidden(false)
.parents(false)
.require_git(false)
.overrides(overrides);
walker_builder
}
// This describes whether a walk of the file tree reaches a path.
#[derive(Debug, Eq, PartialEq)]
pub enum Visibility {
// The walk reaches the file, or a file within the directory.
Visible,
// The walk reaches the directory but no file within it.
Empty,
// The walk never reaches the path, because an ignore rule excludes it or one of its ancestors.
Ignored,
}
// Determine whether a walk of the file tree reaches a path. The walk descends only along the
// path and into its target, stopping at the first file it finds there, so it applies every ignore
// rule without reading unrelated subtrees.
pub fn visibility(
file_root: &FileRoot,
target: &SpelledPath,
cancellation: &CancellationFlag,
) -> Outcome<Visibility> {
// Keep only the ancestors of the target and the entries within it.
let mut walker_builder = file_tree_walker(file_root.path());
walker_builder.filter_entry({
let file_root = file_root.clone();
let target = target.clone();
move |entry| {
let path = file_root.entry_path(entry);
target.starts_with(&path) || path.starts_with(&target)
}
});
// Disregard walk failures, such as broken symlinks, since the filter never sees them, so they
// may concern unrelated entries.
let mut reached = false;
for entry in walker_builder.build().flatten() {
// Stop between entries so a superseded check doesn't walk the rest of the target.
if cancellation.is_cancelled() {
return Outcome::Cancelled;
}
// Note reaching the target, and stop at the first file at or within it.
let path = file_root.entry_path(&entry);
if path == *target {
reached = true;
}
if path.starts_with(target)
&& entry
.file_type()
.expect("Only standard input lacks a file type.")
.is_file()
{
return Outcome::Completed(Visibility::Visible);
}
}
// Distinguish a directory without files from a path that the walk never reached.
Outcome::Completed(if reached {
Visibility::Empty
} else {
Visibility::Ignored
})
}