gix_pathspec/pattern.rs
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 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
use std::path::{Component, Path, PathBuf};
use bstr::{BStr, BString, ByteSlice, ByteVec};
use crate::{normalize, MagicSignature, Pattern, SearchMode};
/// Access
impl Pattern {
/// Returns `true` if this seems to be a pathspec that indicates that 'there is no pathspec'.
///
/// Note that such a spec is `:`.
pub fn is_nil(&self) -> bool {
self.nil
}
/// Return the prefix-portion of the `path` of this spec, which is a *directory*.
/// It can be empty if there is no prefix.
///
/// A prefix is effectively the CWD seen as relative to the working tree, and it's assumed to
/// match case-sensitively. This makes it useful for skipping over large portions of input by
/// directly comparing them.
pub fn prefix_directory(&self) -> &BStr {
self.path[..self.prefix_len].as_bstr()
}
/// Return the path of this spec, typically used for matching.
pub fn path(&self) -> &BStr {
self.path.as_ref()
}
}
/// Mutation
impl Pattern {
/// Normalize the pattern's path by assuring it's relative to the root of the working tree, and contains
/// no relative path components. Further, it assures that `/` are used as path separator.
///
/// If `self.path` is a relative path, it will be put in front of the pattern path if `self.signature` isn't indicating `TOP` already.
/// If `self.path` is an absolute path, we will use `root` to make it worktree relative if possible.
///
/// `prefix` can be empty, we will still normalize this pathspec to resolve relative path components, and
/// it is assumed not to contain any relative path components, e.g. '', 'a', 'a/b' are valid.
/// `root` is the absolute path to the root of either the worktree or the repository's `git_dir`.
pub fn normalize(&mut self, prefix: &Path, root: &Path) -> Result<&mut Self, normalize::Error> {
fn prefix_components_to_subtract(path: &Path) -> usize {
let parent_component_end_bound = path.components().enumerate().fold(None::<usize>, |acc, (idx, c)| {
matches!(c, Component::ParentDir).then_some(idx + 1).or(acc)
});
let count = path
.components()
.take(parent_component_end_bound.unwrap_or(0))
.map(|c| match c {
Component::ParentDir => 1_isize,
Component::Normal(_) => -1,
_ => 0,
})
.sum::<isize>();
(count > 0).then_some(count as usize).unwrap_or_default()
}
let mut path = gix_path::from_bstr(self.path.as_bstr());
let mut num_prefix_components = 0;
let mut was_absolute = false;
if gix_path::is_absolute(path.as_ref()) {
was_absolute = true;
let rela_path = match path.strip_prefix(root) {
Ok(path) => path,
Err(_) => {
return Err(normalize::Error::AbsolutePathOutsideOfWorktree {
path: path.into_owned(),
worktree_path: root.into(),
})
}
};
path = rela_path.to_owned().into();
} else if !prefix.as_os_str().is_empty() && !self.signature.contains(MagicSignature::TOP) {
debug_assert_eq!(
prefix
.components()
.filter(|c| matches!(c, Component::Normal(_)))
.count(),
prefix.components().count(),
"BUG: prefixes must not have relative path components, or calculations here will be wrong so pattern won't match"
);
num_prefix_components = prefix
.components()
.count()
.saturating_sub(prefix_components_to_subtract(path.as_ref()));
path = prefix.join(path).into();
}
let assure_path_cannot_break_out_upwards = Path::new("");
let path = match gix_path::normalize(path.as_ref().into(), assure_path_cannot_break_out_upwards) {
Some(path) => {
if was_absolute {
num_prefix_components = path.components().count().saturating_sub(
if self.signature.contains(MagicSignature::MUST_BE_DIR) {
0
} else {
1
},
);
}
path
}
None => {
return Err(normalize::Error::OutsideOfWorktree {
path: path.into_owned(),
})
}
};
self.path = if path == Path::new(".") {
self.nil = true;
BString::from(".")
} else {
let cleaned = PathBuf::from_iter(path.components().filter(|c| !matches!(c, Component::CurDir)));
let mut out = gix_path::to_unix_separators_on_windows(gix_path::into_bstr(cleaned)).into_owned();
self.prefix_len = {
if self.signature.contains(MagicSignature::MUST_BE_DIR) {
out.push(b'/');
}
let len = out
.find_iter(b"/")
.take(num_prefix_components)
.last()
.unwrap_or_default();
if self.signature.contains(MagicSignature::MUST_BE_DIR) {
out.pop();
}
len
};
out
};
Ok(self)
}
}
/// Access
impl Pattern {
/// Return `true` if this pathspec is negated, which means it will exclude an item from the result set instead of including it.
pub fn is_excluded(&self) -> bool {
self.signature.contains(MagicSignature::EXCLUDE)
}
/// Returns `true` is this pattern is supposed to always match, as it's either empty or designated `nil`.
/// Note that technically the pattern might still be excluded.
pub fn always_matches(&self) -> bool {
self.is_nil() || self.path.is_empty()
}
/// Translate ourselves to a long display format, that when parsed back will yield the same pattern.
///
/// Note that the
pub fn to_bstring(&self) -> BString {
if self.is_nil() {
":".into()
} else {
let mut buf: BString = ":(".into();
if self.signature.contains(MagicSignature::TOP) {
buf.push_str("top,");
}
if self.signature.contains(MagicSignature::EXCLUDE) {
buf.push_str("exclude,");
}
if self.signature.contains(MagicSignature::ICASE) {
buf.push_str("icase,");
}
match self.search_mode {
SearchMode::ShellGlob => {}
SearchMode::Literal => buf.push_str("literal,"),
SearchMode::PathAwareGlob => buf.push_str("glob,"),
}
if self.attributes.is_empty() {
if buf.last() == Some(&b',') {
buf.pop();
}
} else {
buf.push_str("attr:");
for attr in &self.attributes {
let attr = attr.as_ref().to_string().replace(',', "\\,");
buf.push_str(&attr);
buf.push(b' ');
}
buf.pop(); // trailing ' '
}
buf.push(b')');
buf.extend_from_slice(&self.path);
if self.signature.contains(MagicSignature::MUST_BE_DIR) {
buf.push(b'/');
}
buf
}
}
}
impl std::fmt::Display for Pattern {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.to_bstring().fmt(f)
}
}