Skip to content

Commit 5f9de52

Browse files
authored
Merge pull request #2019 from GitoxideLabs/precious-opt-in
precious opt in
2 parents 4f27179 + 1df1ebb commit 5f9de52

File tree

33 files changed

+212
-63
lines changed

33 files changed

+212
-63
lines changed

gitoxide-core/src/repository/exclude.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ pub fn query(
3737
let index = repo.index()?;
3838
let mut cache = repo.excludes(
3939
&index,
40-
Some(gix::ignore::Search::from_overrides(overrides.into_iter())),
40+
Some(gix::ignore::Search::from_overrides(
41+
overrides.into_iter(),
42+
repo.ignore_pattern_parser()?,
43+
)),
4144
Default::default(),
4245
)?;
4346

gix-attributes/src/search/attributes.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ impl Search {
5858
) -> std::io::Result<bool> {
5959
// TODO: should `Pattern` trait use an instance as first argument to carry this information
6060
// (so no `retain` later, it's slower than skipping)
61-
let was_added = gix_glob::search::add_patterns_file(&mut self.patterns, source, follow_symlinks, root, buf)?;
61+
let was_added =
62+
gix_glob::search::add_patterns_file(&mut self.patterns, source, follow_symlinks, root, buf, Attributes)?;
6263
if was_added {
6364
let last = self.patterns.last_mut().expect("just added");
6465
if !allow_macros {
@@ -80,7 +81,8 @@ impl Search {
8081
collection: &mut MetadataCollection,
8182
allow_macros: bool,
8283
) {
83-
self.patterns.push(pattern::List::from_bytes(bytes, source, root));
84+
self.patterns
85+
.push(pattern::List::from_bytes(bytes, source, root, Attributes));
8486
let last = self.patterns.last_mut().expect("just added");
8587
if !allow_macros {
8688
last.patterns
@@ -124,7 +126,7 @@ impl Search {
124126
impl Pattern for Attributes {
125127
type Value = Value;
126128

127-
fn bytes_to_patterns(bytes: &[u8], _source: &std::path::Path) -> Vec<pattern::Mapping<Self::Value>> {
129+
fn bytes_to_patterns(&self, bytes: &[u8], _source: &std::path::Path) -> Vec<pattern::Mapping<Self::Value>> {
128130
fn into_owned_assignments<'a>(
129131
attrs: impl Iterator<Item = Result<crate::AssignmentRef<'a>, crate::name::Error>>,
130132
) -> Option<Assignments> {

gix-dir/tests/walk_utils/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,7 @@ pub fn try_collect_filtered_opts(
330330
Default::default(),
331331
None,
332332
gix_worktree::stack::state::ignore::Source::WorktreeThenIdMappingIfNotSkipped,
333+
gix_ignore::search::Ignore { support_precious: true },
333334
)),
334335
&index,
335336
index.path_backing(),
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

gix-filter/tests/pipeline/mod.rs renamed to gix-filter/tests/filter/pipeline/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ fn attribute_cache(name: &str) -> gix_testtools::Result<gix_worktree::Stack> {
3838
Default::default(),
3939
None,
4040
gix_worktree::stack::state::ignore::Source::WorktreeThenIdMappingIfNotSkipped,
41+
Default::default(),
4142
),
4243
),
4344
Case::Sensitive,

gix-glob/src/search/mod.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,28 @@ pub trait Pattern: Clone + PartialEq + Eq + std::fmt::Debug + std::hash::Hash +
1515
type Value: PartialEq + Eq + std::fmt::Debug + std::hash::Hash + Ord + PartialOrd + Clone;
1616

1717
/// Parse all patterns in `bytes` line by line, ignoring lines with errors, and collect them.
18-
fn bytes_to_patterns(bytes: &[u8], source: &Path) -> Vec<pattern::Mapping<Self::Value>>;
18+
fn bytes_to_patterns(&self, bytes: &[u8], source: &Path) -> Vec<pattern::Mapping<Self::Value>>;
1919
}
2020

2121
/// Add the given file at `source` if it exists, otherwise do nothing.
2222
/// If a `root` is provided, it's not considered a global file anymore.
23+
/// `parse` is a way to parse bytes to pattern.
2324
/// Returns `true` if the file was added, or `false` if it didn't exist.
2425
pub fn add_patterns_file<T: Pattern>(
2526
patterns: &mut Vec<pattern::List<T>>,
2627
source: PathBuf,
2728
follow_symlinks: bool,
2829
root: Option<&Path>,
2930
buf: &mut Vec<u8>,
31+
parse: T,
3032
) -> std::io::Result<bool> {
3133
let previous_len = patterns.len();
32-
patterns.extend(pattern::List::<T>::from_file(source, root, follow_symlinks, buf)?);
34+
patterns.extend(pattern::List::<T>::from_file(
35+
source,
36+
root,
37+
follow_symlinks,
38+
buf,
39+
parse,
40+
)?);
3341
Ok(patterns.len() != previous_len)
3442
}

gix-glob/src/search/pattern.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,9 @@ where
7979
/// `source_file` is the location of the `bytes` which represents a list of patterns, one pattern per line.
8080
/// If `root` is `Some(…)` it's used to see `source_file` as relative to itself, if `source_file` is absolute.
8181
/// If source is relative and should be treated as base, set `root` to `Some("")`.
82-
pub fn from_bytes(bytes: &[u8], source_file: PathBuf, root: Option<&Path>) -> Self {
83-
let patterns = T::bytes_to_patterns(bytes, source_file.as_path());
82+
/// `parse` is a way to parse bytes to pattern.
83+
pub fn from_bytes(bytes: &[u8], source_file: PathBuf, root: Option<&Path>, parse: T) -> Self {
84+
let patterns = parse.bytes_to_patterns(bytes, source_file.as_path());
8485
let base = root
8586
.and_then(|root| source_file.parent().expect("file").strip_prefix(root).ok())
8687
.and_then(|base| {
@@ -101,14 +102,17 @@ where
101102

102103
/// Create a pattern list from the `source` file, which may be located underneath `root`, while optionally
103104
/// following symlinks with `follow_symlinks`, providing `buf` to temporarily store the data contained in the file.
105+
/// `parse` is a way to parse bytes to pattern.
104106
pub fn from_file(
105107
source: impl Into<PathBuf>,
106108
root: Option<&Path>,
107109
follow_symlinks: bool,
108110
buf: &mut Vec<u8>,
111+
parse: T,
109112
) -> std::io::Result<Option<Self>> {
110113
let source = source.into();
111-
Ok(read_in_full_ignore_missing(&source, follow_symlinks, buf)?.then(|| Self::from_bytes(buf, source, root)))
114+
Ok(read_in_full_ignore_missing(&source, follow_symlinks, buf)?
115+
.then(|| Self::from_bytes(buf, source, root, parse)))
112116
}
113117
}
114118

gix-glob/tests/search/pattern.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@ mod list {
1515
impl Pattern for Dummy {
1616
type Value = ();
1717

18-
fn bytes_to_patterns(_bytes: &[u8], _source: &Path) -> Vec<Mapping<Self::Value>> {
18+
fn bytes_to_patterns(&self, _bytes: &[u8], _source: &Path) -> Vec<Mapping<Self::Value>> {
1919
vec![]
2020
}
2121
}
2222

2323
#[test]
2424
fn from_bytes_base() {
2525
{
26-
let list = List::<Dummy>::from_bytes(&[], "a/b/source".into(), None);
26+
let list = List::from_bytes(&[], "a/b/source".into(), None, Dummy);
2727
assert_eq!(list.base, None, "no root always means no-base, i.e. globals lists");
2828
assert_eq!(
2929
list.source.as_deref(),
@@ -34,7 +34,7 @@ mod list {
3434

3535
{
3636
let cwd = std::env::current_dir().expect("cwd available");
37-
let list = List::<Dummy>::from_bytes(&[], cwd.join("a/b/source"), Some(cwd.as_path()));
37+
let list = List::from_bytes(&[], cwd.join("a/b/source"), Some(cwd.as_path()), Dummy);
3838
assert_eq!(
3939
list.base.as_ref().expect("set"),
4040
"a/b/",
@@ -48,7 +48,7 @@ mod list {
4848
}
4949

5050
{
51-
let list = List::<Dummy>::from_bytes(&[], "a/b/source".into(), Some(Path::new("c/")));
51+
let list = List::from_bytes(&[], "a/b/source".into(), Some(Path::new("c/")), Dummy);
5252
assert_eq!(
5353
list.base, None,
5454
"if root doesn't contain source, it silently skips it as base"
@@ -63,7 +63,7 @@ mod list {
6363

6464
#[test]
6565
fn strip_base_handle_recompute_basename_pos() {
66-
let list = List::<Dummy>::from_bytes(&[], "a/b/source".into(), Some(Path::new("")));
66+
let list = List::from_bytes(&[], "a/b/source".into(), Some(Path::new("")), Dummy);
6767
assert_eq!(
6868
list.base.as_ref().expect("set"),
6969
"a/b/",
@@ -91,7 +91,7 @@ mod list {
9191
Path::new(".").join("non-existing-dir").join("pattern-file"),
9292
Path::new("file").to_owned(),
9393
] {
94-
let list = List::<Dummy>::from_file(path, None, false, &mut buf).expect("no io error");
94+
let list = List::from_file(path, None, false, &mut buf, Dummy).expect("no io error");
9595
assert!(list.is_none(), "the file does not exist");
9696
}
9797
}
@@ -102,7 +102,7 @@ mod list {
102102
let dir_path = tmp.path().join(".gitignore");
103103
std::fs::create_dir(&dir_path)?;
104104
let mut buf = Vec::new();
105-
let list = List::<Dummy>::from_file(dir_path, None, false, &mut buf).expect("no io error");
105+
let list = List::from_file(dir_path, None, false, &mut buf, Dummy).expect("no io error");
106106
assert!(list.is_none(), "directories are ignored just like Git does it");
107107

108108
Ok(())

gix-ignore/src/lib.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ pub enum Kind {
4848
pub mod parse;
4949

5050
/// Parse git ignore patterns, line by line, from `bytes`.
51-
pub fn parse(bytes: &[u8]) -> parse::Lines<'_> {
52-
parse::Lines::new(bytes)
51+
///
52+
/// If `support_precious` is `true`, we will parse `$` prefixed entries as precious.
53+
/// This is backward-incompatible as files that actually start with `$` like `$houdini`
54+
/// will then not be ignored anymore, instead it ignores `houdini`.
55+
pub fn parse(bytes: &[u8], support_precious: bool) -> parse::Lines<'_> {
56+
parse::Lines::new(bytes, support_precious)
5357
}

gix-ignore/src/parse.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,22 @@ use bstr::ByteSlice;
44
pub struct Lines<'a> {
55
lines: bstr::Lines<'a>,
66
line_no: usize,
7+
/// Only if `true` we will be able to parse precious files.
8+
support_precious: bool,
79
}
810

911
impl<'a> Lines<'a> {
1012
/// Create a new instance from `buf` to parse ignore patterns from.
11-
pub fn new(buf: &'a [u8]) -> Self {
13+
///
14+
/// If `support_precious` is `true`, we will parse `$` prefixed entries as precious.
15+
/// This is backward-incompatible as files that actually start with `$` like `$houdini`
16+
/// will then not be ignored anymore, instead it ignores `houdini`.
17+
pub fn new(buf: &'a [u8], support_precious: bool) -> Self {
1218
let bom = unicode_bom::Bom::from(buf);
1319
Lines {
1420
lines: buf[bom.len()..].lines(),
1521
line_no: 0,
22+
support_precious,
1623
}
1724
}
1825
}
@@ -27,7 +34,7 @@ impl Iterator for Lines<'_> {
2734
Some(b'#') | None => continue,
2835
Some(c) => c,
2936
};
30-
let (kind, can_negate) = if first == b'$' {
37+
let (kind, can_negate) = if self.support_precious && first == b'$' {
3138
line = &line[1..];
3239
(crate::Kind::Precious, false)
3340
} else {

gix-ignore/src/search.rs

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,20 @@ pub struct Match<'a> {
2121
pub sequence_number: usize,
2222
}
2323

24-
/// An implementation of the [`Pattern`] trait for ignore patterns.
25-
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Default)]
26-
pub struct Ignore;
24+
/// An implementation of the [`Pattern`] trait for ignore-patterns.
25+
#[derive(Default, PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
26+
pub struct Ignore {
27+
/// If `support_precious` is `true`, we will parse `$` prefixed entries as precious.
28+
/// This is backward-incompatible as files that actually start with `$` like `$houdini`
29+
/// will then not be ignored anymore, instead it ignores `houdini`.
30+
pub support_precious: bool,
31+
}
2732

2833
impl Pattern for Ignore {
2934
type Value = crate::Kind;
3035

31-
fn bytes_to_patterns(bytes: &[u8], _source: &std::path::Path) -> Vec<pattern::Mapping<Self::Value>> {
32-
crate::parse(bytes)
36+
fn bytes_to_patterns(&self, bytes: &[u8], _source: &std::path::Path) -> Vec<pattern::Mapping<Self::Value>> {
37+
crate::parse(bytes, self.support_precious)
3338
.map(|(pattern, line_number, kind)| pattern::Mapping {
3439
pattern,
3540
value: kind,
@@ -44,38 +49,48 @@ impl Search {
4449
/// Given `git_dir`, a `.git` repository, load static ignore patterns from `info/exclude`
4550
/// and from `excludes_file` if it is provided.
4651
/// Note that it's not considered an error if the provided `excludes_file` does not exist.
47-
pub fn from_git_dir(git_dir: &Path, excludes_file: Option<PathBuf>, buf: &mut Vec<u8>) -> std::io::Result<Self> {
52+
/// `parse` is a way to parse bytes to ignore patterns.
53+
pub fn from_git_dir(
54+
git_dir: &Path,
55+
excludes_file: Option<PathBuf>,
56+
buf: &mut Vec<u8>,
57+
parse: Ignore,
58+
) -> std::io::Result<Self> {
4859
let mut group = Self::default();
4960

5061
let follow_symlinks = true;
5162
// order matters! More important ones first.
5263
group.patterns.extend(
5364
excludes_file
54-
.and_then(|file| pattern::List::<Ignore>::from_file(file, None, follow_symlinks, buf).transpose())
65+
.and_then(|file| {
66+
pattern::List::<Ignore>::from_file(file, None, follow_symlinks, buf, parse).transpose()
67+
})
5568
.transpose()?,
5669
);
5770
group.patterns.extend(pattern::List::<Ignore>::from_file(
5871
git_dir.join("info").join("exclude"),
5972
None,
6073
follow_symlinks,
6174
buf,
75+
parse,
6276
)?);
6377
Ok(group)
6478
}
6579

6680
/// Parse a list of ignore patterns, using slashes as path separators.
67-
pub fn from_overrides(patterns: impl IntoIterator<Item = impl Into<OsString>>) -> Self {
68-
Self::from_overrides_inner(&mut patterns.into_iter().map(Into::into))
81+
/// `parse` is a way to parse bytes to ignore patterns.
82+
pub fn from_overrides(patterns: impl IntoIterator<Item = impl Into<OsString>>, parse: Ignore) -> Self {
83+
Self::from_overrides_inner(&mut patterns.into_iter().map(Into::into), parse)
6984
}
7085

71-
fn from_overrides_inner(patterns: &mut dyn Iterator<Item = OsString>) -> Self {
86+
fn from_overrides_inner(patterns: &mut dyn Iterator<Item = OsString>, parse: Ignore) -> Self {
7287
Search {
7388
patterns: vec![pattern::List {
7489
patterns: patterns
7590
.enumerate()
7691
.filter_map(|(seq_id, pattern)| {
7792
let pattern = gix_path::try_into_bstr(PathBuf::from(pattern)).ok()?;
78-
crate::parse(pattern.as_ref())
93+
crate::parse(pattern.as_ref(), parse.support_precious)
7994
.next()
8095
.map(|(p, _seq_id, kind)| pattern::Mapping {
8196
pattern: p,
@@ -95,9 +110,16 @@ impl Search {
95110
impl Search {
96111
/// Add patterns as parsed from `bytes`, providing their `source` path and possibly their `root` path, the path they
97112
/// are relative to. This also means that `source` is contained within `root` if `root` is provided.
98-
pub fn add_patterns_buffer(&mut self, bytes: &[u8], source: impl Into<PathBuf>, root: Option<&Path>) {
113+
/// Use `parse` to control how ignore patterns are parsed.
114+
pub fn add_patterns_buffer(
115+
&mut self,
116+
bytes: &[u8],
117+
source: impl Into<PathBuf>,
118+
root: Option<&Path>,
119+
parse: Ignore,
120+
) {
99121
self.patterns
100-
.push(pattern::List::from_bytes(bytes, source.into(), root));
122+
.push(pattern::List::from_bytes(bytes, source.into(), root, parse));
101123
}
102124
}
103125

File renamed without changes.

0 commit comments

Comments
 (0)