-
-
Notifications
You must be signed in to change notification settings - Fork 308
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: match pathspecs just like
git
does.
This is important for selecting files on disk
- Loading branch information
Showing
19 changed files
with
1,035 additions
and
22 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
use crate::{normalize, MagicSignature, MatchMode, Pattern}; | ||
use bstr::{BString, ByteSlice, ByteVec}; | ||
use std::path::{Component, Path, PathBuf}; | ||
|
||
/// 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<(), 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_ref()); | ||
let mut num_prefix_components = 0; | ||
if gix_path::is_absolute(path.as_ref()) { | ||
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(), assure_path_cannot_break_out_upwards) { | ||
Some(path) => path, | ||
None => { | ||
return Err(normalize::Error::OutsideOfWorktree { | ||
path: path.into_owned(), | ||
}) | ||
} | ||
}; | ||
|
||
self.path = if path == Path::new(".") { | ||
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(()) | ||
} | ||
} | ||
|
||
/// 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) | ||
} | ||
|
||
/// 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 { | ||
MatchMode::ShellGlob => {} | ||
MatchMode::Literal => buf.push_str("literal,"), | ||
MatchMode::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) | ||
} | ||
} |
Oops, something went wrong.