Skip to content

Commit 9ce5fb0

Browse files
committed
feat: support to obtain Attributes using the Cache type.
1 parent 1f20b6a commit 9ce5fb0

File tree

4 files changed

+265
-1
lines changed

4 files changed

+265
-1
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:d2ee69dacf23038fbae5fc51b2f649cf4f2bfd77473445bd2c88fd051d1c5aba
3+
size 11432
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#!/bin/bash
2+
set -eu -o pipefail
3+
4+
mkdir basics;
5+
6+
function baseline() {
7+
{
8+
echo "$1"
9+
GIT_ATTR_NOSYSTEM=1 git -c core.attributesFile=$PWD/user.attributes check-attr -a "$1"
10+
echo
11+
} >> baseline
12+
}
13+
14+
15+
(cd basics
16+
git init
17+
18+
# based on https://github.com/git/git/blob/140b9478dad5d19543c1cb4fd293ccec228f1240/t/t0003-attributes.sh#L45
19+
mkdir -p a/b/d a/c b
20+
(
21+
echo "[attr]notest !test"
22+
echo "\" d \" test=d"
23+
echo " e test=e"
24+
echo " e\" test=e"
25+
echo "f test=f"
26+
echo "a/i test=a/i"
27+
echo "onoff test -test"
28+
echo "offon -test test"
29+
echo "no notest"
30+
echo "A/e/F test=A/e/F"
31+
echo "\!escaped test-escaped"
32+
echo "**/recursive test-double-star-slash"
33+
echo "a**f test-double-star-no-slash"
34+
echo "dir-slash/ never"
35+
echo "dir/** always"
36+
) > .gitattributes
37+
(
38+
echo "g test=a/g"
39+
echo "b/g test=a/b/g"
40+
) > a/.gitattributes
41+
(
42+
echo "h test=a/b/h"
43+
echo "d/* test=a/b/d/*"
44+
echo "d/yes notest"
45+
) > a/b/.gitattributes
46+
(
47+
echo "global test=global"
48+
echo "z/x/a global-no-wildcard-case-test"
49+
echo "z/x/* global-wildcard-case-test"
50+
) > user.attributes
51+
52+
git add . && git commit -qm c1
53+
54+
baseline z/x/a
55+
baseline Z/x/a
56+
baseline z/x/A
57+
baseline Z/X/a
58+
baseline Z/x/a
59+
baseline " d "
60+
baseline e
61+
baseline f
62+
baseline dir-slash
63+
baseline dir-slash/a
64+
baseline dir
65+
baseline dir/a
66+
baseline recursive
67+
baseline a/recursive
68+
baseline a/b/recursive
69+
baseline a/b/c/recursive
70+
baseline "!escaped"
71+
baseline af
72+
baseline axf
73+
baseline a/b/d/no
74+
baseline a/e/f
75+
baseline a/f
76+
baseline a/b/d/g
77+
baseline a/B/D/g
78+
baseline b/g
79+
baseline a/c/f
80+
baseline "e\""
81+
baseline a/i
82+
baseline A/b/h
83+
baseline A/B/D/NO
84+
baseline subdir/a/i
85+
baseline onoff
86+
baseline offon
87+
baseline no
88+
baseline A/e/F
89+
baseline a/e/F
90+
baseline a/e/f
91+
baseline a/g
92+
baseline a/b/g
93+
baseline a/b/h
94+
baseline a/b/d/ANY
95+
baseline a/b/d/yes
96+
baseline global
97+
)
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
use std::collections::BTreeMap;
2+
3+
use bstr::{BStr, ByteSlice};
4+
use gix_attributes::{
5+
search::{AttributeId, Outcome},
6+
AssignmentRef, NameRef, StateRef,
7+
};
8+
use gix_glob::pattern::Case;
9+
10+
#[test]
11+
fn baseline() -> crate::Result {
12+
let mut buf = Vec::new();
13+
// Due to the way our setup differs from gits dynamic stack (which involves trying to read files from disk
14+
// by path) we can only test one case baseline, so we require multiple platforms (or filesystems) to run this.
15+
// TODO: what happens in case of the stack?
16+
let case = if gix_fs::Capabilities::probe("../.git").ignore_case {
17+
Case::Fold
18+
} else {
19+
Case::Sensitive
20+
};
21+
let (mut group, mut collection, base, input) = baseline::user_attributes("basics")?;
22+
23+
// Note that we have to hard-code these files for a lack of dynamic stack.
24+
// This isn't a problem as non-matching prefixes will simply be ignored.
25+
for (file, use_base) in [
26+
(".gitattributes", false),
27+
("a/.gitattributes", true),
28+
("a/b/.gitattributes", true),
29+
] {
30+
group.add_patterns_file(
31+
base.join(file),
32+
false,
33+
use_base.then_some(base.as_path()),
34+
&mut buf,
35+
&mut collection,
36+
)?;
37+
}
38+
assert_eq!(
39+
group.num_pattern_lists(),
40+
1 + 4,
41+
"should have loaded all files, and the builtins"
42+
);
43+
44+
let mut actual = gix_attributes::search::Outcome::default();
45+
actual.initialize(&collection);
46+
for (rela_path, expected) in (baseline::Expectations { lines: input.lines() }) {
47+
actual.reset();
48+
let has_match = group.pattern_matching_relative_path(rela_path, case, &mut actual);
49+
assert_references(&actual);
50+
let actual: Vec<_> = actual
51+
.iter()
52+
.filter_map(|m| (!m.assignment.state.is_unspecified()).then(|| m.assignment.as_ref()))
53+
.collect();
54+
assert_eq!(actual, expected, "we have the same matches: {rela_path:?}");
55+
assert_ne!(has_match, actual.is_empty());
56+
}
57+
58+
Ok(())
59+
}
60+
61+
fn assert_references(out: &Outcome) {
62+
for m in out.iter() {
63+
if let Some(source) = m.kind.source_id() {
64+
let sm = out
65+
.match_by_id(source)
66+
.expect("sources are always available in the outcome");
67+
assert_ne!(
68+
sm.assignment.name, m.assignment.name,
69+
"it's impossible to resolve to ourselves"
70+
);
71+
}
72+
}
73+
}
74+
75+
fn _by_name(assignments: Vec<AssignmentRef<'_>>) -> BTreeMap<NameRef<'_>, StateRef<'_>> {
76+
assignments.into_iter().map(|a| (a.name, a.state)).collect()
77+
}
78+
79+
fn _assignments<'a>(
80+
input: impl IntoIterator<Item = (&'a str, usize)> + 'a,
81+
) -> impl Iterator<Item = (AttributeId, gix_attributes::NameRef<'a>)> + 'a {
82+
input.into_iter().map(|(name, order)| {
83+
(
84+
AttributeId(order),
85+
gix_attributes::NameRef::try_from(BStr::new(name)).expect("valid name"),
86+
)
87+
})
88+
}
89+
90+
mod baseline {
91+
use bstr::{BStr, ByteSlice};
92+
93+
/// Read user-attributes and baseline in one go.
94+
pub fn user_attributes_named_baseline(
95+
name: &str,
96+
baseline: &str,
97+
) -> crate::Result<(gix_attributes::Search, MetadataCollection, PathBuf, Vec<u8>)> {
98+
let dir = gix_testtools::scripted_fixture_read_only("make_attributes_baseline.sh")?;
99+
let base = dir.join(name);
100+
let input = std::fs::read(base.join(baseline))?;
101+
102+
let mut buf = Vec::new();
103+
let mut collection = MetadataCollection::default();
104+
let group = gix_attributes::Search::new_globals([base.join("user.attributes")], &mut buf, &mut collection)?;
105+
106+
Ok((group, collection, base, input))
107+
}
108+
use std::path::PathBuf;
109+
110+
use gix_attributes::{search::MetadataCollection, AssignmentRef, StateRef};
111+
112+
/// Read user-attributes and baseline in one go.
113+
pub fn user_attributes(
114+
name: &str,
115+
) -> crate::Result<(gix_attributes::Search, MetadataCollection, PathBuf, Vec<u8>)> {
116+
user_attributes_named_baseline(name, "baseline")
117+
}
118+
119+
pub struct Expectations<'a> {
120+
pub lines: bstr::Lines<'a>,
121+
}
122+
123+
impl<'a> Iterator for Expectations<'a> {
124+
type Item = (
125+
&'a BStr,
126+
// Names might refer to attributes or macros
127+
Vec<AssignmentRef<'a>>,
128+
);
129+
130+
fn next(&mut self) -> Option<Self::Item> {
131+
let path = self.lines.next()?;
132+
let mut assignments = Vec::new();
133+
loop {
134+
let line = self.lines.next()?;
135+
if line.is_empty() {
136+
return Some((path.as_bstr(), assignments));
137+
}
138+
139+
let mut prev = None;
140+
let mut tokens = line.splitn(3, |b| {
141+
let is_match = *b == b' ' && prev.take() == Some(b':');
142+
prev = Some(*b);
143+
is_match
144+
});
145+
146+
if let Some(((_path, attr), info)) = tokens.next().zip(tokens.next()).zip(tokens.next()) {
147+
let state = match info {
148+
b"set" => StateRef::Set,
149+
b"unset" => StateRef::Unset,
150+
b"unspecified" => StateRef::Unspecified,
151+
_ => StateRef::from_bytes(info),
152+
};
153+
let attr = attr.trim_end_with(|b| b == ':');
154+
assignments.push(AssignmentRef {
155+
name: gix_attributes::NameRef::try_from(attr.as_bstr()).expect("valid attributes"),
156+
state,
157+
});
158+
} else {
159+
unreachable!("invalid line format: {line:?}", line = line.as_bstr())
160+
}
161+
}
162+
}
163+
}
164+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
mod create_directory;
22

3-
#[allow(unused)]
3+
mod attributes;
44
mod ignore;

0 commit comments

Comments
 (0)