diff --git a/gix-attributes/src/lib.rs b/gix-attributes/src/lib.rs index 4b1090fa9bb..65d629b2761 100644 --- a/gix-attributes/src/lib.rs +++ b/gix-attributes/src/lib.rs @@ -6,7 +6,8 @@ doc = ::document_features::document_features!() )] #![cfg_attr(all(doc, feature = "document-features"), feature(doc_cfg, doc_auto_cfg))] -#![deny(missing_docs, rust_2018_idioms, unsafe_code)] +#![deny(missing_docs, rust_2018_idioms)] +#![forbid(unsafe_code)] pub use gix_glob as glob; use kstring::{KString, KStringRef}; diff --git a/gix-attributes/src/name.rs b/gix-attributes/src/name.rs index 40d86fd4cec..bed73388e78 100644 --- a/gix-attributes/src/name.rs +++ b/gix-attributes/src/name.rs @@ -1,8 +1,7 @@ +use crate::{Name, NameRef}; use bstr::{BStr, BString, ByteSlice}; use kstring::KStringRef; -use crate::{Name, NameRef}; - impl<'a> NameRef<'a> { /// Turn this ref into its owned counterpart. pub fn to_owned(self) -> Name { diff --git a/gix-attributes/src/parse.rs b/gix-attributes/src/parse.rs index 2d90a006df6..5b9048d3794 100644 --- a/gix-attributes/src/parse.rs +++ b/gix-attributes/src/parse.rs @@ -1,10 +1,9 @@ use std::borrow::Cow; +use crate::{name, AssignmentRef, Name, NameRef, StateRef}; use bstr::{BStr, ByteSlice}; use kstring::KStringRef; -use crate::{name, AssignmentRef, Name, NameRef, StateRef}; - /// The kind of attribute that was parsed. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] diff --git a/gix-attributes/src/search/mod.rs b/gix-attributes/src/search/mod.rs index 83c6d41929c..db17f01fe36 100644 --- a/gix-attributes/src/search/mod.rs +++ b/gix-attributes/src/search/mod.rs @@ -1,7 +1,6 @@ -use std::collections::HashMap; - use kstring::KString; use smallvec::SmallVec; +use std::collections::HashMap; use crate::{Assignment, AssignmentRef}; diff --git a/gix-attributes/src/state.rs b/gix-attributes/src/state.rs index 80ebcfead56..8f2a466bcbf 100644 --- a/gix-attributes/src/state.rs +++ b/gix-attributes/src/state.rs @@ -1,29 +1,24 @@ -use bstr::{BStr, ByteSlice}; -use kstring::{KString, KStringRef}; - use crate::{State, StateRef}; +use bstr::{BStr, BString, ByteSlice}; /// A container to encapsulate a tightly packed and typically unallocated byte value that isn't necessarily UTF8 encoded. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Value(KString); +// TODO: This should be some sort of 'smallbstring' - but can't use `kstring` here due to UTF8 requirement. 5% performance boost possible. +// What's really needed here is a representation that displays as string when serialized which helps with JSON. +// Maybe `smallvec` with display and serialization wrapper would do the trick? +pub struct Value(BString); /// A reference container to encapsulate a tightly packed and typically unallocated byte value that isn't necessarily UTF8 encoded. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct ValueRef<'a>(#[cfg_attr(feature = "serde", serde(borrow))] KStringRef<'a>); +pub struct ValueRef<'a>(#[cfg_attr(feature = "serde", serde(borrow))] &'a [u8]); /// Lifecycle impl<'a> ValueRef<'a> { /// Keep `input` as our value. pub fn from_bytes(input: &'a [u8]) -> Self { - Self(KStringRef::from_ref( - // SAFETY: our API makes accessing that value as `str` impossible, so illformed UTF8 is never exposed as such. - #[allow(unsafe_code)] - unsafe { - std::str::from_utf8_unchecked(input) - }, - )) + Self(input) } } @@ -42,7 +37,7 @@ impl ValueRef<'_> { impl<'a> From<&'a str> for ValueRef<'a> { fn from(v: &'a str) -> Self { - ValueRef(v.into()) + ValueRef(v.as_bytes()) } } @@ -54,7 +49,7 @@ impl<'a> From> for Value { impl From<&str> for Value { fn from(v: &str) -> Self { - Value(KString::from_ref(v)) + Value(v.as_bytes().into()) } } diff --git a/gix-attributes/tests/parse/mod.rs b/gix-attributes/tests/parse/mod.rs index e1fcb060e06..fb40948e7ea 100644 --- a/gix-attributes/tests/parse/mod.rs +++ b/gix-attributes/tests/parse/mod.rs @@ -1,4 +1,5 @@ use bstr::BString; +use gix_attributes::state::ValueRef; use gix_attributes::{parse, StateRef}; use gix_glob::pattern::Mode; use gix_testtools::fixture_bytes; @@ -275,6 +276,19 @@ fn attributes_can_have_values() { ); } +#[test] +fn attributes_can_have_illformed_utf8() { + assert_eq!( + byte_line(b"p a=one b=\xC3\x28\x41 c=d "), + ( + pattern("p", Mode::NO_SUB_DIR, None), + vec![value("a", "one"), byte_value("b", b"\xC3\x28\x41"), value("c", "d")], + 1 + ), + "illformed UTF8 is fully supported" + ); +} + #[test] fn attributes_see_state_adjustments_over_value_assignments() { assert_eq!( @@ -325,6 +339,10 @@ fn value<'b>(attr: &str, value: &'b str) -> (BString, StateRef<'b>) { (attr.into(), StateRef::Value(value.into())) } +fn byte_value<'b>(attr: &str, value: &'b [u8]) -> (BString, StateRef<'b>) { + (attr.into(), StateRef::Value(ValueRef::from_bytes(value))) +} + fn pattern(name: &str, flags: gix_glob::pattern::Mode, first_wildcard_pos: Option) -> parse::Kind { parse::Kind::Pattern(gix_glob::Pattern { text: name.into(), @@ -344,6 +362,17 @@ fn line(input: &str) -> ExpandedAttribute { try_line(input).unwrap() } +fn byte_line(input: &[u8]) -> ExpandedAttribute { + try_byte_line(input).unwrap() +} + +fn try_byte_line(input: &[u8]) -> Result { + let mut lines = gix_attributes::parse(input); + let res = expand(lines.next().unwrap())?; + assert!(lines.next().is_none(), "expected only one line"); + Ok(res) +} + fn lenient_lines(input: &str) -> Vec { gix_attributes::parse(input.as_bytes()) .map(expand) diff --git a/gix-attributes/tests/search/mod.rs b/gix-attributes/tests/search/mod.rs index 6ce6f7b3d3d..2d793f52b29 100644 --- a/gix-attributes/tests/search/mod.rs +++ b/gix-attributes/tests/search/mod.rs @@ -270,7 +270,7 @@ fn given_attributes_are_made_available_in_given_order() -> crate::Result { fn size_of_outcome() { assert_eq!( std::mem::size_of::(), - 904, + 840, "it's quite big, shouldn't change without us noticing" ) } diff --git a/gix-dir/src/walk/classify.rs b/gix-dir/src/walk/classify.rs index 9fb2b54a5a6..2edfadf7e25 100644 --- a/gix-dir/src/walk/classify.rs +++ b/gix-dir/src/walk/classify.rs @@ -169,6 +169,7 @@ pub fn path( .map(|platform| platform.excluded_kind()) }) .map_err(Error::ExcludesAccess)? + .filter(|_| filename_start_idx > 0) { out.status = entry::Status::Ignored(excluded); } @@ -256,6 +257,7 @@ pub fn path( if let Some(excluded) = ctx .excludes .as_mut() + .filter(|_| !rela_path.is_empty()) .map_or(Ok(None), |stack| { stack .at_entry(rela_path.as_bstr(), is_dir, ctx.objects) diff --git a/gix-dir/tests/fixtures/many.sh b/gix-dir/tests/fixtures/many.sh index 8075d18bcd2..03344e8aeb0 100755 --- a/gix-dir/tests/fixtures/many.sh +++ b/gix-dir/tests/fixtures/many.sh @@ -351,3 +351,83 @@ git clone submodule multiple-submodules git submodule add ../submodule a/b git commit -m "add modules" ) + +git clone submodule one-ignored-submodule +(cd one-ignored-submodule + git submodule add ../submodule submodule + echo '/submodule/' > .gitignore + echo '*' > submodule/.gitignore + git commit -m "add seemingly ignored submodule" +) + +git init slash-in-root-and-negated +(cd slash-in-root-and-negated + cat <<'EOF' >.gitignore +/ +!file +!*.md +!.github +!.github/** +EOF + touch file readme.md + mkdir .github + touch .github/workflow.yml + git add .github readme.md .gitignore + git commit -m "init" +) + +git init star-in-root-and-negated +(cd star-in-root-and-negated + cat <<'EOF' >.gitignore +* +!file +!.gitignore +!*.md +!.github +!.github/** +EOF + touch file readme.md + mkdir .github + touch .github/workflow.yml + git add .github readme.md .gitignore + git commit -m "init" +) + +git init slash-in-subdir-and-negated +(cd slash-in-subdir-and-negated + mkdir sub + (cd sub + cat <<'EOF' >.gitignore +/ +!file +!*.md +!.github +!.github/** +EOF + touch file readme.md + mkdir .github + touch .github/workflow.yml + git add .github readme.md .gitignore + git commit -m "init" + ) +) + +git init star-in-subdir-and-negated +(cd star-in-subdir-and-negated + mkdir sub + (cd sub + cat <<'EOF' >.gitignore +* +!file +!.gitignore +!*.md +!.github +!.github/** +EOF + touch file readme.md + mkdir .github + touch .github/workflow.yml + git add .github readme.md .gitignore + git commit -m "init" + ) +) diff --git a/gix-dir/tests/walk/mod.rs b/gix-dir/tests/walk/mod.rs index dc880059856..6fb6c938a0f 100644 --- a/gix-dir/tests/walk/mod.rs +++ b/gix-dir/tests/walk/mod.rs @@ -4276,3 +4276,161 @@ fn type_mismatch_ignore_case_clash_file_is_dir() { If there was no special handling for this, it would have found the file (`d` in the index, icase), which would have been wrong." ); } + +#[test] +fn top_level_slash_with_negations() -> crate::Result { + for repo_name in ["slash-in-root-and-negated", "star-in-root-and-negated"] { + let root = fixture(repo_name); + let ((out, _root), entries) = collect(&root, None, |keep, ctx| walk(&root, ctx, options_emit_all(), keep)); + assert_eq!( + out, + walk::Outcome { + read_dir_calls: 2, + returned_entries: entries.len(), + seen_entries: 5, + } + ); + assert_eq!( + entries, + &[ + entry_nokind(".git", Pruned).with_property(DotGit).with_match(Always), + entry(".github/workflow.yml", Tracked, File), + entry(".gitignore", Tracked, File), + entry("file", Untracked, File), + entry("readme.md", Tracked, File), + ], + "the top-level is never considered ignored" + ); + + let ((out, _root), entries) = collect(&root, None, |keep, ctx| { + walk( + &root, + ctx, + walk::Options { + for_deletion: Some(ForDeletionMode::FindRepositoriesInIgnoredDirectories), + emit_tracked: false, + ..options_emit_all() + }, + keep, + ) + }); + assert_eq!( + out, + walk::Outcome { + read_dir_calls: 2, + returned_entries: entries.len(), + seen_entries: 5, + } + ); + assert_eq!( + entries, + &[ + entry_nokind(".git", Pruned).with_property(DotGit).with_match(Always), + entry("file", Untracked, File) + ], + "And the negated file is correctly detected as untracked" + ); + } + Ok(()) +} + +#[test] +fn subdir_slash_with_negations() -> crate::Result { + for repo_name in ["slash-in-subdir-and-negated", "star-in-subdir-and-negated"] { + let root = fixture(repo_name); + let ((out, _root), entries) = collect(&root, None, |keep, ctx| walk(&root, ctx, options_emit_all(), keep)); + assert_eq!( + out, + walk::Outcome { + read_dir_calls: 3, + returned_entries: entries.len(), + seen_entries: 5, + } + ); + assert_eq!( + entries, + &[ + entry_nokind(".git", Pruned).with_property(DotGit).with_match(Always), + entry("sub/.github/workflow.yml", Tracked, File), + entry("sub/.gitignore", Tracked, File), + entry("sub/file", Untracked, File), + entry("sub/readme.md", Tracked, File), + ], + "subdirectory matches work as expected, also with a `/` which has no bearing." + ); + + let ((out, _root), entries) = collect(&root, None, |keep, ctx| { + walk( + &root, + ctx, + walk::Options { + for_deletion: Some(ForDeletionMode::FindRepositoriesInIgnoredDirectories), + emit_tracked: false, + ..options_emit_all() + }, + keep, + ) + }); + assert_eq!( + out, + walk::Outcome { + read_dir_calls: 3, + returned_entries: entries.len(), + seen_entries: 5, + } + ); + assert_eq!( + entries, + &[ + entry_nokind(".git", Pruned).with_property(DotGit).with_match(Always), + entry("sub/file", Untracked, File) + ], + "This is expected, and the `.git` top-level is pruned." + ); + } + Ok(()) +} + +#[test] +fn one_ignored_submodule() -> crate::Result { + let root = fixture("one-ignored-submodule"); + let ((out, _root), entries) = collect(&root, None, |keep, ctx| walk(&root, ctx, options_emit_all(), keep)); + assert_eq!( + out, + walk::Outcome { + read_dir_calls: 1, + returned_entries: entries.len(), + seen_entries: 5, + } + ); + assert_eq!( + entries, + &[ + entry_nokind(".git", Pruned).with_property(DotGit).with_match(Always), + entry(".gitignore", Untracked, File), + entry(".gitmodules", Tracked, File), + entry("empty", Tracked, File), + entry("submodule", Tracked, Repository), + ], + "when traversing the worktree root, this is correct, the submodule doesn't count as ignored" + ); + + let troot = root.join("submodule"); + let ((out, _root), entries) = collect(&root, Some(&troot), |keep, ctx| { + walk(&root, ctx, options_emit_all(), keep) + }); + assert_eq!( + out, + walk::Outcome { + read_dir_calls: 0, + returned_entries: entries.len(), + seen_entries: 1 + } + ); + assert_eq!( + entries, + &[entryps("submodule", Tracked, Repository, Verbatim)], + "The submodule is simply tracked, it doesn't count as ignored" + ); + Ok(()) +} diff --git a/gix-ignore/tests/fixtures/make_global_and_external_and_dir_ignores.sh b/gix-ignore/tests/fixtures/make_global_and_external_and_dir_ignores.sh index 9959668e671..a0b3fba21a6 100755 --- a/gix-ignore/tests/fixtures/make_global_and_external_and_dir_ignores.sh +++ b/gix-ignore/tests/fixtures/make_global_and_external_and_dir_ignores.sh @@ -15,9 +15,8 @@ a/b/* z/x EOF -mkdir repo; +git init -q repo; (cd repo - git init -q git config core.excludesFile ../user.exclude cat <.git/info/exclude @@ -93,3 +92,70 @@ E/f E/F EOF ) + +git init slash-and-excludes +(cd slash-and-excludes + cat <.gitignore +# a lone slash does nothing +/ +# a file that was never ignored to begin +!file +EOF + + git check-ignore -vn --stdin 2>&1 <git-check-ignore.baseline || : +file +a-file-not-mentioned-in-gitignore +EOF +) + +git init slash-and-excludes-in-subdir +(cd slash-and-excludes-in-subdir + mkdir sub + (cd sub + cat <.gitignore +# a lone slash does nothing +/ +# a file that was never ignored to begin +!file +EOF + ) + git check-ignore -vn --stdin 2>&1 <git-check-ignore.baseline || : +sub/file +sub/a-file-not-mentioned-in-gitignore +a-file-not-mentioned-in-gitignore +EOF +) + +git init star-and-excludes +(cd star-and-excludes + cat <.gitignore +# everything is excluded by default +* +# And negations are used as an allow-list +!file +EOF + + git check-ignore -vn --stdin 2>&1 <git-check-ignore.baseline || : +file +a-file-not-mentioned-in-gitignore +EOF +) + +git init star-and-excludes-in-subdir +(cd star-and-excludes-in-subdir + mkdir sub + (cd sub + cat <.gitignore +# everything is excluded by default +* +# And negations are used as an allow-list +!file +EOF + ) + + git check-ignore -vn --stdin 2>&1 <git-check-ignore.baseline || : +sub/file +sub/a-file-not-mentioned-in-gitignore +a-file-not-mentioned-in-gitignore +EOF +) diff --git a/gix-ignore/tests/search/mod.rs b/gix-ignore/tests/search/mod.rs index af2d44a167b..ee47c44803e 100644 --- a/gix-ignore/tests/search/mod.rs +++ b/gix-ignore/tests/search/mod.rs @@ -1,5 +1,3 @@ -use std::io::Read; - use bstr::{BStr, ByteSlice}; use gix_glob::pattern::Case; use gix_ignore::search::Match; @@ -31,69 +29,84 @@ impl<'a> Iterator for Expectations<'a> { #[test] fn baseline_from_git_dir() -> crate::Result { - let case = if gix_fs::Capabilities::probe("../.git".as_ref()).ignore_case { - Case::Fold - } else { - Case::Sensitive - }; - let dir = gix_testtools::scripted_fixture_read_only("make_global_and_external_and_dir_ignores.sh")?; - let repo_dir = dir.join("repo"); - let git_dir = repo_dir.join(".git"); - let baseline = std::fs::read(git_dir.parent().unwrap().join("git-check-ignore.baseline"))?; - let mut buf = Vec::new(); - let mut group = gix_ignore::Search::from_git_dir(&git_dir, Some(dir.join("user.exclude")), &mut buf)?; + for repo_name in [ + "repo", + "slash-and-excludes", + "star-and-excludes-in-subdir", + "slash-and-excludes-in-subdir", + ] { + let case = if gix_fs::Capabilities::probe("../.git".as_ref()).ignore_case { + Case::Fold + } else { + Case::Sensitive + }; + let dir = gix_testtools::scripted_fixture_read_only("make_global_and_external_and_dir_ignores.sh")?; + let repo_dir = dir.join(repo_name); + let git_dir = repo_dir.join(".git"); + let baseline = std::fs::read(git_dir.parent().unwrap().join("git-check-ignore.baseline"))?; + let mut buf = Vec::new(); + let user_exclude = dir.join("user.exclude"); + let mut group = + gix_ignore::Search::from_git_dir(&git_dir, user_exclude.is_file().then_some(user_exclude), &mut buf)?; - assert!( - !gix_glob::search::add_patterns_file(&mut group.patterns, "not-a-file".into(), false, None, &mut buf)?, - "missing files are no problem and cause a negative response" - ); - assert!( - gix_glob::search::add_patterns_file( - &mut group.patterns, - repo_dir.join(".gitignore"), - true, - repo_dir.as_path().into(), - &mut buf - )?, - "existing files return true" - ); + assert!( + !gix_glob::search::add_patterns_file(&mut group.patterns, "not-a-file".into(), false, None, &mut buf)?, + "missing files are no problem and cause a negative response" + ); + let mut ignore_file = repo_dir.join(".gitignore"); + if !ignore_file.is_file() { + ignore_file.pop(); + ignore_file.push("sub/.gitignore"); + } + assert!( + gix_glob::search::add_patterns_file( + &mut group.patterns, + ignore_file, + true, + repo_dir.as_path().into(), + &mut buf + )?, + "existing files return true" + ); - buf.clear(); - let ignore_file = repo_dir.join("dir-with-ignore").join(".gitignore"); - std::fs::File::open(&ignore_file)?.read_to_end(&mut buf)?; - group.add_patterns_buffer(&buf, ignore_file, repo_dir.as_path().into()); + let ignore_file = repo_dir.join("dir-with-ignore").join(".gitignore"); + if ignore_file.is_file() { + let buf = std::fs::read(&ignore_file)?; + group.add_patterns_buffer(&buf, ignore_file, repo_dir.as_path().into()); + } - for (path, source_and_line) in (Expectations { - lines: baseline.lines(), - }) { - let actual = group.pattern_matching_relative_path( - path, - repo_dir - .join(path.to_str_lossy().as_ref()) - .metadata() - .ok() - .map(|m| m.is_dir()), - case, - ); - match (actual, source_and_line) { - ( - Some(Match { - sequence_number, - pattern: _, - source, - kind: gix_ignore::Kind::Expendable, - }), - Some((expected_source, line, _expected_pattern)), - ) => { - assert_eq!(sequence_number, line, "our counting should match the one used in git"); - assert_eq!( - source.map(|p| p.canonicalize().unwrap()), - Some(repo_dir.join(expected_source.to_str_lossy().as_ref()).canonicalize()?) - ); - } - (None, None) => {} - (actual, expected) => { - panic!("{case:?}: actual {actual:?} should match {expected:?} with path '{path}'") + for (path, source_and_line) in (Expectations { + lines: baseline.lines(), + }) { + let actual = group.pattern_matching_relative_path( + path, + repo_dir + .join(path.to_str_lossy().as_ref()) + .metadata() + .ok() + .map(|m| m.is_dir()), + case, + ); + match (actual, source_and_line) { + ( + Some(Match { + sequence_number, + pattern: _, + source, + kind: gix_ignore::Kind::Expendable, + }), + Some((expected_source, line, _expected_pattern)), + ) => { + assert_eq!(sequence_number, line, "our counting should match the one used in git"); + assert_eq!( + source.map(|p| p.canonicalize().unwrap()), + Some(repo_dir.join(expected_source.to_str_lossy().as_ref()).canonicalize()?) + ); + } + (None, None) => {} + (actual, expected) => { + panic!("{repo_name}: {case:?}: actual {actual:?} should match {expected:?} with path '{path}'") + } } } } diff --git a/gix-transport/src/client/git/mod.rs b/gix-transport/src/client/git/mod.rs index d27f468ff8f..8b246b4ddcd 100644 --- a/gix-transport/src/client/git/mod.rs +++ b/gix-transport/src/client/git/mod.rs @@ -64,7 +64,7 @@ mod message { out.extend_from_slice(host.as_bytes()); if let Some(port) = port { out.push_byte(b':'); - out.push_str(&format!("{port}")); + out.push_str(format!("{port}")); } out.push(0); } diff --git a/gix/src/repository/config/transport.rs b/gix/src/repository/config/transport.rs index ce0751f6347..90376412be3 100644 --- a/gix/src/repository/config/transport.rs +++ b/gix/src/repository/config/transport.rs @@ -193,10 +193,7 @@ impl crate::Repository { remote_name .and_then(|name| { config - .string_filter( - &format!("remote.{}.{}", name, Remote::PROXY.name), - &mut trusted_only, - ) + .string_filter(format!("remote.{}.{}", name, Remote::PROXY.name), &mut trusted_only) .map(|v| (v, Cow::Owned(format!("remote.{name}.proxy").into()), &Remote::PROXY)) }) .or_else(|| { @@ -254,7 +251,7 @@ impl crate::Repository { remote_name .and_then(|name| { config - .string_filter(&format!("remote.{name}.proxyAuthMethod"), &mut trusted_only) + .string_filter(format!("remote.{name}.proxyAuthMethod"), &mut trusted_only) .map(|v| { ( v, diff --git a/gix/src/repository/remote.rs b/gix/src/repository/remote.rs index 99a6c09b216..1314a9ebeac 100644 --- a/gix/src/repository/remote.rs +++ b/gix/src/repository/remote.rs @@ -137,7 +137,7 @@ impl crate::Repository { let mut config_url = |key: &'static config::tree::keys::Url, kind: &'static str| { self.config .resolved - .string_filter(&format!("remote.{}.{}", name_or_url, key.name), &mut filter) + .string_filter(format!("remote.{}.{}", name_or_url, key.name), &mut filter) .map(|url| { key.try_into_url(url).map_err(|err| find::Error::Url { kind, @@ -151,7 +151,7 @@ impl crate::Repository { let config = &self.config.resolved; let fetch_specs = config - .strings_filter(&format!("remote.{}.{}", name_or_url, "fetch"), &mut filter) + .strings_filter(format!("remote.{}.{}", name_or_url, "fetch"), &mut filter) .map(|specs| { config_spec( specs, @@ -161,7 +161,7 @@ impl crate::Repository { ) }); let push_specs = config - .strings_filter(&format!("remote.{}.{}", name_or_url, "push"), &mut filter) + .strings_filter(format!("remote.{}.{}", name_or_url, "push"), &mut filter) .map(|specs| { config_spec( specs, @@ -171,7 +171,7 @@ impl crate::Repository { ) }); let fetch_tags = config - .string_filter(&format!("remote.{}.{}", name_or_url, "tagOpt"), &mut filter) + .string_filter(format!("remote.{}.{}", name_or_url, "tagOpt"), &mut filter) .map(|value| { config::tree::Remote::TAG_OPT .try_into_tag_opt(value) diff --git a/gix/tests/remote/mod.rs b/gix/tests/remote/mod.rs index 03320c0966d..6958f5f7e2a 100644 --- a/gix/tests/remote/mod.rs +++ b/gix/tests/remote/mod.rs @@ -70,6 +70,16 @@ mod ref_map; mod save; mod name { + #[test] + fn origin_is_valid() { + assert!(gix::remote::name::validated("origin").is_ok()); + } + + #[test] + fn multiple_slashes_are_valid() { + assert!(gix::remote::name::validated("origin/another").is_ok()); + } + #[test] fn empty_is_invalid() { assert!(gix::remote::name::validated("").is_err());