History

Merge branch 'fixes'

Sebastian Thiel committed 2 years ago

Commit b4dba1c2 parents db1b223 1e92d1e16 files · +444 −96Browse files

Unified · Split

16 files
  1. gix-attributes/src/lib.rs +2 −1
  2. gix-attributes/src/name.rs +1 −2
  3. gix-attributes/src/parse.rs +1 −2
  4. gix-attributes/src/search/mod.rs +1 −2
  5. gix-attributes/src/state.rs +9 −14
  6. gix-attributes/tests/parse/mod.rs +29 −0
  7. gix-attributes/tests/search/mod.rs +1 −1
  8. gix-dir/src/walk/classify.rs +2 −0
  9. gix-dir/tests/fixtures/many.sh +80 −0
  10. gix-dir/tests/walk/mod.rs +158 −0
  11. gix-ignore/tests/fixtures/make_global_and_external_and_dir_ignores.sh +68 −2
  12. gix-ignore/tests/search/mod.rs +75 −62
  13. gix-transport/src/client/git/mod.rs +1 −1
  14. gix/src/repository/config/transport.rs +2 −5
  15. gix/src/repository/remote.rs +4 −4
  16. gix/tests/remote/mod.rs +10 −0
gix-attributes/src/lib.rs+2−1
@@ -6,7 +6,8 @@ · 5 unmodified lines
66 doc = ::document_features::document_features!()
77 )]
88 #![cfg_attr(all(doc, feature = "document-features"), feature(doc_cfg, doc_auto_cfg))]
9−#![deny(missing_docs, rust_2018_idioms, unsafe_code)]
9+#![deny(missing_docs, rust_2018_idioms)]
10+#![forbid(unsafe_code)]
1011
1112 pub use gix_glob as glob;
1213 use kstring::{KString, KStringRef};
gix-attributes/src/name.rs+1−2
@@ -1,8 +1,7 @@
1+use crate::{Name, NameRef};
12 use bstr::{BStr, BString, ByteSlice};
23 use kstring::KStringRef;
34
4−use crate::{Name, NameRef};
5−
65 impl<'a> NameRef<'a> {
76 /// Turn this ref into its owned counterpart.
87 pub fn to_owned(self) -> Name {
gix-attributes/src/parse.rs+1−2
@@ -1,10 +1,9 @@
11 use std::borrow::Cow;
22
3+use crate::{name, AssignmentRef, Name, NameRef, StateRef};
34 use bstr::{BStr, ByteSlice};
45 use kstring::KStringRef;
56
6−use crate::{name, AssignmentRef, Name, NameRef, StateRef};
7−
87 /// The kind of attribute that was parsed.
98 #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
109 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
gix-attributes/src/search/mod.rs+1−2
@@ -1,7 +1,6 @@
1−use std::collections::HashMap;
2−
31 use kstring::KString;
42 use smallvec::SmallVec;
3+use std::collections::HashMap;
54
65 use crate::{Assignment, AssignmentRef};
76
gix-attributes/src/state.rs+9−14
@@ -1,29 +1,24 @@
1−use bstr::{BStr, ByteSlice};
2−use kstring::{KString, KStringRef};
3−
41 use crate::{State, StateRef};
2+use bstr::{BStr, BString, ByteSlice};
53
64 /// A container to encapsulate a tightly packed and typically unallocated byte value that isn't necessarily UTF8 encoded.
75 #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
86 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9−pub struct Value(KString);
7+// TODO: This should be some sort of 'smallbstring' - but can't use `kstring` here due to UTF8 requirement. 5% performance boost possible.
8+// What's really needed here is a representation that displays as string when serialized which helps with JSON.
9+// Maybe `smallvec` with display and serialization wrapper would do the trick?
10+pub struct Value(BString);
1011
1112 /// A reference container to encapsulate a tightly packed and typically unallocated byte value that isn't necessarily UTF8 encoded.
1213 #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
1314 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14−pub struct ValueRef<'a>(#[cfg_attr(feature = "serde", serde(borrow))] KStringRef<'a>);
15+pub struct ValueRef<'a>(#[cfg_attr(feature = "serde", serde(borrow))] &'a [u8]);
1516
1617 /// Lifecycle
1718 impl<'a> ValueRef<'a> {
1819 /// Keep `input` as our value.
1920 pub fn from_bytes(input: &'a [u8]) -> Self {
20− Self(KStringRef::from_ref(
21− // SAFETY: our API makes accessing that value as `str` impossible, so illformed UTF8 is never exposed as such.
22− #[allow(unsafe_code)]
23− unsafe {
24− std::str::from_utf8_unchecked(input)
25− },
26− ))
21+ Self(input)
2722 }
2823 }
2924
@@ -42,7 +37,7 @@ impl ValueRef<'_> { · 12 unmodified lines
4237
4338 impl<'a> From<&'a str> for ValueRef<'a> {
4439 fn from(v: &'a str) -> Self {
45− ValueRef(v.into())
40+ ValueRef(v.as_bytes())
4641 }
4742 }
4843
@@ -54,7 +49,7 @@ impl<'a> From<ValueRef<'a>> for Value { · 5 unmodified lines
5449
5550 impl From<&str> for Value {
5651 fn from(v: &str) -> Self {
57− Value(KString::from_ref(v))
52+ Value(v.as_bytes().into())
5853 }
5954 }
6055
gix-attributes/tests/parse/mod.rs+29−0
@@ -1,4 +1,5 @@
11 use bstr::BString;
2+use gix_attributes::state::ValueRef;
23 use gix_attributes::{parse, StateRef};
34 use gix_glob::pattern::Mode;
45 use gix_testtools::fixture_bytes;
@@ -275,6 +276,19 @@ fn attributes_can_have_values() { · 270 unmodified lines
275276 );
276277 }
277278
279+#[test]
280+fn attributes_can_have_illformed_utf8() {
281+ assert_eq!(
282+ byte_line(b"p a=one b=\xC3\x28\x41 c=d "),
283+ (
284+ pattern("p", Mode::NO_SUB_DIR, None),
285+ vec![value("a", "one"), byte_value("b", b"\xC3\x28\x41"), value("c", "d")],
286+ 1
287+ ),
288+ "illformed UTF8 is fully supported"
289+ );
290+}
291+
278292 #[test]
279293 fn attributes_see_state_adjustments_over_value_assignments() {
280294 assert_eq!(
@@ -325,6 +339,10 @@ fn value<'b>(attr: &str, value: &'b str) -> (BString, StateRef<'b>) { · 44 unmodified lines
325339 (attr.into(), StateRef::Value(value.into()))
326340 }
327341
342+fn byte_value<'b>(attr: &str, value: &'b [u8]) -> (BString, StateRef<'b>) {
343+ (attr.into(), StateRef::Value(ValueRef::from_bytes(value)))
344+}
345+
328346 fn pattern(name: &str, flags: gix_glob::pattern::Mode, first_wildcard_pos: Option<usize>) -> parse::Kind {
329347 parse::Kind::Pattern(gix_glob::Pattern {
330348 text: name.into(),
@@ -344,6 +362,17 @@ fn line(input: &str) -> ExpandedAttribute { · 13 unmodified lines
344362 try_line(input).unwrap()
345363 }
346364
365+fn byte_line(input: &[u8]) -> ExpandedAttribute {
366+ try_byte_line(input).unwrap()
367+}
368+
369+fn try_byte_line(input: &[u8]) -> Result<ExpandedAttribute, parse::Error> {
370+ let mut lines = gix_attributes::parse(input);
371+ let res = expand(lines.next().unwrap())?;
372+ assert!(lines.next().is_none(), "expected only one line");
373+ Ok(res)
374+}
375+
347376 fn lenient_lines(input: &str) -> Vec<ExpandedAttribute> {
348377 gix_attributes::parse(input.as_bytes())
349378 .map(expand)
gix-attributes/tests/search/mod.rs+1−1
@@ -270,7 +270,7 @@ fn given_attributes_are_made_available_in_given_order() -> crate::Result { · 269 unmodified lines
270270 fn size_of_outcome() {
271271 assert_eq!(
272272 std::mem::size_of::<Outcome>(),
273− 904,
273+ 840,
274274 "it's quite big, shouldn't change without us noticing"
275275 )
276276 }
gix-dir/src/walk/classify.rs+2−0
@@ -169,6 +169,7 @@ pub fn path( · 168 unmodified lines
169169 .map(|platform| platform.excluded_kind())
170170 })
171171 .map_err(Error::ExcludesAccess)?
172+ .filter(|_| filename_start_idx > 0)
172173 {
173174 out.status = entry::Status::Ignored(excluded);
174175 }
@@ -256,6 +257,7 @@ pub fn path( · 81 unmodified lines
256257 if let Some(excluded) = ctx
257258 .excludes
258259 .as_mut()
260+ .filter(|_| !rela_path.is_empty())
259261 .map_or(Ok(None), |stack| {
260262 stack
261263 .at_entry(rela_path.as_bstr(), is_dir, ctx.objects)
gix-dir/tests/fixtures/many.sh+80−0
@@ -351,3 +351,83 @@ git clone submodule multiple-submodules · 350 unmodified lines
351351 git submodule add ../submodule a/b
352352 git commit -m "add modules"
353353 )
354+
355+git clone submodule one-ignored-submodule
356+(cd one-ignored-submodule
357+ git submodule add ../submodule submodule
358+ echo '/submodule/' > .gitignore
359+ echo '*' > submodule/.gitignore
360+ git commit -m "add seemingly ignored submodule"
361+)
362+
363+git init slash-in-root-and-negated
364+(cd slash-in-root-and-negated
365+ cat <<'EOF' >.gitignore
366+/
367+!file
368+!*.md
369+!.github
370+!.github/**
371+EOF
372+ touch file readme.md
373+ mkdir .github
374+ touch .github/workflow.yml
375+ git add .github readme.md .gitignore
376+ git commit -m "init"
377+)
378+
379+git init star-in-root-and-negated
380+(cd star-in-root-and-negated
381+ cat <<'EOF' >.gitignore
382+*
383+!file
384+!.gitignore
385+!*.md
386+!.github
387+!.github/**
388+EOF
389+ touch file readme.md
390+ mkdir .github
391+ touch .github/workflow.yml
392+ git add .github readme.md .gitignore
393+ git commit -m "init"
394+)
395+
396+git init slash-in-subdir-and-negated
397+(cd slash-in-subdir-and-negated
398+ mkdir sub
399+ (cd sub
400+ cat <<'EOF' >.gitignore
401+/
402+!file
403+!*.md
404+!.github
405+!.github/**
406+EOF
407+ touch file readme.md
408+ mkdir .github
409+ touch .github/workflow.yml
410+ git add .github readme.md .gitignore
411+ git commit -m "init"
412+ )
413+)
414+
415+git init star-in-subdir-and-negated
416+(cd star-in-subdir-and-negated
417+ mkdir sub
418+ (cd sub
419+ cat <<'EOF' >.gitignore
420+*
421+!file
422+!.gitignore
423+!*.md
424+!.github
425+!.github/**
426+EOF
427+ touch file readme.md
428+ mkdir .github
429+ touch .github/workflow.yml
430+ git add .github readme.md .gitignore
431+ git commit -m "init"
432+ )
433+)
gix-dir/tests/walk/mod.rs+158−0
@@ -4276,3 +4276,161 @@ fn type_mismatch_ignore_case_clash_file_is_dir() { · 4275 unmodified lines
42764276 If there was no special handling for this, it would have found the file (`d` in the index, icase), which would have been wrong."
42774277 );
42784278 }
4279+
4280+#[test]
4281+fn top_level_slash_with_negations() -> crate::Result {
4282+ for repo_name in ["slash-in-root-and-negated", "star-in-root-and-negated"] {
4283+ let root = fixture(repo_name);
4284+ let ((out, _root), entries) = collect(&root, None, |keep, ctx| walk(&root, ctx, options_emit_all(), keep));
4285+ assert_eq!(
4286+ out,
4287+ walk::Outcome {
4288+ read_dir_calls: 2,
4289+ returned_entries: entries.len(),
4290+ seen_entries: 5,
4291+ }
4292+ );
4293+ assert_eq!(
4294+ entries,
4295+ &[
4296+ entry_nokind(".git", Pruned).with_property(DotGit).with_match(Always),
4297+ entry(".github/workflow.yml", Tracked, File),
4298+ entry(".gitignore", Tracked, File),
4299+ entry("file", Untracked, File),
4300+ entry("readme.md", Tracked, File),
4301+ ],
4302+ "the top-level is never considered ignored"
4303+ );
4304+
4305+ let ((out, _root), entries) = collect(&root, None, |keep, ctx| {
4306+ walk(
4307+ &root,
4308+ ctx,
4309+ walk::Options {
4310+ for_deletion: Some(ForDeletionMode::FindRepositoriesInIgnoredDirectories),
4311+ emit_tracked: false,
4312+ ..options_emit_all()
4313+ },
4314+ keep,
4315+ )
4316+ });
4317+ assert_eq!(
4318+ out,
4319+ walk::Outcome {
4320+ read_dir_calls: 2,
4321+ returned_entries: entries.len(),
4322+ seen_entries: 5,
4323+ }
4324+ );
4325+ assert_eq!(
4326+ entries,
4327+ &[
4328+ entry_nokind(".git", Pruned).with_property(DotGit).with_match(Always),
4329+ entry("file", Untracked, File)
4330+ ],
4331+ "And the negated file is correctly detected as untracked"
4332+ );
4333+ }
4334+ Ok(())
4335+}
4336+
4337+#[test]
4338+fn subdir_slash_with_negations() -> crate::Result {
4339+ for repo_name in ["slash-in-subdir-and-negated", "star-in-subdir-and-negated"] {
4340+ let root = fixture(repo_name);
4341+ let ((out, _root), entries) = collect(&root, None, |keep, ctx| walk(&root, ctx, options_emit_all(), keep));
4342+ assert_eq!(
4343+ out,
4344+ walk::Outcome {
4345+ read_dir_calls: 3,
4346+ returned_entries: entries.len(),
4347+ seen_entries: 5,
4348+ }
4349+ );
4350+ assert_eq!(
4351+ entries,
4352+ &[
4353+ entry_nokind(".git", Pruned).with_property(DotGit).with_match(Always),
4354+ entry("sub/.github/workflow.yml", Tracked, File),
4355+ entry("sub/.gitignore", Tracked, File),
4356+ entry("sub/file", Untracked, File),
4357+ entry("sub/readme.md", Tracked, File),
4358+ ],
4359+ "subdirectory matches work as expected, also with a `/` which has no bearing."
4360+ );
4361+
4362+ let ((out, _root), entries) = collect(&root, None, |keep, ctx| {
4363+ walk(
4364+ &root,
4365+ ctx,
4366+ walk::Options {
4367+ for_deletion: Some(ForDeletionMode::FindRepositoriesInIgnoredDirectories),
4368+ emit_tracked: false,
4369+ ..options_emit_all()
4370+ },
4371+ keep,
4372+ )
4373+ });
4374+ assert_eq!(
4375+ out,
4376+ walk::Outcome {
4377+ read_dir_calls: 3,
4378+ returned_entries: entries.len(),
4379+ seen_entries: 5,
4380+ }
4381+ );
4382+ assert_eq!(
4383+ entries,
4384+ &[
4385+ entry_nokind(".git", Pruned).with_property(DotGit).with_match(Always),
4386+ entry("sub/file", Untracked, File)
4387+ ],
4388+ "This is expected, and the `.git` top-level is pruned."
4389+ );
4390+ }
4391+ Ok(())
4392+}
4393+
4394+#[test]
4395+fn one_ignored_submodule() -> crate::Result {
4396+ let root = fixture("one-ignored-submodule");
4397+ let ((out, _root), entries) = collect(&root, None, |keep, ctx| walk(&root, ctx, options_emit_all(), keep));
4398+ assert_eq!(
4399+ out,
4400+ walk::Outcome {
4401+ read_dir_calls: 1,
4402+ returned_entries: entries.len(),
4403+ seen_entries: 5,
4404+ }
4405+ );
4406+ assert_eq!(
4407+ entries,
4408+ &[
4409+ entry_nokind(".git", Pruned).with_property(DotGit).with_match(Always),
4410+ entry(".gitignore", Untracked, File),
4411+ entry(".gitmodules", Tracked, File),
4412+ entry("empty", Tracked, File),
4413+ entry("submodule", Tracked, Repository),
4414+ ],
4415+ "when traversing the worktree root, this is correct, the submodule doesn't count as ignored"
4416+ );
4417+
4418+ let troot = root.join("submodule");
4419+ let ((out, _root), entries) = collect(&root, Some(&troot), |keep, ctx| {
4420+ walk(&root, ctx, options_emit_all(), keep)
4421+ });
4422+ assert_eq!(
4423+ out,
4424+ walk::Outcome {
4425+ read_dir_calls: 0,
4426+ returned_entries: entries.len(),
4427+ seen_entries: 1
4428+ }
4429+ );
4430+ assert_eq!(
4431+ entries,
4432+ &[entryps("submodule", Tracked, Repository, Verbatim)],
4433+ "The submodule is simply tracked, it doesn't count as ignored"
4434+ );
4435+ Ok(())
4436+}
gix-ignore/tests/fixtures/make_global_and_external_and_dir_ignores.sh+68−2
@@ -15,9 +15,8 @@ a/b/* · 14 unmodified lines
1515 z/x
1616 EOF
1717
18−mkdir repo;
18+git init -q repo;
1919 (cd repo
20− git init -q
2120 git config core.excludesFile ../user.exclude
2221
2322 cat <<EOF >.git/info/exclude
@@ -93,3 +92,70 @@ E/f · 69 unmodified lines
9392 E/F
9493 EOF
9594 )
95+
96+git init slash-and-excludes
97+(cd slash-and-excludes
98+ cat <<EOF >.gitignore
99+# a lone slash does nothing
100+/
101+# a file that was never ignored to begin
102+!file
103+EOF
104+
105+ git check-ignore -vn --stdin 2>&1 <<EOF >git-check-ignore.baseline || :
106+file
107+a-file-not-mentioned-in-gitignore
108+EOF
109+)
110+
111+git init slash-and-excludes-in-subdir
112+(cd slash-and-excludes-in-subdir
113+ mkdir sub
114+ (cd sub
115+ cat <<EOF >.gitignore
116+# a lone slash does nothing
117+/
118+# a file that was never ignored to begin
119+!file
120+EOF
121+ )
122+ git check-ignore -vn --stdin 2>&1 <<EOF >git-check-ignore.baseline || :
123+sub/file
124+sub/a-file-not-mentioned-in-gitignore
125+a-file-not-mentioned-in-gitignore
126+EOF
127+)
128+
129+git init star-and-excludes
130+(cd star-and-excludes
131+ cat <<EOF >.gitignore
132+# everything is excluded by default
133+*
134+# And negations are used as an allow-list
135+!file
136+EOF
137+
138+ git check-ignore -vn --stdin 2>&1 <<EOF >git-check-ignore.baseline || :
139+file
140+a-file-not-mentioned-in-gitignore
141+EOF
142+)
143+
144+git init star-and-excludes-in-subdir
145+(cd star-and-excludes-in-subdir
146+ mkdir sub
147+ (cd sub
148+ cat <<EOF >.gitignore
149+# everything is excluded by default
150+*
151+# And negations are used as an allow-list
152+!file
153+EOF
154+ )
155+
156+ git check-ignore -vn --stdin 2>&1 <<EOF >git-check-ignore.baseline || :
157+sub/file
158+sub/a-file-not-mentioned-in-gitignore
159+a-file-not-mentioned-in-gitignore
160+EOF
161+)
gix-ignore/tests/search/mod.rs+75−62
@@ -1,5 +1,3 @@
1−use std::io::Read;
2−
31 use bstr::{BStr, ByteSlice};
42 use gix_glob::pattern::Case;
53 use gix_ignore::search::Match;
@@ -31,69 +29,84 @@ impl<'a> Iterator for Expectations<'a> { · 25 unmodified lines
3129
3230 #[test]
3331 fn baseline_from_git_dir() -> crate::Result {
34− let case = if gix_fs::Capabilities::probe("../.git".as_ref()).ignore_case {
35− Case::Fold
36− } else {
37− Case::Sensitive
38− };
39− let dir = gix_testtools::scripted_fixture_read_only("make_global_and_external_and_dir_ignores.sh")?;
40− let repo_dir = dir.join("repo");
41− let git_dir = repo_dir.join(".git");
42− let baseline = std::fs::read(git_dir.parent().unwrap().join("git-check-ignore.baseline"))?;
43− let mut buf = Vec::new();
44− let mut group = gix_ignore::Search::from_git_dir(&git_dir, Some(dir.join("user.exclude")), &mut buf)?;
32+ for repo_name in [
33+ "repo",
34+ "slash-and-excludes",
35+ "star-and-excludes-in-subdir",
36+ "slash-and-excludes-in-subdir",
37+ ] {
38+ let case = if gix_fs::Capabilities::probe("../.git".as_ref()).ignore_case {
39+ Case::Fold
40+ } else {
41+ Case::Sensitive
42+ };
43+ let dir = gix_testtools::scripted_fixture_read_only("make_global_and_external_and_dir_ignores.sh")?;
44+ let repo_dir = dir.join(repo_name);
45+ let git_dir = repo_dir.join(".git");
46+ let baseline = std::fs::read(git_dir.parent().unwrap().join("git-check-ignore.baseline"))?;
47+ let mut buf = Vec::new();
48+ let user_exclude = dir.join("user.exclude");
49+ let mut group =
50+ gix_ignore::Search::from_git_dir(&git_dir, user_exclude.is_file().then_some(user_exclude), &mut buf)?;
4551
46− assert!(
47− !gix_glob::search::add_patterns_file(&mut group.patterns, "not-a-file".into(), false, None, &mut buf)?,
48− "missing files are no problem and cause a negative response"
49− );
50− assert!(
51− gix_glob::search::add_patterns_file(
52− &mut group.patterns,
53− repo_dir.join(".gitignore"),
54− true,
55− repo_dir.as_path().into(),
56− &mut buf
57− )?,
58− "existing files return true"
59− );
52+ assert!(
53+ !gix_glob::search::add_patterns_file(&mut group.patterns, "not-a-file".into(), false, None, &mut buf)?,
54+ "missing files are no problem and cause a negative response"
55+ );
56+ let mut ignore_file = repo_dir.join(".gitignore");
57+ if !ignore_file.is_file() {
58+ ignore_file.pop();
59+ ignore_file.push("sub/.gitignore");
60+ }
61+ assert!(
62+ gix_glob::search::add_patterns_file(
63+ &mut group.patterns,
64+ ignore_file,
65+ true,
66+ repo_dir.as_path().into(),
67+ &mut buf
68+ )?,
69+ "existing files return true"
70+ );
6071
61− buf.clear();
62− let ignore_file = repo_dir.join("dir-with-ignore").join(".gitignore");
63− std::fs::File::open(&ignore_file)?.read_to_end(&mut buf)?;
64− group.add_patterns_buffer(&buf, ignore_file, repo_dir.as_path().into());
72+ let ignore_file = repo_dir.join("dir-with-ignore").join(".gitignore");
73+ if ignore_file.is_file() {
74+ let buf = std::fs::read(&ignore_file)?;
75+ group.add_patterns_buffer(&buf, ignore_file, repo_dir.as_path().into());
76+ }
6577
66− for (path, source_and_line) in (Expectations {
67− lines: baseline.lines(),
68− }) {
69− let actual = group.pattern_matching_relative_path(
70− path,
71− repo_dir
72− .join(path.to_str_lossy().as_ref())
73− .metadata()
74− .ok()
75− .map(|m| m.is_dir()),
76− case,
77− );
78− match (actual, source_and_line) {
79− (
80− Some(Match {
81− sequence_number,
82− pattern: _,
83− source,
84− kind: gix_ignore::Kind::Expendable,
85− }),
86− Some((expected_source, line, _expected_pattern)),
87− ) => {
88− assert_eq!(sequence_number, line, "our counting should match the one used in git");
89− assert_eq!(
90− source.map(|p| p.canonicalize().unwrap()),
91− Some(repo_dir.join(expected_source.to_str_lossy().as_ref()).canonicalize()?)
92− );
93− }
94− (None, None) => {}
95− (actual, expected) => {
96− panic!("{case:?}: actual {actual:?} should match {expected:?} with path '{path}'")
78+ for (path, source_and_line) in (Expectations {
79+ lines: baseline.lines(),
80+ }) {
81+ let actual = group.pattern_matching_relative_path(
82+ path,
83+ repo_dir
84+ .join(path.to_str_lossy().as_ref())
85+ .metadata()
86+ .ok()
87+ .map(|m| m.is_dir()),
88+ case,
89+ );
90+ match (actual, source_and_line) {
91+ (
92+ Some(Match {
93+ sequence_number,
94+ pattern: _,
95+ source,
96+ kind: gix_ignore::Kind::Expendable,
97+ }),
98+ Some((expected_source, line, _expected_pattern)),
99+ ) => {
100+ assert_eq!(sequence_number, line, "our counting should match the one used in git");
101+ assert_eq!(
102+ source.map(|p| p.canonicalize().unwrap()),
103+ Some(repo_dir.join(expected_source.to_str_lossy().as_ref()).canonicalize()?)
104+ );
105+ }
106+ (None, None) => {}
107+ (actual, expected) => {
108+ panic!("{repo_name}: {case:?}: actual {actual:?} should match {expected:?} with path '{path}'")
109+ }
97110 }
98111 }
99112 }
gix-transport/src/client/git/mod.rs+1−1
@@ -64,7 +64,7 @@ mod message { · 63 unmodified lines
6464 out.extend_from_slice(host.as_bytes());
6565 if let Some(port) = port {
6666 out.push_byte(b':');
67− out.push_str(&format!("{port}"));
67+ out.push_str(format!("{port}"));
6868 }
6969 out.push(0);
7070 }
gix/src/repository/config/transport.rs+2−5
@@ -193,10 +193,7 @@ impl crate::Repository { · 192 unmodified lines
193193 remote_name
194194 .and_then(|name| {
195195 config
196− .string_filter(
197− &format!("remote.{}.{}", name, Remote::PROXY.name),
198− &mut trusted_only,
199− )
196+ .string_filter(format!("remote.{}.{}", name, Remote::PROXY.name), &mut trusted_only)
200197 .map(|v| (v, Cow::Owned(format!("remote.{name}.proxy").into()), &Remote::PROXY))
201198 })
202199 .or_else(|| {
@@ -254,7 +251,7 @@ impl crate::Repository { · 51 unmodified lines
254251 remote_name
255252 .and_then(|name| {
256253 config
257− .string_filter(&format!("remote.{name}.proxyAuthMethod"), &mut trusted_only)
254+ .string_filter(format!("remote.{name}.proxyAuthMethod"), &mut trusted_only)
258255 .map(|v| {
259256 (
260257 v,
gix/src/repository/remote.rs+4−4
@@ -137,7 +137,7 @@ impl crate::Repository { · 136 unmodified lines
137137 let mut config_url = |key: &'static config::tree::keys::Url, kind: &'static str| {
138138 self.config
139139 .resolved
140− .string_filter(&format!("remote.{}.{}", name_or_url, key.name), &mut filter)
140+ .string_filter(format!("remote.{}.{}", name_or_url, key.name), &mut filter)
141141 .map(|url| {
142142 key.try_into_url(url).map_err(|err| find::Error::Url {
143143 kind,
@@ -151,7 +151,7 @@ impl crate::Repository { · 7 unmodified lines
151151 let config = &self.config.resolved;
152152
153153 let fetch_specs = config
154− .strings_filter(&format!("remote.{}.{}", name_or_url, "fetch"), &mut filter)
154+ .strings_filter(format!("remote.{}.{}", name_or_url, "fetch"), &mut filter)
155155 .map(|specs| {
156156 config_spec(
157157 specs,
@@ -161,7 +161,7 @@ impl crate::Repository { · 3 unmodified lines
161161 )
162162 });
163163 let push_specs = config
164− .strings_filter(&format!("remote.{}.{}", name_or_url, "push"), &mut filter)
164+ .strings_filter(format!("remote.{}.{}", name_or_url, "push"), &mut filter)
165165 .map(|specs| {
166166 config_spec(
167167 specs,
@@ -171,7 +171,7 @@ impl crate::Repository { · 3 unmodified lines
171171 )
172172 });
173173 let fetch_tags = config
174− .string_filter(&format!("remote.{}.{}", name_or_url, "tagOpt"), &mut filter)
174+ .string_filter(format!("remote.{}.{}", name_or_url, "tagOpt"), &mut filter)
175175 .map(|value| {
176176 config::tree::Remote::TAG_OPT
177177 .try_into_tag_opt(value)
gix/tests/remote/mod.rs+10−0
@@ -70,6 +70,16 @@ mod ref_map; · 69 unmodified lines
7070 mod save;
7171 mod name {
7272
73+ #[test]
74+ fn origin_is_valid() {
75+ assert!(gix::remote::name::validated("origin").is_ok());
76+ }
77+
78+ #[test]
79+ fn multiple_slashes_are_valid() {
80+ assert!(gix::remote::name::validated("origin/another").is_ok());
81+ }
82+
7383 #[test]
7484 fn empty_is_invalid() {
7585 assert!(gix::remote::name::validated("").is_err());