From 41dd7545234e6d2637d2bca5bb6d4f6d8bfc8f57 Mon Sep 17 00:00:00 2001 From: Jan Mazur Date: Mon, 4 Jul 2022 01:42:24 -0700 Subject: [PATCH 001/366] git-features' walkdir: 2.3.1 -> 2.3.2 --- git-features/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-features/Cargo.toml b/git-features/Cargo.toml index b380da87aaa..778cfcfb5df 100644 --- a/git-features/Cargo.toml +++ b/git-features/Cargo.toml @@ -97,7 +97,7 @@ parking_lot = { version = "0.12.0", default-features = false, optional = true } jwalk = { version = "0.6.0", optional = true } ## Makes facilities of the `walkdir` crate partially available. ## In conjunction with the **parallel** feature, directory walking will be parallel instead behind a compatible interface. -walkdir = { version = "2.3.1", optional = true } # used when parallel is off +walkdir = { version = "2.3.2", optional = true } # used when parallel is off # hashing and 'fast-sha1' feature sha1_smol = { version = "1.0.0", optional = true } From a0f842c7f449a6a7aedc2742f7fc4f74a12fdd17 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 3 Jul 2022 21:21:48 +0800 Subject: [PATCH 002/366] avoid panics and provide errors instead of just not matching The previous function signatures made proper error handling impossible in the name of keeping it simple, and it might even have worked until now. --- git-config/src/file/from_paths.rs | 6 +- git-config/src/file/resolve_includes.rs | 72 +++++++++++-------- .../from_paths/includes/conditional/mod.rs | 1 + 3 files changed, 49 insertions(+), 30 deletions(-) diff --git a/git-config/src/file/from_paths.rs b/git-config/src/file/from_paths.rs index 657b4ee13a4..e9e57ebe6f7 100644 --- a/git-config/src/file/from_paths.rs +++ b/git-config/src/file/from_paths.rs @@ -10,8 +10,12 @@ pub enum Error { Interpolate(#[from] interpolate::Error), #[error("The maximum allowed length {} of the file include chain built by following nested resolve_includes is exceeded", .max_depth)] IncludeDepthExceeded { max_depth: u8 }, - #[error("Include paths from environment variables must not be relative")] + #[error("Include paths from environment variables must not be relative as no config file paths exists as root")] MissingConfigPath, + #[error("The git directory must be provided to support `gitdir:` conditional includes")] + MissingGitDir, + #[error(transparent)] + Realpath(#[from] git_path::realpath::Error), } /// Options when loading git config using [`File::from_paths()`][crate::File::from_paths()]. diff --git a/git-config/src/file/resolve_includes.rs b/git-config/src/file/resolve_includes.rs index 3dc083cbdf7..d208fdd5ebc 100644 --- a/git-config/src/file/resolve_includes.rs +++ b/git-config/src/file/resolve_includes.rs @@ -6,6 +6,7 @@ use std::{ use bstr::{BString, ByteSlice, ByteVec}; use git_ref::Category; +use crate::file::from_paths::Options; use crate::{ file::{from_paths, SectionId}, parser::Key, @@ -60,7 +61,7 @@ fn resolve_includes_recursive( extract_include_path(target_config, &mut include_paths, id) } else if header.name.0 == "includeIf" { if let Some(condition) = &header.subsection_name { - if include_condition_match(condition, target_config_path, options).is_some() { + if include_condition_match(condition, target_config_path, options)? { extract_include_path(target_config, &mut include_paths, id) } } @@ -96,8 +97,11 @@ fn include_condition_match( condition: &str, target_config_path: Option<&Path>, options: from_paths::Options<'_>, -) -> Option<()> { - let (prefix, condition) = condition.split_once(':')?; +) -> Result { + let (prefix, condition) = match condition.split_once(':') { + Some(t) => t, + None => return Ok(false), + }; match prefix { "gitdir" => gitdir_matches( condition, @@ -111,27 +115,29 @@ fn include_condition_match( options, git_glob::wildmatch::Mode::IGNORE_CASE, ), - "onbranch" => { - let branch_name = options.branch_name?; - let (_, branch_name) = branch_name - .category_and_short_name() - .filter(|(cat, _)| *cat == Category::LocalBranch)?; - - let mut condition = Cow::Borrowed(condition); - if condition.ends_with('/') { - condition = Cow::Owned(format!("{}**", condition)); - } - git_glob::wildmatch( - condition.as_ref().into(), - branch_name, - git_glob::wildmatch::Mode::NO_MATCH_SLASH_LITERAL, - ) - .then(|| ()) - } - _ => None, + "onbranch" => Ok(onbranch_matches(condition, options).is_some()), + _ => Ok(false), } } +fn onbranch_matches(condition: &str, options: Options<'_>) -> Option<()> { + let branch_name = options.branch_name?; + let (_, branch_name) = branch_name + .category_and_short_name() + .filter(|(cat, _)| *cat == Category::LocalBranch)?; + + let mut condition = Cow::Borrowed(condition); + if condition.ends_with('/') { + condition = Cow::Owned(format!("{}**", condition)); + } + git_glob::wildmatch( + condition.as_ref().into(), + branch_name, + git_glob::wildmatch::Mode::NO_MATCH_SLASH_LITERAL, + ) + .then(|| ()) +} + fn gitdir_matches( condition_path: &str, target_config_path: Option<&Path>, @@ -142,19 +148,20 @@ fn gitdir_matches( .. }: from_paths::Options<'_>, wildmatch_mode: git_glob::wildmatch::Mode, -) -> Option<()> { - let git_dir = git_path::to_unix_separators(git_path::into_bstr(git_dir?)); +) -> Result { + let git_dir = git_path::to_unix_separators(git_path::into_bstr(git_dir.ok_or(from_paths::Error::MissingGitDir)?)); let mut pattern_path = { let cow = Cow::Borrowed(condition_path.as_bytes()); - let path = values::Path::from(cow).interpolate(git_install_dir, home_dir).ok()?; + let path = values::Path::from(cow).interpolate(git_install_dir, home_dir)?; git_path::into_bstr(path).into_owned() }; if let Some(relative_pattern_path) = pattern_path.strip_prefix(b"./") { let parent_dir = target_config_path - .and_then(|path| path.parent()) - .unwrap_or_else(|| todo!("an error case for this and maybe even a test - git doesnt support this either")); + .ok_or(from_paths::Error::MissingConfigPath)? + .parent() + .expect("config path can never be /"); let mut joined_path = git_path::to_unix_separators(git_path::into_bstr(parent_dir)).into_owned(); joined_path.push(b'/'); joined_path.extend_from_slice(relative_pattern_path); @@ -177,12 +184,19 @@ fn gitdir_matches( let match_mode = git_glob::wildmatch::Mode::NO_MATCH_SLASH_LITERAL | wildmatch_mode; let is_match = git_glob::wildmatch(pattern_path.as_bstr(), git_dir.as_bstr(), match_mode); if is_match { - return Some(()); + return Ok(true); } - let expanded_git_dir = git_path::realpath(git_path::from_byte_slice(&git_dir), target_config_path?).ok()?; + let expanded_git_dir = git_path::realpath( + git_path::from_byte_slice(&git_dir), + target_config_path.ok_or(from_paths::Error::MissingConfigPath)?, + )?; let expanded_git_dir = git_path::to_unix_separators(git_path::into_bstr(expanded_git_dir)); - git_glob::wildmatch(pattern_path.as_bstr(), expanded_git_dir.as_bstr(), match_mode).then(|| ()) + Ok(git_glob::wildmatch( + pattern_path.as_bstr(), + expanded_git_dir.as_bstr(), + match_mode, + )) } fn resolve( diff --git a/git-config/tests/file/from_paths/includes/conditional/mod.rs b/git-config/tests/file/from_paths/includes/conditional/mod.rs index 1d41f83fa6b..ed68f6a9efc 100644 --- a/git-config/tests/file/from_paths/includes/conditional/mod.rs +++ b/git-config/tests/file/from_paths/includes/conditional/mod.rs @@ -394,6 +394,7 @@ fn various_gitdir() { fn options_with_git_dir(git_dir: &Path) -> from_paths::Options<'_> { from_paths::Options { git_dir: Some(git_dir), + home_dir: Some(git_dir.parent().unwrap()), ..Default::default() } } From 146eb0c831ce0a96e215b1ec6499a86bbf5902c9 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 4 Jul 2022 10:19:56 +0800 Subject: [PATCH 003/366] Definitely don't unconditionally convert to forward slashes This is a step towards figuring out how to handle backslashes correctly. It appears the parser is missing a normalization step, but to do that we should definitely not force `str` anywhere in our data structures so that should change first. --- git-config/src/file/resolve_includes.rs | 9 +++++---- .../tests/file/from_paths/includes/conditional/mod.rs | 6 ++++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/git-config/src/file/resolve_includes.rs b/git-config/src/file/resolve_includes.rs index d208fdd5ebc..04cf2f04198 100644 --- a/git-config/src/file/resolve_includes.rs +++ b/git-config/src/file/resolve_includes.rs @@ -149,7 +149,8 @@ fn gitdir_matches( }: from_paths::Options<'_>, wildmatch_mode: git_glob::wildmatch::Mode, ) -> Result { - let git_dir = git_path::to_unix_separators(git_path::into_bstr(git_dir.ok_or(from_paths::Error::MissingGitDir)?)); + let git_dir = + git_path::to_unix_separators_on_windows(git_path::into_bstr(git_dir.ok_or(from_paths::Error::MissingGitDir)?)); let mut pattern_path = { let cow = Cow::Borrowed(condition_path.as_bytes()); @@ -162,7 +163,7 @@ fn gitdir_matches( .ok_or(from_paths::Error::MissingConfigPath)? .parent() .expect("config path can never be /"); - let mut joined_path = git_path::to_unix_separators(git_path::into_bstr(parent_dir)).into_owned(); + let mut joined_path = git_path::to_unix_separators_on_windows(git_path::into_bstr(parent_dir)).into_owned(); joined_path.push(b'/'); joined_path.extend_from_slice(relative_pattern_path); pattern_path = joined_path; @@ -179,7 +180,7 @@ fn gitdir_matches( pattern_path.push_str("**"); } - pattern_path = git_path::to_unix_separators(pattern_path).into_owned(); + pattern_path = git_path::to_unix_separators_on_windows(pattern_path).into_owned(); let match_mode = git_glob::wildmatch::Mode::NO_MATCH_SLASH_LITERAL | wildmatch_mode; let is_match = git_glob::wildmatch(pattern_path.as_bstr(), git_dir.as_bstr(), match_mode); @@ -191,7 +192,7 @@ fn gitdir_matches( git_path::from_byte_slice(&git_dir), target_config_path.ok_or(from_paths::Error::MissingConfigPath)?, )?; - let expanded_git_dir = git_path::to_unix_separators(git_path::into_bstr(expanded_git_dir)); + let expanded_git_dir = git_path::to_unix_separators_on_windows(git_path::into_bstr(expanded_git_dir)); Ok(git_glob::wildmatch( pattern_path.as_bstr(), expanded_git_dir.as_bstr(), diff --git a/git-config/tests/file/from_paths/includes/conditional/mod.rs b/git-config/tests/file/from_paths/includes/conditional/mod.rs index ed68f6a9efc..658fd781afd 100644 --- a/git-config/tests/file/from_paths/includes/conditional/mod.rs +++ b/git-config/tests/file/from_paths/includes/conditional/mod.rs @@ -278,7 +278,8 @@ fn various_gitdir() { ); } - { + // TODO: figure out what this is testing, I have a feeling double-slash git dirs aren't relevant, it's the condition that matters + if false { let dir = Path::new("/c//d/.git"); let config = File::from_paths(Some(&config_path), options_with_git_dir(dir)).unwrap(); assert_eq!( @@ -307,7 +308,8 @@ fn various_gitdir() { ); } - { + // TODO: see what git thinks about this on unix + if cfg!(windows) { let dir = PathBuf::from("C:\\w\\.git".to_string()); let config = File::from_paths(Some(&config_path), options_with_git_dir(&dir)).unwrap(); assert_eq!( From 311d4b447daf8d4364670382a20901468748d34d Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 4 Jul 2022 11:02:25 +0800 Subject: [PATCH 004/366] feat!: change mostily internal uses of [u8] to BString/BStr --- git-config/src/file/access/comfort.rs | 5 +- .../src/file/access/low_level/mutating.rs | 42 ++-- .../src/file/access/low_level/read_only.rs | 13 +- git-config/src/file/access/raw.rs | 80 +++---- git-config/src/file/from_env.rs | 4 +- git-config/src/file/impls.rs | 15 +- git-config/src/file/mod.rs | 6 +- git-config/src/file/resolve_includes.rs | 42 ++-- git-config/src/file/resolved.rs | 119 ----------- git-config/src/file/section.rs | 103 +++++---- git-config/src/file/try_from_str_tests.rs | 12 +- git-config/src/file/utils.rs | 8 +- git-config/src/file/value.rs | 51 ++--- git-config/src/fs.rs | 9 +- git-config/src/parser.rs | 199 ++++++++---------- git-config/src/test_util.rs | 38 ++-- git-config/src/types.rs | 10 +- git-config/src/values.rs | 197 ++++++++--------- .../file/access/raw/mutable_multi_value.rs | 2 +- .../tests/file/access/raw/mutable_value.rs | 4 +- .../tests/file/access/raw/raw_multi_value.rs | 16 +- git-config/tests/file/access/read_only.rs | 23 +- git-config/tests/parser/mod.rs | 14 +- git-config/tests/values/boolean.rs | 19 +- git-config/tests/values/mod.rs | 8 +- git-config/tests/values/normalize.rs | 29 +-- git-config/tests/values/path.rs | 14 +- git-repository/src/config.rs | 18 +- git-repository/src/lib.rs | 5 +- 29 files changed, 464 insertions(+), 641 deletions(-) delete mode 100644 git-config/src/file/resolved.rs diff --git a/git-config/src/file/access/comfort.rs b/git-config/src/file/access/comfort.rs index a15d17bbb78..eef6efb8b1a 100644 --- a/git-config/src/file/access/comfort.rs +++ b/git-config/src/file/access/comfort.rs @@ -2,6 +2,7 @@ use std::{borrow::Cow, convert::TryFrom}; use bstr::BStr; +use crate::values::normalize_cow; use crate::{value, values, File}; /// Comfortable API for accessing values @@ -12,7 +13,7 @@ impl<'a> File<'a> { pub fn string(&'a self, section_name: &str, subsection_name: Option<&str>, key: &str) -> Option> { self.raw_value(section_name, subsection_name, key) .ok() - .map(|v| values::String::from(v).value) + .map(|v| normalize_cow(v)) } /// Like [`value()`][File::value()], but returning an `Option` if the path wasn't found. @@ -60,7 +61,7 @@ impl<'a> File<'a> { pub fn strings(&self, section_name: &str, subsection_name: Option<&str>, key: &str) -> Option>> { self.raw_multi_value(section_name, subsection_name, key) .ok() - .map(|values| values.into_iter().map(|v| values::String::from(v).value).collect()) + .map(|values| values.into_iter().map(|v| normalize_cow(v)).collect()) } /// Similar to [`multi_value(…)`][File::multi_value()] but returning integers if at least one of them was found diff --git a/git-config/src/file/access/low_level/mutating.rs b/git-config/src/file/access/low_level/mutating.rs index 9efcc5a3a34..5ef9cee2bc6 100644 --- a/git-config/src/file/access/low_level/mutating.rs +++ b/git-config/src/file/access/low_level/mutating.rs @@ -1,3 +1,4 @@ +use bstr::BStr; use std::borrow::Cow; use crate::{ @@ -50,9 +51,10 @@ impl<'a> File<'a> { /// ``` /// # use git_config::File; /// # use std::convert::TryFrom; + /// # use bstr::ByteSlice; /// let mut git_config = git_config::File::new(); /// let mut section = git_config.new_section("hello", Some("world".into())); - /// section.push("a".into(), "b".as_bytes().into()); + /// section.push("a".into(), b"b".as_bstr().into()); /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n a=b\n"); /// let _section = git_config.new_section("core", None); /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n a=b\n[core]\n"); @@ -128,26 +130,15 @@ impl<'a> File<'a> { subsection_name: impl Into>>, section: SectionBody<'a>, ) -> MutableSection<'_, 'a> { - let subsection_name = subsection_name.into(); - if subsection_name.is_some() { - self.push_section_internal( - ParsedSectionHeader { - name: SectionHeaderName(section_name.into()), - separator: Some(" ".into()), - subsection_name, - }, - section, - ) - } else { - self.push_section_internal( - ParsedSectionHeader { - name: SectionHeaderName(section_name.into()), - separator: None, - subsection_name: None, - }, - section, - ) - } + let subsection_name = subsection_name.into().map(into_cow_bstr); + self.push_section_internal( + ParsedSectionHeader { + name: SectionHeaderName(into_cow_bstr(section_name.into())), + separator: subsection_name.is_some().then(|| Cow::Borrowed(" ".into())), + subsection_name, + }, + section, + ) } /// Renames a section, modifying the last matching section. @@ -171,8 +162,15 @@ impl<'a> File<'a> { .get_mut(id) .expect("sections does not have section id from section ids"); header.name = new_section_name.into(); - header.subsection_name = new_subsection_name.into(); + header.subsection_name = new_subsection_name.into().map(into_cow_bstr); Ok(()) } } + +fn into_cow_bstr(c: Cow<'_, str>) -> Cow<'_, BStr> { + match c { + Cow::Borrowed(s) => Cow::Borrowed(s.into()), + Cow::Owned(s) => Cow::Owned(s.into()), + } +} diff --git a/git-config/src/file/access/low_level/read_only.rs b/git-config/src/file/access/low_level/read_only.rs index 582444ceae0..5c35decab4a 100644 --- a/git-config/src/file/access/low_level/read_only.rs +++ b/git-config/src/file/access/low_level/read_only.rs @@ -1,3 +1,4 @@ +use bstr::BStr; use std::{borrow::Cow, convert::TryFrom}; use crate::{file::SectionBody, lookup, parser::ParsedSectionHeader, File}; @@ -42,7 +43,7 @@ impl<'a> File<'a> { /// /// [`values`]: crate::values /// [`TryFrom`]: std::convert::TryFrom - pub fn value>>( + pub fn value>>( &'a self, section_name: &str, subsection_name: Option<&str>, @@ -52,7 +53,7 @@ impl<'a> File<'a> { } /// Like [`value()`][File::value()], but returning an `Option` if the value wasn't found. - pub fn try_value>>( + pub fn try_value>>( &'a self, section_name: &str, subsection_name: Option<&str>, @@ -92,14 +93,14 @@ impl<'a> File<'a> { /// assert_eq!( /// a_value, /// vec![ - /// Boolean::True(TrueVariant::Explicit(Cow::Borrowed("true"))), + /// Boolean::True(TrueVariant::Explicit(Cow::Borrowed("true".into()))), /// Boolean::True(TrueVariant::Implicit), - /// Boolean::False(Cow::Borrowed("false")), + /// Boolean::False(Cow::Borrowed("false".into())), /// ] /// ); /// // ... or explicitly declare the type to avoid the turbofish /// let c_value: Vec = git_config.multi_value("core", None, "c")?; - /// assert_eq!(c_value, vec![Bytes { value: Cow::Borrowed(b"g") }]); + /// assert_eq!(c_value, vec![Bytes { value: Cow::Borrowed("g".into()) }]); /// # Ok::<(), Box>(()) /// ``` /// @@ -111,7 +112,7 @@ impl<'a> File<'a> { /// /// [`values`]: crate::values /// [`TryFrom`]: std::convert::TryFrom - pub fn multi_value<'lookup, T: TryFrom>>( + pub fn multi_value<'lookup, T: TryFrom>>( &'a self, section_name: &'lookup str, subsection_name: Option<&'lookup str>, diff --git a/git-config/src/file/access/raw.rs b/git-config/src/file/access/raw.rs index 2324878567e..a99ab1433ce 100644 --- a/git-config/src/file/access/raw.rs +++ b/git-config/src/file/access/raw.rs @@ -1,3 +1,4 @@ +use bstr::{BStr, BString}; use std::{borrow::Cow, collections::HashMap}; use crate::{ @@ -24,14 +25,14 @@ impl<'a> File<'a> { /// section and subsection, or if the section and subsection do not exist. pub fn raw_value<'lookup>( &self, - section_name: &'lookup str, + section_name: &'lookup str, // TODO: consider making this BStr, while keeping higher-level APIs as 'str' subsection_name: Option<&'lookup str>, key: &'lookup str, - ) -> Result, lookup::existing::Error> { + ) -> Result, lookup::existing::Error> { // Note: cannot wrap around the raw_multi_value method because we need // to guarantee that the highest section id is used (so that we follow // the "last one wins" resolution strategy by `git-config`). - let key = Key(key.into()); + let key = Key(Cow::::Borrowed(key.into())); for section_id in self .section_ids_by_name_and_subname(section_name, subsection_name)? .iter() @@ -43,7 +44,7 @@ impl<'a> File<'a> { .expect("sections does not have section id from section ids") .value(&key) { - return Ok(v.to_vec().into()); + return Ok(v.clone()); } } @@ -67,7 +68,7 @@ impl<'a> File<'a> { key: &'lookup str, ) -> Result, lookup::existing::Error> { let section_ids = self.section_ids_by_name_and_subname(section_name, subsection_name)?; - let key = Key(key.into()); + let key = Key(Cow::::Borrowed(key.into())); for section_id in section_ids.iter().rev() { let mut size = Size(0); @@ -163,16 +164,16 @@ impl<'a> File<'a> { section_name: &str, subsection_name: Option<&str>, key: &str, - ) -> Result>, lookup::existing::Error> { + ) -> Result>, lookup::existing::Error> { let mut values = vec![]; for section_id in self.section_ids_by_name_and_subname(section_name, subsection_name)? { values.extend( self.sections .get(§ion_id) .expect("sections does not have section id from section ids") - .values(&Key(key.into())) + .values(&Key(Cow::::Borrowed(key.into()))) .iter() - .map(|v| Cow::Owned(v.to_vec())), + .map(|v| v.clone()), ); } @@ -204,13 +205,14 @@ impl<'a> File<'a> { /// # use git_config::File; /// # use std::borrow::Cow; /// # use std::convert::TryFrom; + /// # use bstr::BStr; /// # let mut git_config = git_config::File::try_from("[core]a=b\n[core]\na=c\na=d").unwrap(); /// assert_eq!( /// git_config.raw_multi_value("core", None, "a")?, /// vec![ - /// Cow::Borrowed(b"b"), - /// Cow::Borrowed(b"c"), - /// Cow::Borrowed(b"d") + /// Cow::::Borrowed("b".into()), + /// Cow::::Borrowed("c".into()), + /// Cow::::Borrowed("d".into()) /// ] /// ); /// @@ -219,9 +221,9 @@ impl<'a> File<'a> { /// assert_eq!( /// git_config.raw_multi_value("core", None, "a")?, /// vec![ - /// Cow::Borrowed(b"g"), - /// Cow::Borrowed(b"g"), - /// Cow::Borrowed(b"g") + /// Cow::::Borrowed("g".into()), + /// Cow::::Borrowed("g".into()), + /// Cow::::Borrowed("g".into()) /// ], /// ); /// # Ok::<(), git_config::lookup::existing::Error>(()) @@ -245,7 +247,7 @@ impl<'a> File<'a> { key: &'lookup str, ) -> Result, lookup::existing::Error> { let section_ids = self.section_ids_by_name_and_subname(section_name, subsection_name)?; - let key = Key(key.into()); + let key = Key(Cow::::Borrowed(key.into())); let mut offsets = HashMap::new(); let mut entries = vec![]; @@ -310,10 +312,11 @@ impl<'a> File<'a> { /// ``` /// # use git_config::File; /// # use std::borrow::Cow; + /// # use bstr::BStr; /// # use std::convert::TryFrom; /// # let mut git_config = git_config::File::try_from("[core]a=b\n[core]\na=c\na=d").unwrap(); - /// git_config.set_raw_value("core", None, "a", vec![b'e'])?; - /// assert_eq!(git_config.raw_value("core", None, "a")?, Cow::Borrowed(b"e")); + /// git_config.set_raw_value("core", None, "a", "e".into())?; + /// assert_eq!(git_config.raw_value("core", None, "a")?, Cow::::Borrowed("e".into())); /// # Ok::<(), Box>(()) /// ``` /// @@ -325,7 +328,7 @@ impl<'a> File<'a> { section_name: &'lookup str, subsection_name: Option<&'lookup str>, key: &'lookup str, - new_value: Vec, + new_value: BString, ) -> Result<(), lookup::existing::Error> { self.raw_value_mut(section_name, subsection_name, key) .map(|mut entry| entry.set_bytes(new_value)) @@ -363,17 +366,18 @@ impl<'a> File<'a> { /// # use git_config::File; /// # use std::borrow::Cow; /// # use std::convert::TryFrom; + /// # use bstr::BStr; /// # let mut git_config = git_config::File::try_from("[core]a=b\n[core]\na=c\na=d").unwrap(); - /// let new_values: Vec> = vec![ - /// Cow::Borrowed(b"x"), - /// Cow::Borrowed(b"y"), - /// Cow::Borrowed(b"z"), + /// let new_values = vec![ + /// Cow::::Borrowed("x".into()), + /// Cow::::Borrowed("y".into()), + /// Cow::::Borrowed("z".into()), /// ]; /// git_config.set_raw_multi_value("core", None, "a", new_values.into_iter())?; /// let fetched_config = git_config.raw_multi_value("core", None, "a")?; - /// assert!(fetched_config.contains(&Cow::Borrowed(b"x"))); - /// assert!(fetched_config.contains(&Cow::Borrowed(b"y"))); - /// assert!(fetched_config.contains(&Cow::Borrowed(b"z"))); + /// assert!(fetched_config.contains(&Cow::::Borrowed("x".into()))); + /// assert!(fetched_config.contains(&Cow::::Borrowed("y".into()))); + /// assert!(fetched_config.contains(&Cow::::Borrowed("z".into()))); /// # Ok::<(), git_config::lookup::existing::Error>(()) /// ``` /// @@ -383,15 +387,16 @@ impl<'a> File<'a> { /// # use git_config::File; /// # use std::borrow::Cow; /// # use std::convert::TryFrom; + /// # use bstr::BStr; /// # let mut git_config = git_config::File::try_from("[core]a=b\n[core]\na=c\na=d").unwrap(); - /// let new_values: Vec> = vec![ - /// Cow::Borrowed(b"x"), - /// Cow::Borrowed(b"y"), + /// let new_values = vec![ + /// Cow::::Borrowed("x".into()), + /// Cow::::Borrowed("y".into()), /// ]; /// git_config.set_raw_multi_value("core", None, "a", new_values.into_iter())?; /// let fetched_config = git_config.raw_multi_value("core", None, "a")?; - /// assert!(fetched_config.contains(&Cow::Borrowed(b"x"))); - /// assert!(fetched_config.contains(&Cow::Borrowed(b"y"))); + /// assert!(fetched_config.contains(&Cow::::Borrowed("x".into()))); + /// assert!(fetched_config.contains(&Cow::::Borrowed("y".into()))); /// # Ok::<(), git_config::lookup::existing::Error>(()) /// ``` /// @@ -401,15 +406,16 @@ impl<'a> File<'a> { /// # use git_config::File; /// # use std::borrow::Cow; /// # use std::convert::TryFrom; + /// # use bstr::BStr; /// # let mut git_config = git_config::File::try_from("[core]a=b\n[core]\na=c\na=d").unwrap(); - /// let new_values: Vec> = vec![ - /// Cow::Borrowed(b"x"), - /// Cow::Borrowed(b"y"), - /// Cow::Borrowed(b"z"), - /// Cow::Borrowed(b"discarded"), + /// let new_values = vec![ + /// Cow::::Borrowed("x".into()), + /// Cow::::Borrowed("y".into()), + /// Cow::::Borrowed("z".into()), + /// Cow::::Borrowed("discarded".into()), /// ]; /// git_config.set_raw_multi_value("core", None, "a", new_values.into_iter())?; - /// assert!(!git_config.raw_multi_value("core", None, "a")?.contains(&Cow::Borrowed(b"discarded"))); + /// assert!(!git_config.raw_multi_value("core", None, "a")?.contains(&Cow::::Borrowed("discarded".into()))); /// # Ok::<(), git_config::lookup::existing::Error>(()) /// ``` /// @@ -423,7 +429,7 @@ impl<'a> File<'a> { section_name: &'lookup str, subsection_name: Option<&'lookup str>, key: &'lookup str, - new_values: impl Iterator>, + new_values: impl Iterator>, ) -> Result<(), lookup::existing::Error> { self.raw_multi_value_mut(section_name, subsection_name, key) .map(|mut v| v.set_values(new_values)) diff --git a/git-config/src/file/from_env.rs b/git-config/src/file/from_env.rs index af166271c6a..d3fd539253a 100644 --- a/git-config/src/file/from_env.rs +++ b/git-config/src/file/from_env.rs @@ -1,7 +1,9 @@ +use bstr::BString; use std::{borrow::Cow, path::PathBuf}; use crate::{ file::{from_paths, resolve_includes}, + parser, values::path::interpolate, File, }; @@ -113,7 +115,7 @@ impl<'a> File<'a> { }; section.push( - Cow::::Owned(key.to_string()).into(), + parser::Key(Cow::Owned(BString::from(key))), Cow::Owned(git_path::into_bstr(PathBuf::from(value)).into_owned().into()), ); } else { diff --git a/git-config/src/file/impls.rs b/git-config/src/file/impls.rs index 6f5184821c9..9655a9cffaa 100644 --- a/git-config/src/file/impls.rs +++ b/git-config/src/file/impls.rs @@ -1,3 +1,4 @@ +use bstr::{BString, ByteVec}; use std::{convert::TryFrom, fmt::Display}; use crate::{ @@ -86,27 +87,27 @@ impl<'a> From> for File<'a> { } } -impl From> for Vec { +impl From> for BString { fn from(c: File<'_>) -> Self { c.into() } } -impl From<&File<'_>> for Vec { +impl From<&File<'_>> for BString { fn from(config: &File<'_>) -> Self { - let mut value = Self::new(); + let mut value = BString::default(); for events in config.frontmatter_events.as_ref() { - value.extend(events.to_vec()); + value.push_str(events.to_bstring()); } for section_id in &config.section_order { - value.extend( + value.push_str( config .section_headers .get(section_id) .expect("section_header does not contain section id from section_order") - .to_vec(), + .to_bstring(), ); for event in config @@ -115,7 +116,7 @@ impl From<&File<'_>> for Vec { .expect("sections does not contain section id from section_order") .as_ref() { - value.extend(event.to_vec()); + value.push_str(event.to_bstring()); } } diff --git a/git-config/src/file/mod.rs b/git-config/src/file/mod.rs index ed3fe4f04e7..4bcc7773ae7 100644 --- a/git-config/src/file/mod.rs +++ b/git-config/src/file/mod.rs @@ -1,13 +1,11 @@ //! This module provides a high level wrapper around a single `git-config` file. +use bstr::BStr; use std::{ borrow::Cow, collections::HashMap, ops::{Add, AddAssign}, }; -mod resolved; -pub use resolved::*; - mod section; pub use section::*; @@ -59,7 +57,7 @@ pub(crate) struct SectionId(pub(crate) usize); #[derive(PartialEq, Eq, Clone, Debug)] pub(crate) enum LookupTreeNode<'a> { Terminal(Vec), - NonTerminal(HashMap, Vec>), + NonTerminal(HashMap, Vec>), } pub mod from_env; diff --git a/git-config/src/file/resolve_includes.rs b/git-config/src/file/resolve_includes.rs index 04cf2f04198..1a95e4b5614 100644 --- a/git-config/src/file/resolve_includes.rs +++ b/git-config/src/file/resolve_includes.rs @@ -3,7 +3,7 @@ use std::{ path::{Path, PathBuf}, }; -use bstr::{BString, ByteSlice, ByteVec}; +use bstr::{BStr, BString, ByteSlice, ByteVec}; use git_ref::Category; use crate::file::from_paths::Options; @@ -57,11 +57,11 @@ fn resolve_includes_recursive( let mut include_paths = Vec::new(); for (id, _) in incl_section_ids { if let Some(header) = target_config.section_headers.get(&id) { - if header.name.0 == "include" && header.subsection_name.is_none() { + if header.name.0.as_ref() == "include" && header.subsection_name.is_none() { extract_include_path(target_config, &mut include_paths, id) - } else if header.name.0 == "includeIf" { + } else if header.name.0.as_ref() == "includeIf" { if let Some(condition) = &header.subsection_name { - if include_condition_match(condition, target_config_path, options)? { + if include_condition_match(condition.as_ref(), target_config_path, options)? { extract_include_path(target_config, &mut include_paths, id) } } @@ -94,42 +94,48 @@ fn extract_include_path<'a>(target_config: &mut File<'a>, include_paths: &mut Ve } fn include_condition_match( - condition: &str, + condition: &BStr, target_config_path: Option<&Path>, options: from_paths::Options<'_>, ) -> Result { - let (prefix, condition) = match condition.split_once(':') { - Some(t) => t, - None => return Ok(false), + let mut tokens = condition.splitn(2, |b| *b == b':'); + let (prefix, condition) = match (tokens.next(), tokens.next()) { + (Some(a), Some(b)) => (a, b), + _ => return Ok(false), }; + let condition = condition.as_bstr(); match prefix { - "gitdir" => gitdir_matches( + b"gitdir" => gitdir_matches( condition, target_config_path, options, git_glob::wildmatch::Mode::empty(), ), - "gitdir/i" => gitdir_matches( + b"gitdir/i" => gitdir_matches( condition, target_config_path, options, git_glob::wildmatch::Mode::IGNORE_CASE, ), - "onbranch" => Ok(onbranch_matches(condition, options).is_some()), + b"onbranch" => Ok(onbranch_matches(condition, options).is_some()), _ => Ok(false), } } -fn onbranch_matches(condition: &str, options: Options<'_>) -> Option<()> { +fn onbranch_matches(condition: &BStr, options: Options<'_>) -> Option<()> { let branch_name = options.branch_name?; let (_, branch_name) = branch_name .category_and_short_name() .filter(|(cat, _)| *cat == Category::LocalBranch)?; - let mut condition = Cow::Borrowed(condition); - if condition.ends_with('/') { - condition = Cow::Owned(format!("{}**", condition)); - } + let condition = if condition.ends_with(b"/") { + let mut condition: BString = condition.into(); + condition.push_str("**"); + Cow::Owned(condition) + } else { + condition.into() + }; + git_glob::wildmatch( condition.as_ref().into(), branch_name, @@ -139,7 +145,7 @@ fn onbranch_matches(condition: &str, options: Options<'_>) -> Option<()> { } fn gitdir_matches( - condition_path: &str, + condition_path: &BStr, target_config_path: Option<&Path>, from_paths::Options { git_install_dir, @@ -153,7 +159,7 @@ fn gitdir_matches( git_path::to_unix_separators_on_windows(git_path::into_bstr(git_dir.ok_or(from_paths::Error::MissingGitDir)?)); let mut pattern_path = { - let cow = Cow::Borrowed(condition_path.as_bytes()); + let cow = Cow::Borrowed(condition_path); let path = values::Path::from(cow).interpolate(git_install_dir, home_dir)?; git_path::into_bstr(path).into_owned() }; diff --git a/git-config/src/file/resolved.rs b/git-config/src/file/resolved.rs deleted file mode 100644 index c9a9859c2db..00000000000 --- a/git-config/src/file/resolved.rs +++ /dev/null @@ -1,119 +0,0 @@ -use std::{borrow::Cow, collections::HashMap, convert::TryFrom, path::Path}; - -use super::SectionId; -use crate::{ - file::LookupTreeNode, - parser, - parser::{Key, SectionHeaderName}, - File, -}; - -enum ResolvedTreeNode<'event> { - Terminal(HashMap, Cow<'event, [u8]>>), - NonTerminal(HashMap, HashMap, Cow<'event, [u8]>>>), -} - -/// A `git-config` that resolves entries on creation, providing a -/// [`HashMap`]-like interface for users. -/// -/// This does not provide the same guarantees as [`File`]; namely, it does -/// not remember comments nor whitespace. Additionally, values are normalized -/// upon creation, so it's not possible to retrieve the original value. -#[allow(clippy::module_name_repetitions)] -#[derive(PartialEq, Eq, Debug)] -pub struct ResolvedGitConfig<'data>(HashMap, HashMap, Cow<'data, [u8]>>>); - -type SectionLookupTuple<'data> = (SectionHeaderName<'data>, Option>); - -impl ResolvedGitConfig<'static> { - /// Opens a `git-config` file from the given path. - /// - /// # Errors - /// - /// This returns an error if an IO error occurs, or if the file is not a - /// valid `git-config` file. - pub fn open>(path: P) -> Result> { - File::at(path.as_ref()).map(Self::from) - } -} - -impl<'data> ResolvedGitConfig<'data> { - /// Resolves a given [`File`]. - #[must_use] - pub fn from_config(config: File<'data>) -> Self { - // Map a into >>. - let sections: HashMap<_, _> = config - .sections - .into_iter() - .map(|(key, section_body)| { - let mut mapping: HashMap, Cow<'_, [u8]>> = HashMap::new(); - for (key, value) in section_body { - mapping.insert(key, value); - } - (key, mapping) - }) - .collect(); - - let section_name_to_node = config.section_lookup_tree.into_iter().map(|(section_name, vec)| { - let node = vec.into_iter().map(|node| match node { - LookupTreeNode::Terminal(items) => ResolvedTreeNode::Terminal(resolve_sections(§ions, items)), - LookupTreeNode::NonTerminal(mapping) => { - let items = mapping - .into_iter() - .map(|(key, items)| (key, resolve_sections(§ions, items))) - .collect(); - ResolvedTreeNode::NonTerminal(items) - } - }); - - (section_name, node) - }); - - let mut resolved: HashMap<_, HashMap, Cow<'_, [u8]>>> = HashMap::new(); - - for (section_name, node) in section_name_to_node { - for node in node { - match node { - ResolvedTreeNode::Terminal(mapping) => { - let entry = resolved.entry((section_name.clone(), None)).or_default(); - entry.extend(mapping); - } - ResolvedTreeNode::NonTerminal(mapping) => { - for (subsection, mapping) in mapping { - let entry = resolved.entry((section_name.clone(), Some(subsection))).or_default(); - entry.extend(mapping); - } - } - }; - } - } - - Self(resolved) - } -} - -fn resolve_sections<'key, 'data>( - mapping: &HashMap, Cow<'data, [u8]>>>, - sections: Vec, -) -> HashMap, Cow<'data, [u8]>> { - sections - .into_iter() - .flat_map(|section_id| mapping.get(§ion_id).expect("GitConfig invariant failed").iter()) - // Copy the Cow struct, not the underlying slice. - .map(|(key, value)| (Key::clone(key), Cow::clone(value))) - .collect() -} - -impl TryFrom<&Path> for ResolvedGitConfig<'static> { - type Error = parser::ParserOrIoError<'static>; - - fn try_from(path: &Path) -> Result { - Self::open(path) - } -} - -impl<'data> From> for ResolvedGitConfig<'data> { - fn from(config: File<'data>) -> Self { - Self::from_config(config) - } -} diff --git a/git-config/src/file/section.rs b/git-config/src/file/section.rs index d2cfc6b38ae..e5042571bb1 100644 --- a/git-config/src/file/section.rs +++ b/git-config/src/file/section.rs @@ -1,5 +1,6 @@ +use bstr::{BStr, BString, ByteVec}; use std::{ - borrow::{Borrow, Cow}, + borrow::Cow, collections::VecDeque, convert::TryFrom, iter::FusedIterator, @@ -10,7 +11,7 @@ use crate::{ file::Index, lookup, parser::{Event, Key}, - values::{normalize_cow, normalize_vec}, + values::{normalize_bstring, normalize_cow}, }; /// A opaque type that represents a mutable reference to a section. @@ -24,24 +25,26 @@ pub struct MutableSection<'borrow, 'event> { impl<'borrow, 'event> MutableSection<'borrow, 'event> { /// Adds an entry to the end of this section. - pub fn push(&mut self, key: Key<'event>, value: Cow<'event, [u8]>) { + pub fn push(&mut self, key: Key<'event>, value: Cow<'event, BStr>) { if self.whitespace > 0 { - self.section - .0 - .push(Event::Whitespace(" ".repeat(self.whitespace).into())); + self.section.0.push(Event::Whitespace({ + let mut s = BString::default(); + s.extend(std::iter::repeat(b' ').take(self.whitespace)); + s.into() + })); } self.section.0.push(Event::Key(key)); self.section.0.push(Event::KeyValueSeparator); self.section.0.push(Event::Value(value)); if self.implicit_newline { - self.section.0.push(Event::Newline("\n".into())); + self.section.0.push(Event::Newline(BString::from("\n").into())); } } /// Removes all events until a key value pair is removed. This will also /// remove the whitespace preceding the key value pair, if any is found. - pub fn pop(&mut self) -> Option<(Key<'_>, Cow<'event, [u8]>)> { + pub fn pop(&mut self) -> Option<(Key<'_>, Cow<'event, BStr>)> { let mut values = vec![]; // events are popped in reverse order while let Some(e) = self.section.0.pop() { @@ -59,13 +62,13 @@ impl<'borrow, 'event> MutableSection<'borrow, 'event> { return Some(( k, - normalize_vec( - values - .into_iter() - .rev() - .flat_map(|v: Cow<'_, [u8]>| v.to_vec()) - .collect(), - ), + normalize_bstring({ + let mut s = BString::default(); + for value in values.into_iter().rev() { + s.push_str(value.as_ref()); + } + s + }), )); } Event::Value(v) | Event::ValueNotDone(v) | Event::ValueDone(v) => values.push(v), @@ -78,7 +81,7 @@ impl<'borrow, 'event> MutableSection<'borrow, 'event> { /// Sets the last key value pair if it exists, or adds the new value. /// Returns the previous value if it replaced a value, or None if it adds /// the value. - pub fn set(&mut self, key: Key<'event>, value: Cow<'event, [u8]>) -> Option> { + pub fn set(&mut self, key: Key<'event>, value: Cow<'event, BStr>) -> Option> { let range = self.value_range_by_key(&key); if range.is_empty() { self.push(key, value); @@ -91,7 +94,7 @@ impl<'borrow, 'event> MutableSection<'borrow, 'event> { } /// Removes the latest value by key and returns it, if it exists. - pub fn remove(&mut self, key: &Key<'event>) -> Option> { + pub fn remove(&mut self, key: &Key<'event>) -> Option> { let range = self.value_range_by_key(key); if range.is_empty() { return None; @@ -101,17 +104,17 @@ impl<'borrow, 'event> MutableSection<'borrow, 'event> { /// Performs the removal, assuming the range is valid. This is used to /// avoid duplicating searching for the range in [`Self::set`]. - fn remove_internal(&mut self, range: Range) -> Cow<'event, [u8]> { + fn remove_internal(&mut self, range: Range) -> Cow<'event, BStr> { self.section .0 .drain(range) - .fold(Cow::<[u8]>::Owned(vec![]), |acc, e| match e { + .fold(Cow::Owned(BString::default()), |acc, e| match e { Event::Value(v) | Event::ValueNotDone(v) | Event::ValueDone(v) => { // This is fine because we start out with an owned // variant, so we never actually clone the // accumulator. let mut acc = acc.into_owned(); - acc.extend(&*v); + acc.extend(&**v); Cow::Owned(acc) } _ => acc, @@ -121,7 +124,7 @@ impl<'borrow, 'event> MutableSection<'borrow, 'event> { /// Adds a new line event. Note that you don't need to call this unless /// you've disabled implicit newlines. pub fn push_newline(&mut self) { - self.section.0.push(Event::Newline("\n".into())); + self.section.0.push(Event::Newline(Cow::Borrowed("\n".into()))); } /// Enables or disables automatically adding newline events after adding @@ -160,7 +163,7 @@ impl<'borrow, 'event> MutableSection<'borrow, 'event> { key: &Key<'key>, start: Index, end: Index, - ) -> Result, lookup::existing::Error> { + ) -> Result, lookup::existing::Error> { let mut found_key = false; let mut latest_value = None; let mut partial_value = None; @@ -177,11 +180,11 @@ impl<'borrow, 'event> MutableSection<'borrow, 'event> { } Event::ValueNotDone(v) if found_key => { latest_value = None; - partial_value = Some((*v).to_vec()); + partial_value = Some(v.as_ref().to_owned()); } Event::ValueDone(v) if found_key => { found_key = false; - partial_value.as_mut().unwrap().extend(&**v); + partial_value.as_mut().unwrap().push_str(v.as_ref()); } _ => (), } @@ -189,7 +192,7 @@ impl<'borrow, 'event> MutableSection<'borrow, 'event> { latest_value .map(normalize_cow) - .or_else(|| partial_value.map(normalize_vec)) + .or_else(|| partial_value.map(normalize_bstring)) .ok_or(lookup::existing::Error::KeyMissing) } @@ -197,7 +200,7 @@ impl<'borrow, 'event> MutableSection<'borrow, 'event> { self.section.0.drain(start.0..=end.0); } - pub(crate) fn set_internal(&mut self, index: Index, key: Key<'event>, value: Vec) { + pub(crate) fn set_internal(&mut self, index: Index, key: Key<'event>, value: BString) { self.section.0.insert(index.0, Event::Value(Cow::Owned(value))); self.section.0.insert(index.0, Event::KeyValueSeparator); self.section.0.insert(index.0, Event::Key(key)); @@ -238,7 +241,7 @@ impl<'event> SectionBody<'event> { // function. #[allow(clippy::missing_panics_doc)] #[must_use] - pub fn value(&self, key: &Key<'_>) -> Option> { + pub fn value(&self, key: &Key<'_>) -> Option> { let range = self.value_range_by_key(key); if range.is_empty() { return None; @@ -253,20 +256,14 @@ impl<'event> SectionBody<'event> { }); } - Some(normalize_cow(self.0[range].iter().fold( - Cow::<[u8]>::Owned(vec![]), - |acc, e| match e { - Event::Value(v) | Event::ValueNotDone(v) | Event::ValueDone(v) => { - // This is fine because we start out with an owned - // variant, so we never actually clone the - // accumulator. - let mut acc = acc.into_owned(); - acc.extend(&**v); - Cow::Owned(acc) - } - _ => acc, - }, - ))) + normalize_bstring(self.0[range].iter().fold(BString::default(), |mut acc, e| match e { + Event::Value(v) | Event::ValueNotDone(v) | Event::ValueDone(v) => { + acc.push_str(v.as_ref()); + acc + } + _ => acc, + })) + .into() } /// Retrieves the last matching value in a section with the given key, and @@ -275,7 +272,7 @@ impl<'event> SectionBody<'event> { /// # Errors /// /// Returns an error if the key was not found, or if the conversion failed. - pub fn value_as>>(&self, key: &Key<'_>) -> Result> { + pub fn value_as>>(&self, key: &Key<'_>) -> Result> { T::try_from(self.value(key).ok_or(lookup::existing::Error::KeyMissing)?) .map_err(lookup::Error::FailedConversion) } @@ -283,7 +280,7 @@ impl<'event> SectionBody<'event> { /// Retrieves all values that have the provided key name. This may return /// an empty vec, which implies there were no values with the provided key. #[must_use] - pub fn values(&self, key: &Key<'_>) -> Vec> { + pub fn values(&self, key: &Key<'_>) -> Vec> { let mut values = vec![]; let mut found_key = false; let mut partial_value = None; @@ -300,15 +297,15 @@ impl<'event> SectionBody<'event> { partial_value = None; } Event::ValueNotDone(v) if found_key => { - partial_value = Some((*v).to_vec()); + partial_value = Some(v.as_ref().to_owned()); } Event::ValueDone(v) if found_key => { found_key = false; let mut value = partial_value .take() .expect("ValueDone event called before ValueNotDone"); - value.extend(&**v); - values.push(normalize_cow(Cow::Owned(value))); + value.push_str(v.as_ref()); + values.push(normalize_bstring(value)); } _ => (), } @@ -323,7 +320,7 @@ impl<'event> SectionBody<'event> { /// # Errors /// /// Returns an error if the conversion failed. - pub fn values_as>>(&self, key: &Key<'_>) -> Result, lookup::Error> { + pub fn values_as>>(&self, key: &Key<'_>) -> Result, lookup::Error> { self.values(key) .into_iter() .map(T::try_from) @@ -396,7 +393,7 @@ impl<'event> SectionBody<'event> { } impl<'event> IntoIterator for SectionBody<'event> { - type Item = (Key<'event>, Cow<'event, [u8]>); + type Item = (Key<'event>, Cow<'event, BStr>); type IntoIter = SectionBodyIter<'event>; @@ -410,11 +407,11 @@ impl<'event> IntoIterator for SectionBody<'event> { pub struct SectionBodyIter<'event>(VecDeque>); impl<'event> Iterator for SectionBodyIter<'event> { - type Item = (Key<'event>, Cow<'event, [u8]>); + type Item = (Key<'event>, Cow<'event, BStr>); fn next(&mut self) -> Option { let mut key = None; - let mut partial_value: Vec = Vec::new(); + let mut partial_value = BString::default(); let mut value = None; while let Some(event) = self.0.pop_front() { @@ -424,10 +421,10 @@ impl<'event> Iterator for SectionBodyIter<'event> { value = Some(v); break; } - Event::ValueNotDone(v) => partial_value.extend::<&[u8]>(v.borrow()), + Event::ValueNotDone(v) => partial_value.push_str(v.as_ref()), Event::ValueDone(v) => { - partial_value.extend::<&[u8]>(v.borrow()); - value = Some(Cow::Owned(partial_value)); + partial_value.push_str(v.as_ref()); + value = Some(partial_value.into()); break; } _ => (), diff --git a/git-config/src/file/try_from_str_tests.rs b/git-config/src/file/try_from_str_tests.rs index 15f8fa7c485..1b9f1768d9b 100644 --- a/git-config/src/file/try_from_str_tests.rs +++ b/git-config/src/file/try_from_str_tests.rs @@ -31,7 +31,7 @@ fn parse_single_section() { let expected_lookup_tree = { let mut tree = HashMap::new(); tree.insert( - SectionHeaderName(Cow::Borrowed("core")), + SectionHeaderName(Cow::Borrowed("core".into())), vec![LookupTreeNode::Terminal(vec![SectionId(0)])], ); tree @@ -71,9 +71,9 @@ fn parse_single_subsection() { let expected_lookup_tree = { let mut tree = HashMap::new(); let mut inner_tree = HashMap::new(); - inner_tree.insert(Cow::Borrowed("sub"), vec![SectionId(0)]); + inner_tree.insert(Cow::Borrowed("sub".into()), vec![SectionId(0)]); tree.insert( - SectionHeaderName(Cow::Borrowed("core")), + SectionHeaderName(Cow::Borrowed("core".into())), vec![LookupTreeNode::NonTerminal(inner_tree)], ); tree @@ -114,11 +114,11 @@ fn parse_multiple_sections() { let expected_lookup_tree = { let mut tree = HashMap::new(); tree.insert( - SectionHeaderName(Cow::Borrowed("core")), + SectionHeaderName(Cow::Borrowed("core".into())), vec![LookupTreeNode::Terminal(vec![SectionId(0)])], ); tree.insert( - SectionHeaderName(Cow::Borrowed("other")), + SectionHeaderName(Cow::Borrowed("other".into())), vec![LookupTreeNode::Terminal(vec![SectionId(1)])], ); tree @@ -164,7 +164,7 @@ fn parse_multiple_duplicate_sections() { let expected_lookup_tree = { let mut tree = HashMap::new(); tree.insert( - SectionHeaderName(Cow::Borrowed("core")), + SectionHeaderName(Cow::Borrowed("core".into())), vec![LookupTreeNode::Terminal(vec![SectionId(0), SectionId(1)])], ); tree diff --git a/git-config/src/file/utils.rs b/git-config/src/file/utils.rs index f2909c848fe..3fc901251e0 100644 --- a/git-config/src/file/utils.rs +++ b/git-config/src/file/utils.rs @@ -1,3 +1,4 @@ +use bstr::BStr; use std::collections::HashMap; use crate::{ @@ -71,6 +72,7 @@ impl<'event> File<'event> { // `n + 1` checks, while the if and matches will result in the for loop // needing `2n` checks. if let Some(subsection_name) = subsection_name { + let subsection_name: &BStr = subsection_name.into(); for node in section_ids { if let LookupTreeNode::NonTerminal(subsection_lookup) = node { maybe_ids = subsection_lookup.get(subsection_name); @@ -118,11 +120,7 @@ impl<'event> File<'event> { section_indices.sort(); for section_index in section_indices { let section_header = other.section_headers.remove(§ion_index).expect("present"); - self.push_section( - section_header.name.0, - section_header.subsection_name, - other.sections.remove(§ion_index).expect("present"), - ); + self.push_section_internal(section_header, other.sections.remove(§ion_index).expect("present")); } } } diff --git a/git-config/src/file/value.rs b/git-config/src/file/value.rs index d63f159f225..ad688404578 100644 --- a/git-config/src/file/value.rs +++ b/git-config/src/file/value.rs @@ -1,8 +1,5 @@ -use std::{ - borrow::{Borrow, Cow}, - collections::HashMap, - ops::DerefMut, -}; +use bstr::{BStr, BString}; +use std::{borrow::Cow, collections::HashMap, ops::DerefMut}; use crate::{ file::{ @@ -11,7 +8,7 @@ use crate::{ }, lookup, parser::{Event, Key}, - values::{normalize_bytes, normalize_vec}, + values::{normalize_bstr, normalize_bstring}, }; /// An intermediate representation of a mutable value obtained from @@ -53,7 +50,7 @@ impl<'borrow, 'lookup, 'event> MutableValue<'borrow, 'lookup, 'event> { /// # Errors /// /// Returns an error if the lookup failed. - pub fn get(&self) -> Result, lookup::existing::Error> { + pub fn get(&self) -> Result, lookup::existing::Error> { self.section.get(&self.key, self.index, self.index + self.size) } @@ -61,19 +58,18 @@ impl<'borrow, 'lookup, 'event> MutableValue<'borrow, 'lookup, 'event> { /// the Value event(s) are replaced with a single new event containing the /// new value. pub fn set_string(&mut self, input: String) { - self.set_bytes(input.into_bytes()); + self.set_bytes(input.into()); } /// Update the value to the provided one. This modifies the value such that /// the Value event(s) are replaced with a single new event containing the /// new value. - pub fn set_bytes(&mut self, input: Vec) { + pub fn set_bytes(&mut self, input: BString) { if self.size.0 > 0 { self.section.delete(self.index, self.index + self.size); } self.size = Size(3); - self.section - .set_internal(self.index, Key(Cow::Owned(self.key.to_string())), input); + self.section.set_internal(self.index, self.key.to_owned(), input); } /// Removes the value. Does nothing when called multiple times in @@ -146,7 +142,7 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { /// # Errors /// /// Returns an error if the lookup failed. - pub fn get(&self) -> Result>, lookup::existing::Error> { + pub fn get(&self) -> Result>, lookup::existing::Error> { let mut found_key = false; let mut values = vec![]; let mut partial_value = None; @@ -168,7 +164,7 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { Event::Key(event_key) if *event_key == self.key => found_key = true, Event::Value(v) if found_key => { found_key = false; - values.push(normalize_bytes(v.borrow())); + values.push(normalize_bstr(v.as_ref())); } Event::ValueNotDone(v) if found_key => { partial_value = Some((*v).to_vec()); @@ -178,8 +174,8 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { let mut value = partial_value .take() .expect("Somehow got ValueDone before ValueNotDone event"); - value.extend(&**v); - values.push(normalize_vec(value)); + value.extend(&***v); + values.push(normalize_bstring(value)); } _ => (), } @@ -212,7 +208,7 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { /// /// This will panic if the index is out of range. pub fn set_string(&mut self, index: usize, input: String) { - self.set_bytes(index, input.into_bytes()); + self.set_bytes(index, input); } /// Sets the value at the given index. @@ -220,8 +216,8 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { /// # Safety /// /// This will panic if the index is out of range. - pub fn set_bytes(&mut self, index: usize, input: Vec) { - self.set_value(index, Cow::Owned(input)); + pub fn set_bytes(&mut self, index: usize, input: impl Into) { + self.set_value(index, Cow::Owned(input.into())); } /// Sets the value at the given index. @@ -229,7 +225,7 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { /// # Safety /// /// This will panic if the index is out of range. - pub fn set_value<'a: 'event>(&mut self, index: usize, input: Cow<'a, [u8]>) { + pub fn set_value<'a: 'event>(&mut self, index: usize, input: Cow<'a, BStr>) { let EntryData { section_id, offset_index, @@ -253,7 +249,7 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { /// remaining values are ignored. /// /// [`zip`]: std::iter::Iterator::zip - pub fn set_values<'a: 'event>(&mut self, input: impl Iterator>) { + pub fn set_values<'a: 'event>(&mut self, input: impl Iterator>) { for ( EntryData { section_id, @@ -278,12 +274,13 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { /// Sets all values in this multivar to the provided one by copying the /// input for all values. pub fn set_str_all(&mut self, input: &str) { - self.set_owned_values_all(input.as_bytes()); + self.set_owned_values_all(input); } /// Sets all values in this multivar to the provided one by copying the /// input bytes for all values. - pub fn set_owned_values_all(&mut self, input: &[u8]) { + pub fn set_owned_values_all<'a>(&mut self, input: impl Into<&'a BStr>) { + let input = input.into(); for EntryData { section_id, offset_index, @@ -297,7 +294,7 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { .expect("sections does not have section id from section ids"), *section_id, *offset_index, - Cow::Owned(input.to_vec()), + Cow::Owned(input.to_owned()), ); } } @@ -309,7 +306,7 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { /// need for a more ergonomic interface. /// /// [`File`]: crate::File - pub fn set_values_all<'a: 'event>(&mut self, input: &'a [u8]) { + pub fn set_values_all<'a: 'event>(&mut self, input: &'a BStr) { for EntryData { section_id, offset_index, @@ -334,7 +331,7 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { section: &mut SectionBody<'event>, section_id: SectionId, offset_index: usize, - input: Cow<'a, [u8]>, + input: Cow<'a, BStr>, ) { let (offset, size) = MutableMultiValue::index_and_size(offsets, section_id, offset_index); section.as_mut().drain(offset..offset + size); @@ -342,9 +339,7 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { MutableMultiValue::set_offset(offsets, section_id, offset_index, 3); section.as_mut().insert(offset, Event::Value(input)); section.as_mut().insert(offset, Event::KeyValueSeparator); - section - .as_mut() - .insert(offset, Event::Key(Key(Cow::Owned(key.0.to_string())))); + section.as_mut().insert(offset, Event::Key(key.to_owned())); } /// Removes the value at the given index. Does nothing when called multiple diff --git a/git-config/src/fs.rs b/git-config/src/fs.rs index 8d784fec94e..1005979aadf 100644 --- a/git-config/src/fs.rs +++ b/git-config/src/fs.rs @@ -1,6 +1,7 @@ #![allow(unused)] #![allow(clippy::result_unit_err)] +use bstr::BStr; use std::{ borrow::Cow, convert::TryFrom, @@ -149,7 +150,7 @@ pub struct Config<'a> { impl<'a> Config<'a> { #[must_use] - pub fn value>>( + pub fn value>>( &'a self, section_name: &str, subsection_name: Option<&str>, @@ -159,7 +160,7 @@ impl<'a> Config<'a> { .map(|(value, _)| value) } - fn value_with_source>>( + fn value_with_source>>( &'a self, section_name: &str, subsection_name: Option<&str>, @@ -178,7 +179,7 @@ impl<'a> Config<'a> { None } - pub fn try_value<'lookup, T: TryFrom>>( + pub fn try_value<'lookup, T: TryFrom>>( &'a self, section_name: &'lookup str, subsection_name: Option<&'lookup str>, @@ -192,7 +193,7 @@ impl<'a> Config<'a> { /// if the key was not found. On a successful parse, the value will be /// returned as well as the source location. This respects the priority of /// the various configuration files. - pub fn try_value_with_source<'lookup, T: TryFrom>>( + pub fn try_value_with_source<'lookup, T: TryFrom>>( &'a self, section_name: &'lookup str, subsection_name: Option<&'lookup str>, diff --git a/git-config/src/parser.rs b/git-config/src/parser.rs index 0380c281fbf..5e13066978d 100644 --- a/git-config/src/parser.rs +++ b/git-config/src/parser.rs @@ -9,6 +9,7 @@ //! //! [`File`]: crate::File +use bstr::{BStr, BString, ByteSlice, ByteVec}; use std::{borrow::Cow, convert::TryFrom, fmt::Display, hash::Hash, io::Read, iter::FusedIterator, path::Path}; use nom::{ @@ -50,21 +51,21 @@ pub enum Event<'a> { /// if an implicit boolean value is used. Note that these values may contain /// spaces and any special character. This value is also unprocessed, so it /// it may contain double quotes that should be replaced. - Value(Cow<'a, [u8]>), + Value(Cow<'a, BStr>), /// Represents any token used to signify a new line character. On Unix /// platforms, this is typically just `\n`, but can be any valid newline /// sequence. Multiple newlines (such as `\n\n`) will be merged as a single /// newline event. - Newline(Cow<'a, str>), + Newline(Cow<'a, BStr>), /// Any value that isn't completed. This occurs when the value is continued /// onto the next line. A Newline event is guaranteed after, followed by /// either a ValueDone, a Whitespace, or another ValueNotDone. - ValueNotDone(Cow<'a, [u8]>), + ValueNotDone(Cow<'a, BStr>), /// The last line of a value which was continued onto another line. - ValueDone(Cow<'a, [u8]>), + ValueDone(Cow<'a, BStr>), /// A continuous section of insignificant whitespace. Values with internal /// spaces will not be separated by this event. - Whitespace(Cow<'a, str>), + Whitespace(Cow<'a, BStr>), /// This event is emitted when the parser counters a valid `=` character /// separating the key and value. This event is necessary as it eliminates /// the ambiguity for whitespace events between a key and value event. @@ -76,7 +77,7 @@ impl Event<'_> { /// non-UTF-8 sequences are present or a UTF-8 representation can't be /// guaranteed. #[must_use] - pub fn to_vec(&self) -> Vec { + pub fn to_bstring(&self) -> BString { self.into() } @@ -129,21 +130,21 @@ impl Display for Event<'_> { } } -impl From> for Vec { +impl From> for BString { fn from(event: Event<'_>) -> Self { event.into() } } -impl From<&Event<'_>> for Vec { +impl From<&Event<'_>> for BString { fn from(event: &Event<'_>) -> Self { match event { - Event::Value(e) | Event::ValueNotDone(e) | Event::ValueDone(e) => e.to_vec(), + Event::Value(e) | Event::ValueNotDone(e) | Event::ValueDone(e) => e.as_ref().into(), Event::Comment(e) => e.into(), Event::SectionHeader(e) => e.into(), - Event::Key(e) => e.0.as_bytes().to_vec(), - Event::Newline(e) | Event::Whitespace(e) => e.as_bytes().to_vec(), - Event::KeyValueSeparator => vec![b'='], + Event::Key(e) => e.0.as_ref().into(), + Event::Newline(e) | Event::Whitespace(e) => e.as_ref().into(), + Event::KeyValueSeparator => "=".into(), } } } @@ -246,12 +247,12 @@ macro_rules! generate_case_insensitive { impl<'a> From<&'a str> for $name<'a> { fn from(s: &'a str) -> Self { - Self(Cow::Borrowed(s)) + Self(Cow::Borrowed(s.into())) } } - impl<'a> From> for $name<'a> { - fn from(s: Cow<'a, str>) -> Self { + impl<'a> From> for $name<'a> { + fn from(s: Cow<'a, BStr>) -> Self { Self(s) } } @@ -268,13 +269,13 @@ macro_rules! generate_case_insensitive { generate_case_insensitive!( SectionHeaderName, - str, + BStr, "Wrapper struct for section header names, since section headers are case-insensitive." ); generate_case_insensitive!( Key, - str, + BStr, "Wrapper struct for key names, since keys are case-insensitive." ); @@ -292,9 +293,9 @@ pub struct ParsedSectionHeader<'a> { /// reconstruction of subsection format is dependent on this value. If this /// is all whitespace, then the subsection name needs to be surrounded by /// quotes to have perfect reconstruction. - pub separator: Option>, + pub separator: Option>, /// The subsection name without quotes if any exist. - pub subsection_name: Option>, + pub subsection_name: Option>, } impl ParsedSectionHeader<'_> { @@ -302,7 +303,7 @@ impl ParsedSectionHeader<'_> { /// non-UTF-8 sequences are present or a UTF-8 representation can't be /// guaranteed. #[must_use] - pub fn to_vec(&self) -> Vec { + pub fn to_bstring(&self) -> BString { self.into() } @@ -338,7 +339,7 @@ impl Display for ParsedSectionHeader<'_> { // Separator must be utf-8 v.fmt(f)?; let subsection_name = self.subsection_name.as_ref().unwrap(); - if v == "." { + if v.as_ref() == "." { subsection_name.fmt(f)?; } else { write!(f, "\"{}\"", subsection_name)?; @@ -349,15 +350,15 @@ impl Display for ParsedSectionHeader<'_> { } } -impl From> for Vec { +impl From> for BString { fn from(header: ParsedSectionHeader<'_>) -> Self { header.into() } } -impl From<&ParsedSectionHeader<'_>> for Vec { +impl From<&ParsedSectionHeader<'_>> for BString { fn from(header: &ParsedSectionHeader<'_>) -> Self { - header.to_string().into_bytes() + header.to_string().into() } } @@ -371,9 +372,9 @@ impl<'a> From> for Event<'a> { #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] pub struct ParsedComment<'a> { /// The comment marker used. This is either a semicolon or octothorpe. - pub comment_tag: char, + pub comment_tag: u8, /// The parsed comment. - pub comment: Cow<'a, [u8]>, + pub comment: Cow<'a, BStr>, } impl ParsedComment<'_> { @@ -395,7 +396,7 @@ impl ParsedComment<'_> { pub fn to_owned(&self) -> ParsedComment<'static> { ParsedComment { comment_tag: self.comment_tag, - comment: Cow::Owned(self.comment.to_vec()), + comment: Cow::Owned(self.comment.as_ref().into()), } } } @@ -414,16 +415,16 @@ impl Display for ParsedComment<'_> { } } -impl From> for Vec { +impl From> for BString { fn from(c: ParsedComment<'_>) -> Self { c.into() } } -impl From<&ParsedComment<'_>> for Vec { +impl From<&ParsedComment<'_>> for BString { fn from(c: &ParsedComment<'_>) -> Self { - let mut values = vec![c.comment_tag as u8]; - values.extend(c.comment.iter()); + let mut values = BString::from(vec![c.comment_tag]); + values.push_str(c.comment.as_ref()); values } } @@ -435,7 +436,7 @@ impl From<&ParsedComment<'_>> for Vec { pub struct Error<'a> { line_number: usize, last_attempted_parser: ParserNode, - parsed_until: Cow<'a, [u8]>, + parsed_until: Cow<'a, BStr>, } impl Error<'_> { @@ -652,20 +653,20 @@ impl Display for ParserNode { /// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str, SectionHeaderName, Key}; /// # use std::borrow::Cow; /// # let section_header = ParsedSectionHeader { -/// # name: SectionHeaderName(Cow::Borrowed("core")), +/// # name: SectionHeaderName(Cow::Borrowed("core".into())), /// # separator: None, /// # subsection_name: None, /// # }; /// # let section_data = "[core]\n autocrlf = input"; /// # assert_eq!(parse_from_str(section_data).unwrap().into_vec(), vec![ /// Event::SectionHeader(section_header), -/// Event::Newline(Cow::Borrowed("\n")), -/// Event::Whitespace(Cow::Borrowed(" ")), -/// Event::Key(Key(Cow::Borrowed("autocrlf"))), -/// Event::Whitespace(Cow::Borrowed(" ")), +/// Event::Newline(Cow::Borrowed("\n".into())), +/// Event::Whitespace(Cow::Borrowed(" ".into())), +/// Event::Key(Key(Cow::Borrowed("autocrlf".into()))), +/// Event::Whitespace(Cow::Borrowed(" ".into())), /// Event::KeyValueSeparator, -/// Event::Whitespace(Cow::Borrowed(" ")), -/// Event::Value(Cow::Borrowed(b"input")), +/// Event::Whitespace(Cow::Borrowed(" ".into())), +/// Event::Value(Cow::Borrowed("input".into())), /// # ]); /// ``` /// @@ -691,17 +692,17 @@ impl Display for ParserNode { /// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str, SectionHeaderName, Key}; /// # use std::borrow::Cow; /// # let section_header = ParsedSectionHeader { -/// # name: SectionHeaderName(Cow::Borrowed("core")), +/// # name: SectionHeaderName(Cow::Borrowed("core".into())), /// # separator: None, /// # subsection_name: None, /// # }; /// # let section_data = "[core]\n autocrlf"; /// # assert_eq!(parse_from_str(section_data).unwrap().into_vec(), vec![ /// Event::SectionHeader(section_header), -/// Event::Newline(Cow::Borrowed("\n")), -/// Event::Whitespace(Cow::Borrowed(" ")), -/// Event::Key(Key(Cow::Borrowed("autocrlf"))), -/// Event::Value(Cow::Borrowed(b"")), +/// Event::Newline(Cow::Borrowed("\n".into())), +/// Event::Whitespace(Cow::Borrowed(" ".into())), +/// Event::Key(Key(Cow::Borrowed("autocrlf".into()))), +/// Event::Value(Cow::Borrowed("".into())), /// # ]); /// ``` /// @@ -725,21 +726,21 @@ impl Display for ParserNode { /// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str, SectionHeaderName, Key}; /// # use std::borrow::Cow; /// # let section_header = ParsedSectionHeader { -/// # name: SectionHeaderName(Cow::Borrowed("core")), +/// # name: SectionHeaderName(Cow::Borrowed("core".into())), /// # separator: None, /// # subsection_name: None, /// # }; /// # let section_data = "[core]\nautocrlf=true\"\"\nfilemode=fa\"lse\""; /// # assert_eq!(parse_from_str(section_data).unwrap().into_vec(), vec![ /// Event::SectionHeader(section_header), -/// Event::Newline(Cow::Borrowed("\n")), -/// Event::Key(Key(Cow::Borrowed("autocrlf"))), +/// Event::Newline(Cow::Borrowed("\n".into())), +/// Event::Key(Key(Cow::Borrowed("autocrlf".into()))), /// Event::KeyValueSeparator, -/// Event::Value(Cow::Borrowed(br#"true"""#)), -/// Event::Newline(Cow::Borrowed("\n")), -/// Event::Key(Key(Cow::Borrowed("filemode"))), +/// Event::Value(Cow::Borrowed(r#"true"""#.into())), +/// Event::Newline(Cow::Borrowed("\n".into())), +/// Event::Key(Key(Cow::Borrowed("filemode".into()))), /// Event::KeyValueSeparator, -/// Event::Value(Cow::Borrowed(br#"fa"lse""#)), +/// Event::Value(Cow::Borrowed(r#"fa"lse""#.into())), /// # ]); /// ``` /// @@ -762,19 +763,19 @@ impl Display for ParserNode { /// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str, SectionHeaderName, Key}; /// # use std::borrow::Cow; /// # let section_header = ParsedSectionHeader { -/// # name: SectionHeaderName(Cow::Borrowed("some-section")), +/// # name: SectionHeaderName(Cow::Borrowed("some-section".into())), /// # separator: None, /// # subsection_name: None, /// # }; /// # let section_data = "[some-section]\nfile=a\\\n c"; /// # assert_eq!(parse_from_str(section_data).unwrap().into_vec(), vec![ /// Event::SectionHeader(section_header), -/// Event::Newline(Cow::Borrowed("\n")), -/// Event::Key(Key(Cow::Borrowed("file"))), +/// Event::Newline(Cow::Borrowed("\n".into())), +/// Event::Key(Key(Cow::Borrowed("file".into()))), /// Event::KeyValueSeparator, -/// Event::ValueNotDone(Cow::Borrowed(b"a")), -/// Event::Newline(Cow::Borrowed("\n")), -/// Event::ValueDone(Cow::Borrowed(b" c")), +/// Event::ValueNotDone(Cow::Borrowed("a".into())), +/// Event::Newline(Cow::Borrowed("\n".into())), +/// Event::ValueDone(Cow::Borrowed(" c".into())), /// # ]); /// ``` /// @@ -936,7 +937,7 @@ pub fn parse_from_bytes(input: &[u8]) -> Result, Error<'_>> { let (i, sections) = maybe_sections.map_err(|_| Error { line_number: newlines, last_attempted_parser: node, - parsed_until: i.into(), + parsed_until: i.as_bstr().into(), })?; let sections = sections @@ -953,7 +954,7 @@ pub fn parse_from_bytes(input: &[u8]) -> Result, Error<'_>> { return Err(Error { line_number: newlines, last_attempted_parser: node, - parsed_until: i.into(), + parsed_until: i.as_bstr().into(), }); } @@ -980,10 +981,12 @@ pub fn parse_from_bytes_owned(input: &[u8]) -> Result, Error<'st let bom = unicode_bom::Bom::from(input); let (i, frontmatter) = many0(alt(( map(comment, Event::Comment), - map(take_spaces, |whitespace| Event::Whitespace(Cow::Borrowed(whitespace))), + map(take_spaces, |whitespace| { + Event::Whitespace(Cow::Borrowed(whitespace.as_bstr())) + }), map(take_newlines, |(newline, counter)| { newlines += counter; - Event::Newline(Cow::Borrowed(newline)) + Event::Newline(Cow::Borrowed(newline.as_bstr())) }), )))(&input[bom.len()..]) // I don't think this can panic. many0 errors if the child parser returns @@ -1036,8 +1039,8 @@ fn comment(i: &[u8]) -> IResult<&[u8], ParsedComment<'_>> { Ok(( i, ParsedComment { - comment_tag, - comment: Cow::Borrowed(comment), + comment_tag: comment_tag as u8, // TODO: don't use character based nom functions + comment: Cow::Borrowed(comment.as_bstr()), }, )) } @@ -1056,7 +1059,7 @@ fn section<'a, 'b>(i: &'a [u8], node: &'b mut ParserNode) -> IResult<&'a [u8], ( if let Ok((new_i, v)) = take_spaces(i) { if old_i != new_i { i = new_i; - items.push(Event::Whitespace(Cow::Borrowed(v))); + items.push(Event::Whitespace(Cow::Borrowed(v.as_bstr()))); } } @@ -1064,7 +1067,7 @@ fn section<'a, 'b>(i: &'a [u8], node: &'b mut ParserNode) -> IResult<&'a [u8], ( if old_i != new_i { i = new_i; newlines += new_newlines; - items.push(Event::Newline(Cow::Borrowed(v))); + items.push(Event::Newline(Cow::Borrowed(v.as_bstr()))); } } @@ -1103,24 +1106,18 @@ fn section_header(i: &[u8]) -> IResult<&[u8], ParsedSectionHeader<'_>> { // No spaces must be between section name and section start let (i, name) = take_while(|c: u8| c.is_ascii_alphanumeric() || c == b'-' || c == b'.')(i)?; - let name = std::str::from_utf8(name).map_err(|_| { - nom::Err::Error(NomError::<&[u8]> { - input: i, - code: ErrorKind::AlphaNumeric, - }) - })?; - + let name = name.as_bstr(); if let Ok((i, _)) = char::<_, NomError<&[u8]>>(']')(i) { // Either section does not have a subsection or using deprecated // subsection syntax at this point. let header = match memchr::memrchr(b'.', name.as_bytes()) { Some(index) => ParsedSectionHeader { - name: SectionHeaderName(Cow::Borrowed(&name[..index])), - separator: name.get(index..=index).map(Cow::Borrowed), - subsection_name: name.get(index + 1..).map(Cow::Borrowed), + name: SectionHeaderName(Cow::Borrowed(name[..index].as_bstr())), + separator: name.get(index..=index).map(|s| Cow::Borrowed(s.as_bstr())), + subsection_name: name.get(index + 1..).map(|s| Cow::Borrowed(s.as_bstr())), }, None => ParsedSectionHeader { - name: SectionHeaderName(Cow::Borrowed(name)), + name: SectionHeaderName(Cow::Borrowed(name.as_bstr())), separator: None, subsection_name: None, }, @@ -1138,21 +1135,14 @@ fn section_header(i: &[u8]) -> IResult<&[u8], ParsedSectionHeader<'_>> { tag("\"]"), )(i)?; - let subsection_name = subsection_name.map(std::str::from_utf8).transpose().map_err(|_| { - nom::Err::Error(NomError::<&[u8]> { - input: i, - code: ErrorKind::AlphaNumeric, - }) - })?; + let subsection_name = subsection_name.map(|s| s.as_bstr()); Ok(( i, ParsedSectionHeader { name: SectionHeaderName(Cow::Borrowed(name)), separator: Some(Cow::Borrowed(whitespace)), - // We know that there's some section name here, so if we get an - // empty vec here then we actually parsed an empty section name. - subsection_name: subsection_name.or(Some("")).map(Cow::Borrowed), + subsection_name: Cow::Borrowed(subsection_name.unwrap_or_default()).into(), }, )) } @@ -1180,7 +1170,7 @@ fn section_body<'a, 'b, 'c>( /// Parses the config name of a config pair. Assumes the input has already been /// trimmed of any leading whitespace. -fn config_name(i: &[u8]) -> IResult<&[u8], &str> { +fn config_name(i: &[u8]) -> IResult<&[u8], &BStr> { if i.is_empty() { return Err(nom::Err::Error(NomError { input: i, @@ -1196,14 +1186,7 @@ fn config_name(i: &[u8]) -> IResult<&[u8], &str> { } let (i, v) = take_while(|c: u8| (c as char).is_alphanumeric() || c == b'-')(i)?; - let v = std::str::from_utf8(v).map_err(|_| { - nom::Err::Error(NomError::<&[u8]> { - input: i, - code: ErrorKind::AlphaNumeric, - }) - })?; - - Ok((i, v)) + Ok((i, v.as_bstr())) } fn config_value<'a, 'b>(i: &'a [u8], events: &'b mut Vec>) -> IResult<&'a [u8], ()> { @@ -1216,7 +1199,7 @@ fn config_value<'a, 'b>(i: &'a [u8], events: &'b mut Vec>) -> IResult< let (i, _) = value_impl(i, events)?; Ok((i, ())) } else { - events.push(Event::Value(Cow::Borrowed(b""))); + events.push(Event::Value(Cow::Borrowed("".into()))); Ok((i, ())) } } @@ -1247,10 +1230,8 @@ fn value_impl<'a, 'b>(i: &'a [u8], events: &'b mut Vec>) -> IResult<&' // continuation. b'\n' => { partial_value_found = true; - events.push(Event::ValueNotDone(Cow::Borrowed(&i[offset..index - 1]))); - events.push(Event::Newline(Cow::Borrowed( - std::str::from_utf8(&i[index..=index]).unwrap(), - ))); + events.push(Event::ValueNotDone(Cow::Borrowed(i[offset..index - 1].as_bstr()))); + events.push(Event::Newline(Cow::Borrowed(i[index..=index].as_bstr()))); offset = index + 1; parsed_index = 0; } @@ -1285,8 +1266,8 @@ fn value_impl<'a, 'b>(i: &'a [u8], events: &'b mut Vec>) -> IResult<&' parsed_index = i.len(); } else { // Didn't parse anything at all, newline straight away. - events.push(Event::Value(Cow::Owned(Vec::new()))); - events.push(Event::Newline(Cow::Borrowed("\n"))); + events.push(Event::Value(Cow::Owned(BString::default()))); + events.push(Event::Newline(Cow::Borrowed("\n".into()))); return Ok((&i[1..], ())); } } @@ -1319,15 +1300,15 @@ fn value_impl<'a, 'b>(i: &'a [u8], events: &'b mut Vec>) -> IResult<&' }; if partial_value_found { - events.push(Event::ValueDone(Cow::Borrowed(remainder_value))); + events.push(Event::ValueDone(Cow::Borrowed(remainder_value.as_bstr()))); } else { - events.push(Event::Value(Cow::Borrowed(remainder_value))); + events.push(Event::Value(Cow::Borrowed(remainder_value.as_bstr()))); } Ok((i, ())) } -fn take_spaces(i: &[u8]) -> IResult<&[u8], &str> { +fn take_spaces(i: &[u8]) -> IResult<&[u8], &BStr> { let (i, v) = take_while(|c| (c as char).is_ascii() && is_space(c))(i)?; if v.is_empty() { Err(nom::Err::Error(NomError { @@ -1335,12 +1316,11 @@ fn take_spaces(i: &[u8]) -> IResult<&[u8], &str> { code: ErrorKind::Eof, })) } else { - // v is guaranteed to be utf-8 - Ok((i, std::str::from_utf8(v).unwrap())) + Ok((i, v.as_bstr())) } } -fn take_newlines(i: &[u8]) -> IResult<&[u8], (&str, usize)> { +fn take_newlines(i: &[u8]) -> IResult<&[u8], (&BStr, usize)> { let mut counter = 0; let mut consumed_bytes = 0; let mut next_must_be_newline = false; @@ -1370,8 +1350,7 @@ fn take_newlines(i: &[u8]) -> IResult<&[u8], (&str, usize)> { code: ErrorKind::Eof, })) } else { - // v is guaranteed to be utf-8 - Ok((i, (std::str::from_utf8(v).unwrap(), counter))) + Ok((i, (v.as_bstr(), counter))) } } @@ -1484,7 +1463,7 @@ mod config_name { #[test] fn just_name() { - assert_eq!(config_name(b"name").unwrap(), fully_consumed("name")); + assert_eq!(config_name(b"name").unwrap(), fully_consumed("name".into())); } #[test] diff --git a/git-config/src/test_util.rs b/git-config/src/test_util.rs index d7183c87c43..c999e42d41a 100644 --- a/git-config/src/test_util.rs +++ b/git-config/src/test_util.rs @@ -17,8 +17,8 @@ pub fn section_header( if let Some((separator, subsection_name)) = subsection.into() { ParsedSectionHeader { name, - separator: Some(Cow::Borrowed(separator)), - subsection_name: Some(Cow::Borrowed(subsection_name)), + separator: Some(Cow::Borrowed(separator.into())), + subsection_name: Some(Cow::Borrowed(subsection_name.into())), } } else { ParsedSectionHeader { @@ -29,42 +29,42 @@ pub fn section_header( } } -pub(crate) const fn name_event(name: &'static str) -> Event<'static> { - Event::Key(Key(Cow::Borrowed(name))) +pub(crate) fn name_event(name: &'static str) -> Event<'static> { + Event::Key(Key(Cow::Borrowed(name.into()))) } -pub(crate) const fn value_event(value: &'static str) -> Event<'static> { - Event::Value(Cow::Borrowed(value.as_bytes())) +pub(crate) fn value_event(value: &'static str) -> Event<'static> { + Event::Value(Cow::Borrowed(value.into())) } -pub(crate) const fn value_not_done_event(value: &'static str) -> Event<'static> { - Event::ValueNotDone(Cow::Borrowed(value.as_bytes())) +pub(crate) fn value_not_done_event(value: &'static str) -> Event<'static> { + Event::ValueNotDone(Cow::Borrowed(value.into())) } -pub(crate) const fn value_done_event(value: &'static str) -> Event<'static> { - Event::ValueDone(Cow::Borrowed(value.as_bytes())) +pub(crate) fn value_done_event(value: &'static str) -> Event<'static> { + Event::ValueDone(Cow::Borrowed(value.into())) } -pub(crate) const fn newline_event() -> Event<'static> { +pub(crate) fn newline_event() -> Event<'static> { newline_custom_event("\n") } -pub(crate) const fn newline_custom_event(value: &'static str) -> Event<'static> { - Event::Newline(Cow::Borrowed(value)) +pub(crate) fn newline_custom_event(value: &'static str) -> Event<'static> { + Event::Newline(Cow::Borrowed(value.into())) } -pub(crate) const fn whitespace_event(value: &'static str) -> Event<'static> { - Event::Whitespace(Cow::Borrowed(value)) +pub(crate) fn whitespace_event(value: &'static str) -> Event<'static> { + Event::Whitespace(Cow::Borrowed(value.into())) } -pub(crate) const fn comment_event(tag: char, msg: &'static str) -> Event<'static> { +pub(crate) fn comment_event(tag: char, msg: &'static str) -> Event<'static> { Event::Comment(comment(tag, msg)) } -pub(crate) const fn comment(comment_tag: char, comment: &'static str) -> ParsedComment<'static> { +pub(crate) fn comment(comment_tag: char, comment: &'static str) -> ParsedComment<'static> { ParsedComment { - comment_tag, - comment: Cow::Borrowed(comment.as_bytes()), + comment_tag: comment_tag as u8, + comment: Cow::Borrowed(comment.into()), } } diff --git a/git-config/src/types.rs b/git-config/src/types.rs index 0ba92ac8f50..0bfc19614de 100644 --- a/git-config/src/types.rs +++ b/git-config/src/types.rs @@ -8,14 +8,7 @@ use crate::{ /// High level `git-config` reader and writer. /// /// This is the full-featured implementation that can deserialize, serialize, -/// and edit `git-config` files without loss of whitespace or comments. As a -/// result, it's lot more complex than it's read-only variant, -/// [`ResolvedGitConfig`] that exposes a [`HashMap`]-like interface. Users that -/// only need to read `git-config` files should use that instead. -/// -/// Internally, this uses various acceleration data structures to improve -/// performance of the typical usage behavior of many lookups and relatively -/// fewer insertions. +/// and edit `git-config` files without loss of whitespace or comments. /// /// # Multivar behavior /// @@ -52,7 +45,6 @@ use crate::{ /// Consider the `multi` variants of the methods instead, if you want to work /// with all values instead. /// -/// [`ResolvedGitConfig`]: crate::file::ResolvedGitConfig /// [`raw_value`]: Self::raw_value #[derive(PartialEq, Eq, Clone, Debug, Default)] pub struct File<'event> { diff --git a/git-config/src/values.rs b/git-config/src/values.rs index 5b3608a61e1..fa108c6c418 100644 --- a/git-config/src/values.rs +++ b/git-config/src/values.rs @@ -2,7 +2,7 @@ use std::{borrow::Cow, convert::TryFrom, fmt::Display, str::FromStr}; -use bstr::{BStr, BString}; +use bstr::{BStr, BString, ByteSlice}; #[cfg(feature = "serde")] use serde::{Serialize, Serializer}; @@ -23,7 +23,7 @@ use crate::value; /// from the parser, you may want to use this to help with value interpretation. /// /// Generally speaking, you'll want to use one of the variants of this function, -/// such as [`normalize_str`] or [`normalize_vec`]. +/// such as [`normalize_bstr`] or [`normalize_bstring`]. /// /// # Examples /// @@ -31,54 +31,58 @@ use crate::value; /// /// ``` /// # use std::borrow::Cow; -/// # use git_config::values::normalize_str; -/// assert_eq!(normalize_str("hello world"), Cow::Borrowed(b"hello world".as_slice())); +/// # use bstr::ByteSlice; +/// # use git_config::values::normalize_bstr; +/// assert_eq!(normalize_bstr("hello world"), Cow::Borrowed(b"hello world".as_bstr())); /// ``` /// /// Fully quoted values are optimized to not need allocations. /// /// ``` /// # use std::borrow::Cow; -/// # use git_config::values::normalize_str; -/// assert_eq!(normalize_str("\"hello world\""), Cow::Borrowed(b"hello world".as_slice())); +/// # use bstr::ByteSlice; +/// # use git_config::values::normalize_bstr; +/// assert_eq!(normalize_bstr("\"hello world\""), Cow::Borrowed(b"hello world".as_bstr())); /// ``` /// /// Quoted values are unwrapped as an owned variant. /// /// ``` /// # use std::borrow::Cow; -/// # use git_config::values::normalize_str; -/// assert_eq!(normalize_str("hello \"world\""), Cow::<[u8]>::Owned(b"hello world".to_vec())); +/// # use bstr::{BStr, BString}; +/// # use git_config::values::{normalize_bstr}; +/// assert_eq!(normalize_bstr("hello \"world\""), Cow::::Owned(BString::from( "hello world" ))); /// ``` /// /// Escaped quotes are unescaped. /// /// ``` /// # use std::borrow::Cow; -/// # use git_config::values::normalize_str; -/// assert_eq!(normalize_str(r#"hello "world\"""#), Cow::<[u8]>::Owned(br#"hello world""#.to_vec())); +/// # use bstr::{BStr, BString}; +/// # use git_config::values::normalize_bstr; +/// assert_eq!(normalize_bstr(r#"hello "world\"""#), Cow::::Owned(BString::from(r#"hello world""#))); /// ``` /// /// [`parser`]: crate::parser::Parser #[must_use] -pub fn normalize_cow(input: Cow<'_, [u8]>) -> Cow<'_, [u8]> { +pub fn normalize_cow(input: Cow<'_, BStr>) -> Cow<'_, BStr> { let size = input.len(); - if &*input == b"\"\"" { - return Cow::Borrowed(&[]); + if input.as_ref() == "\"\"" { + return Cow::default(); } if size >= 3 && input[0] == b'=' && input[size - 1] == b'=' && input[size - 2] != b'\\' { match input { - Cow::Borrowed(input) => return normalize_bytes(&input[1..size]), + Cow::Borrowed(input) => return normalize_bstr(&input[1..size]), Cow::Owned(mut input) => { input.pop(); input.remove(0); - return normalize_vec(input); + return normalize_bstring(input); } } } - let mut owned = vec![]; + let mut owned = BString::default(); let mut first_index = 0; let mut last_index = 0; @@ -88,10 +92,10 @@ pub fn normalize_cow(input: Cow<'_, [u8]>) -> Cow<'_, [u8]> { was_escaped = false; if *c == b'"' { if first_index == 0 { - owned.extend(&input[last_index..i - 1]); + owned.extend(&*input[last_index..i - 1]); last_index = i; } else { - owned.extend(&input[first_index..i - 1]); + owned.extend(&*input[first_index..i - 1]); first_index = i; } } @@ -102,10 +106,10 @@ pub fn normalize_cow(input: Cow<'_, [u8]>) -> Cow<'_, [u8]> { was_escaped = true; } else if *c == b'"' { if first_index == 0 { - owned.extend(&input[last_index..i]); + owned.extend(&*input[last_index..i]); first_index = i + 1; } else { - owned.extend(&input[first_index..i]); + owned.extend(&*input[first_index..i]); first_index = 0; last_index = i + 1; } @@ -115,27 +119,21 @@ pub fn normalize_cow(input: Cow<'_, [u8]>) -> Cow<'_, [u8]> { if last_index == 0 { input } else { - owned.extend(&input[last_index..]); + owned.extend(&*input[last_index..]); Cow::Owned(owned) } } /// `&[u8]` variant of [`normalize_cow`]. #[must_use] -pub fn normalize_bytes(input: &[u8]) -> Cow<'_, [u8]> { - normalize_cow(Cow::Borrowed(input)) +pub fn normalize_bstr<'a>(input: impl Into<&'a BStr>) -> Cow<'a, BStr> { + normalize_cow(Cow::Borrowed(input.into())) } /// `Vec[u8]` variant of [`normalize_cow`]. #[must_use] -pub fn normalize_vec(input: Vec) -> Cow<'static, [u8]> { - normalize_cow(Cow::Owned(input)) -} - -/// [`str`] variant of [`normalize_cow`]. -#[must_use] -pub fn normalize_str(input: &str) -> Cow<'_, [u8]> { - normalize_bytes(input.as_bytes()) +pub fn normalize_bstring(input: impl Into) -> Cow<'static, BStr> { + normalize_cow(Cow::Owned(input.into())) } // TODO: remove bytes @@ -143,25 +141,25 @@ pub fn normalize_str(input: &str) -> Cow<'_, [u8]> { #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] pub struct Bytes<'a> { /// bytes - pub value: Cow<'a, [u8]>, + pub value: Cow<'a, BStr>, } -impl<'a> From<&'a [u8]> for Bytes<'a> { - fn from(s: &'a [u8]) -> Self { +impl<'a> From<&'a BStr> for Bytes<'a> { + fn from(s: &'a BStr) -> Self { Self { value: Cow::Borrowed(s), } } } -impl From> for Bytes<'_> { - fn from(s: Vec) -> Self { +impl From for Bytes<'_> { + fn from(s: BString) -> Self { Self { value: Cow::Owned(s) } } } -impl<'a> From> for Bytes<'a> { - fn from(c: Cow<'a, [u8]>) -> Self { +impl<'a> From> for Bytes<'a> { + fn from(c: Cow<'a, BStr>) -> Self { match c { Cow::Borrowed(c) => Self::from(c), Cow::Owned(c) => Self::from(c), @@ -176,13 +174,10 @@ pub struct String<'a> { pub value: Cow<'a, BStr>, } -impl<'a> From> for String<'a> { - fn from(c: Cow<'a, [u8]>) -> Self { +impl<'a> From> for String<'a> { + fn from(c: Cow<'a, BStr>) -> Self { String { - value: match normalize_cow(c) { - Cow::Borrowed(c) => Cow::Borrowed(c.into()), - Cow::Owned(c) => Cow::Owned(c.into()), - }, + value: normalize_cow(c), } } } @@ -339,8 +334,8 @@ impl<'a> AsRef for Path<'a> { } } -impl<'a> From> for Path<'a> { - fn from(value: Cow<'a, [u8]>) -> Self { +impl<'a> From> for Path<'a> { + fn from(value: Cow<'a, BStr>) -> Self { Path { value: match value { Cow::Borrowed(v) => Cow::Borrowed(v.into()), @@ -360,7 +355,7 @@ impl<'a> From> for Path<'a> { #[allow(missing_docs)] pub enum Boolean<'a> { True(TrueVariant<'a>), - False(Cow<'a, str>), + False(Cow<'a, BStr>), } impl Boolean<'_> { @@ -375,15 +370,7 @@ impl Boolean<'_> { /// non-UTF-8 sequences are present or a UTF-8 representation can't be /// guaranteed. #[must_use] - pub fn to_vec(&self) -> Vec { - self.into() - } - - /// Generates a byte representation of the value. This should be used when - /// non-UTF-8 sequences are present or a UTF-8 representation can't be - /// guaranteed. - #[must_use] - pub fn as_bytes(&self) -> &[u8] { + pub fn to_bstring(&self) -> BString { self.into() } } @@ -395,10 +382,10 @@ fn bool_err(input: impl Into) -> value::parse::Error { ) } -impl<'a> TryFrom<&'a [u8]> for Boolean<'a> { +impl<'a> TryFrom<&'a BStr> for Boolean<'a> { type Error = value::parse::Error; - fn try_from(value: &'a [u8]) -> Result { + fn try_from(value: &'a BStr) -> Result { if let Ok(v) = TrueVariant::try_from(value) { return Ok(Self::True(v)); } @@ -407,39 +394,35 @@ impl<'a> TryFrom<&'a [u8]> for Boolean<'a> { || value.eq_ignore_ascii_case(b"off") || value.eq_ignore_ascii_case(b"false") || value.eq_ignore_ascii_case(b"zero") - || value == b"\"\"" + || value == "\"\"" { - return Ok(Self::False( - std::str::from_utf8(value).expect("value is already validated").into(), - )); + return Ok(Self::False(value.as_bstr().into())); } Err(bool_err(value)) } } -impl TryFrom> for Boolean<'_> { +impl TryFrom for Boolean<'_> { type Error = value::parse::Error; - fn try_from(value: Vec) -> Result { + fn try_from(value: BString) -> Result { if value.eq_ignore_ascii_case(b"no") || value.eq_ignore_ascii_case(b"off") || value.eq_ignore_ascii_case(b"false") || value.eq_ignore_ascii_case(b"zero") - || value == b"\"\"" + || value == "\"\"" { - return Ok(Self::False(Cow::Owned( - std::string::String::from_utf8(value).expect("value is already validated"), - ))); + return Ok(Self::False(Cow::Owned(value.into()))); } TrueVariant::try_from(value).map(Self::True) } } -impl<'a> TryFrom> for Boolean<'a> { +impl<'a> TryFrom> for Boolean<'a> { type Error = value::parse::Error; - fn try_from(c: Cow<'a, [u8]>) -> Result { + fn try_from(c: Cow<'a, BStr>) -> Result { match c { Cow::Borrowed(c) => Self::try_from(c), Cow::Owned(c) => Self::try_from(c), @@ -465,24 +448,24 @@ impl From> for bool { } } -impl<'a, 'b: 'a> From<&'b Boolean<'a>> for &'a [u8] { +impl<'a, 'b: 'a> From<&'b Boolean<'a>> for &'a BStr { fn from(b: &'b Boolean<'_>) -> Self { match b { Boolean::True(t) => t.into(), - Boolean::False(f) => f.as_bytes(), + Boolean::False(f) => f.as_ref(), } } } -impl From> for Vec { +impl From> for BString { fn from(b: Boolean<'_>) -> Self { b.into() } } -impl From<&Boolean<'_>> for Vec { +impl From<&Boolean<'_>> for BString { fn from(b: &Boolean<'_>) -> Self { - b.to_string().into_bytes() + b.to_string().into() } } @@ -505,23 +488,21 @@ impl Serialize for Boolean<'_> { #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] #[allow(missing_docs)] pub enum TrueVariant<'a> { - Explicit(Cow<'a, str>), + Explicit(Cow<'a, BStr>), /// For values defined without a `= `. Implicit, } -impl<'a> TryFrom<&'a [u8]> for TrueVariant<'a> { +impl<'a> TryFrom<&'a BStr> for TrueVariant<'a> { type Error = value::parse::Error; - fn try_from(value: &'a [u8]) -> Result { + fn try_from(value: &'a BStr) -> Result { if value.eq_ignore_ascii_case(b"yes") || value.eq_ignore_ascii_case(b"on") || value.eq_ignore_ascii_case(b"true") || value.eq_ignore_ascii_case(b"one") { - Ok(Self::Explicit( - std::str::from_utf8(value).expect("value is already validated").into(), - )) + Ok(Self::Explicit(value.as_bstr().into())) } else if value.is_empty() { Ok(Self::Implicit) } else { @@ -530,18 +511,16 @@ impl<'a> TryFrom<&'a [u8]> for TrueVariant<'a> { } } -impl TryFrom> for TrueVariant<'_> { +impl TryFrom for TrueVariant<'_> { type Error = value::parse::Error; - fn try_from(value: Vec) -> Result { + fn try_from(value: BString) -> Result { if value.eq_ignore_ascii_case(b"yes") || value.eq_ignore_ascii_case(b"on") || value.eq_ignore_ascii_case(b"true") || value.eq_ignore_ascii_case(b"one") { - Ok(Self::Explicit(Cow::Owned( - std::string::String::from_utf8(value).expect("value is already validated"), - ))) + Ok(Self::Explicit(Cow::Owned(value.into()))) } else if value.is_empty() { Ok(Self::Implicit) } else { @@ -560,11 +539,11 @@ impl Display for TrueVariant<'_> { } } -impl<'a, 'b: 'a> From<&'b TrueVariant<'a>> for &'a [u8] { +impl<'a, 'b: 'a> From<&'b TrueVariant<'a>> for &'a BStr { fn from(t: &'b TrueVariant<'a>) -> Self { match t { - TrueVariant::Explicit(e) => e.as_bytes(), - TrueVariant::Implicit => &[], + TrueVariant::Explicit(e) => e.as_ref(), + TrueVariant::Implicit => "".into(), } } } @@ -603,7 +582,7 @@ impl Integer { /// non-UTF-8 sequences are present or a UTF-8 representation can't be /// guaranteed. #[must_use] - pub fn to_vec(self) -> Vec { + pub fn to_bstring(self) -> BString { self.into() } @@ -656,10 +635,10 @@ fn int_err(input: impl Into) -> value::parse::Error { ) } -impl TryFrom<&[u8]> for Integer { +impl TryFrom<&BStr> for Integer { type Error = value::parse::Error; - fn try_from(s: &[u8]) -> Result { + fn try_from(s: &BStr) -> Result { let s = std::str::from_utf8(s).map_err(|err| int_err(s).with_err(err))?; if let Ok(value) = s.parse() { return Ok(Self { value, suffix: None }); @@ -683,18 +662,18 @@ impl TryFrom<&[u8]> for Integer { } } -impl TryFrom> for Integer { +impl TryFrom for Integer { type Error = value::parse::Error; - fn try_from(value: Vec) -> Result { + fn try_from(value: BString) -> Result { Self::try_from(value.as_ref()) } } -impl TryFrom> for Integer { +impl TryFrom> for Integer { type Error = value::parse::Error; - fn try_from(c: Cow<'_, [u8]>) -> Result { + fn try_from(c: Cow<'_, BStr>) -> Result { match c { Cow::Borrowed(c) => Self::try_from(c), Cow::Owned(c) => Self::try_from(c), @@ -702,15 +681,15 @@ impl TryFrom> for Integer { } } -impl From for Vec { +impl From for BString { fn from(i: Integer) -> Self { i.into() } } -impl From<&Integer> for Vec { +impl From<&Integer> for BString { fn from(i: &Integer) -> Self { - i.to_string().into_bytes() + i.to_string().into() } } @@ -812,7 +791,7 @@ impl Color { /// non-UTF-8 sequences are present or a UTF-8 representation can't be /// guaranteed. #[must_use] - pub fn to_vec(&self) -> Vec { + pub fn to_bstring(&self) -> BString { self.into() } } @@ -853,10 +832,10 @@ fn color_err(input: impl Into) -> value::parse::Error { ) } -impl TryFrom<&[u8]> for Color { +impl TryFrom<&BStr> for Color { type Error = value::parse::Error; - fn try_from(s: &[u8]) -> Result { + fn try_from(s: &BStr) -> Result { let s = std::str::from_utf8(s).map_err(|err| color_err(s).with_err(err))?; enum ColorItem { Value(ColorValue), @@ -898,18 +877,18 @@ impl TryFrom<&[u8]> for Color { } } -impl TryFrom> for Color { +impl TryFrom for Color { type Error = value::parse::Error; - fn try_from(value: Vec) -> Result { + fn try_from(value: BString) -> Result { Self::try_from(value.as_ref()) } } -impl TryFrom> for Color { +impl TryFrom> for Color { type Error = value::parse::Error; - fn try_from(c: Cow<'_, [u8]>) -> Result { + fn try_from(c: Cow<'_, BStr>) -> Result { match c { Cow::Borrowed(c) => Self::try_from(c), Cow::Owned(c) => Self::try_from(c), @@ -917,15 +896,15 @@ impl TryFrom> for Color { } } -impl From for Vec { +impl From for BString { fn from(c: Color) -> Self { c.into() } } -impl From<&Color> for Vec { +impl From<&Color> for BString { fn from(c: &Color) -> Self { - c.to_string().into_bytes() + c.to_string().into() } } diff --git a/git-config/tests/file/access/raw/mutable_multi_value.rs b/git-config/tests/file/access/raw/mutable_multi_value.rs index bd00582f2f5..5343caa31d3 100644 --- a/git-config/tests/file/access/raw/mutable_multi_value.rs +++ b/git-config/tests/file/access/raw/mutable_multi_value.rs @@ -55,7 +55,7 @@ fn set_value_at_end() { fn set_values_all() { let mut git_config = init_config(); let mut values = git_config.raw_multi_value_mut("core", None, "a").unwrap(); - values.set_owned_values_all(b"Hello"); + values.set_owned_values_all("Hello"); assert_eq!( git_config.to_string(), "[core]\n a=Hello\n [core]\n a=Hello\n a=Hello" diff --git a/git-config/tests/file/access/raw/mutable_value.rs b/git-config/tests/file/access/raw/mutable_value.rs index d44be72cbbb..e768e35eae0 100644 --- a/git-config/tests/file/access/raw/mutable_value.rs +++ b/git-config/tests/file/access/raw/mutable_value.rs @@ -18,7 +18,7 @@ fn value_is_correct() { let mut git_config = init_config(); let value = git_config.raw_value_mut("core", None, "a").unwrap(); - assert_eq!(&*value.get().unwrap(), b"b100"); + assert_eq!(&*value.get().unwrap(), "b100"); } #[test] @@ -119,7 +119,7 @@ b ) .unwrap(); let mut value = git_config.raw_value_mut("core", None, "a").unwrap(); - assert_eq!(&*value.get().unwrap(), b"b100b"); + assert_eq!(&*value.get().unwrap(), "b100b"); value.delete(); assert_eq!( git_config.to_string(), diff --git a/git-config/tests/file/access/raw/raw_multi_value.rs b/git-config/tests/file/access/raw/raw_multi_value.rs index bad3f4d292e..3023acfc67f 100644 --- a/git-config/tests/file/access/raw/raw_multi_value.rs +++ b/git-config/tests/file/access/raw/raw_multi_value.rs @@ -1,5 +1,6 @@ -use std::{borrow::Cow, convert::TryFrom}; +use std::convert::TryFrom; +use crate::file::cow_str; use git_config::{lookup, File}; #[test] @@ -16,7 +17,7 @@ fn multi_value_in_section() { let config = File::try_from("[core]\na=b\na=c").unwrap(); assert_eq!( config.raw_multi_value("core", None, "a").unwrap(), - vec![Cow::Borrowed(b"b"), Cow::Borrowed(b"c")] + vec![cow_str("b"), cow_str("c")] ); } @@ -25,7 +26,7 @@ fn multi_value_across_sections() { let config = File::try_from("[core]\na=b\na=c\n[core]a=d").unwrap(); assert_eq!( config.raw_multi_value("core", None, "a").unwrap(), - vec![Cow::Borrowed(b"b"), Cow::Borrowed(b"c"), Cow::Borrowed(b"d")] + vec![cow_str("b"), cow_str("c"), cow_str("d")] ); } @@ -59,13 +60,10 @@ fn key_not_found() { #[test] fn subsection_must_be_respected() { let config = File::try_from("[core]a=b\n[core.a]a=c").unwrap(); - assert_eq!( - config.raw_multi_value("core", None, "a").unwrap(), - vec![Cow::Borrowed(b"b")] - ); + assert_eq!(config.raw_multi_value("core", None, "a").unwrap(), vec![cow_str("b")]); assert_eq!( config.raw_multi_value("core", Some("a"), "a").unwrap(), - vec![Cow::Borrowed(b"c")] + vec![cow_str("c")] ); } @@ -74,6 +72,6 @@ fn non_relevant_subsection_is_ignored() { let config = File::try_from("[core]\na=b\na=c\n[core]a=d\n[core]g=g").unwrap(); assert_eq!( config.raw_multi_value("core", None, "a").unwrap(), - vec![Cow::Borrowed(b"b"), Cow::Borrowed(b"c"), Cow::Borrowed(b"d")] + vec![cow_str("b"), cow_str("c"), cow_str("d")] ); } diff --git a/git-config/tests/file/access/read_only.rs b/git-config/tests/file/access/read_only.rs index 9e16eabd645..28b13001489 100644 --- a/git-config/tests/file/access/read_only.rs +++ b/git-config/tests/file/access/read_only.rs @@ -1,3 +1,5 @@ +use crate::file::cow_str; +use bstr::BStr; use git_config::{ values::{Boolean, Bytes, TrueVariant, *}, File, @@ -24,7 +26,7 @@ fn get_value_for_all_provided_values() -> crate::Result { assert_eq!( file.value::("core", None, "bool-explicit")?, - Boolean::False(Cow::Borrowed("false")) + Boolean::False(Cow::Borrowed("false".into())) ); assert!(!file.boolean("core", None, "bool-explicit").expect("exists")?); @@ -75,15 +77,13 @@ fn get_value_for_all_provided_values() -> crate::Result { ); assert_eq!( - file.value::("core", None, "other")?, - Bytes { - value: Cow::Borrowed(b"hello world") - } + file.value::>("core", None, "other")?, + cow_str("hello world") ); assert_eq!( file.value::("core", None, "other-quoted")?, String { - value: Cow::Borrowed("hello world".into()) + value: cow_str("hello world") } ); @@ -131,7 +131,7 @@ fn get_value_looks_up_all_sections_before_failing() -> crate::Result { assert_eq!( file.value::("core", None, "bool-explicit")?, - Boolean::False(Cow::Borrowed("false")) + Boolean::False(cow_str("false")) ); Ok(()) @@ -170,12 +170,7 @@ fn single_section() -> Result<(), Box> { let first_value: Bytes = config.value("core", None, "a")?; let second_value: Boolean = config.value("core", None, "c")?; - assert_eq!( - first_value, - Bytes { - value: Cow::Borrowed(b"b") - } - ); + assert_eq!(first_value, Bytes { value: cow_str("b") }); assert_eq!(second_value, Boolean::True(TrueVariant::Implicit)); Ok(()) @@ -199,7 +194,7 @@ fn sections_by_name() { assert_eq!( value, Bytes { - value: Cow::Borrowed(b"git@github.com:Byron/gitoxide.git") + value: cow_str("git@github.com:Byron/gitoxide.git") } ); } diff --git a/git-config/tests/parser/mod.rs b/git-config/tests/parser/mod.rs index d847bb1696f..23c5425bf36 100644 --- a/git-config/tests/parser/mod.rs +++ b/git-config/tests/parser/mod.rs @@ -10,12 +10,12 @@ pub fn section_header( name: &str, subsection: impl Into>, ) -> ParsedSectionHeader<'_> { - let name = SectionHeaderName(Cow::Borrowed(name)); + let name = SectionHeaderName(Cow::Borrowed(name.into())); if let Some((separator, subsection_name)) = subsection.into() { ParsedSectionHeader { name, - separator: Some(Cow::Borrowed(separator)), - subsection_name: Some(Cow::Borrowed(subsection_name)), + separator: Some(Cow::Borrowed(separator.into())), + subsection_name: Some(Cow::Borrowed(subsection_name.into())), } } else { ParsedSectionHeader { @@ -27,11 +27,11 @@ pub fn section_header( } fn name(name: &'static str) -> Event<'static> { - Event::Key(Key(Cow::Borrowed(name))) + Event::Key(Key(Cow::Borrowed(name.into()))) } fn value(value: &'static str) -> Event<'static> { - Event::Value(Cow::Borrowed(value.as_bytes())) + Event::Value(Cow::Borrowed(value.into())) } fn newline() -> Event<'static> { @@ -39,11 +39,11 @@ fn newline() -> Event<'static> { } fn newline_custom(value: &'static str) -> Event<'static> { - Event::Newline(Cow::Borrowed(value)) + Event::Newline(Cow::Borrowed(value.into())) } fn whitespace(value: &'static str) -> Event<'static> { - Event::Whitespace(Cow::Borrowed(value)) + Event::Whitespace(Cow::Borrowed(value.into())) } fn separator() -> Event<'static> { diff --git a/git-config/tests/values/boolean.rs b/git-config/tests/values/boolean.rs index 778677dfffc..8129ab06793 100644 --- a/git-config/tests/values/boolean.rs +++ b/git-config/tests/values/boolean.rs @@ -1,35 +1,36 @@ use std::convert::TryFrom; +use crate::file::cow_str; use git_config::values::{Boolean, TrueVariant}; use crate::values::b; #[test] fn from_str_false() { - assert_eq!(Boolean::try_from(b("no")), Ok(Boolean::False("no".into()))); - assert_eq!(Boolean::try_from(b("off")), Ok(Boolean::False("off".into()))); - assert_eq!(Boolean::try_from(b("false")), Ok(Boolean::False("false".into()))); - assert_eq!(Boolean::try_from(b("zero")), Ok(Boolean::False("zero".into()))); - assert_eq!(Boolean::try_from(b("\"\"")), Ok(Boolean::False("\"\"".into()))); + assert_eq!(Boolean::try_from(b("no")), Ok(Boolean::False(cow_str("no")))); + assert_eq!(Boolean::try_from(b("off")), Ok(Boolean::False(cow_str("off")))); + assert_eq!(Boolean::try_from(b("false")), Ok(Boolean::False(cow_str("false")))); + assert_eq!(Boolean::try_from(b("zero")), Ok(Boolean::False(cow_str("zero")))); + assert_eq!(Boolean::try_from(b("\"\"")), Ok(Boolean::False(cow_str("\"\"")))); } #[test] fn from_str_true() { assert_eq!( Boolean::try_from(b("yes")), - Ok(Boolean::True(TrueVariant::Explicit("yes".into()))) + Ok(Boolean::True(TrueVariant::Explicit(cow_str("yes")))) ); assert_eq!( Boolean::try_from(b("on")), - Ok(Boolean::True(TrueVariant::Explicit("on".into()))) + Ok(Boolean::True(TrueVariant::Explicit(cow_str("on")))) ); assert_eq!( Boolean::try_from(b("true")), - Ok(Boolean::True(TrueVariant::Explicit("true".into()))) + Ok(Boolean::True(TrueVariant::Explicit(cow_str("true")))) ); assert_eq!( Boolean::try_from(b("one")), - Ok(Boolean::True(TrueVariant::Explicit("one".into()))) + Ok(Boolean::True(TrueVariant::Explicit(cow_str("one")))) ); } diff --git a/git-config/tests/values/mod.rs b/git-config/tests/values/mod.rs index ed6d4a63444..c73ba9769ea 100644 --- a/git-config/tests/values/mod.rs +++ b/git-config/tests/values/mod.rs @@ -1,6 +1,8 @@ -/// Converts string to byte slice -fn b(s: &str) -> &[u8] { - s.as_bytes() +use bstr::BStr; + +/// Converts string to a bstr +fn b(s: &str) -> &BStr { + s.into() } mod normalize; diff --git a/git-config/tests/values/normalize.rs b/git-config/tests/values/normalize.rs index b0a42eb6002..febe5d5b806 100644 --- a/git-config/tests/values/normalize.rs +++ b/git-config/tests/values/normalize.rs @@ -1,52 +1,45 @@ -use std::borrow::Cow; - -use git_config::values::normalize_str; +use crate::file::cow_str; +use git_config::values::normalize_bstr; #[test] fn not_modified_is_borrowed() { - assert_eq!(normalize_str("hello world"), Cow::Borrowed(b"hello world")); + assert_eq!(normalize_bstr("hello world"), cow_str("hello world")); } #[test] fn modified_is_owned() { - assert_eq!( - normalize_str("hello \"world\""), - Cow::<[u8]>::Owned(b"hello world".to_vec()) - ); + assert_eq!(normalize_bstr("hello \"world\""), cow_str("hello world").to_owned()); } #[test] fn all_quoted_is_optimized() { - assert_eq!(normalize_str("\"hello world\""), Cow::Borrowed(b"hello world")); + assert_eq!(normalize_bstr("\"hello world\""), cow_str("hello world")); } #[test] fn all_quote_optimization_is_correct() { - assert_eq!(normalize_str(r#""hello" world\""#), Cow::Borrowed(b"hello world\"")); + assert_eq!(normalize_bstr(r#""hello" world\""#), cow_str("hello world\"")); } #[test] fn quotes_right_next_to_each_other() { - assert_eq!( - normalize_str("\"hello\"\" world\""), - Cow::<[u8]>::Owned(b"hello world".to_vec()) - ); + assert_eq!(normalize_bstr("\"hello\"\" world\""), cow_str("hello world").to_owned()); } #[test] fn escaped_quotes_are_kept() { assert_eq!( - normalize_str(r#""hello \"\" world""#), - Cow::<[u8]>::Owned(b"hello \"\" world".to_vec()) + normalize_bstr(r#""hello \"\" world""#), + cow_str("hello \"\" world").to_owned(), ); } #[test] fn empty_string() { - assert_eq!(normalize_str(""), Cow::Borrowed(b"")); + assert_eq!(normalize_bstr(""), cow_str("")); } #[test] fn empty_normalized_string_is_optimized() { - assert_eq!(normalize_str("\"\""), Cow::Borrowed(b"")); + assert_eq!(normalize_bstr("\"\""), cow_str("")); } diff --git a/git-config/tests/values/path.rs b/git-config/tests/values/path.rs index ac94f82ec1f..7bbc2f37fc0 100644 --- a/git-config/tests/values/path.rs +++ b/git-config/tests/values/path.rs @@ -4,6 +4,7 @@ mod interpolate { use git_config::values::Path as InterpolatingPath; + use crate::file::cow_str; use crate::values::b; use git_config::values::path::interpolate::Error; @@ -31,14 +32,11 @@ mod interpolate { #[test] fn prefix_substitutes_git_install_dir() { for git_install_dir in &["/tmp/git", "C:\\git"] { - for (val, expected) in &[ - (&b"%(prefix)/foo/bar"[..], "foo/bar"), - (b"%(prefix)/foo\\bar", "foo\\bar"), - ] { + for (val, expected) in &[("%(prefix)/foo/bar", "foo/bar"), ("%(prefix)/foo\\bar", "foo\\bar")] { let expected = - &std::path::PathBuf::from(format!("{}{}{}", git_install_dir, std::path::MAIN_SEPARATOR, expected)); + std::path::PathBuf::from(format!("{}{}{}", git_install_dir, std::path::MAIN_SEPARATOR, expected)); assert_eq!( - &*InterpolatingPath::from(Cow::Borrowed(*val)) + InterpolatingPath::from(cow_str(val)) .interpolate(Path::new(git_install_dir).into(), None) .unwrap(), expected, @@ -68,11 +66,11 @@ mod interpolate { #[test] fn tilde_slash_substitutes_current_user() { - let path = &b"~/foo/bar"[..]; + let path = "~/foo/bar"; let home = std::env::current_dir().unwrap(); let expected = format!("{}{}foo/bar", home.display(), std::path::MAIN_SEPARATOR); assert_eq!( - InterpolatingPath::from(Cow::Borrowed(path)) + InterpolatingPath::from(cow_str(path)) .interpolate(None, Some(&home)) .unwrap() .as_ref(), diff --git a/git-repository/src/config.rs b/git-repository/src/config.rs index e80ac211b3a..9c8644fa1fa 100644 --- a/git-repository/src/config.rs +++ b/git-repository/src/config.rs @@ -81,15 +81,15 @@ mod cache { let object_hash = (repo_format_version != 1) .then(|| Ok(git_hash::Kind::Sha1)) .or_else(|| { - config - .raw_value("extensions", None, "objectFormat") - .ok() - .map(|format| match format.as_ref() { - b"sha1" => Ok(git_hash::Kind::Sha1), - _ => Err(Error::UnsupportedObjectFormat { + config.string("extensions", None, "objectFormat").map(|format| { + if format.as_ref() == "sha1" { + Ok(git_hash::Kind::Sha1) + } else { + Err(Error::UnsupportedObjectFormat { name: format.to_vec().into(), - }), - }) + }) + } + }) }) .transpose()? .unwrap_or(git_hash::Kind::Sha1); @@ -100,7 +100,7 @@ mod cache { return Err(Error::EmptyValue { key: "core.abbrev" }); } if hex_len_str.as_ref() != "auto" { - let value_bytes = hex_len_str.as_ref().as_ref(); + let value_bytes = hex_len_str.as_ref(); if let Ok(Boolean::False(_)) = Boolean::try_from(value_bytes) { hex_len = object_hash.len_in_hex().into(); } else { diff --git a/git-repository/src/lib.rs b/git-repository/src/lib.rs index 60646b17488..bbc7cc663a8 100644 --- a/git-repository/src/lib.rs +++ b/git-repository/src/lib.rs @@ -380,6 +380,7 @@ pub mod state { pub mod discover { use std::path::Path; + use crate::bstr::BString; pub use git_discover::*; use crate::ThreadSafeRepository; @@ -440,8 +441,8 @@ pub mod discover { use crate::bstr::ByteVec; - if let Some(cross_fs) = - std::env::var_os("GIT_DISCOVERY_ACROSS_FILESYSTEM").and_then(|v| Vec::from_os_string(v).ok()) + if let Some(cross_fs) = std::env::var_os("GIT_DISCOVERY_ACROSS_FILESYSTEM") + .and_then(|v| Vec::from_os_string(v).ok().map(BString::from)) { if let Ok(b) = git_config::values::Boolean::try_from(cross_fs) { opts.cross_fs = b.to_bool(); From aa630ad6ec2c6306d3307d5c77e272cb24b00ddd Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 4 Jul 2022 17:05:33 +0800 Subject: [PATCH 005/366] change!: remove `values::Bytes` - use `values::String` instead. Note that these values are always normalized and it's only possible to get a raw values using the `raw_value()` API. --- .../src/file/access/low_level/read_only.rs | 6 ++-- git-config/src/values.rs | 31 ------------------- git-config/tests/file/access/read_only.rs | 10 +++--- 3 files changed, 8 insertions(+), 39 deletions(-) diff --git a/git-config/src/file/access/low_level/read_only.rs b/git-config/src/file/access/low_level/read_only.rs index 5c35decab4a..d352d0724a3 100644 --- a/git-config/src/file/access/low_level/read_only.rs +++ b/git-config/src/file/access/low_level/read_only.rs @@ -76,7 +76,7 @@ impl<'a> File<'a> { /// /// ``` /// # use git_config::File; - /// # use git_config::values::{Integer, Bytes, Boolean, TrueVariant}; + /// # use git_config::values::{Integer, String, Boolean, TrueVariant}; /// # use std::borrow::Cow; /// # use std::convert::TryFrom; /// let config = r#" @@ -99,8 +99,8 @@ impl<'a> File<'a> { /// ] /// ); /// // ... or explicitly declare the type to avoid the turbofish - /// let c_value: Vec = git_config.multi_value("core", None, "c")?; - /// assert_eq!(c_value, vec![Bytes { value: Cow::Borrowed("g".into()) }]); + /// let c_value: Vec = git_config.multi_value("core", None, "c")?; + /// assert_eq!(c_value, vec![String { value: Cow::Borrowed("g".into()) }]); /// # Ok::<(), Box>(()) /// ``` /// diff --git a/git-config/src/values.rs b/git-config/src/values.rs index fa108c6c418..cc415ce772a 100644 --- a/git-config/src/values.rs +++ b/git-config/src/values.rs @@ -136,37 +136,6 @@ pub fn normalize_bstring(input: impl Into) -> Cow<'static, BStr> { normalize_cow(Cow::Owned(input.into())) } -// TODO: remove bytes -/// Any string value -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] -pub struct Bytes<'a> { - /// bytes - pub value: Cow<'a, BStr>, -} - -impl<'a> From<&'a BStr> for Bytes<'a> { - fn from(s: &'a BStr) -> Self { - Self { - value: Cow::Borrowed(s), - } - } -} - -impl From for Bytes<'_> { - fn from(s: BString) -> Self { - Self { value: Cow::Owned(s) } - } -} - -impl<'a> From> for Bytes<'a> { - fn from(c: Cow<'a, BStr>) -> Self { - match c { - Cow::Borrowed(c) => Self::from(c), - Cow::Owned(c) => Self::from(c), - } - } -} - /// Any string value #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] pub struct String<'a> { diff --git a/git-config/tests/file/access/read_only.rs b/git-config/tests/file/access/read_only.rs index 28b13001489..392b240f125 100644 --- a/git-config/tests/file/access/read_only.rs +++ b/git-config/tests/file/access/read_only.rs @@ -1,7 +1,7 @@ use crate::file::cow_str; use bstr::BStr; use git_config::{ - values::{Boolean, Bytes, TrueVariant, *}, + values::{Boolean, TrueVariant, *}, File, }; use std::{borrow::Cow, convert::TryFrom, error::Error}; @@ -167,10 +167,10 @@ fn value_names_are_case_insensitive() -> crate::Result { #[test] fn single_section() -> Result<(), Box> { let config = File::try_from("[core]\na=b\nc").unwrap(); - let first_value: Bytes = config.value("core", None, "a")?; + let first_value: String = config.value("core", None, "a")?; let second_value: Boolean = config.value("core", None, "c")?; - assert_eq!(first_value, Bytes { value: cow_str("b") }); + assert_eq!(first_value, String { value: cow_str("b") }); assert_eq!(second_value, Boolean::True(TrueVariant::Implicit)); Ok(()) @@ -190,10 +190,10 @@ fn sections_by_name() { "#; let config = File::try_from(config).unwrap(); - let value = config.value::("remote", Some("origin"), "url").unwrap(); + let value = config.value::("remote", Some("origin"), "url").unwrap(); assert_eq!( value, - Bytes { + String { value: cow_str("git@github.com:Byron/gitoxide.git") } ); From 9b6a67bf369fcf51c6a3289784c3ef8ab366bee7 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 4 Jul 2022 17:07:43 +0800 Subject: [PATCH 006/366] thanks clippy --- git-config/src/file/access/comfort.rs | 4 ++-- git-config/src/file/access/raw.rs | 2 +- git-config/src/file/from_env.rs | 2 +- git-config/src/file/resolve_includes.rs | 2 +- git-config/src/values.rs | 11 +++-------- 5 files changed, 8 insertions(+), 13 deletions(-) diff --git a/git-config/src/file/access/comfort.rs b/git-config/src/file/access/comfort.rs index eef6efb8b1a..92aca916cd4 100644 --- a/git-config/src/file/access/comfort.rs +++ b/git-config/src/file/access/comfort.rs @@ -13,7 +13,7 @@ impl<'a> File<'a> { pub fn string(&'a self, section_name: &str, subsection_name: Option<&str>, key: &str) -> Option> { self.raw_value(section_name, subsection_name, key) .ok() - .map(|v| normalize_cow(v)) + .map(normalize_cow) } /// Like [`value()`][File::value()], but returning an `Option` if the path wasn't found. @@ -61,7 +61,7 @@ impl<'a> File<'a> { pub fn strings(&self, section_name: &str, subsection_name: Option<&str>, key: &str) -> Option>> { self.raw_multi_value(section_name, subsection_name, key) .ok() - .map(|values| values.into_iter().map(|v| normalize_cow(v)).collect()) + .map(|values| values.into_iter().map(normalize_cow).collect()) } /// Similar to [`multi_value(…)`][File::multi_value()] but returning integers if at least one of them was found diff --git a/git-config/src/file/access/raw.rs b/git-config/src/file/access/raw.rs index a99ab1433ce..12007d5dd82 100644 --- a/git-config/src/file/access/raw.rs +++ b/git-config/src/file/access/raw.rs @@ -173,7 +173,7 @@ impl<'a> File<'a> { .expect("sections does not have section id from section ids") .values(&Key(Cow::::Borrowed(key.into()))) .iter() - .map(|v| v.clone()), + .cloned(), ); } diff --git a/git-config/src/file/from_env.rs b/git-config/src/file/from_env.rs index d3fd539253a..b4584f36150 100644 --- a/git-config/src/file/from_env.rs +++ b/git-config/src/file/from_env.rs @@ -116,7 +116,7 @@ impl<'a> File<'a> { section.push( parser::Key(Cow::Owned(BString::from(key))), - Cow::Owned(git_path::into_bstr(PathBuf::from(value)).into_owned().into()), + Cow::Owned(git_path::into_bstr(PathBuf::from(value)).into_owned()), ); } else { return Err(Error::InvalidKeyValue { diff --git a/git-config/src/file/resolve_includes.rs b/git-config/src/file/resolve_includes.rs index 1a95e4b5614..9145a636f63 100644 --- a/git-config/src/file/resolve_includes.rs +++ b/git-config/src/file/resolve_includes.rs @@ -137,7 +137,7 @@ fn onbranch_matches(condition: &BStr, options: Options<'_>) -> Option<()> { }; git_glob::wildmatch( - condition.as_ref().into(), + condition.as_ref(), branch_name, git_glob::wildmatch::Mode::NO_MATCH_SLASH_LITERAL, ) diff --git a/git-config/src/values.rs b/git-config/src/values.rs index cc415ce772a..64d0a57bdc7 100644 --- a/git-config/src/values.rs +++ b/git-config/src/values.rs @@ -305,12 +305,7 @@ impl<'a> AsRef for Path<'a> { impl<'a> From> for Path<'a> { fn from(value: Cow<'a, BStr>) -> Self { - Path { - value: match value { - Cow::Borrowed(v) => Cow::Borrowed(v.into()), - Cow::Owned(v) => Cow::Owned(v.into()), - }, - } + Path { value } } } @@ -382,7 +377,7 @@ impl TryFrom for Boolean<'_> { || value.eq_ignore_ascii_case(b"zero") || value == "\"\"" { - return Ok(Self::False(Cow::Owned(value.into()))); + return Ok(Self::False(Cow::Owned(value))); } TrueVariant::try_from(value).map(Self::True) @@ -489,7 +484,7 @@ impl TryFrom for TrueVariant<'_> { || value.eq_ignore_ascii_case(b"true") || value.eq_ignore_ascii_case(b"one") { - Ok(Self::Explicit(Cow::Owned(value.into()))) + Ok(Self::Explicit(Cow::Owned(value))) } else if value.is_empty() { Ok(Self::Implicit) } else { From ffc4a85b9a914b685d7ab528b30f2a3eefb44094 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 4 Jul 2022 17:16:03 +0800 Subject: [PATCH 007/366] change!: `From<&[u8]>` is now `From<&BStr>` This better represents the meaning of the input, and simplifies interactions with `git-config`. --- git-url/src/lib.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/git-url/src/lib.rs b/git-url/src/lib.rs index 7335d60dcf0..309a50bd8e0 100644 --- a/git-url/src/lib.rs +++ b/git-url/src/lib.rs @@ -7,7 +7,7 @@ use std::{ fmt::{self, Write}, }; -use bstr::ByteSlice; +use bstr::{BStr, ByteSlice}; /// pub mod parse; @@ -101,18 +101,18 @@ impl Url { } } -impl TryFrom<&[u8]> for Url { +impl TryFrom<&BStr> for Url { type Error = parse::Error; - fn try_from(value: &[u8]) -> Result { + fn try_from(value: &BStr) -> Result { Self::from_bytes(value) } } -impl<'a> TryFrom> for Url { +impl<'a> TryFrom> for Url { type Error = parse::Error; - fn try_from(value: std::borrow::Cow<'a, [u8]>) -> Result { + fn try_from(value: std::borrow::Cow<'a, BStr>) -> Result { Self::try_from(&*value) } } From 1f0242034071ce317743df75cc685e7428b604b0 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 4 Jul 2022 17:16:52 +0800 Subject: [PATCH 008/366] fix build after changes to `git-url` and `git-config` --- cargo-smart-release/src/git/mod.rs | 2 +- cargo-smart-release/tests/changelog/write_and_parse/mod.rs | 6 +++--- git-config/src/file/impls.rs | 6 +++--- git-config/src/values.rs | 4 ++-- gitoxide-core/src/organize.rs | 5 ++++- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/cargo-smart-release/src/git/mod.rs b/cargo-smart-release/src/git/mod.rs index 07e80c78ab8..0f35572d888 100644 --- a/cargo-smart-release/src/git/mod.rs +++ b/cargo-smart-release/src/git/mod.rs @@ -106,7 +106,7 @@ pub fn remote_url() -> anyhow::Result> { output .status .success() - .then(|| output.stdout.as_slice().try_into().map_err(Into::into)) + .then(|| output.stdout.as_bstr().try_into().map_err(Into::into)) .transpose() } diff --git a/cargo-smart-release/tests/changelog/write_and_parse/mod.rs b/cargo-smart-release/tests/changelog/write_and_parse/mod.rs index d9bfd04a706..daca8d6b489 100644 --- a/cargo-smart-release/tests/changelog/write_and_parse/mod.rs +++ b/cargo-smart-release/tests/changelog/write_and_parse/mod.rs @@ -5,8 +5,8 @@ use cargo_smart_release::{ changelog::{section, section::segment::conventional, Section}, ChangeLog, }; +use git_testtools::bstr::ByteSlice; use git_testtools::hex_to_id; -use nom::AsBytes; use crate::Result; @@ -54,7 +54,7 @@ fn conventional_write_empty_messages() -> Result { for link_mode in &[ changelog::write::Linkables::AsText, changelog::write::Linkables::AsLinks { - repository_url: git_repository::Url::try_from(b"https://github.com/user/repo.git".as_bytes())?.into(), + repository_url: git_repository::Url::try_from(b"https://github.com/user/repo.git".as_bstr())?.into(), }, ] { let log = log.clone(); @@ -160,7 +160,7 @@ fn all_section_types_round_trips_lossy() -> Result { for link_mode in &[ changelog::write::Linkables::AsText, changelog::write::Linkables::AsLinks { - repository_url: git_repository::Url::try_from(b"https://github.com/user/repo".as_bytes())?.into(), + repository_url: git_repository::Url::try_from(b"https://github.com/user/repo".as_bstr())?.into(), }, ] { // NOTE: we can't run this a second time as the statistical information will be gone (it was never parsed back) diff --git a/git-config/src/file/impls.rs b/git-config/src/file/impls.rs index 9655a9cffaa..0d51d906f2e 100644 --- a/git-config/src/file/impls.rs +++ b/git-config/src/file/impls.rs @@ -31,15 +31,15 @@ impl<'a> TryFrom<&'a [u8]> for File<'a> { } } -impl<'a> TryFrom<&'a Vec> for File<'a> { +impl<'a> TryFrom<&'a BString> for File<'a> { type Error = Error<'a>; /// Convenience constructor. Attempts to parse the provided byte string into //// a [`File`]. See [`parse_from_bytes`] for more information. /// /// [`parse_from_bytes`]: crate::parser::parse_from_bytes - fn try_from(value: &'a Vec) -> Result, Self::Error> { - parse_from_bytes(value).map(File::from) + fn try_from(value: &'a BString) -> Result, Self::Error> { + parse_from_bytes(value.as_ref()).map(File::from) } } diff --git a/git-config/src/values.rs b/git-config/src/values.rs index 64d0a57bdc7..70165b0ddca 100644 --- a/git-config/src/values.rs +++ b/git-config/src/values.rs @@ -725,10 +725,10 @@ impl TryFrom<&[u8]> for IntegerSuffix { } } -impl TryFrom> for IntegerSuffix { +impl TryFrom for IntegerSuffix { type Error = (); - fn try_from(value: Vec) -> Result { + fn try_from(value: BString) -> Result { Self::try_from(value.as_ref()) } } diff --git a/gitoxide-core/src/organize.rs b/gitoxide-core/src/organize.rs index 91824c85dc0..21ddf1a37a0 100644 --- a/gitoxide-core/src/organize.rs +++ b/gitoxide-core/src/organize.rs @@ -102,7 +102,10 @@ where fn find_origin_remote(repo: &Path) -> anyhow::Result> { let non_bare = repo.join(".git").join("config"); let config = File::at(non_bare.as_path()).or_else(|_| File::at(repo.join("config").as_path()))?; - Ok(config.value("remote", Some("origin"), "url").ok()) + Ok(config + .string("remote", Some("origin"), "url") + .map(|url| git_url::Url::from_bytes(url.as_ref())) + .transpose()?) } fn handle( From 6f4f325a42656800c8c76c8eae075508c31657be Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 4 Jul 2022 19:02:59 +0800 Subject: [PATCH 009/366] Allow backslashes in subsections This is valid actually and git doesn't choke on it either. --- git-config/src/parser.rs | 2 +- .../tests/file/from_paths/includes/conditional/gitdir/mod.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/git-config/src/parser.rs b/git-config/src/parser.rs index 5e13066978d..70b2b7e0834 100644 --- a/git-config/src/parser.rs +++ b/git-config/src/parser.rs @@ -1131,7 +1131,7 @@ fn section_header(i: &[u8]) -> IResult<&[u8], ParsedSectionHeader<'_>> { let (i, whitespace) = take_spaces(i)?; let (i, subsection_name) = delimited( char('"'), - opt(escaped(none_of("\"\\\n\0"), '\\', one_of(r#""\"#))), + opt(escaped(none_of("\"\n\0"), '\\', one_of(r#""\"#))), tag("\"]"), )(i)?; diff --git a/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs b/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs index 498a929375e..459b99bb6f4 100644 --- a/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs +++ b/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs @@ -113,7 +113,6 @@ fn pattern_with_escaped_backslash() -> crate::Result { } #[test] -#[ignore] fn pattern_with_backslash() -> crate::Result { assert_section_value(Condition::new(r#"gitdir:work\tree/"#), GitEnv::repo_name("worktree")?) } From b3a752a0a873cf9d685e1893c8d35255d7f7323a Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 4 Jul 2022 19:50:43 +0800 Subject: [PATCH 010/366] Only copy pattern if required (#331) --- git-config/src/file/resolve_includes.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/git-config/src/file/resolve_includes.rs b/git-config/src/file/resolve_includes.rs index 9145a636f63..ccacbe75f09 100644 --- a/git-config/src/file/resolve_includes.rs +++ b/git-config/src/file/resolve_includes.rs @@ -158,10 +158,9 @@ fn gitdir_matches( let git_dir = git_path::to_unix_separators_on_windows(git_path::into_bstr(git_dir.ok_or(from_paths::Error::MissingGitDir)?)); - let mut pattern_path = { - let cow = Cow::Borrowed(condition_path); - let path = values::Path::from(cow).interpolate(git_install_dir, home_dir)?; - git_path::into_bstr(path).into_owned() + let mut pattern_path: Cow<'_, _> = { + let path = values::Path::from(Cow::Borrowed(condition_path)).interpolate(git_install_dir, home_dir)?; + git_path::into_bstr(path).into_owned().into() }; if let Some(relative_pattern_path) = pattern_path.strip_prefix(b"./") { @@ -172,21 +171,24 @@ fn gitdir_matches( let mut joined_path = git_path::to_unix_separators_on_windows(git_path::into_bstr(parent_dir)).into_owned(); joined_path.push(b'/'); joined_path.extend_from_slice(relative_pattern_path); - pattern_path = joined_path; + pattern_path = joined_path.into(); } if ["~/", "./", "/"] .iter() .all(|prefix| !pattern_path.starts_with(prefix.as_bytes())) { - let v = bstr::concat(&[&b"**/"[..], &pattern_path]); - pattern_path = BString::from(v); + let mut prefixed = pattern_path.into_owned(); + prefixed.insert_str(0, "**/"); + pattern_path = prefixed.into() } if pattern_path.ends_with(b"/") { - pattern_path.push_str("**"); + let mut suffixed = pattern_path.into_owned(); + suffixed.push_str("**"); + pattern_path = suffixed.into(); } - pattern_path = git_path::to_unix_separators_on_windows(pattern_path).into_owned(); + pattern_path = git_path::to_unix_separators_on_windows(pattern_path); let match_mode = git_glob::wildmatch::Mode::NO_MATCH_SLASH_LITERAL | wildmatch_mode; let is_match = git_glob::wildmatch(pattern_path.as_bstr(), git_dir.as_bstr(), match_mode); From 6366148f538ee03314dd866e083157de810d4ad4 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 4 Jul 2022 20:49:51 +0800 Subject: [PATCH 011/366] conforming subsection parsing handling backslashes like git (#331) But with significantly more (not to say: all) error handling. To me it seems that the git parser can get into a loop there. --- git-config/src/parser.rs | 61 +++++++++++++++---- .../includes/conditional/gitdir/mod.rs | 1 - 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/git-config/src/parser.rs b/git-config/src/parser.rs index 70b2b7e0834..486e33dba9f 100644 --- a/git-config/src/parser.rs +++ b/git-config/src/parser.rs @@ -14,9 +14,9 @@ use std::{borrow::Cow, convert::TryFrom, fmt::Display, hash::Hash, io::Read, ite use nom::{ branch::alt, - bytes::complete::{escaped, tag, take_till, take_while}, + bytes::complete::{tag, take_till, take_while}, character::{ - complete::{char, none_of, one_of}, + complete::{char, one_of}, is_space, }, combinator::{map, opt}, @@ -1129,24 +1129,63 @@ fn section_header(i: &[u8]) -> IResult<&[u8], ParsedSectionHeader<'_>> { // Section header must be using modern subsection syntax at this point. let (i, whitespace) = take_spaces(i)?; - let (i, subsection_name) = delimited( - char('"'), - opt(escaped(none_of("\"\n\0"), '\\', one_of(r#""\"#))), - tag("\"]"), - )(i)?; - - let subsection_name = subsection_name.map(|s| s.as_bstr()); + let (i, subsection_name) = delimited(char('"'), opt(sub_section), tag("\"]"))(i)?; Ok(( i, ParsedSectionHeader { name: SectionHeaderName(Cow::Borrowed(name)), separator: Some(Cow::Borrowed(whitespace)), - subsection_name: Cow::Borrowed(subsection_name.unwrap_or_default()).into(), + subsection_name: subsection_name.map(Cow::Owned), }, )) } +fn sub_section(i: &[u8]) -> IResult<&[u8], BString> { + let mut cursor = 0; + let mut bytes = i.iter().copied(); + let mut found_terminator = false; + let mut buf = BString::default(); + while let Some(mut b) = bytes.next() { + cursor += 1; + if b == b'\n' { + return Err(nom::Err::Error(NomError { + input: &i[cursor..], + code: ErrorKind::NonEmpty, + })); + } + if b == b'"' { + found_terminator = true; + break; + } + if b == b'\\' { + b = bytes.next().ok_or_else(|| { + nom::Err::Error(NomError { + input: &i[cursor..], + code: ErrorKind::NonEmpty, + }) + })?; + cursor += 1; + if b == b'\n' { + return Err(nom::Err::Error(NomError { + input: &i[cursor..], + code: ErrorKind::NonEmpty, + })); + } + } + buf.push_byte(b); + } + + if !found_terminator { + return Err(nom::Err::Error(NomError { + input: &i[cursor..], + code: ErrorKind::NonEmpty, + })); + } + + Ok((&i[cursor - 1..], buf)) +} + fn section_body<'a, 'b, 'c>( i: &'a [u8], node: &'b mut ParserNode, @@ -1409,7 +1448,7 @@ mod section_headers { fn escaped_subsection() { assert_eq!( section_header(br#"[hello "foo\\bar\""]"#).unwrap(), - fully_consumed(parsed_section_header("hello", (" ", r#"foo\\bar\""#))), + fully_consumed(parsed_section_header("hello", (" ", r#"foo\bar""#))), ); } diff --git a/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs b/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs index 459b99bb6f4..d7c29340164 100644 --- a/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs +++ b/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs @@ -104,7 +104,6 @@ fn case_insensitive_matches_any_case() -> crate::Result { } #[test] -#[ignore] fn pattern_with_escaped_backslash() -> crate::Result { assert_section_value( Condition::new(r#"gitdir:\\work\\tree\\/"#), From d76aee22498cb980ab0b53295a2e51af04a8cb7c Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 4 Jul 2022 20:55:08 +0800 Subject: [PATCH 012/366] refactor (#331) --- git-config/src/parser.rs | 18 +++++++++--------- .../includes/conditional/gitdir/mod.rs | 3 ++- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/git-config/src/parser.rs b/git-config/src/parser.rs index 486e33dba9f..92f52177573 100644 --- a/git-config/src/parser.rs +++ b/git-config/src/parser.rs @@ -1039,7 +1039,7 @@ fn comment(i: &[u8]) -> IResult<&[u8], ParsedComment<'_>> { Ok(( i, ParsedComment { - comment_tag: comment_tag as u8, // TODO: don't use character based nom functions + comment_tag: comment_tag as u8, comment: Cow::Borrowed(comment.as_bstr()), }, )) @@ -1217,14 +1217,14 @@ fn config_name(i: &[u8]) -> IResult<&[u8], &BStr> { })); } - if !(i[0] as char).is_alphabetic() { + if !i[0].is_ascii_alphabetic() { return Err(nom::Err::Error(NomError { input: i, code: ErrorKind::Alpha, })); } - let (i, v) = take_while(|c: u8| (c as char).is_alphanumeric() || c == b'-')(i)?; + let (i, v) = take_while(|c: u8| c.is_ascii_alphanumeric() || c == b'-')(i)?; Ok((i, v.as_bstr())) } @@ -1330,7 +1330,7 @@ fn value_impl<'a, 'b>(i: &'a [u8], events: &'b mut Vec>) -> IResult<&' let (i, remainder_value) = { let mut new_index = parsed_index; for index in (offset..parsed_index).rev() { - if !(i[index] as char).is_whitespace() { + if !i[index].is_ascii_whitespace() { new_index = index + 1; break; } @@ -1348,7 +1348,7 @@ fn value_impl<'a, 'b>(i: &'a [u8], events: &'b mut Vec>) -> IResult<&' } fn take_spaces(i: &[u8]) -> IResult<&[u8], &BStr> { - let (i, v) = take_while(|c| (c as char).is_ascii() && is_space(c))(i)?; + let (i, v) = take_while(|c: u8| c.is_ascii() && is_space(c))(i)?; if v.is_empty() { Err(nom::Err::Error(NomError { input: i, @@ -1363,18 +1363,18 @@ fn take_newlines(i: &[u8]) -> IResult<&[u8], (&BStr, usize)> { let mut counter = 0; let mut consumed_bytes = 0; let mut next_must_be_newline = false; - for b in i.iter() { - if !(*b as char).is_ascii() { + for b in i.iter().copied() { + if !b.is_ascii() { break; }; - if *b == b'\r' { + if b == b'\r' { if next_must_be_newline { break; } next_must_be_newline = true; continue; }; - if *b == b'\n' { + if b == b'\n' { counter += 1; consumed_bytes += if next_must_be_newline { 2 } else { 1 }; next_must_be_newline = false; diff --git a/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs b/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs index d7c29340164..273add3cd39 100644 --- a/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs +++ b/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs @@ -1,5 +1,6 @@ mod util; +use serial_test::serial; use util::{assert_section_value, git_env_with_symlinked_repo, Condition, GitEnv}; #[test] @@ -47,7 +48,7 @@ fn dot_slash_path_is_replaced_with_directory_containing_the_including_config_fil } #[test] -#[serial_test::serial] +#[serial] #[ignore] fn dot_slash_from_environment_causes_error() { use git_config::file::from_paths; From a922f0a817d290ef4a539bbf99238a4f96d443f9 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 5 Jul 2022 09:20:06 +0800 Subject: [PATCH 013/366] fix windows test (#331) Due to different backslash handling on windows, it operates a little differently. --- .../file/from_paths/includes/conditional/gitdir/mod.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs b/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs index 273add3cd39..0990fec332c 100644 --- a/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs +++ b/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs @@ -107,7 +107,14 @@ fn case_insensitive_matches_any_case() -> crate::Result { #[test] fn pattern_with_escaped_backslash() -> crate::Result { assert_section_value( - Condition::new(r#"gitdir:\\work\\tree\\/"#), + { + let condition = Condition::new(r#"gitdir:\\work\\tree\\/"#); + if cfg!(windows) { + condition.expect_original_value() + } else { + condition + } + }, GitEnv::repo_name("worktree")?, ) } From 9d48b2f51777de37cc996ad54261f2d20f417901 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 5 Jul 2022 09:28:10 +0800 Subject: [PATCH 014/366] fix build warnings on windows (#331) --- .../file/from_paths/includes/conditional/gitdir/mod.rs | 10 +++++----- .../from_paths/includes/conditional/gitdir/util.rs | 6 ++++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs b/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs index 0990fec332c..e17bafb6056 100644 --- a/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs +++ b/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs @@ -1,7 +1,7 @@ mod util; use serial_test::serial; -use util::{assert_section_value, git_env_with_symlinked_repo, Condition, GitEnv}; +use util::{assert_section_value, Condition, GitEnv}; #[test] fn relative_path_with_trailing_slash_matches_like_star_star() -> crate::Result { @@ -135,14 +135,14 @@ fn star_star_in_the_middle() -> crate::Result { #[test] #[cfg(not(windows))] fn tilde_expansion_with_symlink() -> crate::Result { - let env = git_env_with_symlinked_repo()?; + let env = util::git_env_with_symlinked_repo()?; assert_section_value(Condition::new("gitdir:~/symlink-worktree/"), env) } #[test] #[cfg(not(windows))] fn dot_path_with_symlink() -> crate::Result { - let env = git_env_with_symlinked_repo()?; + let env = util::git_env_with_symlinked_repo()?; assert_section_value( Condition::new("gitdir:./symlink-worktree/.git").set_user_config_instead_of_repo_config(), env, @@ -152,7 +152,7 @@ fn dot_path_with_symlink() -> crate::Result { #[test] #[cfg(not(windows))] fn relative_path_matching_symlink() -> crate::Result { - let env = git_env_with_symlinked_repo()?; + let env = util::git_env_with_symlinked_repo()?; assert_section_value( Condition::new("gitdir:symlink-worktree/").set_user_config_instead_of_repo_config(), env, @@ -162,7 +162,7 @@ fn relative_path_matching_symlink() -> crate::Result { #[test] #[cfg(not(windows))] fn dot_path_matching_symlink_with_icase() -> crate::Result { - let env = git_env_with_symlinked_repo()?; + let env = util::git_env_with_symlinked_repo()?; assert_section_value( Condition::new("gitdir/i:SYMLINK-WORKTREE/").set_user_config_instead_of_repo_config(), env, diff --git a/git-config/tests/file/from_paths/includes/conditional/gitdir/util.rs b/git-config/tests/file/from_paths/includes/conditional/gitdir/util.rs index b4d0e4977bd..072d3ded358 100644 --- a/git-config/tests/file/from_paths/includes/conditional/gitdir/util.rs +++ b/git-config/tests/file/from_paths/includes/conditional/gitdir/util.rs @@ -1,6 +1,8 @@ +#![cfg_attr(windows, allow(dead_code))] + use crate::file::cow_str; use crate::file::from_paths::escape_backslashes; -use crate::file::from_paths::includes::conditional::{create_symlink, options_with_git_dir}; +use crate::file::from_paths::includes::conditional::options_with_git_dir; use bstr::{BString, ByteSlice}; use std::io::Write; use std::path::{Path, PathBuf}; @@ -135,7 +137,7 @@ pub fn assert_section_value( pub fn git_env_with_symlinked_repo() -> crate::Result { let mut env = GitEnv::repo_name("worktree")?; let link_destination = env.root_dir().join("symlink-worktree"); - create_symlink(&link_destination, env.worktree_dir()); + crate::file::from_paths::includes::conditional::create_symlink(&link_destination, env.worktree_dir()); let git_dir_through_symlink = link_destination.join(".git"); env.set_git_dir(git_dir_through_symlink); From a3b7828e8bf9d90775f10b0d996fc7ad82f92466 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 5 Jul 2022 11:34:48 +0800 Subject: [PATCH 015/366] improved slash/backslash handling on windows (#331) It's very icky, and needs careful comparison to what git actually does so we can mimick it. When backslashes get into play, things get a bit 'special' as git will sometimes transform the pattern to forward slashes, but not always. Every one of our invocations of `to_unix_paths_on_windows()` is motivated by a test. --- git-config/src/file/resolve_includes.rs | 12 ++-- .../includes/conditional/gitdir/mod.rs | 62 ++++++++++++++++--- .../includes/conditional/gitdir/util.rs | 10 +++ .../from_paths/includes/conditional/mod.rs | 49 --------------- 4 files changed, 70 insertions(+), 63 deletions(-) diff --git a/git-config/src/file/resolve_includes.rs b/git-config/src/file/resolve_includes.rs index ccacbe75f09..ef7a60c2af2 100644 --- a/git-config/src/file/resolve_includes.rs +++ b/git-config/src/file/resolve_includes.rs @@ -162,6 +162,10 @@ fn gitdir_matches( let path = values::Path::from(Cow::Borrowed(condition_path)).interpolate(git_install_dir, home_dir)?; git_path::into_bstr(path).into_owned().into() }; + // NOTE: yes, only if we do path interpolation will the slashes be forced to unix separators on windows + if pattern_path != condition_path { + pattern_path = git_path::to_unix_separators_on_windows(pattern_path); + } if let Some(relative_pattern_path) = pattern_path.strip_prefix(b"./") { let parent_dir = target_config_path @@ -174,9 +178,9 @@ fn gitdir_matches( pattern_path = joined_path.into(); } - if ["~/", "./", "/"] - .iter() - .all(|prefix| !pattern_path.starts_with(prefix.as_bytes())) + // NOTE: this special handling of leading backslash is needed to do it like git does + if pattern_path.iter().next() != Some(&(std::path::MAIN_SEPARATOR as u8)) + && !git_path::from_bstr(pattern_path.clone()).is_absolute() { let mut prefixed = pattern_path.into_owned(); prefixed.insert_str(0, "**/"); @@ -188,8 +192,6 @@ fn gitdir_matches( pattern_path = suffixed.into(); } - pattern_path = git_path::to_unix_separators_on_windows(pattern_path); - let match_mode = git_glob::wildmatch::Mode::NO_MATCH_SLASH_LITERAL | wildmatch_mode; let is_match = git_glob::wildmatch(pattern_path.as_bstr(), git_dir.as_bstr(), match_mode); if is_match { diff --git a/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs b/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs index e17bafb6056..027b922f67e 100644 --- a/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs +++ b/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs @@ -23,7 +23,7 @@ fn relative_path_without_trailing_slash_and_dot_git_suffix_matches() -> crate::R #[test] fn tilde_slash_expands_the_current_user_home() -> crate::Result { - let env = GitEnv::repo_name("subdir/worktree")?; + let env = GitEnv::repo_name(format!("subdir{}worktree", std::path::MAIN_SEPARATOR))?; assert_section_value(Condition::new("gitdir:~/subdir/worktree/"), env) } @@ -38,6 +38,41 @@ fn explicit_star_star_prefix_and_suffix_match_zero_or_more_path_components() -> assert_section_value(Condition::new("gitdir:**/worktree/**"), GitEnv::repo_name("worktree")?) } +#[test] +fn double_slash_does_not_match() -> crate::Result { + assert_section_value( + Condition::new("gitdir://worktree").expect_original_value(), + GitEnv::repo_name("worktree")?, + ) +} + +#[test] +fn absolute_git_dir_with_os_separators_match() -> crate::Result { + assert_section_value( + original_value_on_windows(Condition::new("gitdir:$gitdir")), + GitEnv::repo_name("worktree")?, + ) +} + +#[test] +fn absolute_worktree_dir_with_os_separators_does_not_match_if_trailing_slash_is_missing() -> crate::Result { + assert_section_value( + Condition::new("gitdir:$worktree").expect_original_value(), + GitEnv::repo_name("worktree")?, + ) +} + +#[test] +fn absolute_worktree_dir_with_os_separators_matches_with_trailing_glob() -> crate::Result { + assert_section_value( + original_value_on_windows(Condition::new(format!( + "gitdir:$worktree{}**", + std::path::MAIN_SEPARATOR + ))), + GitEnv::repo_name("worktree")?, + ) +} + #[test] fn dot_slash_path_is_replaced_with_directory_containing_the_including_config_file() -> crate::Result { // TODO: understand this @@ -95,6 +130,14 @@ fn dot_slash_path_with_dot_git_suffix_matches() -> crate::Result { ) } +#[test] +fn globbing_and_wildcards() -> crate::Result { + assert_section_value( + Condition::new("gitdir:stan?ard/glo*ng/[xwz]ildcards/.git").set_user_config_instead_of_repo_config(), + GitEnv::repo_name("standard/globbing/wildcards")?, + ) +} + #[test] fn case_insensitive_matches_any_case() -> crate::Result { assert_section_value(Condition::new("gitdir/i:WORKTREE/"), GitEnv::repo_name("worktree")?)?; @@ -107,14 +150,7 @@ fn case_insensitive_matches_any_case() -> crate::Result { #[test] fn pattern_with_escaped_backslash() -> crate::Result { assert_section_value( - { - let condition = Condition::new(r#"gitdir:\\work\\tree\\/"#); - if cfg!(windows) { - condition.expect_original_value() - } else { - condition - } - }, + original_value_on_windows(Condition::new(r#"gitdir:\\work\\tree\\/"#)), GitEnv::repo_name("worktree")?, ) } @@ -168,3 +204,11 @@ fn dot_path_matching_symlink_with_icase() -> crate::Result { env, ) } + +fn original_value_on_windows(c: Condition) -> Condition { + if cfg!(windows) { + c.expect_original_value() + } else { + c + } +} diff --git a/git-config/tests/file/from_paths/includes/conditional/gitdir/util.rs b/git-config/tests/file/from_paths/includes/conditional/gitdir/util.rs index 072d3ded358..f33cd4cd34d 100644 --- a/git-config/tests/file/from_paths/includes/conditional/gitdir/util.rs +++ b/git-config/tests/file/from_paths/includes/conditional/gitdir/util.rs @@ -215,6 +215,16 @@ fn write_main_config( }; let condition = condition.as_ref(); + let condition = { + let c = condition + .replace("$gitdir", &env.git_dir().to_string_lossy()) + .replace("$worktree", &env.worktree_dir().to_string_lossy()); + if c == condition { + condition.to_owned() + } else { + escape_backslashes(c) + } + }; let include_file_path = escape_backslashes(include_file_path); let mut file = std::fs::OpenOptions::new() .append(true) diff --git a/git-config/tests/file/from_paths/includes/conditional/mod.rs b/git-config/tests/file/from_paths/includes/conditional/mod.rs index 658fd781afd..c1a649a52bc 100644 --- a/git-config/tests/file/from_paths/includes/conditional/mod.rs +++ b/git-config/tests/file/from_paths/includes/conditional/mod.rs @@ -128,11 +128,9 @@ fn various_gitdir() { let absolute_path = dir.path().join("b"); let home_dot_git_path = dir.path().join("c"); let foo_trailing_slash_path = dir.path().join("foo_slash"); - let wildcards_path = dir.path().join("d"); let relative_path = dir.path().join("e"); let casei_path = dir.path().join("i"); let relative_dot_git_path = dir.path().join("w"); - let relative_with_backslash_path = dir.path().join("x"); let tmp_path = dir.path().join("tmp"); let dot_dot_path = dir.path().join("dot_dot"); let dir = canonicalized_tempdir().unwrap(); @@ -155,8 +153,6 @@ fn various_gitdir() { x = 1 [includeIf "gitdir/i:a/B/c/D/"] path = {} -[includeIf "gitdir:c\\d/"] - path = {} [includeIf "gitdir:foo/bar"] path = {} [includeIf "gitdir:.."] @@ -167,20 +163,16 @@ fn various_gitdir() { path = {} [includeIf "gitdir:foo/"] path = {} -[includeIf "gitdir:stan?ard/glo*ng/[xwz]ildcards/.git"] - path = {} [includeIf "gitdir:{}"] path = {} [includeIf "gitdir:/e/x/"] path = {}"#, escape_backslashes(&casei_path), - escape_backslashes(&relative_with_backslash_path), escape_backslashes(&relative_path), escape_backslashes(&dot_dot_path), escape_backslashes(&relative_dot_git_path), escape_backslashes(&home_dot_git_path), escape_backslashes(&foo_trailing_slash_path), - escape_backslashes(&wildcards_path), &tmp_dir_m_n_with_slash, escape_backslashes(&tmp_path), escape_backslashes(&absolute_path), @@ -196,14 +188,6 @@ fn various_gitdir() { ) .unwrap(); - fs::write( - relative_with_backslash_path.as_path(), - " -[core] - c = absolute with double-slash do match", - ) - .unwrap(); - fs::write( absolute_path.as_path(), " @@ -220,14 +204,6 @@ fn various_gitdir() { ) .unwrap(); - fs::write( - wildcards_path.as_path(), - " -[core] - b = standard-globbing-wildcards", - ) - .unwrap(); - fs::write( relative_path.as_path(), " @@ -278,16 +254,6 @@ fn various_gitdir() { ); } - // TODO: figure out what this is testing, I have a feeling double-slash git dirs aren't relevant, it's the condition that matters - if false { - let dir = Path::new("/c//d/.git"); - let config = File::from_paths(Some(&config_path), options_with_git_dir(dir)).unwrap(); - assert_eq!( - config.string("core", None, "c"), - Some(cow_str("absolute with double-slash do match")), - ); - } - { let dir = config_path.join("foo").join("bar"); let config = File::from_paths(Some(&config_path), options_with_git_dir(&dir)).unwrap(); @@ -344,21 +310,6 @@ fn various_gitdir() { ); } - { - let dir = dir - .path() - .join("standard") - .join("globbing") - .join("wildcards") - .join(".git"); - let config = File::from_paths(Some(&config_path), options_with_git_dir(&dir)).unwrap(); - assert_eq!( - config.string("core", None, "b"), - Some(cow_str("standard-globbing-wildcards")), - "standard globbing wildcards works" - ); - } - if cfg!(not(windows)) { // TODO: this seems to fail on windows, fix it let home = std::env::current_dir().unwrap(); From 3af09e5800648df87cdaf22191dd4d1dc4b278a3 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 5 Jul 2022 11:42:44 +0800 Subject: [PATCH 016/366] remove unmotivated forward-slash conversion (#331) It would need validation with actual symlink tests, but these are hard to do on windows. There should be a way though, or we just leave it for now. --- git-config/src/file/resolve_includes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-config/src/file/resolve_includes.rs b/git-config/src/file/resolve_includes.rs index ef7a60c2af2..102cb870394 100644 --- a/git-config/src/file/resolve_includes.rs +++ b/git-config/src/file/resolve_includes.rs @@ -202,7 +202,7 @@ fn gitdir_matches( git_path::from_byte_slice(&git_dir), target_config_path.ok_or(from_paths::Error::MissingConfigPath)?, )?; - let expanded_git_dir = git_path::to_unix_separators_on_windows(git_path::into_bstr(expanded_git_dir)); + let expanded_git_dir = git_path::into_bstr(expanded_git_dir); Ok(git_glob::wildmatch( pattern_path.as_bstr(), expanded_git_dir.as_bstr(), From 5c713946b1f35675bacb27bd5392addf25010942 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 5 Jul 2022 11:52:46 +0800 Subject: [PATCH 017/366] remove duplicate gitdir tests that don't have a baseline (#331) They are all superseded by better tests in 'gitdir.rs' where they are tested against git as well. --- .../from_paths/includes/conditional/mod.rs | 281 +----------------- 1 file changed, 3 insertions(+), 278 deletions(-) diff --git a/git-config/tests/file/from_paths/includes/conditional/mod.rs b/git-config/tests/file/from_paths/includes/conditional/mod.rs index c1a649a52bc..28f56df34c1 100644 --- a/git-config/tests/file/from_paths/includes/conditional/mod.rs +++ b/git-config/tests/file/from_paths/includes/conditional/mod.rs @@ -1,10 +1,10 @@ use std::fs; -use std::path::{Path, PathBuf}; +use std::path::Path; use crate::file::{cow_str, from_paths::escape_backslashes}; use git_config::file::from_paths; use git_config::File; -use tempfile::{tempdir, tempdir_in}; +use tempfile::tempdir; mod gitdir; mod onbranch; @@ -77,273 +77,6 @@ fn include_and_includeif_correct_inclusion_order() { ); } -#[test] -fn pattern_is_current_dir() { - let dir = tempdir().unwrap(); - let config_path = dir.path().join("a"); - let relative_dot_slash_path = dir.path().join("g"); - fs::write( - relative_dot_slash_path.as_path(), - " -[core] - b = relative-dot-slash-path", - ) - .unwrap(); - - fs::write( - config_path.as_path(), - format!( - r#" -[core] - x = 1 -[includeIf "gitdir:./"] - path = {}"#, - escape_backslashes(&relative_dot_slash_path), - ), - ) - .unwrap(); - - { - let dir = config_path.parent().unwrap().join("p").join("q").join(".git"); - let config = File::from_paths(Some(&config_path), options_with_git_dir(&dir)).unwrap(); - assert_eq!( - config.string("core", None, "b"), - Some(cow_str("relative-dot-slash-path")), - "relative path pattern is matched correctly" - ); - } -} - -#[test] -fn various_gitdir() { - // TODO: check against specific gitdir/onbranch tests which should also support running it against baseline git - fn canonicalized_tempdir() -> crate::Result { - let canonicalized_tempdir = git_path::realpath(std::env::temp_dir(), std::env::current_dir()?)?; - Ok(tempdir_in(canonicalized_tempdir)?) - } - - let dir = tempdir().unwrap(); - - let config_path = dir.path().join("a"); - let absolute_path = dir.path().join("b"); - let home_dot_git_path = dir.path().join("c"); - let foo_trailing_slash_path = dir.path().join("foo_slash"); - let relative_path = dir.path().join("e"); - let casei_path = dir.path().join("i"); - let relative_dot_git_path = dir.path().join("w"); - let tmp_path = dir.path().join("tmp"); - let dot_dot_path = dir.path().join("dot_dot"); - let dir = canonicalized_tempdir().unwrap(); - let tmp_dir_m_n_with_slash = format!( - "{}/", - dir.path().join("m").join("n").to_str().unwrap().replace('\\', "/") - ); - - fs::write( - config_path.as_path(), - format!( - r#" -[core] - a = 1 - b = 1 - c = 1 - d = 1 - i = 1 - t = 1 - x = 1 -[includeIf "gitdir/i:a/B/c/D/"] - path = {} -[includeIf "gitdir:foo/bar"] - path = {} -[includeIf "gitdir:.."] - path = {} -[includeIf "gitdir:w/.git"] - path = {} -[includeIf "gitdir:~/"] - path = {} -[includeIf "gitdir:foo/"] - path = {} -[includeIf "gitdir:{}"] - path = {} -[includeIf "gitdir:/e/x/"] - path = {}"#, - escape_backslashes(&casei_path), - escape_backslashes(&relative_path), - escape_backslashes(&dot_dot_path), - escape_backslashes(&relative_dot_git_path), - escape_backslashes(&home_dot_git_path), - escape_backslashes(&foo_trailing_slash_path), - &tmp_dir_m_n_with_slash, - escape_backslashes(&tmp_path), - escape_backslashes(&absolute_path), - ), - ) - .unwrap(); - - fs::write( - casei_path.as_path(), - " -[core] - i = case-i-match", - ) - .unwrap(); - - fs::write( - absolute_path.as_path(), - " -[core] - b = absolute-path", - ) - .unwrap(); - - fs::write( - home_dot_git_path.as_path(), - " -[core] - b = home-dot-git", - ) - .unwrap(); - - fs::write( - relative_path.as_path(), - " -[core] - a = relative-path", - ) - .unwrap(); - - fs::write( - dot_dot_path.as_path(), - " -[core] - d = dot-dot-path", - ) - .unwrap(); - - fs::write( - relative_dot_git_path.as_path(), - " -[core] - a = relative-dot-git", - ) - .unwrap(); - - fs::write( - foo_trailing_slash_path.as_path(), - " -[core] - b = foo-trailing-slash", - ) - .unwrap(); - - fs::write( - tmp_path.as_path(), - " -[core] - t = absolute-path-with-symlink", - ) - .unwrap(); - - { - let dir = Path::new("/A/b/C/d/.git"); - let config = File::from_paths(Some(&config_path), options_with_git_dir(dir)).unwrap(); - assert_eq!( - config.string("core", None, "i"), - Some(cow_str("case-i-match")), - "case insensitive patterns match" - ); - } - - { - let dir = config_path.join("foo").join("bar"); - let config = File::from_paths(Some(&config_path), options_with_git_dir(&dir)).unwrap(); - assert_eq!( - config.string("core", None, "a"), - Some(cow_str("relative-path")), - "the pattern is prefixed and suffixed with ** to match GIT_DIR containing it in the middle" - ); - } - - { - let dir = dot_dot_path.parent().unwrap(); - let config = File::from_paths(Some(&config_path), options_with_git_dir(dir)).unwrap(); - assert_eq!( - config.string("core", None, "d"), - Some(cow_str("1")), - ".. is not special and usually not what you want" - ); - } - - // TODO: see what git thinks about this on unix - if cfg!(windows) { - let dir = PathBuf::from("C:\\w\\.git".to_string()); - let config = File::from_paths(Some(&config_path), options_with_git_dir(&dir)).unwrap(); - assert_eq!( - config.string("core", None, "a"), - Some(cow_str("relative-dot-git")), - "backslashes in GIT_DIR are converted to forward slashes" - ); - } - - if cfg!(not(windows)) { - // TODO: this seems to fail on windows, fix it - let home = std::env::current_dir().unwrap(); - let config = File::from_paths( - Some(&config_path), - options_with_git_dir_in_home(&home.join(".git"), &home), - ) - .unwrap(); - assert_eq!( - config.strings("core", None, "b"), - Some(vec![cow_str("1"), cow_str("home-dot-git")]), - "tilde ~ path is resolved to home directory" - ); - } - - { - let dir = config_path.join("foo").join(".git"); - let config = File::from_paths(Some(&config_path), options_with_git_dir(&dir)).unwrap(); - assert_eq!( - config.string("core", None, "b"), - Some(cow_str("foo-trailing-slash")), - "path with trailing slash is matched" - ); - } - - if cfg!(not(windows)) { - // TODO: this seems to fail on windows, fix it - let home = std::env::current_dir().unwrap(); - let config = File::from_paths( - Some(config_path.as_path()), - options_with_git_dir_in_home(&home.join(".git"), &home), - ) - .unwrap(); - assert_eq!( - config.string("core", None, "b"), - Some(cow_str("home-dot-git")), - "absolute path pattern is matched with sub path from GIT_DIR" - ); - } - - if cfg!(not(windows)) { - // TODO: this seems to fail on windows, fix it - let dir = canonicalized_tempdir().unwrap(); - let symlink_outside_tempdir_m_n = dir.path().join("m").join("symlink"); - create_symlink( - &symlink_outside_tempdir_m_n, - &PathBuf::from(&format!("{}.git", tmp_dir_m_n_with_slash)), - ); - let dir = PathBuf::from(&symlink_outside_tempdir_m_n); - let config = File::from_paths(Some(config_path), options_with_git_dir(&dir)).unwrap(); - assert_eq!( - config.string("core", None, "t"), - Some(cow_str("absolute-path-with-symlink")), - "absolute path pattern is matched with path from GIT_DIR when it contains symlink" - ); - fs::remove_file(symlink_outside_tempdir_m_n.as_path()).unwrap(); - } -} - fn options_with_git_dir(git_dir: &Path) -> from_paths::Options<'_> { from_paths::Options { git_dir: Some(git_dir), @@ -352,15 +85,7 @@ fn options_with_git_dir(git_dir: &Path) -> from_paths::Options<'_> { } } -fn options_with_git_dir_in_home<'a>(git_dir: &'a Path, home: &'a Path) -> from_paths::Options<'a> { - from_paths::Options { - git_dir: Some(git_dir), - home_dir: Some(home), - ..Default::default() - } -} - -pub fn create_symlink(from: impl AsRef, to: impl AsRef) { +fn create_symlink(from: impl AsRef, to: impl AsRef) { std::fs::create_dir_all(from.as_ref().parent().unwrap()).unwrap(); #[cfg(not(windows))] std::os::unix::fs::symlink(to, from).unwrap(); From 53efbf54364c373426a7790c28c74c787670877a Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 5 Jul 2022 11:58:52 +0800 Subject: [PATCH 018/366] prevent race when calling `git` around `GIT_CONFIG_*` env vars (#331) --- .../tests/file/from_paths/includes/conditional/gitdir/util.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/git-config/tests/file/from_paths/includes/conditional/gitdir/util.rs b/git-config/tests/file/from_paths/includes/conditional/gitdir/util.rs index f33cd4cd34d..31e716c8bbf 100644 --- a/git-config/tests/file/from_paths/includes/conditional/gitdir/util.rs +++ b/git-config/tests/file/from_paths/includes/conditional/gitdir/util.rs @@ -149,6 +149,7 @@ fn assure_git_agrees(expected: Option, env: GitEnv) -> crate::Result { .args(["config", "--get", "section.value"]) .env("HOME", env.home_dir()) .env("GIT_DIR", env.git_dir()) + .env_remove("GIT_CONFIG_COUNT") .current_dir(env.worktree_dir()) .output()?; From 0229e2583ed7beccaf59dc0c82893c5b67c285dd Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 5 Jul 2022 21:23:08 +0800 Subject: [PATCH 019/366] refactor (#331) --- .../includes/conditional/onbranch.rs | 150 +++++++++--------- 1 file changed, 75 insertions(+), 75 deletions(-) diff --git a/git-config/tests/file/from_paths/includes/conditional/onbranch.rs b/git-config/tests/file/from_paths/includes/conditional/onbranch.rs index 18898bbe964..119c049cfec 100644 --- a/git-config/tests/file/from_paths/includes/conditional/onbranch.rs +++ b/git-config/tests/file/from_paths/includes/conditional/onbranch.rs @@ -7,81 +7,6 @@ use tempfile::tempdir; use crate::file::{cow_str, from_paths::escape_backslashes}; -enum Value { - Base, - OverrideByInclude, -} - -struct Options<'a> { - condition: &'a str, - branch_name: &'a str, - expect: Value, -} - -fn assert_section_value(opts: Options) { - assert_section_value_msg(opts, None) -} - -fn assert_section_value_msg( - Options { - condition, - branch_name, - expect, - }: Options, - message: Option<&str>, -) { - let dir = tempdir().unwrap(); - let root_config = dir.path().join("root.config"); - let included_config = dir.path().join("include.config"); - - fs::write( - &root_config, - format!( - r#" -[section] -value = base-value - -[includeIf "onbranch:{}"] -path = {}"#, - condition, - escape_backslashes(&included_config), - ), - ) - .unwrap(); - - fs::write( - included_config, - r#" -[section] -value = branch-override-by-include -"#, - ) - .unwrap(); - - let branch_name = FullName::try_from(BString::from(branch_name)).unwrap(); - let branch_name = branch_name.as_ref(); - let options = from_paths::Options { - branch_name: Some(branch_name), - ..Default::default() - }; - - let config = git_config::File::from_paths(Some(&root_config), options).unwrap(); - assert_eq!( - config.string("section", None, "value"), - Some(cow_str(match expect { - Value::OverrideByInclude => "branch-override-by-include", - Value::Base => "base-value", - })), - "{}, info: {:?}", - match expect { - Value::Base => "the base value should not be overridden as the branch does not match", - Value::OverrideByInclude => - "the base value is overridden by an included file because the condition matches", - }, - message - ); -} - #[test] fn literal_branch_names_match() { assert_section_value(Options { @@ -191,3 +116,78 @@ fn double_star_globs_cross_component_boundaries() { expect: Value::OverrideByInclude, }); } + +enum Value { + Base, + OverrideByInclude, +} + +struct Options<'a> { + condition: &'a str, + branch_name: &'a str, + expect: Value, +} + +fn assert_section_value(opts: Options) { + assert_section_value_msg(opts, None) +} + +fn assert_section_value_msg( + Options { + condition, + branch_name, + expect, + }: Options, + message: Option<&str>, +) { + let dir = tempdir().unwrap(); + let root_config = dir.path().join("root.config"); + let included_config = dir.path().join("include.config"); + + fs::write( + &root_config, + format!( + r#" +[section] +value = base-value + +[includeIf "onbranch:{}"] +path = {}"#, + condition, + escape_backslashes(&included_config), + ), + ) + .unwrap(); + + fs::write( + included_config, + r#" +[section] +value = branch-override-by-include +"#, + ) + .unwrap(); + + let branch_name = FullName::try_from(BString::from(branch_name)).unwrap(); + let branch_name = branch_name.as_ref(); + let options = from_paths::Options { + branch_name: Some(branch_name), + ..Default::default() + }; + + let config = git_config::File::from_paths(Some(&root_config), options).unwrap(); + assert_eq!( + config.string("section", None, "value"), + Some(cow_str(match expect { + Value::OverrideByInclude => "branch-override-by-include", + Value::Base => "base-value", + })), + "{}, info: {:?}", + match expect { + Value::Base => "the base value should not be overridden as the branch does not match", + Value::OverrideByInclude => + "the base value is overridden by an included file because the condition matches", + }, + message + ); +} From adcddb0056c14302f0133de251fa07e877b6f509 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 5 Jul 2022 22:02:54 +0800 Subject: [PATCH 020/366] Also test against git baseline (#331) For now using simple git command-invocations --- .../includes/conditional/gitdir/util.rs | 1 + .../includes/conditional/onbranch.rs | 100 +++++++++++++----- 2 files changed, 75 insertions(+), 26 deletions(-) diff --git a/git-config/tests/file/from_paths/includes/conditional/gitdir/util.rs b/git-config/tests/file/from_paths/includes/conditional/gitdir/util.rs index 31e716c8bbf..3963e63964f 100644 --- a/git-config/tests/file/from_paths/includes/conditional/gitdir/util.rs +++ b/git-config/tests/file/from_paths/includes/conditional/gitdir/util.rs @@ -150,6 +150,7 @@ fn assure_git_agrees(expected: Option, env: GitEnv) -> crate::Result { .env("HOME", env.home_dir()) .env("GIT_DIR", env.git_dir()) .env_remove("GIT_CONFIG_COUNT") + .env_remove("XDG_CONFIG_HOME") .current_dir(env.worktree_dir()) .output()?; diff --git a/git-config/tests/file/from_paths/includes/conditional/onbranch.rs b/git-config/tests/file/from_paths/includes/conditional/onbranch.rs index 119c049cfec..031fc1b8fc4 100644 --- a/git-config/tests/file/from_paths/includes/conditional/onbranch.rs +++ b/git-config/tests/file/from_paths/includes/conditional/onbranch.rs @@ -1,11 +1,13 @@ +use std::convert::TryInto; use std::{convert::TryFrom, fs}; -use bstr::BString; +use bstr::{BString, ByteSlice}; use git_config::file::from_paths; -use git_ref::FullName; +use git_ref::transaction::{Change, PreviousValue, RefEdit}; +use git_ref::{FullName, Target}; use tempfile::tempdir; -use crate::file::{cow_str, from_paths::escape_backslashes}; +use crate::file::cow_str; #[test] fn literal_branch_names_match() { @@ -35,7 +37,7 @@ fn non_branches_never_match() { } #[test] -fn patterns_ending_with_slash_match_subdirectories_recursively() { +fn patterns_ending_with_slash_match_subdirectories_recursively() -> crate::Result { assert_section_value(Options { condition: "feature/b/", branch_name: "refs/heads/feature/b/start", @@ -53,11 +55,11 @@ fn patterns_ending_with_slash_match_subdirectories_recursively() { expect: Value::OverrideByInclude, }, "just for good measure, we would expect branch paths to work as well".into(), - ); + ) } #[test] -fn simple_glob_patterns() { +fn simple_glob_patterns() -> crate::Result { assert_section_value(Options { condition: "prefix*", branch_name: "refs/heads/prefixsuffix", @@ -70,7 +72,7 @@ fn simple_glob_patterns() { expect: Value::Base, }, "single-stars do not cross component boundaries".into(), - ); + )?; assert_section_value(Options { condition: "*suffix", branch_name: "refs/heads/prefixsuffix", @@ -88,11 +90,11 @@ fn simple_glob_patterns() { expect: Value::Base, }, "single-stars do not cross component boundaries".into(), - ); + ) } #[test] -fn simple_globs_do_not_cross_component_boundary() { +fn simple_globs_do_not_cross_component_boundary() -> crate::Result { assert_section_value(Options { condition: "feature/*/start", branch_name: "refs/heads/feature/a/start", @@ -105,7 +107,7 @@ fn simple_globs_do_not_cross_component_boundary() { expect: Value::Base, }, "path matching would never match 'a/b' as it cannot cross /".into(), - ); + ) } #[test] @@ -129,7 +131,7 @@ struct Options<'a> { } fn assert_section_value(opts: Options) { - assert_section_value_msg(opts, None) + assert_section_value_msg(opts, None).unwrap() } fn assert_section_value_msg( @@ -139,9 +141,10 @@ fn assert_section_value_msg( expect, }: Options, message: Option<&str>, -) { - let dir = tempdir().unwrap(); - let root_config = dir.path().join("root.config"); +) -> crate::Result { + let dir = tempdir()?; + let repo = git_repository::init_bare(dir.path())?; + let root_config = dir.path().join("config"); let included_config = dir.path().join("include.config"); fs::write( @@ -152,12 +155,10 @@ fn assert_section_value_msg( value = base-value [includeIf "onbranch:{}"] -path = {}"#, +path = ./include.config"#, condition, - escape_backslashes(&included_config), ), - ) - .unwrap(); + )?; fs::write( included_config, @@ -165,29 +166,76 @@ path = {}"#, [section] value = branch-override-by-include "#, - ) - .unwrap(); + )?; - let branch_name = FullName::try_from(BString::from(branch_name)).unwrap(); - let branch_name = branch_name.as_ref(); + let branch_name = FullName::try_from(BString::from(branch_name))?; let options = from_paths::Options { - branch_name: Some(branch_name), + branch_name: Some(branch_name.as_ref()), ..Default::default() }; - let config = git_config::File::from_paths(Some(&root_config), options).unwrap(); + let config = git_config::File::from_paths(Some(&root_config), options)?; assert_eq!( config.string("section", None, "value"), Some(cow_str(match expect { Value::OverrideByInclude => "branch-override-by-include", Value::Base => "base-value", })), - "{}, info: {:?}", + "{}, info: {:?}, debug at {:?}", match expect { Value::Base => "the base value should not be overridden as the branch does not match", Value::OverrideByInclude => "the base value is overridden by an included file because the condition matches", }, - message + message, + dir.into_path() + ); + + repo.refs + .transaction() + .prepare( + Some(RefEdit { + name: "HEAD".try_into()?, + change: Change::Update { + log: Default::default(), + expected: PreviousValue::Any, + new: Target::Symbolic(branch_name), + }, + deref: false, + }), + git_repository::lock::acquire::Fail::Immediately, + )? + .commit(repo.committer().to_ref())?; + + assure_git_agrees(expect, dir) +} + +fn assure_git_agrees(expected: Value, dir: tempfile::TempDir) -> crate::Result { + let git_dir = dir.path(); + let output = std::process::Command::new("git") + .args(["config", "--get", "section.value"]) + .env("GIT_DIR", git_dir) + .env("HOME", git_dir) + .env_remove("GIT_CONFIG_COUNT") + .env_remove("XDG_CONFIG_HOME") + .current_dir(git_dir) + .output()?; + + assert!( + output.status.success(), + "{:?}, {:?} for debugging", + output, + dir.into_path() + ); + let git_output: BString = output.stdout.trim_end().into(); + assert_eq!( + git_output, + match expected { + Value::Base => "base-value", + Value::OverrideByInclude => "branch-override-by-include", + }, + "git disagrees with git-config, {:?} for debugging", + dir.into_path() ); + Ok(()) } From 600161324edc370707613841ce9228320c700bf6 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 5 Jul 2022 22:25:53 +0800 Subject: [PATCH 021/366] reuse the initialized environment for a little speed (#331) Even though this is technically a little less clean, we are read-only and really only need a bare repo whose three files we fully control to setup the test-case. It's still a bit strange the the `Command` invocations of `git` are very slow, even though git finishes in 10ms when tried by hand. --- .../includes/conditional/onbranch.rs | 199 ++++++++++++------ 1 file changed, 130 insertions(+), 69 deletions(-) diff --git a/git-config/tests/file/from_paths/includes/conditional/onbranch.rs b/git-config/tests/file/from_paths/includes/conditional/onbranch.rs index 031fc1b8fc4..dababa65273 100644 --- a/git-config/tests/file/from_paths/includes/conditional/onbranch.rs +++ b/git-config/tests/file/from_paths/includes/conditional/onbranch.rs @@ -5,118 +5,165 @@ use bstr::{BString, ByteSlice}; use git_config::file::from_paths; use git_ref::transaction::{Change, PreviousValue, RefEdit}; use git_ref::{FullName, Target}; +use git_repository as git; use tempfile::tempdir; use crate::file::cow_str; +type Result = crate::Result; + #[test] -fn literal_branch_names_match() { - assert_section_value(Options { - condition: "literal-match", - branch_name: "refs/heads/literal-match", - expect: Value::OverrideByInclude, - }); +fn literal_branch_names_match() -> Result { + assert_section_value( + Options { + condition: "literal-match", + branch_name: "refs/heads/literal-match", + expect: Value::OverrideByInclude, + }, + GitEnv::new()?, + )?; + Ok(()) } #[test] -fn full_ref_names_do_not_match() { - assert_section_value(Options { - condition: "refs/heads/simple", - branch_name: "refs/heads/simple", - expect: Value::Base, - }); +fn full_ref_names_do_not_match() -> Result { + assert_section_value( + Options { + condition: "refs/heads/simple", + branch_name: "refs/heads/simple", + expect: Value::Base, + }, + GitEnv::new()?, + )?; + Ok(()) } #[test] -fn non_branches_never_match() { - assert_section_value(Options { - condition: "good", - branch_name: "refs/bisect/good", - expect: Value::Base, - }); +fn non_branches_never_match() -> Result { + assert_section_value( + Options { + condition: "good", + branch_name: "refs/bisect/good", + expect: Value::Base, + }, + GitEnv::new()?, + )?; + Ok(()) } #[test] -fn patterns_ending_with_slash_match_subdirectories_recursively() -> crate::Result { - assert_section_value(Options { - condition: "feature/b/", - branch_name: "refs/heads/feature/b/start", - expect: Value::OverrideByInclude, - }); - assert_section_value(Options { - condition: "feature/", - branch_name: "refs/heads/feature/b/start", - expect: Value::OverrideByInclude, - }); +fn patterns_ending_with_slash_match_subdirectories_recursively() -> Result { + let mut env = GitEnv::new()?; + env = assert_section_value( + Options { + condition: "feature/b/", + branch_name: "refs/heads/feature/b/start", + expect: Value::OverrideByInclude, + }, + env, + )?; + env = assert_section_value( + Options { + condition: "feature/", + branch_name: "refs/heads/feature/b/start", + expect: Value::OverrideByInclude, + }, + env, + )?; assert_section_value_msg( Options { condition: "feature/b/start", branch_name: "refs/heads/feature/b/start", expect: Value::OverrideByInclude, }, + env, "just for good measure, we would expect branch paths to work as well".into(), - ) + )?; + Ok(()) } #[test] -fn simple_glob_patterns() -> crate::Result { - assert_section_value(Options { - condition: "prefix*", - branch_name: "refs/heads/prefixsuffix", - expect: Value::OverrideByInclude, - }); - assert_section_value_msg( +fn simple_glob_patterns() -> Result { + let mut env = GitEnv::new()?; + env = assert_section_value( + Options { + condition: "prefix*", + branch_name: "refs/heads/prefixsuffix", + expect: Value::OverrideByInclude, + }, + env, + )?; + env = assert_section_value_msg( Options { condition: "prefix*", branch_name: "refs/heads/prefix/suffix", expect: Value::Base, }, + env, "single-stars do not cross component boundaries".into(), )?; - assert_section_value(Options { - condition: "*suffix", - branch_name: "refs/heads/prefixsuffix", - expect: Value::OverrideByInclude, - }); - assert_section_value(Options { - condition: "*/suffix", - branch_name: "refs/heads/prefix/suffix", - expect: Value::OverrideByInclude, - }); + env = assert_section_value( + Options { + condition: "*suffix", + branch_name: "refs/heads/prefixsuffix", + expect: Value::OverrideByInclude, + }, + env, + )?; + env = assert_section_value( + Options { + condition: "*/suffix", + branch_name: "refs/heads/prefix/suffix", + expect: Value::OverrideByInclude, + }, + env, + )?; assert_section_value_msg( Options { condition: "*suffix", branch_name: "refs/heads/prefix/suffix", expect: Value::Base, }, + env, "single-stars do not cross component boundaries".into(), - ) + )?; + Ok(()) } #[test] -fn simple_globs_do_not_cross_component_boundary() -> crate::Result { - assert_section_value(Options { - condition: "feature/*/start", - branch_name: "refs/heads/feature/a/start", - expect: Value::OverrideByInclude, - }); +fn simple_globs_do_not_cross_component_boundary() -> Result { + let mut env = GitEnv::new()?; + env = assert_section_value( + Options { + condition: "feature/*/start", + branch_name: "refs/heads/feature/a/start", + expect: Value::OverrideByInclude, + }, + env, + )?; assert_section_value_msg( Options { condition: "feature/*/start", branch_name: "refs/heads/feature/a/b/start", expect: Value::Base, }, + env, "path matching would never match 'a/b' as it cannot cross /".into(), - ) + )?; + Ok(()) } #[test] -fn double_star_globs_cross_component_boundaries() { - assert_section_value(Options { - condition: "feature/**/start", - branch_name: "refs/heads/feature/a/b/start", - expect: Value::OverrideByInclude, - }); +fn double_star_globs_cross_component_boundaries() -> Result { + assert_section_value( + Options { + condition: "feature/**/start", + branch_name: "refs/heads/feature/a/b/start", + expect: Value::OverrideByInclude, + }, + GitEnv::new()?, + )?; + Ok(()) } enum Value { @@ -124,14 +171,28 @@ enum Value { OverrideByInclude, } +#[derive(Debug)] +struct GitEnv { + repo: git::Repository, + dir: tempfile::TempDir, +} + +impl GitEnv { + fn new() -> crate::Result { + let dir = tempdir()?; + let repo = git_repository::init_bare(dir.path())?; + Ok(GitEnv { repo, dir }) + } +} + struct Options<'a> { condition: &'a str, branch_name: &'a str, expect: Value, } -fn assert_section_value(opts: Options) { - assert_section_value_msg(opts, None).unwrap() +fn assert_section_value(opts: Options, env: GitEnv) -> crate::Result { + assert_section_value_msg(opts, env, None) } fn assert_section_value_msg( @@ -140,10 +201,9 @@ fn assert_section_value_msg( branch_name, expect, }: Options, + GitEnv { repo, dir }: GitEnv, message: Option<&str>, -) -> crate::Result { - let dir = tempdir()?; - let repo = git_repository::init_bare(dir.path())?; +) -> crate::Result { let root_config = dir.path().join("config"); let included_config = dir.path().join("include.config"); @@ -207,10 +267,11 @@ value = branch-override-by-include )? .commit(repo.committer().to_ref())?; - assure_git_agrees(expect, dir) + let dir = assure_git_agrees(expect, dir)?; + Ok(GitEnv { repo, dir }) } -fn assure_git_agrees(expected: Value, dir: tempfile::TempDir) -> crate::Result { +fn assure_git_agrees(expected: Value, dir: tempfile::TempDir) -> crate::Result { let git_dir = dir.path(); let output = std::process::Command::new("git") .args(["config", "--get", "section.value"]) @@ -237,5 +298,5 @@ fn assure_git_agrees(expected: Value, dir: tempfile::TempDir) -> crate::Result { "git disagrees with git-config, {:?} for debugging", dir.into_path() ); - Ok(()) + Ok(dir) } From 7d27dd5e3558a22865e0c9159d269577431097f3 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 6 Jul 2022 09:41:01 +0800 Subject: [PATCH 022/366] a test to validate relative includepaths aren't valid for includeIf (#331) --- git-config/src/file/resolve_includes.rs | 5 +- git-config/tests/file/from_env/mod.rs | 4 +- .../includes/conditional/gitdir/mod.rs | 78 +++++++++++++++---- .../includes/conditional/gitdir/util.rs | 2 +- 4 files changed, 67 insertions(+), 22 deletions(-) diff --git a/git-config/src/file/resolve_includes.rs b/git-config/src/file/resolve_includes.rs index 102cb870394..7388d3e9ed5 100644 --- a/git-config/src/file/resolve_includes.rs +++ b/git-config/src/file/resolve_includes.rs @@ -198,10 +198,7 @@ fn gitdir_matches( return Ok(true); } - let expanded_git_dir = git_path::realpath( - git_path::from_byte_slice(&git_dir), - target_config_path.ok_or(from_paths::Error::MissingConfigPath)?, - )?; + let expanded_git_dir = git_path::realpath(git_path::from_byte_slice(&git_dir), std::env::current_dir().unwrap())?; let expanded_git_dir = git_path::into_bstr(expanded_git_dir); Ok(git_glob::wildmatch( pattern_path.as_bstr(), diff --git a/git-config/tests/file/from_env/mod.rs b/git-config/tests/file/from_env/mod.rs index bf88f30502b..d0d77bccbe4 100644 --- a/git-config/tests/file/from_env/mod.rs +++ b/git-config/tests/file/from_env/mod.rs @@ -18,8 +18,8 @@ impl<'a> Env<'a> { } } - pub(crate) fn set(mut self, var: &'a str, value: &'a str) -> Self { - env::set_var(var, value); + pub(crate) fn set(mut self, var: &'a str, value: impl Into) -> Self { + env::set_var(var, value.into()); self.altered_vars.push(var); self } diff --git a/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs b/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs index 027b922f67e..f513902d97b 100644 --- a/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs +++ b/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs @@ -1,5 +1,6 @@ mod util; +use crate::file::from_paths::escape_backslashes; use serial_test::serial; use util::{assert_section_value, Condition, GitEnv}; @@ -84,22 +85,69 @@ fn dot_slash_path_is_replaced_with_directory_containing_the_including_config_fil #[test] #[serial] -#[ignore] -fn dot_slash_from_environment_causes_error() { +fn dot_slash_from_environment_causes_error() -> crate::Result { use git_config::file::from_paths; - // TODO: figure out how to do this, how do we parse sub-keys? Can git do that even? YES, git can actually! - let _env = crate::file::from_env::Env::new() - .set("GIT_CONFIG_COUNT", "1") - .set("GIT_CONFIG_KEY_0", "includeIf.path") - .set("GIT_CONFIG_VALUE_0", "some_git_config"); - - let res = git_config::File::from_env(from_paths::Options::default()); - assert!(matches!( - res, - Err(git_config::file::from_env::Error::FromPathsError( - from_paths::Error::MissingConfigPath - )) - )); + let env = GitEnv::repo_name("worktree")?; + + { + let _environment = crate::file::from_env::Env::new() + .set("GIT_CONFIG_COUNT", "1") + .set( + "GIT_CONFIG_KEY_0", + format!("includeIf.gitdir:{}.path", escape_backslashes(env.git_dir())), + ) + .set("GIT_CONFIG_VALUE_0", "./include.path"); + + let res = git_config::File::from_env(env.include_options()); + assert!( + matches!( + res, + Err(git_config::file::from_env::Error::FromPathsError( + from_paths::Error::MissingConfigPath + )) + ), + "this is a failure of resolving the include path, after trying to include it" + ); + } + + let absolute_path = format!( + "{}{}include.config", + env.home_dir().display(), + std::path::MAIN_SEPARATOR + ); + { + let _environment = crate::file::from_env::Env::new() + .set("GIT_CONFIG_COUNT", "1") + .set("GIT_CONFIG_KEY_0", format!("includeIf.gitdir:./worktree/.path")) + .set("GIT_CONFIG_VALUE_0", &absolute_path); + + let res = git_config::File::from_env(env.include_options()); + assert!( + matches!( + res, + Err(git_config::file::from_env::Error::FromPathsError( + from_paths::Error::MissingConfigPath + )) + ), + "here the pattern path tries to be resolved and fails as target config isn't set" + ); + } + + { + let _environment = crate::file::from_env::Env::new() + .set("GIT_CONFIG_COUNT", "1") + .set( + "GIT_CONFIG_KEY_0", + format!("includeIf.gitdir:{}.path", escape_backslashes(env.git_dir())), + ) + .set("GIT_CONFIG_VALUE_0", absolute_path); + + let res = git_config::File::from_env(env.include_options()); + dbg!(&res); + assert!(res.is_ok(), "missing paths are ignored as before"); + } + + Ok(()) } #[test] diff --git a/git-config/tests/file/from_paths/includes/conditional/gitdir/util.rs b/git-config/tests/file/from_paths/includes/conditional/gitdir/util.rs index 3963e63964f..b2b25f208c1 100644 --- a/git-config/tests/file/from_paths/includes/conditional/gitdir/util.rs +++ b/git-config/tests/file/from_paths/includes/conditional/gitdir/util.rs @@ -79,7 +79,7 @@ impl GitEnv { } impl GitEnv { - fn include_options(&self) -> git_config::file::from_paths::Options { + pub fn include_options(&self) -> git_config::file::from_paths::Options { let mut opts = options_with_git_dir(self.git_dir()); opts.home_dir = Some(self.home_dir()); opts From dfa1e05d3c983f1e8b1cb3b80d03608341187883 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 6 Jul 2022 10:18:24 +0800 Subject: [PATCH 023/366] change!: `realpath()` handles `cwd` internally (#331) This makes for more convenient usage in the common case. --- git-path/src/realpath.rs | 23 +++++++++++++++++------ git-path/tests/realpath/mod.rs | 2 +- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/git-path/src/realpath.rs b/git-path/src/realpath.rs index 88819f22499..9b822a209b9 100644 --- a/git-path/src/realpath.rs +++ b/git-path/src/realpath.rs @@ -5,14 +5,20 @@ pub enum Error { #[error("The maximum allowed number {} of symlinks in path is exceeded", .max_symlinks)] MaxSymlinksExceeded { max_symlinks: u8 }, #[error(transparent)] - ReadLink(#[from] std::io::Error), + ReadLink(std::io::Error), + #[error(transparent)] + CurrentWorkingDir(std::io::Error), #[error("Empty is not a valid path")] EmptyPath, #[error("Ran out of path components while following parent component '..'")] MissingParent, } +/// The default amount of symlinks we may follow when resolving a path in [`realpath`]. +pub const MAX_SYMLINKS: u8 = 32; + pub(crate) mod function { + use crate::realpath::MAX_SYMLINKS; use std::path::{ Component::{CurDir, Normal, ParentDir, Prefix, RootDir}, Path, PathBuf, @@ -23,10 +29,15 @@ pub(crate) mod function { /// Check each component of `path` and see if it is a symlink. If so, resolve it. /// Do not fail for non-existing components, but assume these are as is. /// - /// If `path` is relative, `cwd` will be used to make it absolute (assuming `cwd` is absolute too). - pub fn realpath(path: impl AsRef, cwd: impl AsRef) -> Result { - let git_default = 32; - realpath_opts(path, cwd, git_default) + /// If `path` is relative, the current working directory be used to make it absolute. + pub fn realpath(path: impl AsRef) -> Result { + let cwd = path + .as_ref() + .is_relative() + .then(|| std::env::current_dir()) + .unwrap_or_else(|| Ok(PathBuf::default())) + .map_err(Error::CurrentWorkingDir)?; + realpath_opts(path, cwd, MAX_SYMLINKS) } /// The same as [`realpath()`], but allow to configure `max_symlinks` to configure how many symbolic links we are going to follow. @@ -61,7 +72,7 @@ pub(crate) mod function { if num_symlinks > max_symlinks { return Err(Error::MaxSymlinksExceeded { max_symlinks }); } - let mut link_destination = std::fs::read_link(real_path.as_path())?; + let mut link_destination = std::fs::read_link(real_path.as_path()).map_err(Error::ReadLink)?; if link_destination.is_absolute() { // pushing absolute path to real_path resets it to the pushed absolute path } else { diff --git a/git-path/tests/realpath/mod.rs b/git-path/tests/realpath/mod.rs index 3169b76b731..56a2596b73d 100644 --- a/git-path/tests/realpath/mod.rs +++ b/git-path/tests/realpath/mod.rs @@ -139,6 +139,6 @@ fn create_symlink(from: impl AsRef, to: impl AsRef) -> std::io::Resu } fn canonicalized_tempdir() -> crate::Result { - let canonicalized_tempdir = git_path::realpath(std::env::temp_dir(), std::env::current_dir()?)?; + let canonicalized_tempdir = git_path::realpath(std::env::temp_dir())?; Ok(tempfile::tempdir_in(canonicalized_tempdir)?) } From 4420ae932d5b20a9662a6d36353a27111b5cd672 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 6 Jul 2022 10:22:26 +0800 Subject: [PATCH 024/366] adjustments due to breaking changes in `git_path` (#331) --- git-config/src/file/resolve_includes.rs | 3 +-- .../file/from_paths/includes/conditional/gitdir/util.rs | 5 ++--- git-discover/src/upwards.rs | 2 +- git-discover/tests/upwards/mod.rs | 2 +- git-odb/src/alternate/mod.rs | 5 +++-- git-repository/src/repository/location.rs | 4 +++- src/porcelain/options.rs | 2 +- 7 files changed, 12 insertions(+), 11 deletions(-) diff --git a/git-config/src/file/resolve_includes.rs b/git-config/src/file/resolve_includes.rs index 7388d3e9ed5..6a49772613d 100644 --- a/git-config/src/file/resolve_includes.rs +++ b/git-config/src/file/resolve_includes.rs @@ -198,8 +198,7 @@ fn gitdir_matches( return Ok(true); } - let expanded_git_dir = git_path::realpath(git_path::from_byte_slice(&git_dir), std::env::current_dir().unwrap())?; - let expanded_git_dir = git_path::into_bstr(expanded_git_dir); + let expanded_git_dir = git_path::into_bstr(git_path::realpath(git_path::from_byte_slice(&git_dir))?); Ok(git_glob::wildmatch( pattern_path.as_bstr(), expanded_git_dir.as_bstr(), diff --git a/git-config/tests/file/from_paths/includes/conditional/gitdir/util.rs b/git-config/tests/file/from_paths/includes/conditional/gitdir/util.rs index b2b25f208c1..99a89371c2c 100644 --- a/git-config/tests/file/from_paths/includes/conditional/gitdir/util.rs +++ b/git-config/tests/file/from_paths/includes/conditional/gitdir/util.rs @@ -60,11 +60,10 @@ impl Condition { impl GitEnv { pub fn repo_name(repo_name: impl AsRef) -> crate::Result { let tempdir = tempfile::tempdir()?; - let cwd = std::env::current_dir()?; - let root_dir = git_path::realpath(tempdir.path(), &cwd)?; + let root_dir = git_path::realpath(tempdir.path())?; let worktree_dir = root_dir.join(repo_name); std::fs::create_dir_all(&worktree_dir)?; - let home_dir = git_path::realpath(tempdir.path(), cwd)?; + let home_dir = git_path::realpath(tempdir.path())?; Ok(Self { tempdir, root_dir, diff --git a/git-discover/src/upwards.rs b/git-discover/src/upwards.rs index 71af066a71a..9360746768b 100644 --- a/git-discover/src/upwards.rs +++ b/git-discover/src/upwards.rs @@ -102,7 +102,7 @@ fn parse_ceiling_dirs(ceiling_dirs: &[u8]) -> Vec { } if should_normalize { - if let Ok(normalized) = git_path::realpath(&dir, "") { + if let Ok(normalized) = git_path::realpath(&dir) { dir = Cow::Owned(normalized); } } diff --git a/git-discover/tests/upwards/mod.rs b/git-discover/tests/upwards/mod.rs index 7374d0ab822..48ba128a2b5 100644 --- a/git-discover/tests/upwards/mod.rs +++ b/git-discover/tests/upwards/mod.rs @@ -162,7 +162,7 @@ fn from_existing_worktree() -> crate::Result { assert_eq!(trust, expected_trust()); let (git_dir, worktree) = path.into_repository_and_work_tree_directories(); assert_eq!( - git_dir.strip_prefix(git_path::realpath(&top_level_repo, std::env::current_dir()?).unwrap()), + git_dir.strip_prefix(git_path::realpath(&top_level_repo).unwrap()), Ok(std::path::Path::new(expected_git_dir)), "we don't skip over worktrees and discover their git dir (gitdir is absolute in file)" ); diff --git a/git-odb/src/alternate/mod.rs b/git-odb/src/alternate/mod.rs index 418124a193e..ba55173f1f2 100644 --- a/git-odb/src/alternate/mod.rs +++ b/git-odb/src/alternate/mod.rs @@ -16,6 +16,7 @@ //! ``` //! //! Based on the [canonical implementation](https://github.com/git/git/blob/master/sha1-file.c#L598:L609). +use git_path::realpath::MAX_SYMLINKS; use std::{fs, io, path::PathBuf}; /// @@ -45,13 +46,13 @@ pub fn resolve(objects_directory: impl Into) -> Result, Er let mut dirs = vec![(0, relative_base.clone())]; let mut out = Vec::new(); let cwd = std::env::current_dir()?; - let mut seen = vec![git_path::realpath(&relative_base, &cwd)?]; + let mut seen = vec![git_path::realpath_opts(&relative_base, &cwd, MAX_SYMLINKS)?]; while let Some((depth, dir)) = dirs.pop() { match fs::read(dir.join("info").join("alternates")) { Ok(input) => { for path in parse::content(&input)?.into_iter() { let path = relative_base.join(path); - let path_canonicalized = git_path::realpath(&path, &cwd)?; + let path_canonicalized = git_path::realpath_opts(&path, &cwd, MAX_SYMLINKS)?; if seen.contains(&path_canonicalized) { return Err(Error::Cycle(seen)); } diff --git a/git-repository/src/repository/location.rs b/git-repository/src/repository/location.rs index ab7ef25fc1e..0e23cbc0a1a 100644 --- a/git-repository/src/repository/location.rs +++ b/git-repository/src/repository/location.rs @@ -1,3 +1,5 @@ +use git_path::realpath::MAX_SYMLINKS; + impl crate::Repository { /// Returns the main git repository if this is a repository on a linked work-tree, or the `git_dir` itself. pub fn common_dir(&self) -> &std::path::Path { @@ -27,7 +29,7 @@ impl crate::Repository { pub fn prefix(&self) -> Option> { self.work_tree.as_ref().map(|root| { std::env::current_dir().and_then(|cwd| { - git_path::realpath(root, &cwd) + git_path::realpath_opts(root, &cwd, MAX_SYMLINKS) .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err)) .and_then(|root| { cwd.strip_prefix(&root) diff --git a/src/porcelain/options.rs b/src/porcelain/options.rs index e6ecb8a37cf..4e55689d487 100644 --- a/src/porcelain/options.rs +++ b/src/porcelain/options.rs @@ -113,7 +113,7 @@ mod validator { fn is_repo_inner(dir: &OsStr) -> anyhow::Result<()> { let git_dir = PathBuf::from(dir).join(".git"); - let p = git::path::realpath(&git_dir, std::env::current_dir()?) + let p = git::path::realpath(&git_dir) .with_context(|| format!("Could not canonicalize git repository at '{}'", git_dir.display()))?; if p.extension().unwrap_or_default() == "git" || p.file_name().unwrap_or_default() == ".git" From 7a2a31e5758a2be8434f22cd9401ac00539f2bd9 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 6 Jul 2022 10:22:52 +0800 Subject: [PATCH 025/366] thanks clippy --- .../tests/file/from_paths/includes/conditional/gitdir/mod.rs | 3 +-- git-path/src/realpath.rs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs b/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs index f513902d97b..f1efd6af6d7 100644 --- a/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs +++ b/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs @@ -118,7 +118,7 @@ fn dot_slash_from_environment_causes_error() -> crate::Result { { let _environment = crate::file::from_env::Env::new() .set("GIT_CONFIG_COUNT", "1") - .set("GIT_CONFIG_KEY_0", format!("includeIf.gitdir:./worktree/.path")) + .set("GIT_CONFIG_KEY_0", "includeIf.gitdir:./worktree/.path") .set("GIT_CONFIG_VALUE_0", &absolute_path); let res = git_config::File::from_env(env.include_options()); @@ -143,7 +143,6 @@ fn dot_slash_from_environment_causes_error() -> crate::Result { .set("GIT_CONFIG_VALUE_0", absolute_path); let res = git_config::File::from_env(env.include_options()); - dbg!(&res); assert!(res.is_ok(), "missing paths are ignored as before"); } diff --git a/git-path/src/realpath.rs b/git-path/src/realpath.rs index 9b822a209b9..0755dfb5894 100644 --- a/git-path/src/realpath.rs +++ b/git-path/src/realpath.rs @@ -34,7 +34,7 @@ pub(crate) mod function { let cwd = path .as_ref() .is_relative() - .then(|| std::env::current_dir()) + .then(std::env::current_dir) .unwrap_or_else(|| Ok(PathBuf::default())) .map_err(Error::CurrentWorkingDir)?; realpath_opts(path, cwd, MAX_SYMLINKS) From 34aed2fb608df79bdc56b244f7ac216f46322e5f Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 6 Jul 2022 10:55:49 +0800 Subject: [PATCH 026/366] fix build after breaking changes in `git-path` (#331) --- git-sec/src/identity.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/git-sec/src/identity.rs b/git-sec/src/identity.rs index 61e1c7cfb85..d6523da4284 100644 --- a/git-sec/src/identity.rs +++ b/git-sec/src/identity.rs @@ -83,11 +83,7 @@ mod impl_ { // Home is not actually owned by the corresponding user // but it can be considered de-facto owned by the user // Ignore errors here and just do the regular checks below - if std::env::current_dir() - .ok() - .and_then(|cwd| git_path::realpath(path, cwd).ok()) - == dirs::home_dir() - { + if git_path::realpath(path).ok() == dirs::home_dir() { return Ok(true); } From d4fbf2ea71ee1f285c195dd00bfa4e21bf429922 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 6 Jul 2022 11:14:24 +0800 Subject: [PATCH 027/366] Make symlink tests so that they test real-path conversion (#331) --- .../from_paths/includes/conditional/gitdir/mod.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs b/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs index f1efd6af6d7..4745f37af90 100644 --- a/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs +++ b/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs @@ -76,10 +76,10 @@ fn absolute_worktree_dir_with_os_separators_matches_with_trailing_glob() -> crat #[test] fn dot_slash_path_is_replaced_with_directory_containing_the_including_config_file() -> crate::Result { - // TODO: understand this assert_section_value( Condition::new("gitdir:./").set_user_config_instead_of_repo_config(), GitEnv::repo_name("worktree")?, + // the user configuration is in $HOME, which is parent to $HOME/worktree, and the pattern path ends up being $HOME/**, including worktree/.git ) } @@ -160,13 +160,8 @@ fn dot_dot_slash_prefixes_are_not_special_and_are_not_what_you_want() -> crate:: } #[test] -#[ignore] fn leading_dots_are_not_special() -> crate::Result { - // TODO: write this test so that it could fail - right now it's naturally correct - assert_section_value( - Condition::new("gitdir:.hidden/").expect_original_value(), - GitEnv::repo_name(".hidden")?, - ) + assert_section_value(Condition::new("gitdir:.hidden/"), GitEnv::repo_name(".hidden")?) } #[test] @@ -219,7 +214,7 @@ fn star_star_in_the_middle() -> crate::Result { #[cfg(not(windows))] fn tilde_expansion_with_symlink() -> crate::Result { let env = util::git_env_with_symlinked_repo()?; - assert_section_value(Condition::new("gitdir:~/symlink-worktree/"), env) + assert_section_value(Condition::new("gitdir:~/worktree/"), env) } #[test] From 4f8e3b169e57d599439c7abc861c82c08bcd92e3 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 6 Jul 2022 11:19:17 +0800 Subject: [PATCH 028/366] fix docs --- git-path/src/realpath.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-path/src/realpath.rs b/git-path/src/realpath.rs index 0755dfb5894..c90e9229b47 100644 --- a/git-path/src/realpath.rs +++ b/git-path/src/realpath.rs @@ -14,7 +14,7 @@ pub enum Error { MissingParent, } -/// The default amount of symlinks we may follow when resolving a path in [`realpath`]. +/// The default amount of symlinks we may follow when resolving a path in [`realpath()`][crate::realpath()]. pub const MAX_SYMLINKS: u8 = 32; pub(crate) mod function { From 4fe33c1cbe478dd7118154d5f0d8d51cf40a66bc Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 6 Jul 2022 11:50:24 +0800 Subject: [PATCH 029/366] update dependencies to fix cargo-deny error (#331) --- Cargo.lock | 90 +++++++++++++++++++++++++++--------------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6fefa4c664d..8a5155c3df2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -316,7 +316,7 @@ dependencies = [ "bitflags", "cargo_metadata", "cargo_toml", - "clap 3.2.6", + "clap 3.2.8", "crates-index", "env_logger", "git-conventional", @@ -408,9 +408,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.6" +version = "3.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1fe12880bae935d142c8702d500c63a4e8634b6c3c57ad72bf978fc7b6249a" +checksum = "190814073e85d238f31ff738fcb0bf6910cedeb73376c87cd69291028966fd83" dependencies = [ "atty", "bitflags", @@ -425,9 +425,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "3.2.6" +version = "3.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6db9e867166a43a53f7199b5e4d1f522a1e5bd626654be263c999ce59df39a" +checksum = "759bf187376e1afa7b85b959e6a664a3e7a95203415dba952ad19139e798f902" dependencies = [ "heck", "proc-macro-error", @@ -438,9 +438,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87eba3c8c7f42ef17f6c659fc7416d0f4758cd3e58861ee63c5fa4a4dde649e4" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" dependencies = [ "os_str_bytes", ] @@ -478,9 +478,9 @@ checksum = "f3f6d59c71e7dc3af60f0af9db32364d96a16e9310f3f5db2b55ed642162dd35" [[package]] name = "compact_str" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb6f80f92629b81f5b17b616a99f72870556ca457bbbd99aeda7bb5d194316a2" +checksum = "e33b5c3ee2b4ffa00ac2b00d1645cd9229ade668139bccf95f15fadcf374127b" dependencies = [ "castaway", "itoa 1.0.2", @@ -712,9 +712,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +checksum = "5999502d32b9c48d492abe66392408144895020ec4709e549e840799f3bb74c0" dependencies = [ "generic-array", "typenum", @@ -802,9 +802,9 @@ checksum = "647605a6345d5e89c3950a36a638c56478af9b414c55c6f2477c73b115f9acde" [[package]] name = "diff" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" [[package]] name = "diffing" @@ -861,9 +861,9 @@ checksum = "b01c09fd63b5136fba41aa625c7b3254f0aa0a435ff6ec4b2c9a28d496c83c88" [[package]] name = "either" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" [[package]] name = "encode_unicode" @@ -900,14 +900,14 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0408e2626025178a6a7f7ffc05a25bc47103229f19c113755de7bf63816290c" +checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c" dependencies = [ "cfg-if", "libc", "redox_syscall", - "winapi", + "windows-sys", ] [[package]] @@ -1646,7 +1646,7 @@ version = "0.13.0" dependencies = [ "anyhow", "atty", - "clap 3.2.6", + "clap 3.2.8", "crosstermion", "document-features", "env_logger", @@ -2108,9 +2108,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" +checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" [[package]] name = "oorandom" @@ -2126,9 +2126,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "111.21.0+1.1.1p" +version = "111.22.0+1.1.1q" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d0a8313729211913936f1b95ca47a5fc7f2e04cd658c115388287f8a8361008" +checksum = "8f31f0d509d1c1ae9cada2f9539ff8f37933831fd5098879e482aa687d659853" dependencies = [ "cc", ] @@ -2242,9 +2242,9 @@ checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" [[package]] name = "plotters" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a3fd9ec30b9749ce28cd91f255d569591cdf937fe280c312143e3c4bad6f2a" +checksum = "9428003b84df1496fb9d6eeee9c5f8145cb41ca375eb0dad204328888832811f" dependencies = [ "num-traits", "plotters-backend", @@ -2255,15 +2255,15 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d88417318da0eaf0fdcdb51a0ee6c3bed624333bff8f946733049380be67ac1c" +checksum = "1c89e57ae773e34419b0f62d68c1934a97ac0637f36741dfde4efb88aaf711a0" [[package]] name = "plotters-svg" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521fa9638fa597e1dc53e9412a4f9cefb01187ee1f7413076f9e6749e2885ba9" +checksum = "e0918736323d1baff32ee0eade54984f6f201ad7e97d5cfb5d6ab4a358529615" dependencies = [ "plotters-backend", ] @@ -2423,9 +2423,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.5.6" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" dependencies = [ "regex-syntax", ] @@ -2438,9 +2438,9 @@ checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" [[package]] name = "regex-syntax" -version = "0.6.26" +version = "0.6.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" [[package]] name = "remove_dir_all" @@ -2505,18 +2505,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "semver" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41d061efea015927ac527063765e73601444cdc344ba855bc7bd44578b25e1c" +checksum = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1" dependencies = [ "serde", ] [[package]] name = "serde" -version = "1.0.137" +version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +checksum = "1578c6245786b9d168c5447eeacfb96856573ca56c9d68fdcf394be134882a47" dependencies = [ "serde_derive", ] @@ -2533,9 +2533,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.137" +version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +checksum = "023e9b1467aef8a10fb88f25611870ada9800ef7e22afce356bb0d2387b6f27c" dependencies = [ "proc-macro2", "quote", @@ -2544,9 +2544,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" dependencies = [ "itoa 1.0.2", "ryu", @@ -2661,9 +2661,9 @@ checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" [[package]] name = "smallvec" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc88c725d61fc6c3132893370cac4a0200e3fedf5da8331c570664b1987f5ca2" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" dependencies = [ "serde", ] @@ -2933,9 +2933,9 @@ checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" [[package]] name = "unicode-normalization" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dee68f85cab8cf68dec42158baf3a79a1cdc065a8b103025965d6ccb7f6cbd" +checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" dependencies = [ "tinyvec", ] From 38f31174e8c117af675cdfbc21926133b821ec38 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 6 Jul 2022 13:18:03 +0800 Subject: [PATCH 030/366] change!: move `Color` to own `value` module (#331) --- git-config/src/value.rs | 3 + git-config/src/value/color.rs | 386 +++++++++++++++++++++ git-config/src/values.rs | 380 -------------------- git-config/tests/file/access/read_only.rs | 1 + git-config/tests/values/color_attribute.rs | 2 +- git-config/tests/values/color_value.rs | 2 +- 6 files changed, 392 insertions(+), 382 deletions(-) create mode 100644 git-config/src/value/color.rs diff --git a/git-config/src/value.rs b/git-config/src/value.rs index b113100165d..5a28dab567a 100644 --- a/git-config/src/value.rs +++ b/git-config/src/value.rs @@ -27,3 +27,6 @@ pub mod parse { } } } + +mod color; +pub use color::{Color, ColorAttribute, ColorValue}; diff --git a/git-config/src/value/color.rs b/git-config/src/value/color.rs new file mode 100644 index 00000000000..d4bf8f9352c --- /dev/null +++ b/git-config/src/value/color.rs @@ -0,0 +1,386 @@ +use crate::value; +use bstr::{BStr, BString}; +use std::borrow::Cow; +use std::convert::TryFrom; +use std::fmt::Display; +use std::str::FromStr; + +/// Any value that may contain a foreground color, background color, a +/// collection of color (text) modifiers, or a combination of any of the +/// aforementioned values. +/// +/// Note that `git-config` allows color values to simply be a collection of +/// [`ColorAttribute`]s, and does not require a [`ColorValue`] for either the +/// foreground or background color. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] +pub struct Color { + /// A provided foreground color + pub foreground: Option, + /// A provided background color + pub background: Option, + /// A potentially empty list of text attributes + pub attributes: Vec, +} + +impl Color { + /// Generates a byte representation of the value. This should be used when + /// non-UTF-8 sequences are present or a UTF-8 representation can't be + /// guaranteed. + #[must_use] + pub fn to_bstring(&self) -> BString { + self.into() + } +} + +impl Display for Color { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(fg) = self.foreground { + fg.fmt(f)?; + } + + write!(f, " ")?; + + if let Some(bg) = self.background { + bg.fmt(f)?; + } + + self.attributes + .iter() + .try_for_each(|attr| write!(f, " ").and_then(|_| attr.fmt(f))) + } +} + +#[cfg(feature = "serde")] +impl Serialize for Color { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + // todo: maybe not? + serializer.serialize_str(&self.to_string()) + } +} + +fn color_err(input: impl Into) -> value::parse::Error { + value::parse::Error::new( + "Colors are specific color values and their attributes, like 'brightred', or 'blue'", + input, + ) +} + +impl TryFrom<&BStr> for Color { + type Error = value::parse::Error; + + fn try_from(s: &BStr) -> Result { + let s = std::str::from_utf8(s).map_err(|err| color_err(s).with_err(err))?; + enum ColorItem { + Value(ColorValue), + Attr(ColorAttribute), + } + + let items = s.split_whitespace().filter_map(|s| { + if s.is_empty() { + return None; + } + + Some( + ColorValue::from_str(s) + .map(ColorItem::Value) + .or_else(|_| ColorAttribute::from_str(s).map(ColorItem::Attr)), + ) + }); + + let mut new_self = Self::default(); + for item in items { + match item { + Ok(item) => match item { + ColorItem::Value(v) => { + if new_self.foreground.is_none() { + new_self.foreground = Some(v); + } else if new_self.background.is_none() { + new_self.background = Some(v); + } else { + return Err(color_err(s)); + } + } + ColorItem::Attr(a) => new_self.attributes.push(a), + }, + Err(_) => return Err(color_err(s)), + } + } + + Ok(new_self) + } +} + +impl TryFrom for Color { + type Error = value::parse::Error; + + fn try_from(value: BString) -> Result { + Self::try_from(value.as_ref()) + } +} + +impl TryFrom> for Color { + type Error = value::parse::Error; + + fn try_from(c: Cow<'_, BStr>) -> Result { + match c { + Cow::Borrowed(c) => Self::try_from(c), + Cow::Owned(c) => Self::try_from(c), + } + } +} + +impl From for BString { + fn from(c: Color) -> Self { + c.into() + } +} + +impl From<&Color> for BString { + fn from(c: &Color) -> Self { + c.to_string().into() + } +} + +/// Discriminating enum for [`Color`] values. +/// +/// `git-config` supports the eight standard colors, their bright variants, an +/// ANSI color code, or a 24-bit hex value prefixed with an octothorpe. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +#[allow(missing_docs)] +pub enum ColorValue { + Normal, + Black, + BrightBlack, + Red, + BrightRed, + Green, + BrightGreen, + Yellow, + BrightYellow, + Blue, + BrightBlue, + Magenta, + BrightMagenta, + Cyan, + BrightCyan, + White, + BrightWhite, + Ansi(u8), + Rgb(u8, u8, u8), +} + +impl Display for ColorValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Normal => write!(f, "normal"), + Self::Black => write!(f, "black"), + Self::BrightBlack => write!(f, "brightblack"), + Self::Red => write!(f, "red"), + Self::BrightRed => write!(f, "brightred"), + Self::Green => write!(f, "green"), + Self::BrightGreen => write!(f, "brightgreen"), + Self::Yellow => write!(f, "yellow"), + Self::BrightYellow => write!(f, "brightyellow"), + Self::Blue => write!(f, "blue"), + Self::BrightBlue => write!(f, "brightblue"), + Self::Magenta => write!(f, "magenta"), + Self::BrightMagenta => write!(f, "brightmagenta"), + Self::Cyan => write!(f, "cyan"), + Self::BrightCyan => write!(f, "brightcyan"), + Self::White => write!(f, "white"), + Self::BrightWhite => write!(f, "brightwhite"), + Self::Ansi(num) => num.fmt(f), + Self::Rgb(r, g, b) => write!(f, "#{:02x}{:02x}{:02x}", r, g, b), + } + } +} + +#[cfg(feature = "serde")] +impl Serialize for ColorValue { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +impl FromStr for ColorValue { + type Err = value::parse::Error; + + fn from_str(s: &str) -> Result { + let mut s = s; + let bright = if s.starts_with("bright") { + s = &s[6..]; + true + } else { + false + }; + + match s { + "normal" if !bright => return Ok(Self::Normal), + "normal" if bright => return Err(color_err(s)), + "black" if !bright => return Ok(Self::Black), + "black" if bright => return Ok(Self::BrightBlack), + "red" if !bright => return Ok(Self::Red), + "red" if bright => return Ok(Self::BrightRed), + "green" if !bright => return Ok(Self::Green), + "green" if bright => return Ok(Self::BrightGreen), + "yellow" if !bright => return Ok(Self::Yellow), + "yellow" if bright => return Ok(Self::BrightYellow), + "blue" if !bright => return Ok(Self::Blue), + "blue" if bright => return Ok(Self::BrightBlue), + "magenta" if !bright => return Ok(Self::Magenta), + "magenta" if bright => return Ok(Self::BrightMagenta), + "cyan" if !bright => return Ok(Self::Cyan), + "cyan" if bright => return Ok(Self::BrightCyan), + "white" if !bright => return Ok(Self::White), + "white" if bright => return Ok(Self::BrightWhite), + _ => (), + } + + if let Ok(v) = u8::from_str(s) { + return Ok(Self::Ansi(v)); + } + + if let Some(s) = s.strip_prefix('#') { + if s.len() == 6 { + let rgb = ( + u8::from_str_radix(&s[..2], 16), + u8::from_str_radix(&s[2..4], 16), + u8::from_str_radix(&s[4..], 16), + ); + + if let (Ok(r), Ok(g), Ok(b)) = rgb { + return Ok(Self::Rgb(r, g, b)); + } + } + } + + Err(color_err(s)) + } +} + +impl TryFrom<&[u8]> for ColorValue { + type Error = value::parse::Error; + + fn try_from(s: &[u8]) -> Result { + Self::from_str(std::str::from_utf8(s).map_err(|err| color_err(s).with_err(err))?) + } +} + +/// Discriminating enum for [`Color`] attributes. +/// +/// `git-config` supports modifiers and their negators. The negating color +/// attributes are equivalent to having a `no` or `no-` prefix to the normal +/// variant. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +#[allow(missing_docs)] +pub enum ColorAttribute { + Bold, + NoBold, + Dim, + NoDim, + Ul, + NoUl, + Blink, + NoBlink, + Reverse, + NoReverse, + Italic, + NoItalic, + Strike, + NoStrike, +} + +impl Display for ColorAttribute { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Bold => write!(f, "bold"), + Self::NoBold => write!(f, "nobold"), + Self::Dim => write!(f, "dim"), + Self::NoDim => write!(f, "nodim"), + Self::Ul => write!(f, "ul"), + Self::NoUl => write!(f, "noul"), + Self::Blink => write!(f, "blink"), + Self::NoBlink => write!(f, "noblink"), + Self::Reverse => write!(f, "reverse"), + Self::NoReverse => write!(f, "noreverse"), + Self::Italic => write!(f, "italic"), + Self::NoItalic => write!(f, "noitalic"), + Self::Strike => write!(f, "strike"), + Self::NoStrike => write!(f, "nostrike"), + } + } +} + +#[cfg(feature = "serde")] +impl Serialize for ColorAttribute { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(match self { + Self::Bold => "bold", + Self::NoBold => "nobold", + Self::Dim => "dim", + Self::NoDim => "nodim", + Self::Ul => "ul", + Self::NoUl => "noul", + Self::Blink => "blink", + Self::NoBlink => "noblink", + Self::Reverse => "reverse", + Self::NoReverse => "noreverse", + Self::Italic => "italic", + Self::NoItalic => "noitalic", + Self::Strike => "strike", + Self::NoStrike => "nostrike", + }) + } +} + +impl FromStr for ColorAttribute { + type Err = value::parse::Error; + + fn from_str(s: &str) -> Result { + let inverted = s.starts_with("no"); + let mut parsed = s; + + if inverted { + parsed = &parsed[2..]; + + if parsed.starts_with('-') { + parsed = &parsed[1..]; + } + } + + match parsed { + "bold" if !inverted => Ok(Self::Bold), + "bold" if inverted => Ok(Self::NoBold), + "dim" if !inverted => Ok(Self::Dim), + "dim" if inverted => Ok(Self::NoDim), + "ul" if !inverted => Ok(Self::Ul), + "ul" if inverted => Ok(Self::NoUl), + "blink" if !inverted => Ok(Self::Blink), + "blink" if inverted => Ok(Self::NoBlink), + "reverse" if !inverted => Ok(Self::Reverse), + "reverse" if inverted => Ok(Self::NoReverse), + "italic" if !inverted => Ok(Self::Italic), + "italic" if inverted => Ok(Self::NoItalic), + "strike" if !inverted => Ok(Self::Strike), + "strike" if inverted => Ok(Self::NoStrike), + _ => Err(color_err(parsed)), + } + } +} + +impl TryFrom<&[u8]> for ColorAttribute { + type Error = value::parse::Error; + + fn try_from(s: &[u8]) -> Result { + Self::from_str(std::str::from_utf8(s).map_err(|err| color_err(s).with_err(err))?) + } +} diff --git a/git-config/src/values.rs b/git-config/src/values.rs index 70165b0ddca..7bd7d6ea663 100644 --- a/git-config/src/values.rs +++ b/git-config/src/values.rs @@ -732,383 +732,3 @@ impl TryFrom for IntegerSuffix { Self::try_from(value.as_ref()) } } - -/// Any value that may contain a foreground color, background color, a -/// collection of color (text) modifiers, or a combination of any of the -/// aforementioned values. -/// -/// Note that `git-config` allows color values to simply be a collection of -/// [`ColorAttribute`]s, and does not require a [`ColorValue`] for either the -/// foreground or background color. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] -pub struct Color { - /// A provided foreground color - pub foreground: Option, - /// A provided background color - pub background: Option, - /// A potentially empty list of text attributes - pub attributes: Vec, -} - -impl Color { - /// Generates a byte representation of the value. This should be used when - /// non-UTF-8 sequences are present or a UTF-8 representation can't be - /// guaranteed. - #[must_use] - pub fn to_bstring(&self) -> BString { - self.into() - } -} - -impl Display for Color { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if let Some(fg) = self.foreground { - fg.fmt(f)?; - } - - write!(f, " ")?; - - if let Some(bg) = self.background { - bg.fmt(f)?; - } - - self.attributes - .iter() - .try_for_each(|attr| write!(f, " ").and_then(|_| attr.fmt(f))) - } -} - -#[cfg(feature = "serde")] -impl Serialize for Color { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - // todo: maybe not? - serializer.serialize_str(&self.to_string()) - } -} - -fn color_err(input: impl Into) -> value::parse::Error { - value::parse::Error::new( - "Colors are specific color values and their attributes, like 'brightred', or 'blue'", - input, - ) -} - -impl TryFrom<&BStr> for Color { - type Error = value::parse::Error; - - fn try_from(s: &BStr) -> Result { - let s = std::str::from_utf8(s).map_err(|err| color_err(s).with_err(err))?; - enum ColorItem { - Value(ColorValue), - Attr(ColorAttribute), - } - - let items = s.split_whitespace().filter_map(|s| { - if s.is_empty() { - return None; - } - - Some( - ColorValue::from_str(s) - .map(ColorItem::Value) - .or_else(|_| ColorAttribute::from_str(s).map(ColorItem::Attr)), - ) - }); - - let mut new_self = Self::default(); - for item in items { - match item { - Ok(item) => match item { - ColorItem::Value(v) => { - if new_self.foreground.is_none() { - new_self.foreground = Some(v); - } else if new_self.background.is_none() { - new_self.background = Some(v); - } else { - return Err(color_err(s)); - } - } - ColorItem::Attr(a) => new_self.attributes.push(a), - }, - Err(_) => return Err(color_err(s)), - } - } - - Ok(new_self) - } -} - -impl TryFrom for Color { - type Error = value::parse::Error; - - fn try_from(value: BString) -> Result { - Self::try_from(value.as_ref()) - } -} - -impl TryFrom> for Color { - type Error = value::parse::Error; - - fn try_from(c: Cow<'_, BStr>) -> Result { - match c { - Cow::Borrowed(c) => Self::try_from(c), - Cow::Owned(c) => Self::try_from(c), - } - } -} - -impl From for BString { - fn from(c: Color) -> Self { - c.into() - } -} - -impl From<&Color> for BString { - fn from(c: &Color) -> Self { - c.to_string().into() - } -} - -/// Discriminating enum for [`Color`] values. -/// -/// `git-config` supports the eight standard colors, their bright variants, an -/// ANSI color code, or a 24-bit hex value prefixed with an octothorpe. -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] -#[allow(missing_docs)] -pub enum ColorValue { - Normal, - Black, - BrightBlack, - Red, - BrightRed, - Green, - BrightGreen, - Yellow, - BrightYellow, - Blue, - BrightBlue, - Magenta, - BrightMagenta, - Cyan, - BrightCyan, - White, - BrightWhite, - Ansi(u8), - Rgb(u8, u8, u8), -} - -impl Display for ColorValue { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Normal => write!(f, "normal"), - Self::Black => write!(f, "black"), - Self::BrightBlack => write!(f, "brightblack"), - Self::Red => write!(f, "red"), - Self::BrightRed => write!(f, "brightred"), - Self::Green => write!(f, "green"), - Self::BrightGreen => write!(f, "brightgreen"), - Self::Yellow => write!(f, "yellow"), - Self::BrightYellow => write!(f, "brightyellow"), - Self::Blue => write!(f, "blue"), - Self::BrightBlue => write!(f, "brightblue"), - Self::Magenta => write!(f, "magenta"), - Self::BrightMagenta => write!(f, "brightmagenta"), - Self::Cyan => write!(f, "cyan"), - Self::BrightCyan => write!(f, "brightcyan"), - Self::White => write!(f, "white"), - Self::BrightWhite => write!(f, "brightwhite"), - Self::Ansi(num) => num.fmt(f), - Self::Rgb(r, g, b) => write!(f, "#{:02x}{:02x}{:02x}", r, g, b), - } - } -} - -#[cfg(feature = "serde")] -impl Serialize for ColorValue { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_str(&self.to_string()) - } -} - -impl FromStr for ColorValue { - type Err = value::parse::Error; - - fn from_str(s: &str) -> Result { - let mut s = s; - let bright = if s.starts_with("bright") { - s = &s[6..]; - true - } else { - false - }; - - match s { - "normal" if !bright => return Ok(Self::Normal), - "normal" if bright => return Err(color_err(s)), - "black" if !bright => return Ok(Self::Black), - "black" if bright => return Ok(Self::BrightBlack), - "red" if !bright => return Ok(Self::Red), - "red" if bright => return Ok(Self::BrightRed), - "green" if !bright => return Ok(Self::Green), - "green" if bright => return Ok(Self::BrightGreen), - "yellow" if !bright => return Ok(Self::Yellow), - "yellow" if bright => return Ok(Self::BrightYellow), - "blue" if !bright => return Ok(Self::Blue), - "blue" if bright => return Ok(Self::BrightBlue), - "magenta" if !bright => return Ok(Self::Magenta), - "magenta" if bright => return Ok(Self::BrightMagenta), - "cyan" if !bright => return Ok(Self::Cyan), - "cyan" if bright => return Ok(Self::BrightCyan), - "white" if !bright => return Ok(Self::White), - "white" if bright => return Ok(Self::BrightWhite), - _ => (), - } - - if let Ok(v) = u8::from_str(s) { - return Ok(Self::Ansi(v)); - } - - if let Some(s) = s.strip_prefix('#') { - if s.len() == 6 { - let rgb = ( - u8::from_str_radix(&s[..2], 16), - u8::from_str_radix(&s[2..4], 16), - u8::from_str_radix(&s[4..], 16), - ); - - if let (Ok(r), Ok(g), Ok(b)) = rgb { - return Ok(Self::Rgb(r, g, b)); - } - } - } - - Err(color_err(s)) - } -} - -impl TryFrom<&[u8]> for ColorValue { - type Error = value::parse::Error; - - fn try_from(s: &[u8]) -> Result { - Self::from_str(std::str::from_utf8(s).map_err(|err| color_err(s).with_err(err))?) - } -} - -/// Discriminating enum for [`Color`] attributes. -/// -/// `git-config` supports modifiers and their negators. The negating color -/// attributes are equivalent to having a `no` or `no-` prefix to the normal -/// variant. -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] -#[allow(missing_docs)] -pub enum ColorAttribute { - Bold, - NoBold, - Dim, - NoDim, - Ul, - NoUl, - Blink, - NoBlink, - Reverse, - NoReverse, - Italic, - NoItalic, - Strike, - NoStrike, -} - -impl Display for ColorAttribute { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Bold => write!(f, "bold"), - Self::NoBold => write!(f, "nobold"), - Self::Dim => write!(f, "dim"), - Self::NoDim => write!(f, "nodim"), - Self::Ul => write!(f, "ul"), - Self::NoUl => write!(f, "noul"), - Self::Blink => write!(f, "blink"), - Self::NoBlink => write!(f, "noblink"), - Self::Reverse => write!(f, "reverse"), - Self::NoReverse => write!(f, "noreverse"), - Self::Italic => write!(f, "italic"), - Self::NoItalic => write!(f, "noitalic"), - Self::Strike => write!(f, "strike"), - Self::NoStrike => write!(f, "nostrike"), - } - } -} - -#[cfg(feature = "serde")] -impl Serialize for ColorAttribute { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_str(match self { - Self::Bold => "bold", - Self::NoBold => "nobold", - Self::Dim => "dim", - Self::NoDim => "nodim", - Self::Ul => "ul", - Self::NoUl => "noul", - Self::Blink => "blink", - Self::NoBlink => "noblink", - Self::Reverse => "reverse", - Self::NoReverse => "noreverse", - Self::Italic => "italic", - Self::NoItalic => "noitalic", - Self::Strike => "strike", - Self::NoStrike => "nostrike", - }) - } -} - -impl FromStr for ColorAttribute { - type Err = value::parse::Error; - - fn from_str(s: &str) -> Result { - let inverted = s.starts_with("no"); - let mut parsed = s; - - if inverted { - parsed = &parsed[2..]; - - if parsed.starts_with('-') { - parsed = &parsed[1..]; - } - } - - match parsed { - "bold" if !inverted => Ok(Self::Bold), - "bold" if inverted => Ok(Self::NoBold), - "dim" if !inverted => Ok(Self::Dim), - "dim" if inverted => Ok(Self::NoDim), - "ul" if !inverted => Ok(Self::Ul), - "ul" if inverted => Ok(Self::NoUl), - "blink" if !inverted => Ok(Self::Blink), - "blink" if inverted => Ok(Self::NoBlink), - "reverse" if !inverted => Ok(Self::Reverse), - "reverse" if inverted => Ok(Self::NoReverse), - "italic" if !inverted => Ok(Self::Italic), - "italic" if inverted => Ok(Self::NoItalic), - "strike" if !inverted => Ok(Self::Strike), - "strike" if inverted => Ok(Self::NoStrike), - _ => Err(color_err(parsed)), - } - } -} - -impl TryFrom<&[u8]> for ColorAttribute { - type Error = value::parse::Error; - - fn try_from(s: &[u8]) -> Result { - Self::from_str(std::str::from_utf8(s).map_err(|err| color_err(s).with_err(err))?) - } -} diff --git a/git-config/tests/file/access/read_only.rs b/git-config/tests/file/access/read_only.rs index 392b240f125..70c1581f024 100644 --- a/git-config/tests/file/access/read_only.rs +++ b/git-config/tests/file/access/read_only.rs @@ -1,5 +1,6 @@ use crate::file::cow_str; use bstr::BStr; +use git_config::value::{Color, ColorAttribute, ColorValue}; use git_config::{ values::{Boolean, TrueVariant, *}, File, diff --git a/git-config/tests/values/color_attribute.rs b/git-config/tests/values/color_attribute.rs index 6c8b777cd28..bec1f3901f1 100644 --- a/git-config/tests/values/color_attribute.rs +++ b/git-config/tests/values/color_attribute.rs @@ -1,6 +1,6 @@ use std::str::FromStr; -use git_config::values::ColorAttribute; +use git_config::value::ColorAttribute; #[test] fn non_inverted() { diff --git a/git-config/tests/values/color_value.rs b/git-config/tests/values/color_value.rs index 58a80a5dc84..514582702d3 100644 --- a/git-config/tests/values/color_value.rs +++ b/git-config/tests/values/color_value.rs @@ -1,6 +1,6 @@ use std::str::FromStr; -use git_config::values::ColorValue; +use git_config::value::ColorValue; #[test] fn non_bright() { From d4444e18042891b0fe5b9c6e6813fed26df6c560 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 6 Jul 2022 13:29:43 +0800 Subject: [PATCH 031/366] change!: move `values::Integer` into `value` module (#331) --- git-config/src/file/access/comfort.rs | 4 +- .../src/file/access/low_level/read_only.rs | 6 +- git-config/src/value/integer.rs | 217 ++++++++++++++++++ git-config/src/{value.rs => value/mod.rs} | 9 +- git-config/src/values.rs | 213 +---------------- git-config/tests/file/access/read_only.rs | 4 +- git-config/tests/values/integer.rs | 2 +- 7 files changed, 232 insertions(+), 223 deletions(-) create mode 100644 git-config/src/value/integer.rs rename git-config/src/{value.rs => value/mod.rs} (93%) diff --git a/git-config/src/file/access/comfort.rs b/git-config/src/file/access/comfort.rs index 92aca916cd4..9011e08e336 100644 --- a/git-config/src/file/access/comfort.rs +++ b/git-config/src/file/access/comfort.rs @@ -51,7 +51,7 @@ impl<'a> File<'a> { key: &str, ) -> Option> { let int = self.raw_value(section_name, subsection_name, key).ok()?; - Some(values::Integer::try_from(int.as_ref()).and_then(|b| { + Some(value::Integer::try_from(int.as_ref()).and_then(|b| { b.to_decimal() .ok_or_else(|| value::parse::Error::new("Integer overflow", int.into_owned())) })) @@ -78,7 +78,7 @@ impl<'a> File<'a> { values .into_iter() .map(|v| { - values::Integer::try_from(v.as_ref()).and_then(|int| { + value::Integer::try_from(v.as_ref()).and_then(|int| { int.to_decimal() .ok_or_else(|| value::parse::Error::new("Integer overflow", v.into_owned())) }) diff --git a/git-config/src/file/access/low_level/read_only.rs b/git-config/src/file/access/low_level/read_only.rs index d352d0724a3..3316e3ae0ec 100644 --- a/git-config/src/file/access/low_level/read_only.rs +++ b/git-config/src/file/access/low_level/read_only.rs @@ -19,7 +19,7 @@ impl<'a> File<'a> { /// /// ``` /// # use git_config::File; - /// # use git_config::values::{Integer, Boolean}; + /// # use git_config::value::{Integer, Boolean}; /// # use std::borrow::Cow; /// # use std::convert::TryFrom; /// let config = r#" @@ -76,7 +76,7 @@ impl<'a> File<'a> { /// /// ``` /// # use git_config::File; - /// # use git_config::values::{Integer, String, Boolean, TrueVariant}; + /// # use git_config::value::{Integer, String, Boolean, TrueVariant}; /// # use std::borrow::Cow; /// # use std::convert::TryFrom; /// let config = r#" @@ -160,7 +160,7 @@ impl<'a> File<'a> { /// /// ``` /// # use git_config::File; - /// # use git_config::values::{Integer, Boolean, TrueVariant}; + /// # use git_config::value::{Integer, Boolean, TrueVariant}; /// # use std::borrow::Cow; /// # use std::convert::TryFrom; /// let config = r#" diff --git a/git-config/src/value/integer.rs b/git-config/src/value/integer.rs new file mode 100644 index 00000000000..be024d0b00e --- /dev/null +++ b/git-config/src/value/integer.rs @@ -0,0 +1,217 @@ +use crate::value; +use bstr::{BStr, BString}; +use std::borrow::Cow; +use std::convert::TryFrom; +use std::fmt::Display; +use std::str::FromStr; + +/// Any value that can be interpreted as an integer. +/// +/// This supports any numeric value that can fit in a [`i64`], excluding the +/// suffix. The suffix is parsed separately from the value itself, so if you +/// wish to obtain the true value of the integer, you must account for the +/// suffix after fetching the value. [`IntegerSuffix`] provides +/// [`bitwise_offset`] to help with the math, but do be warned that if the value +/// is very large, you may run into overflows. +/// +/// [`BStr`]: bstr::BStr +/// [`bitwise_offset`]: IntegerSuffix::bitwise_offset +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub struct Integer { + /// The value, without any suffix modification + pub value: i64, + /// A provided suffix, if any. + pub suffix: Option, +} + +impl Integer { + /// Generates a byte representation of the value. This should be used when + /// non-UTF-8 sequences are present or a UTF-8 representation can't be + /// guaranteed. + #[must_use] + pub fn to_bstring(self) -> BString { + self.into() + } + + /// Canonicalize values as simple decimal numbers. + /// An optional suffix of k, m, or g (case-insensitive), upon creation, will cause the value to be multiplied by + /// 1024 (k), 1048576 (m), or 1073741824 (g) respectively. + /// + /// Returns the result if no multiplication overflow. + pub fn to_decimal(&self) -> Option { + match self.suffix { + None => Some(self.value), + Some(suffix) => match suffix { + IntegerSuffix::Kibi => self.value.checked_mul(1024), + IntegerSuffix::Mebi => self.value.checked_mul(1024 * 1024), + IntegerSuffix::Gibi => self.value.checked_mul(1024 * 1024 * 1024), + }, + } + } +} + +impl Display for Integer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.value)?; + if let Some(suffix) = self.suffix { + write!(f, "{}", suffix) + } else { + Ok(()) + } + } +} + +#[cfg(feature = "serde")] +impl Serialize for Integer { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + if let Some(suffix) = self.suffix { + serializer.serialize_i64(self.value << suffix.bitwise_offset()) + } else { + serializer.serialize_i64(self.value) + } + } +} + +fn int_err(input: impl Into) -> value::parse::Error { + value::parse::Error::new( + "Integers needs to be positive or negative numbers which may have a suffix like 1k, 42, or 50G", + input, + ) +} + +impl TryFrom<&BStr> for Integer { + type Error = value::parse::Error; + + fn try_from(s: &BStr) -> Result { + let s = std::str::from_utf8(s).map_err(|err| int_err(s).with_err(err))?; + if let Ok(value) = s.parse() { + return Ok(Self { value, suffix: None }); + } + + // Assume we have a prefix at this point. + + if s.len() <= 1 { + return Err(int_err(s)); + } + + let (number, suffix) = s.split_at(s.len() - 1); + if let (Ok(value), Ok(suffix)) = (number.parse(), suffix.parse()) { + Ok(Self { + value, + suffix: Some(suffix), + }) + } else { + Err(int_err(s)) + } + } +} + +impl TryFrom for Integer { + type Error = value::parse::Error; + + fn try_from(value: BString) -> Result { + Self::try_from(value.as_ref()) + } +} + +impl TryFrom> for Integer { + type Error = value::parse::Error; + + fn try_from(c: Cow<'_, BStr>) -> Result { + match c { + Cow::Borrowed(c) => Self::try_from(c), + Cow::Owned(c) => Self::try_from(c), + } + } +} + +impl From for BString { + fn from(i: Integer) -> Self { + i.into() + } +} + +impl From<&Integer> for BString { + fn from(i: &Integer) -> Self { + i.to_string().into() + } +} + +/// Integer prefixes that are supported by `git-config`. +/// +/// These values are base-2 unit of measurements, not the base-10 variants. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +#[allow(missing_docs)] +pub enum IntegerSuffix { + Kibi, + Mebi, + Gibi, +} + +impl IntegerSuffix { + /// Returns the number of bits that the suffix shifts left by. + #[must_use] + pub const fn bitwise_offset(self) -> usize { + match self { + Self::Kibi => 10, + Self::Mebi => 20, + Self::Gibi => 30, + } + } +} + +impl Display for IntegerSuffix { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Kibi => write!(f, "k"), + Self::Mebi => write!(f, "m"), + Self::Gibi => write!(f, "g"), + } + } +} + +#[cfg(feature = "serde")] +impl Serialize for IntegerSuffix { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(match self { + Self::Kibi => "k", + Self::Mebi => "m", + Self::Gibi => "g", + }) + } +} + +impl FromStr for IntegerSuffix { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "k" | "K" => Ok(Self::Kibi), + "m" | "M" => Ok(Self::Mebi), + "g" | "G" => Ok(Self::Gibi), + _ => Err(()), + } + } +} + +impl TryFrom<&[u8]> for IntegerSuffix { + type Error = (); + + fn try_from(s: &[u8]) -> Result { + Self::from_str(std::str::from_utf8(s).map_err(|_| ())?) + } +} + +impl TryFrom for IntegerSuffix { + type Error = (); + + fn try_from(value: BString) -> Result { + Self::try_from(value.as_ref()) + } +} diff --git a/git-config/src/value.rs b/git-config/src/value/mod.rs similarity index 93% rename from git-config/src/value.rs rename to git-config/src/value/mod.rs index 5a28dab567a..ed9c2121ef8 100644 --- a/git-config/src/value.rs +++ b/git-config/src/value/mod.rs @@ -1,3 +1,9 @@ +mod color; +pub use color::{Color, ColorAttribute, ColorValue}; + +mod integer; +pub use integer::{Integer, IntegerSuffix}; + pub mod parse { use bstr::BString; @@ -27,6 +33,3 @@ pub mod parse { } } } - -mod color; -pub use color::{Color, ColorAttribute, ColorValue}; diff --git a/git-config/src/values.rs b/git-config/src/values.rs index 7bd7d6ea663..4058a4c0e38 100644 --- a/git-config/src/values.rs +++ b/git-config/src/values.rs @@ -1,6 +1,6 @@ //! Rust containers for valid `git-config` types. -use std::{borrow::Cow, convert::TryFrom, fmt::Display, str::FromStr}; +use std::{borrow::Cow, convert::TryFrom, fmt::Display}; use bstr::{BStr, BString, ByteSlice}; #[cfg(feature = "serde")] @@ -521,214 +521,3 @@ impl Serialize for TrueVariant<'_> { serializer.serialize_bool(true) } } - -/// Any value that can be interpreted as an integer. -/// -/// This supports any numeric value that can fit in a [`i64`], excluding the -/// suffix. The suffix is parsed separately from the value itself, so if you -/// wish to obtain the true value of the integer, you must account for the -/// suffix after fetching the value. [`IntegerSuffix`] provides -/// [`bitwise_offset`] to help with the math, but do be warned that if the value -/// is very large, you may run into overflows. -/// -/// [`BStr`]: bstr::BStr -/// [`bitwise_offset`]: IntegerSuffix::bitwise_offset -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] -pub struct Integer { - /// The value, without any suffix modification - pub value: i64, - /// A provided suffix, if any. - pub suffix: Option, -} - -impl Integer { - /// Generates a byte representation of the value. This should be used when - /// non-UTF-8 sequences are present or a UTF-8 representation can't be - /// guaranteed. - #[must_use] - pub fn to_bstring(self) -> BString { - self.into() - } - - /// Canonicalize values as simple decimal numbers. - /// An optional suffix of k, m, or g (case-insensitive), upon creation, will cause the value to be multiplied by - /// 1024 (k), 1048576 (m), or 1073741824 (g) respectively. - /// - /// Returns the result if no multiplication overflow. - pub fn to_decimal(&self) -> Option { - match self.suffix { - None => Some(self.value), - Some(suffix) => match suffix { - IntegerSuffix::Kibi => self.value.checked_mul(1024), - IntegerSuffix::Mebi => self.value.checked_mul(1024 * 1024), - IntegerSuffix::Gibi => self.value.checked_mul(1024 * 1024 * 1024), - }, - } - } -} - -impl Display for Integer { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.value)?; - if let Some(suffix) = self.suffix { - write!(f, "{}", suffix) - } else { - Ok(()) - } - } -} - -#[cfg(feature = "serde")] -impl Serialize for Integer { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - if let Some(suffix) = self.suffix { - serializer.serialize_i64(self.value << suffix.bitwise_offset()) - } else { - serializer.serialize_i64(self.value) - } - } -} - -fn int_err(input: impl Into) -> value::parse::Error { - value::parse::Error::new( - "Integers needs to be positive or negative numbers which may have a suffix like 1k, 42, or 50G", - input, - ) -} - -impl TryFrom<&BStr> for Integer { - type Error = value::parse::Error; - - fn try_from(s: &BStr) -> Result { - let s = std::str::from_utf8(s).map_err(|err| int_err(s).with_err(err))?; - if let Ok(value) = s.parse() { - return Ok(Self { value, suffix: None }); - } - - // Assume we have a prefix at this point. - - if s.len() <= 1 { - return Err(int_err(s)); - } - - let (number, suffix) = s.split_at(s.len() - 1); - if let (Ok(value), Ok(suffix)) = (number.parse(), suffix.parse()) { - Ok(Self { - value, - suffix: Some(suffix), - }) - } else { - Err(int_err(s)) - } - } -} - -impl TryFrom for Integer { - type Error = value::parse::Error; - - fn try_from(value: BString) -> Result { - Self::try_from(value.as_ref()) - } -} - -impl TryFrom> for Integer { - type Error = value::parse::Error; - - fn try_from(c: Cow<'_, BStr>) -> Result { - match c { - Cow::Borrowed(c) => Self::try_from(c), - Cow::Owned(c) => Self::try_from(c), - } - } -} - -impl From for BString { - fn from(i: Integer) -> Self { - i.into() - } -} - -impl From<&Integer> for BString { - fn from(i: &Integer) -> Self { - i.to_string().into() - } -} - -/// Integer prefixes that are supported by `git-config`. -/// -/// These values are base-2 unit of measurements, not the base-10 variants. -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] -#[allow(missing_docs)] -pub enum IntegerSuffix { - Kibi, - Mebi, - Gibi, -} - -impl IntegerSuffix { - /// Returns the number of bits that the suffix shifts left by. - #[must_use] - pub const fn bitwise_offset(self) -> usize { - match self { - Self::Kibi => 10, - Self::Mebi => 20, - Self::Gibi => 30, - } - } -} - -impl Display for IntegerSuffix { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Kibi => write!(f, "k"), - Self::Mebi => write!(f, "m"), - Self::Gibi => write!(f, "g"), - } - } -} - -#[cfg(feature = "serde")] -impl Serialize for IntegerSuffix { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(match self { - Self::Kibi => "k", - Self::Mebi => "m", - Self::Gibi => "g", - }) - } -} - -impl FromStr for IntegerSuffix { - type Err = (); - - fn from_str(s: &str) -> Result { - match s { - "k" | "K" => Ok(Self::Kibi), - "m" | "M" => Ok(Self::Mebi), - "g" | "G" => Ok(Self::Gibi), - _ => Err(()), - } - } -} - -impl TryFrom<&[u8]> for IntegerSuffix { - type Error = (); - - fn try_from(s: &[u8]) -> Result { - Self::from_str(std::str::from_utf8(s).map_err(|_| ())?) - } -} - -impl TryFrom for IntegerSuffix { - type Error = (); - - fn try_from(value: BString) -> Result { - Self::try_from(value.as_ref()) - } -} diff --git a/git-config/tests/file/access/read_only.rs b/git-config/tests/file/access/read_only.rs index 70c1581f024..83e6cd2cf95 100644 --- a/git-config/tests/file/access/read_only.rs +++ b/git-config/tests/file/access/read_only.rs @@ -1,8 +1,8 @@ use crate::file::cow_str; use bstr::BStr; -use git_config::value::{Color, ColorAttribute, ColorValue}; +use git_config::value::{Color, ColorAttribute, ColorValue, Integer, IntegerSuffix}; use git_config::{ - values::{Boolean, TrueVariant, *}, + values::{Boolean, String, TrueVariant}, File, }; use std::{borrow::Cow, convert::TryFrom, error::Error}; diff --git a/git-config/tests/values/integer.rs b/git-config/tests/values/integer.rs index 7610dc54d9d..4922819d654 100644 --- a/git-config/tests/values/integer.rs +++ b/git-config/tests/values/integer.rs @@ -1,6 +1,6 @@ use std::convert::TryFrom; -use git_config::values::{Integer, IntegerSuffix}; +use git_config::{value::Integer, value::IntegerSuffix}; use crate::values::b; From 6033f3f93d2356399a661567353a83a044662699 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 6 Jul 2022 13:40:46 +0800 Subject: [PATCH 032/366] change!: Move `Boolean` and `String` from `values` into `value` module (#331) --- git-config/src/file/access/comfort.rs | 10 +- git-config/src/file/section.rs | 10 +- git-config/src/value/boolean.rs | 218 ++++++++++++++++++++ git-config/src/value/mod.rs | 6 + git-config/src/value/string.rs | 17 ++ git-config/src/values.rs | 240 +--------------------- git-config/tests/file/access/read_only.rs | 7 +- git-config/tests/values/boolean.rs | 2 +- 8 files changed, 258 insertions(+), 252 deletions(-) create mode 100644 git-config/src/value/boolean.rs create mode 100644 git-config/src/value/string.rs diff --git a/git-config/src/file/access/comfort.rs b/git-config/src/file/access/comfort.rs index 9011e08e336..a7fb07a20f6 100644 --- a/git-config/src/file/access/comfort.rs +++ b/git-config/src/file/access/comfort.rs @@ -2,7 +2,7 @@ use std::{borrow::Cow, convert::TryFrom}; use bstr::BStr; -use crate::values::normalize_cow; +use crate::values::normalize; use crate::{value, values, File}; /// Comfortable API for accessing values @@ -11,9 +11,7 @@ impl<'a> File<'a> { /// /// As strings perform no conversions, this will never fail. pub fn string(&'a self, section_name: &str, subsection_name: Option<&str>, key: &str) -> Option> { - self.raw_value(section_name, subsection_name, key) - .ok() - .map(normalize_cow) + self.raw_value(section_name, subsection_name, key).ok().map(normalize) } /// Like [`value()`][File::value()], but returning an `Option` if the path wasn't found. @@ -40,7 +38,7 @@ impl<'a> File<'a> { ) -> Option> { self.raw_value(section_name, subsection_name, key) .ok() - .map(|v| values::Boolean::try_from(v).map(|b| b.to_bool())) + .map(|v| value::Boolean::try_from(v).map(|b| b.to_bool())) } /// Like [`value()`][File::value()], but returning an `Option` if the integer wasn't found. @@ -61,7 +59,7 @@ impl<'a> File<'a> { pub fn strings(&self, section_name: &str, subsection_name: Option<&str>, key: &str) -> Option>> { self.raw_multi_value(section_name, subsection_name, key) .ok() - .map(|values| values.into_iter().map(normalize_cow).collect()) + .map(|values| values.into_iter().map(normalize).collect()) } /// Similar to [`multi_value(…)`][File::multi_value()] but returning integers if at least one of them was found diff --git a/git-config/src/file/section.rs b/git-config/src/file/section.rs index e5042571bb1..f1e13013793 100644 --- a/git-config/src/file/section.rs +++ b/git-config/src/file/section.rs @@ -11,7 +11,7 @@ use crate::{ file::Index, lookup, parser::{Event, Key}, - values::{normalize_bstring, normalize_cow}, + values::{normalize, normalize_bstring}, }; /// A opaque type that represents a mutable reference to a section. @@ -57,7 +57,7 @@ impl<'borrow, 'event> MutableSection<'borrow, 'event> { if values.len() == 1 { let value = values.pop().expect("vec is non-empty but popped to empty value"); - return Some((k, normalize_cow(value))); + return Some((k, normalize(value))); } return Some(( @@ -191,7 +191,7 @@ impl<'borrow, 'event> MutableSection<'borrow, 'event> { } latest_value - .map(normalize_cow) + .map(normalize) .or_else(|| partial_value.map(normalize_bstring)) .ok_or(lookup::existing::Error::KeyMissing) } @@ -293,7 +293,7 @@ impl<'event> SectionBody<'event> { Event::Value(v) if found_key => { found_key = false; // Clones the Cow, doesn't copy underlying value if borrowed - values.push(normalize_cow(v.clone())); + values.push(normalize(v.clone())); partial_value = None; } Event::ValueNotDone(v) if found_key => { @@ -431,7 +431,7 @@ impl<'event> Iterator for SectionBodyIter<'event> { } } - key.zip(value.map(normalize_cow)) + key.zip(value.map(normalize)) } } diff --git a/git-config/src/value/boolean.rs b/git-config/src/value/boolean.rs new file mode 100644 index 00000000000..81505051cab --- /dev/null +++ b/git-config/src/value/boolean.rs @@ -0,0 +1,218 @@ +use crate::value; +use bstr::{BStr, BString, ByteSlice}; +use std::borrow::Cow; +use std::convert::TryFrom; +use std::fmt::Display; + +/// Any value that can be interpreted as a boolean. +/// +/// Note that while values can effectively be any byte string, the `git-config` +/// documentation has a strict subset of values that may be interpreted as a +/// boolean value, all of which are ASCII and thus UTF-8 representable. +/// Consequently, variants hold [`str`]s rather than [`[u8]`]s. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +#[allow(missing_docs)] +pub enum Boolean<'a> { + True(TrueVariant<'a>), + False(Cow<'a, BStr>), +} + +impl Boolean<'_> { + /// Return ourselves as plain bool. + pub fn to_bool(&self) -> bool { + match self { + Boolean::True(_) => true, + Boolean::False(_) => false, + } + } + /// Generates a byte representation of the value. This should be used when + /// non-UTF-8 sequences are present or a UTF-8 representation can't be + /// guaranteed. + #[must_use] + pub fn to_bstring(&self) -> BString { + self.into() + } +} + +fn bool_err(input: impl Into) -> value::parse::Error { + value::parse::Error::new( + "Booleans need to be 'no', 'off', 'false', 'zero' or 'yes', 'on', 'true', 'one'", + input, + ) +} + +impl<'a> TryFrom<&'a BStr> for Boolean<'a> { + type Error = value::parse::Error; + + fn try_from(value: &'a BStr) -> Result { + if let Ok(v) = TrueVariant::try_from(value) { + return Ok(Self::True(v)); + } + + if value.eq_ignore_ascii_case(b"no") + || value.eq_ignore_ascii_case(b"off") + || value.eq_ignore_ascii_case(b"false") + || value.eq_ignore_ascii_case(b"zero") + || value == "\"\"" + { + return Ok(Self::False(value.as_bstr().into())); + } + + Err(bool_err(value)) + } +} + +impl TryFrom for Boolean<'_> { + type Error = value::parse::Error; + + fn try_from(value: BString) -> Result { + if value.eq_ignore_ascii_case(b"no") + || value.eq_ignore_ascii_case(b"off") + || value.eq_ignore_ascii_case(b"false") + || value.eq_ignore_ascii_case(b"zero") + || value == "\"\"" + { + return Ok(Self::False(Cow::Owned(value))); + } + + TrueVariant::try_from(value).map(Self::True) + } +} + +impl<'a> TryFrom> for Boolean<'a> { + type Error = value::parse::Error; + fn try_from(c: Cow<'a, BStr>) -> Result { + match c { + Cow::Borrowed(c) => Self::try_from(c), + Cow::Owned(c) => Self::try_from(c), + } + } +} + +impl Display for Boolean<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Boolean::True(v) => v.fmt(f), + Boolean::False(v) => write!(f, "{}", v), + } + } +} + +impl From> for bool { + fn from(b: Boolean<'_>) -> Self { + match b { + Boolean::True(_) => true, + Boolean::False(_) => false, + } + } +} + +impl<'a, 'b: 'a> From<&'b Boolean<'a>> for &'a BStr { + fn from(b: &'b Boolean<'_>) -> Self { + match b { + Boolean::True(t) => t.into(), + Boolean::False(f) => f.as_ref(), + } + } +} + +impl From> for BString { + fn from(b: Boolean<'_>) -> Self { + b.into() + } +} + +impl From<&Boolean<'_>> for BString { + fn from(b: &Boolean<'_>) -> Self { + b.to_string().into() + } +} + +#[cfg(feature = "serde")] +impl Serialize for Boolean<'_> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self { + Boolean::True(_) => serializer.serialize_bool(true), + Boolean::False(_) => serializer.serialize_bool(false), + } + } +} + +/// Discriminating enum between implicit and explicit truthy values. +/// +/// This enum is part of the [`Boolean`] struct. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +#[allow(missing_docs)] +pub enum TrueVariant<'a> { + Explicit(Cow<'a, BStr>), + /// For values defined without a `= `. + Implicit, +} + +impl<'a> TryFrom<&'a BStr> for TrueVariant<'a> { + type Error = value::parse::Error; + + fn try_from(value: &'a BStr) -> Result { + if value.eq_ignore_ascii_case(b"yes") + || value.eq_ignore_ascii_case(b"on") + || value.eq_ignore_ascii_case(b"true") + || value.eq_ignore_ascii_case(b"one") + { + Ok(Self::Explicit(value.as_bstr().into())) + } else if value.is_empty() { + Ok(Self::Implicit) + } else { + Err(bool_err(value)) + } + } +} + +impl TryFrom for TrueVariant<'_> { + type Error = value::parse::Error; + + fn try_from(value: BString) -> Result { + if value.eq_ignore_ascii_case(b"yes") + || value.eq_ignore_ascii_case(b"on") + || value.eq_ignore_ascii_case(b"true") + || value.eq_ignore_ascii_case(b"one") + { + Ok(Self::Explicit(Cow::Owned(value))) + } else if value.is_empty() { + Ok(Self::Implicit) + } else { + Err(bool_err(value)) + } + } +} + +impl Display for TrueVariant<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Self::Explicit(v) = self { + write!(f, "{}", v) + } else { + Ok(()) + } + } +} + +impl<'a, 'b: 'a> From<&'b TrueVariant<'a>> for &'a BStr { + fn from(t: &'b TrueVariant<'a>) -> Self { + match t { + TrueVariant::Explicit(e) => e.as_ref(), + TrueVariant::Implicit => "".into(), + } + } +} + +#[cfg(feature = "serde")] +impl Serialize for TrueVariant<'_> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_bool(true) + } +} diff --git a/git-config/src/value/mod.rs b/git-config/src/value/mod.rs index ed9c2121ef8..07116ff046a 100644 --- a/git-config/src/value/mod.rs +++ b/git-config/src/value/mod.rs @@ -1,9 +1,15 @@ +mod string; +pub use string::String; + mod color; pub use color::{Color, ColorAttribute, ColorValue}; mod integer; pub use integer::{Integer, IntegerSuffix}; +mod boolean; +pub use boolean::{Boolean, TrueVariant}; + pub mod parse { use bstr::BString; diff --git a/git-config/src/value/string.rs b/git-config/src/value/string.rs new file mode 100644 index 00000000000..633914dff45 --- /dev/null +++ b/git-config/src/value/string.rs @@ -0,0 +1,17 @@ +use bstr::BStr; +use std::borrow::Cow; + +/// Any string value +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub struct String<'a> { + /// The string value + pub value: Cow<'a, BStr>, +} + +impl<'a> From> for String<'a> { + fn from(c: Cow<'a, BStr>) -> Self { + String { + value: crate::values::normalize(c), + } + } +} diff --git a/git-config/src/values.rs b/git-config/src/values.rs index 4058a4c0e38..b3ae73969f3 100644 --- a/git-config/src/values.rs +++ b/git-config/src/values.rs @@ -1,13 +1,11 @@ //! Rust containers for valid `git-config` types. -use std::{borrow::Cow, convert::TryFrom, fmt::Display}; +use std::borrow::Cow; -use bstr::{BStr, BString, ByteSlice}; +use bstr::{BStr, BString}; #[cfg(feature = "serde")] use serde::{Serialize, Serializer}; -use crate::value; - /// Removes quotes, if any, from the provided inputs. This assumes the input /// contains a even number of unescaped quotes, and will unescape escaped /// quotes. The return values should be safe for value interpretation. @@ -65,7 +63,7 @@ use crate::value; /// /// [`parser`]: crate::parser::Parser #[must_use] -pub fn normalize_cow(input: Cow<'_, BStr>) -> Cow<'_, BStr> { +pub fn normalize(input: Cow<'_, BStr>) -> Cow<'_, BStr> { let size = input.len(); if input.as_ref() == "\"\"" { return Cow::default(); @@ -127,28 +125,13 @@ pub fn normalize_cow(input: Cow<'_, BStr>) -> Cow<'_, BStr> { /// `&[u8]` variant of [`normalize_cow`]. #[must_use] pub fn normalize_bstr<'a>(input: impl Into<&'a BStr>) -> Cow<'a, BStr> { - normalize_cow(Cow::Borrowed(input.into())) + normalize(Cow::Borrowed(input.into())) } /// `Vec[u8]` variant of [`normalize_cow`]. #[must_use] pub fn normalize_bstring(input: impl Into) -> Cow<'static, BStr> { - normalize_cow(Cow::Owned(input.into())) -} - -/// Any string value -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] -pub struct String<'a> { - /// The string value - pub value: Cow<'a, BStr>, -} - -impl<'a> From> for String<'a> { - fn from(c: Cow<'a, BStr>) -> Self { - String { - value: normalize_cow(c), - } - } + normalize(Cow::Owned(input.into())) } /// @@ -308,216 +291,3 @@ impl<'a> From> for Path<'a> { Path { value } } } - -/// Any value that can be interpreted as a boolean. -/// -/// Note that while values can effectively be any byte string, the `git-config` -/// documentation has a strict subset of values that may be interpreted as a -/// boolean value, all of which are ASCII and thus UTF-8 representable. -/// Consequently, variants hold [`str`]s rather than [`[u8]`]s. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] -#[allow(missing_docs)] -pub enum Boolean<'a> { - True(TrueVariant<'a>), - False(Cow<'a, BStr>), -} - -impl Boolean<'_> { - /// Return ourselves as plain bool. - pub fn to_bool(&self) -> bool { - match self { - Boolean::True(_) => true, - Boolean::False(_) => false, - } - } - /// Generates a byte representation of the value. This should be used when - /// non-UTF-8 sequences are present or a UTF-8 representation can't be - /// guaranteed. - #[must_use] - pub fn to_bstring(&self) -> BString { - self.into() - } -} - -fn bool_err(input: impl Into) -> value::parse::Error { - value::parse::Error::new( - "Booleans need to be 'no', 'off', 'false', 'zero' or 'yes', 'on', 'true', 'one'", - input, - ) -} - -impl<'a> TryFrom<&'a BStr> for Boolean<'a> { - type Error = value::parse::Error; - - fn try_from(value: &'a BStr) -> Result { - if let Ok(v) = TrueVariant::try_from(value) { - return Ok(Self::True(v)); - } - - if value.eq_ignore_ascii_case(b"no") - || value.eq_ignore_ascii_case(b"off") - || value.eq_ignore_ascii_case(b"false") - || value.eq_ignore_ascii_case(b"zero") - || value == "\"\"" - { - return Ok(Self::False(value.as_bstr().into())); - } - - Err(bool_err(value)) - } -} - -impl TryFrom for Boolean<'_> { - type Error = value::parse::Error; - - fn try_from(value: BString) -> Result { - if value.eq_ignore_ascii_case(b"no") - || value.eq_ignore_ascii_case(b"off") - || value.eq_ignore_ascii_case(b"false") - || value.eq_ignore_ascii_case(b"zero") - || value == "\"\"" - { - return Ok(Self::False(Cow::Owned(value))); - } - - TrueVariant::try_from(value).map(Self::True) - } -} - -impl<'a> TryFrom> for Boolean<'a> { - type Error = value::parse::Error; - fn try_from(c: Cow<'a, BStr>) -> Result { - match c { - Cow::Borrowed(c) => Self::try_from(c), - Cow::Owned(c) => Self::try_from(c), - } - } -} - -impl Display for Boolean<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Boolean::True(v) => v.fmt(f), - Boolean::False(v) => write!(f, "{}", v), - } - } -} - -impl From> for bool { - fn from(b: Boolean<'_>) -> Self { - match b { - Boolean::True(_) => true, - Boolean::False(_) => false, - } - } -} - -impl<'a, 'b: 'a> From<&'b Boolean<'a>> for &'a BStr { - fn from(b: &'b Boolean<'_>) -> Self { - match b { - Boolean::True(t) => t.into(), - Boolean::False(f) => f.as_ref(), - } - } -} - -impl From> for BString { - fn from(b: Boolean<'_>) -> Self { - b.into() - } -} - -impl From<&Boolean<'_>> for BString { - fn from(b: &Boolean<'_>) -> Self { - b.to_string().into() - } -} - -#[cfg(feature = "serde")] -impl Serialize for Boolean<'_> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - match self { - Boolean::True(_) => serializer.serialize_bool(true), - Boolean::False(_) => serializer.serialize_bool(false), - } - } -} - -/// Discriminating enum between implicit and explicit truthy values. -/// -/// This enum is part of the [`Boolean`] struct. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] -#[allow(missing_docs)] -pub enum TrueVariant<'a> { - Explicit(Cow<'a, BStr>), - /// For values defined without a `= `. - Implicit, -} - -impl<'a> TryFrom<&'a BStr> for TrueVariant<'a> { - type Error = value::parse::Error; - - fn try_from(value: &'a BStr) -> Result { - if value.eq_ignore_ascii_case(b"yes") - || value.eq_ignore_ascii_case(b"on") - || value.eq_ignore_ascii_case(b"true") - || value.eq_ignore_ascii_case(b"one") - { - Ok(Self::Explicit(value.as_bstr().into())) - } else if value.is_empty() { - Ok(Self::Implicit) - } else { - Err(bool_err(value)) - } - } -} - -impl TryFrom for TrueVariant<'_> { - type Error = value::parse::Error; - - fn try_from(value: BString) -> Result { - if value.eq_ignore_ascii_case(b"yes") - || value.eq_ignore_ascii_case(b"on") - || value.eq_ignore_ascii_case(b"true") - || value.eq_ignore_ascii_case(b"one") - { - Ok(Self::Explicit(Cow::Owned(value))) - } else if value.is_empty() { - Ok(Self::Implicit) - } else { - Err(bool_err(value)) - } - } -} - -impl Display for TrueVariant<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if let Self::Explicit(v) = self { - write!(f, "{}", v) - } else { - Ok(()) - } - } -} - -impl<'a, 'b: 'a> From<&'b TrueVariant<'a>> for &'a BStr { - fn from(t: &'b TrueVariant<'a>) -> Self { - match t { - TrueVariant::Explicit(e) => e.as_ref(), - TrueVariant::Implicit => "".into(), - } - } -} - -#[cfg(feature = "serde")] -impl Serialize for TrueVariant<'_> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_bool(true) - } -} diff --git a/git-config/tests/file/access/read_only.rs b/git-config/tests/file/access/read_only.rs index 83e6cd2cf95..199fd0efd7b 100644 --- a/git-config/tests/file/access/read_only.rs +++ b/git-config/tests/file/access/read_only.rs @@ -1,10 +1,7 @@ use crate::file::cow_str; use bstr::BStr; -use git_config::value::{Color, ColorAttribute, ColorValue, Integer, IntegerSuffix}; -use git_config::{ - values::{Boolean, String, TrueVariant}, - File, -}; +use git_config::value::{Boolean, Color, ColorAttribute, ColorValue, Integer, IntegerSuffix, String, TrueVariant}; +use git_config::File; use std::{borrow::Cow, convert::TryFrom, error::Error}; /// Asserts we can cast into all variants of our type diff --git a/git-config/tests/values/boolean.rs b/git-config/tests/values/boolean.rs index 8129ab06793..8f2246d2b8d 100644 --- a/git-config/tests/values/boolean.rs +++ b/git-config/tests/values/boolean.rs @@ -1,7 +1,7 @@ use std::convert::TryFrom; use crate::file::cow_str; -use git_config::values::{Boolean, TrueVariant}; +use git_config::value::{Boolean, TrueVariant}; use crate::values::b; From 767bedccdae1f3e6faf853d59ecf884a06cc3827 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 6 Jul 2022 13:49:48 +0800 Subject: [PATCH 033/366] change!: move `Path` from `values` to `value` module (#331) --- git-config/src/file/access/comfort.rs | 6 +- git-config/src/file/from_env.rs | 2 +- git-config/src/file/from_paths.rs | 2 +- git-config/src/file/resolve_includes.rs | 10 +- git-config/src/value/mod.rs | 11 ++ git-config/src/value/path.rs | 145 ++++++++++++++++++++ git-config/src/values.rs | 158 ---------------------- git-config/tests/file/access/read_only.rs | 2 +- git-config/tests/values/path.rs | 6 +- 9 files changed, 170 insertions(+), 172 deletions(-) create mode 100644 git-config/src/value/path.rs diff --git a/git-config/src/file/access/comfort.rs b/git-config/src/file/access/comfort.rs index a7fb07a20f6..5fe3e0a67e2 100644 --- a/git-config/src/file/access/comfort.rs +++ b/git-config/src/file/access/comfort.rs @@ -3,7 +3,7 @@ use std::{borrow::Cow, convert::TryFrom}; use bstr::BStr; use crate::values::normalize; -use crate::{value, values, File}; +use crate::{value, File}; /// Comfortable API for accessing values impl<'a> File<'a> { @@ -23,10 +23,10 @@ impl<'a> File<'a> { // TODO: add `secure_path()` or similar to make use of our knowledge of the trust associated with each configuration // file, maybe even remove the insecure version to force every caller to ask themselves if the resource can // be used securely or not. - pub fn path(&'a self, section_name: &str, subsection_name: Option<&str>, key: &str) -> Option> { + pub fn path(&'a self, section_name: &str, subsection_name: Option<&str>, key: &str) -> Option> { self.raw_value(section_name, subsection_name, key) .ok() - .map(values::Path::from) + .map(value::Path::from) } /// Like [`value()`][File::value()], but returning an `Option` if the boolean wasn't found. diff --git a/git-config/src/file/from_env.rs b/git-config/src/file/from_env.rs index b4584f36150..b99fba5ad7d 100644 --- a/git-config/src/file/from_env.rs +++ b/git-config/src/file/from_env.rs @@ -4,7 +4,7 @@ use std::{borrow::Cow, path::PathBuf}; use crate::{ file::{from_paths, resolve_includes}, parser, - values::path::interpolate, + value::path::interpolate, File, }; diff --git a/git-config/src/file/from_paths.rs b/git-config/src/file/from_paths.rs index e9e57ebe6f7..741c572ebd5 100644 --- a/git-config/src/file/from_paths.rs +++ b/git-config/src/file/from_paths.rs @@ -1,4 +1,4 @@ -use crate::{parser, values::path::interpolate}; +use crate::{parser, value::path::interpolate}; /// The error returned by [`File::from_paths()`][crate::File::from_paths()] and [`File::from_env_paths()`][crate::File::from_env_paths()]. #[derive(Debug, thiserror::Error)] diff --git a/git-config/src/file/resolve_includes.rs b/git-config/src/file/resolve_includes.rs index 6a49772613d..4f1a03bb5a7 100644 --- a/git-config/src/file/resolve_includes.rs +++ b/git-config/src/file/resolve_includes.rs @@ -10,7 +10,7 @@ use crate::file::from_paths::Options; use crate::{ file::{from_paths, SectionId}, parser::Key, - values, File, + value, File, }; pub(crate) fn resolve_includes( @@ -85,10 +85,10 @@ fn resolve_includes_recursive( Ok(()) } -fn extract_include_path<'a>(target_config: &mut File<'a>, include_paths: &mut Vec>, id: SectionId) { +fn extract_include_path<'a>(target_config: &mut File<'a>, include_paths: &mut Vec>, id: SectionId) { if let Some(body) = target_config.sections.get(&id) { let paths = body.values(&Key::from("path")); - let paths = paths.iter().map(|path| values::Path::from(path.clone())); + let paths = paths.iter().map(|path| value::Path::from(path.clone())); include_paths.extend(paths); } } @@ -159,7 +159,7 @@ fn gitdir_matches( git_path::to_unix_separators_on_windows(git_path::into_bstr(git_dir.ok_or(from_paths::Error::MissingGitDir)?)); let mut pattern_path: Cow<'_, _> = { - let path = values::Path::from(Cow::Borrowed(condition_path)).interpolate(git_install_dir, home_dir)?; + let path = value::Path::from(Cow::Borrowed(condition_path)).interpolate(git_install_dir, home_dir)?; git_path::into_bstr(path).into_owned().into() }; // NOTE: yes, only if we do path interpolation will the slashes be forced to unix separators on windows @@ -207,7 +207,7 @@ fn gitdir_matches( } fn resolve( - path: values::Path<'_>, + path: value::Path<'_>, target_config_path: Option<&Path>, from_paths::Options { git_install_dir, diff --git a/git-config/src/value/mod.rs b/git-config/src/value/mod.rs index 07116ff046a..97bfc72289d 100644 --- a/git-config/src/value/mod.rs +++ b/git-config/src/value/mod.rs @@ -10,6 +10,17 @@ pub use integer::{Integer, IntegerSuffix}; mod boolean; pub use boolean::{Boolean, TrueVariant}; +/// Any value that can be interpreted as a file path. +/// +/// Git represents file paths as byte arrays, modeled here as owned or borrowed byte sequences. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub struct Path<'a> { + /// The path string, un-interpolated + pub value: std::borrow::Cow<'a, bstr::BStr>, +} +/// +pub mod path; + pub mod parse { use bstr::BString; diff --git a/git-config/src/value/path.rs b/git-config/src/value/path.rs new file mode 100644 index 00000000000..8b3746d2f80 --- /dev/null +++ b/git-config/src/value/path.rs @@ -0,0 +1,145 @@ +use crate::value::Path; +use bstr::BStr; +use std::borrow::Cow; + +pub mod interpolate { + /// The error returned by [`Path::interpolate()`][crate::values::Path::interpolate()]. + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error("{} is missing", .what)] + Missing { what: &'static str }, + #[error("Ill-formed UTF-8 in {}", .what)] + Utf8Conversion { + what: &'static str, + #[source] + err: git_path::Utf8Error, + }, + #[error("Ill-formed UTF-8 in username")] + UsernameConversion(#[from] std::str::Utf8Error), + #[error("User interpolation is not available on this platform")] + UserInterpolationUnsupported, + } +} + +impl<'a> std::ops::Deref for Path<'a> { + type Target = BStr; + + fn deref(&self) -> &Self::Target { + self.value.as_ref() + } +} + +impl<'a> AsRef<[u8]> for Path<'a> { + fn as_ref(&self) -> &[u8] { + self.value.as_ref() + } +} + +impl<'a> AsRef for Path<'a> { + fn as_ref(&self) -> &BStr { + self.value.as_ref() + } +} + +impl<'a> From> for Path<'a> { + fn from(value: Cow<'a, BStr>) -> Self { + Path { value } + } +} + +impl<'a> Path<'a> { + /// Interpolates this path into a file system path. + /// + /// If this path starts with `~/` or `~user/` or `%(prefix)/` + /// - `~/` is expanded to the value of `home_dir`. The caller can use the [dirs](https://crates.io/crates/dirs) crate to obtain it. + /// It it is required but not set, an error is produced. + /// - `~user/` to the specified user’s home directory, e.g `~alice` might get expanded to `/home/alice` on linux. + /// The interpolation uses `getpwnam` sys call and is therefore not available on windows. See also [pwd](https://crates.io/crates/pwd). + /// - `%(prefix)/` is expanded to the location where gitoxide is installed. This location is not known at compile time and therefore need to be + /// optionally provided by the caller through `git_install_dir`. + /// + /// Any other, non-empty path value is returned unchanged and error is returned in case of an empty path value. + pub fn interpolate( + self, + git_install_dir: Option<&std::path::Path>, + home_dir: Option<&std::path::Path>, + ) -> Result, interpolate::Error> { + if self.is_empty() { + return Err(interpolate::Error::Missing { what: "path" }); + } + + const PREFIX: &[u8] = b"%(prefix)/"; + const USER_HOME: &[u8] = b"~/"; + if self.starts_with(PREFIX) { + let git_install_dir = git_install_dir.ok_or(interpolate::Error::Missing { + what: "git install dir", + })?; + let (_prefix, path_without_trailing_slash) = self.split_at(PREFIX.len()); + let path_without_trailing_slash = + git_path::try_from_bstring(path_without_trailing_slash).map_err(|err| { + interpolate::Error::Utf8Conversion { + what: "path past %(prefix)", + err, + } + })?; + Ok(git_install_dir.join(path_without_trailing_slash).into()) + } else if self.starts_with(USER_HOME) { + let home_path = home_dir.ok_or(interpolate::Error::Missing { what: "home dir" })?; + let (_prefix, val) = self.split_at(USER_HOME.len()); + let val = git_path::try_from_byte_slice(val).map_err(|err| interpolate::Error::Utf8Conversion { + what: "path past ~/", + err, + })?; + Ok(home_path.join(val).into()) + } else if self.starts_with(b"~") && self.contains(&b'/') { + self.interpolate_user() + } else { + Ok(git_path::from_bstr(self.value)) + } + } + + #[cfg(any(target_os = "windows", target_os = "android"))] + fn interpolate_user(self) -> Result, interpolate::Error> { + Err(interpolate::Error::UserInterpolationUnsupported) + } + + #[cfg(not(windows))] + fn interpolate_user(self) -> Result, interpolate::Error> { + #[cfg(not(any(target_os = "android", target_os = "windows")))] + fn home_for_user(name: &str) -> Option { + let cname = std::ffi::CString::new(name).ok()?; + // SAFETY: calling this in a threaded program that modifies the pw database is not actually safe. + // TODO: use the `*_r` version, but it's much harder to use. + #[allow(unsafe_code)] + let pwd = unsafe { libc::getpwnam(cname.as_ptr()) }; + if pwd.is_null() { + None + } else { + use std::os::unix::ffi::OsStrExt; + // SAFETY: pw_dir is a cstr and it lives as long as… well, we hope nobody changes the pw database while we are at it + // from another thread. Otherwise it lives long enough. + #[allow(unsafe_code)] + let cstr = unsafe { std::ffi::CStr::from_ptr((*pwd).pw_dir) }; + Some(std::ffi::OsStr::from_bytes(cstr.to_bytes()).into()) + } + } + + let (_prefix, val) = self.split_at("/".len()); + let i = val + .iter() + .position(|&e| e == b'/') + .ok_or(interpolate::Error::Missing { what: "/" })?; + let (username, path_with_leading_slash) = val.split_at(i); + let username = std::str::from_utf8(username)?; + let home = home_for_user(username).ok_or(interpolate::Error::Missing { what: "pwd user info" })?; + let path_past_user_prefix = + git_path::try_from_byte_slice(&path_with_leading_slash["/".len()..]).map_err(|err| { + interpolate::Error::Utf8Conversion { + what: "path past ~user/", + err, + } + })?; + Ok(home.join(path_past_user_prefix).into()) + } +} diff --git a/git-config/src/values.rs b/git-config/src/values.rs index b3ae73969f3..fee3c2dbd5f 100644 --- a/git-config/src/values.rs +++ b/git-config/src/values.rs @@ -133,161 +133,3 @@ pub fn normalize_bstr<'a>(input: impl Into<&'a BStr>) -> Cow<'a, BStr> { pub fn normalize_bstring(input: impl Into) -> Cow<'static, BStr> { normalize(Cow::Owned(input.into())) } - -/// -pub mod path { - use std::borrow::Cow; - - #[cfg(not(any(target_os = "android", target_os = "windows")))] - fn home_for_user(name: &str) -> Option { - let cname = std::ffi::CString::new(name).ok()?; - // SAFETY: calling this in a threaded program that modifies the pw database is not actually safe. - // TODO: use the `*_r` version, but it's much harder to use. - #[allow(unsafe_code)] - let pwd = unsafe { libc::getpwnam(cname.as_ptr()) }; - if pwd.is_null() { - None - } else { - use std::os::unix::ffi::OsStrExt; - // SAFETY: pw_dir is a cstr and it lives as long as… well, we hope nobody changes the pw database while we are at it - // from another thread. Otherwise it lives long enough. - #[allow(unsafe_code)] - let cstr = unsafe { std::ffi::CStr::from_ptr((*pwd).pw_dir) }; - Some(std::ffi::OsStr::from_bytes(cstr.to_bytes()).into()) - } - } - - use crate::values::Path; - - pub mod interpolate { - /// The error returned by [`Path::interpolate()`][crate::values::Path::interpolate()]. - #[derive(Debug, thiserror::Error)] - #[allow(missing_docs)] - pub enum Error { - #[error("{} is missing", .what)] - Missing { what: &'static str }, - #[error("Ill-formed UTF-8 in {}", .what)] - Utf8Conversion { - what: &'static str, - #[source] - err: git_path::Utf8Error, - }, - #[error("Ill-formed UTF-8 in username")] - UsernameConversion(#[from] std::str::Utf8Error), - #[error("User interpolation is not available on this platform")] - UserInterpolationUnsupported, - } - } - - impl<'a> Path<'a> { - /// Interpolates this path into a file system path. - /// - /// If this path starts with `~/` or `~user/` or `%(prefix)/` - /// - `~/` is expanded to the value of `home_dir`. The caller can use the [dirs](https://crates.io/crates/dirs) crate to obtain it. - /// It it is required but not set, an error is produced. - /// - `~user/` to the specified user’s home directory, e.g `~alice` might get expanded to `/home/alice` on linux. - /// The interpolation uses `getpwnam` sys call and is therefore not available on windows. See also [pwd](https://crates.io/crates/pwd). - /// - `%(prefix)/` is expanded to the location where gitoxide is installed. This location is not known at compile time and therefore need to be - /// optionally provided by the caller through `git_install_dir`. - /// - /// Any other, non-empty path value is returned unchanged and error is returned in case of an empty path value. - pub fn interpolate( - self, - git_install_dir: Option<&std::path::Path>, - home_dir: Option<&std::path::Path>, - ) -> Result, interpolate::Error> { - if self.is_empty() { - return Err(interpolate::Error::Missing { what: "path" }); - } - - const PREFIX: &[u8] = b"%(prefix)/"; - const USER_HOME: &[u8] = b"~/"; - if self.starts_with(PREFIX) { - let git_install_dir = git_install_dir.ok_or(interpolate::Error::Missing { - what: "git install dir", - })?; - let (_prefix, path_without_trailing_slash) = self.split_at(PREFIX.len()); - let path_without_trailing_slash = - git_path::try_from_bstring(path_without_trailing_slash).map_err(|err| { - interpolate::Error::Utf8Conversion { - what: "path past %(prefix)", - err, - } - })?; - Ok(git_install_dir.join(path_without_trailing_slash).into()) - } else if self.starts_with(USER_HOME) { - let home_path = home_dir.ok_or(interpolate::Error::Missing { what: "home dir" })?; - let (_prefix, val) = self.split_at(USER_HOME.len()); - let val = git_path::try_from_byte_slice(val).map_err(|err| interpolate::Error::Utf8Conversion { - what: "path past ~/", - err, - })?; - Ok(home_path.join(val).into()) - } else if self.starts_with(b"~") && self.contains(&b'/') { - self.interpolate_user() - } else { - Ok(git_path::from_bstr(self.value)) - } - } - - #[cfg(any(target_os = "windows", target_os = "android"))] - fn interpolate_user(self) -> Result, interpolate::Error> { - Err(interpolate::Error::UserInterpolationUnsupported) - } - - #[cfg(not(windows))] - fn interpolate_user(self) -> Result, interpolate::Error> { - let (_prefix, val) = self.split_at("/".len()); - let i = val - .iter() - .position(|&e| e == b'/') - .ok_or(interpolate::Error::Missing { what: "/" })?; - let (username, path_with_leading_slash) = val.split_at(i); - let username = std::str::from_utf8(username)?; - let home = home_for_user(username).ok_or(interpolate::Error::Missing { what: "pwd user info" })?; - let path_past_user_prefix = - git_path::try_from_byte_slice(&path_with_leading_slash["/".len()..]).map_err(|err| { - interpolate::Error::Utf8Conversion { - what: "path past ~user/", - err, - } - })?; - Ok(home.join(path_past_user_prefix).into()) - } - } -} - -/// Any value that can be interpreted as a file path. -/// -/// Git represents file paths as byte arrays, modeled here as owned or borrowed byte sequences. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] -pub struct Path<'a> { - /// The path string, un-interpolated - pub value: Cow<'a, BStr>, -} - -impl<'a> std::ops::Deref for Path<'a> { - type Target = BStr; - - fn deref(&self) -> &Self::Target { - self.value.as_ref() - } -} - -impl<'a> AsRef<[u8]> for Path<'a> { - fn as_ref(&self) -> &[u8] { - self.value.as_ref() - } -} - -impl<'a> AsRef for Path<'a> { - fn as_ref(&self) -> &BStr { - self.value.as_ref() - } -} - -impl<'a> From> for Path<'a> { - fn from(value: Cow<'a, BStr>) -> Self { - Path { value } - } -} diff --git a/git-config/tests/file/access/read_only.rs b/git-config/tests/file/access/read_only.rs index 199fd0efd7b..f48a6ebe86f 100644 --- a/git-config/tests/file/access/read_only.rs +++ b/git-config/tests/file/access/read_only.rs @@ -94,7 +94,7 @@ fn get_value_for_all_provided_values() -> crate::Result { "hello world" ); - let actual = file.value::("core", None, "location")?; + let actual = file.value::("core", None, "location")?; assert_eq!(&*actual, "~/tmp", "no interpolation occurs when querying a path"); let home = std::env::current_dir()?; diff --git a/git-config/tests/values/path.rs b/git-config/tests/values/path.rs index 7bbc2f37fc0..1f90622cfd0 100644 --- a/git-config/tests/values/path.rs +++ b/git-config/tests/values/path.rs @@ -2,11 +2,11 @@ mod interpolate { use std::borrow::Cow; use std::path::Path; - use git_config::values::Path as InterpolatingPath; + use git_config::value::Path as InterpolatingPath; use crate::file::cow_str; use crate::values::b; - use git_config::values::path::interpolate::Error; + use git_config::value::path::interpolate::Error; #[test] fn backslash_is_not_special_and_they_are_not_escaping_anything() -> crate::Result { @@ -109,7 +109,7 @@ mod interpolate { fn interpolate_without_context( path: impl AsRef, - ) -> Result, git_config::values::path::interpolate::Error> { + ) -> Result, git_config::value::path::interpolate::Error> { InterpolatingPath::from(Cow::Owned(path.as_ref().to_owned().into())).interpolate(None, None) } } From 66a8237ff284c2cf7f80cc909c7b613b599e1358 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 6 Jul 2022 13:55:45 +0800 Subject: [PATCH 034/366] remove unused serde feature (#331) It might be brought back via serde1 --- Cargo.lock | 1 - git-config/Cargo.toml | 4 ---- git-config/src/lib.rs | 1 - git-config/src/value/boolean.rs | 4 ++-- git-config/src/value/color.rs | 6 +++--- git-config/src/value/integer.rs | 4 ++-- git-config/src/values.rs | 2 -- 7 files changed, 7 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8a5155c3df2..082552dda0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1094,7 +1094,6 @@ dependencies = [ "libc", "memchr", "nom", - "serde", "serde_derive", "serial_test", "tempfile", diff --git a/git-config/Cargo.toml b/git-config/Cargo.toml index 4c1e97a29cf..30d87454191 100644 --- a/git-config/Cargo.toml +++ b/git-config/Cargo.toml @@ -10,9 +10,6 @@ keywords = ["git-config", "git", "config", "gitoxide"] categories = ["config", "parser-implementations"] include = ["src/**/*", "LICENSE-*", "README.md", "CHANGELOG.md"] -[features] -# serde = ["serde_crate"] - [dependencies] git-features = { version = "^0.21.1", path = "../git-features"} git-path = { version = "^0.3.0", path = "../git-path" } @@ -22,7 +19,6 @@ git-glob = { version = "0.3.0", path = "../git-glob" } nom = { version = "7", default_features = false, features = [ "std" ] } memchr = "2" -serde_crate = { version = "1", package = "serde", optional = true } thiserror = "1.0.26" unicode-bom = "1.1.4" bstr = { version = "0.2.13", default-features = false, features = ["std"] } diff --git a/git-config/src/lib.rs b/git-config/src/lib.rs index 180efd2d29f..ec9c9af0978 100644 --- a/git-config/src/lib.rs +++ b/git-config/src/lib.rs @@ -59,7 +59,6 @@ pub mod fs; pub mod lookup; pub mod parser; mod permissions; -/// The future home of the `values` module (TODO). pub mod value; pub mod values; diff --git a/git-config/src/value/boolean.rs b/git-config/src/value/boolean.rs index 81505051cab..baa08761883 100644 --- a/git-config/src/value/boolean.rs +++ b/git-config/src/value/boolean.rs @@ -129,7 +129,7 @@ impl From<&Boolean<'_>> for BString { } #[cfg(feature = "serde")] -impl Serialize for Boolean<'_> { +impl serde::Serialize for Boolean<'_> { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, @@ -208,7 +208,7 @@ impl<'a, 'b: 'a> From<&'b TrueVariant<'a>> for &'a BStr { } #[cfg(feature = "serde")] -impl Serialize for TrueVariant<'_> { +impl serde::Serialize for TrueVariant<'_> { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, diff --git a/git-config/src/value/color.rs b/git-config/src/value/color.rs index d4bf8f9352c..fc3ee8c3b18 100644 --- a/git-config/src/value/color.rs +++ b/git-config/src/value/color.rs @@ -51,7 +51,7 @@ impl Display for Color { } #[cfg(feature = "serde")] -impl Serialize for Color { +impl serde::Serialize for Color { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, @@ -199,7 +199,7 @@ impl Display for ColorValue { } #[cfg(feature = "serde")] -impl Serialize for ColorValue { +impl serde::Serialize for ColorValue { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, @@ -318,7 +318,7 @@ impl Display for ColorAttribute { } #[cfg(feature = "serde")] -impl Serialize for ColorAttribute { +impl serde::Serialize for ColorAttribute { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, diff --git a/git-config/src/value/integer.rs b/git-config/src/value/integer.rs index be024d0b00e..c3886a7be85 100644 --- a/git-config/src/value/integer.rs +++ b/git-config/src/value/integer.rs @@ -62,7 +62,7 @@ impl Display for Integer { } #[cfg(feature = "serde")] -impl Serialize for Integer { +impl serde::Serialize for Integer { fn serialize(&self, serializer: S) -> Result where S: Serializer, @@ -174,7 +174,7 @@ impl Display for IntegerSuffix { } #[cfg(feature = "serde")] -impl Serialize for IntegerSuffix { +impl serde::Serialize for IntegerSuffix { fn serialize(&self, serializer: S) -> Result where S: Serializer, diff --git a/git-config/src/values.rs b/git-config/src/values.rs index fee3c2dbd5f..ce194df06fe 100644 --- a/git-config/src/values.rs +++ b/git-config/src/values.rs @@ -3,8 +3,6 @@ use std::borrow::Cow; use bstr::{BStr, BString}; -#[cfg(feature = "serde")] -use serde::{Serialize, Serializer}; /// Removes quotes, if any, from the provided inputs. This assumes the input /// contains a even number of unescaped quotes, and will unescape escaped From 5a8f242ee98793e2467e7bc9806f8780b9d320ce Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 6 Jul 2022 14:02:35 +0800 Subject: [PATCH 035/366] feat: `serde1` feature to add limited serde support (#331) --- Cargo.lock | 1 + git-config/Cargo.toml | 5 +++++ git-config/src/lib.rs | 7 ------- git-config/src/value/integer.rs | 4 ++-- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 082552dda0b..8a5155c3df2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1094,6 +1094,7 @@ dependencies = [ "libc", "memchr", "nom", + "serde", "serde_derive", "serial_test", "tempfile", diff --git a/git-config/Cargo.toml b/git-config/Cargo.toml index 30d87454191..94233e08cfc 100644 --- a/git-config/Cargo.toml +++ b/git-config/Cargo.toml @@ -10,6 +10,10 @@ keywords = ["git-config", "git", "config", "gitoxide"] categories = ["config", "parser-implementations"] include = ["src/**/*", "LICENSE-*", "README.md", "CHANGELOG.md"] +[features] +## Data structures implement `serde::Serialize` and `serde::Deserialize`. +serde1 = ["serde", "bstr/serde1", "git-sec/serde1", "git-ref/serde1", "git-glob/serde1"] + [dependencies] git-features = { version = "^0.21.1", path = "../git-features"} git-path = { version = "^0.3.0", path = "../git-path" } @@ -22,6 +26,7 @@ memchr = "2" thiserror = "1.0.26" unicode-bom = "1.1.4" bstr = { version = "0.2.13", default-features = false, features = ["std"] } +serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]} [target.'cfg(not(windows))'.dependencies] libc = "0.2" diff --git a/git-config/src/lib.rs b/git-config/src/lib.rs index ec9c9af0978..81ed52c938d 100644 --- a/git-config/src/lib.rs +++ b/git-config/src/lib.rs @@ -47,13 +47,6 @@ //! [`values`]: crate::values //! [`nom`]: https://github.com/Geal/nom -// Cargo.toml cannot have self-referential dependencies, so you can't just -// specify the actual serde crate when you define a feature called serde. We -// instead call the serde crate as serde_crate and then rename the crate to -// serde, to get around this in an intuitive manner. -#[cfg(feature = "serde")] -extern crate serde_crate as serde; - pub mod file; pub mod fs; pub mod lookup; diff --git a/git-config/src/value/integer.rs b/git-config/src/value/integer.rs index c3886a7be85..aa61fa2dc82 100644 --- a/git-config/src/value/integer.rs +++ b/git-config/src/value/integer.rs @@ -65,7 +65,7 @@ impl Display for Integer { impl serde::Serialize for Integer { fn serialize(&self, serializer: S) -> Result where - S: Serializer, + S: serde::Serializer, { if let Some(suffix) = self.suffix { serializer.serialize_i64(self.value << suffix.bitwise_offset()) @@ -177,7 +177,7 @@ impl Display for IntegerSuffix { impl serde::Serialize for IntegerSuffix { fn serialize(&self, serializer: S) -> Result where - S: Serializer, + S: serde::Serializer, { serializer.serialize_str(match self { Self::Kibi => "k", From 26e4a9c83af7550eab1acaf0256099774be97965 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 6 Jul 2022 14:09:24 +0800 Subject: [PATCH 036/366] Documentation for feature flags (#331) --- Cargo.lock | 1 + git-config/Cargo.toml | 6 ++++++ git-config/src/lib.rs | 6 ++++++ git-config/src/value/path.rs | 2 +- git-config/src/values.rs | 4 ++-- 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8a5155c3df2..4b4d5548eba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1084,6 +1084,7 @@ version = "0.6.0" dependencies = [ "bstr", "criterion", + "document-features", "git-features", "git-glob", "git-path", diff --git a/git-config/Cargo.toml b/git-config/Cargo.toml index 94233e08cfc..ad4c325dd5b 100644 --- a/git-config/Cargo.toml +++ b/git-config/Cargo.toml @@ -28,6 +28,8 @@ unicode-bom = "1.1.4" bstr = { version = "0.2.13", default-features = false, features = ["std"] } serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]} +document-features = { version = "0.2.0", optional = true } + [target.'cfg(not(windows))'.dependencies] libc = "0.2" @@ -43,3 +45,7 @@ tempfile = "3.2.0" name = "large_config_file" harness = false path = "./benches/large_config_file.rs" + +[package.metadata.docs.rs] +features = ["document-features"] +all-features = true diff --git a/git-config/src/lib.rs b/git-config/src/lib.rs index 81ed52c938d..309cf7f711c 100644 --- a/git-config/src/lib.rs +++ b/git-config/src/lib.rs @@ -46,6 +46,12 @@ //! [`Parser`]: crate::parser::Parser //! [`values`]: crate::values //! [`nom`]: https://github.com/Geal/nom +//! +//! ## Feature Flags +#![cfg_attr( +feature = "document-features", +cfg_attr(doc, doc = ::document_features::document_features!()) +)] pub mod file; pub mod fs; diff --git a/git-config/src/value/path.rs b/git-config/src/value/path.rs index 8b3746d2f80..0a3fb5cf81a 100644 --- a/git-config/src/value/path.rs +++ b/git-config/src/value/path.rs @@ -3,7 +3,7 @@ use bstr::BStr; use std::borrow::Cow; pub mod interpolate { - /// The error returned by [`Path::interpolate()`][crate::values::Path::interpolate()]. + /// The error returned by [`Path::interpolate()`][crate::value::Path::interpolate()]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { diff --git a/git-config/src/values.rs b/git-config/src/values.rs index ce194df06fe..70a5f0fd773 100644 --- a/git-config/src/values.rs +++ b/git-config/src/values.rs @@ -120,13 +120,13 @@ pub fn normalize(input: Cow<'_, BStr>) -> Cow<'_, BStr> { } } -/// `&[u8]` variant of [`normalize_cow`]. +/// `&[u8]` variant of [`normalize`]. #[must_use] pub fn normalize_bstr<'a>(input: impl Into<&'a BStr>) -> Cow<'a, BStr> { normalize(Cow::Borrowed(input.into())) } -/// `Vec[u8]` variant of [`normalize_cow`]. +/// `Vec[u8]` variant of [`normalize`]. #[must_use] pub fn normalize_bstring(input: impl Into) -> Cow<'static, BStr> { normalize(Cow::Owned(input.into())) From 58b22152a0295998935abb43563e9096589ef53e Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 6 Jul 2022 14:18:57 +0800 Subject: [PATCH 037/366] change!: rename `normalize_cow()` to `normalize()` and move all `normalize*` functions from `values` to the `value` module (#331) --- git-config/src/file/access/comfort.rs | 2 +- git-config/src/file/section.rs | 2 +- git-config/src/file/value.rs | 2 +- git-config/src/value/mod.rs | 3 + git-config/src/value/normalize.rs | 131 +++++++++++++++++++++++++ git-config/src/value/string.rs | 2 +- git-config/src/values.rs | 132 -------------------------- git-config/tests/values/normalize.rs | 2 +- 8 files changed, 139 insertions(+), 137 deletions(-) create mode 100644 git-config/src/value/normalize.rs diff --git a/git-config/src/file/access/comfort.rs b/git-config/src/file/access/comfort.rs index 5fe3e0a67e2..061060b085b 100644 --- a/git-config/src/file/access/comfort.rs +++ b/git-config/src/file/access/comfort.rs @@ -2,7 +2,7 @@ use std::{borrow::Cow, convert::TryFrom}; use bstr::BStr; -use crate::values::normalize; +use crate::value::normalize; use crate::{value, File}; /// Comfortable API for accessing values diff --git a/git-config/src/file/section.rs b/git-config/src/file/section.rs index f1e13013793..11bae92c9da 100644 --- a/git-config/src/file/section.rs +++ b/git-config/src/file/section.rs @@ -11,7 +11,7 @@ use crate::{ file::Index, lookup, parser::{Event, Key}, - values::{normalize, normalize_bstring}, + value::{normalize, normalize_bstring}, }; /// A opaque type that represents a mutable reference to a section. diff --git a/git-config/src/file/value.rs b/git-config/src/file/value.rs index ad688404578..951ccb0e2eb 100644 --- a/git-config/src/file/value.rs +++ b/git-config/src/file/value.rs @@ -8,7 +8,7 @@ use crate::{ }, lookup, parser::{Event, Key}, - values::{normalize_bstr, normalize_bstring}, + value::{normalize_bstr, normalize_bstring}, }; /// An intermediate representation of a mutable value obtained from diff --git a/git-config/src/value/mod.rs b/git-config/src/value/mod.rs index 97bfc72289d..bb60d36f1ee 100644 --- a/git-config/src/value/mod.rs +++ b/git-config/src/value/mod.rs @@ -1,3 +1,6 @@ +mod normalize; +pub use normalize::{normalize, normalize_bstr, normalize_bstring}; + mod string; pub use string::String; diff --git a/git-config/src/value/normalize.rs b/git-config/src/value/normalize.rs new file mode 100644 index 00000000000..e8b0e45875f --- /dev/null +++ b/git-config/src/value/normalize.rs @@ -0,0 +1,131 @@ +use std::borrow::Cow; + +use bstr::{BStr, BString}; + +/// Removes quotes, if any, from the provided inputs. This assumes the input +/// contains a even number of unescaped quotes, and will unescape escaped +/// quotes. The return values should be safe for value interpretation. +/// +/// This has optimizations for fully-quoted values, where the returned value +/// will be a borrowed reference if the only mutation necessary is to unquote +/// the value. +/// +/// This is the function used to normalize raw values from higher level +/// abstractions over the [`parser`] implementation. Generally speaking these +/// high level abstractions will handle normalization for you, and you do not +/// need to call this yourself. However, if you're directly handling events +/// from the parser, you may want to use this to help with value interpretation. +/// +/// Generally speaking, you'll want to use one of the variants of this function, +/// such as [`normalize_bstr`] or [`normalize_bstring`]. +/// +/// # Examples +/// +/// Values don't need modification are returned borrowed, without allocation. +/// +/// ``` +/// # use std::borrow::Cow; +/// # use bstr::ByteSlice; +/// # use git_config::value::normalize_bstr; +/// assert_eq!(normalize_bstr("hello world"), Cow::Borrowed(b"hello world".as_bstr())); +/// ``` +/// +/// Fully quoted values are optimized to not need allocations. +/// +/// ``` +/// # use std::borrow::Cow; +/// # use bstr::ByteSlice; +/// # use git_config::value::normalize_bstr; +/// assert_eq!(normalize_bstr("\"hello world\""), Cow::Borrowed(b"hello world".as_bstr())); +/// ``` +/// +/// Quoted values are unwrapped as an owned variant. +/// +/// ``` +/// # use std::borrow::Cow; +/// # use bstr::{BStr, BString}; +/// # use git_config::value::{normalize_bstr}; +/// assert_eq!(normalize_bstr("hello \"world\""), Cow::::Owned(BString::from( "hello world" ))); +/// ``` +/// +/// Escaped quotes are unescaped. +/// +/// ``` +/// # use std::borrow::Cow; +/// # use bstr::{BStr, BString}; +/// # use git_config::value::normalize_bstr; +/// assert_eq!(normalize_bstr(r#"hello "world\"""#), Cow::::Owned(BString::from(r#"hello world""#))); +/// ``` +/// +/// [`parser`]: crate::parser::Parser +#[must_use] +pub fn normalize(input: Cow<'_, BStr>) -> Cow<'_, BStr> { + let size = input.len(); + if input.as_ref() == "\"\"" { + return Cow::default(); + } + + if size >= 3 && input[0] == b'=' && input[size - 1] == b'=' && input[size - 2] != b'\\' { + match input { + Cow::Borrowed(input) => return normalize_bstr(&input[1..size]), + Cow::Owned(mut input) => { + input.pop(); + input.remove(0); + return normalize_bstring(input); + } + } + } + + let mut owned = BString::default(); + + let mut first_index = 0; + let mut last_index = 0; + let mut was_escaped = false; + for (i, c) in input.iter().enumerate() { + if was_escaped { + was_escaped = false; + if *c == b'"' { + if first_index == 0 { + owned.extend(&*input[last_index..i - 1]); + last_index = i; + } else { + owned.extend(&*input[first_index..i - 1]); + first_index = i; + } + } + continue; + } + + if *c == b'\\' { + was_escaped = true; + } else if *c == b'"' { + if first_index == 0 { + owned.extend(&*input[last_index..i]); + first_index = i + 1; + } else { + owned.extend(&*input[first_index..i]); + first_index = 0; + last_index = i + 1; + } + } + } + + if last_index == 0 { + input + } else { + owned.extend(&*input[last_index..]); + Cow::Owned(owned) + } +} + +/// `&[u8]` variant of [`normalize`]. +#[must_use] +pub fn normalize_bstr<'a>(input: impl Into<&'a BStr>) -> Cow<'a, BStr> { + normalize(Cow::Borrowed(input.into())) +} + +/// `Vec[u8]` variant of [`normalize`]. +#[must_use] +pub fn normalize_bstring(input: impl Into) -> Cow<'static, BStr> { + normalize(Cow::Owned(input.into())) +} diff --git a/git-config/src/value/string.rs b/git-config/src/value/string.rs index 633914dff45..28b96a728e7 100644 --- a/git-config/src/value/string.rs +++ b/git-config/src/value/string.rs @@ -11,7 +11,7 @@ pub struct String<'a> { impl<'a> From> for String<'a> { fn from(c: Cow<'a, BStr>) -> Self { String { - value: crate::values::normalize(c), + value: crate::value::normalize(c), } } } diff --git a/git-config/src/values.rs b/git-config/src/values.rs index 70a5f0fd773..a6efe5b033d 100644 --- a/git-config/src/values.rs +++ b/git-config/src/values.rs @@ -1,133 +1 @@ //! Rust containers for valid `git-config` types. - -use std::borrow::Cow; - -use bstr::{BStr, BString}; - -/// Removes quotes, if any, from the provided inputs. This assumes the input -/// contains a even number of unescaped quotes, and will unescape escaped -/// quotes. The return values should be safe for value interpretation. -/// -/// This has optimizations for fully-quoted values, where the returned value -/// will be a borrowed reference if the only mutation necessary is to unquote -/// the value. -/// -/// This is the function used to normalize raw values from higher level -/// abstractions over the [`parser`] implementation. Generally speaking these -/// high level abstractions will handle normalization for you, and you do not -/// need to call this yourself. However, if you're directly handling events -/// from the parser, you may want to use this to help with value interpretation. -/// -/// Generally speaking, you'll want to use one of the variants of this function, -/// such as [`normalize_bstr`] or [`normalize_bstring`]. -/// -/// # Examples -/// -/// Values don't need modification are returned borrowed, without allocation. -/// -/// ``` -/// # use std::borrow::Cow; -/// # use bstr::ByteSlice; -/// # use git_config::values::normalize_bstr; -/// assert_eq!(normalize_bstr("hello world"), Cow::Borrowed(b"hello world".as_bstr())); -/// ``` -/// -/// Fully quoted values are optimized to not need allocations. -/// -/// ``` -/// # use std::borrow::Cow; -/// # use bstr::ByteSlice; -/// # use git_config::values::normalize_bstr; -/// assert_eq!(normalize_bstr("\"hello world\""), Cow::Borrowed(b"hello world".as_bstr())); -/// ``` -/// -/// Quoted values are unwrapped as an owned variant. -/// -/// ``` -/// # use std::borrow::Cow; -/// # use bstr::{BStr, BString}; -/// # use git_config::values::{normalize_bstr}; -/// assert_eq!(normalize_bstr("hello \"world\""), Cow::::Owned(BString::from( "hello world" ))); -/// ``` -/// -/// Escaped quotes are unescaped. -/// -/// ``` -/// # use std::borrow::Cow; -/// # use bstr::{BStr, BString}; -/// # use git_config::values::normalize_bstr; -/// assert_eq!(normalize_bstr(r#"hello "world\"""#), Cow::::Owned(BString::from(r#"hello world""#))); -/// ``` -/// -/// [`parser`]: crate::parser::Parser -#[must_use] -pub fn normalize(input: Cow<'_, BStr>) -> Cow<'_, BStr> { - let size = input.len(); - if input.as_ref() == "\"\"" { - return Cow::default(); - } - - if size >= 3 && input[0] == b'=' && input[size - 1] == b'=' && input[size - 2] != b'\\' { - match input { - Cow::Borrowed(input) => return normalize_bstr(&input[1..size]), - Cow::Owned(mut input) => { - input.pop(); - input.remove(0); - return normalize_bstring(input); - } - } - } - - let mut owned = BString::default(); - - let mut first_index = 0; - let mut last_index = 0; - let mut was_escaped = false; - for (i, c) in input.iter().enumerate() { - if was_escaped { - was_escaped = false; - if *c == b'"' { - if first_index == 0 { - owned.extend(&*input[last_index..i - 1]); - last_index = i; - } else { - owned.extend(&*input[first_index..i - 1]); - first_index = i; - } - } - continue; - } - - if *c == b'\\' { - was_escaped = true; - } else if *c == b'"' { - if first_index == 0 { - owned.extend(&*input[last_index..i]); - first_index = i + 1; - } else { - owned.extend(&*input[first_index..i]); - first_index = 0; - last_index = i + 1; - } - } - } - - if last_index == 0 { - input - } else { - owned.extend(&*input[last_index..]); - Cow::Owned(owned) - } -} - -/// `&[u8]` variant of [`normalize`]. -#[must_use] -pub fn normalize_bstr<'a>(input: impl Into<&'a BStr>) -> Cow<'a, BStr> { - normalize(Cow::Borrowed(input.into())) -} - -/// `Vec[u8]` variant of [`normalize`]. -#[must_use] -pub fn normalize_bstring(input: impl Into) -> Cow<'static, BStr> { - normalize(Cow::Owned(input.into())) -} diff --git a/git-config/tests/values/normalize.rs b/git-config/tests/values/normalize.rs index febe5d5b806..6ea0283a796 100644 --- a/git-config/tests/values/normalize.rs +++ b/git-config/tests/values/normalize.rs @@ -1,5 +1,5 @@ use crate::file::cow_str; -use git_config::values::normalize_bstr; +use git_config::value::normalize_bstr; #[test] fn not_modified_is_borrowed() { From 0d1be2b893574f2a9d4ba35ac4f2b3da710d4b03 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 6 Jul 2022 14:26:17 +0800 Subject: [PATCH 038/366] fix docs (#331) --- git-config/src/lib.rs | 7 +++---- git-config/src/values.rs | 1 - 2 files changed, 3 insertions(+), 5 deletions(-) delete mode 100644 git-config/src/values.rs diff --git a/git-config/src/lib.rs b/git-config/src/lib.rs index 309cf7f711c..8ae1ff6ff94 100644 --- a/git-config/src/lib.rs +++ b/git-config/src/lib.rs @@ -16,11 +16,11 @@ //! | ------------- | --------------------------------------------------- | ----------------- | //! | [`File`] | Accelerated wrapper for reading and writing values. | On some reads[^1] | //! | [`Parser`] | Syntactic event emitter for `git-config` files. | Yes | -//! | [`values`] | Wrappers for `git-config` value types. | Yes | +//! | [`value`] | Wrappers for `git-config` value types. | Yes | //! //! This crate also exposes efficient value normalization which unescapes //! characters and removes quotes through the `normalize_*` family of functions, -//! located in the [`values`] module. +//! located in the [`value`] module. //! //! # Zero-copy versus zero-alloc //! @@ -44,7 +44,7 @@ //! [INI file format]: https://en.wikipedia.org/wiki/INI_file //! [`File`]: crate::File //! [`Parser`]: crate::parser::Parser -//! [`values`]: crate::values +//! [`value`]: crate::value //! [`nom`]: https://github.com/Geal/nom //! //! ## Feature Flags @@ -59,7 +59,6 @@ pub mod lookup; pub mod parser; mod permissions; pub mod value; -pub mod values; mod types; pub use types::File; diff --git a/git-config/src/values.rs b/git-config/src/values.rs deleted file mode 100644 index a6efe5b033d..00000000000 --- a/git-config/src/values.rs +++ /dev/null @@ -1 +0,0 @@ -//! Rust containers for valid `git-config` types. From 5b66202d96bf664ed84755afc3ec49c301ecd62c Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 6 Jul 2022 14:26:25 +0800 Subject: [PATCH 039/366] adjust to breaking changes in `git-config` (#331) --- git-repository/src/config.rs | 7 ++----- git-repository/src/lib.rs | 4 ++-- git-repository/src/repository/snapshots.rs | 2 +- git-repository/src/repository/worktree.rs | 6 +++--- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/git-repository/src/config.rs b/git-repository/src/config.rs index 9c8644fa1fa..d66684c124c 100644 --- a/git-repository/src/config.rs +++ b/git-repository/src/config.rs @@ -13,7 +13,7 @@ pub enum Error { #[error("Value '{}' at key '{}' could not be decoded as boolean", .value, .key)] DecodeBoolean { key: String, value: BString }, #[error(transparent)] - PathInterpolation(#[from] git_config::values::path::interpolate::Error), + PathInterpolation(#[from] git_config::value::path::interpolate::Error), } /// Utility type to keep pre-obtained configuration values. @@ -46,10 +46,7 @@ pub(crate) struct Cache { mod cache { use std::{convert::TryFrom, path::PathBuf}; - use git_config::{ - values::{Boolean, Integer}, - File, - }; + use git_config::{value::Boolean, value::Integer, File}; use super::{Cache, Error}; use crate::{bstr::ByteSlice, permission}; diff --git a/git-repository/src/lib.rs b/git-repository/src/lib.rs index bbc7cc663a8..e0a50bace5d 100644 --- a/git-repository/src/lib.rs +++ b/git-repository/src/lib.rs @@ -293,7 +293,7 @@ pub mod mailmap { #[error("The configured mailmap.blob could not be parsed")] BlobSpec(#[from] git_hash::decode::Error), #[error(transparent)] - PathInterpolate(#[from] git_config::values::path::interpolate::Error), + PathInterpolate(#[from] git_config::value::path::interpolate::Error), #[error("Could not find object configured in `mailmap.blob`")] FindExisting(#[from] crate::object::find::existing::OdbError), } @@ -444,7 +444,7 @@ pub mod discover { if let Some(cross_fs) = std::env::var_os("GIT_DISCOVERY_ACROSS_FILESYSTEM") .and_then(|v| Vec::from_os_string(v).ok().map(BString::from)) { - if let Ok(b) = git_config::values::Boolean::try_from(cross_fs) { + if let Ok(b) = git_config::value::Boolean::try_from(cross_fs) { opts.cross_fs = b.to_bool(); } } diff --git a/git-repository/src/repository/snapshots.rs b/git-repository/src/repository/snapshots.rs index f739ae7df98..529499aaed0 100644 --- a/git-repository/src/repository/snapshots.rs +++ b/git-repository/src/repository/snapshots.rs @@ -93,7 +93,7 @@ impl crate::Repository { let configured_path = self .config .resolved - .value::>("mailmap", None, "file") + .value::>("mailmap", None, "file") .ok() .and_then(|path| { let install_dir = self.install_dir().ok()?; diff --git a/git-repository/src/repository/worktree.rs b/git-repository/src/repository/worktree.rs index b8962476a83..ae3e0b7bb28 100644 --- a/git-repository/src/repository/worktree.rs +++ b/git-repository/src/repository/worktree.rs @@ -69,7 +69,7 @@ impl crate::Repository { /// Note that it may fail if there is no index. // TODO: test #[cfg(feature = "git-index")] - pub fn open_index(&self) -> Result { + pub fn open_index(&self) -> Result { use std::convert::{TryFrom, TryInto}; let thread_limit = self .config @@ -77,8 +77,8 @@ impl crate::Repository { .boolean("index", None, "threads") .map(|res| { res.map(|value| if value { 0usize } else { 1 }).or_else(|err| { - git_config::values::Integer::try_from(err.input.as_ref()) - .map_err(|err| crate::worktree::open_index::Error::ConfigIndexThreads { + git_config::value::Integer::try_from(err.input.as_ref()) + .map_err(|err| worktree::open_index::Error::ConfigIndexThreads { value: err.input.clone(), err, }) From 90dd2cec8ea88980365bfd08a16614d145e87095 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 6 Jul 2022 16:01:53 +0800 Subject: [PATCH 040/366] refactor (#331) --- git-config/src/file/try_from_str_tests.rs | 6 +- git-config/src/lib.rs | 30 +- git-config/src/{parser.rs => parser/mod.rs} | 595 +----------------- git-config/src/parser/tests.rs | 659 ++++++++++++++++++++ git-config/src/permissions.rs | 25 +- git-config/src/test_util.rs | 73 --- 6 files changed, 690 insertions(+), 698 deletions(-) rename git-config/src/{parser.rs => parser/mod.rs} (73%) create mode 100644 git-config/src/parser/tests.rs delete mode 100644 git-config/src/test_util.rs diff --git a/git-config/src/file/try_from_str_tests.rs b/git-config/src/file/try_from_str_tests.rs index 1b9f1768d9b..8359b8dd976 100644 --- a/git-config/src/file/try_from_str_tests.rs +++ b/git-config/src/file/try_from_str_tests.rs @@ -3,8 +3,10 @@ use std::convert::TryFrom; use super::{Cow, HashMap, LookupTreeNode, SectionId}; use crate::{ file::SectionBody, - parser::{Event, SectionHeaderName}, - test_util::{name_event, newline_event, section_header, value_event}, + parser::{ + tests::util::{name_event, newline_event, section_header, value_event}, + Event, SectionHeaderName, + }, File, }; diff --git a/git-config/src/lib.rs b/git-config/src/lib.rs index 8ae1ff6ff94..a69cf060310 100644 --- a/git-config/src/lib.rs +++ b/git-config/src/lib.rs @@ -57,36 +57,10 @@ pub mod file; pub mod fs; pub mod lookup; pub mod parser; -mod permissions; pub mod value; mod types; pub use types::File; -/// Configure security relevant options when loading a git configuration. -#[derive(Copy, Clone, Ord, PartialOrd, PartialEq, Eq, Debug, Hash)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] -pub struct Permissions { - /// How to use the system configuration. - /// This is defined as `$(prefix)/etc/gitconfig` on unix. - pub system: git_sec::Permission, - /// How to use the global configuration. - /// This is usually `~/.gitconfig`. - pub global: git_sec::Permission, - /// How to use the user configuration. - /// Second user-specific configuration path; if `$XDG_CONFIG_HOME` is not - /// set or empty, `$HOME/.config/git/config` will be used. - pub user: git_sec::Permission, - /// How to use the repository configuration. - pub repository: git_sec::Permission, - /// How to use worktree configuration from `config.worktree`. - // TODO: figure out how this really applies and provide more information here. - pub worktree: git_sec::Permission, - /// How to use the configuration from environment variables. - pub env: git_sec::Permission, - /// What to do when include files are encountered in loaded configuration. - pub includes: git_sec::Permission, -} - -#[cfg(test)] -pub mod test_util; +mod permissions; +pub use permissions::Permissions; diff --git a/git-config/src/parser.rs b/git-config/src/parser/mod.rs similarity index 73% rename from git-config/src/parser.rs rename to git-config/src/parser/mod.rs index 92f52177573..817d39f44cf 100644 --- a/git-config/src/parser.rs +++ b/git-config/src/parser/mod.rs @@ -1394,597 +1394,4 @@ fn take_newlines(i: &[u8]) -> IResult<&[u8], (&BStr, usize)> { } #[cfg(test)] -mod comments { - use super::comment; - use crate::test_util::{comment as parsed_comment, fully_consumed}; - - #[test] - fn semicolon() { - assert_eq!( - comment(b"; this is a semicolon comment").unwrap(), - fully_consumed(parsed_comment(';', " this is a semicolon comment")), - ); - } - - #[test] - fn octothorpe() { - assert_eq!( - comment(b"# this is an octothorpe comment").unwrap(), - fully_consumed(parsed_comment('#', " this is an octothorpe comment")), - ); - } - - #[test] - fn multiple_markers() { - assert_eq!( - comment(b"###### this is an octothorpe comment").unwrap(), - fully_consumed(parsed_comment('#', "##### this is an octothorpe comment")), - ); - } -} - -#[cfg(test)] -mod section_headers { - use super::section_header; - use crate::test_util::{fully_consumed, section_header as parsed_section_header}; - - #[test] - fn no_subsection() { - assert_eq!( - section_header(b"[hello]").unwrap(), - fully_consumed(parsed_section_header("hello", None)), - ); - } - - #[test] - fn modern_subsection() { - assert_eq!( - section_header(br#"[hello "world"]"#).unwrap(), - fully_consumed(parsed_section_header("hello", (" ", "world"))), - ); - } - - #[test] - fn escaped_subsection() { - assert_eq!( - section_header(br#"[hello "foo\\bar\""]"#).unwrap(), - fully_consumed(parsed_section_header("hello", (" ", r#"foo\bar""#))), - ); - } - - #[test] - fn deprecated_subsection() { - assert_eq!( - section_header(br#"[hello.world]"#).unwrap(), - fully_consumed(parsed_section_header("hello", (".", "world"))) - ); - } - - #[test] - fn empty_legacy_subsection_name() { - assert_eq!( - section_header(br#"[hello.]"#).unwrap(), - fully_consumed(parsed_section_header("hello", (".", ""))) - ); - } - - #[test] - fn empty_modern_subsection_name() { - assert_eq!( - section_header(br#"[hello ""]"#).unwrap(), - fully_consumed(parsed_section_header("hello", (" ", ""))) - ); - } - - #[test] - fn newline_in_header() { - assert!(section_header(b"[hello\n]").is_err()); - } - - #[test] - fn null_byte_in_header() { - assert!(section_header(b"[hello\0]").is_err()); - } - - #[test] - fn right_brace_in_subsection_name() { - assert_eq!( - section_header(br#"[hello "]"]"#).unwrap(), - fully_consumed(parsed_section_header("hello", (" ", "]"))) - ); - } -} - -#[cfg(test)] -mod config_name { - use super::config_name; - use crate::test_util::fully_consumed; - - #[test] - fn just_name() { - assert_eq!(config_name(b"name").unwrap(), fully_consumed("name".into())); - } - - #[test] - fn must_start_with_alphabetic() { - assert!(config_name(b"4aaa").is_err()); - assert!(config_name(b"-aaa").is_err()); - } - - #[test] - fn cannot_be_empty() { - assert!(config_name(b"").is_err()); - } -} - -#[cfg(test)] -mod section_body { - use super::{section_body, Event, ParserNode}; - use crate::test_util::{name_event, value_event, whitespace_event}; - - #[test] - fn whitespace_is_not_ambigious() { - let mut node = ParserNode::SectionHeader; - let mut vec = vec![]; - assert!(section_body(b"a =b", &mut node, &mut vec).is_ok()); - assert_eq!( - vec, - vec![ - name_event("a"), - whitespace_event(" "), - Event::KeyValueSeparator, - value_event("b") - ] - ); - - let mut vec = vec![]; - assert!(section_body(b"a= b", &mut node, &mut vec).is_ok()); - assert_eq!( - vec, - vec![ - name_event("a"), - Event::KeyValueSeparator, - whitespace_event(" "), - value_event("b") - ] - ); - } -} - -#[cfg(test)] -mod value_no_continuation { - use super::value_impl; - use crate::test_util::value_event; - - #[test] - fn no_comment() { - let mut events = vec![]; - assert_eq!(value_impl(b"hello", &mut events).unwrap().0, b""); - assert_eq!(events, vec![value_event("hello")]); - } - - #[test] - fn no_comment_newline() { - let mut events = vec![]; - assert_eq!(value_impl(b"hello\na", &mut events).unwrap().0, b"\na"); - assert_eq!(events, vec![value_event("hello")]); - } - - #[test] - fn semicolon_comment_not_consumed() { - let mut events = vec![]; - assert_eq!(value_impl(b"hello;world", &mut events).unwrap().0, b";world"); - assert_eq!(events, vec![value_event("hello")]); - } - - #[test] - fn octothorpe_comment_not_consumed() { - let mut events = vec![]; - assert_eq!(value_impl(b"hello#world", &mut events).unwrap().0, b"#world"); - assert_eq!(events, vec![value_event("hello")]); - } - - #[test] - fn values_with_extraneous_whitespace_without_comment() { - let mut events = vec![]; - assert_eq!( - value_impl(b"hello ", &mut events).unwrap().0, - b" " - ); - assert_eq!(events, vec![value_event("hello")]); - } - - #[test] - fn values_with_extraneous_whitespace_before_comment() { - let mut events = vec![]; - assert_eq!( - value_impl(b"hello #world", &mut events).unwrap().0, - b" #world" - ); - assert_eq!(events, vec![value_event("hello")]); - - let mut events = vec![]; - assert_eq!( - value_impl(b"hello ;world", &mut events).unwrap().0, - b" ;world" - ); - assert_eq!(events, vec![value_event("hello")]); - } - - #[test] - fn trans_escaped_comment_marker_not_consumed() { - let mut events = vec![]; - assert_eq!(value_impl(br##"hello"#"world; a"##, &mut events).unwrap().0, b"; a"); - assert_eq!(events, vec![value_event(r##"hello"#"world"##)]); - } - - #[test] - fn complex_test() { - let mut events = vec![]; - assert_eq!(value_impl(br#"value";";ahhhh"#, &mut events).unwrap().0, b";ahhhh"); - assert_eq!(events, vec![value_event(r#"value";""#)]); - } - - #[test] - fn garbage_after_continution_is_err() { - assert!(value_impl(b"hello \\afwjdls", &mut vec![]).is_err()); - } - - #[test] - fn incomplete_quote() { - assert!(value_impl(br#"hello "world"#, &mut vec![]).is_err()); - } - - #[test] - fn incomplete_escape() { - assert!(value_impl(br#"hello world\"#, &mut vec![]).is_err()); - } -} - -#[cfg(test)] -mod value_continuation { - use super::value_impl; - use crate::test_util::{newline_event, value_done_event, value_not_done_event}; - - #[test] - fn simple_continuation() { - let mut events = vec![]; - assert_eq!(value_impl(b"hello\\\nworld", &mut events).unwrap().0, b""); - assert_eq!( - events, - vec![ - value_not_done_event("hello"), - newline_event(), - value_done_event("world") - ] - ); - } - - #[test] - fn continuation_with_whitespace() { - let mut events = vec![]; - assert_eq!(value_impl(b"hello\\\n world", &mut events).unwrap().0, b""); - assert_eq!( - events, - vec![ - value_not_done_event("hello"), - newline_event(), - value_done_event(" world") - ] - ); - } - - #[test] - fn complex_continuation_with_leftover_comment() { - let mut events = vec![]; - assert_eq!( - value_impl(b"1 \"\\\"\\\na ; e \"\\\"\\\nd # \"b\t ; c", &mut events) - .unwrap() - .0, - b" # \"b\t ; c" - ); - assert_eq!( - events, - vec![ - value_not_done_event(r#"1 "\""#), - newline_event(), - value_not_done_event(r#"a ; e "\""#), - newline_event(), - value_done_event("d") - ] - ); - } - - #[test] - fn quote_split_over_two_lines_with_leftover_comment() { - let mut events = vec![]; - assert_eq!(value_impl(b"\"\\\n;\";a", &mut events).unwrap().0, b";a"); - assert_eq!( - events, - vec![value_not_done_event("\""), newline_event(), value_done_event(";\"")] - ); - } -} - -#[cfg(test)] -mod section { - use super::{section, Event, ParsedSection, ParserNode}; - use crate::test_util::{ - comment_event, fully_consumed, name_event, newline_event, section_header as parsed_section_header, - value_done_event, value_event, value_not_done_event, whitespace_event, - }; - - #[test] - fn empty_section() { - let mut node = ParserNode::SectionHeader; - assert_eq!( - section(b"[test]", &mut node).unwrap(), - fully_consumed(( - ParsedSection { - section_header: parsed_section_header("test", None), - events: vec![] - }, - 0 - )), - ); - } - - #[test] - fn simple_section() { - let mut node = ParserNode::SectionHeader; - let section_data = br#"[hello] - a = b - c - d = "lol""#; - assert_eq!( - section(section_data, &mut node).unwrap(), - fully_consumed(( - ParsedSection { - section_header: parsed_section_header("hello", None), - events: vec![ - newline_event(), - whitespace_event(" "), - name_event("a"), - whitespace_event(" "), - Event::KeyValueSeparator, - whitespace_event(" "), - value_event("b"), - newline_event(), - whitespace_event(" "), - name_event("c"), - value_event(""), - newline_event(), - whitespace_event(" "), - name_event("d"), - whitespace_event(" "), - Event::KeyValueSeparator, - whitespace_event(" "), - value_event("\"lol\"") - ] - }, - 3 - )) - ); - } - - #[test] - fn section_with_empty_value() { - let mut node = ParserNode::SectionHeader; - let section_data = br#"[hello] - a = b - c= - d = "lol""#; - assert_eq!( - section(section_data, &mut node).unwrap(), - fully_consumed(( - ParsedSection { - section_header: parsed_section_header("hello", None), - events: vec![ - newline_event(), - whitespace_event(" "), - name_event("a"), - whitespace_event(" "), - Event::KeyValueSeparator, - whitespace_event(" "), - value_event("b"), - newline_event(), - whitespace_event(" "), - name_event("c"), - Event::KeyValueSeparator, - value_event(""), - newline_event(), - whitespace_event(" "), - name_event("d"), - whitespace_event(" "), - Event::KeyValueSeparator, - whitespace_event(" "), - value_event("\"lol\"") - ] - }, - 2 - )) - ); - } - - #[test] - fn section_single_line() { - let mut node = ParserNode::SectionHeader; - assert_eq!( - section(b"[hello] c", &mut node).unwrap(), - fully_consumed(( - ParsedSection { - section_header: parsed_section_header("hello", None), - events: vec![whitespace_event(" "), name_event("c"), value_event("")] - }, - 0 - )) - ); - } - - #[test] - fn section_very_commented() { - let mut node = ParserNode::SectionHeader; - let section_data = br#"[hello] ; commentA - a = b # commentB - ; commentC - ; commentD - c = d"#; - assert_eq!( - section(section_data, &mut node).unwrap(), - fully_consumed(( - ParsedSection { - section_header: parsed_section_header("hello", None), - events: vec![ - whitespace_event(" "), - comment_event(';', " commentA"), - newline_event(), - whitespace_event(" "), - name_event("a"), - whitespace_event(" "), - Event::KeyValueSeparator, - whitespace_event(" "), - value_event("b"), - whitespace_event(" "), - comment_event('#', " commentB"), - newline_event(), - whitespace_event(" "), - comment_event(';', " commentC"), - newline_event(), - whitespace_event(" "), - comment_event(';', " commentD"), - newline_event(), - whitespace_event(" "), - name_event("c"), - whitespace_event(" "), - Event::KeyValueSeparator, - whitespace_event(" "), - value_event("d"), - ] - }, - 4 - )) - ); - } - - #[test] - fn complex_continuation() { - let mut node = ParserNode::SectionHeader; - // This test is absolute hell. Good luck if this fails. - assert_eq!( - section(b"[section] a = 1 \"\\\"\\\na ; e \"\\\"\\\nd # \"b\t ; c", &mut node).unwrap(), - fully_consumed(( - ParsedSection { - section_header: parsed_section_header("section", None), - events: vec![ - whitespace_event(" "), - name_event("a"), - whitespace_event(" "), - Event::KeyValueSeparator, - whitespace_event(" "), - value_not_done_event(r#"1 "\""#), - newline_event(), - value_not_done_event(r#"a ; e "\""#), - newline_event(), - value_done_event("d"), - whitespace_event(" "), - comment_event('#', " \"b\t ; c"), - ] - }, - 0 - )) - ); - } - - #[test] - fn quote_split_over_two_lines() { - let mut node = ParserNode::SectionHeader; - assert_eq!( - section(b"[section \"a\"] b =\"\\\n;\";a", &mut node).unwrap(), - fully_consumed(( - ParsedSection { - section_header: parsed_section_header("section", (" ", "a")), - events: vec![ - whitespace_event(" "), - name_event("b"), - whitespace_event(" "), - Event::KeyValueSeparator, - value_not_done_event("\""), - newline_event(), - value_done_event(";\""), - comment_event(';', "a"), - ] - }, - 0 - )) - ); - } - - #[test] - fn section_handles_extranous_whitespace_before_comment() { - let mut node = ParserNode::SectionHeader; - assert_eq!( - section(b"[s]hello #world", &mut node).unwrap(), - fully_consumed(( - ParsedSection { - section_header: parsed_section_header("s", None), - events: vec![ - name_event("hello"), - whitespace_event(" "), - value_event(""), - comment_event('#', "world"), - ] - }, - 0 - )) - ); - } -} - -#[cfg(test)] -mod parse { - use crate::parser::{parse_from_bytes, parse_from_bytes_owned}; - - #[test] - fn parser_skips_bom() { - let bytes = b" - [core] - a = 1 - "; - let bytes_with_gb18030_bom = "\u{feff} - [core] - a = 1 - "; - - assert_eq!( - parse_from_bytes(bytes), - parse_from_bytes(bytes_with_gb18030_bom.as_bytes()) - ); - assert_eq!( - parse_from_bytes_owned(bytes), - parse_from_bytes_owned(bytes_with_gb18030_bom.as_bytes()) - ); - } -} - -#[cfg(test)] -mod error { - use super::parse_from_str; - - #[test] - fn line_no_is_one_indexed() { - assert_eq!(parse_from_str("[hello").unwrap_err().line_number(), 1); - } - - #[test] - fn remaining_data_contains_bad_tokens() { - assert_eq!(parse_from_str("[hello").unwrap_err().remaining_data(), b"[hello"); - } - - #[test] - fn to_string_truncates_extra_values() { - assert_eq!( - parse_from_str("[1234567890").unwrap_err().to_string(), - "Got an unexpected token on line 1 while trying to parse a section header: '[123456789' ... (1 characters omitted)" - ); - } -} +pub(crate) mod tests; diff --git a/git-config/src/parser/tests.rs b/git-config/src/parser/tests.rs new file mode 100644 index 00000000000..c50e6859a1f --- /dev/null +++ b/git-config/src/parser/tests.rs @@ -0,0 +1,659 @@ +mod comments { + use crate::parser::comment; + use crate::parser::tests::util::{comment as parsed_comment, fully_consumed}; + + #[test] + fn semicolon() { + assert_eq!( + comment(b"; this is a semicolon comment").unwrap(), + fully_consumed(parsed_comment(';', " this is a semicolon comment")), + ); + } + + #[test] + fn octothorpe() { + assert_eq!( + comment(b"# this is an octothorpe comment").unwrap(), + fully_consumed(parsed_comment('#', " this is an octothorpe comment")), + ); + } + + #[test] + fn multiple_markers() { + assert_eq!( + comment(b"###### this is an octothorpe comment").unwrap(), + fully_consumed(parsed_comment('#', "##### this is an octothorpe comment")), + ); + } +} + +mod section_headers { + use crate::parser::section_header; + use crate::parser::tests::util::{fully_consumed, section_header as parsed_section_header}; + + #[test] + fn no_subsection() { + assert_eq!( + section_header(b"[hello]").unwrap(), + fully_consumed(parsed_section_header("hello", None)), + ); + } + + #[test] + fn modern_subsection() { + assert_eq!( + section_header(br#"[hello "world"]"#).unwrap(), + fully_consumed(parsed_section_header("hello", (" ", "world"))), + ); + } + + #[test] + fn escaped_subsection() { + assert_eq!( + section_header(br#"[hello "foo\\bar\""]"#).unwrap(), + fully_consumed(parsed_section_header("hello", (" ", r#"foo\bar""#))), + ); + } + + #[test] + fn deprecated_subsection() { + assert_eq!( + section_header(br#"[hello.world]"#).unwrap(), + fully_consumed(parsed_section_header("hello", (".", "world"))) + ); + } + + #[test] + fn empty_legacy_subsection_name() { + assert_eq!( + section_header(br#"[hello.]"#).unwrap(), + fully_consumed(parsed_section_header("hello", (".", ""))) + ); + } + + #[test] + fn empty_modern_subsection_name() { + assert_eq!( + section_header(br#"[hello ""]"#).unwrap(), + fully_consumed(parsed_section_header("hello", (" ", ""))) + ); + } + + #[test] + fn newline_in_header() { + assert!(section_header(b"[hello\n]").is_err()); + } + + #[test] + fn null_byte_in_header() { + assert!(section_header(b"[hello\0]").is_err()); + } + + #[test] + fn right_brace_in_subsection_name() { + assert_eq!( + section_header(br#"[hello "]"]"#).unwrap(), + fully_consumed(parsed_section_header("hello", (" ", "]"))) + ); + } +} + +mod config_name { + use crate::parser::config_name; + use crate::parser::tests::util::fully_consumed; + + #[test] + fn just_name() { + assert_eq!(config_name(b"name").unwrap(), fully_consumed("name".into())); + } + + #[test] + fn must_start_with_alphabetic() { + assert!(config_name(b"4aaa").is_err()); + assert!(config_name(b"-aaa").is_err()); + } + + #[test] + fn cannot_be_empty() { + assert!(config_name(b"").is_err()); + } +} + +mod section_body { + use crate::parser::tests::util::{name_event, value_event, whitespace_event}; + use crate::parser::{section_body, Event, ParserNode}; + + #[test] + fn whitespace_is_not_ambigious() { + let mut node = ParserNode::SectionHeader; + let mut vec = vec![]; + assert!(section_body(b"a =b", &mut node, &mut vec).is_ok()); + assert_eq!( + vec, + vec![ + name_event("a"), + whitespace_event(" "), + Event::KeyValueSeparator, + value_event("b") + ] + ); + + let mut vec = vec![]; + assert!(section_body(b"a= b", &mut node, &mut vec).is_ok()); + assert_eq!( + vec, + vec![ + name_event("a"), + Event::KeyValueSeparator, + whitespace_event(" "), + value_event("b") + ] + ); + } +} + +mod value_no_continuation { + use crate::parser::tests::util::value_event; + use crate::parser::value_impl; + + #[test] + fn no_comment() { + let mut events = vec![]; + assert_eq!(value_impl(b"hello", &mut events).unwrap().0, b""); + assert_eq!(events, vec![value_event("hello")]); + } + + #[test] + fn no_comment_newline() { + let mut events = vec![]; + assert_eq!(value_impl(b"hello\na", &mut events).unwrap().0, b"\na"); + assert_eq!(events, vec![value_event("hello")]); + } + + #[test] + fn semicolon_comment_not_consumed() { + let mut events = vec![]; + assert_eq!(value_impl(b"hello;world", &mut events).unwrap().0, b";world"); + assert_eq!(events, vec![value_event("hello")]); + } + + #[test] + fn octothorpe_comment_not_consumed() { + let mut events = vec![]; + assert_eq!(value_impl(b"hello#world", &mut events).unwrap().0, b"#world"); + assert_eq!(events, vec![value_event("hello")]); + } + + #[test] + fn values_with_extraneous_whitespace_without_comment() { + let mut events = vec![]; + assert_eq!( + value_impl(b"hello ", &mut events).unwrap().0, + b" " + ); + assert_eq!(events, vec![value_event("hello")]); + } + + #[test] + fn values_with_extraneous_whitespace_before_comment() { + let mut events = vec![]; + assert_eq!( + value_impl(b"hello #world", &mut events).unwrap().0, + b" #world" + ); + assert_eq!(events, vec![value_event("hello")]); + + let mut events = vec![]; + assert_eq!( + value_impl(b"hello ;world", &mut events).unwrap().0, + b" ;world" + ); + assert_eq!(events, vec![value_event("hello")]); + } + + #[test] + fn trans_escaped_comment_marker_not_consumed() { + let mut events = vec![]; + assert_eq!(value_impl(br##"hello"#"world; a"##, &mut events).unwrap().0, b"; a"); + assert_eq!(events, vec![value_event(r##"hello"#"world"##)]); + } + + #[test] + fn complex_test() { + let mut events = vec![]; + assert_eq!(value_impl(br#"value";";ahhhh"#, &mut events).unwrap().0, b";ahhhh"); + assert_eq!(events, vec![value_event(r#"value";""#)]); + } + + #[test] + fn garbage_after_continution_is_err() { + assert!(value_impl(b"hello \\afwjdls", &mut vec![]).is_err()); + } + + #[test] + fn incomplete_quote() { + assert!(value_impl(br#"hello "world"#, &mut vec![]).is_err()); + } + + #[test] + fn incomplete_escape() { + assert!(value_impl(br#"hello world\"#, &mut vec![]).is_err()); + } +} + +mod value_continuation { + use crate::parser::tests::util::{newline_event, value_done_event, value_not_done_event}; + use crate::parser::value_impl; + + #[test] + fn simple_continuation() { + let mut events = vec![]; + assert_eq!(value_impl(b"hello\\\nworld", &mut events).unwrap().0, b""); + assert_eq!( + events, + vec![ + value_not_done_event("hello"), + newline_event(), + value_done_event("world") + ] + ); + } + + #[test] + fn continuation_with_whitespace() { + let mut events = vec![]; + assert_eq!(value_impl(b"hello\\\n world", &mut events).unwrap().0, b""); + assert_eq!( + events, + vec![ + value_not_done_event("hello"), + newline_event(), + value_done_event(" world") + ] + ); + } + + #[test] + fn complex_continuation_with_leftover_comment() { + let mut events = vec![]; + assert_eq!( + value_impl(b"1 \"\\\"\\\na ; e \"\\\"\\\nd # \"b\t ; c", &mut events) + .unwrap() + .0, + b" # \"b\t ; c" + ); + assert_eq!( + events, + vec![ + value_not_done_event(r#"1 "\""#), + newline_event(), + value_not_done_event(r#"a ; e "\""#), + newline_event(), + value_done_event("d") + ] + ); + } + + #[test] + fn quote_split_over_two_lines_with_leftover_comment() { + let mut events = vec![]; + assert_eq!(value_impl(b"\"\\\n;\";a", &mut events).unwrap().0, b";a"); + assert_eq!( + events, + vec![value_not_done_event("\""), newline_event(), value_done_event(";\"")] + ); + } +} + +mod section { + use crate::parser::tests::util::{ + comment_event, fully_consumed, name_event, newline_event, section_header as parsed_section_header, + value_done_event, value_event, value_not_done_event, whitespace_event, + }; + use crate::parser::{section, Event, ParsedSection, ParserNode}; + + #[test] + fn empty_section() { + let mut node = ParserNode::SectionHeader; + assert_eq!( + section(b"[test]", &mut node).unwrap(), + fully_consumed(( + ParsedSection { + section_header: parsed_section_header("test", None), + events: vec![] + }, + 0 + )), + ); + } + + #[test] + fn simple_section() { + let mut node = ParserNode::SectionHeader; + let section_data = br#"[hello] + a = b + c + d = "lol""#; + assert_eq!( + section(section_data, &mut node).unwrap(), + fully_consumed(( + ParsedSection { + section_header: parsed_section_header("hello", None), + events: vec![ + newline_event(), + whitespace_event(" "), + name_event("a"), + whitespace_event(" "), + Event::KeyValueSeparator, + whitespace_event(" "), + value_event("b"), + newline_event(), + whitespace_event(" "), + name_event("c"), + value_event(""), + newline_event(), + whitespace_event(" "), + name_event("d"), + whitespace_event(" "), + Event::KeyValueSeparator, + whitespace_event(" "), + value_event("\"lol\"") + ] + }, + 3 + )) + ); + } + + #[test] + fn section_with_empty_value() { + let mut node = ParserNode::SectionHeader; + let section_data = br#"[hello] + a = b + c= + d = "lol""#; + assert_eq!( + section(section_data, &mut node).unwrap(), + fully_consumed(( + ParsedSection { + section_header: parsed_section_header("hello", None), + events: vec![ + newline_event(), + whitespace_event(" "), + name_event("a"), + whitespace_event(" "), + Event::KeyValueSeparator, + whitespace_event(" "), + value_event("b"), + newline_event(), + whitespace_event(" "), + name_event("c"), + Event::KeyValueSeparator, + value_event(""), + newline_event(), + whitespace_event(" "), + name_event("d"), + whitespace_event(" "), + Event::KeyValueSeparator, + whitespace_event(" "), + value_event("\"lol\"") + ] + }, + 2 + )) + ); + } + + #[test] + fn section_single_line() { + let mut node = ParserNode::SectionHeader; + assert_eq!( + section(b"[hello] c", &mut node).unwrap(), + fully_consumed(( + ParsedSection { + section_header: parsed_section_header("hello", None), + events: vec![whitespace_event(" "), name_event("c"), value_event("")] + }, + 0 + )) + ); + } + + #[test] + fn section_very_commented() { + let mut node = ParserNode::SectionHeader; + let section_data = br#"[hello] ; commentA + a = b # commentB + ; commentC + ; commentD + c = d"#; + assert_eq!( + section(section_data, &mut node).unwrap(), + fully_consumed(( + ParsedSection { + section_header: parsed_section_header("hello", None), + events: vec![ + whitespace_event(" "), + comment_event(';', " commentA"), + newline_event(), + whitespace_event(" "), + name_event("a"), + whitespace_event(" "), + Event::KeyValueSeparator, + whitespace_event(" "), + value_event("b"), + whitespace_event(" "), + comment_event('#', " commentB"), + newline_event(), + whitespace_event(" "), + comment_event(';', " commentC"), + newline_event(), + whitespace_event(" "), + comment_event(';', " commentD"), + newline_event(), + whitespace_event(" "), + name_event("c"), + whitespace_event(" "), + Event::KeyValueSeparator, + whitespace_event(" "), + value_event("d"), + ] + }, + 4 + )) + ); + } + + #[test] + fn complex_continuation() { + let mut node = ParserNode::SectionHeader; + // This test is absolute hell. Good luck if this fails. + assert_eq!( + section(b"[section] a = 1 \"\\\"\\\na ; e \"\\\"\\\nd # \"b\t ; c", &mut node).unwrap(), + fully_consumed(( + ParsedSection { + section_header: parsed_section_header("section", None), + events: vec![ + whitespace_event(" "), + name_event("a"), + whitespace_event(" "), + Event::KeyValueSeparator, + whitespace_event(" "), + value_not_done_event(r#"1 "\""#), + newline_event(), + value_not_done_event(r#"a ; e "\""#), + newline_event(), + value_done_event("d"), + whitespace_event(" "), + comment_event('#', " \"b\t ; c"), + ] + }, + 0 + )) + ); + } + + #[test] + fn quote_split_over_two_lines() { + let mut node = ParserNode::SectionHeader; + assert_eq!( + section(b"[section \"a\"] b =\"\\\n;\";a", &mut node).unwrap(), + fully_consumed(( + ParsedSection { + section_header: parsed_section_header("section", (" ", "a")), + events: vec![ + whitespace_event(" "), + name_event("b"), + whitespace_event(" "), + Event::KeyValueSeparator, + value_not_done_event("\""), + newline_event(), + value_done_event(";\""), + comment_event(';', "a"), + ] + }, + 0 + )) + ); + } + + #[test] + fn section_handles_extranous_whitespace_before_comment() { + let mut node = ParserNode::SectionHeader; + assert_eq!( + section(b"[s]hello #world", &mut node).unwrap(), + fully_consumed(( + ParsedSection { + section_header: parsed_section_header("s", None), + events: vec![ + name_event("hello"), + whitespace_event(" "), + value_event(""), + comment_event('#', "world"), + ] + }, + 0 + )) + ); + } +} + +mod parse { + use crate::parser::{parse_from_bytes, parse_from_bytes_owned}; + + #[test] + fn parser_skips_bom() { + let bytes = b" + [core] + a = 1 + "; + let bytes_with_gb18030_bom = "\u{feff} + [core] + a = 1 + "; + + assert_eq!( + parse_from_bytes(bytes), + parse_from_bytes(bytes_with_gb18030_bom.as_bytes()) + ); + assert_eq!( + parse_from_bytes_owned(bytes), + parse_from_bytes_owned(bytes_with_gb18030_bom.as_bytes()) + ); + } +} + +#[cfg(test)] +mod error { + use crate::parser::parse_from_str; + + #[test] + fn line_no_is_one_indexed() { + assert_eq!(parse_from_str("[hello").unwrap_err().line_number(), 1); + } + + #[test] + fn remaining_data_contains_bad_tokens() { + assert_eq!(parse_from_str("[hello").unwrap_err().remaining_data(), b"[hello"); + } + + #[test] + fn to_string_truncates_extra_values() { + assert_eq!( + parse_from_str("[1234567890").unwrap_err().to_string(), + "Got an unexpected token on line 1 while trying to parse a section header: '[123456789' ... (1 characters omitted)" + ); + } +} + +pub(crate) mod util { + //! This module is only included for tests, and contains common unit test helper + //! functions. + + use std::borrow::Cow; + + use crate::parser::{Event, Key, ParsedComment, ParsedSectionHeader}; + + pub fn section_header( + name: &str, + subsection: impl Into>, + ) -> ParsedSectionHeader<'_> { + let name = name.into(); + if let Some((separator, subsection_name)) = subsection.into() { + ParsedSectionHeader { + name, + separator: Some(Cow::Borrowed(separator.into())), + subsection_name: Some(Cow::Borrowed(subsection_name.into())), + } + } else { + ParsedSectionHeader { + name, + separator: None, + subsection_name: None, + } + } + } + + pub(crate) fn name_event(name: &'static str) -> Event<'static> { + Event::Key(Key(Cow::Borrowed(name.into()))) + } + + pub(crate) fn value_event(value: &'static str) -> Event<'static> { + Event::Value(Cow::Borrowed(value.into())) + } + + pub(crate) fn value_not_done_event(value: &'static str) -> Event<'static> { + Event::ValueNotDone(Cow::Borrowed(value.into())) + } + + pub(crate) fn value_done_event(value: &'static str) -> Event<'static> { + Event::ValueDone(Cow::Borrowed(value.into())) + } + + pub(crate) fn newline_event() -> Event<'static> { + newline_custom_event("\n") + } + + pub(crate) fn newline_custom_event(value: &'static str) -> Event<'static> { + Event::Newline(Cow::Borrowed(value.into())) + } + + pub(crate) fn whitespace_event(value: &'static str) -> Event<'static> { + Event::Whitespace(Cow::Borrowed(value.into())) + } + + pub(crate) fn comment_event(tag: char, msg: &'static str) -> Event<'static> { + Event::Comment(comment(tag, msg)) + } + + pub(crate) fn comment(comment_tag: char, comment: &'static str) -> ParsedComment<'static> { + ParsedComment { + comment_tag: comment_tag as u8, + comment: Cow::Borrowed(comment.into()), + } + } + + pub(crate) const fn fully_consumed(t: T) -> (&'static [u8], T) { + (&[], t) + } +} diff --git a/git-config/src/permissions.rs b/git-config/src/permissions.rs index d5cfd95e56e..327792e5f0d 100644 --- a/git-config/src/permissions.rs +++ b/git-config/src/permissions.rs @@ -1,4 +1,27 @@ -use crate::Permissions; +/// Configure security relevant options when loading a git configuration. +#[derive(Copy, Clone, Ord, PartialOrd, PartialEq, Eq, Debug, Hash)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +pub struct Permissions { + /// How to use the system configuration. + /// This is defined as `$(prefix)/etc/gitconfig` on unix. + pub system: git_sec::Permission, + /// How to use the global configuration. + /// This is usually `~/.gitconfig`. + pub global: git_sec::Permission, + /// How to use the user configuration. + /// Second user-specific configuration path; if `$XDG_CONFIG_HOME` is not + /// set or empty, `$HOME/.config/git/config` will be used. + pub user: git_sec::Permission, + /// How to use the repository configuration. + pub repository: git_sec::Permission, + /// How to use worktree configuration from `config.worktree`. + // TODO: figure out how this really applies and provide more information here. + pub worktree: git_sec::Permission, + /// How to use the configuration from environment variables. + pub env: git_sec::Permission, + /// What to do when include files are encountered in loaded configuration. + pub includes: git_sec::Permission, +} impl Permissions { /// Allow everything which usually relates to a fully trusted environment diff --git a/git-config/src/test_util.rs b/git-config/src/test_util.rs deleted file mode 100644 index c999e42d41a..00000000000 --- a/git-config/src/test_util.rs +++ /dev/null @@ -1,73 +0,0 @@ -//! This module is only included for tests, and contains common unit test helper -//! functions. - -use std::borrow::Cow; - -use crate::parser::{Event, Key, ParsedComment, ParsedSectionHeader}; - -pub fn section_header_event(name: &str, subsection: impl Into>) -> Event<'_> { - Event::SectionHeader(section_header(name, subsection)) -} - -pub fn section_header( - name: &str, - subsection: impl Into>, -) -> ParsedSectionHeader<'_> { - let name = name.into(); - if let Some((separator, subsection_name)) = subsection.into() { - ParsedSectionHeader { - name, - separator: Some(Cow::Borrowed(separator.into())), - subsection_name: Some(Cow::Borrowed(subsection_name.into())), - } - } else { - ParsedSectionHeader { - name, - separator: None, - subsection_name: None, - } - } -} - -pub(crate) fn name_event(name: &'static str) -> Event<'static> { - Event::Key(Key(Cow::Borrowed(name.into()))) -} - -pub(crate) fn value_event(value: &'static str) -> Event<'static> { - Event::Value(Cow::Borrowed(value.into())) -} - -pub(crate) fn value_not_done_event(value: &'static str) -> Event<'static> { - Event::ValueNotDone(Cow::Borrowed(value.into())) -} - -pub(crate) fn value_done_event(value: &'static str) -> Event<'static> { - Event::ValueDone(Cow::Borrowed(value.into())) -} - -pub(crate) fn newline_event() -> Event<'static> { - newline_custom_event("\n") -} - -pub(crate) fn newline_custom_event(value: &'static str) -> Event<'static> { - Event::Newline(Cow::Borrowed(value.into())) -} - -pub(crate) fn whitespace_event(value: &'static str) -> Event<'static> { - Event::Whitespace(Cow::Borrowed(value.into())) -} - -pub(crate) fn comment_event(tag: char, msg: &'static str) -> Event<'static> { - Event::Comment(comment(tag, msg)) -} - -pub(crate) fn comment(comment_tag: char, comment: &'static str) -> ParsedComment<'static> { - ParsedComment { - comment_tag: comment_tag as u8, - comment: Cow::Borrowed(comment.into()), - } -} - -pub(crate) const fn fully_consumed(t: T) -> (&'static [u8], T) { - (&[], t) -} From 8bd9cd695d608d05859d8bff4033883e71ce7caa Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 6 Jul 2022 16:47:46 +0800 Subject: [PATCH 041/366] refactor (#331) Split parser module up before refactoring --- git-config/src/parser/comment.rs | 56 ++ git-config/src/parser/error.rs | 127 +++ git-config/src/parser/event.rs | 81 ++ git-config/src/parser/mod.rs | 1290 ++++------------------------ git-config/src/parser/nom/mod.rs | 558 ++++++++++++ git-config/src/parser/nom/tests.rs | 543 ++++++++++++ git-config/src/parser/section.rs | 108 +++ git-config/src/parser/tests.rs | 540 ------------ git-config/src/parser/types.rs | 85 ++ 9 files changed, 1716 insertions(+), 1672 deletions(-) create mode 100644 git-config/src/parser/comment.rs create mode 100644 git-config/src/parser/error.rs create mode 100644 git-config/src/parser/event.rs create mode 100644 git-config/src/parser/nom/mod.rs create mode 100644 git-config/src/parser/nom/tests.rs create mode 100644 git-config/src/parser/section.rs create mode 100644 git-config/src/parser/types.rs diff --git a/git-config/src/parser/comment.rs b/git-config/src/parser/comment.rs new file mode 100644 index 00000000000..48a2dd87ae5 --- /dev/null +++ b/git-config/src/parser/comment.rs @@ -0,0 +1,56 @@ +use crate::parser::ParsedComment; +use bstr::{BString, ByteVec}; +use std::borrow::Cow; +use std::fmt::Display; + +impl ParsedComment<'_> { + /// Coerces into an owned instance. This differs from the standard [`clone`] + /// implementation as calling clone will _not_ copy the borrowed variant, + /// while this method will. In other words: + /// + /// | Borrow type | `.clone()` | `to_owned()` | + /// | ----------- | ---------- | ------------ | + /// | Borrowed | Borrowed | Owned | + /// | Owned | Owned | Owned | + /// + /// This can be most effectively seen by the differing lifetimes between the + /// two. This method guarantees a `'static` lifetime, while `clone` does + /// not. + /// + /// [`clone`]: Self::clone + #[must_use] + pub fn to_owned(&self) -> ParsedComment<'static> { + ParsedComment { + comment_tag: self.comment_tag, + comment: Cow::Owned(self.comment.as_ref().into()), + } + } +} + +impl Display for ParsedComment<'_> { + /// Note that this is a best-effort attempt at printing an comment. If + /// there are non UTF-8 values in your config, this will _NOT_ render + /// as read. + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.comment_tag.fmt(f)?; + if let Ok(s) = std::str::from_utf8(&self.comment) { + s.fmt(f) + } else { + write!(f, "{:02x?}", self.comment) + } + } +} + +impl From> for BString { + fn from(c: ParsedComment<'_>) -> Self { + c.into() + } +} + +impl From<&ParsedComment<'_>> for BString { + fn from(c: &ParsedComment<'_>) -> Self { + let mut values = BString::from(vec![c.comment_tag]); + values.push_str(c.comment.as_ref()); + values + } +} diff --git a/git-config/src/parser/error.rs b/git-config/src/parser/error.rs new file mode 100644 index 00000000000..78c601ff634 --- /dev/null +++ b/git-config/src/parser/error.rs @@ -0,0 +1,127 @@ +use crate::parser::{Error, ParserOrIoError}; +use std::fmt::Display; + +/// A list of parsers that parsing can fail on. This is used for pretty-printing +/// errors +#[derive(PartialEq, Debug, Clone, Copy)] +pub(crate) enum ParserNode { + SectionHeader, + ConfigName, +} + +impl Display for ParserNode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::SectionHeader => write!(f, "section header"), + Self::ConfigName => write!(f, "config name"), + } + } +} + +impl Error<'_> { + /// The one-indexed line number where the error occurred. This is determined + /// by the number of newlines that were successfully parsed. + #[must_use] + pub const fn line_number(&self) -> usize { + self.line_number + 1 + } + + /// The remaining data that was left unparsed. + #[must_use] + pub fn remaining_data(&self) -> &[u8] { + &self.parsed_until + } + + /// Coerces into an owned instance. This differs from the standard [`clone`] + /// implementation as calling clone will _not_ copy the borrowed variant, + /// while this method will. In other words: + /// + /// | Borrow type | `.clone()` | `to_owned()` | + /// | ----------- | ---------- | ------------ | + /// | Borrowed | Borrowed | Owned | + /// | Owned | Owned | Owned | + /// + /// This can be most effectively seen by the differing lifetimes between the + /// two. This method guarantees a `'static` lifetime, while `clone` does + /// not. + /// + /// [`clone`]: std::clone::Clone::clone + #[must_use] + pub fn to_owned(&self) -> Error<'static> { + Error { + line_number: self.line_number, + last_attempted_parser: self.last_attempted_parser, + parsed_until: self.parsed_until.clone().into_owned().into(), + } + } +} + +impl Display for Error<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let data_size = self.parsed_until.len(); + let data = std::str::from_utf8(&self.parsed_until); + write!( + f, + "Got an unexpected token on line {} while trying to parse a {}: ", + self.line_number + 1, + self.last_attempted_parser, + )?; + + match (data, data_size) { + (Ok(data), _) if data_size > 10 => { + write!(f, "'{}' ... ({} characters omitted)", &data[..10], data_size - 10) + } + (Ok(data), _) => write!(f, "'{}'", data), + (Err(_), _) if data_size > 10 => write!( + f, + "'{:02x?}' ... ({} characters omitted)", + &self.parsed_until[..10], + data_size - 10 + ), + (Err(_), _) => write!(f, "'{:02x?}'", self.parsed_until), + } + } +} + +impl std::error::Error for Error<'_> {} + +impl ParserOrIoError<'_> { + /// Coerces into an owned instance. This differs from the standard [`clone`] + /// implementation as calling clone will _not_ copy the borrowed variant, + /// while this method will. In other words: + /// + /// | Borrow type | `.clone()` | `to_owned()` | + /// | ----------- | ---------- | ------------ | + /// | Borrowed | Borrowed | Owned | + /// | Owned | Owned | Owned | + /// + /// This can be most effectively seen by the differing lifetimes between the + /// two. This method guarantees a `'static` lifetime, while `clone` does + /// not. + /// + /// [`clone`]: std::clone::Clone::clone + #[must_use] + pub fn into_owned(self) -> ParserOrIoError<'static> { + match self { + ParserOrIoError::Parser(error) => ParserOrIoError::Parser(error.to_owned()), + ParserOrIoError::Io(error) => ParserOrIoError::Io(error), + } + } +} + +impl Display for ParserOrIoError<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ParserOrIoError::Parser(e) => e.fmt(f), + ParserOrIoError::Io(e) => e.fmt(f), + } + } +} + +impl From for ParserOrIoError<'_> { + fn from(e: std::io::Error) -> Self { + Self::Io(e) + } +} + +impl std::error::Error for ParserOrIoError<'_> {} diff --git a/git-config/src/parser/event.rs b/git-config/src/parser/event.rs new file mode 100644 index 00000000000..f6d3fadfe0e --- /dev/null +++ b/git-config/src/parser/event.rs @@ -0,0 +1,81 @@ +use crate::parser::Event; +use bstr::BString; +use std::borrow::Cow; +use std::fmt::Display; + +impl Event<'_> { + /// Generates a byte representation of the value. This should be used when + /// non-UTF-8 sequences are present or a UTF-8 representation can't be + /// guaranteed. + #[must_use] + pub fn to_bstring(&self) -> BString { + self.into() + } + + /// Coerces into an owned instance. This differs from the standard [`clone`] + /// implementation as calling clone will _not_ copy the borrowed variant, + /// while this method will. In other words: + /// + /// | Borrow type | `.clone()` | `to_owned()` | + /// | ----------- | ---------- | ------------ | + /// | Borrowed | Borrowed | Owned | + /// | Owned | Owned | Owned | + /// + /// This can be most effectively seen by the differing lifetimes between the + /// two. This method guarantees a `'static` lifetime, while `clone` does + /// not. + /// + /// [`clone`]: Self::clone + #[must_use] + pub fn to_owned(&self) -> Event<'static> { + match self { + Event::Comment(e) => Event::Comment(e.to_owned()), + Event::SectionHeader(e) => Event::SectionHeader(e.to_owned()), + Event::Key(e) => Event::Key(e.to_owned()), + Event::Value(e) => Event::Value(Cow::Owned(e.clone().into_owned())), + Event::ValueNotDone(e) => Event::ValueNotDone(Cow::Owned(e.clone().into_owned())), + Event::ValueDone(e) => Event::ValueDone(Cow::Owned(e.clone().into_owned())), + Event::Newline(e) => Event::Newline(Cow::Owned(e.clone().into_owned())), + Event::Whitespace(e) => Event::Whitespace(Cow::Owned(e.clone().into_owned())), + Event::KeyValueSeparator => Event::KeyValueSeparator, + } + } +} + +impl Display for Event<'_> { + /// Note that this is a best-effort attempt at printing an `Event`. If + /// there are non UTF-8 values in your config, this will _NOT_ render + /// as read. + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Value(e) | Self::ValueNotDone(e) | Self::ValueDone(e) => match std::str::from_utf8(e) { + Ok(e) => e.fmt(f), + Err(_) => write!(f, "{:02x?}", e), + }, + Self::Comment(e) => e.fmt(f), + Self::SectionHeader(e) => e.fmt(f), + Self::Key(e) => e.fmt(f), + Self::Newline(e) | Self::Whitespace(e) => e.fmt(f), + Self::KeyValueSeparator => write!(f, "="), + } + } +} + +impl From> for BString { + fn from(event: Event<'_>) -> Self { + event.into() + } +} + +impl From<&Event<'_>> for BString { + fn from(event: &Event<'_>) -> Self { + match event { + Event::Value(e) | Event::ValueNotDone(e) | Event::ValueDone(e) => e.as_ref().into(), + Event::Comment(e) => e.into(), + Event::SectionHeader(e) => e.into(), + Event::Key(e) => e.0.as_ref().into(), + Event::Newline(e) | Event::Whitespace(e) => e.as_ref().into(), + Event::KeyValueSeparator => "=".into(), + } + } +} diff --git a/git-config/src/parser/mod.rs b/git-config/src/parser/mod.rs index 817d39f44cf..c452491e72e 100644 --- a/git-config/src/parser/mod.rs +++ b/git-config/src/parser/mod.rs @@ -9,572 +9,8 @@ //! //! [`File`]: crate::File -use bstr::{BStr, BString, ByteSlice, ByteVec}; -use std::{borrow::Cow, convert::TryFrom, fmt::Display, hash::Hash, io::Read, iter::FusedIterator, path::Path}; - -use nom::{ - branch::alt, - bytes::complete::{tag, take_till, take_while}, - character::{ - complete::{char, one_of}, - is_space, - }, - combinator::{map, opt}, - error::{Error as NomError, ErrorKind}, - multi::{many0, many1}, - sequence::delimited, - IResult, -}; - -/// Syntactic events that occurs in the config. Despite all these variants -/// holding a [`Cow`] instead over a simple reference, the parser will only emit -/// borrowed `Cow` variants. -/// -/// The `Cow` smart pointer is used here for ease of inserting events in a -/// middle of an Event iterator. This is used, for example, in the [`File`] -/// struct when adding values. -/// -/// [`Cow`]: std::borrow::Cow -/// [`File`]: crate::File -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] -pub enum Event<'a> { - /// A comment with a comment tag and the comment itself. Note that the - /// comment itself may contain additional whitespace and comment markers - /// at the beginning. - Comment(ParsedComment<'a>), - /// A section header containing the section name and a subsection, if it - /// exists. - SectionHeader(ParsedSectionHeader<'a>), - /// A name to a value in a section. - Key(Key<'a>), - /// A completed value. This may be any string, including the empty string, - /// if an implicit boolean value is used. Note that these values may contain - /// spaces and any special character. This value is also unprocessed, so it - /// it may contain double quotes that should be replaced. - Value(Cow<'a, BStr>), - /// Represents any token used to signify a new line character. On Unix - /// platforms, this is typically just `\n`, but can be any valid newline - /// sequence. Multiple newlines (such as `\n\n`) will be merged as a single - /// newline event. - Newline(Cow<'a, BStr>), - /// Any value that isn't completed. This occurs when the value is continued - /// onto the next line. A Newline event is guaranteed after, followed by - /// either a ValueDone, a Whitespace, or another ValueNotDone. - ValueNotDone(Cow<'a, BStr>), - /// The last line of a value which was continued onto another line. - ValueDone(Cow<'a, BStr>), - /// A continuous section of insignificant whitespace. Values with internal - /// spaces will not be separated by this event. - Whitespace(Cow<'a, BStr>), - /// This event is emitted when the parser counters a valid `=` character - /// separating the key and value. This event is necessary as it eliminates - /// the ambiguity for whitespace events between a key and value event. - KeyValueSeparator, -} - -impl Event<'_> { - /// Generates a byte representation of the value. This should be used when - /// non-UTF-8 sequences are present or a UTF-8 representation can't be - /// guaranteed. - #[must_use] - pub fn to_bstring(&self) -> BString { - self.into() - } - - /// Coerces into an owned instance. This differs from the standard [`clone`] - /// implementation as calling clone will _not_ copy the borrowed variant, - /// while this method will. In other words: - /// - /// | Borrow type | `.clone()` | `to_owned()` | - /// | ----------- | ---------- | ------------ | - /// | Borrowed | Borrowed | Owned | - /// | Owned | Owned | Owned | - /// - /// This can be most effectively seen by the differing lifetimes between the - /// two. This method guarantees a `'static` lifetime, while `clone` does - /// not. - /// - /// [`clone`]: Self::clone - #[must_use] - pub fn to_owned(&self) -> Event<'static> { - match self { - Event::Comment(e) => Event::Comment(e.to_owned()), - Event::SectionHeader(e) => Event::SectionHeader(e.to_owned()), - Event::Key(e) => Event::Key(e.to_owned()), - Event::Value(e) => Event::Value(Cow::Owned(e.clone().into_owned())), - Event::ValueNotDone(e) => Event::ValueNotDone(Cow::Owned(e.clone().into_owned())), - Event::ValueDone(e) => Event::ValueDone(Cow::Owned(e.clone().into_owned())), - Event::Newline(e) => Event::Newline(Cow::Owned(e.clone().into_owned())), - Event::Whitespace(e) => Event::Whitespace(Cow::Owned(e.clone().into_owned())), - Event::KeyValueSeparator => Event::KeyValueSeparator, - } - } -} - -impl Display for Event<'_> { - /// Note that this is a best-effort attempt at printing an `Event`. If - /// there are non UTF-8 values in your config, this will _NOT_ render - /// as read. - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Value(e) | Self::ValueNotDone(e) | Self::ValueDone(e) => match std::str::from_utf8(e) { - Ok(e) => e.fmt(f), - Err(_) => write!(f, "{:02x?}", e), - }, - Self::Comment(e) => e.fmt(f), - Self::SectionHeader(e) => e.fmt(f), - Self::Key(e) => e.fmt(f), - Self::Newline(e) | Self::Whitespace(e) => e.fmt(f), - Self::KeyValueSeparator => write!(f, "="), - } - } -} - -impl From> for BString { - fn from(event: Event<'_>) -> Self { - event.into() - } -} - -impl From<&Event<'_>> for BString { - fn from(event: &Event<'_>) -> Self { - match event { - Event::Value(e) | Event::ValueNotDone(e) | Event::ValueDone(e) => e.as_ref().into(), - Event::Comment(e) => e.into(), - Event::SectionHeader(e) => e.into(), - Event::Key(e) => e.0.as_ref().into(), - Event::Newline(e) | Event::Whitespace(e) => e.as_ref().into(), - Event::KeyValueSeparator => "=".into(), - } - } -} - -/// A parsed section containing the header and the section events. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] -pub struct ParsedSection<'a> { - /// The section name and subsection name, if any. - pub section_header: ParsedSectionHeader<'a>, - /// The syntactic events found in this section. - pub events: Vec>, -} - -impl ParsedSection<'_> { - /// Coerces into an owned instance. This differs from the standard [`clone`] - /// implementation as calling clone will _not_ copy the borrowed variant, - /// while this method will. In other words: - /// - /// | Borrow type | `.clone()` | `to_owned()` | - /// | ----------- | ---------- | ------------ | - /// | Borrowed | Borrowed | Owned | - /// | Owned | Owned | Owned | - /// - /// This can be most effectively seen by the differing lifetimes between the - /// two. This method guarantees a `'static` lifetime, while `clone` does - /// not. - /// - /// [`clone`]: Self::clone - #[must_use] - pub fn to_owned(&self) -> ParsedSection<'static> { - ParsedSection { - section_header: self.section_header.to_owned(), - events: self.events.iter().map(Event::to_owned).collect(), - } - } -} - -impl Display for ParsedSection<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.section_header)?; - for event in &self.events { - event.fmt(f)?; - } - Ok(()) - } -} - -macro_rules! generate_case_insensitive { - ($name:ident, $cow_inner_type:ty, $comment:literal) => { - #[doc = $comment] - #[derive(Clone, Eq, Ord, Debug, Default)] - pub struct $name<'a>(pub Cow<'a, $cow_inner_type>); - - impl $name<'_> { - /// Coerces into an owned instance. This differs from the standard - /// [`clone`] implementation as calling clone will _not_ copy the - /// borrowed variant, while this method will. In other words: - /// - /// | Borrow type | `.clone()` | `to_owned()` | - /// | ----------- | ---------- | ------------ | - /// | Borrowed | Borrowed | Owned | - /// | Owned | Owned | Owned | - /// - /// This can be most effectively seen by the differing lifetimes - /// between the two. This method guarantees a `'static` lifetime, - /// while `clone` does not. - /// - /// [`clone`]: Self::clone - #[must_use] - pub fn to_owned(&self) -> $name<'static> { - $name(Cow::Owned(self.0.clone().into_owned())) - } - } - - impl PartialEq for $name<'_> { - fn eq(&self, other: &Self) -> bool { - self.0.eq_ignore_ascii_case(&other.0) - } - } - - impl Display for $name<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) - } - } - - impl PartialOrd for $name<'_> { - fn partial_cmp(&self, other: &Self) -> Option { - self.0 - .to_ascii_lowercase() - .partial_cmp(&other.0.to_ascii_lowercase()) - } - } - - impl std::hash::Hash for $name<'_> { - fn hash(&self, state: &mut H) { - self.0.to_ascii_lowercase().hash(state) - } - } - - impl<'a> From<&'a str> for $name<'a> { - fn from(s: &'a str) -> Self { - Self(Cow::Borrowed(s.into())) - } - } - - impl<'a> From> for $name<'a> { - fn from(s: Cow<'a, BStr>) -> Self { - Self(s) - } - } - - impl<'a> std::ops::Deref for $name<'a> { - type Target = $cow_inner_type; - - fn deref(&self) -> &Self::Target { - &self.0 - } - } - }; -} - -generate_case_insensitive!( - SectionHeaderName, - BStr, - "Wrapper struct for section header names, since section headers are case-insensitive." -); - -generate_case_insensitive!( - Key, - BStr, - "Wrapper struct for key names, since keys are case-insensitive." -); - -/// A parsed section header, containing a name and optionally a subsection name. -/// -/// Note that section headers must be parsed as valid ASCII, and thus all valid -/// instances must also necessarily be valid UTF-8, which is why we use a -/// [`str`] instead of [`[u8]`]. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] -pub struct ParsedSectionHeader<'a> { - /// The name of the header. - pub name: SectionHeaderName<'a>, - /// The separator used to determine if the section contains a subsection. - /// This is either a period `.` or a string of whitespace. Note that - /// reconstruction of subsection format is dependent on this value. If this - /// is all whitespace, then the subsection name needs to be surrounded by - /// quotes to have perfect reconstruction. - pub separator: Option>, - /// The subsection name without quotes if any exist. - pub subsection_name: Option>, -} - -impl ParsedSectionHeader<'_> { - /// Generates a byte representation of the value. This should be used when - /// non-UTF-8 sequences are present or a UTF-8 representation can't be - /// guaranteed. - #[must_use] - pub fn to_bstring(&self) -> BString { - self.into() - } - - /// Coerces into an owned instance. This differs from the standard [`clone`] - /// implementation as calling clone will _not_ copy the borrowed variant, - /// while this method will. In other words: - /// - /// | Borrow type | `.clone()` | `to_owned()` | - /// | ----------- | ---------- | ------------ | - /// | Borrowed | Borrowed | Owned | - /// | Owned | Owned | Owned | - /// - /// This can be most effectively seen by the differing lifetimes between the - /// two. This method guarantees a `'static` lifetime, while `clone` does - /// not. - /// - /// [`clone`]: Self::clone - #[must_use] - pub fn to_owned(&self) -> ParsedSectionHeader<'static> { - ParsedSectionHeader { - name: self.name.to_owned(), - separator: self.separator.clone().map(|v| Cow::Owned(v.into_owned())), - subsection_name: self.subsection_name.clone().map(|v| Cow::Owned(v.into_owned())), - } - } -} - -impl Display for ParsedSectionHeader<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "[{}", self.name)?; - - if let Some(v) = &self.separator { - // Separator must be utf-8 - v.fmt(f)?; - let subsection_name = self.subsection_name.as_ref().unwrap(); - if v.as_ref() == "." { - subsection_name.fmt(f)?; - } else { - write!(f, "\"{}\"", subsection_name)?; - } - } - - write!(f, "]") - } -} - -impl From> for BString { - fn from(header: ParsedSectionHeader<'_>) -> Self { - header.into() - } -} - -impl From<&ParsedSectionHeader<'_>> for BString { - fn from(header: &ParsedSectionHeader<'_>) -> Self { - header.to_string().into() - } -} - -impl<'a> From> for Event<'a> { - fn from(header: ParsedSectionHeader<'_>) -> Event<'_> { - Event::SectionHeader(header) - } -} - -/// A parsed comment event containing the comment marker and comment. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] -pub struct ParsedComment<'a> { - /// The comment marker used. This is either a semicolon or octothorpe. - pub comment_tag: u8, - /// The parsed comment. - pub comment: Cow<'a, BStr>, -} - -impl ParsedComment<'_> { - /// Coerces into an owned instance. This differs from the standard [`clone`] - /// implementation as calling clone will _not_ copy the borrowed variant, - /// while this method will. In other words: - /// - /// | Borrow type | `.clone()` | `to_owned()` | - /// | ----------- | ---------- | ------------ | - /// | Borrowed | Borrowed | Owned | - /// | Owned | Owned | Owned | - /// - /// This can be most effectively seen by the differing lifetimes between the - /// two. This method guarantees a `'static` lifetime, while `clone` does - /// not. - /// - /// [`clone`]: Self::clone - #[must_use] - pub fn to_owned(&self) -> ParsedComment<'static> { - ParsedComment { - comment_tag: self.comment_tag, - comment: Cow::Owned(self.comment.as_ref().into()), - } - } -} - -impl Display for ParsedComment<'_> { - /// Note that this is a best-effort attempt at printing an comment. If - /// there are non UTF-8 values in your config, this will _NOT_ render - /// as read. - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.comment_tag.fmt(f)?; - if let Ok(s) = std::str::from_utf8(&self.comment) { - s.fmt(f) - } else { - write!(f, "{:02x?}", self.comment) - } - } -} - -impl From> for BString { - fn from(c: ParsedComment<'_>) -> Self { - c.into() - } -} - -impl From<&ParsedComment<'_>> for BString { - fn from(c: &ParsedComment<'_>) -> Self { - let mut values = BString::from(vec![c.comment_tag]); - values.push_str(c.comment.as_ref()); - values - } -} - -/// A parser error reports the one-indexed line number where the parsing error -/// occurred, as well as the last parser node and the remaining data to be -/// parsed. -#[derive(PartialEq, Debug)] -pub struct Error<'a> { - line_number: usize, - last_attempted_parser: ParserNode, - parsed_until: Cow<'a, BStr>, -} - -impl Error<'_> { - /// The one-indexed line number where the error occurred. This is determined - /// by the number of newlines that were successfully parsed. - #[must_use] - pub const fn line_number(&self) -> usize { - self.line_number + 1 - } - - /// The remaining data that was left unparsed. - #[must_use] - pub fn remaining_data(&self) -> &[u8] { - &self.parsed_until - } - - /// Coerces into an owned instance. This differs from the standard [`clone`] - /// implementation as calling clone will _not_ copy the borrowed variant, - /// while this method will. In other words: - /// - /// | Borrow type | `.clone()` | `to_owned()` | - /// | ----------- | ---------- | ------------ | - /// | Borrowed | Borrowed | Owned | - /// | Owned | Owned | Owned | - /// - /// This can be most effectively seen by the differing lifetimes between the - /// two. This method guarantees a `'static` lifetime, while `clone` does - /// not. - /// - /// [`clone`]: std::clone::Clone::clone - #[must_use] - pub fn to_owned(&self) -> Error<'static> { - Error { - line_number: self.line_number, - last_attempted_parser: self.last_attempted_parser, - parsed_until: self.parsed_until.clone().into_owned().into(), - } - } -} - -impl Display for Error<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let data_size = self.parsed_until.len(); - let data = std::str::from_utf8(&self.parsed_until); - write!( - f, - "Got an unexpected token on line {} while trying to parse a {}: ", - self.line_number + 1, - self.last_attempted_parser, - )?; - - match (data, data_size) { - (Ok(data), _) if data_size > 10 => { - write!(f, "'{}' ... ({} characters omitted)", &data[..10], data_size - 10) - } - (Ok(data), _) => write!(f, "'{}'", data), - (Err(_), _) if data_size > 10 => write!( - f, - "'{:02x?}' ... ({} characters omitted)", - &self.parsed_until[..10], - data_size - 10 - ), - (Err(_), _) => write!(f, "'{:02x?}'", self.parsed_until), - } - } -} - -impl std::error::Error for Error<'_> {} - -/// An error type representing a Parser [`Error`] or an [`IO error`]. This is -/// returned from functions that will perform IO on top of standard parsing, -/// such as reading from a file. -/// -/// [`IO error`]: std::io::Error -#[derive(Debug)] -#[allow(missing_docs, clippy::module_name_repetitions)] -pub enum ParserOrIoError<'a> { - Parser(Error<'a>), - Io(std::io::Error), -} - -impl ParserOrIoError<'_> { - /// Coerces into an owned instance. This differs from the standard [`clone`] - /// implementation as calling clone will _not_ copy the borrowed variant, - /// while this method will. In other words: - /// - /// | Borrow type | `.clone()` | `to_owned()` | - /// | ----------- | ---------- | ------------ | - /// | Borrowed | Borrowed | Owned | - /// | Owned | Owned | Owned | - /// - /// This can be most effectively seen by the differing lifetimes between the - /// two. This method guarantees a `'static` lifetime, while `clone` does - /// not. - /// - /// [`clone`]: std::clone::Clone::clone - #[must_use] - pub fn into_owned(self) -> ParserOrIoError<'static> { - match self { - ParserOrIoError::Parser(error) => ParserOrIoError::Parser(error.to_owned()), - ParserOrIoError::Io(error) => ParserOrIoError::Io(error), - } - } -} - -impl Display for ParserOrIoError<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ParserOrIoError::Parser(e) => e.fmt(f), - ParserOrIoError::Io(e) => e.fmt(f), - } - } -} - -impl From for ParserOrIoError<'_> { - fn from(e: std::io::Error) -> Self { - Self::Io(e) - } -} - -impl std::error::Error for ParserOrIoError<'_> {} - -/// A list of parsers that parsing can fail on. This is used for pretty-printing -/// errors -#[derive(PartialEq, Debug, Clone, Copy)] -enum ParserNode { - SectionHeader, - ConfigName, -} - -impl Display for ParserNode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::SectionHeader => write!(f, "section header"), - Self::ConfigName => write!(f, "config name"), - } - } -} +use bstr::BStr; +use std::{borrow::Cow, hash::Hash}; /// A zero-copy `git-config` file parser. /// @@ -790,608 +226,198 @@ pub struct Parser<'a> { sections: Vec>, } -impl<'a> Parser<'a> { - /// Returns the leading events (any comments, whitespace, or newlines before - /// a section) from the parser. Consider [`Parser::take_frontmatter`] if - /// you need an owned copy only once. If that function was called, then this - /// will always return an empty slice. - #[must_use] - pub fn frontmatter(&self) -> &[Event<'a>] { - &self.frontmatter - } - - /// Takes the leading events (any comments, whitespace, or newlines before - /// a section) from the parser. Subsequent calls will return an empty vec. - /// Consider [`Parser::frontmatter`] if you only need a reference to the - /// frontmatter - pub fn take_frontmatter(&mut self) -> Vec> { - std::mem::take(&mut self.frontmatter) - } - - /// Returns the parsed sections from the parser. Consider - /// [`Parser::take_sections`] if you need an owned copy only once. If that - /// function was called, then this will always return an empty slice. - #[must_use] - pub fn sections(&self) -> &[ParsedSection<'a>] { - &self.sections - } - - /// Takes the parsed sections from the parser. Subsequent calls will return - /// an empty vec. Consider [`Parser::sections`] if you only need a reference - /// to the comments. - pub fn take_sections(&mut self) -> Vec> { - let mut to_return = vec![]; - std::mem::swap(&mut self.sections, &mut to_return); - to_return - } - - /// Consumes the parser to produce a Vec of Events. - #[must_use] - pub fn into_vec(self) -> Vec> { - self.into_iter().collect() - } - - /// Consumes the parser to produce an iterator of Events. - #[must_use = "iterators are lazy and do nothing unless consumed"] - #[allow(clippy::should_implement_trait)] - pub fn into_iter(self) -> impl Iterator> + FusedIterator { - self.frontmatter.into_iter().chain( - self.sections.into_iter().flat_map(|section| { - std::iter::once(Event::SectionHeader(section.section_header)).chain(section.events) - }), - ) - } -} - -impl<'a> TryFrom<&'a str> for Parser<'a> { - type Error = Error<'a>; - - fn try_from(value: &'a str) -> Result { - parse_from_str(value) - } -} - -impl<'a> TryFrom<&'a [u8]> for Parser<'a> { - type Error = Error<'a>; - - fn try_from(value: &'a [u8]) -> Result { - parse_from_bytes(value) - } -} - -/// Parses a git config located at the provided path. On success, returns a -/// [`Parser`] that provides methods to accessing leading comments and sections -/// of a `git-config` file and can be converted into an iterator of [`Event`] -/// for higher level processing. -/// -/// Note that since we accept a path rather than a reference to the actual -/// bytes, this function is _not_ zero-copy, as the Parser must own (and thus -/// copy) the bytes that it reads from. Consider one of the other variants if -/// performance is a concern. -/// -/// # Errors -/// -/// Returns an error if there was an IO error or the read file is not a valid -/// `git-config` This generally is due to either invalid names or if there's -/// extraneous data succeeding valid `git-config` data. -pub fn parse_from_path>(path: P) -> Result, ParserOrIoError<'static>> { - let mut bytes = vec![]; - let mut file = std::fs::File::open(path)?; - file.read_to_end(&mut bytes)?; - parse_from_bytes_owned(&bytes).map_err(ParserOrIoError::Parser) -} - -/// Attempt to zero-copy parse the provided `&str`. On success, returns a -/// [`Parser`] that provides methods to accessing leading comments and sections -/// of a `git-config` file and can be converted into an iterator of [`Event`] -/// for higher level processing. -/// -/// # Errors -/// -/// Returns an error if the string provided is not a valid `git-config`. -/// This generally is due to either invalid names or if there's extraneous -/// data succeeding valid `git-config` data. -pub fn parse_from_str(input: &str) -> Result, Error<'_>> { - parse_from_bytes(input.as_bytes()) -} - -/// Attempt to zero-copy parse the provided bytes. On success, returns a -/// [`Parser`] that provides methods to accessing leading comments and sections -/// of a `git-config` file and can be converted into an iterator of [`Event`] -/// for higher level processing. -/// -/// # Errors -/// -/// Returns an error if the string provided is not a valid `git-config`. -/// This generally is due to either invalid names or if there's extraneous -/// data succeeding valid `git-config` data. -#[allow(clippy::shadow_unrelated)] -pub fn parse_from_bytes(input: &[u8]) -> Result, Error<'_>> { - let bom = unicode_bom::Bom::from(input); - let mut newlines = 0; - let (i, frontmatter) = many0(alt(( - map(comment, Event::Comment), - map(take_spaces, |whitespace| Event::Whitespace(Cow::Borrowed(whitespace))), - map(take_newlines, |(newline, counter)| { - newlines += counter; - Event::Newline(Cow::Borrowed(newline)) - }), - )))(&input[bom.len()..]) - // I don't think this can panic. many0 errors if the child parser returns - // a success where the input was not consumed, but alt will only return Ok - // if one of its children succeed. However, all of it's children are - // guaranteed to consume something if they succeed, so the Ok(i) == i case - // can never occur. - .expect("many0(alt(...)) panicked. Likely a bug in one of the children parser."); - - if i.is_empty() { - return Ok(Parser { - frontmatter, - sections: vec![], - }); - } - - let mut node = ParserNode::SectionHeader; - - let maybe_sections = many1(|i| section(i, &mut node))(i); - let (i, sections) = maybe_sections.map_err(|_| Error { - line_number: newlines, - last_attempted_parser: node, - parsed_until: i.as_bstr().into(), - })?; - - let sections = sections - .into_iter() - .map(|(section, additional_newlines)| { - newlines += additional_newlines; - section - }) - .collect(); - - // This needs to happen after we collect sections, otherwise the line number - // will be off. - if !i.is_empty() { - return Err(Error { - line_number: newlines, - last_attempted_parser: node, - parsed_until: i.as_bstr().into(), - }); - } +mod state { + use crate::parser::{parse_from_bytes, parse_from_str, Error, Event, ParsedSection, Parser}; + use std::convert::TryFrom; - Ok(Parser { frontmatter, sections }) -} - -/// Parses the provided bytes, returning an [`Parser`] that contains allocated -/// and owned events. This is similar to [`parse_from_bytes`], but performance -/// is degraded as it requires allocation for every event. However, this permits -/// the reference bytes to be dropped, allowing the parser to be passed around -/// without lifetime worries. -/// -/// # Errors -/// -/// Returns an error if the string provided is not a valid `git-config`. -/// This generally is due to either invalid names or if there's extraneous -/// data succeeding valid `git-config` data. -#[allow(clippy::shadow_unrelated)] -pub fn parse_from_bytes_owned(input: &[u8]) -> Result, Error<'static>> { - // FIXME: This is duplication is necessary until comment, take_spaces, and take_newlines - // accept cows instead, since we don't want to unnecessarily copy the frontmatter - // events in a hypothetical parse_from_cow function. - let mut newlines = 0; - let bom = unicode_bom::Bom::from(input); - let (i, frontmatter) = many0(alt(( - map(comment, Event::Comment), - map(take_spaces, |whitespace| { - Event::Whitespace(Cow::Borrowed(whitespace.as_bstr())) - }), - map(take_newlines, |(newline, counter)| { - newlines += counter; - Event::Newline(Cow::Borrowed(newline.as_bstr())) - }), - )))(&input[bom.len()..]) - // I don't think this can panic. many0 errors if the child parser returns - // a success where the input was not consumed, but alt will only return Ok - // if one of its children succeed. However, all of it's children are - // guaranteed to consume something if they succeed, so the Ok(i) == i case - // can never occur. - .expect("many0(alt(...)) panicked. Likely a bug in one of the children parser."); - let frontmatter = frontmatter.iter().map(Event::to_owned).collect(); - if i.is_empty() { - return Ok(Parser { - frontmatter, - sections: vec![], - }); - } - - let mut node = ParserNode::SectionHeader; - - let maybe_sections = many1(|i| section(i, &mut node))(i); - let (i, sections) = maybe_sections.map_err(|_| Error { - line_number: newlines, - last_attempted_parser: node, - parsed_until: Cow::Owned(i.into()), - })?; - - let sections = sections - .into_iter() - .map(|(section, additional_newlines)| { - newlines += additional_newlines; - section.to_owned() - }) - .collect(); - - // This needs to happen after we collect sections, otherwise the line number - // will be off. - if !i.is_empty() { - return Err(Error { - line_number: newlines, - last_attempted_parser: node, - parsed_until: Cow::Owned(i.into()), - }); - } - - Ok(Parser { frontmatter, sections }) -} - -fn comment(i: &[u8]) -> IResult<&[u8], ParsedComment<'_>> { - let (i, comment_tag) = one_of(";#")(i)?; - let (i, comment) = take_till(|c| c == b'\n')(i)?; - Ok(( - i, - ParsedComment { - comment_tag: comment_tag as u8, - comment: Cow::Borrowed(comment.as_bstr()), - }, - )) -} - -fn section<'a, 'b>(i: &'a [u8], node: &'b mut ParserNode) -> IResult<&'a [u8], (ParsedSection<'a>, usize)> { - let (mut i, section_header) = section_header(i)?; - - let mut newlines = 0; - let mut items = vec![]; - - // This would usually be a many0(alt(...)), the manual loop allows us to - // optimize vec insertions - loop { - let old_i = i; - - if let Ok((new_i, v)) = take_spaces(i) { - if old_i != new_i { - i = new_i; - items.push(Event::Whitespace(Cow::Borrowed(v.as_bstr()))); - } + impl<'a> Parser<'a> { + /// Returns the leading events (any comments, whitespace, or newlines before + /// a section) from the parser. Consider [`Parser::take_frontmatter`] if + /// you need an owned copy only once. If that function was called, then this + /// will always return an empty slice. + #[must_use] + pub fn frontmatter(&self) -> &[Event<'a>] { + &self.frontmatter } - if let Ok((new_i, (v, new_newlines))) = take_newlines(i) { - if old_i != new_i { - i = new_i; - newlines += new_newlines; - items.push(Event::Newline(Cow::Borrowed(v.as_bstr()))); - } + /// Takes the leading events (any comments, whitespace, or newlines before + /// a section) from the parser. Subsequent calls will return an empty vec. + /// Consider [`Parser::frontmatter`] if you only need a reference to the + /// frontmatter + pub fn take_frontmatter(&mut self) -> Vec> { + std::mem::take(&mut self.frontmatter) } - if let Ok((new_i, _)) = section_body(i, node, &mut items) { - if old_i != new_i { - i = new_i; - } + /// Returns the parsed sections from the parser. Consider + /// [`Parser::take_sections`] if you need an owned copy only once. If that + /// function was called, then this will always return an empty slice. + #[must_use] + pub fn sections(&self) -> &[ParsedSection<'a>] { + &self.sections } - if let Ok((new_i, comment)) = comment(i) { - if old_i != new_i { - i = new_i; - items.push(Event::Comment(comment)); - } + /// Takes the parsed sections from the parser. Subsequent calls will return + /// an empty vec. Consider [`Parser::sections`] if you only need a reference + /// to the comments. + pub fn take_sections(&mut self) -> Vec> { + let mut to_return = vec![]; + std::mem::swap(&mut self.sections, &mut to_return); + to_return } - if old_i == i { - break; + /// Consumes the parser to produce a Vec of Events. + #[must_use] + pub fn into_vec(self) -> Vec> { + self.into_iter().collect() } - } - - Ok(( - i, - ( - ParsedSection { - section_header, - events: items, - }, - newlines, - ), - )) -} - -fn section_header(i: &[u8]) -> IResult<&[u8], ParsedSectionHeader<'_>> { - let (i, _) = char('[')(i)?; - // No spaces must be between section name and section start - let (i, name) = take_while(|c: u8| c.is_ascii_alphanumeric() || c == b'-' || c == b'.')(i)?; - - let name = name.as_bstr(); - if let Ok((i, _)) = char::<_, NomError<&[u8]>>(']')(i) { - // Either section does not have a subsection or using deprecated - // subsection syntax at this point. - let header = match memchr::memrchr(b'.', name.as_bytes()) { - Some(index) => ParsedSectionHeader { - name: SectionHeaderName(Cow::Borrowed(name[..index].as_bstr())), - separator: name.get(index..=index).map(|s| Cow::Borrowed(s.as_bstr())), - subsection_name: name.get(index + 1..).map(|s| Cow::Borrowed(s.as_bstr())), - }, - None => ParsedSectionHeader { - name: SectionHeaderName(Cow::Borrowed(name.as_bstr())), - separator: None, - subsection_name: None, - }, - }; - - return Ok((i, header)); - } - - // Section header must be using modern subsection syntax at this point. - let (i, whitespace) = take_spaces(i)?; - let (i, subsection_name) = delimited(char('"'), opt(sub_section), tag("\"]"))(i)?; - - Ok(( - i, - ParsedSectionHeader { - name: SectionHeaderName(Cow::Borrowed(name)), - separator: Some(Cow::Borrowed(whitespace)), - subsection_name: subsection_name.map(Cow::Owned), - }, - )) -} - -fn sub_section(i: &[u8]) -> IResult<&[u8], BString> { - let mut cursor = 0; - let mut bytes = i.iter().copied(); - let mut found_terminator = false; - let mut buf = BString::default(); - while let Some(mut b) = bytes.next() { - cursor += 1; - if b == b'\n' { - return Err(nom::Err::Error(NomError { - input: &i[cursor..], - code: ErrorKind::NonEmpty, - })); - } - if b == b'"' { - found_terminator = true; - break; + /// Consumes the parser to produce an iterator of Events. + #[must_use = "iterators are lazy and do nothing unless consumed"] + #[allow(clippy::should_implement_trait)] + pub fn into_iter(self) -> impl Iterator> + std::iter::FusedIterator { + self.frontmatter + .into_iter() + .chain(self.sections.into_iter().flat_map(|section| { + std::iter::once(Event::SectionHeader(section.section_header)).chain(section.events) + })) } - if b == b'\\' { - b = bytes.next().ok_or_else(|| { - nom::Err::Error(NomError { - input: &i[cursor..], - code: ErrorKind::NonEmpty, - }) - })?; - cursor += 1; - if b == b'\n' { - return Err(nom::Err::Error(NomError { - input: &i[cursor..], - code: ErrorKind::NonEmpty, - })); - } - } - buf.push_byte(b); - } - - if !found_terminator { - return Err(nom::Err::Error(NomError { - input: &i[cursor..], - code: ErrorKind::NonEmpty, - })); } - Ok((&i[cursor - 1..], buf)) -} - -fn section_body<'a, 'b, 'c>( - i: &'a [u8], - node: &'b mut ParserNode, - items: &'c mut Vec>, -) -> IResult<&'a [u8], ()> { - // maybe need to check for [ here - *node = ParserNode::ConfigName; - let (i, name) = config_name(i)?; - - items.push(Event::Key(Key(Cow::Borrowed(name)))); - - let (i, whitespace) = opt(take_spaces)(i)?; - - if let Some(whitespace) = whitespace { - items.push(Event::Whitespace(Cow::Borrowed(whitespace))); - } + impl<'a> TryFrom<&'a str> for Parser<'a> { + type Error = Error<'a>; - let (i, _) = config_value(i, items)?; - Ok((i, ())) -} - -/// Parses the config name of a config pair. Assumes the input has already been -/// trimmed of any leading whitespace. -fn config_name(i: &[u8]) -> IResult<&[u8], &BStr> { - if i.is_empty() { - return Err(nom::Err::Error(NomError { - input: i, - code: ErrorKind::NonEmpty, - })); - } - - if !i[0].is_ascii_alphabetic() { - return Err(nom::Err::Error(NomError { - input: i, - code: ErrorKind::Alpha, - })); + fn try_from(value: &'a str) -> Result { + parse_from_str(value) + } } - let (i, v) = take_while(|c: u8| c.is_ascii_alphanumeric() || c == b'-')(i)?; - Ok((i, v.as_bstr())) -} + impl<'a> TryFrom<&'a [u8]> for Parser<'a> { + type Error = Error<'a>; -fn config_value<'a, 'b>(i: &'a [u8], events: &'b mut Vec>) -> IResult<&'a [u8], ()> { - if let (i, Some(_)) = opt(char('='))(i)? { - events.push(Event::KeyValueSeparator); - let (i, whitespace) = opt(take_spaces)(i)?; - if let Some(whitespace) = whitespace { - events.push(Event::Whitespace(Cow::Borrowed(whitespace))); + fn try_from(value: &'a [u8]) -> Result { + parse_from_bytes(value) } - let (i, _) = value_impl(i, events)?; - Ok((i, ())) - } else { - events.push(Event::Value(Cow::Borrowed("".into()))); - Ok((i, ())) } } -/// Handles parsing of known-to-be values. This function handles both single -/// line values as well as values that are continuations. +/// Syntactic events that occurs in the config. Despite all these variants +/// holding a [`Cow`] instead over a simple reference, the parser will only emit +/// borrowed `Cow` variants. /// -/// # Errors +/// The `Cow` smart pointer is used here for ease of inserting events in a +/// middle of an Event iterator. This is used, for example, in the [`File`] +/// struct when adding values. /// -/// Returns an error if an invalid escape was used, if there was an unfinished -/// quote, or there was an escape but there is nothing left to escape. -fn value_impl<'a, 'b>(i: &'a [u8], events: &'b mut Vec>) -> IResult<&'a [u8], ()> { - let mut parsed_index: usize = 0; - let mut offset: usize = 0; +/// [`Cow`]: std::borrow::Cow +/// [`File`]: crate::File +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub enum Event<'a> { + /// A comment with a comment tag and the comment itself. Note that the + /// comment itself may contain additional whitespace and comment markers + /// at the beginning. + Comment(ParsedComment<'a>), + /// A section header containing the section name and a subsection, if it + /// exists. + SectionHeader(ParsedSectionHeader<'a>), + /// A name to a value in a section. + Key(Key<'a>), + /// A completed value. This may be any string, including the empty string, + /// if an implicit boolean value is used. Note that these values may contain + /// spaces and any special character. This value is also unprocessed, so it + /// it may contain double quotes that should be replaced. + Value(Cow<'a, BStr>), + /// Represents any token used to signify a new line character. On Unix + /// platforms, this is typically just `\n`, but can be any valid newline + /// sequence. Multiple newlines (such as `\n\n`) will be merged as a single + /// newline event. + Newline(Cow<'a, BStr>), + /// Any value that isn't completed. This occurs when the value is continued + /// onto the next line. A Newline event is guaranteed after, followed by + /// either a ValueDone, a Whitespace, or another ValueNotDone. + ValueNotDone(Cow<'a, BStr>), + /// The last line of a value which was continued onto another line. + ValueDone(Cow<'a, BStr>), + /// A continuous section of insignificant whitespace. Values with internal + /// spaces will not be separated by this event. + Whitespace(Cow<'a, BStr>), + /// This event is emitted when the parser counters a valid `=` character + /// separating the key and value. This event is necessary as it eliminates + /// the ambiguity for whitespace events between a key and value event. + KeyValueSeparator, +} - let mut was_prev_char_escape_char = false; - // This is required to ignore comment markers if they're in a quote. - let mut is_in_quotes = false; - // Used to determine if we return a Value or Value{Not,}Done - let mut partial_value_found = false; - let mut index: usize = 0; +mod event; - for c in i.iter() { - if was_prev_char_escape_char { - was_prev_char_escape_char = false; - match c { - // We're escaping a newline, which means we've found a - // continuation. - b'\n' => { - partial_value_found = true; - events.push(Event::ValueNotDone(Cow::Borrowed(i[offset..index - 1].as_bstr()))); - events.push(Event::Newline(Cow::Borrowed(i[index..=index].as_bstr()))); - offset = index + 1; - parsed_index = 0; - } - b't' | b'\\' | b'n' | b'"' => (), - _ => { - return Err(nom::Err::Error(NomError { - input: i, - code: ErrorKind::Escaped, - })); - } - } - } else { - match c { - b'\n' => { - parsed_index = index; - break; - } - b';' | b'#' if !is_in_quotes => { - parsed_index = index; - break; - } - b'\\' => was_prev_char_escape_char = true, - b'"' => is_in_quotes = !is_in_quotes, - _ => {} - } - } - index += 1; - } +/// A parsed section containing the header and the section events. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] +pub struct ParsedSection<'a> { + /// The section name and subsection name, if any. + pub section_header: ParsedSectionHeader<'a>, + /// The syntactic events found in this section. + pub events: Vec>, +} - if parsed_index == 0 { - if index != 0 { - parsed_index = i.len(); - } else { - // Didn't parse anything at all, newline straight away. - events.push(Event::Value(Cow::Owned(BString::default()))); - events.push(Event::Newline(Cow::Borrowed("\n".into()))); - return Ok((&i[1..], ())); - } - } +/// A parsed section header, containing a name and optionally a subsection name. +/// +/// Note that section headers must be parsed as valid ASCII, and thus all valid +/// instances must also necessarily be valid UTF-8, which is why we use a +/// [`str`] instead of [`[u8]`]. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] +pub struct ParsedSectionHeader<'a> { + /// The name of the header. + pub name: SectionHeaderName<'a>, + /// The separator used to determine if the section contains a subsection. + /// This is either a period `.` or a string of whitespace. Note that + /// reconstruction of subsection format is dependent on this value. If this + /// is all whitespace, then the subsection name needs to be surrounded by + /// quotes to have perfect reconstruction. + pub separator: Option>, + /// The subsection name without quotes if any exist. + pub subsection_name: Option>, +} - // Handle incomplete escape - if was_prev_char_escape_char { - return Err(nom::Err::Error(NomError { - input: i, - code: ErrorKind::Escaped, - })); - } +mod section; - // Handle incomplete quotes - if is_in_quotes { - return Err(nom::Err::Error(NomError { - input: i, - code: ErrorKind::Tag, - })); - } +mod types; +pub use types::{Key, SectionHeaderName}; - let (i, remainder_value) = { - let mut new_index = parsed_index; - for index in (offset..parsed_index).rev() { - if !i[index].is_ascii_whitespace() { - new_index = index + 1; - break; - } - } - (&i[new_index..], &i[offset..new_index]) - }; +/// A parsed comment event containing the comment marker and comment. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] +pub struct ParsedComment<'a> { + /// The comment marker used. This is either a semicolon or octothorpe. + pub comment_tag: u8, + /// The parsed comment. + pub comment: Cow<'a, BStr>, +} - if partial_value_found { - events.push(Event::ValueDone(Cow::Borrowed(remainder_value.as_bstr()))); - } else { - events.push(Event::Value(Cow::Borrowed(remainder_value.as_bstr()))); - } +mod comment; - Ok((i, ())) +/// A parser error reports the one-indexed line number where the parsing error +/// occurred, as well as the last parser node and the remaining data to be +/// parsed. +#[derive(PartialEq, Debug)] +pub struct Error<'a> { + line_number: usize, + last_attempted_parser: error::ParserNode, + parsed_until: Cow<'a, BStr>, } -fn take_spaces(i: &[u8]) -> IResult<&[u8], &BStr> { - let (i, v) = take_while(|c: u8| c.is_ascii() && is_space(c))(i)?; - if v.is_empty() { - Err(nom::Err::Error(NomError { - input: i, - code: ErrorKind::Eof, - })) - } else { - Ok((i, v.as_bstr())) - } +/// An error type representing a Parser [`Error`] or an [`IO error`]. This is +/// returned from functions that will perform IO on top of standard parsing, +/// such as reading from a file. +/// +/// [`IO error`]: std::io::Error +#[derive(Debug)] +#[allow(missing_docs, clippy::module_name_repetitions)] +pub enum ParserOrIoError<'a> { + Parser(Error<'a>), + Io(std::io::Error), } -fn take_newlines(i: &[u8]) -> IResult<&[u8], (&BStr, usize)> { - let mut counter = 0; - let mut consumed_bytes = 0; - let mut next_must_be_newline = false; - for b in i.iter().copied() { - if !b.is_ascii() { - break; - }; - if b == b'\r' { - if next_must_be_newline { - break; - } - next_must_be_newline = true; - continue; - }; - if b == b'\n' { - counter += 1; - consumed_bytes += if next_must_be_newline { 2 } else { 1 }; - next_must_be_newline = false; - } else { - break; - } - } - let (v, i) = i.split_at(consumed_bytes); - if v.is_empty() { - Err(nom::Err::Error(NomError { - input: i, - code: ErrorKind::Eof, - })) - } else { - Ok((i, (v.as_bstr(), counter))) - } -} +mod error; + +mod nom; +pub use self::nom::{parse_from_bytes, parse_from_bytes_owned, parse_from_path, parse_from_str}; #[cfg(test)] pub(crate) mod tests; diff --git a/git-config/src/parser/nom/mod.rs b/git-config/src/parser/nom/mod.rs new file mode 100644 index 00000000000..6eb637cfd51 --- /dev/null +++ b/git-config/src/parser/nom/mod.rs @@ -0,0 +1,558 @@ +use crate::parser::{ + Error, Event, Key, ParsedComment, ParsedSection, ParsedSectionHeader, Parser, ParserOrIoError, SectionHeaderName, +}; +use bstr::{BStr, BString, ByteSlice, ByteVec}; +use std::borrow::Cow; +use std::io::Read; + +use crate::parser::error::ParserNode; +use nom::{ + branch::alt, + bytes::complete::{tag, take_till, take_while}, + character::{ + complete::{char, one_of}, + is_space, + }, + combinator::{map, opt}, + error::{Error as NomError, ErrorKind}, + multi::{many0, many1}, + sequence::delimited, + IResult, +}; + +/// Parses a git config located at the provided path. On success, returns a +/// [`Parser`] that provides methods to accessing leading comments and sections +/// of a `git-config` file and can be converted into an iterator of [`Event`] +/// for higher level processing. +/// +/// Note that since we accept a path rather than a reference to the actual +/// bytes, this function is _not_ zero-copy, as the Parser must own (and thus +/// copy) the bytes that it reads from. Consider one of the other variants if +/// performance is a concern. +/// +/// # Errors +/// +/// Returns an error if there was an IO error or the read file is not a valid +/// `git-config` This generally is due to either invalid names or if there's +/// extraneous data succeeding valid `git-config` data. +pub fn parse_from_path>(path: P) -> Result, ParserOrIoError<'static>> { + let mut bytes = vec![]; + let mut file = std::fs::File::open(path)?; + file.read_to_end(&mut bytes)?; + parse_from_bytes_owned(&bytes).map_err(ParserOrIoError::Parser) +} + +/// Attempt to zero-copy parse the provided `&str`. On success, returns a +/// [`Parser`] that provides methods to accessing leading comments and sections +/// of a `git-config` file and can be converted into an iterator of [`Event`] +/// for higher level processing. +/// +/// # Errors +/// +/// Returns an error if the string provided is not a valid `git-config`. +/// This generally is due to either invalid names or if there's extraneous +/// data succeeding valid `git-config` data. +pub fn parse_from_str(input: &str) -> Result, Error<'_>> { + parse_from_bytes(input.as_bytes()) +} + +/// Attempt to zero-copy parse the provided bytes. On success, returns a +/// [`Parser`] that provides methods to accessing leading comments and sections +/// of a `git-config` file and can be converted into an iterator of [`Event`] +/// for higher level processing. +/// +/// # Errors +/// +/// Returns an error if the string provided is not a valid `git-config`. +/// This generally is due to either invalid names or if there's extraneous +/// data succeeding valid `git-config` data. +#[allow(clippy::shadow_unrelated)] +pub fn parse_from_bytes(input: &[u8]) -> Result, Error<'_>> { + let bom = unicode_bom::Bom::from(input); + let mut newlines = 0; + let (i, frontmatter) = many0(alt(( + map(comment, Event::Comment), + map(take_spaces, |whitespace| Event::Whitespace(Cow::Borrowed(whitespace))), + map(take_newlines, |(newline, counter)| { + newlines += counter; + Event::Newline(Cow::Borrowed(newline)) + }), + )))(&input[bom.len()..]) + // I don't think this can panic. many0 errors if the child parser returns + // a success where the input was not consumed, but alt will only return Ok + // if one of its children succeed. However, all of it's children are + // guaranteed to consume something if they succeed, so the Ok(i) == i case + // can never occur. + .expect("many0(alt(...)) panicked. Likely a bug in one of the children parser."); + + if i.is_empty() { + return Ok(Parser { + frontmatter, + sections: vec![], + }); + } + + let mut node = ParserNode::SectionHeader; + + let maybe_sections = many1(|i| section(i, &mut node))(i); + let (i, sections) = maybe_sections.map_err(|_| Error { + line_number: newlines, + last_attempted_parser: node, + parsed_until: i.as_bstr().into(), + })?; + + let sections = sections + .into_iter() + .map(|(section, additional_newlines)| { + newlines += additional_newlines; + section + }) + .collect(); + + // This needs to happen after we collect sections, otherwise the line number + // will be off. + if !i.is_empty() { + return Err(Error { + line_number: newlines, + last_attempted_parser: node, + parsed_until: i.as_bstr().into(), + }); + } + + Ok(Parser { frontmatter, sections }) +} + +/// Parses the provided bytes, returning an [`Parser`] that contains allocated +/// and owned events. This is similar to [`parse_from_bytes`], but performance +/// is degraded as it requires allocation for every event. However, this permits +/// the reference bytes to be dropped, allowing the parser to be passed around +/// without lifetime worries. +/// +/// # Errors +/// +/// Returns an error if the string provided is not a valid `git-config`. +/// This generally is due to either invalid names or if there's extraneous +/// data succeeding valid `git-config` data. +#[allow(clippy::shadow_unrelated)] +pub fn parse_from_bytes_owned(input: &[u8]) -> Result, Error<'static>> { + // FIXME: This is duplication is necessary until comment, take_spaces, and take_newlines + // accept cows instead, since we don't want to unnecessarily copy the frontmatter + // events in a hypothetical parse_from_cow function. + let mut newlines = 0; + let bom = unicode_bom::Bom::from(input); + let (i, frontmatter) = many0(alt(( + map(comment, Event::Comment), + map(take_spaces, |whitespace| { + Event::Whitespace(Cow::Borrowed(whitespace.as_bstr())) + }), + map(take_newlines, |(newline, counter)| { + newlines += counter; + Event::Newline(Cow::Borrowed(newline.as_bstr())) + }), + )))(&input[bom.len()..]) + // I don't think this can panic. many0 errors if the child parser returns + // a success where the input was not consumed, but alt will only return Ok + // if one of its children succeed. However, all of it's children are + // guaranteed to consume something if they succeed, so the Ok(i) == i case + // can never occur. + .expect("many0(alt(...)) panicked. Likely a bug in one of the children parser."); + let frontmatter = frontmatter.iter().map(Event::to_owned).collect(); + if i.is_empty() { + return Ok(Parser { + frontmatter, + sections: vec![], + }); + } + + let mut node = ParserNode::SectionHeader; + + let maybe_sections = many1(|i| section(i, &mut node))(i); + let (i, sections) = maybe_sections.map_err(|_| Error { + line_number: newlines, + last_attempted_parser: node, + parsed_until: Cow::Owned(i.into()), + })?; + + let sections = sections + .into_iter() + .map(|(section, additional_newlines)| { + newlines += additional_newlines; + section.to_owned() + }) + .collect(); + + // This needs to happen after we collect sections, otherwise the line number + // will be off. + if !i.is_empty() { + return Err(Error { + line_number: newlines, + last_attempted_parser: node, + parsed_until: Cow::Owned(i.into()), + }); + } + + Ok(Parser { frontmatter, sections }) +} + +fn comment(i: &[u8]) -> IResult<&[u8], ParsedComment<'_>> { + let (i, comment_tag) = one_of(";#")(i)?; + let (i, comment) = take_till(|c| c == b'\n')(i)?; + Ok(( + i, + ParsedComment { + comment_tag: comment_tag as u8, + comment: Cow::Borrowed(comment.as_bstr()), + }, + )) +} + +#[cfg(test)] +mod tests; + +fn section<'a, 'b>(i: &'a [u8], node: &'b mut ParserNode) -> IResult<&'a [u8], (ParsedSection<'a>, usize)> { + let (mut i, section_header) = section_header(i)?; + + let mut newlines = 0; + let mut items = vec![]; + + // This would usually be a many0(alt(...)), the manual loop allows us to + // optimize vec insertions + loop { + let old_i = i; + + if let Ok((new_i, v)) = take_spaces(i) { + if old_i != new_i { + i = new_i; + items.push(Event::Whitespace(Cow::Borrowed(v.as_bstr()))); + } + } + + if let Ok((new_i, (v, new_newlines))) = take_newlines(i) { + if old_i != new_i { + i = new_i; + newlines += new_newlines; + items.push(Event::Newline(Cow::Borrowed(v.as_bstr()))); + } + } + + if let Ok((new_i, _)) = section_body(i, node, &mut items) { + if old_i != new_i { + i = new_i; + } + } + + if let Ok((new_i, comment)) = comment(i) { + if old_i != new_i { + i = new_i; + items.push(Event::Comment(comment)); + } + } + + if old_i == i { + break; + } + } + + Ok(( + i, + ( + ParsedSection { + section_header, + events: items, + }, + newlines, + ), + )) +} + +fn section_header(i: &[u8]) -> IResult<&[u8], ParsedSectionHeader<'_>> { + let (i, _) = char('[')(i)?; + // No spaces must be between section name and section start + let (i, name) = take_while(|c: u8| c.is_ascii_alphanumeric() || c == b'-' || c == b'.')(i)?; + + let name = name.as_bstr(); + if let Ok((i, _)) = char::<_, NomError<&[u8]>>(']')(i) { + // Either section does not have a subsection or using deprecated + // subsection syntax at this point. + let header = match memchr::memrchr(b'.', name.as_bytes()) { + Some(index) => ParsedSectionHeader { + name: SectionHeaderName(Cow::Borrowed(name[..index].as_bstr())), + separator: name.get(index..=index).map(|s| Cow::Borrowed(s.as_bstr())), + subsection_name: name.get(index + 1..).map(|s| Cow::Borrowed(s.as_bstr())), + }, + None => ParsedSectionHeader { + name: SectionHeaderName(Cow::Borrowed(name.as_bstr())), + separator: None, + subsection_name: None, + }, + }; + + return Ok((i, header)); + } + + // Section header must be using modern subsection syntax at this point. + + let (i, whitespace) = take_spaces(i)?; + let (i, subsection_name) = delimited(char('"'), opt(sub_section), tag("\"]"))(i)?; + + Ok(( + i, + ParsedSectionHeader { + name: SectionHeaderName(Cow::Borrowed(name)), + separator: Some(Cow::Borrowed(whitespace)), + subsection_name: subsection_name.map(Cow::Owned), + }, + )) +} + +fn sub_section(i: &[u8]) -> IResult<&[u8], BString> { + let mut cursor = 0; + let mut bytes = i.iter().copied(); + let mut found_terminator = false; + let mut buf = BString::default(); + while let Some(mut b) = bytes.next() { + cursor += 1; + if b == b'\n' { + return Err(nom::Err::Error(NomError { + input: &i[cursor..], + code: ErrorKind::NonEmpty, + })); + } + if b == b'"' { + found_terminator = true; + break; + } + if b == b'\\' { + b = bytes.next().ok_or_else(|| { + nom::Err::Error(NomError { + input: &i[cursor..], + code: ErrorKind::NonEmpty, + }) + })?; + cursor += 1; + if b == b'\n' { + return Err(nom::Err::Error(NomError { + input: &i[cursor..], + code: ErrorKind::NonEmpty, + })); + } + } + buf.push_byte(b); + } + + if !found_terminator { + return Err(nom::Err::Error(NomError { + input: &i[cursor..], + code: ErrorKind::NonEmpty, + })); + } + + Ok((&i[cursor - 1..], buf)) +} + +fn section_body<'a, 'b, 'c>( + i: &'a [u8], + node: &'b mut ParserNode, + items: &'c mut Vec>, +) -> IResult<&'a [u8], ()> { + // maybe need to check for [ here + *node = ParserNode::ConfigName; + let (i, name) = config_name(i)?; + + items.push(Event::Key(Key(Cow::Borrowed(name)))); + + let (i, whitespace) = opt(take_spaces)(i)?; + + if let Some(whitespace) = whitespace { + items.push(Event::Whitespace(Cow::Borrowed(whitespace))); + } + + let (i, _) = config_value(i, items)?; + Ok((i, ())) +} + +/// Parses the config name of a config pair. Assumes the input has already been +/// trimmed of any leading whitespace. +fn config_name(i: &[u8]) -> IResult<&[u8], &BStr> { + if i.is_empty() { + return Err(nom::Err::Error(NomError { + input: i, + code: ErrorKind::NonEmpty, + })); + } + + if !i[0].is_ascii_alphabetic() { + return Err(nom::Err::Error(NomError { + input: i, + code: ErrorKind::Alpha, + })); + } + + let (i, v) = take_while(|c: u8| c.is_ascii_alphanumeric() || c == b'-')(i)?; + Ok((i, v.as_bstr())) +} + +fn config_value<'a, 'b>(i: &'a [u8], events: &'b mut Vec>) -> IResult<&'a [u8], ()> { + if let (i, Some(_)) = opt(char('='))(i)? { + events.push(Event::KeyValueSeparator); + let (i, whitespace) = opt(take_spaces)(i)?; + if let Some(whitespace) = whitespace { + events.push(Event::Whitespace(Cow::Borrowed(whitespace))); + } + let (i, _) = value_impl(i, events)?; + Ok((i, ())) + } else { + events.push(Event::Value(Cow::Borrowed("".into()))); + Ok((i, ())) + } +} + +/// Handles parsing of known-to-be values. This function handles both single +/// line values as well as values that are continuations. +/// +/// # Errors +/// +/// Returns an error if an invalid escape was used, if there was an unfinished +/// quote, or there was an escape but there is nothing left to escape. +fn value_impl<'a, 'b>(i: &'a [u8], events: &'b mut Vec>) -> IResult<&'a [u8], ()> { + let mut parsed_index: usize = 0; + let mut offset: usize = 0; + + let mut was_prev_char_escape_char = false; + // This is required to ignore comment markers if they're in a quote. + let mut is_in_quotes = false; + // Used to determine if we return a Value or Value{Not,}Done + let mut partial_value_found = false; + let mut index: usize = 0; + + for c in i.iter() { + if was_prev_char_escape_char { + was_prev_char_escape_char = false; + match c { + // We're escaping a newline, which means we've found a + // continuation. + b'\n' => { + partial_value_found = true; + events.push(Event::ValueNotDone(Cow::Borrowed(i[offset..index - 1].as_bstr()))); + events.push(Event::Newline(Cow::Borrowed(i[index..=index].as_bstr()))); + offset = index + 1; + parsed_index = 0; + } + b't' | b'\\' | b'n' | b'"' => (), + _ => { + return Err(nom::Err::Error(NomError { + input: i, + code: ErrorKind::Escaped, + })); + } + } + } else { + match c { + b'\n' => { + parsed_index = index; + break; + } + b';' | b'#' if !is_in_quotes => { + parsed_index = index; + break; + } + b'\\' => was_prev_char_escape_char = true, + b'"' => is_in_quotes = !is_in_quotes, + _ => {} + } + } + index += 1; + } + + if parsed_index == 0 { + if index != 0 { + parsed_index = i.len(); + } else { + // Didn't parse anything at all, newline straight away. + events.push(Event::Value(Cow::Owned(BString::default()))); + events.push(Event::Newline(Cow::Borrowed("\n".into()))); + return Ok((&i[1..], ())); + } + } + + // Handle incomplete escape + if was_prev_char_escape_char { + return Err(nom::Err::Error(NomError { + input: i, + code: ErrorKind::Escaped, + })); + } + + // Handle incomplete quotes + if is_in_quotes { + return Err(nom::Err::Error(NomError { + input: i, + code: ErrorKind::Tag, + })); + } + + let (i, remainder_value) = { + let mut new_index = parsed_index; + for index in (offset..parsed_index).rev() { + if !i[index].is_ascii_whitespace() { + new_index = index + 1; + break; + } + } + (&i[new_index..], &i[offset..new_index]) + }; + + if partial_value_found { + events.push(Event::ValueDone(Cow::Borrowed(remainder_value.as_bstr()))); + } else { + events.push(Event::Value(Cow::Borrowed(remainder_value.as_bstr()))); + } + + Ok((i, ())) +} + +fn take_spaces(i: &[u8]) -> IResult<&[u8], &BStr> { + let (i, v) = take_while(|c: u8| c.is_ascii() && is_space(c))(i)?; + if v.is_empty() { + Err(nom::Err::Error(NomError { + input: i, + code: ErrorKind::Eof, + })) + } else { + Ok((i, v.as_bstr())) + } +} + +fn take_newlines(i: &[u8]) -> IResult<&[u8], (&BStr, usize)> { + let mut counter = 0; + let mut consumed_bytes = 0; + let mut next_must_be_newline = false; + for b in i.iter().copied() { + if !b.is_ascii() { + break; + }; + if b == b'\r' { + if next_must_be_newline { + break; + } + next_must_be_newline = true; + continue; + }; + if b == b'\n' { + counter += 1; + consumed_bytes += if next_must_be_newline { 2 } else { 1 }; + next_must_be_newline = false; + } else { + break; + } + } + let (v, i) = i.split_at(consumed_bytes); + if v.is_empty() { + Err(nom::Err::Error(NomError { + input: i, + code: ErrorKind::Eof, + })) + } else { + Ok((i, (v.as_bstr(), counter))) + } +} diff --git a/git-config/src/parser/nom/tests.rs b/git-config/src/parser/nom/tests.rs new file mode 100644 index 00000000000..05bc73b4a8a --- /dev/null +++ b/git-config/src/parser/nom/tests.rs @@ -0,0 +1,543 @@ +use super::*; + +mod section_headers { + use super::section_header; + use crate::parser::tests::util::{fully_consumed, section_header as parsed_section_header}; + + #[test] + fn no_subsection() { + assert_eq!( + section_header(b"[hello]").unwrap(), + fully_consumed(parsed_section_header("hello", None)), + ); + } + + #[test] + fn modern_subsection() { + assert_eq!( + section_header(br#"[hello "world"]"#).unwrap(), + fully_consumed(parsed_section_header("hello", (" ", "world"))), + ); + } + + #[test] + fn escaped_subsection() { + assert_eq!( + section_header(br#"[hello "foo\\bar\""]"#).unwrap(), + fully_consumed(parsed_section_header("hello", (" ", r#"foo\bar""#))), + ); + } + + #[test] + fn deprecated_subsection() { + assert_eq!( + section_header(br#"[hello.world]"#).unwrap(), + fully_consumed(parsed_section_header("hello", (".", "world"))) + ); + } + + #[test] + fn empty_legacy_subsection_name() { + assert_eq!( + section_header(br#"[hello.]"#).unwrap(), + fully_consumed(parsed_section_header("hello", (".", ""))) + ); + } + + #[test] + fn empty_modern_subsection_name() { + assert_eq!( + section_header(br#"[hello ""]"#).unwrap(), + fully_consumed(parsed_section_header("hello", (" ", ""))) + ); + } + + #[test] + fn newline_in_header() { + assert!(section_header(b"[hello\n]").is_err()); + } + + #[test] + fn null_byte_in_header() { + assert!(section_header(b"[hello\0]").is_err()); + } + + #[test] + fn right_brace_in_subsection_name() { + assert_eq!( + section_header(br#"[hello "]"]"#).unwrap(), + fully_consumed(parsed_section_header("hello", (" ", "]"))) + ); + } +} + +mod config_name { + use super::config_name; + use crate::parser::tests::util::fully_consumed; + + #[test] + fn just_name() { + assert_eq!(config_name(b"name").unwrap(), fully_consumed("name".into())); + } + + #[test] + fn must_start_with_alphabetic() { + assert!(config_name(b"4aaa").is_err()); + assert!(config_name(b"-aaa").is_err()); + } + + #[test] + fn cannot_be_empty() { + assert!(config_name(b"").is_err()); + } +} + +mod section { + use super::section; + use crate::parser::tests::util::{ + comment_event, fully_consumed, name_event, newline_event, section_header as parsed_section_header, + value_done_event, value_event, value_not_done_event, whitespace_event, + }; + use crate::parser::{error::ParserNode, Event, ParsedSection}; + + #[test] + fn empty_section() { + let mut node = ParserNode::SectionHeader; + assert_eq!( + section(b"[test]", &mut node).unwrap(), + fully_consumed(( + ParsedSection { + section_header: parsed_section_header("test", None), + events: vec![] + }, + 0 + )), + ); + } + + #[test] + fn simple_section() { + let mut node = ParserNode::SectionHeader; + let section_data = br#"[hello] + a = b + c + d = "lol""#; + assert_eq!( + section(section_data, &mut node).unwrap(), + fully_consumed(( + ParsedSection { + section_header: parsed_section_header("hello", None), + events: vec![ + newline_event(), + whitespace_event(" "), + name_event("a"), + whitespace_event(" "), + Event::KeyValueSeparator, + whitespace_event(" "), + value_event("b"), + newline_event(), + whitespace_event(" "), + name_event("c"), + value_event(""), + newline_event(), + whitespace_event(" "), + name_event("d"), + whitespace_event(" "), + Event::KeyValueSeparator, + whitespace_event(" "), + value_event("\"lol\"") + ] + }, + 3 + )) + ); + } + + #[test] + fn section_with_empty_value() { + let mut node = ParserNode::SectionHeader; + let section_data = br#"[hello] + a = b + c= + d = "lol""#; + assert_eq!( + section(section_data, &mut node).unwrap(), + fully_consumed(( + ParsedSection { + section_header: parsed_section_header("hello", None), + events: vec![ + newline_event(), + whitespace_event(" "), + name_event("a"), + whitespace_event(" "), + Event::KeyValueSeparator, + whitespace_event(" "), + value_event("b"), + newline_event(), + whitespace_event(" "), + name_event("c"), + Event::KeyValueSeparator, + value_event(""), + newline_event(), + whitespace_event(" "), + name_event("d"), + whitespace_event(" "), + Event::KeyValueSeparator, + whitespace_event(" "), + value_event("\"lol\"") + ] + }, + 2 + )) + ); + } + + #[test] + fn section_single_line() { + let mut node = ParserNode::SectionHeader; + assert_eq!( + section(b"[hello] c", &mut node).unwrap(), + fully_consumed(( + ParsedSection { + section_header: parsed_section_header("hello", None), + events: vec![whitespace_event(" "), name_event("c"), value_event("")] + }, + 0 + )) + ); + } + + #[test] + fn section_very_commented() { + let mut node = ParserNode::SectionHeader; + let section_data = br#"[hello] ; commentA + a = b # commentB + ; commentC + ; commentD + c = d"#; + assert_eq!( + section(section_data, &mut node).unwrap(), + fully_consumed(( + ParsedSection { + section_header: parsed_section_header("hello", None), + events: vec![ + whitespace_event(" "), + comment_event(';', " commentA"), + newline_event(), + whitespace_event(" "), + name_event("a"), + whitespace_event(" "), + Event::KeyValueSeparator, + whitespace_event(" "), + value_event("b"), + whitespace_event(" "), + comment_event('#', " commentB"), + newline_event(), + whitespace_event(" "), + comment_event(';', " commentC"), + newline_event(), + whitespace_event(" "), + comment_event(';', " commentD"), + newline_event(), + whitespace_event(" "), + name_event("c"), + whitespace_event(" "), + Event::KeyValueSeparator, + whitespace_event(" "), + value_event("d"), + ] + }, + 4 + )) + ); + } + + #[test] + fn complex_continuation() { + let mut node = ParserNode::SectionHeader; + // This test is absolute hell. Good luck if this fails. + assert_eq!( + section(b"[section] a = 1 \"\\\"\\\na ; e \"\\\"\\\nd # \"b\t ; c", &mut node).unwrap(), + fully_consumed(( + ParsedSection { + section_header: parsed_section_header("section", None), + events: vec![ + whitespace_event(" "), + name_event("a"), + whitespace_event(" "), + Event::KeyValueSeparator, + whitespace_event(" "), + value_not_done_event(r#"1 "\""#), + newline_event(), + value_not_done_event(r#"a ; e "\""#), + newline_event(), + value_done_event("d"), + whitespace_event(" "), + comment_event('#', " \"b\t ; c"), + ] + }, + 0 + )) + ); + } + + #[test] + fn quote_split_over_two_lines() { + let mut node = ParserNode::SectionHeader; + assert_eq!( + section(b"[section \"a\"] b =\"\\\n;\";a", &mut node).unwrap(), + fully_consumed(( + ParsedSection { + section_header: parsed_section_header("section", (" ", "a")), + events: vec![ + whitespace_event(" "), + name_event("b"), + whitespace_event(" "), + Event::KeyValueSeparator, + value_not_done_event("\""), + newline_event(), + value_done_event(";\""), + comment_event(';', "a"), + ] + }, + 0 + )) + ); + } + + #[test] + fn section_handles_extranous_whitespace_before_comment() { + let mut node = ParserNode::SectionHeader; + assert_eq!( + section(b"[s]hello #world", &mut node).unwrap(), + fully_consumed(( + ParsedSection { + section_header: parsed_section_header("s", None), + events: vec![ + name_event("hello"), + whitespace_event(" "), + value_event(""), + comment_event('#', "world"), + ] + }, + 0 + )) + ); + } +} + +mod value_continuation { + use super::value_impl; + use crate::parser::tests::util::{newline_event, value_done_event, value_not_done_event}; + + #[test] + fn simple_continuation() { + let mut events = vec![]; + assert_eq!(value_impl(b"hello\\\nworld", &mut events).unwrap().0, b""); + assert_eq!( + events, + vec![ + value_not_done_event("hello"), + newline_event(), + value_done_event("world") + ] + ); + } + + #[test] + fn continuation_with_whitespace() { + let mut events = vec![]; + assert_eq!(value_impl(b"hello\\\n world", &mut events).unwrap().0, b""); + assert_eq!( + events, + vec![ + value_not_done_event("hello"), + newline_event(), + value_done_event(" world") + ] + ); + } + + #[test] + fn complex_continuation_with_leftover_comment() { + let mut events = vec![]; + assert_eq!( + value_impl(b"1 \"\\\"\\\na ; e \"\\\"\\\nd # \"b\t ; c", &mut events) + .unwrap() + .0, + b" # \"b\t ; c" + ); + assert_eq!( + events, + vec![ + value_not_done_event(r#"1 "\""#), + newline_event(), + value_not_done_event(r#"a ; e "\""#), + newline_event(), + value_done_event("d") + ] + ); + } + + #[test] + fn quote_split_over_two_lines_with_leftover_comment() { + let mut events = vec![]; + assert_eq!(value_impl(b"\"\\\n;\";a", &mut events).unwrap().0, b";a"); + assert_eq!( + events, + vec![value_not_done_event("\""), newline_event(), value_done_event(";\"")] + ); + } +} + +mod value_no_continuation { + use super::value_impl; + use crate::parser::tests::util::value_event; + + #[test] + fn no_comment() { + let mut events = vec![]; + assert_eq!(value_impl(b"hello", &mut events).unwrap().0, b""); + assert_eq!(events, vec![value_event("hello")]); + } + + #[test] + fn no_comment_newline() { + let mut events = vec![]; + assert_eq!(value_impl(b"hello\na", &mut events).unwrap().0, b"\na"); + assert_eq!(events, vec![value_event("hello")]); + } + + #[test] + fn semicolon_comment_not_consumed() { + let mut events = vec![]; + assert_eq!(value_impl(b"hello;world", &mut events).unwrap().0, b";world"); + assert_eq!(events, vec![value_event("hello")]); + } + + #[test] + fn octothorpe_comment_not_consumed() { + let mut events = vec![]; + assert_eq!(value_impl(b"hello#world", &mut events).unwrap().0, b"#world"); + assert_eq!(events, vec![value_event("hello")]); + } + + #[test] + fn values_with_extraneous_whitespace_without_comment() { + let mut events = vec![]; + assert_eq!( + value_impl(b"hello ", &mut events).unwrap().0, + b" " + ); + assert_eq!(events, vec![value_event("hello")]); + } + + #[test] + fn values_with_extraneous_whitespace_before_comment() { + let mut events = vec![]; + assert_eq!( + value_impl(b"hello #world", &mut events).unwrap().0, + b" #world" + ); + assert_eq!(events, vec![value_event("hello")]); + + let mut events = vec![]; + assert_eq!( + value_impl(b"hello ;world", &mut events).unwrap().0, + b" ;world" + ); + assert_eq!(events, vec![value_event("hello")]); + } + + #[test] + fn trans_escaped_comment_marker_not_consumed() { + let mut events = vec![]; + assert_eq!(value_impl(br##"hello"#"world; a"##, &mut events).unwrap().0, b"; a"); + assert_eq!(events, vec![value_event(r##"hello"#"world"##)]); + } + + #[test] + fn complex_test() { + let mut events = vec![]; + assert_eq!(value_impl(br#"value";";ahhhh"#, &mut events).unwrap().0, b";ahhhh"); + assert_eq!(events, vec![value_event(r#"value";""#)]); + } + + #[test] + fn garbage_after_continution_is_err() { + assert!(value_impl(b"hello \\afwjdls", &mut vec![]).is_err()); + } + + #[test] + fn incomplete_quote() { + assert!(value_impl(br#"hello "world"#, &mut vec![]).is_err()); + } + + #[test] + fn incomplete_escape() { + assert!(value_impl(br#"hello world\"#, &mut vec![]).is_err()); + } +} + +mod section_body { + use super::section_body; + use crate::parser::tests::util::{name_event, value_event, whitespace_event}; + use crate::parser::{error::ParserNode, Event}; + + #[test] + fn whitespace_is_not_ambigious() { + let mut node = ParserNode::SectionHeader; + let mut vec = vec![]; + assert!(section_body(b"a =b", &mut node, &mut vec).is_ok()); + assert_eq!( + vec, + vec![ + name_event("a"), + whitespace_event(" "), + Event::KeyValueSeparator, + value_event("b") + ] + ); + + let mut vec = vec![]; + assert!(section_body(b"a= b", &mut node, &mut vec).is_ok()); + assert_eq!( + vec, + vec![ + name_event("a"), + Event::KeyValueSeparator, + whitespace_event(" "), + value_event("b") + ] + ); + } +} + +mod comment { + use super::comment; + use crate::parser::tests::util::{comment as parsed_comment, fully_consumed}; + + #[test] + fn semicolon() { + assert_eq!( + comment(b"; this is a semicolon comment").unwrap(), + fully_consumed(parsed_comment(';', " this is a semicolon comment")), + ); + } + + #[test] + fn octothorpe() { + assert_eq!( + comment(b"# this is an octothorpe comment").unwrap(), + fully_consumed(parsed_comment('#', " this is an octothorpe comment")), + ); + } + + #[test] + fn multiple_markers() { + assert_eq!( + comment(b"###### this is an octothorpe comment").unwrap(), + fully_consumed(parsed_comment('#', "##### this is an octothorpe comment")), + ); + } +} diff --git a/git-config/src/parser/section.rs b/git-config/src/parser/section.rs new file mode 100644 index 00000000000..a74216c9721 --- /dev/null +++ b/git-config/src/parser/section.rs @@ -0,0 +1,108 @@ +use crate::parser::{Event, ParsedSection, ParsedSectionHeader}; +use bstr::BString; +use std::borrow::Cow; +use std::fmt::Display; + +impl ParsedSection<'_> { + /// Coerces into an owned instance. This differs from the standard [`clone`] + /// implementation as calling clone will _not_ copy the borrowed variant, + /// while this method will. In other words: + /// + /// | Borrow type | `.clone()` | `to_owned()` | + /// | ----------- | ---------- | ------------ | + /// | Borrowed | Borrowed | Owned | + /// | Owned | Owned | Owned | + /// + /// This can be most effectively seen by the differing lifetimes between the + /// two. This method guarantees a `'static` lifetime, while `clone` does + /// not. + /// + /// [`clone`]: Self::clone + #[must_use] + pub fn to_owned(&self) -> ParsedSection<'static> { + ParsedSection { + section_header: self.section_header.to_owned(), + events: self.events.iter().map(Event::to_owned).collect(), + } + } +} + +impl Display for ParsedSection<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.section_header)?; + for event in &self.events { + event.fmt(f)?; + } + Ok(()) + } +} + +impl ParsedSectionHeader<'_> { + /// Generates a byte representation of the value. This should be used when + /// non-UTF-8 sequences are present or a UTF-8 representation can't be + /// guaranteed. + #[must_use] + pub fn to_bstring(&self) -> BString { + self.into() + } + + /// Coerces into an owned instance. This differs from the standard [`clone`] + /// implementation as calling clone will _not_ copy the borrowed variant, + /// while this method will. In other words: + /// + /// | Borrow type | `.clone()` | `to_owned()` | + /// | ----------- | ---------- | ------------ | + /// | Borrowed | Borrowed | Owned | + /// | Owned | Owned | Owned | + /// + /// This can be most effectively seen by the differing lifetimes between the + /// two. This method guarantees a `'static` lifetime, while `clone` does + /// not. + /// + /// [`clone`]: Self::clone + #[must_use] + pub fn to_owned(&self) -> ParsedSectionHeader<'static> { + ParsedSectionHeader { + name: self.name.to_owned(), + separator: self.separator.clone().map(|v| Cow::Owned(v.into_owned())), + subsection_name: self.subsection_name.clone().map(|v| Cow::Owned(v.into_owned())), + } + } +} + +impl Display for ParsedSectionHeader<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "[{}", self.name)?; + + if let Some(v) = &self.separator { + // Separator must be utf-8 + v.fmt(f)?; + let subsection_name = self.subsection_name.as_ref().unwrap(); + if v.as_ref() == "." { + subsection_name.fmt(f)?; + } else { + write!(f, "\"{}\"", subsection_name)?; + } + } + + write!(f, "]") + } +} + +impl From> for BString { + fn from(header: ParsedSectionHeader<'_>) -> Self { + header.into() + } +} + +impl From<&ParsedSectionHeader<'_>> for BString { + fn from(header: &ParsedSectionHeader<'_>) -> Self { + header.to_string().into() + } +} + +impl<'a> From> for Event<'a> { + fn from(header: ParsedSectionHeader<'_>) -> Event<'_> { + Event::SectionHeader(header) + } +} diff --git a/git-config/src/parser/tests.rs b/git-config/src/parser/tests.rs index c50e6859a1f..9bb44d056cc 100644 --- a/git-config/src/parser/tests.rs +++ b/git-config/src/parser/tests.rs @@ -1,543 +1,3 @@ -mod comments { - use crate::parser::comment; - use crate::parser::tests::util::{comment as parsed_comment, fully_consumed}; - - #[test] - fn semicolon() { - assert_eq!( - comment(b"; this is a semicolon comment").unwrap(), - fully_consumed(parsed_comment(';', " this is a semicolon comment")), - ); - } - - #[test] - fn octothorpe() { - assert_eq!( - comment(b"# this is an octothorpe comment").unwrap(), - fully_consumed(parsed_comment('#', " this is an octothorpe comment")), - ); - } - - #[test] - fn multiple_markers() { - assert_eq!( - comment(b"###### this is an octothorpe comment").unwrap(), - fully_consumed(parsed_comment('#', "##### this is an octothorpe comment")), - ); - } -} - -mod section_headers { - use crate::parser::section_header; - use crate::parser::tests::util::{fully_consumed, section_header as parsed_section_header}; - - #[test] - fn no_subsection() { - assert_eq!( - section_header(b"[hello]").unwrap(), - fully_consumed(parsed_section_header("hello", None)), - ); - } - - #[test] - fn modern_subsection() { - assert_eq!( - section_header(br#"[hello "world"]"#).unwrap(), - fully_consumed(parsed_section_header("hello", (" ", "world"))), - ); - } - - #[test] - fn escaped_subsection() { - assert_eq!( - section_header(br#"[hello "foo\\bar\""]"#).unwrap(), - fully_consumed(parsed_section_header("hello", (" ", r#"foo\bar""#))), - ); - } - - #[test] - fn deprecated_subsection() { - assert_eq!( - section_header(br#"[hello.world]"#).unwrap(), - fully_consumed(parsed_section_header("hello", (".", "world"))) - ); - } - - #[test] - fn empty_legacy_subsection_name() { - assert_eq!( - section_header(br#"[hello.]"#).unwrap(), - fully_consumed(parsed_section_header("hello", (".", ""))) - ); - } - - #[test] - fn empty_modern_subsection_name() { - assert_eq!( - section_header(br#"[hello ""]"#).unwrap(), - fully_consumed(parsed_section_header("hello", (" ", ""))) - ); - } - - #[test] - fn newline_in_header() { - assert!(section_header(b"[hello\n]").is_err()); - } - - #[test] - fn null_byte_in_header() { - assert!(section_header(b"[hello\0]").is_err()); - } - - #[test] - fn right_brace_in_subsection_name() { - assert_eq!( - section_header(br#"[hello "]"]"#).unwrap(), - fully_consumed(parsed_section_header("hello", (" ", "]"))) - ); - } -} - -mod config_name { - use crate::parser::config_name; - use crate::parser::tests::util::fully_consumed; - - #[test] - fn just_name() { - assert_eq!(config_name(b"name").unwrap(), fully_consumed("name".into())); - } - - #[test] - fn must_start_with_alphabetic() { - assert!(config_name(b"4aaa").is_err()); - assert!(config_name(b"-aaa").is_err()); - } - - #[test] - fn cannot_be_empty() { - assert!(config_name(b"").is_err()); - } -} - -mod section_body { - use crate::parser::tests::util::{name_event, value_event, whitespace_event}; - use crate::parser::{section_body, Event, ParserNode}; - - #[test] - fn whitespace_is_not_ambigious() { - let mut node = ParserNode::SectionHeader; - let mut vec = vec![]; - assert!(section_body(b"a =b", &mut node, &mut vec).is_ok()); - assert_eq!( - vec, - vec![ - name_event("a"), - whitespace_event(" "), - Event::KeyValueSeparator, - value_event("b") - ] - ); - - let mut vec = vec![]; - assert!(section_body(b"a= b", &mut node, &mut vec).is_ok()); - assert_eq!( - vec, - vec![ - name_event("a"), - Event::KeyValueSeparator, - whitespace_event(" "), - value_event("b") - ] - ); - } -} - -mod value_no_continuation { - use crate::parser::tests::util::value_event; - use crate::parser::value_impl; - - #[test] - fn no_comment() { - let mut events = vec![]; - assert_eq!(value_impl(b"hello", &mut events).unwrap().0, b""); - assert_eq!(events, vec![value_event("hello")]); - } - - #[test] - fn no_comment_newline() { - let mut events = vec![]; - assert_eq!(value_impl(b"hello\na", &mut events).unwrap().0, b"\na"); - assert_eq!(events, vec![value_event("hello")]); - } - - #[test] - fn semicolon_comment_not_consumed() { - let mut events = vec![]; - assert_eq!(value_impl(b"hello;world", &mut events).unwrap().0, b";world"); - assert_eq!(events, vec![value_event("hello")]); - } - - #[test] - fn octothorpe_comment_not_consumed() { - let mut events = vec![]; - assert_eq!(value_impl(b"hello#world", &mut events).unwrap().0, b"#world"); - assert_eq!(events, vec![value_event("hello")]); - } - - #[test] - fn values_with_extraneous_whitespace_without_comment() { - let mut events = vec![]; - assert_eq!( - value_impl(b"hello ", &mut events).unwrap().0, - b" " - ); - assert_eq!(events, vec![value_event("hello")]); - } - - #[test] - fn values_with_extraneous_whitespace_before_comment() { - let mut events = vec![]; - assert_eq!( - value_impl(b"hello #world", &mut events).unwrap().0, - b" #world" - ); - assert_eq!(events, vec![value_event("hello")]); - - let mut events = vec![]; - assert_eq!( - value_impl(b"hello ;world", &mut events).unwrap().0, - b" ;world" - ); - assert_eq!(events, vec![value_event("hello")]); - } - - #[test] - fn trans_escaped_comment_marker_not_consumed() { - let mut events = vec![]; - assert_eq!(value_impl(br##"hello"#"world; a"##, &mut events).unwrap().0, b"; a"); - assert_eq!(events, vec![value_event(r##"hello"#"world"##)]); - } - - #[test] - fn complex_test() { - let mut events = vec![]; - assert_eq!(value_impl(br#"value";";ahhhh"#, &mut events).unwrap().0, b";ahhhh"); - assert_eq!(events, vec![value_event(r#"value";""#)]); - } - - #[test] - fn garbage_after_continution_is_err() { - assert!(value_impl(b"hello \\afwjdls", &mut vec![]).is_err()); - } - - #[test] - fn incomplete_quote() { - assert!(value_impl(br#"hello "world"#, &mut vec![]).is_err()); - } - - #[test] - fn incomplete_escape() { - assert!(value_impl(br#"hello world\"#, &mut vec![]).is_err()); - } -} - -mod value_continuation { - use crate::parser::tests::util::{newline_event, value_done_event, value_not_done_event}; - use crate::parser::value_impl; - - #[test] - fn simple_continuation() { - let mut events = vec![]; - assert_eq!(value_impl(b"hello\\\nworld", &mut events).unwrap().0, b""); - assert_eq!( - events, - vec![ - value_not_done_event("hello"), - newline_event(), - value_done_event("world") - ] - ); - } - - #[test] - fn continuation_with_whitespace() { - let mut events = vec![]; - assert_eq!(value_impl(b"hello\\\n world", &mut events).unwrap().0, b""); - assert_eq!( - events, - vec![ - value_not_done_event("hello"), - newline_event(), - value_done_event(" world") - ] - ); - } - - #[test] - fn complex_continuation_with_leftover_comment() { - let mut events = vec![]; - assert_eq!( - value_impl(b"1 \"\\\"\\\na ; e \"\\\"\\\nd # \"b\t ; c", &mut events) - .unwrap() - .0, - b" # \"b\t ; c" - ); - assert_eq!( - events, - vec![ - value_not_done_event(r#"1 "\""#), - newline_event(), - value_not_done_event(r#"a ; e "\""#), - newline_event(), - value_done_event("d") - ] - ); - } - - #[test] - fn quote_split_over_two_lines_with_leftover_comment() { - let mut events = vec![]; - assert_eq!(value_impl(b"\"\\\n;\";a", &mut events).unwrap().0, b";a"); - assert_eq!( - events, - vec![value_not_done_event("\""), newline_event(), value_done_event(";\"")] - ); - } -} - -mod section { - use crate::parser::tests::util::{ - comment_event, fully_consumed, name_event, newline_event, section_header as parsed_section_header, - value_done_event, value_event, value_not_done_event, whitespace_event, - }; - use crate::parser::{section, Event, ParsedSection, ParserNode}; - - #[test] - fn empty_section() { - let mut node = ParserNode::SectionHeader; - assert_eq!( - section(b"[test]", &mut node).unwrap(), - fully_consumed(( - ParsedSection { - section_header: parsed_section_header("test", None), - events: vec![] - }, - 0 - )), - ); - } - - #[test] - fn simple_section() { - let mut node = ParserNode::SectionHeader; - let section_data = br#"[hello] - a = b - c - d = "lol""#; - assert_eq!( - section(section_data, &mut node).unwrap(), - fully_consumed(( - ParsedSection { - section_header: parsed_section_header("hello", None), - events: vec![ - newline_event(), - whitespace_event(" "), - name_event("a"), - whitespace_event(" "), - Event::KeyValueSeparator, - whitespace_event(" "), - value_event("b"), - newline_event(), - whitespace_event(" "), - name_event("c"), - value_event(""), - newline_event(), - whitespace_event(" "), - name_event("d"), - whitespace_event(" "), - Event::KeyValueSeparator, - whitespace_event(" "), - value_event("\"lol\"") - ] - }, - 3 - )) - ); - } - - #[test] - fn section_with_empty_value() { - let mut node = ParserNode::SectionHeader; - let section_data = br#"[hello] - a = b - c= - d = "lol""#; - assert_eq!( - section(section_data, &mut node).unwrap(), - fully_consumed(( - ParsedSection { - section_header: parsed_section_header("hello", None), - events: vec![ - newline_event(), - whitespace_event(" "), - name_event("a"), - whitespace_event(" "), - Event::KeyValueSeparator, - whitespace_event(" "), - value_event("b"), - newline_event(), - whitespace_event(" "), - name_event("c"), - Event::KeyValueSeparator, - value_event(""), - newline_event(), - whitespace_event(" "), - name_event("d"), - whitespace_event(" "), - Event::KeyValueSeparator, - whitespace_event(" "), - value_event("\"lol\"") - ] - }, - 2 - )) - ); - } - - #[test] - fn section_single_line() { - let mut node = ParserNode::SectionHeader; - assert_eq!( - section(b"[hello] c", &mut node).unwrap(), - fully_consumed(( - ParsedSection { - section_header: parsed_section_header("hello", None), - events: vec![whitespace_event(" "), name_event("c"), value_event("")] - }, - 0 - )) - ); - } - - #[test] - fn section_very_commented() { - let mut node = ParserNode::SectionHeader; - let section_data = br#"[hello] ; commentA - a = b # commentB - ; commentC - ; commentD - c = d"#; - assert_eq!( - section(section_data, &mut node).unwrap(), - fully_consumed(( - ParsedSection { - section_header: parsed_section_header("hello", None), - events: vec![ - whitespace_event(" "), - comment_event(';', " commentA"), - newline_event(), - whitespace_event(" "), - name_event("a"), - whitespace_event(" "), - Event::KeyValueSeparator, - whitespace_event(" "), - value_event("b"), - whitespace_event(" "), - comment_event('#', " commentB"), - newline_event(), - whitespace_event(" "), - comment_event(';', " commentC"), - newline_event(), - whitespace_event(" "), - comment_event(';', " commentD"), - newline_event(), - whitespace_event(" "), - name_event("c"), - whitespace_event(" "), - Event::KeyValueSeparator, - whitespace_event(" "), - value_event("d"), - ] - }, - 4 - )) - ); - } - - #[test] - fn complex_continuation() { - let mut node = ParserNode::SectionHeader; - // This test is absolute hell. Good luck if this fails. - assert_eq!( - section(b"[section] a = 1 \"\\\"\\\na ; e \"\\\"\\\nd # \"b\t ; c", &mut node).unwrap(), - fully_consumed(( - ParsedSection { - section_header: parsed_section_header("section", None), - events: vec![ - whitespace_event(" "), - name_event("a"), - whitespace_event(" "), - Event::KeyValueSeparator, - whitespace_event(" "), - value_not_done_event(r#"1 "\""#), - newline_event(), - value_not_done_event(r#"a ; e "\""#), - newline_event(), - value_done_event("d"), - whitespace_event(" "), - comment_event('#', " \"b\t ; c"), - ] - }, - 0 - )) - ); - } - - #[test] - fn quote_split_over_two_lines() { - let mut node = ParserNode::SectionHeader; - assert_eq!( - section(b"[section \"a\"] b =\"\\\n;\";a", &mut node).unwrap(), - fully_consumed(( - ParsedSection { - section_header: parsed_section_header("section", (" ", "a")), - events: vec![ - whitespace_event(" "), - name_event("b"), - whitespace_event(" "), - Event::KeyValueSeparator, - value_not_done_event("\""), - newline_event(), - value_done_event(";\""), - comment_event(';', "a"), - ] - }, - 0 - )) - ); - } - - #[test] - fn section_handles_extranous_whitespace_before_comment() { - let mut node = ParserNode::SectionHeader; - assert_eq!( - section(b"[s]hello #world", &mut node).unwrap(), - fully_consumed(( - ParsedSection { - section_header: parsed_section_header("s", None), - events: vec![ - name_event("hello"), - whitespace_event(" "), - value_event(""), - comment_event('#', "world"), - ] - }, - 0 - )) - ); - } -} - mod parse { use crate::parser::{parse_from_bytes, parse_from_bytes_owned}; diff --git a/git-config/src/parser/types.rs b/git-config/src/parser/types.rs new file mode 100644 index 00000000000..e09970258f7 --- /dev/null +++ b/git-config/src/parser/types.rs @@ -0,0 +1,85 @@ +macro_rules! generate_case_insensitive { + ($name:ident, $cow_inner_type:ty, $comment:literal) => { + #[doc = $comment] + #[derive(Clone, Eq, Ord, Debug, Default)] + pub struct $name<'a>(pub std::borrow::Cow<'a, $cow_inner_type>); + + impl $name<'_> { + /// Coerces into an owned instance. This differs from the standard + /// [`clone`] implementation as calling clone will _not_ copy the + /// borrowed variant, while this method will. In other words: + /// + /// | Borrow type | `.clone()` | `to_owned()` | + /// | ----------- | ---------- | ------------ | + /// | Borrowed | Borrowed | Owned | + /// | Owned | Owned | Owned | + /// + /// This can be most effectively seen by the differing lifetimes + /// between the two. This method guarantees a `'static` lifetime, + /// while `clone` does not. + /// + /// [`clone`]: Self::clone + #[must_use] + pub fn to_owned(&self) -> $name<'static> { + $name(std::borrow::Cow::Owned(self.0.clone().into_owned())) + } + } + + impl PartialEq for $name<'_> { + fn eq(&self, other: &Self) -> bool { + self.0.eq_ignore_ascii_case(&other.0) + } + } + + impl std::fmt::Display for $name<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } + } + + impl PartialOrd for $name<'_> { + fn partial_cmp(&self, other: &Self) -> Option { + self.0 + .to_ascii_lowercase() + .partial_cmp(&other.0.to_ascii_lowercase()) + } + } + + impl std::hash::Hash for $name<'_> { + fn hash(&self, state: &mut H) { + self.0.to_ascii_lowercase().hash(state) + } + } + + impl<'a> From<&'a str> for $name<'a> { + fn from(s: &'a str) -> Self { + Self(std::borrow::Cow::Borrowed(s.into())) + } + } + + impl<'a> From> for $name<'a> { + fn from(s: std::borrow::Cow<'a, bstr::BStr>) -> Self { + Self(s) + } + } + + impl<'a> std::ops::Deref for $name<'a> { + type Target = $cow_inner_type; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + }; +} +generate_case_insensitive!( + SectionHeaderName, + bstr::BStr, + "Wrapper struct for section header names, since section headers are case-insensitive." +); + +generate_case_insensitive!( + Key, + bstr::BStr, + "Wrapper struct for key names, since keys are case-insensitive." +); From b05aed1cfc15a2e29d7796bad4c9a6d4019f4353 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 6 Jul 2022 16:49:49 +0800 Subject: [PATCH 042/366] fix docs (#331) --- git-config/src/file/access/low_level/read_only.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/git-config/src/file/access/low_level/read_only.rs b/git-config/src/file/access/low_level/read_only.rs index 3316e3ae0ec..2128edcdb18 100644 --- a/git-config/src/file/access/low_level/read_only.rs +++ b/git-config/src/file/access/low_level/read_only.rs @@ -8,7 +8,7 @@ impl<'a> File<'a> { /// Returns an interpreted value given a section, an optional subsection and /// key. /// - /// It's recommended to use one of the values in the [`values`] module as + /// It's recommended to use one of the values in the [`value`] module as /// the conversion is already implemented, but this function is flexible and /// will accept any type that implements [`TryFrom<&[u8]>`][`TryFrom`]. /// @@ -41,7 +41,7 @@ impl<'a> File<'a> { /// section and subsection, if the section and subsection do not exist, or /// if there was an issue converting the type into the requested variant. /// - /// [`values`]: crate::values + /// [`value`]: crate::value /// [`TryFrom`]: std::convert::TryFrom pub fn value>>( &'a self, @@ -65,7 +65,7 @@ impl<'a> File<'a> { /// Returns all interpreted values given a section, an optional subsection /// and key. /// - /// It's recommended to use one of the values in the [`values`] module as + /// It's recommended to use one of the values in the [`value`] module as /// the conversion is already implemented, but this function is flexible and /// will accept any type that implements [`TryFrom<&[u8]>`][`TryFrom`]. /// @@ -110,7 +110,7 @@ impl<'a> File<'a> { /// section and subsection, if the section and subsection do not exist, or /// if there was an issue converting the type into the requested variant. /// - /// [`values`]: crate::values + /// [`value`]: crate::value /// [`TryFrom`]: std::convert::TryFrom pub fn multi_value<'lookup, T: TryFrom>>( &'a self, From 3724850e0411f1f76e52c6c767fd8cebe8aea0f6 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 6 Jul 2022 16:58:02 +0800 Subject: [PATCH 043/366] change!: rename `parser` module to `parse` (#331) --- git-config/benches/large_config_file.rs | 2 +- .../src/file/access/low_level/mutating.rs | 2 +- .../src/file/access/low_level/read_only.rs | 4 ++-- git-config/src/file/access/raw.rs | 2 +- git-config/src/file/from_env.rs | 4 ++-- git-config/src/file/from_paths.rs | 4 ++-- git-config/src/file/impls.rs | 4 ++-- git-config/src/file/init.rs | 6 +++--- git-config/src/file/resolve_includes.rs | 2 +- git-config/src/file/section.rs | 2 +- git-config/src/file/try_from_str_tests.rs | 2 +- git-config/src/file/utils.rs | 2 +- git-config/src/file/value.rs | 2 +- git-config/src/lib.rs | 4 ++-- git-config/src/{parser => parse}/comment.rs | 2 +- git-config/src/{parser => parse}/error.rs | 2 +- git-config/src/{parser => parse}/event.rs | 2 +- git-config/src/{parser => parse}/mod.rs | 10 +++++----- git-config/src/{parser => parse}/nom/mod.rs | 8 ++++---- git-config/src/{parser => parse}/nom/tests.rs | 18 +++++++++--------- git-config/src/{parser => parse}/section.rs | 2 +- git-config/src/{parser => parse}/tests.rs | 6 +++--- git-config/src/{parser => parse}/types.rs | 0 git-config/src/types.rs | 2 +- git-config/src/value/normalize.rs | 2 +- git-config/tests/file/from_paths/mod.rs | 2 +- git-config/tests/parser/mod.rs | 2 +- 27 files changed, 50 insertions(+), 50 deletions(-) rename git-config/src/{parser => parse}/comment.rs (98%) rename git-config/src/{parser => parse}/error.rs (98%) rename git-config/src/{parser => parse}/event.rs (99%) rename git-config/src/{parser => parse}/mod.rs (97%) rename git-config/src/{parser => parse}/nom/mod.rs (99%) rename git-config/src/{parser => parse}/nom/tests.rs (96%) rename git-config/src/{parser => parse}/section.rs (98%) rename git-config/src/{parser => parse}/tests.rs (94%) rename git-config/src/{parser => parse}/types.rs (100%) diff --git a/git-config/benches/large_config_file.rs b/git-config/benches/large_config_file.rs index e90e42d05a0..36b22f6ca31 100644 --- a/git-config/benches/large_config_file.rs +++ b/git-config/benches/large_config_file.rs @@ -1,7 +1,7 @@ use std::convert::TryFrom; use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use git_config::{parser::Parser, File}; +use git_config::{parse::Parser, File}; fn git_config(c: &mut Criterion) { c.bench_function("GitConfig large config file", |b| { diff --git a/git-config/src/file/access/low_level/mutating.rs b/git-config/src/file/access/low_level/mutating.rs index 5ef9cee2bc6..ef0a65687cf 100644 --- a/git-config/src/file/access/low_level/mutating.rs +++ b/git-config/src/file/access/low_level/mutating.rs @@ -4,7 +4,7 @@ use std::borrow::Cow; use crate::{ file::{MutableSection, SectionBody}, lookup, - parser::{ParsedSectionHeader, SectionHeaderName}, + parse::{ParsedSectionHeader, SectionHeaderName}, File, }; diff --git a/git-config/src/file/access/low_level/read_only.rs b/git-config/src/file/access/low_level/read_only.rs index 2128edcdb18..57ec592f0ee 100644 --- a/git-config/src/file/access/low_level/read_only.rs +++ b/git-config/src/file/access/low_level/read_only.rs @@ -1,7 +1,7 @@ use bstr::BStr; use std::{borrow::Cow, convert::TryFrom}; -use crate::{file::SectionBody, lookup, parser::ParsedSectionHeader, File}; +use crate::{file::SectionBody, lookup, parse::ParsedSectionHeader, File}; /// Read-only low-level access methods. impl<'a> File<'a> { @@ -204,7 +204,7 @@ impl<'a> File<'a> { /// /// ```rust /// use git_config::File; - /// use git_config::parser::Key; + /// use git_config::parse::Key; /// use std::borrow::Cow; /// use std::convert::TryFrom; /// use nom::AsBytes; diff --git a/git-config/src/file/access/raw.rs b/git-config/src/file/access/raw.rs index 12007d5dd82..eca9a76edc3 100644 --- a/git-config/src/file/access/raw.rs +++ b/git-config/src/file/access/raw.rs @@ -4,7 +4,7 @@ use std::{borrow::Cow, collections::HashMap}; use crate::{ file::{EntryData, Index, MutableMultiValue, MutableSection, MutableValue, Size}, lookup, - parser::{Event, Key}, + parse::{Event, Key}, File, }; diff --git a/git-config/src/file/from_env.rs b/git-config/src/file/from_env.rs index b99fba5ad7d..93f40c02762 100644 --- a/git-config/src/file/from_env.rs +++ b/git-config/src/file/from_env.rs @@ -3,7 +3,7 @@ use std::{borrow::Cow, path::PathBuf}; use crate::{ file::{from_paths, resolve_includes}, - parser, + parse, value::path::interpolate, File, }; @@ -115,7 +115,7 @@ impl<'a> File<'a> { }; section.push( - parser::Key(Cow::Owned(BString::from(key))), + parse::Key(Cow::Owned(BString::from(key))), Cow::Owned(git_path::into_bstr(PathBuf::from(value)).into_owned()), ); } else { diff --git a/git-config/src/file/from_paths.rs b/git-config/src/file/from_paths.rs index 741c572ebd5..4dd2973fdab 100644 --- a/git-config/src/file/from_paths.rs +++ b/git-config/src/file/from_paths.rs @@ -1,11 +1,11 @@ -use crate::{parser, value::path::interpolate}; +use crate::{parse, value::path::interpolate}; /// The error returned by [`File::from_paths()`][crate::File::from_paths()] and [`File::from_env_paths()`][crate::File::from_env_paths()]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { #[error(transparent)] - ParserOrIoError(#[from] parser::ParserOrIoError<'static>), + ParserOrIoError(#[from] parse::ParserOrIoError<'static>), #[error(transparent)] Interpolate(#[from] interpolate::Error), #[error("The maximum allowed length {} of the file include chain built by following nested resolve_includes is exceeded", .max_depth)] diff --git a/git-config/src/file/impls.rs b/git-config/src/file/impls.rs index 0d51d906f2e..23f2fa21e38 100644 --- a/git-config/src/file/impls.rs +++ b/git-config/src/file/impls.rs @@ -3,7 +3,7 @@ use std::{convert::TryFrom, fmt::Display}; use crate::{ file::SectionBody, - parser::{parse_from_bytes, parse_from_str, Error, Event, Parser}, + parse::{parse_from_bytes, parse_from_str, Error, Event, Parser}, File, }; @@ -13,7 +13,7 @@ impl<'a> TryFrom<&'a str> for File<'a> { /// Convenience constructor. Attempts to parse the provided string into a /// [`File`]. See [`parse_from_str`] for more information. /// - /// [`parse_from_str`]: crate::parser::parse_from_str + /// [`parse_from_str`]: crate::parse::parse_from_str fn try_from(s: &'a str) -> Result, Self::Error> { parse_from_str(s).map(Self::from) } diff --git a/git-config/src/file/init.rs b/git-config/src/file/init.rs index ced08ad9b43..9cb5edcc66c 100644 --- a/git-config/src/file/init.rs +++ b/git-config/src/file/init.rs @@ -2,8 +2,8 @@ use std::path::Path; use crate::{ file::{from_paths, resolve_includes}, - parser, - parser::parse_from_path, + parse, + parse::parse_from_path, File, }; @@ -20,7 +20,7 @@ impl<'a> File<'a> { /// /// Returns an error if there was an IO error or if the file wasn't a valid /// git-config file. - pub fn at>(path: P) -> Result> { + pub fn at>(path: P) -> Result> { parse_from_path(path).map(Self::from) } diff --git a/git-config/src/file/resolve_includes.rs b/git-config/src/file/resolve_includes.rs index 4f1a03bb5a7..8393b00d537 100644 --- a/git-config/src/file/resolve_includes.rs +++ b/git-config/src/file/resolve_includes.rs @@ -9,7 +9,7 @@ use git_ref::Category; use crate::file::from_paths::Options; use crate::{ file::{from_paths, SectionId}, - parser::Key, + parse::Key, value, File, }; diff --git a/git-config/src/file/section.rs b/git-config/src/file/section.rs index 11bae92c9da..0c293b86646 100644 --- a/git-config/src/file/section.rs +++ b/git-config/src/file/section.rs @@ -10,7 +10,7 @@ use std::{ use crate::{ file::Index, lookup, - parser::{Event, Key}, + parse::{Event, Key}, value::{normalize, normalize_bstring}, }; diff --git a/git-config/src/file/try_from_str_tests.rs b/git-config/src/file/try_from_str_tests.rs index 8359b8dd976..b412741d4f6 100644 --- a/git-config/src/file/try_from_str_tests.rs +++ b/git-config/src/file/try_from_str_tests.rs @@ -3,7 +3,7 @@ use std::convert::TryFrom; use super::{Cow, HashMap, LookupTreeNode, SectionId}; use crate::{ file::SectionBody, - parser::{ + parse::{ tests::util::{name_event, newline_event, section_header, value_event}, Event, SectionHeaderName, }, diff --git a/git-config/src/file/utils.rs b/git-config/src/file/utils.rs index 3fc901251e0..6e6af59d26c 100644 --- a/git-config/src/file/utils.rs +++ b/git-config/src/file/utils.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use crate::{ file::{LookupTreeNode, MutableSection, SectionBody, SectionId}, lookup, - parser::{ParsedSectionHeader, SectionHeaderName}, + parse::{ParsedSectionHeader, SectionHeaderName}, File, }; diff --git a/git-config/src/file/value.rs b/git-config/src/file/value.rs index 951ccb0e2eb..e76b40b80e1 100644 --- a/git-config/src/file/value.rs +++ b/git-config/src/file/value.rs @@ -7,7 +7,7 @@ use crate::{ Index, SectionId, Size, }, lookup, - parser::{Event, Key}, + parse::{Event, Key}, value::{normalize_bstr, normalize_bstring}, }; diff --git a/git-config/src/lib.rs b/git-config/src/lib.rs index a69cf060310..501df1cfd23 100644 --- a/git-config/src/lib.rs +++ b/git-config/src/lib.rs @@ -43,7 +43,7 @@ //! [`git-config` files]: https://git-scm.com/docs/git-config#_configuration_file //! [INI file format]: https://en.wikipedia.org/wiki/INI_file //! [`File`]: crate::File -//! [`Parser`]: crate::parser::Parser +//! [`Parser`]: crate::parse::Parser //! [`value`]: crate::value //! [`nom`]: https://github.com/Geal/nom //! @@ -56,7 +56,7 @@ cfg_attr(doc, doc = ::document_features::document_features!()) pub mod file; pub mod fs; pub mod lookup; -pub mod parser; +pub mod parse; pub mod value; mod types; diff --git a/git-config/src/parser/comment.rs b/git-config/src/parse/comment.rs similarity index 98% rename from git-config/src/parser/comment.rs rename to git-config/src/parse/comment.rs index 48a2dd87ae5..47ac0f7f61e 100644 --- a/git-config/src/parser/comment.rs +++ b/git-config/src/parse/comment.rs @@ -1,4 +1,4 @@ -use crate::parser::ParsedComment; +use crate::parse::ParsedComment; use bstr::{BString, ByteVec}; use std::borrow::Cow; use std::fmt::Display; diff --git a/git-config/src/parser/error.rs b/git-config/src/parse/error.rs similarity index 98% rename from git-config/src/parser/error.rs rename to git-config/src/parse/error.rs index 78c601ff634..b1b66fc956e 100644 --- a/git-config/src/parser/error.rs +++ b/git-config/src/parse/error.rs @@ -1,4 +1,4 @@ -use crate::parser::{Error, ParserOrIoError}; +use crate::parse::{Error, ParserOrIoError}; use std::fmt::Display; /// A list of parsers that parsing can fail on. This is used for pretty-printing diff --git a/git-config/src/parser/event.rs b/git-config/src/parse/event.rs similarity index 99% rename from git-config/src/parser/event.rs rename to git-config/src/parse/event.rs index f6d3fadfe0e..01c767e3251 100644 --- a/git-config/src/parser/event.rs +++ b/git-config/src/parse/event.rs @@ -1,4 +1,4 @@ -use crate::parser::Event; +use crate::parse::Event; use bstr::BString; use std::borrow::Cow; use std::fmt::Display; diff --git a/git-config/src/parser/mod.rs b/git-config/src/parse/mod.rs similarity index 97% rename from git-config/src/parser/mod.rs rename to git-config/src/parse/mod.rs index c452491e72e..703f457d850 100644 --- a/git-config/src/parser/mod.rs +++ b/git-config/src/parse/mod.rs @@ -86,7 +86,7 @@ use std::{borrow::Cow, hash::Hash}; /// non-significant events that occur in addition to the ones you may expect: /// /// ``` -/// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str, SectionHeaderName, Key}; +/// # use git_config::parse::{Event, ParsedSectionHeader, parse_from_str, SectionHeaderName, Key}; /// # use std::borrow::Cow; /// # let section_header = ParsedSectionHeader { /// # name: SectionHeaderName(Cow::Borrowed("core".into())), @@ -125,7 +125,7 @@ use std::{borrow::Cow, hash::Hash}; /// which means that the corresponding event won't appear either: /// /// ``` -/// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str, SectionHeaderName, Key}; +/// # use git_config::parse::{Event, ParsedSectionHeader, parse_from_str, SectionHeaderName, Key}; /// # use std::borrow::Cow; /// # let section_header = ParsedSectionHeader { /// # name: SectionHeaderName(Cow::Borrowed("core".into())), @@ -159,7 +159,7 @@ use std::{borrow::Cow, hash::Hash}; /// relevant event stream emitted is thus emitted as: /// /// ``` -/// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str, SectionHeaderName, Key}; +/// # use git_config::parse::{Event, ParsedSectionHeader, parse_from_str, SectionHeaderName, Key}; /// # use std::borrow::Cow; /// # let section_header = ParsedSectionHeader { /// # name: SectionHeaderName(Cow::Borrowed("core".into())), @@ -196,7 +196,7 @@ use std::{borrow::Cow, hash::Hash}; /// split value accordingly: /// /// ``` -/// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str, SectionHeaderName, Key}; +/// # use git_config::parse::{Event, ParsedSectionHeader, parse_from_str, SectionHeaderName, Key}; /// # use std::borrow::Cow; /// # let section_header = ParsedSectionHeader { /// # name: SectionHeaderName(Cow::Borrowed("some-section".into())), @@ -227,7 +227,7 @@ pub struct Parser<'a> { } mod state { - use crate::parser::{parse_from_bytes, parse_from_str, Error, Event, ParsedSection, Parser}; + use crate::parse::{parse_from_bytes, parse_from_str, Error, Event, ParsedSection, Parser}; use std::convert::TryFrom; impl<'a> Parser<'a> { diff --git a/git-config/src/parser/nom/mod.rs b/git-config/src/parse/nom/mod.rs similarity index 99% rename from git-config/src/parser/nom/mod.rs rename to git-config/src/parse/nom/mod.rs index 6eb637cfd51..b3dd4af2a56 100644 --- a/git-config/src/parser/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -1,11 +1,11 @@ -use crate::parser::{ +use crate::parse::{ Error, Event, Key, ParsedComment, ParsedSection, ParsedSectionHeader, Parser, ParserOrIoError, SectionHeaderName, }; use bstr::{BStr, BString, ByteSlice, ByteVec}; use std::borrow::Cow; use std::io::Read; -use crate::parser::error::ParserNode; +use crate::parse::error::ParserNode; use nom::{ branch::alt, bytes::complete::{tag, take_till, take_while}, @@ -83,7 +83,7 @@ pub fn parse_from_bytes(input: &[u8]) -> Result, Error<'_>> { // if one of its children succeed. However, all of it's children are // guaranteed to consume something if they succeed, so the Ok(i) == i case // can never occur. - .expect("many0(alt(...)) panicked. Likely a bug in one of the children parser."); + .expect("many0(alt(...)) panicked. Likely a bug in one of the children parsers."); if i.is_empty() { return Ok(Parser { @@ -155,7 +155,7 @@ pub fn parse_from_bytes_owned(input: &[u8]) -> Result, Error<'st // if one of its children succeed. However, all of it's children are // guaranteed to consume something if they succeed, so the Ok(i) == i case // can never occur. - .expect("many0(alt(...)) panicked. Likely a bug in one of the children parser."); + .expect("many0(alt(...)) panicked. Likely a bug in one of the children parsers."); let frontmatter = frontmatter.iter().map(Event::to_owned).collect(); if i.is_empty() { return Ok(Parser { diff --git a/git-config/src/parser/nom/tests.rs b/git-config/src/parse/nom/tests.rs similarity index 96% rename from git-config/src/parser/nom/tests.rs rename to git-config/src/parse/nom/tests.rs index 05bc73b4a8a..9656887bd09 100644 --- a/git-config/src/parser/nom/tests.rs +++ b/git-config/src/parse/nom/tests.rs @@ -2,7 +2,7 @@ use super::*; mod section_headers { use super::section_header; - use crate::parser::tests::util::{fully_consumed, section_header as parsed_section_header}; + use crate::parse::tests::util::{fully_consumed, section_header as parsed_section_header}; #[test] fn no_subsection() { @@ -73,7 +73,7 @@ mod section_headers { mod config_name { use super::config_name; - use crate::parser::tests::util::fully_consumed; + use crate::parse::tests::util::fully_consumed; #[test] fn just_name() { @@ -94,11 +94,11 @@ mod config_name { mod section { use super::section; - use crate::parser::tests::util::{ + use crate::parse::tests::util::{ comment_event, fully_consumed, name_event, newline_event, section_header as parsed_section_header, value_done_event, value_event, value_not_done_event, whitespace_event, }; - use crate::parser::{error::ParserNode, Event, ParsedSection}; + use crate::parse::{error::ParserNode, Event, ParsedSection}; #[test] fn empty_section() { @@ -328,7 +328,7 @@ mod section { mod value_continuation { use super::value_impl; - use crate::parser::tests::util::{newline_event, value_done_event, value_not_done_event}; + use crate::parse::tests::util::{newline_event, value_done_event, value_not_done_event}; #[test] fn simple_continuation() { @@ -392,7 +392,7 @@ mod value_continuation { mod value_no_continuation { use super::value_impl; - use crate::parser::tests::util::value_event; + use crate::parse::tests::util::value_event; #[test] fn no_comment() { @@ -481,8 +481,8 @@ mod value_no_continuation { mod section_body { use super::section_body; - use crate::parser::tests::util::{name_event, value_event, whitespace_event}; - use crate::parser::{error::ParserNode, Event}; + use crate::parse::tests::util::{name_event, value_event, whitespace_event}; + use crate::parse::{error::ParserNode, Event}; #[test] fn whitespace_is_not_ambigious() { @@ -515,7 +515,7 @@ mod section_body { mod comment { use super::comment; - use crate::parser::tests::util::{comment as parsed_comment, fully_consumed}; + use crate::parse::tests::util::{comment as parsed_comment, fully_consumed}; #[test] fn semicolon() { diff --git a/git-config/src/parser/section.rs b/git-config/src/parse/section.rs similarity index 98% rename from git-config/src/parser/section.rs rename to git-config/src/parse/section.rs index a74216c9721..79bdb3f9af5 100644 --- a/git-config/src/parser/section.rs +++ b/git-config/src/parse/section.rs @@ -1,4 +1,4 @@ -use crate::parser::{Event, ParsedSection, ParsedSectionHeader}; +use crate::parse::{Event, ParsedSection, ParsedSectionHeader}; use bstr::BString; use std::borrow::Cow; use std::fmt::Display; diff --git a/git-config/src/parser/tests.rs b/git-config/src/parse/tests.rs similarity index 94% rename from git-config/src/parser/tests.rs rename to git-config/src/parse/tests.rs index 9bb44d056cc..91e40f46c44 100644 --- a/git-config/src/parser/tests.rs +++ b/git-config/src/parse/tests.rs @@ -1,5 +1,5 @@ mod parse { - use crate::parser::{parse_from_bytes, parse_from_bytes_owned}; + use crate::parse::{parse_from_bytes, parse_from_bytes_owned}; #[test] fn parser_skips_bom() { @@ -25,7 +25,7 @@ mod parse { #[cfg(test)] mod error { - use crate::parser::parse_from_str; + use crate::parse::parse_from_str; #[test] fn line_no_is_one_indexed() { @@ -52,7 +52,7 @@ pub(crate) mod util { use std::borrow::Cow; - use crate::parser::{Event, Key, ParsedComment, ParsedSectionHeader}; + use crate::parse::{Event, Key, ParsedComment, ParsedSectionHeader}; pub fn section_header( name: &str, diff --git a/git-config/src/parser/types.rs b/git-config/src/parse/types.rs similarity index 100% rename from git-config/src/parser/types.rs rename to git-config/src/parse/types.rs diff --git a/git-config/src/types.rs b/git-config/src/types.rs index 0bfc19614de..5d2377d622a 100644 --- a/git-config/src/types.rs +++ b/git-config/src/types.rs @@ -2,7 +2,7 @@ use std::collections::{HashMap, VecDeque}; use crate::{ file::{LookupTreeNode, SectionBody, SectionId}, - parser::{ParsedSectionHeader, SectionHeaderName}, + parse::{ParsedSectionHeader, SectionHeaderName}, }; /// High level `git-config` reader and writer. diff --git a/git-config/src/value/normalize.rs b/git-config/src/value/normalize.rs index e8b0e45875f..a89bfa41efd 100644 --- a/git-config/src/value/normalize.rs +++ b/git-config/src/value/normalize.rs @@ -57,7 +57,7 @@ use bstr::{BStr, BString}; /// assert_eq!(normalize_bstr(r#"hello "world\"""#), Cow::::Owned(BString::from(r#"hello world""#))); /// ``` /// -/// [`parser`]: crate::parser::Parser +/// [`parser`]: crate::parse::Parser #[must_use] pub fn normalize(input: Cow<'_, BStr>) -> Cow<'_, BStr> { let size = input.len(); diff --git a/git-config/tests/file/from_paths/mod.rs b/git-config/tests/file/from_paths/mod.rs index 52b05946128..1f6117fe000 100644 --- a/git-config/tests/file/from_paths/mod.rs +++ b/git-config/tests/file/from_paths/mod.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, fs, io}; -use git_config::{file::from_paths::Error, parser::ParserOrIoError, File}; +use git_config::{file::from_paths::Error, parse::ParserOrIoError, File}; use tempfile::tempdir; use crate::file::cow_str; diff --git a/git-config/tests/parser/mod.rs b/git-config/tests/parser/mod.rs index 23c5425bf36..8b1f5a82af6 100644 --- a/git-config/tests/parser/mod.rs +++ b/git-config/tests/parser/mod.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use git_config::parser::{parse_from_str, Event, Key, ParsedSectionHeader, SectionHeaderName}; +use git_config::parse::{parse_from_str, Event, Key, ParsedSectionHeader, SectionHeaderName}; pub fn section_header_event(name: &str, subsection: impl Into>) -> Event<'_> { Event::SectionHeader(section_header(name, subsection)) From 60af4c9ecb1b99f21df0e8facc33e5f6fc70c424 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 6 Jul 2022 17:29:27 +0800 Subject: [PATCH 044/366] change!: rename `parse::Parser` to `parse::State`. (#331) Furthermore, make `State` the entry point for all parsing, removing all free-standing functions that returned a `State`. --- git-config/benches/large_config_file.rs | 4 +- git-config/src/file/impls.rs | 21 ++-- git-config/src/file/init.rs | 6 +- git-config/src/lib.rs | 4 +- git-config/src/parse/mod.rs | 97 ++-------------- git-config/src/parse/nom/mod.rs | 53 ++------- git-config/src/parse/state.rs | 143 ++++++++++++++++++++++++ git-config/src/parse/tests.rs | 18 +-- git-config/src/value/normalize.rs | 4 +- git-config/tests/parser/mod.rs | 18 +-- 10 files changed, 196 insertions(+), 172 deletions(-) create mode 100644 git-config/src/parse/state.rs diff --git a/git-config/benches/large_config_file.rs b/git-config/benches/large_config_file.rs index 36b22f6ca31..192a4213e98 100644 --- a/git-config/benches/large_config_file.rs +++ b/git-config/benches/large_config_file.rs @@ -1,7 +1,7 @@ use std::convert::TryFrom; use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use git_config::{parse::Parser, File}; +use git_config::{parse::State, File}; fn git_config(c: &mut Criterion) { c.bench_function("GitConfig large config file", |b| { @@ -11,7 +11,7 @@ fn git_config(c: &mut Criterion) { fn parser(c: &mut Criterion) { c.bench_function("Parser large config file", |b| { - b.iter(|| Parser::try_from(black_box(CONFIG_FILE)).unwrap()) + b.iter(|| State::try_from(black_box(CONFIG_FILE)).unwrap()) }); } diff --git a/git-config/src/file/impls.rs b/git-config/src/file/impls.rs index 23f2fa21e38..747ad6b24d2 100644 --- a/git-config/src/file/impls.rs +++ b/git-config/src/file/impls.rs @@ -3,7 +3,8 @@ use std::{convert::TryFrom, fmt::Display}; use crate::{ file::SectionBody, - parse::{parse_from_bytes, parse_from_str, Error, Event, Parser}, + parse, + parse::{Error, Event, State}, File, }; @@ -11,11 +12,9 @@ impl<'a> TryFrom<&'a str> for File<'a> { type Error = Error<'a>; /// Convenience constructor. Attempts to parse the provided string into a - /// [`File`]. See [`parse_from_str`] for more information. - /// - /// [`parse_from_str`]: crate::parse::parse_from_str + /// [`File`]. See [`State::from_str()`] for more information. fn try_from(s: &'a str) -> Result, Self::Error> { - parse_from_str(s).map(Self::from) + parse::State::from_str(s).map(Self::from) } } @@ -27,7 +26,7 @@ impl<'a> TryFrom<&'a [u8]> for File<'a> { /// /// [`parse_from_bytes`]: crate::parser::parse_from_bytes fn try_from(value: &'a [u8]) -> Result, Self::Error> { - parse_from_bytes(value).map(File::from) + parse::State::from_bytes(value).map(File::from) } } @@ -35,16 +34,14 @@ impl<'a> TryFrom<&'a BString> for File<'a> { type Error = Error<'a>; /// Convenience constructor. Attempts to parse the provided byte string into - //// a [`File`]. See [`parse_from_bytes`] for more information. - /// - /// [`parse_from_bytes`]: crate::parser::parse_from_bytes + //// a [`File`]. See [`State::from_bytes()`] for more information. fn try_from(value: &'a BString) -> Result, Self::Error> { - parse_from_bytes(value.as_ref()).map(File::from) + parse::State::from_bytes(value.as_ref()).map(File::from) } } -impl<'a> From> for File<'a> { - fn from(parser: Parser<'a>) -> Self { +impl<'a> From> for File<'a> { + fn from(parser: State<'a>) -> Self { let mut new_self = Self::default(); // Current section that we're building diff --git a/git-config/src/file/init.rs b/git-config/src/file/init.rs index 9cb5edcc66c..d1a38a5dacd 100644 --- a/git-config/src/file/init.rs +++ b/git-config/src/file/init.rs @@ -2,9 +2,7 @@ use std::path::Path; use crate::{ file::{from_paths, resolve_includes}, - parse, - parse::parse_from_path, - File, + parse, File, }; impl<'a> File<'a> { @@ -21,7 +19,7 @@ impl<'a> File<'a> { /// Returns an error if there was an IO error or if the file wasn't a valid /// git-config file. pub fn at>(path: P) -> Result> { - parse_from_path(path).map(Self::from) + parse::State::from_path(path).map(Self::from) } /// Constructs a `git-config` file from the provided paths in the order provided. diff --git a/git-config/src/lib.rs b/git-config/src/lib.rs index 501df1cfd23..1d587bb320a 100644 --- a/git-config/src/lib.rs +++ b/git-config/src/lib.rs @@ -15,7 +15,7 @@ //! | Offering | Description | Zero-copy? | //! | ------------- | --------------------------------------------------- | ----------------- | //! | [`File`] | Accelerated wrapper for reading and writing values. | On some reads[^1] | -//! | [`Parser`] | Syntactic event emitter for `git-config` files. | Yes | +//! | [`parse::State`] | Syntactic events for `git-config` files. | Yes | //! | [`value`] | Wrappers for `git-config` value types. | Yes | //! //! This crate also exposes efficient value normalization which unescapes @@ -43,7 +43,7 @@ //! [`git-config` files]: https://git-scm.com/docs/git-config#_configuration_file //! [INI file format]: https://en.wikipedia.org/wiki/INI_file //! [`File`]: crate::File -//! [`Parser`]: crate::parse::Parser +//! [`parse::State`]: crate::parse::State //! [`value`]: crate::value //! [`nom`]: https://github.com/Geal/nom //! diff --git a/git-config/src/parse/mod.rs b/git-config/src/parse/mod.rs index 703f457d850..71732b6d483 100644 --- a/git-config/src/parse/mod.rs +++ b/git-config/src/parse/mod.rs @@ -3,8 +3,8 @@ //! explicit reason to work with events instead. //! //! The general workflow for interacting with this is to use one of the -//! `parse_from_*` function variants. These will return a [`Parser`] on success, -//! which can be converted into an [`Event`] iterator. The [`Parser`] also has +//! `parse_from_*` function variants. These will return a [`State`] on success, +//! which can be converted into an [`Event`] iterator. The [`State`] also has //! additional methods for accessing leading comments or events by section. //! //! [`File`]: crate::File @@ -86,7 +86,7 @@ use std::{borrow::Cow, hash::Hash}; /// non-significant events that occur in addition to the ones you may expect: /// /// ``` -/// # use git_config::parse::{Event, ParsedSectionHeader, parse_from_str, SectionHeaderName, Key}; +/// # use git_config::parse::{Event, ParsedSectionHeader, State, SectionHeaderName, Key}; /// # use std::borrow::Cow; /// # let section_header = ParsedSectionHeader { /// # name: SectionHeaderName(Cow::Borrowed("core".into())), @@ -94,7 +94,7 @@ use std::{borrow::Cow, hash::Hash}; /// # subsection_name: None, /// # }; /// # let section_data = "[core]\n autocrlf = input"; -/// # assert_eq!(parse_from_str(section_data).unwrap().into_vec(), vec![ +/// # assert_eq!(State::from_str(section_data).unwrap().into_vec(), vec![ /// Event::SectionHeader(section_header), /// Event::Newline(Cow::Borrowed("\n".into())), /// Event::Whitespace(Cow::Borrowed(" ".into())), @@ -125,7 +125,7 @@ use std::{borrow::Cow, hash::Hash}; /// which means that the corresponding event won't appear either: /// /// ``` -/// # use git_config::parse::{Event, ParsedSectionHeader, parse_from_str, SectionHeaderName, Key}; +/// # use git_config::parse::{Event, ParsedSectionHeader, State, SectionHeaderName, Key}; /// # use std::borrow::Cow; /// # let section_header = ParsedSectionHeader { /// # name: SectionHeaderName(Cow::Borrowed("core".into())), @@ -133,7 +133,7 @@ use std::{borrow::Cow, hash::Hash}; /// # subsection_name: None, /// # }; /// # let section_data = "[core]\n autocrlf"; -/// # assert_eq!(parse_from_str(section_data).unwrap().into_vec(), vec![ +/// # assert_eq!(State::from_str(section_data).unwrap().into_vec(), vec![ /// Event::SectionHeader(section_header), /// Event::Newline(Cow::Borrowed("\n".into())), /// Event::Whitespace(Cow::Borrowed(" ".into())), @@ -159,7 +159,7 @@ use std::{borrow::Cow, hash::Hash}; /// relevant event stream emitted is thus emitted as: /// /// ``` -/// # use git_config::parse::{Event, ParsedSectionHeader, parse_from_str, SectionHeaderName, Key}; +/// # use git_config::parse::{Event, ParsedSectionHeader, State, SectionHeaderName, Key}; /// # use std::borrow::Cow; /// # let section_header = ParsedSectionHeader { /// # name: SectionHeaderName(Cow::Borrowed("core".into())), @@ -167,7 +167,7 @@ use std::{borrow::Cow, hash::Hash}; /// # subsection_name: None, /// # }; /// # let section_data = "[core]\nautocrlf=true\"\"\nfilemode=fa\"lse\""; -/// # assert_eq!(parse_from_str(section_data).unwrap().into_vec(), vec![ +/// # assert_eq!(State::from_str(section_data).unwrap().into_vec(), vec![ /// Event::SectionHeader(section_header), /// Event::Newline(Cow::Borrowed("\n".into())), /// Event::Key(Key(Cow::Borrowed("autocrlf".into()))), @@ -196,7 +196,7 @@ use std::{borrow::Cow, hash::Hash}; /// split value accordingly: /// /// ``` -/// # use git_config::parse::{Event, ParsedSectionHeader, parse_from_str, SectionHeaderName, Key}; +/// # use git_config::parse::{Event, ParsedSectionHeader, State, SectionHeaderName, Key}; /// # use std::borrow::Cow; /// # let section_header = ParsedSectionHeader { /// # name: SectionHeaderName(Cow::Borrowed("some-section".into())), @@ -204,7 +204,7 @@ use std::{borrow::Cow, hash::Hash}; /// # subsection_name: None, /// # }; /// # let section_data = "[some-section]\nfile=a\\\n c"; -/// # assert_eq!(parse_from_str(section_data).unwrap().into_vec(), vec![ +/// # assert_eq!(State::from_str(section_data).unwrap().into_vec(), vec![ /// Event::SectionHeader(section_header), /// Event::Newline(Cow::Borrowed("\n".into())), /// Event::Key(Key(Cow::Borrowed("file".into()))), @@ -221,84 +221,12 @@ use std::{borrow::Cow, hash::Hash}; /// [`FromStr`]: std::str::FromStr /// [`From<&'_ str>`]: std::convert::From #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] -pub struct Parser<'a> { +pub struct State<'a> { frontmatter: Vec>, sections: Vec>, } -mod state { - use crate::parse::{parse_from_bytes, parse_from_str, Error, Event, ParsedSection, Parser}; - use std::convert::TryFrom; - - impl<'a> Parser<'a> { - /// Returns the leading events (any comments, whitespace, or newlines before - /// a section) from the parser. Consider [`Parser::take_frontmatter`] if - /// you need an owned copy only once. If that function was called, then this - /// will always return an empty slice. - #[must_use] - pub fn frontmatter(&self) -> &[Event<'a>] { - &self.frontmatter - } - - /// Takes the leading events (any comments, whitespace, or newlines before - /// a section) from the parser. Subsequent calls will return an empty vec. - /// Consider [`Parser::frontmatter`] if you only need a reference to the - /// frontmatter - pub fn take_frontmatter(&mut self) -> Vec> { - std::mem::take(&mut self.frontmatter) - } - - /// Returns the parsed sections from the parser. Consider - /// [`Parser::take_sections`] if you need an owned copy only once. If that - /// function was called, then this will always return an empty slice. - #[must_use] - pub fn sections(&self) -> &[ParsedSection<'a>] { - &self.sections - } - - /// Takes the parsed sections from the parser. Subsequent calls will return - /// an empty vec. Consider [`Parser::sections`] if you only need a reference - /// to the comments. - pub fn take_sections(&mut self) -> Vec> { - let mut to_return = vec![]; - std::mem::swap(&mut self.sections, &mut to_return); - to_return - } - - /// Consumes the parser to produce a Vec of Events. - #[must_use] - pub fn into_vec(self) -> Vec> { - self.into_iter().collect() - } - - /// Consumes the parser to produce an iterator of Events. - #[must_use = "iterators are lazy and do nothing unless consumed"] - #[allow(clippy::should_implement_trait)] - pub fn into_iter(self) -> impl Iterator> + std::iter::FusedIterator { - self.frontmatter - .into_iter() - .chain(self.sections.into_iter().flat_map(|section| { - std::iter::once(Event::SectionHeader(section.section_header)).chain(section.events) - })) - } - } - - impl<'a> TryFrom<&'a str> for Parser<'a> { - type Error = Error<'a>; - - fn try_from(value: &'a str) -> Result { - parse_from_str(value) - } - } - - impl<'a> TryFrom<&'a [u8]> for Parser<'a> { - type Error = Error<'a>; - - fn try_from(value: &'a [u8]) -> Result { - parse_from_bytes(value) - } - } -} +mod state; /// Syntactic events that occurs in the config. Despite all these variants /// holding a [`Cow`] instead over a simple reference, the parser will only emit @@ -417,7 +345,6 @@ pub enum ParserOrIoError<'a> { mod error; mod nom; -pub use self::nom::{parse_from_bytes, parse_from_bytes_owned, parse_from_path, parse_from_str}; #[cfg(test)] pub(crate) mod tests; diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index b3dd4af2a56..14701efb84b 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -1,9 +1,6 @@ -use crate::parse::{ - Error, Event, Key, ParsedComment, ParsedSection, ParsedSectionHeader, Parser, ParserOrIoError, SectionHeaderName, -}; +use crate::parse::{Error, Event, Key, ParsedComment, ParsedSection, ParsedSectionHeader, SectionHeaderName, State}; use bstr::{BStr, BString, ByteSlice, ByteVec}; use std::borrow::Cow; -use std::io::Read; use crate::parse::error::ParserNode; use nom::{ @@ -20,42 +17,6 @@ use nom::{ IResult, }; -/// Parses a git config located at the provided path. On success, returns a -/// [`Parser`] that provides methods to accessing leading comments and sections -/// of a `git-config` file and can be converted into an iterator of [`Event`] -/// for higher level processing. -/// -/// Note that since we accept a path rather than a reference to the actual -/// bytes, this function is _not_ zero-copy, as the Parser must own (and thus -/// copy) the bytes that it reads from. Consider one of the other variants if -/// performance is a concern. -/// -/// # Errors -/// -/// Returns an error if there was an IO error or the read file is not a valid -/// `git-config` This generally is due to either invalid names or if there's -/// extraneous data succeeding valid `git-config` data. -pub fn parse_from_path>(path: P) -> Result, ParserOrIoError<'static>> { - let mut bytes = vec![]; - let mut file = std::fs::File::open(path)?; - file.read_to_end(&mut bytes)?; - parse_from_bytes_owned(&bytes).map_err(ParserOrIoError::Parser) -} - -/// Attempt to zero-copy parse the provided `&str`. On success, returns a -/// [`Parser`] that provides methods to accessing leading comments and sections -/// of a `git-config` file and can be converted into an iterator of [`Event`] -/// for higher level processing. -/// -/// # Errors -/// -/// Returns an error if the string provided is not a valid `git-config`. -/// This generally is due to either invalid names or if there's extraneous -/// data succeeding valid `git-config` data. -pub fn parse_from_str(input: &str) -> Result, Error<'_>> { - parse_from_bytes(input.as_bytes()) -} - /// Attempt to zero-copy parse the provided bytes. On success, returns a /// [`Parser`] that provides methods to accessing leading comments and sections /// of a `git-config` file and can be converted into an iterator of [`Event`] @@ -67,7 +28,7 @@ pub fn parse_from_str(input: &str) -> Result, Error<'_>> { /// This generally is due to either invalid names or if there's extraneous /// data succeeding valid `git-config` data. #[allow(clippy::shadow_unrelated)] -pub fn parse_from_bytes(input: &[u8]) -> Result, Error<'_>> { +pub fn from_bytes(input: &[u8]) -> Result, Error<'_>> { let bom = unicode_bom::Bom::from(input); let mut newlines = 0; let (i, frontmatter) = many0(alt(( @@ -86,7 +47,7 @@ pub fn parse_from_bytes(input: &[u8]) -> Result, Error<'_>> { .expect("many0(alt(...)) panicked. Likely a bug in one of the children parsers."); if i.is_empty() { - return Ok(Parser { + return Ok(State { frontmatter, sections: vec![], }); @@ -119,7 +80,7 @@ pub fn parse_from_bytes(input: &[u8]) -> Result, Error<'_>> { }); } - Ok(Parser { frontmatter, sections }) + Ok(State { frontmatter, sections }) } /// Parses the provided bytes, returning an [`Parser`] that contains allocated @@ -134,7 +95,7 @@ pub fn parse_from_bytes(input: &[u8]) -> Result, Error<'_>> { /// This generally is due to either invalid names or if there's extraneous /// data succeeding valid `git-config` data. #[allow(clippy::shadow_unrelated)] -pub fn parse_from_bytes_owned(input: &[u8]) -> Result, Error<'static>> { +pub fn from_bytes_owned(input: &[u8]) -> Result, Error<'static>> { // FIXME: This is duplication is necessary until comment, take_spaces, and take_newlines // accept cows instead, since we don't want to unnecessarily copy the frontmatter // events in a hypothetical parse_from_cow function. @@ -158,7 +119,7 @@ pub fn parse_from_bytes_owned(input: &[u8]) -> Result, Error<'st .expect("many0(alt(...)) panicked. Likely a bug in one of the children parsers."); let frontmatter = frontmatter.iter().map(Event::to_owned).collect(); if i.is_empty() { - return Ok(Parser { + return Ok(State { frontmatter, sections: vec![], }); @@ -191,7 +152,7 @@ pub fn parse_from_bytes_owned(input: &[u8]) -> Result, Error<'st }); } - Ok(Parser { frontmatter, sections }) + Ok(State { frontmatter, sections }) } fn comment(i: &[u8]) -> IResult<&[u8], ParsedComment<'_>> { diff --git a/git-config/src/parse/state.rs b/git-config/src/parse/state.rs new file mode 100644 index 00000000000..5d9d7cb3254 --- /dev/null +++ b/git-config/src/parse/state.rs @@ -0,0 +1,143 @@ +use crate::parse::{Error, Event, ParsedSection, ParserOrIoError, State}; +use std::convert::TryFrom; +use std::io::Read; + +impl State<'static> { + /// Parses a git config located at the provided path. On success, returns a + /// [`State`] that provides methods to accessing leading comments and sections + /// of a `git-config` file and can be converted into an iterator of [`Event`] + /// for higher level processing. + /// + /// Note that since we accept a path rather than a reference to the actual + /// bytes, this function is _not_ zero-copy, as the Parser must own (and thus + /// copy) the bytes that it reads from. Consider one of the other variants if + /// performance is a concern. + /// + /// # Errors + /// + /// Returns an error if there was an IO error or the read file is not a valid + /// `git-config` This generally is due to either invalid names or if there's + /// extraneous data succeeding valid `git-config` data. + pub fn from_path>(path: P) -> Result, ParserOrIoError<'static>> { + let mut bytes = vec![]; + let mut file = std::fs::File::open(path)?; + file.read_to_end(&mut bytes)?; + crate::parse::nom::from_bytes_owned(&bytes).map_err(ParserOrIoError::Parser) + } + + /// Parses the provided bytes, returning an [`State`] that contains allocated + /// and owned events. This is similar to [`State::from_bytes()`], but performance + /// is degraded as it requires allocation for every event. However, this permits + /// the reference bytes to be dropped, allowing the parser to be passed around + /// without lifetime worries. + /// + /// # Errors + /// + /// Returns an error if the string provided is not a valid `git-config`. + /// This generally is due to either invalid names or if there's extraneous + /// data succeeding valid `git-config` data. + #[allow(clippy::shadow_unrelated)] + pub fn from_bytes_owned(input: &[u8]) -> Result, Error<'static>> { + crate::parse::nom::from_bytes_owned(input) + } +} + +impl<'a> State<'a> { + /// Attempt to zero-copy parse the provided `&str`. On success, returns a + /// [`State`] that provides methods to accessing leading comments and sections + /// of a `git-config` file and can be converted into an iterator of [`Event`] + /// for higher level processing. + /// + /// # Errors + /// + /// Returns an error if the string provided is not a valid `git-config`. + /// This generally is due to either invalid names or if there's extraneous + /// data succeeding valid `git-config` data. + pub fn from_str(input: &'a str) -> Result, Error<'a>> { + crate::parse::nom::from_bytes(input.as_bytes()) + } + + /// Attempt to zero-copy parse the provided bytes. On success, returns a + /// [`State`] that provides methods to accessing leading comments and sections + /// of a `git-config` file and can be converted into an iterator of [`Event`] + /// for higher level processing. + /// + /// # Errors + /// + /// Returns an error if the string provided is not a valid `git-config`. + /// This generally is due to either invalid names or if there's extraneous + /// data succeeding valid `git-config` data. + #[allow(clippy::shadow_unrelated)] + pub fn from_bytes(input: &'a [u8]) -> Result, Error<'a>> { + crate::parse::nom::from_bytes(input) + } +} + +impl<'a> State<'a> { + /// Returns the leading events (any comments, whitespace, or newlines before + /// a section) from the parser. Consider [`State::take_frontmatter`] if + /// you need an owned copy only once. If that function was called, then this + /// will always return an empty slice. + #[must_use] + pub fn frontmatter(&self) -> &[Event<'a>] { + &self.frontmatter + } + + /// Takes the leading events (any comments, whitespace, or newlines before + /// a section) from the parser. Subsequent calls will return an empty vec. + /// Consider [`State::frontmatter`] if you only need a reference to the + /// frontmatter + pub fn take_frontmatter(&mut self) -> Vec> { + std::mem::take(&mut self.frontmatter) + } + + /// Returns the parsed sections from the parser. Consider + /// [`State::take_sections`] if you need an owned copy only once. If that + /// function was called, then this will always return an empty slice. + #[must_use] + pub fn sections(&self) -> &[ParsedSection<'a>] { + &self.sections + } + + /// Takes the parsed sections from the parser. Subsequent calls will return + /// an empty vec. Consider [`State::sections`] if you only need a reference + /// to the comments. + pub fn take_sections(&mut self) -> Vec> { + let mut to_return = vec![]; + std::mem::swap(&mut self.sections, &mut to_return); + to_return + } + + /// Consumes the parser to produce a Vec of Events. + #[must_use] + pub fn into_vec(self) -> Vec> { + self.into_iter().collect() + } + + /// Consumes the parser to produce an iterator of Events. + #[must_use = "iterators are lazy and do nothing unless consumed"] + #[allow(clippy::should_implement_trait)] + pub fn into_iter(self) -> impl Iterator> + std::iter::FusedIterator { + self.frontmatter.into_iter().chain( + self.sections.into_iter().flat_map(|section| { + std::iter::once(Event::SectionHeader(section.section_header)).chain(section.events) + }), + ) + } +} + +impl<'a> TryFrom<&'a str> for State<'a> { + type Error = Error<'a>; + + fn try_from(value: &'a str) -> Result { + Self::from_str(value) + } +} + +impl<'a> TryFrom<&'a [u8]> for State<'a> { + type Error = Error<'a>; + + fn try_from(value: &'a [u8]) -> Result { + crate::parse::nom::from_bytes(value) + } +} diff --git a/git-config/src/parse/tests.rs b/git-config/src/parse/tests.rs index 91e40f46c44..4ba03c79853 100644 --- a/git-config/src/parse/tests.rs +++ b/git-config/src/parse/tests.rs @@ -1,5 +1,5 @@ mod parse { - use crate::parse::{parse_from_bytes, parse_from_bytes_owned}; + use crate::parse::State; #[test] fn parser_skips_bom() { @@ -13,34 +13,34 @@ mod parse { "; assert_eq!( - parse_from_bytes(bytes), - parse_from_bytes(bytes_with_gb18030_bom.as_bytes()) + State::from_bytes(bytes), + State::from_bytes(bytes_with_gb18030_bom.as_bytes()) ); assert_eq!( - parse_from_bytes_owned(bytes), - parse_from_bytes_owned(bytes_with_gb18030_bom.as_bytes()) + State::from_bytes_owned(bytes), + State::from_bytes_owned(bytes_with_gb18030_bom.as_bytes()) ); } } #[cfg(test)] mod error { - use crate::parse::parse_from_str; + use crate::parse::State; #[test] fn line_no_is_one_indexed() { - assert_eq!(parse_from_str("[hello").unwrap_err().line_number(), 1); + assert_eq!(State::from_str("[hello").unwrap_err().line_number(), 1); } #[test] fn remaining_data_contains_bad_tokens() { - assert_eq!(parse_from_str("[hello").unwrap_err().remaining_data(), b"[hello"); + assert_eq!(State::from_str("[hello").unwrap_err().remaining_data(), b"[hello"); } #[test] fn to_string_truncates_extra_values() { assert_eq!( - parse_from_str("[1234567890").unwrap_err().to_string(), + State::from_str("[1234567890").unwrap_err().to_string(), "Got an unexpected token on line 1 while trying to parse a section header: '[123456789' ... (1 characters omitted)" ); } diff --git a/git-config/src/value/normalize.rs b/git-config/src/value/normalize.rs index a89bfa41efd..34348770426 100644 --- a/git-config/src/value/normalize.rs +++ b/git-config/src/value/normalize.rs @@ -11,7 +11,7 @@ use bstr::{BStr, BString}; /// the value. /// /// This is the function used to normalize raw values from higher level -/// abstractions over the [`parser`] implementation. Generally speaking these +/// abstractions. Generally speaking these /// high level abstractions will handle normalization for you, and you do not /// need to call this yourself. However, if you're directly handling events /// from the parser, you may want to use this to help with value interpretation. @@ -56,8 +56,6 @@ use bstr::{BStr, BString}; /// # use git_config::value::normalize_bstr; /// assert_eq!(normalize_bstr(r#"hello "world\"""#), Cow::::Owned(BString::from(r#"hello world""#))); /// ``` -/// -/// [`parser`]: crate::parse::Parser #[must_use] pub fn normalize(input: Cow<'_, BStr>) -> Cow<'_, BStr> { let size = input.len(); diff --git a/git-config/tests/parser/mod.rs b/git-config/tests/parser/mod.rs index 8b1f5a82af6..6f1d1f5cb6a 100644 --- a/git-config/tests/parser/mod.rs +++ b/git-config/tests/parser/mod.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use git_config::parse::{parse_from_str, Event, Key, ParsedSectionHeader, SectionHeaderName}; +use git_config::parse::{Event, Key, ParsedSectionHeader, SectionHeaderName, State}; pub fn section_header_event(name: &str, subsection: impl Into>) -> Event<'_> { Event::SectionHeader(section_header(name, subsection)) @@ -74,7 +74,7 @@ fn personal_config() { defaultBranch = master"#; assert_eq!( - parse_from_str(config) + State::from_str(config) .unwrap() .into_vec(), vec![ @@ -189,13 +189,13 @@ fn personal_config() { #[test] fn parse_empty() { - assert_eq!(parse_from_str("").unwrap().into_vec(), vec![]); + assert_eq!(State::from_str("").unwrap().into_vec(), vec![]); } #[test] fn parse_whitespace() { assert_eq!( - parse_from_str("\n \n \n").unwrap().into_vec(), + State::from_str("\n \n \n").unwrap().into_vec(), vec![newline(), whitespace(" "), newline(), whitespace(" "), newline()] ) } @@ -203,7 +203,7 @@ fn parse_whitespace() { #[test] fn newline_events_are_merged() { assert_eq!( - parse_from_str("\n\n\n\n\n").unwrap().into_vec(), + State::from_str("\n\n\n\n\n").unwrap().into_vec(), vec![newline_custom("\n\n\n\n\n")] ); } @@ -212,23 +212,23 @@ fn newline_events_are_merged() { fn error() { let input = "[a_b]\n c=d"; assert_eq!( - parse_from_str(input).unwrap_err().to_string(), + State::from_str(input).unwrap_err().to_string(), "Got an unexpected token on line 1 while trying to parse a section header: '[a_b]\n c=d'", "underscores in section names aren't allowed and will be rejected by git" ); let input = "[core] a=b\n 4a=3"; assert_eq!( - parse_from_str(input).unwrap_err().to_string(), + State::from_str(input).unwrap_err().to_string(), "Got an unexpected token on line 2 while trying to parse a config name: '4a=3'" ); let input = "[core] a=b\n =3"; assert_eq!( - parse_from_str(input).unwrap_err().to_string(), + State::from_str(input).unwrap_err().to_string(), "Got an unexpected token on line 2 while trying to parse a config name: '=3'" ); let input = "[core"; assert_eq!( - parse_from_str(input).unwrap_err().to_string(), + State::from_str(input).unwrap_err().to_string(), "Got an unexpected token on line 1 while trying to parse a section header: '[core'" ); } From 41bfd3b4122e37370d268608b60cb00a671a8879 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 6 Jul 2022 17:31:18 +0800 Subject: [PATCH 045/366] adjustments required due to changed in `git-config` (#331) --- git-config/src/parse/state.rs | 1 + git-repository/src/config.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/git-config/src/parse/state.rs b/git-config/src/parse/state.rs index 5d9d7cb3254..6f152f1bf2b 100644 --- a/git-config/src/parse/state.rs +++ b/git-config/src/parse/state.rs @@ -53,6 +53,7 @@ impl<'a> State<'a> { /// Returns an error if the string provided is not a valid `git-config`. /// This generally is due to either invalid names or if there's extraneous /// data succeeding valid `git-config` data. + #[allow(clippy::should_implement_trait)] pub fn from_str(input: &'a str) -> Result, Error<'a>> { crate::parse::nom::from_bytes(input.as_bytes()) } diff --git a/git-repository/src/config.rs b/git-repository/src/config.rs index d66684c124c..4b8f589771f 100644 --- a/git-repository/src/config.rs +++ b/git-repository/src/config.rs @@ -3,7 +3,7 @@ use crate::{bstr::BString, permission}; #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Could not open repository conifguration file")] - Open(#[from] git_config::parser::ParserOrIoError<'static>), + Open(#[from] git_config::parse::ParserOrIoError<'static>), #[error("Cannot handle objects formatted as {:?}", .name)] UnsupportedObjectFormat { name: crate::bstr::BString }, #[error("The value for '{}' cannot be empty", .key)] From 239cbfb450a8cddfc5bec1de21f3dc54fab914ce Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 6 Jul 2022 20:07:59 +0800 Subject: [PATCH 046/366] change!: rename `parse::Section*` related types. (#331) These are now located in `section::*`. --- .../src/file/access/low_level/mutating.rs | 8 +- .../src/file/access/low_level/read_only.rs | 8 +- git-config/src/file/access/raw.rs | 14 +- git-config/src/file/from_env.rs | 2 +- git-config/src/file/impls.rs | 2 +- git-config/src/file/resolve_includes.rs | 4 +- git-config/src/file/section.rs | 22 +-- git-config/src/file/try_from_str_tests.rs | 13 +- git-config/src/file/utils.rs | 8 +- git-config/src/file/value.rs | 16 +- git-config/src/parse/event.rs | 6 +- git-config/src/parse/mod.rs | 71 ++++----- git-config/src/parse/nom/mod.rs | 22 +-- git-config/src/parse/nom/tests.rs | 18 +-- git-config/src/parse/section.rs | 141 ++++++++++++++++-- git-config/src/parse/state.rs | 6 +- git-config/src/parse/tests.rs | 10 +- git-config/src/parse/types.rs | 85 ----------- git-config/src/types.rs | 6 +- git-config/tests/parser/mod.rs | 15 +- 20 files changed, 239 insertions(+), 238 deletions(-) delete mode 100644 git-config/src/parse/types.rs diff --git a/git-config/src/file/access/low_level/mutating.rs b/git-config/src/file/access/low_level/mutating.rs index ef0a65687cf..11fa4ee1beb 100644 --- a/git-config/src/file/access/low_level/mutating.rs +++ b/git-config/src/file/access/low_level/mutating.rs @@ -4,7 +4,7 @@ use std::borrow::Cow; use crate::{ file::{MutableSection, SectionBody}, lookup, - parse::{ParsedSectionHeader, SectionHeaderName}, + parse::section, File, }; @@ -132,8 +132,8 @@ impl<'a> File<'a> { ) -> MutableSection<'_, 'a> { let subsection_name = subsection_name.into().map(into_cow_bstr); self.push_section_internal( - ParsedSectionHeader { - name: SectionHeaderName(into_cow_bstr(section_name.into())), + section::Header { + name: section::Name(into_cow_bstr(section_name.into())), separator: subsection_name.is_some().then(|| Cow::Borrowed(" ".into())), subsection_name, }, @@ -150,7 +150,7 @@ impl<'a> File<'a> { &mut self, section_name: &'lookup str, subsection_name: impl Into>, - new_section_name: impl Into>, + new_section_name: impl Into>, new_subsection_name: impl Into>>, ) -> Result<(), lookup::existing::Error> { let id = self.section_ids_by_name_and_subname(section_name, subsection_name.into())?; diff --git a/git-config/src/file/access/low_level/read_only.rs b/git-config/src/file/access/low_level/read_only.rs index 57ec592f0ee..56c6c58c81a 100644 --- a/git-config/src/file/access/low_level/read_only.rs +++ b/git-config/src/file/access/low_level/read_only.rs @@ -1,7 +1,7 @@ use bstr::BStr; use std::{borrow::Cow, convert::TryFrom}; -use crate::{file::SectionBody, lookup, parse::ParsedSectionHeader, File}; +use crate::{file::SectionBody, lookup, parse::section, File}; /// Read-only low-level access methods. impl<'a> File<'a> { @@ -204,7 +204,7 @@ impl<'a> File<'a> { /// /// ```rust /// use git_config::File; - /// use git_config::parse::Key; + /// use git_config::parse::section; /// use std::borrow::Cow; /// use std::convert::TryFrom; /// use nom::AsBytes; @@ -221,7 +221,7 @@ impl<'a> File<'a> { /// /// for (i, (header, body)) in url.iter().enumerate() { /// let url = header.subsection_name.as_ref(); - /// let instead_of = body.value(&Key::from("insteadOf")); + /// let instead_of = body.value(§ion::Key::from("insteadOf")); /// /// // todo(unstable-order): the order is not always the same, so `i` cannot be used here /// if instead_of.as_ref().unwrap().as_ref().as_bytes().eq("https://github.com/".as_bytes()) { @@ -236,7 +236,7 @@ impl<'a> File<'a> { pub fn sections_by_name_with_header<'lookup>( &self, section_name: &'lookup str, - ) -> Vec<(&ParsedSectionHeader<'a>, &SectionBody<'a>)> { + ) -> Vec<(§ion::Header<'a>, &SectionBody<'a>)> { self.section_ids_by_name(section_name) .unwrap_or_default() .into_iter() diff --git a/git-config/src/file/access/raw.rs b/git-config/src/file/access/raw.rs index eca9a76edc3..42fc3d8983b 100644 --- a/git-config/src/file/access/raw.rs +++ b/git-config/src/file/access/raw.rs @@ -4,7 +4,7 @@ use std::{borrow::Cow, collections::HashMap}; use crate::{ file::{EntryData, Index, MutableMultiValue, MutableSection, MutableValue, Size}, lookup, - parse::{Event, Key}, + parse::{section, Event}, File, }; @@ -32,7 +32,7 @@ impl<'a> File<'a> { // Note: cannot wrap around the raw_multi_value method because we need // to guarantee that the highest section id is used (so that we follow // the "last one wins" resolution strategy by `git-config`). - let key = Key(Cow::::Borrowed(key.into())); + let key = section::Key(Cow::::Borrowed(key.into())); for section_id in self .section_ids_by_name_and_subname(section_name, subsection_name)? .iter() @@ -68,7 +68,7 @@ impl<'a> File<'a> { key: &'lookup str, ) -> Result, lookup::existing::Error> { let section_ids = self.section_ids_by_name_and_subname(section_name, subsection_name)?; - let key = Key(Cow::::Borrowed(key.into())); + let key = section::Key(Cow::::Borrowed(key.into())); for section_id in section_ids.iter().rev() { let mut size = Size(0); @@ -84,7 +84,7 @@ impl<'a> File<'a> { .enumerate() { match event { - Event::Key(event_key) if *event_key == key => { + Event::SectionKey(event_key) if *event_key == key => { found_key = true; size = Size(1); index = Index(i); @@ -171,7 +171,7 @@ impl<'a> File<'a> { self.sections .get(§ion_id) .expect("sections does not have section id from section ids") - .values(&Key(Cow::::Borrowed(key.into()))) + .values(§ion::Key(Cow::::Borrowed(key.into()))) .iter() .cloned(), ); @@ -247,7 +247,7 @@ impl<'a> File<'a> { key: &'lookup str, ) -> Result, lookup::existing::Error> { let section_ids = self.section_ids_by_name_and_subname(section_name, subsection_name)?; - let key = Key(Cow::::Borrowed(key.into())); + let key = section::Key(Cow::::Borrowed(key.into())); let mut offsets = HashMap::new(); let mut entries = vec![]; @@ -265,7 +265,7 @@ impl<'a> File<'a> { .enumerate() { match event { - Event::Key(event_key) if *event_key == key => { + Event::SectionKey(event_key) if *event_key == key => { found_key = true; offset_list.push(i - last_boundary); offset_index += 1; diff --git a/git-config/src/file/from_env.rs b/git-config/src/file/from_env.rs index 93f40c02762..d25b1d8764d 100644 --- a/git-config/src/file/from_env.rs +++ b/git-config/src/file/from_env.rs @@ -115,7 +115,7 @@ impl<'a> File<'a> { }; section.push( - parse::Key(Cow::Owned(BString::from(key))), + parse::section::Key(Cow::Owned(BString::from(key))), Cow::Owned(git_path::into_bstr(PathBuf::from(value)).into_owned()), ); } else { diff --git a/git-config/src/file/impls.rs b/git-config/src/file/impls.rs index 747ad6b24d2..a61301f45eb 100644 --- a/git-config/src/file/impls.rs +++ b/git-config/src/file/impls.rs @@ -61,7 +61,7 @@ impl<'a> From> for File<'a> { prev_section_header = Some(header); section_events = SectionBody::new(); } - e @ Event::Key(_) + e @ Event::SectionKey(_) | e @ Event::Value(_) | e @ Event::ValueNotDone(_) | e @ Event::ValueDone(_) diff --git a/git-config/src/file/resolve_includes.rs b/git-config/src/file/resolve_includes.rs index 8393b00d537..884517b9b6e 100644 --- a/git-config/src/file/resolve_includes.rs +++ b/git-config/src/file/resolve_includes.rs @@ -9,7 +9,7 @@ use git_ref::Category; use crate::file::from_paths::Options; use crate::{ file::{from_paths, SectionId}, - parse::Key, + parse::section, value, File, }; @@ -87,7 +87,7 @@ fn resolve_includes_recursive( fn extract_include_path<'a>(target_config: &mut File<'a>, include_paths: &mut Vec>, id: SectionId) { if let Some(body) = target_config.sections.get(&id) { - let paths = body.values(&Key::from("path")); + let paths = body.values(§ion::Key::from("path")); let paths = paths.iter().map(|path| value::Path::from(path.clone())); include_paths.extend(paths); } diff --git a/git-config/src/file/section.rs b/git-config/src/file/section.rs index 0c293b86646..99299b72b17 100644 --- a/git-config/src/file/section.rs +++ b/git-config/src/file/section.rs @@ -10,7 +10,7 @@ use std::{ use crate::{ file::Index, lookup, - parse::{Event, Key}, + parse::{section::Key, Event}, value::{normalize, normalize_bstring}, }; @@ -34,7 +34,7 @@ impl<'borrow, 'event> MutableSection<'borrow, 'event> { })); } - self.section.0.push(Event::Key(key)); + self.section.0.push(Event::SectionKey(key)); self.section.0.push(Event::KeyValueSeparator); self.section.0.push(Event::Value(value)); if self.implicit_newline { @@ -49,7 +49,7 @@ impl<'borrow, 'event> MutableSection<'borrow, 'event> { // events are popped in reverse order while let Some(e) = self.section.0.pop() { match e { - Event::Key(k) => { + Event::SectionKey(k) => { // pop leading whitespace if let Some(Event::Whitespace(_)) = self.section.0.last() { self.section.0.pop(); @@ -172,7 +172,7 @@ impl<'borrow, 'event> MutableSection<'borrow, 'event> { for event in &self.section.0[start.0..=end.0] { match event { - Event::Key(event_key) if event_key == key => found_key = true, + Event::SectionKey(event_key) if event_key == key => found_key = true, Event::Value(v) if found_key => { found_key = false; // Clones the Cow, doesn't copy underlying value if borrowed @@ -203,7 +203,7 @@ impl<'borrow, 'event> MutableSection<'borrow, 'event> { pub(crate) fn set_internal(&mut self, index: Index, key: Key<'event>, value: BString) { self.section.0.insert(index.0, Event::Value(Cow::Owned(value))); self.section.0.insert(index.0, Event::KeyValueSeparator); - self.section.0.insert(index.0, Event::Key(key)); + self.section.0.insert(index.0, Event::SectionKey(key)); } } @@ -289,7 +289,7 @@ impl<'event> SectionBody<'event> { // section anyways for event in &self.0 { match event { - Event::Key(event_key) if event_key == key => found_key = true, + Event::SectionKey(event_key) if event_key == key => found_key = true, Event::Value(v) if found_key => { found_key = false; // Clones the Cow, doesn't copy underlying value if borrowed @@ -332,7 +332,7 @@ impl<'event> SectionBody<'event> { pub fn keys(&self) -> impl Iterator> { self.0 .iter() - .filter_map(|e| if let Event::Key(k) = e { Some(k) } else { None }) + .filter_map(|e| if let Event::SectionKey(k) = e { Some(k) } else { None }) } /// Checks if the section contains the provided key. @@ -340,7 +340,7 @@ impl<'event> SectionBody<'event> { pub fn contains_key(&self, key: &Key<'_>) -> bool { self.0.iter().any(|e| { matches!(e, - Event::Key(k) if k == key + Event::SectionKey(k) if k == key ) }) } @@ -348,7 +348,7 @@ impl<'event> SectionBody<'event> { /// Returns the number of values in the section. #[must_use] pub fn num_values(&self) -> usize { - self.0.iter().filter(|e| matches!(e, Event::Key(_))).count() + self.0.iter().filter(|e| matches!(e, Event::SectionKey(_))).count() } /// Returns if the section is empty. @@ -366,7 +366,7 @@ impl<'event> SectionBody<'event> { let mut values_end = 0; for (i, e) in self.0.iter().enumerate().rev() { match e { - Event::Key(k) => { + Event::SectionKey(k) => { if k == key { break; } @@ -416,7 +416,7 @@ impl<'event> Iterator for SectionBodyIter<'event> { while let Some(event) = self.0.pop_front() { match event { - Event::Key(k) => key = Some(k), + Event::SectionKey(k) => key = Some(k), Event::Value(v) => { value = Some(v); break; diff --git a/git-config/src/file/try_from_str_tests.rs b/git-config/src/file/try_from_str_tests.rs index b412741d4f6..d9dbc0bf1a4 100644 --- a/git-config/src/file/try_from_str_tests.rs +++ b/git-config/src/file/try_from_str_tests.rs @@ -4,8 +4,9 @@ use super::{Cow, HashMap, LookupTreeNode, SectionId}; use crate::{ file::SectionBody, parse::{ + section, tests::util::{name_event, newline_event, section_header, value_event}, - Event, SectionHeaderName, + Event, }, File, }; @@ -33,7 +34,7 @@ fn parse_single_section() { let expected_lookup_tree = { let mut tree = HashMap::new(); tree.insert( - SectionHeaderName(Cow::Borrowed("core".into())), + section::Name(Cow::Borrowed("core".into())), vec![LookupTreeNode::Terminal(vec![SectionId(0)])], ); tree @@ -75,7 +76,7 @@ fn parse_single_subsection() { let mut inner_tree = HashMap::new(); inner_tree.insert(Cow::Borrowed("sub".into()), vec![SectionId(0)]); tree.insert( - SectionHeaderName(Cow::Borrowed("core".into())), + section::Name(Cow::Borrowed("core".into())), vec![LookupTreeNode::NonTerminal(inner_tree)], ); tree @@ -116,11 +117,11 @@ fn parse_multiple_sections() { let expected_lookup_tree = { let mut tree = HashMap::new(); tree.insert( - SectionHeaderName(Cow::Borrowed("core".into())), + section::Name(Cow::Borrowed("core".into())), vec![LookupTreeNode::Terminal(vec![SectionId(0)])], ); tree.insert( - SectionHeaderName(Cow::Borrowed("other".into())), + section::Name(Cow::Borrowed("other".into())), vec![LookupTreeNode::Terminal(vec![SectionId(1)])], ); tree @@ -166,7 +167,7 @@ fn parse_multiple_duplicate_sections() { let expected_lookup_tree = { let mut tree = HashMap::new(); tree.insert( - SectionHeaderName(Cow::Borrowed("core".into())), + section::Name(Cow::Borrowed("core".into())), vec![LookupTreeNode::Terminal(vec![SectionId(0), SectionId(1)])], ); tree diff --git a/git-config/src/file/utils.rs b/git-config/src/file/utils.rs index 6e6af59d26c..0e87d59161b 100644 --- a/git-config/src/file/utils.rs +++ b/git-config/src/file/utils.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use crate::{ file::{LookupTreeNode, MutableSection, SectionBody, SectionId}, lookup, - parse::{ParsedSectionHeader, SectionHeaderName}, + parse::section, File, }; @@ -13,7 +13,7 @@ impl<'event> File<'event> { /// Adds a new section to the config file. pub(crate) fn push_section_internal( &mut self, - header: ParsedSectionHeader<'event>, + header: section::Header<'event>, section: SectionBody<'event>, ) -> MutableSection<'_, 'event> { let new_section_id = SectionId(self.section_id_counter); @@ -59,7 +59,7 @@ impl<'event> File<'event> { /// Returns the mapping between section and subsection name to section ids. pub(crate) fn section_ids_by_name_and_subname<'lookup>( &self, - section_name: impl Into>, + section_name: impl Into>, subsection_name: Option<&'lookup str>, ) -> Result, lookup::existing::Error> { let section_name = section_name.into(); @@ -94,7 +94,7 @@ impl<'event> File<'event> { pub(crate) fn section_ids_by_name<'lookup>( &self, - section_name: impl Into>, + section_name: impl Into>, ) -> Result, lookup::existing::Error> { let section_name = section_name.into(); self.section_lookup_tree diff --git a/git-config/src/file/value.rs b/git-config/src/file/value.rs index e76b40b80e1..5aa11300d51 100644 --- a/git-config/src/file/value.rs +++ b/git-config/src/file/value.rs @@ -7,7 +7,7 @@ use crate::{ Index, SectionId, Size, }, lookup, - parse::{Event, Key}, + parse::{section, Event}, value::{normalize_bstr, normalize_bstring}, }; @@ -24,7 +24,7 @@ use crate::{ #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] pub struct MutableValue<'borrow, 'lookup, 'event> { section: MutableSection<'borrow, 'event>, - key: Key<'lookup>, + key: section::Key<'lookup>, index: Index, size: Size, } @@ -32,7 +32,7 @@ pub struct MutableValue<'borrow, 'lookup, 'event> { impl<'borrow, 'lookup, 'event> MutableValue<'borrow, 'lookup, 'event> { pub(crate) const fn new( section: MutableSection<'borrow, 'event>, - key: Key<'lookup>, + key: section::Key<'lookup>, index: Index, size: Size, ) -> Self { @@ -111,7 +111,7 @@ impl EntryData { #[derive(PartialEq, Eq, Debug)] pub struct MutableMultiValue<'borrow, 'lookup, 'event> { section: &'borrow mut HashMap>, - key: Key<'lookup>, + key: section::Key<'lookup>, /// Each entry data struct provides sufficient information to index into /// [`Self::offsets`]. This layer of indirection is used for users to index /// into the offsets rather than leaking the internal data structures. @@ -125,7 +125,7 @@ pub struct MutableMultiValue<'borrow, 'lookup, 'event> { impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { pub(crate) fn new( section: &'borrow mut HashMap>, - key: Key<'lookup>, + key: section::Key<'lookup>, indices_and_sizes: Vec, offsets: HashMap>, ) -> Self { @@ -161,7 +161,7 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { .as_ref()[offset..offset + size] { match event { - Event::Key(event_key) if *event_key == self.key => found_key = true, + Event::SectionKey(section_key) if *section_key == self.key => found_key = true, Event::Value(v) if found_key => { found_key = false; values.push(normalize_bstr(v.as_ref())); @@ -326,7 +326,7 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { } fn set_value_inner<'a: 'event>( - key: &Key<'lookup>, + key: §ion::Key<'lookup>, offsets: &mut HashMap>, section: &mut SectionBody<'event>, section_id: SectionId, @@ -339,7 +339,7 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { MutableMultiValue::set_offset(offsets, section_id, offset_index, 3); section.as_mut().insert(offset, Event::Value(input)); section.as_mut().insert(offset, Event::KeyValueSeparator); - section.as_mut().insert(offset, Event::Key(key.to_owned())); + section.as_mut().insert(offset, Event::SectionKey(key.to_owned())); } /// Removes the value at the given index. Does nothing when called multiple diff --git a/git-config/src/parse/event.rs b/git-config/src/parse/event.rs index 01c767e3251..63dad9774f0 100644 --- a/git-config/src/parse/event.rs +++ b/git-config/src/parse/event.rs @@ -31,7 +31,7 @@ impl Event<'_> { match self { Event::Comment(e) => Event::Comment(e.to_owned()), Event::SectionHeader(e) => Event::SectionHeader(e.to_owned()), - Event::Key(e) => Event::Key(e.to_owned()), + Event::SectionKey(e) => Event::SectionKey(e.to_owned()), Event::Value(e) => Event::Value(Cow::Owned(e.clone().into_owned())), Event::ValueNotDone(e) => Event::ValueNotDone(Cow::Owned(e.clone().into_owned())), Event::ValueDone(e) => Event::ValueDone(Cow::Owned(e.clone().into_owned())), @@ -54,7 +54,7 @@ impl Display for Event<'_> { }, Self::Comment(e) => e.fmt(f), Self::SectionHeader(e) => e.fmt(f), - Self::Key(e) => e.fmt(f), + Self::SectionKey(e) => e.fmt(f), Self::Newline(e) | Self::Whitespace(e) => e.fmt(f), Self::KeyValueSeparator => write!(f, "="), } @@ -73,7 +73,7 @@ impl From<&Event<'_>> for BString { Event::Value(e) | Event::ValueNotDone(e) | Event::ValueDone(e) => e.as_ref().into(), Event::Comment(e) => e.into(), Event::SectionHeader(e) => e.into(), - Event::Key(e) => e.0.as_ref().into(), + Event::SectionKey(e) => e.0.as_ref().into(), Event::Newline(e) | Event::Whitespace(e) => e.as_ref().into(), Event::KeyValueSeparator => "=".into(), } diff --git a/git-config/src/parse/mod.rs b/git-config/src/parse/mod.rs index 71732b6d483..753c9681b7c 100644 --- a/git-config/src/parse/mod.rs +++ b/git-config/src/parse/mod.rs @@ -86,10 +86,10 @@ use std::{borrow::Cow, hash::Hash}; /// non-significant events that occur in addition to the ones you may expect: /// /// ``` -/// # use git_config::parse::{Event, ParsedSectionHeader, State, SectionHeaderName, Key}; +/// # use git_config::parse::{Event, State, section}; /// # use std::borrow::Cow; -/// # let section_header = ParsedSectionHeader { -/// # name: SectionHeaderName(Cow::Borrowed("core".into())), +/// # let section_header = section::Header { +/// # name: section::Name(Cow::Borrowed("core".into())), /// # separator: None, /// # subsection_name: None, /// # }; @@ -98,7 +98,7 @@ use std::{borrow::Cow, hash::Hash}; /// Event::SectionHeader(section_header), /// Event::Newline(Cow::Borrowed("\n".into())), /// Event::Whitespace(Cow::Borrowed(" ".into())), -/// Event::Key(Key(Cow::Borrowed("autocrlf".into()))), +/// Event::SectionKey(section::Key(Cow::Borrowed("autocrlf".into()))), /// Event::Whitespace(Cow::Borrowed(" ".into())), /// Event::KeyValueSeparator, /// Event::Whitespace(Cow::Borrowed(" ".into())), @@ -125,10 +125,10 @@ use std::{borrow::Cow, hash::Hash}; /// which means that the corresponding event won't appear either: /// /// ``` -/// # use git_config::parse::{Event, ParsedSectionHeader, State, SectionHeaderName, Key}; +/// # use git_config::parse::{Event, State, section}; /// # use std::borrow::Cow; -/// # let section_header = ParsedSectionHeader { -/// # name: SectionHeaderName(Cow::Borrowed("core".into())), +/// # let section_header = section::Header { +/// # name: section::Name(Cow::Borrowed("core".into())), /// # separator: None, /// # subsection_name: None, /// # }; @@ -137,7 +137,7 @@ use std::{borrow::Cow, hash::Hash}; /// Event::SectionHeader(section_header), /// Event::Newline(Cow::Borrowed("\n".into())), /// Event::Whitespace(Cow::Borrowed(" ".into())), -/// Event::Key(Key(Cow::Borrowed("autocrlf".into()))), +/// Event::SectionKey(section::Key(Cow::Borrowed("autocrlf".into()))), /// Event::Value(Cow::Borrowed("".into())), /// # ]); /// ``` @@ -159,10 +159,10 @@ use std::{borrow::Cow, hash::Hash}; /// relevant event stream emitted is thus emitted as: /// /// ``` -/// # use git_config::parse::{Event, ParsedSectionHeader, State, SectionHeaderName, Key}; +/// # use git_config::parse::{Event, State, section}; /// # use std::borrow::Cow; -/// # let section_header = ParsedSectionHeader { -/// # name: SectionHeaderName(Cow::Borrowed("core".into())), +/// # let section_header = section::Header { +/// # name: section::Name(Cow::Borrowed("core".into())), /// # separator: None, /// # subsection_name: None, /// # }; @@ -170,11 +170,11 @@ use std::{borrow::Cow, hash::Hash}; /// # assert_eq!(State::from_str(section_data).unwrap().into_vec(), vec![ /// Event::SectionHeader(section_header), /// Event::Newline(Cow::Borrowed("\n".into())), -/// Event::Key(Key(Cow::Borrowed("autocrlf".into()))), +/// Event::SectionKey(section::Key(Cow::Borrowed("autocrlf".into()))), /// Event::KeyValueSeparator, /// Event::Value(Cow::Borrowed(r#"true"""#.into())), /// Event::Newline(Cow::Borrowed("\n".into())), -/// Event::Key(Key(Cow::Borrowed("filemode".into()))), +/// Event::SectionKey(section::Key(Cow::Borrowed("filemode".into()))), /// Event::KeyValueSeparator, /// Event::Value(Cow::Borrowed(r#"fa"lse""#.into())), /// # ]); @@ -196,10 +196,10 @@ use std::{borrow::Cow, hash::Hash}; /// split value accordingly: /// /// ``` -/// # use git_config::parse::{Event, ParsedSectionHeader, State, SectionHeaderName, Key}; +/// # use git_config::parse::{Event, State, section}; /// # use std::borrow::Cow; -/// # let section_header = ParsedSectionHeader { -/// # name: SectionHeaderName(Cow::Borrowed("some-section".into())), +/// # let section_header = section::Header { +/// # name: section::Name(Cow::Borrowed("some-section".into())), /// # separator: None, /// # subsection_name: None, /// # }; @@ -207,7 +207,7 @@ use std::{borrow::Cow, hash::Hash}; /// # assert_eq!(State::from_str(section_data).unwrap().into_vec(), vec![ /// Event::SectionHeader(section_header), /// Event::Newline(Cow::Borrowed("\n".into())), -/// Event::Key(Key(Cow::Borrowed("file".into()))), +/// Event::SectionKey(section::Key(Cow::Borrowed("file".into()))), /// Event::KeyValueSeparator, /// Event::ValueNotDone(Cow::Borrowed("a".into())), /// Event::Newline(Cow::Borrowed("\n".into())), @@ -223,7 +223,7 @@ use std::{borrow::Cow, hash::Hash}; #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] pub struct State<'a> { frontmatter: Vec>, - sections: Vec>, + sections: Vec>, } mod state; @@ -245,10 +245,10 @@ pub enum Event<'a> { /// at the beginning. Comment(ParsedComment<'a>), /// A section header containing the section name and a subsection, if it - /// exists. - SectionHeader(ParsedSectionHeader<'a>), - /// A name to a value in a section. - Key(Key<'a>), + /// exists, like `remote "origin"`. + SectionHeader(section::Header<'a>), + /// A name to a value in a section, like `url` in `remote.origin.url`. + SectionKey(section::Key<'a>), /// A completed value. This may be any string, including the empty string, /// if an implicit boolean value is used. Note that these values may contain /// spaces and any special character. This value is also unprocessed, so it @@ -278,36 +278,15 @@ mod event; /// A parsed section containing the header and the section events. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] -pub struct ParsedSection<'a> { +pub struct Section<'a> { /// The section name and subsection name, if any. - pub section_header: ParsedSectionHeader<'a>, + pub section_header: section::Header<'a>, /// The syntactic events found in this section. pub events: Vec>, } -/// A parsed section header, containing a name and optionally a subsection name. /// -/// Note that section headers must be parsed as valid ASCII, and thus all valid -/// instances must also necessarily be valid UTF-8, which is why we use a -/// [`str`] instead of [`[u8]`]. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] -pub struct ParsedSectionHeader<'a> { - /// The name of the header. - pub name: SectionHeaderName<'a>, - /// The separator used to determine if the section contains a subsection. - /// This is either a period `.` or a string of whitespace. Note that - /// reconstruction of subsection format is dependent on this value. If this - /// is all whitespace, then the subsection name needs to be surrounded by - /// quotes to have perfect reconstruction. - pub separator: Option>, - /// The subsection name without quotes if any exist. - pub subsection_name: Option>, -} - -mod section; - -mod types; -pub use types::{Key, SectionHeaderName}; +pub mod section; /// A parsed comment event containing the comment marker and comment. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index 14701efb84b..b405bf38376 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -1,4 +1,4 @@ -use crate::parse::{Error, Event, Key, ParsedComment, ParsedSection, ParsedSectionHeader, SectionHeaderName, State}; +use crate::parse::{section, Error, Event, ParsedComment, Section, State}; use bstr::{BStr, BString, ByteSlice, ByteVec}; use std::borrow::Cow; @@ -170,7 +170,7 @@ fn comment(i: &[u8]) -> IResult<&[u8], ParsedComment<'_>> { #[cfg(test)] mod tests; -fn section<'a, 'b>(i: &'a [u8], node: &'b mut ParserNode) -> IResult<&'a [u8], (ParsedSection<'a>, usize)> { +fn section<'a, 'b>(i: &'a [u8], node: &'b mut ParserNode) -> IResult<&'a [u8], (Section<'a>, usize)> { let (mut i, section_header) = section_header(i)?; let mut newlines = 0; @@ -217,7 +217,7 @@ fn section<'a, 'b>(i: &'a [u8], node: &'b mut ParserNode) -> IResult<&'a [u8], ( Ok(( i, ( - ParsedSection { + Section { section_header, events: items, }, @@ -226,7 +226,7 @@ fn section<'a, 'b>(i: &'a [u8], node: &'b mut ParserNode) -> IResult<&'a [u8], ( )) } -fn section_header(i: &[u8]) -> IResult<&[u8], ParsedSectionHeader<'_>> { +fn section_header(i: &[u8]) -> IResult<&[u8], section::Header<'_>> { let (i, _) = char('[')(i)?; // No spaces must be between section name and section start let (i, name) = take_while(|c: u8| c.is_ascii_alphanumeric() || c == b'-' || c == b'.')(i)?; @@ -236,13 +236,13 @@ fn section_header(i: &[u8]) -> IResult<&[u8], ParsedSectionHeader<'_>> { // Either section does not have a subsection or using deprecated // subsection syntax at this point. let header = match memchr::memrchr(b'.', name.as_bytes()) { - Some(index) => ParsedSectionHeader { - name: SectionHeaderName(Cow::Borrowed(name[..index].as_bstr())), + Some(index) => section::Header { + name: section::Name(Cow::Borrowed(name[..index].as_bstr())), separator: name.get(index..=index).map(|s| Cow::Borrowed(s.as_bstr())), subsection_name: name.get(index + 1..).map(|s| Cow::Borrowed(s.as_bstr())), }, - None => ParsedSectionHeader { - name: SectionHeaderName(Cow::Borrowed(name.as_bstr())), + None => section::Header { + name: section::Name(Cow::Borrowed(name.as_bstr())), separator: None, subsection_name: None, }, @@ -258,8 +258,8 @@ fn section_header(i: &[u8]) -> IResult<&[u8], ParsedSectionHeader<'_>> { Ok(( i, - ParsedSectionHeader { - name: SectionHeaderName(Cow::Borrowed(name)), + section::Header { + name: section::Name(Cow::Borrowed(name)), separator: Some(Cow::Borrowed(whitespace)), subsection_name: subsection_name.map(Cow::Owned), }, @@ -320,7 +320,7 @@ fn section_body<'a, 'b, 'c>( *node = ParserNode::ConfigName; let (i, name) = config_name(i)?; - items.push(Event::Key(Key(Cow::Borrowed(name)))); + items.push(Event::SectionKey(section::Key(Cow::Borrowed(name)))); let (i, whitespace) = opt(take_spaces)(i)?; diff --git a/git-config/src/parse/nom/tests.rs b/git-config/src/parse/nom/tests.rs index 9656887bd09..f7f4415249b 100644 --- a/git-config/src/parse/nom/tests.rs +++ b/git-config/src/parse/nom/tests.rs @@ -98,7 +98,7 @@ mod section { comment_event, fully_consumed, name_event, newline_event, section_header as parsed_section_header, value_done_event, value_event, value_not_done_event, whitespace_event, }; - use crate::parse::{error::ParserNode, Event, ParsedSection}; + use crate::parse::{error::ParserNode, Event, Section}; #[test] fn empty_section() { @@ -106,7 +106,7 @@ mod section { assert_eq!( section(b"[test]", &mut node).unwrap(), fully_consumed(( - ParsedSection { + Section { section_header: parsed_section_header("test", None), events: vec![] }, @@ -125,7 +125,7 @@ mod section { assert_eq!( section(section_data, &mut node).unwrap(), fully_consumed(( - ParsedSection { + Section { section_header: parsed_section_header("hello", None), events: vec![ newline_event(), @@ -163,7 +163,7 @@ mod section { assert_eq!( section(section_data, &mut node).unwrap(), fully_consumed(( - ParsedSection { + Section { section_header: parsed_section_header("hello", None), events: vec![ newline_event(), @@ -198,7 +198,7 @@ mod section { assert_eq!( section(b"[hello] c", &mut node).unwrap(), fully_consumed(( - ParsedSection { + Section { section_header: parsed_section_header("hello", None), events: vec![whitespace_event(" "), name_event("c"), value_event("")] }, @@ -218,7 +218,7 @@ mod section { assert_eq!( section(section_data, &mut node).unwrap(), fully_consumed(( - ParsedSection { + Section { section_header: parsed_section_header("hello", None), events: vec![ whitespace_event(" "), @@ -259,7 +259,7 @@ mod section { assert_eq!( section(b"[section] a = 1 \"\\\"\\\na ; e \"\\\"\\\nd # \"b\t ; c", &mut node).unwrap(), fully_consumed(( - ParsedSection { + Section { section_header: parsed_section_header("section", None), events: vec![ whitespace_event(" "), @@ -287,7 +287,7 @@ mod section { assert_eq!( section(b"[section \"a\"] b =\"\\\n;\";a", &mut node).unwrap(), fully_consumed(( - ParsedSection { + Section { section_header: parsed_section_header("section", (" ", "a")), events: vec![ whitespace_event(" "), @@ -311,7 +311,7 @@ mod section { assert_eq!( section(b"[s]hello #world", &mut node).unwrap(), fully_consumed(( - ParsedSection { + Section { section_header: parsed_section_header("s", None), events: vec![ name_event("hello"), diff --git a/git-config/src/parse/section.rs b/git-config/src/parse/section.rs index 79bdb3f9af5..29ffa65739d 100644 --- a/git-config/src/parse/section.rs +++ b/git-config/src/parse/section.rs @@ -1,9 +1,28 @@ -use crate::parse::{Event, ParsedSection, ParsedSectionHeader}; -use bstr::BString; +use crate::parse::{Event, Section}; +use bstr::{BStr, BString}; use std::borrow::Cow; use std::fmt::Display; -impl ParsedSection<'_> { +/// A parsed section header, containing a name and optionally a subsection name. +/// +/// Note that section headers must be parsed as valid ASCII, and thus all valid +/// instances must also necessarily be valid UTF-8. +// TODO: turn these into strings in with str::from_utf8_unchecked +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] +pub struct Header<'a> { + /// The name of the header. + pub name: Name<'a>, + /// The separator used to determine if the section contains a subsection. + /// This is either a period `.` or a string of whitespace. Note that + /// reconstruction of subsection format is dependent on this value. If this + /// is all whitespace, then the subsection name needs to be surrounded by + /// quotes to have perfect reconstruction. + pub separator: Option>, + /// The subsection name without quotes if any exist. + pub subsection_name: Option>, +} + +impl Section<'_> { /// Coerces into an owned instance. This differs from the standard [`clone`] /// implementation as calling clone will _not_ copy the borrowed variant, /// while this method will. In other words: @@ -19,15 +38,15 @@ impl ParsedSection<'_> { /// /// [`clone`]: Self::clone #[must_use] - pub fn to_owned(&self) -> ParsedSection<'static> { - ParsedSection { + pub fn to_owned(&self) -> Section<'static> { + Section { section_header: self.section_header.to_owned(), events: self.events.iter().map(Event::to_owned).collect(), } } } -impl Display for ParsedSection<'_> { +impl Display for Section<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.section_header)?; for event in &self.events { @@ -37,7 +56,7 @@ impl Display for ParsedSection<'_> { } } -impl ParsedSectionHeader<'_> { +impl Header<'_> { /// Generates a byte representation of the value. This should be used when /// non-UTF-8 sequences are present or a UTF-8 representation can't be /// guaranteed. @@ -61,8 +80,8 @@ impl ParsedSectionHeader<'_> { /// /// [`clone`]: Self::clone #[must_use] - pub fn to_owned(&self) -> ParsedSectionHeader<'static> { - ParsedSectionHeader { + pub fn to_owned(&self) -> Header<'static> { + Header { name: self.name.to_owned(), separator: self.separator.clone().map(|v| Cow::Owned(v.into_owned())), subsection_name: self.subsection_name.clone().map(|v| Cow::Owned(v.into_owned())), @@ -70,7 +89,7 @@ impl ParsedSectionHeader<'_> { } } -impl Display for ParsedSectionHeader<'_> { +impl Display for Header<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "[{}", self.name)?; @@ -89,20 +108,110 @@ impl Display for ParsedSectionHeader<'_> { } } -impl From> for BString { - fn from(header: ParsedSectionHeader<'_>) -> Self { +impl From> for BString { + fn from(header: Header<'_>) -> Self { header.into() } } -impl From<&ParsedSectionHeader<'_>> for BString { - fn from(header: &ParsedSectionHeader<'_>) -> Self { +impl From<&Header<'_>> for BString { + fn from(header: &Header<'_>) -> Self { header.to_string().into() } } -impl<'a> From> for Event<'a> { - fn from(header: ParsedSectionHeader<'_>) -> Event<'_> { +impl<'a> From> for Event<'a> { + fn from(header: Header<'_>) -> Event<'_> { Event::SectionHeader(header) } } + +mod types { + macro_rules! generate_case_insensitive { + ($name:ident, $cow_inner_type:ty, $comment:literal) => { + #[doc = $comment] + #[derive(Clone, Eq, Ord, Debug, Default)] + pub struct $name<'a>(pub std::borrow::Cow<'a, $cow_inner_type>); + + impl $name<'_> { + /// Coerces into an owned instance. This differs from the standard + /// [`clone`] implementation as calling clone will _not_ copy the + /// borrowed variant, while this method will. In other words: + /// + /// | Borrow type | `.clone()` | `to_owned()` | + /// | ----------- | ---------- | ------------ | + /// | Borrowed | Borrowed | Owned | + /// | Owned | Owned | Owned | + /// + /// This can be most effectively seen by the differing lifetimes + /// between the two. This method guarantees a `'static` lifetime, + /// while `clone` does not. + /// + /// [`clone`]: Self::clone + #[must_use] + pub fn to_owned(&self) -> $name<'static> { + $name(std::borrow::Cow::Owned(self.0.clone().into_owned())) + } + } + + impl PartialEq for $name<'_> { + fn eq(&self, other: &Self) -> bool { + self.0.eq_ignore_ascii_case(&other.0) + } + } + + impl std::fmt::Display for $name<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } + } + + // TODO: compare without lowercase conversion + impl PartialOrd for $name<'_> { + fn partial_cmp(&self, other: &Self) -> Option { + self.0 + .to_ascii_lowercase() + .partial_cmp(&other.0.to_ascii_lowercase()) + } + } + + impl std::hash::Hash for $name<'_> { + fn hash(&self, state: &mut H) { + self.0.to_ascii_lowercase().hash(state) + } + } + + impl<'a> From<&'a str> for $name<'a> { + fn from(s: &'a str) -> Self { + Self(std::borrow::Cow::Borrowed(s.into())) + } + } + + impl<'a> From> for $name<'a> { + fn from(s: std::borrow::Cow<'a, bstr::BStr>) -> Self { + Self(s) + } + } + + impl<'a> std::ops::Deref for $name<'a> { + type Target = $cow_inner_type; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + }; + } + generate_case_insensitive!( + Name, + bstr::BStr, + "Wrapper struct for section header names, like `includeIf`, since these are case-insensitive." + ); + + generate_case_insensitive!( + Key, + bstr::BStr, + "Wrapper struct for key names, like `path` in `include.path`, since keys are case-insensitive." + ); +} +pub use types::{Key, Name}; diff --git a/git-config/src/parse/state.rs b/git-config/src/parse/state.rs index 6f152f1bf2b..cfb46618990 100644 --- a/git-config/src/parse/state.rs +++ b/git-config/src/parse/state.rs @@ -1,4 +1,4 @@ -use crate::parse::{Error, Event, ParsedSection, ParserOrIoError, State}; +use crate::parse::{Error, Event, ParserOrIoError, Section, State}; use std::convert::TryFrom; use std::io::Read; @@ -96,14 +96,14 @@ impl<'a> State<'a> { /// [`State::take_sections`] if you need an owned copy only once. If that /// function was called, then this will always return an empty slice. #[must_use] - pub fn sections(&self) -> &[ParsedSection<'a>] { + pub fn sections(&self) -> &[Section<'a>] { &self.sections } /// Takes the parsed sections from the parser. Subsequent calls will return /// an empty vec. Consider [`State::sections`] if you only need a reference /// to the comments. - pub fn take_sections(&mut self) -> Vec> { + pub fn take_sections(&mut self) -> Vec> { let mut to_return = vec![]; std::mem::swap(&mut self.sections, &mut to_return); to_return diff --git a/git-config/src/parse/tests.rs b/git-config/src/parse/tests.rs index 4ba03c79853..2cdff6e9d09 100644 --- a/git-config/src/parse/tests.rs +++ b/git-config/src/parse/tests.rs @@ -52,21 +52,21 @@ pub(crate) mod util { use std::borrow::Cow; - use crate::parse::{Event, Key, ParsedComment, ParsedSectionHeader}; + use crate::parse::{section, Event, ParsedComment}; pub fn section_header( name: &str, subsection: impl Into>, - ) -> ParsedSectionHeader<'_> { + ) -> section::Header<'_> { let name = name.into(); if let Some((separator, subsection_name)) = subsection.into() { - ParsedSectionHeader { + section::Header { name, separator: Some(Cow::Borrowed(separator.into())), subsection_name: Some(Cow::Borrowed(subsection_name.into())), } } else { - ParsedSectionHeader { + section::Header { name, separator: None, subsection_name: None, @@ -75,7 +75,7 @@ pub(crate) mod util { } pub(crate) fn name_event(name: &'static str) -> Event<'static> { - Event::Key(Key(Cow::Borrowed(name.into()))) + Event::SectionKey(section::Key(Cow::Borrowed(name.into()))) } pub(crate) fn value_event(value: &'static str) -> Event<'static> { diff --git a/git-config/src/parse/types.rs b/git-config/src/parse/types.rs deleted file mode 100644 index e09970258f7..00000000000 --- a/git-config/src/parse/types.rs +++ /dev/null @@ -1,85 +0,0 @@ -macro_rules! generate_case_insensitive { - ($name:ident, $cow_inner_type:ty, $comment:literal) => { - #[doc = $comment] - #[derive(Clone, Eq, Ord, Debug, Default)] - pub struct $name<'a>(pub std::borrow::Cow<'a, $cow_inner_type>); - - impl $name<'_> { - /// Coerces into an owned instance. This differs from the standard - /// [`clone`] implementation as calling clone will _not_ copy the - /// borrowed variant, while this method will. In other words: - /// - /// | Borrow type | `.clone()` | `to_owned()` | - /// | ----------- | ---------- | ------------ | - /// | Borrowed | Borrowed | Owned | - /// | Owned | Owned | Owned | - /// - /// This can be most effectively seen by the differing lifetimes - /// between the two. This method guarantees a `'static` lifetime, - /// while `clone` does not. - /// - /// [`clone`]: Self::clone - #[must_use] - pub fn to_owned(&self) -> $name<'static> { - $name(std::borrow::Cow::Owned(self.0.clone().into_owned())) - } - } - - impl PartialEq for $name<'_> { - fn eq(&self, other: &Self) -> bool { - self.0.eq_ignore_ascii_case(&other.0) - } - } - - impl std::fmt::Display for $name<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) - } - } - - impl PartialOrd for $name<'_> { - fn partial_cmp(&self, other: &Self) -> Option { - self.0 - .to_ascii_lowercase() - .partial_cmp(&other.0.to_ascii_lowercase()) - } - } - - impl std::hash::Hash for $name<'_> { - fn hash(&self, state: &mut H) { - self.0.to_ascii_lowercase().hash(state) - } - } - - impl<'a> From<&'a str> for $name<'a> { - fn from(s: &'a str) -> Self { - Self(std::borrow::Cow::Borrowed(s.into())) - } - } - - impl<'a> From> for $name<'a> { - fn from(s: std::borrow::Cow<'a, bstr::BStr>) -> Self { - Self(s) - } - } - - impl<'a> std::ops::Deref for $name<'a> { - type Target = $cow_inner_type; - - fn deref(&self) -> &Self::Target { - &self.0 - } - } - }; -} -generate_case_insensitive!( - SectionHeaderName, - bstr::BStr, - "Wrapper struct for section header names, since section headers are case-insensitive." -); - -generate_case_insensitive!( - Key, - bstr::BStr, - "Wrapper struct for key names, since keys are case-insensitive." -); diff --git a/git-config/src/types.rs b/git-config/src/types.rs index 5d2377d622a..88280fe9e78 100644 --- a/git-config/src/types.rs +++ b/git-config/src/types.rs @@ -2,7 +2,7 @@ use std::collections::{HashMap, VecDeque}; use crate::{ file::{LookupTreeNode, SectionBody, SectionId}, - parse::{ParsedSectionHeader, SectionHeaderName}, + parse::section, }; /// High level `git-config` reader and writer. @@ -55,14 +55,14 @@ pub struct File<'event> { /// Section name and subsection name to section id lookup tree. This is /// effectively a n-tree (opposed to a binary tree) that can have a height /// of at most three (including an implicit root node). - pub(crate) section_lookup_tree: HashMap, Vec>>, + pub(crate) section_lookup_tree: HashMap, Vec>>, /// SectionId to section mapping. The value of this HashMap contains actual /// events. /// /// This indirection with the SectionId as the key is critical to flexibly /// supporting `git-config` sections, as duplicated keys are permitted. pub(crate) sections: HashMap>, - pub(crate) section_headers: HashMap>, + pub(crate) section_headers: HashMap>, /// Internal monotonically increasing counter for section ids. pub(crate) section_id_counter: usize, /// Section order for output ordering. diff --git a/git-config/tests/parser/mod.rs b/git-config/tests/parser/mod.rs index 6f1d1f5cb6a..31f0b9dd65e 100644 --- a/git-config/tests/parser/mod.rs +++ b/git-config/tests/parser/mod.rs @@ -1,24 +1,21 @@ use std::borrow::Cow; -use git_config::parse::{Event, Key, ParsedSectionHeader, SectionHeaderName, State}; +use git_config::parse::{section, Event, State}; pub fn section_header_event(name: &str, subsection: impl Into>) -> Event<'_> { Event::SectionHeader(section_header(name, subsection)) } -pub fn section_header( - name: &str, - subsection: impl Into>, -) -> ParsedSectionHeader<'_> { - let name = SectionHeaderName(Cow::Borrowed(name.into())); +pub fn section_header(name: &str, subsection: impl Into>) -> section::Header<'_> { + let name = section::Name(Cow::Borrowed(name.into())); if let Some((separator, subsection_name)) = subsection.into() { - ParsedSectionHeader { + section::Header { name, separator: Some(Cow::Borrowed(separator.into())), subsection_name: Some(Cow::Borrowed(subsection_name.into())), } } else { - ParsedSectionHeader { + section::Header { name, separator: None, subsection_name: None, @@ -27,7 +24,7 @@ pub fn section_header( } fn name(name: &'static str) -> Event<'static> { - Event::Key(Key(Cow::Borrowed(name.into()))) + Event::SectionKey(section::Key(Cow::Borrowed(name.into()))) } fn value(value: &'static str) -> Event<'static> { From 94608db648cd717af43a97785ea842bc75361b7e Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 6 Jul 2022 20:34:51 +0800 Subject: [PATCH 047/366] allocation-free case-inequality tests for section keys and names (#331) --- git-config/src/parse/section.rs | 43 +++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/git-config/src/parse/section.rs b/git-config/src/parse/section.rs index 29ffa65739d..4473112069e 100644 --- a/git-config/src/parse/section.rs +++ b/git-config/src/parse/section.rs @@ -130,7 +130,7 @@ mod types { macro_rules! generate_case_insensitive { ($name:ident, $cow_inner_type:ty, $comment:literal) => { #[doc = $comment] - #[derive(Clone, Eq, Ord, Debug, Default)] + #[derive(Clone, Eq, Debug, Default)] pub struct $name<'a>(pub std::borrow::Cow<'a, $cow_inner_type>); impl $name<'_> { @@ -166,12 +166,17 @@ mod types { } } - // TODO: compare without lowercase conversion impl PartialOrd for $name<'_> { fn partial_cmp(&self, other: &Self) -> Option { - self.0 - .to_ascii_lowercase() - .partial_cmp(&other.0.to_ascii_lowercase()) + self.cmp(other).into() + } + } + + impl Ord for $name<'_> { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + let a = self.0.iter().map(|c| c.to_ascii_lowercase()); + let b = other.0.iter().map(|c| c.to_ascii_lowercase()); + a.cmp(b) } } @@ -213,5 +218,33 @@ mod types { bstr::BStr, "Wrapper struct for key names, like `path` in `include.path`, since keys are case-insensitive." ); + + #[cfg(test)] + mod tests { + use super::*; + use std::cmp::Ordering; + + #[test] + fn case_insentive_eq() { + assert_eq!(Key::from("aBc"), Key::from("AbC")); + } + + #[test] + fn case_insentive_ord() { + assert_eq!(Key::from("a").cmp(&Key::from("a")), Ordering::Equal); + assert_eq!(Key::from("aBc").cmp(&Key::from("AbC")), Ordering::Equal); + } + + #[test] + fn case_insentive_hash() { + fn calculate_hash(t: T) -> u64 { + use std::hash::Hasher; + let mut s = std::collections::hash_map::DefaultHasher::new(); + t.hash(&mut s); + s.finish() + } + assert_eq!(calculate_hash(Key::from("aBc")), calculate_hash(Key::from("AbC"))); + } + } } pub use types::{Key, Name}; From 44d00611178a4e2f6a080574c41355a50b79b181 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 6 Jul 2022 20:58:47 +0800 Subject: [PATCH 048/366] Allocation-free hashing for section keys and names (#331) --- git-config/src/parse/section.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/git-config/src/parse/section.rs b/git-config/src/parse/section.rs index 4473112069e..a73c265cd3f 100644 --- a/git-config/src/parse/section.rs +++ b/git-config/src/parse/section.rs @@ -182,7 +182,9 @@ mod types { impl std::hash::Hash for $name<'_> { fn hash(&self, state: &mut H) { - self.0.to_ascii_lowercase().hash(state) + for b in self.0.iter() { + b.to_ascii_lowercase().hash(state); + } } } From b6b31e9c8dd8b3dc4860431069bb1cf5eacd1702 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 6 Jul 2022 21:02:07 +0800 Subject: [PATCH 049/366] change!: rename `parse::ParsedComment` into `parse::Comment` (#331) --- git-config/src/parse/comment.rs | 18 +++++++++--------- git-config/src/parse/mod.rs | 4 ++-- git-config/src/parse/nom/mod.rs | 6 +++--- git-config/src/parse/tests.rs | 6 +++--- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/git-config/src/parse/comment.rs b/git-config/src/parse/comment.rs index 47ac0f7f61e..1512ae9f80e 100644 --- a/git-config/src/parse/comment.rs +++ b/git-config/src/parse/comment.rs @@ -1,9 +1,9 @@ -use crate::parse::ParsedComment; +use crate::parse::Comment; use bstr::{BString, ByteVec}; use std::borrow::Cow; use std::fmt::Display; -impl ParsedComment<'_> { +impl Comment<'_> { /// Coerces into an owned instance. This differs from the standard [`clone`] /// implementation as calling clone will _not_ copy the borrowed variant, /// while this method will. In other words: @@ -19,15 +19,15 @@ impl ParsedComment<'_> { /// /// [`clone`]: Self::clone #[must_use] - pub fn to_owned(&self) -> ParsedComment<'static> { - ParsedComment { + pub fn to_owned(&self) -> Comment<'static> { + Comment { comment_tag: self.comment_tag, comment: Cow::Owned(self.comment.as_ref().into()), } } } -impl Display for ParsedComment<'_> { +impl Display for Comment<'_> { /// Note that this is a best-effort attempt at printing an comment. If /// there are non UTF-8 values in your config, this will _NOT_ render /// as read. @@ -41,14 +41,14 @@ impl Display for ParsedComment<'_> { } } -impl From> for BString { - fn from(c: ParsedComment<'_>) -> Self { +impl From> for BString { + fn from(c: Comment<'_>) -> Self { c.into() } } -impl From<&ParsedComment<'_>> for BString { - fn from(c: &ParsedComment<'_>) -> Self { +impl From<&Comment<'_>> for BString { + fn from(c: &Comment<'_>) -> Self { let mut values = BString::from(vec![c.comment_tag]); values.push_str(c.comment.as_ref()); values diff --git a/git-config/src/parse/mod.rs b/git-config/src/parse/mod.rs index 753c9681b7c..143e2f57f24 100644 --- a/git-config/src/parse/mod.rs +++ b/git-config/src/parse/mod.rs @@ -243,7 +243,7 @@ pub enum Event<'a> { /// A comment with a comment tag and the comment itself. Note that the /// comment itself may contain additional whitespace and comment markers /// at the beginning. - Comment(ParsedComment<'a>), + Comment(Comment<'a>), /// A section header containing the section name and a subsection, if it /// exists, like `remote "origin"`. SectionHeader(section::Header<'a>), @@ -290,7 +290,7 @@ pub mod section; /// A parsed comment event containing the comment marker and comment. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] -pub struct ParsedComment<'a> { +pub struct Comment<'a> { /// The comment marker used. This is either a semicolon or octothorpe. pub comment_tag: u8, /// The parsed comment. diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index b405bf38376..65f05042927 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -1,4 +1,4 @@ -use crate::parse::{section, Error, Event, ParsedComment, Section, State}; +use crate::parse::{section, Comment, Error, Event, Section, State}; use bstr::{BStr, BString, ByteSlice, ByteVec}; use std::borrow::Cow; @@ -155,12 +155,12 @@ pub fn from_bytes_owned(input: &[u8]) -> Result, Error<'static>> Ok(State { frontmatter, sections }) } -fn comment(i: &[u8]) -> IResult<&[u8], ParsedComment<'_>> { +fn comment(i: &[u8]) -> IResult<&[u8], Comment<'_>> { let (i, comment_tag) = one_of(";#")(i)?; let (i, comment) = take_till(|c| c == b'\n')(i)?; Ok(( i, - ParsedComment { + Comment { comment_tag: comment_tag as u8, comment: Cow::Borrowed(comment.as_bstr()), }, diff --git a/git-config/src/parse/tests.rs b/git-config/src/parse/tests.rs index 2cdff6e9d09..f4b186afd2f 100644 --- a/git-config/src/parse/tests.rs +++ b/git-config/src/parse/tests.rs @@ -52,7 +52,7 @@ pub(crate) mod util { use std::borrow::Cow; - use crate::parse::{section, Event, ParsedComment}; + use crate::parse::{section, Comment, Event}; pub fn section_header( name: &str, @@ -106,8 +106,8 @@ pub(crate) mod util { Event::Comment(comment(tag, msg)) } - pub(crate) fn comment(comment_tag: char, comment: &'static str) -> ParsedComment<'static> { - ParsedComment { + pub(crate) fn comment(comment_tag: char, comment: &'static str) -> Comment<'static> { + Comment { comment_tag: comment_tag as u8, comment: Cow::Borrowed(comment.into()), } From a0f6252343a62b0b55eef02888ac00c09100687a Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 6 Jul 2022 21:11:05 +0800 Subject: [PATCH 050/366] change!: Turn `parse::ParseOrIoError` into `parse::state::from_path::Error` (#331) --- git-config/src/file/from_paths.rs | 2 +- git-config/src/file/init.rs | 2 +- git-config/src/parse/error.rs | 47 ++----------------------- git-config/src/parse/mod.rs | 17 ++------- git-config/src/parse/nom/mod.rs | 12 +++---- git-config/src/parse/nom/tests.rs | 22 ++++++------ git-config/src/parse/state.rs | 21 +++++++++-- git-config/tests/file/from_paths/mod.rs | 4 +-- 8 files changed, 45 insertions(+), 82 deletions(-) diff --git a/git-config/src/file/from_paths.rs b/git-config/src/file/from_paths.rs index 4dd2973fdab..e6e94073810 100644 --- a/git-config/src/file/from_paths.rs +++ b/git-config/src/file/from_paths.rs @@ -5,7 +5,7 @@ use crate::{parse, value::path::interpolate}; #[allow(missing_docs)] pub enum Error { #[error(transparent)] - ParserOrIoError(#[from] parse::ParserOrIoError<'static>), + Parse(#[from] parse::state::from_path::Error), #[error(transparent)] Interpolate(#[from] interpolate::Error), #[error("The maximum allowed length {} of the file include chain built by following nested resolve_includes is exceeded", .max_depth)] diff --git a/git-config/src/file/init.rs b/git-config/src/file/init.rs index d1a38a5dacd..4b7d522f8c3 100644 --- a/git-config/src/file/init.rs +++ b/git-config/src/file/init.rs @@ -18,7 +18,7 @@ impl<'a> File<'a> { /// /// Returns an error if there was an IO error or if the file wasn't a valid /// git-config file. - pub fn at>(path: P) -> Result> { + pub fn at>(path: P) -> Result { parse::State::from_path(path).map(Self::from) } diff --git a/git-config/src/parse/error.rs b/git-config/src/parse/error.rs index b1b66fc956e..755e9c3766e 100644 --- a/git-config/src/parse/error.rs +++ b/git-config/src/parse/error.rs @@ -1,15 +1,15 @@ -use crate::parse::{Error, ParserOrIoError}; +use crate::parse::Error; use std::fmt::Display; /// A list of parsers that parsing can fail on. This is used for pretty-printing /// errors #[derive(PartialEq, Debug, Clone, Copy)] -pub(crate) enum ParserNode { +pub(crate) enum ParseNode { SectionHeader, ConfigName, } -impl Display for ParserNode { +impl Display for ParseNode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::SectionHeader => write!(f, "section header"), @@ -84,44 +84,3 @@ impl Display for Error<'_> { } impl std::error::Error for Error<'_> {} - -impl ParserOrIoError<'_> { - /// Coerces into an owned instance. This differs from the standard [`clone`] - /// implementation as calling clone will _not_ copy the borrowed variant, - /// while this method will. In other words: - /// - /// | Borrow type | `.clone()` | `to_owned()` | - /// | ----------- | ---------- | ------------ | - /// | Borrowed | Borrowed | Owned | - /// | Owned | Owned | Owned | - /// - /// This can be most effectively seen by the differing lifetimes between the - /// two. This method guarantees a `'static` lifetime, while `clone` does - /// not. - /// - /// [`clone`]: std::clone::Clone::clone - #[must_use] - pub fn into_owned(self) -> ParserOrIoError<'static> { - match self { - ParserOrIoError::Parser(error) => ParserOrIoError::Parser(error.to_owned()), - ParserOrIoError::Io(error) => ParserOrIoError::Io(error), - } - } -} - -impl Display for ParserOrIoError<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ParserOrIoError::Parser(e) => e.fmt(f), - ParserOrIoError::Io(e) => e.fmt(f), - } - } -} - -impl From for ParserOrIoError<'_> { - fn from(e: std::io::Error) -> Self { - Self::Io(e) - } -} - -impl std::error::Error for ParserOrIoError<'_> {} diff --git a/git-config/src/parse/mod.rs b/git-config/src/parse/mod.rs index 143e2f57f24..2970c079287 100644 --- a/git-config/src/parse/mod.rs +++ b/git-config/src/parse/mod.rs @@ -226,7 +226,8 @@ pub struct State<'a> { sections: Vec>, } -mod state; +/// +pub mod state; /// Syntactic events that occurs in the config. Despite all these variants /// holding a [`Cow`] instead over a simple reference, the parser will only emit @@ -305,22 +306,10 @@ mod comment; #[derive(PartialEq, Debug)] pub struct Error<'a> { line_number: usize, - last_attempted_parser: error::ParserNode, + last_attempted_parser: error::ParseNode, parsed_until: Cow<'a, BStr>, } -/// An error type representing a Parser [`Error`] or an [`IO error`]. This is -/// returned from functions that will perform IO on top of standard parsing, -/// such as reading from a file. -/// -/// [`IO error`]: std::io::Error -#[derive(Debug)] -#[allow(missing_docs, clippy::module_name_repetitions)] -pub enum ParserOrIoError<'a> { - Parser(Error<'a>), - Io(std::io::Error), -} - mod error; mod nom; diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index 65f05042927..9ffd36d4c60 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -2,7 +2,7 @@ use crate::parse::{section, Comment, Error, Event, Section, State}; use bstr::{BStr, BString, ByteSlice, ByteVec}; use std::borrow::Cow; -use crate::parse::error::ParserNode; +use crate::parse::error::ParseNode; use nom::{ branch::alt, bytes::complete::{tag, take_till, take_while}, @@ -53,7 +53,7 @@ pub fn from_bytes(input: &[u8]) -> Result, Error<'_>> { }); } - let mut node = ParserNode::SectionHeader; + let mut node = ParseNode::SectionHeader; let maybe_sections = many1(|i| section(i, &mut node))(i); let (i, sections) = maybe_sections.map_err(|_| Error { @@ -125,7 +125,7 @@ pub fn from_bytes_owned(input: &[u8]) -> Result, Error<'static>> }); } - let mut node = ParserNode::SectionHeader; + let mut node = ParseNode::SectionHeader; let maybe_sections = many1(|i| section(i, &mut node))(i); let (i, sections) = maybe_sections.map_err(|_| Error { @@ -170,7 +170,7 @@ fn comment(i: &[u8]) -> IResult<&[u8], Comment<'_>> { #[cfg(test)] mod tests; -fn section<'a, 'b>(i: &'a [u8], node: &'b mut ParserNode) -> IResult<&'a [u8], (Section<'a>, usize)> { +fn section<'a, 'b>(i: &'a [u8], node: &'b mut ParseNode) -> IResult<&'a [u8], (Section<'a>, usize)> { let (mut i, section_header) = section_header(i)?; let mut newlines = 0; @@ -313,11 +313,11 @@ fn sub_section(i: &[u8]) -> IResult<&[u8], BString> { fn section_body<'a, 'b, 'c>( i: &'a [u8], - node: &'b mut ParserNode, + node: &'b mut ParseNode, items: &'c mut Vec>, ) -> IResult<&'a [u8], ()> { // maybe need to check for [ here - *node = ParserNode::ConfigName; + *node = ParseNode::ConfigName; let (i, name) = config_name(i)?; items.push(Event::SectionKey(section::Key(Cow::Borrowed(name)))); diff --git a/git-config/src/parse/nom/tests.rs b/git-config/src/parse/nom/tests.rs index f7f4415249b..6f4ee1e3bfa 100644 --- a/git-config/src/parse/nom/tests.rs +++ b/git-config/src/parse/nom/tests.rs @@ -98,11 +98,11 @@ mod section { comment_event, fully_consumed, name_event, newline_event, section_header as parsed_section_header, value_done_event, value_event, value_not_done_event, whitespace_event, }; - use crate::parse::{error::ParserNode, Event, Section}; + use crate::parse::{error::ParseNode, Event, Section}; #[test] fn empty_section() { - let mut node = ParserNode::SectionHeader; + let mut node = ParseNode::SectionHeader; assert_eq!( section(b"[test]", &mut node).unwrap(), fully_consumed(( @@ -117,7 +117,7 @@ mod section { #[test] fn simple_section() { - let mut node = ParserNode::SectionHeader; + let mut node = ParseNode::SectionHeader; let section_data = br#"[hello] a = b c @@ -155,7 +155,7 @@ mod section { #[test] fn section_with_empty_value() { - let mut node = ParserNode::SectionHeader; + let mut node = ParseNode::SectionHeader; let section_data = br#"[hello] a = b c= @@ -194,7 +194,7 @@ mod section { #[test] fn section_single_line() { - let mut node = ParserNode::SectionHeader; + let mut node = ParseNode::SectionHeader; assert_eq!( section(b"[hello] c", &mut node).unwrap(), fully_consumed(( @@ -209,7 +209,7 @@ mod section { #[test] fn section_very_commented() { - let mut node = ParserNode::SectionHeader; + let mut node = ParseNode::SectionHeader; let section_data = br#"[hello] ; commentA a = b # commentB ; commentC @@ -254,7 +254,7 @@ mod section { #[test] fn complex_continuation() { - let mut node = ParserNode::SectionHeader; + let mut node = ParseNode::SectionHeader; // This test is absolute hell. Good luck if this fails. assert_eq!( section(b"[section] a = 1 \"\\\"\\\na ; e \"\\\"\\\nd # \"b\t ; c", &mut node).unwrap(), @@ -283,7 +283,7 @@ mod section { #[test] fn quote_split_over_two_lines() { - let mut node = ParserNode::SectionHeader; + let mut node = ParseNode::SectionHeader; assert_eq!( section(b"[section \"a\"] b =\"\\\n;\";a", &mut node).unwrap(), fully_consumed(( @@ -307,7 +307,7 @@ mod section { #[test] fn section_handles_extranous_whitespace_before_comment() { - let mut node = ParserNode::SectionHeader; + let mut node = ParseNode::SectionHeader; assert_eq!( section(b"[s]hello #world", &mut node).unwrap(), fully_consumed(( @@ -482,11 +482,11 @@ mod value_no_continuation { mod section_body { use super::section_body; use crate::parse::tests::util::{name_event, value_event, whitespace_event}; - use crate::parse::{error::ParserNode, Event}; + use crate::parse::{error::ParseNode, Event}; #[test] fn whitespace_is_not_ambigious() { - let mut node = ParserNode::SectionHeader; + let mut node = ParseNode::SectionHeader; let mut vec = vec![]; assert!(section_body(b"a =b", &mut node, &mut vec).is_ok()); assert_eq!( diff --git a/git-config/src/parse/state.rs b/git-config/src/parse/state.rs index cfb46618990..01becf205b0 100644 --- a/git-config/src/parse/state.rs +++ b/git-config/src/parse/state.rs @@ -1,4 +1,4 @@ -use crate::parse::{Error, Event, ParserOrIoError, Section, State}; +use crate::parse::{Error, Event, Section, State}; use std::convert::TryFrom; use std::io::Read; @@ -18,11 +18,11 @@ impl State<'static> { /// Returns an error if there was an IO error or the read file is not a valid /// `git-config` This generally is due to either invalid names or if there's /// extraneous data succeeding valid `git-config` data. - pub fn from_path>(path: P) -> Result, ParserOrIoError<'static>> { + pub fn from_path>(path: P) -> Result, from_path::Error> { let mut bytes = vec![]; let mut file = std::fs::File::open(path)?; file.read_to_end(&mut bytes)?; - crate::parse::nom::from_bytes_owned(&bytes).map_err(ParserOrIoError::Parser) + crate::parse::nom::from_bytes_owned(&bytes).map_err(from_path::Error::Parse) } /// Parses the provided bytes, returning an [`State`] that contains allocated @@ -142,3 +142,18 @@ impl<'a> TryFrom<&'a [u8]> for State<'a> { crate::parse::nom::from_bytes(value) } } + +pub mod from_path { + /// An error type representing a Parser [`Error`] or an [`IO error`]. This is + /// returned from functions that will perform IO on top of standard parsing, + /// such as reading from a file. + /// + /// [`IO error`]: std::io::Error + #[derive(Debug, thiserror::Error)] + pub enum Error { + #[error(transparent)] + Parse(crate::parse::Error<'static>), + #[error(transparent)] + Io(#[from] std::io::Error), + } +} diff --git a/git-config/tests/file/from_paths/mod.rs b/git-config/tests/file/from_paths/mod.rs index 1f6117fe000..dfe097a596a 100644 --- a/git-config/tests/file/from_paths/mod.rs +++ b/git-config/tests/file/from_paths/mod.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, fs, io}; -use git_config::{file::from_paths::Error, parse::ParserOrIoError, File}; +use git_config::{file::from_paths::Error, parse, File}; use tempfile::tempdir; use crate::file::cow_str; @@ -18,7 +18,7 @@ fn file_not_found() { let paths = vec![config_path]; let error = File::from_paths(paths, Default::default()).unwrap_err(); assert!( - matches!(error, Error::ParserOrIoError(ParserOrIoError::Io(io_error)) if io_error.kind() == io::ErrorKind::NotFound) + matches!(error, Error::Parse(parse::state::from_path::Error::Io(io_error)) if io_error.kind() == io::ErrorKind::NotFound) ); } From 920d56e4f5141eeb536956cdc5fac042ddee3525 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 6 Jul 2022 21:11:28 +0800 Subject: [PATCH 051/366] adjust to changes in `git-config` (#331) --- git-repository/src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-repository/src/config.rs b/git-repository/src/config.rs index 4b8f589771f..110ef112f22 100644 --- a/git-repository/src/config.rs +++ b/git-repository/src/config.rs @@ -3,7 +3,7 @@ use crate::{bstr::BString, permission}; #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Could not open repository conifguration file")] - Open(#[from] git_config::parse::ParserOrIoError<'static>), + Open(#[from] git_config::parse::state::from_path::Error), #[error("Cannot handle objects formatted as {:?}", .name)] UnsupportedObjectFormat { name: crate::bstr::BString }, #[error("The value for '{}' cannot be empty", .key)] From aeca6cce7b4cfe67b18cd80abb600f1271ad6057 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 6 Jul 2022 21:13:07 +0800 Subject: [PATCH 052/366] Keep BStr even though str could be used. (#331) Now that everything is changed it seems like added danger if we wanted to use `str::from_utf8_unchecked()`, and I don't see much benefit right now. Maybe it's just too late, let's wait for demand for this. --- git-config/src/parse/section.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/git-config/src/parse/section.rs b/git-config/src/parse/section.rs index a73c265cd3f..79f2459fcc2 100644 --- a/git-config/src/parse/section.rs +++ b/git-config/src/parse/section.rs @@ -7,7 +7,6 @@ use std::fmt::Display; /// /// Note that section headers must be parsed as valid ASCII, and thus all valid /// instances must also necessarily be valid UTF-8. -// TODO: turn these into strings in with str::from_utf8_unchecked #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] pub struct Header<'a> { /// The name of the header. From ef5b48c71e0e78fa602699a2f8ca8563c10455c4 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 6 Jul 2022 21:17:09 +0800 Subject: [PATCH 053/366] keep str in value API (#331) These are ascii-only and validated, so that should always be OK to do and makes the API a bit simpler and more convenient, especially if one day we add `impl AsRef` for even more flexibility. --- git-config/src/file/access/raw.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-config/src/file/access/raw.rs b/git-config/src/file/access/raw.rs index 42fc3d8983b..a51f2b2c1d9 100644 --- a/git-config/src/file/access/raw.rs +++ b/git-config/src/file/access/raw.rs @@ -25,7 +25,7 @@ impl<'a> File<'a> { /// section and subsection, or if the section and subsection do not exist. pub fn raw_value<'lookup>( &self, - section_name: &'lookup str, // TODO: consider making this BStr, while keeping higher-level APIs as 'str' + section_name: &'lookup str, subsection_name: Option<&'lookup str>, key: &'lookup str, ) -> Result, lookup::existing::Error> { From 0845c84b6f694d97519d5f86a97bca49739df8bf Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 08:43:26 +0800 Subject: [PATCH 054/366] refactor (#331) --- git-config/src/file/mod.rs | 2 +- git-config/src/file/tests.rs | 204 ++++++++++++++++++++++ git-config/src/file/try_from_str_tests.rs | 200 --------------------- 3 files changed, 205 insertions(+), 201 deletions(-) create mode 100644 git-config/src/file/tests.rs delete mode 100644 git-config/src/file/try_from_str_tests.rs diff --git a/git-config/src/file/mod.rs b/git-config/src/file/mod.rs index 4bcc7773ae7..6f9b038f531 100644 --- a/git-config/src/file/mod.rs +++ b/git-config/src/file/mod.rs @@ -73,4 +73,4 @@ mod init; mod utils; #[cfg(test)] -mod try_from_str_tests; +mod tests; diff --git a/git-config/src/file/tests.rs b/git-config/src/file/tests.rs new file mode 100644 index 00000000000..403b8af040c --- /dev/null +++ b/git-config/src/file/tests.rs @@ -0,0 +1,204 @@ +mod try_from { + use crate::file::{LookupTreeNode, SectionId}; + use std::borrow::Cow; + use std::collections::HashMap; + use std::convert::TryFrom; + + use crate::{ + file::SectionBody, + parse::{ + section, + tests::util::{name_event, newline_event, section_header, value_event}, + Event, + }, + File, + }; + + #[test] + fn empty() { + let config = File::try_from("").unwrap(); + assert!(config.section_headers.is_empty()); + assert_eq!(config.section_id_counter, 0); + assert!(config.section_lookup_tree.is_empty()); + assert!(config.sections.is_empty()); + assert!(config.section_order.is_empty()); + } + + #[test] + fn single_section() { + let mut config = File::try_from("[core]\na=b\nc=d").unwrap(); + let expected_separators = { + let mut map = HashMap::new(); + map.insert(SectionId(0), section_header("core", None)); + map + }; + assert_eq!(config.section_headers, expected_separators); + assert_eq!(config.section_id_counter, 1); + let expected_lookup_tree = { + let mut tree = HashMap::new(); + tree.insert( + section::Name(Cow::Borrowed("core".into())), + vec![LookupTreeNode::Terminal(vec![SectionId(0)])], + ); + tree + }; + assert_eq!(config.section_lookup_tree, expected_lookup_tree); + let expected_sections = { + let mut sections = HashMap::new(); + sections.insert( + SectionId(0), + SectionBody::from(vec![ + newline_event(), + name_event("a"), + Event::KeyValueSeparator, + value_event("b"), + newline_event(), + name_event("c"), + Event::KeyValueSeparator, + value_event("d"), + ]), + ); + sections + }; + assert_eq!(config.sections, expected_sections); + assert_eq!(config.section_order.make_contiguous(), &[SectionId(0)]); + } + + #[test] + fn single_subsection() { + let mut config = File::try_from("[core.sub]\na=b\nc=d").unwrap(); + let expected_separators = { + let mut map = HashMap::new(); + map.insert(SectionId(0), section_header("core", (".", "sub"))); + map + }; + assert_eq!(config.section_headers, expected_separators); + assert_eq!(config.section_id_counter, 1); + let expected_lookup_tree = { + let mut tree = HashMap::new(); + let mut inner_tree = HashMap::new(); + inner_tree.insert(Cow::Borrowed("sub".into()), vec![SectionId(0)]); + tree.insert( + section::Name(Cow::Borrowed("core".into())), + vec![LookupTreeNode::NonTerminal(inner_tree)], + ); + tree + }; + assert_eq!(config.section_lookup_tree, expected_lookup_tree); + let expected_sections = { + let mut sections = HashMap::new(); + sections.insert( + SectionId(0), + SectionBody::from(vec![ + newline_event(), + name_event("a"), + Event::KeyValueSeparator, + value_event("b"), + newline_event(), + name_event("c"), + Event::KeyValueSeparator, + value_event("d"), + ]), + ); + sections + }; + assert_eq!(config.sections, expected_sections); + assert_eq!(config.section_order.make_contiguous(), &[SectionId(0)]); + } + + #[test] + fn multiple_sections() { + let mut config = File::try_from("[core]\na=b\nc=d\n[other]e=f").unwrap(); + let expected_separators = { + let mut map = HashMap::new(); + map.insert(SectionId(0), section_header("core", None)); + map.insert(SectionId(1), section_header("other", None)); + map + }; + assert_eq!(config.section_headers, expected_separators); + assert_eq!(config.section_id_counter, 2); + let expected_lookup_tree = { + let mut tree = HashMap::new(); + tree.insert( + section::Name(Cow::Borrowed("core".into())), + vec![LookupTreeNode::Terminal(vec![SectionId(0)])], + ); + tree.insert( + section::Name(Cow::Borrowed("other".into())), + vec![LookupTreeNode::Terminal(vec![SectionId(1)])], + ); + tree + }; + assert_eq!(config.section_lookup_tree, expected_lookup_tree); + let expected_sections = { + let mut sections = HashMap::new(); + sections.insert( + SectionId(0), + SectionBody::from(vec![ + newline_event(), + name_event("a"), + Event::KeyValueSeparator, + value_event("b"), + newline_event(), + name_event("c"), + Event::KeyValueSeparator, + value_event("d"), + newline_event(), + ]), + ); + sections.insert( + SectionId(1), + SectionBody::from(vec![name_event("e"), Event::KeyValueSeparator, value_event("f")]), + ); + sections + }; + assert_eq!(config.sections, expected_sections); + assert_eq!(config.section_order.make_contiguous(), &[SectionId(0), SectionId(1)]); + } + + #[test] + fn multiple_duplicate_sections() { + let mut config = File::try_from("[core]\na=b\nc=d\n[core]e=f").unwrap(); + let expected_separators = { + let mut map = HashMap::new(); + map.insert(SectionId(0), section_header("core", None)); + map.insert(SectionId(1), section_header("core", None)); + map + }; + assert_eq!(config.section_headers, expected_separators); + assert_eq!(config.section_id_counter, 2); + let expected_lookup_tree = { + let mut tree = HashMap::new(); + tree.insert( + section::Name(Cow::Borrowed("core".into())), + vec![LookupTreeNode::Terminal(vec![SectionId(0), SectionId(1)])], + ); + tree + }; + assert_eq!(config.section_lookup_tree, expected_lookup_tree); + let expected_sections = { + let mut sections = HashMap::new(); + sections.insert( + SectionId(0), + SectionBody::from(vec![ + newline_event(), + name_event("a"), + Event::KeyValueSeparator, + value_event("b"), + newline_event(), + name_event("c"), + Event::KeyValueSeparator, + value_event("d"), + newline_event(), + ]), + ); + sections.insert( + SectionId(1), + SectionBody::from(vec![name_event("e"), Event::KeyValueSeparator, value_event("f")]), + ); + sections + }; + assert_eq!(config.sections, expected_sections); + assert_eq!(config.section_order.make_contiguous(), &[SectionId(0), SectionId(1)]); + } +} diff --git a/git-config/src/file/try_from_str_tests.rs b/git-config/src/file/try_from_str_tests.rs deleted file mode 100644 index d9dbc0bf1a4..00000000000 --- a/git-config/src/file/try_from_str_tests.rs +++ /dev/null @@ -1,200 +0,0 @@ -use std::convert::TryFrom; - -use super::{Cow, HashMap, LookupTreeNode, SectionId}; -use crate::{ - file::SectionBody, - parse::{ - section, - tests::util::{name_event, newline_event, section_header, value_event}, - Event, - }, - File, -}; - -#[test] -fn parse_empty() { - let config = File::try_from("").unwrap(); - assert!(config.section_headers.is_empty()); - assert_eq!(config.section_id_counter, 0); - assert!(config.section_lookup_tree.is_empty()); - assert!(config.sections.is_empty()); - assert!(config.section_order.is_empty()); -} - -#[test] -fn parse_single_section() { - let mut config = File::try_from("[core]\na=b\nc=d").unwrap(); - let expected_separators = { - let mut map = HashMap::new(); - map.insert(SectionId(0), section_header("core", None)); - map - }; - assert_eq!(config.section_headers, expected_separators); - assert_eq!(config.section_id_counter, 1); - let expected_lookup_tree = { - let mut tree = HashMap::new(); - tree.insert( - section::Name(Cow::Borrowed("core".into())), - vec![LookupTreeNode::Terminal(vec![SectionId(0)])], - ); - tree - }; - assert_eq!(config.section_lookup_tree, expected_lookup_tree); - let expected_sections = { - let mut sections = HashMap::new(); - sections.insert( - SectionId(0), - SectionBody::from(vec![ - newline_event(), - name_event("a"), - Event::KeyValueSeparator, - value_event("b"), - newline_event(), - name_event("c"), - Event::KeyValueSeparator, - value_event("d"), - ]), - ); - sections - }; - assert_eq!(config.sections, expected_sections); - assert_eq!(config.section_order.make_contiguous(), &[SectionId(0)]); -} - -#[test] -fn parse_single_subsection() { - let mut config = File::try_from("[core.sub]\na=b\nc=d").unwrap(); - let expected_separators = { - let mut map = HashMap::new(); - map.insert(SectionId(0), section_header("core", (".", "sub"))); - map - }; - assert_eq!(config.section_headers, expected_separators); - assert_eq!(config.section_id_counter, 1); - let expected_lookup_tree = { - let mut tree = HashMap::new(); - let mut inner_tree = HashMap::new(); - inner_tree.insert(Cow::Borrowed("sub".into()), vec![SectionId(0)]); - tree.insert( - section::Name(Cow::Borrowed("core".into())), - vec![LookupTreeNode::NonTerminal(inner_tree)], - ); - tree - }; - assert_eq!(config.section_lookup_tree, expected_lookup_tree); - let expected_sections = { - let mut sections = HashMap::new(); - sections.insert( - SectionId(0), - SectionBody::from(vec![ - newline_event(), - name_event("a"), - Event::KeyValueSeparator, - value_event("b"), - newline_event(), - name_event("c"), - Event::KeyValueSeparator, - value_event("d"), - ]), - ); - sections - }; - assert_eq!(config.sections, expected_sections); - assert_eq!(config.section_order.make_contiguous(), &[SectionId(0)]); -} - -#[test] -fn parse_multiple_sections() { - let mut config = File::try_from("[core]\na=b\nc=d\n[other]e=f").unwrap(); - let expected_separators = { - let mut map = HashMap::new(); - map.insert(SectionId(0), section_header("core", None)); - map.insert(SectionId(1), section_header("other", None)); - map - }; - assert_eq!(config.section_headers, expected_separators); - assert_eq!(config.section_id_counter, 2); - let expected_lookup_tree = { - let mut tree = HashMap::new(); - tree.insert( - section::Name(Cow::Borrowed("core".into())), - vec![LookupTreeNode::Terminal(vec![SectionId(0)])], - ); - tree.insert( - section::Name(Cow::Borrowed("other".into())), - vec![LookupTreeNode::Terminal(vec![SectionId(1)])], - ); - tree - }; - assert_eq!(config.section_lookup_tree, expected_lookup_tree); - let expected_sections = { - let mut sections = HashMap::new(); - sections.insert( - SectionId(0), - SectionBody::from(vec![ - newline_event(), - name_event("a"), - Event::KeyValueSeparator, - value_event("b"), - newline_event(), - name_event("c"), - Event::KeyValueSeparator, - value_event("d"), - newline_event(), - ]), - ); - sections.insert( - SectionId(1), - SectionBody::from(vec![name_event("e"), Event::KeyValueSeparator, value_event("f")]), - ); - sections - }; - assert_eq!(config.sections, expected_sections); - assert_eq!(config.section_order.make_contiguous(), &[SectionId(0), SectionId(1)]); -} - -#[test] -fn parse_multiple_duplicate_sections() { - let mut config = File::try_from("[core]\na=b\nc=d\n[core]e=f").unwrap(); - let expected_separators = { - let mut map = HashMap::new(); - map.insert(SectionId(0), section_header("core", None)); - map.insert(SectionId(1), section_header("core", None)); - map - }; - assert_eq!(config.section_headers, expected_separators); - assert_eq!(config.section_id_counter, 2); - let expected_lookup_tree = { - let mut tree = HashMap::new(); - tree.insert( - section::Name(Cow::Borrowed("core".into())), - vec![LookupTreeNode::Terminal(vec![SectionId(0), SectionId(1)])], - ); - tree - }; - assert_eq!(config.section_lookup_tree, expected_lookup_tree); - let expected_sections = { - let mut sections = HashMap::new(); - sections.insert( - SectionId(0), - SectionBody::from(vec![ - newline_event(), - name_event("a"), - Event::KeyValueSeparator, - value_event("b"), - newline_event(), - name_event("c"), - Event::KeyValueSeparator, - value_event("d"), - newline_event(), - ]), - ); - sections.insert( - SectionId(1), - SectionBody::from(vec![name_event("e"), Event::KeyValueSeparator, value_event("f")]), - ); - sections - }; - assert_eq!(config.sections, expected_sections); - assert_eq!(config.section_order.make_contiguous(), &[SectionId(0), SectionId(1)]); -} From a0f7f44c4fca20d3c9b95a3fafe65cef84c760e7 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 08:48:23 +0800 Subject: [PATCH 055/366] refactor (#331) --- git-config/tests/config.rs | 4 ++-- git-config/tests/{parser => parse}/mod.rs | 0 git-config/tests/{values => value}/boolean.rs | 2 +- git-config/tests/{values => value}/color_attribute.rs | 0 git-config/tests/{values => value}/color_value.rs | 0 git-config/tests/{values => value}/integer.rs | 2 +- git-config/tests/{values => value}/mod.rs | 0 git-config/tests/{values => value}/normalize.rs | 0 git-config/tests/{values => value}/path.rs | 2 +- 9 files changed, 5 insertions(+), 5 deletions(-) rename git-config/tests/{parser => parse}/mod.rs (100%) rename git-config/tests/{values => value}/boolean.rs (98%) rename git-config/tests/{values => value}/color_attribute.rs (100%) rename git-config/tests/{values => value}/color_value.rs (100%) rename git-config/tests/{values => value}/integer.rs (98%) rename git-config/tests/{values => value}/mod.rs (100%) rename git-config/tests/{values => value}/normalize.rs (100%) rename git-config/tests/{values => value}/path.rs (99%) diff --git a/git-config/tests/config.rs b/git-config/tests/config.rs index 9e3795f9b29..874365a2d06 100644 --- a/git-config/tests/config.rs +++ b/git-config/tests/config.rs @@ -1,5 +1,5 @@ type Result = std::result::Result>; mod file; -mod parser; -mod values; +mod parse; +mod value; diff --git a/git-config/tests/parser/mod.rs b/git-config/tests/parse/mod.rs similarity index 100% rename from git-config/tests/parser/mod.rs rename to git-config/tests/parse/mod.rs diff --git a/git-config/tests/values/boolean.rs b/git-config/tests/value/boolean.rs similarity index 98% rename from git-config/tests/values/boolean.rs rename to git-config/tests/value/boolean.rs index 8f2246d2b8d..cce6dba7e71 100644 --- a/git-config/tests/values/boolean.rs +++ b/git-config/tests/value/boolean.rs @@ -3,7 +3,7 @@ use std::convert::TryFrom; use crate::file::cow_str; use git_config::value::{Boolean, TrueVariant}; -use crate::values::b; +use crate::value::b; #[test] fn from_str_false() { diff --git a/git-config/tests/values/color_attribute.rs b/git-config/tests/value/color_attribute.rs similarity index 100% rename from git-config/tests/values/color_attribute.rs rename to git-config/tests/value/color_attribute.rs diff --git a/git-config/tests/values/color_value.rs b/git-config/tests/value/color_value.rs similarity index 100% rename from git-config/tests/values/color_value.rs rename to git-config/tests/value/color_value.rs diff --git a/git-config/tests/values/integer.rs b/git-config/tests/value/integer.rs similarity index 98% rename from git-config/tests/values/integer.rs rename to git-config/tests/value/integer.rs index 4922819d654..6f44eb27b6f 100644 --- a/git-config/tests/values/integer.rs +++ b/git-config/tests/value/integer.rs @@ -2,7 +2,7 @@ use std::convert::TryFrom; use git_config::{value::Integer, value::IntegerSuffix}; -use crate::values::b; +use crate::value::b; #[test] fn from_str_no_suffix() { diff --git a/git-config/tests/values/mod.rs b/git-config/tests/value/mod.rs similarity index 100% rename from git-config/tests/values/mod.rs rename to git-config/tests/value/mod.rs diff --git a/git-config/tests/values/normalize.rs b/git-config/tests/value/normalize.rs similarity index 100% rename from git-config/tests/values/normalize.rs rename to git-config/tests/value/normalize.rs diff --git a/git-config/tests/values/path.rs b/git-config/tests/value/path.rs similarity index 99% rename from git-config/tests/values/path.rs rename to git-config/tests/value/path.rs index 1f90622cfd0..8cc47ce53d0 100644 --- a/git-config/tests/values/path.rs +++ b/git-config/tests/value/path.rs @@ -5,7 +5,7 @@ mod interpolate { use git_config::value::Path as InterpolatingPath; use crate::file::cow_str; - use crate::values::b; + use crate::value::b; use git_config::value::path::interpolate::Error; #[test] From d085037ad9c067af7ce3ba3ab6e5d5ddb45b4057 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 08:57:03 +0800 Subject: [PATCH 056/366] change!: rename `value::Color(Attribute|Value)` to `value::color::Attribute` and `value::color::Name`. (#331) --- git-config/src/value/color.rs | 46 ++++------ git-config/src/value/mod.rs | 19 +++- git-config/tests/file/access/read_only.rs | 8 +- git-config/tests/value/color.rs | 102 ++++++++++++++++++++++ git-config/tests/value/color_attribute.rs | 45 ---------- git-config/tests/value/color_value.rs | 52 ----------- git-config/tests/value/mod.rs | 4 +- 7 files changed, 139 insertions(+), 137 deletions(-) create mode 100644 git-config/tests/value/color.rs delete mode 100644 git-config/tests/value/color_attribute.rs delete mode 100644 git-config/tests/value/color_value.rs diff --git a/git-config/src/value/color.rs b/git-config/src/value/color.rs index fc3ee8c3b18..f1bd27511d5 100644 --- a/git-config/src/value/color.rs +++ b/git-config/src/value/color.rs @@ -1,27 +1,11 @@ use crate::value; +use crate::value::Color; use bstr::{BStr, BString}; use std::borrow::Cow; use std::convert::TryFrom; use std::fmt::Display; use std::str::FromStr; -/// Any value that may contain a foreground color, background color, a -/// collection of color (text) modifiers, or a combination of any of the -/// aforementioned values. -/// -/// Note that `git-config` allows color values to simply be a collection of -/// [`ColorAttribute`]s, and does not require a [`ColorValue`] for either the -/// foreground or background color. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] -pub struct Color { - /// A provided foreground color - pub foreground: Option, - /// A provided background color - pub background: Option, - /// A potentially empty list of text attributes - pub attributes: Vec, -} - impl Color { /// Generates a byte representation of the value. This should be used when /// non-UTF-8 sequences are present or a UTF-8 representation can't be @@ -74,8 +58,8 @@ impl TryFrom<&BStr> for Color { fn try_from(s: &BStr) -> Result { let s = std::str::from_utf8(s).map_err(|err| color_err(s).with_err(err))?; enum ColorItem { - Value(ColorValue), - Attr(ColorAttribute), + Value(Name), + Attr(Attribute), } let items = s.split_whitespace().filter_map(|s| { @@ -84,9 +68,9 @@ impl TryFrom<&BStr> for Color { } Some( - ColorValue::from_str(s) + Name::from_str(s) .map(ColorItem::Value) - .or_else(|_| ColorAttribute::from_str(s).map(ColorItem::Attr)), + .or_else(|_| Attribute::from_str(s).map(ColorItem::Attr)), ) }); @@ -150,7 +134,7 @@ impl From<&Color> for BString { /// ANSI color code, or a 24-bit hex value prefixed with an octothorpe. #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] #[allow(missing_docs)] -pub enum ColorValue { +pub enum Name { Normal, Black, BrightBlack, @@ -172,7 +156,7 @@ pub enum ColorValue { Rgb(u8, u8, u8), } -impl Display for ColorValue { +impl Display for Name { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Normal => write!(f, "normal"), @@ -199,7 +183,7 @@ impl Display for ColorValue { } #[cfg(feature = "serde")] -impl serde::Serialize for ColorValue { +impl serde::Serialize for Name { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, @@ -208,7 +192,7 @@ impl serde::Serialize for ColorValue { } } -impl FromStr for ColorValue { +impl FromStr for Name { type Err = value::parse::Error; fn from_str(s: &str) -> Result { @@ -264,7 +248,7 @@ impl FromStr for ColorValue { } } -impl TryFrom<&[u8]> for ColorValue { +impl TryFrom<&[u8]> for Name { type Error = value::parse::Error; fn try_from(s: &[u8]) -> Result { @@ -279,7 +263,7 @@ impl TryFrom<&[u8]> for ColorValue { /// variant. #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] #[allow(missing_docs)] -pub enum ColorAttribute { +pub enum Attribute { Bold, NoBold, Dim, @@ -296,7 +280,7 @@ pub enum ColorAttribute { NoStrike, } -impl Display for ColorAttribute { +impl Display for Attribute { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Bold => write!(f, "bold"), @@ -318,7 +302,7 @@ impl Display for ColorAttribute { } #[cfg(feature = "serde")] -impl serde::Serialize for ColorAttribute { +impl serde::Serialize for Attribute { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, @@ -342,7 +326,7 @@ impl serde::Serialize for ColorAttribute { } } -impl FromStr for ColorAttribute { +impl FromStr for Attribute { type Err = value::parse::Error; fn from_str(s: &str) -> Result { @@ -377,7 +361,7 @@ impl FromStr for ColorAttribute { } } -impl TryFrom<&[u8]> for ColorAttribute { +impl TryFrom<&[u8]> for Attribute { type Error = value::parse::Error; fn try_from(s: &[u8]) -> Result { diff --git a/git-config/src/value/mod.rs b/git-config/src/value/mod.rs index bb60d36f1ee..e44b318c6c5 100644 --- a/git-config/src/value/mod.rs +++ b/git-config/src/value/mod.rs @@ -4,8 +4,23 @@ pub use normalize::{normalize, normalize_bstr, normalize_bstring}; mod string; pub use string::String; -mod color; -pub use color::{Color, ColorAttribute, ColorValue}; +/// Any value that may contain a foreground color, background color, a +/// collection of color (text) modifiers, or a combination of any of the +/// aforementioned values. +/// +/// Note that `git-config` allows color values to simply be a collection of +/// [`color::Attribute`]s, and does not require a [`color::Name`] for either the +/// foreground or background color. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] +pub struct Color { + /// A provided foreground color + pub foreground: Option, + /// A provided background color + pub background: Option, + /// A potentially empty list of text attributes + pub attributes: Vec, +} +pub mod color; mod integer; pub use integer::{Integer, IntegerSuffix}; diff --git a/git-config/tests/file/access/read_only.rs b/git-config/tests/file/access/read_only.rs index f48a6ebe86f..9dd6f5a6026 100644 --- a/git-config/tests/file/access/read_only.rs +++ b/git-config/tests/file/access/read_only.rs @@ -1,6 +1,6 @@ use crate::file::cow_str; use bstr::BStr; -use git_config::value::{Boolean, Color, ColorAttribute, ColorValue, Integer, IntegerSuffix, String, TrueVariant}; +use git_config::value::{color, Boolean, Color, Integer, IntegerSuffix, String, TrueVariant}; use git_config::File; use std::{borrow::Cow, convert::TryFrom, error::Error}; @@ -68,9 +68,9 @@ fn get_value_for_all_provided_values() -> crate::Result { assert_eq!( file.value::("core", None, "color")?, Color { - foreground: Some(ColorValue::BrightGreen), - background: Some(ColorValue::Red), - attributes: vec![ColorAttribute::Bold] + foreground: Some(color::Name::BrightGreen), + background: Some(color::Name::Red), + attributes: vec![color::Attribute::Bold] } ); diff --git a/git-config/tests/value/color.rs b/git-config/tests/value/color.rs new file mode 100644 index 00000000000..ddf252092fc --- /dev/null +++ b/git-config/tests/value/color.rs @@ -0,0 +1,102 @@ +mod name { + use std::str::FromStr; + + use git_config::value::color::Name; + + #[test] + fn non_bright() { + assert_eq!(Name::from_str("normal"), Ok(Name::Normal)); + assert_eq!(Name::from_str("black"), Ok(Name::Black)); + assert_eq!(Name::from_str("red"), Ok(Name::Red)); + assert_eq!(Name::from_str("green"), Ok(Name::Green)); + assert_eq!(Name::from_str("yellow"), Ok(Name::Yellow)); + assert_eq!(Name::from_str("blue"), Ok(Name::Blue)); + assert_eq!(Name::from_str("magenta"), Ok(Name::Magenta)); + assert_eq!(Name::from_str("cyan"), Ok(Name::Cyan)); + assert_eq!(Name::from_str("white"), Ok(Name::White)); + } + + #[test] + fn bright() { + assert_eq!(Name::from_str("brightblack"), Ok(Name::BrightBlack)); + assert_eq!(Name::from_str("brightred"), Ok(Name::BrightRed)); + assert_eq!(Name::from_str("brightgreen"), Ok(Name::BrightGreen)); + assert_eq!(Name::from_str("brightyellow"), Ok(Name::BrightYellow)); + assert_eq!(Name::from_str("brightblue"), Ok(Name::BrightBlue)); + assert_eq!(Name::from_str("brightmagenta"), Ok(Name::BrightMagenta)); + assert_eq!(Name::from_str("brightcyan"), Ok(Name::BrightCyan)); + assert_eq!(Name::from_str("brightwhite"), Ok(Name::BrightWhite)); + } + + #[test] + fn ansi() { + assert_eq!(Name::from_str("255"), Ok(Name::Ansi(255))); + assert_eq!(Name::from_str("0"), Ok(Name::Ansi(0))); + } + + #[test] + fn hex() { + assert_eq!(Name::from_str("#ff0010"), Ok(Name::Rgb(255, 0, 16))); + assert_eq!(Name::from_str("#ffffff"), Ok(Name::Rgb(255, 255, 255))); + assert_eq!(Name::from_str("#000000"), Ok(Name::Rgb(0, 0, 0))); + } + + #[test] + fn invalid() { + assert!(Name::from_str("brightnormal").is_err()); + assert!(Name::from_str("").is_err()); + assert!(Name::from_str("bright").is_err()); + assert!(Name::from_str("256").is_err()); + assert!(Name::from_str("#").is_err()); + assert!(Name::from_str("#fff").is_err()); + assert!(Name::from_str("#gggggg").is_err()); + } +} + +mod attribute { + use std::str::FromStr; + + use git_config::value::color::Attribute; + + #[test] + fn non_inverted() { + assert_eq!(Attribute::from_str("bold"), Ok(Attribute::Bold)); + assert_eq!(Attribute::from_str("dim"), Ok(Attribute::Dim)); + assert_eq!(Attribute::from_str("ul"), Ok(Attribute::Ul)); + assert_eq!(Attribute::from_str("blink"), Ok(Attribute::Blink)); + assert_eq!(Attribute::from_str("reverse"), Ok(Attribute::Reverse)); + assert_eq!(Attribute::from_str("italic"), Ok(Attribute::Italic)); + assert_eq!(Attribute::from_str("strike"), Ok(Attribute::Strike)); + } + + #[test] + fn inverted_no_dash() { + assert_eq!(Attribute::from_str("nobold"), Ok(Attribute::NoBold)); + assert_eq!(Attribute::from_str("nodim"), Ok(Attribute::NoDim)); + assert_eq!(Attribute::from_str("noul"), Ok(Attribute::NoUl)); + assert_eq!(Attribute::from_str("noblink"), Ok(Attribute::NoBlink)); + assert_eq!(Attribute::from_str("noreverse"), Ok(Attribute::NoReverse)); + assert_eq!(Attribute::from_str("noitalic"), Ok(Attribute::NoItalic)); + assert_eq!(Attribute::from_str("nostrike"), Ok(Attribute::NoStrike)); + } + + #[test] + fn inverted_dashed() { + assert_eq!(Attribute::from_str("no-bold"), Ok(Attribute::NoBold)); + assert_eq!(Attribute::from_str("no-dim"), Ok(Attribute::NoDim)); + assert_eq!(Attribute::from_str("no-ul"), Ok(Attribute::NoUl)); + assert_eq!(Attribute::from_str("no-blink"), Ok(Attribute::NoBlink)); + assert_eq!(Attribute::from_str("no-reverse"), Ok(Attribute::NoReverse)); + assert_eq!(Attribute::from_str("no-italic"), Ok(Attribute::NoItalic)); + assert_eq!(Attribute::from_str("no-strike"), Ok(Attribute::NoStrike)); + } + + #[test] + fn invalid() { + assert!(Attribute::from_str("a").is_err()); + assert!(Attribute::from_str("no bold").is_err()); + assert!(Attribute::from_str("").is_err()); + assert!(Attribute::from_str("no").is_err()); + assert!(Attribute::from_str("no-").is_err()); + } +} diff --git a/git-config/tests/value/color_attribute.rs b/git-config/tests/value/color_attribute.rs deleted file mode 100644 index bec1f3901f1..00000000000 --- a/git-config/tests/value/color_attribute.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::str::FromStr; - -use git_config::value::ColorAttribute; - -#[test] -fn non_inverted() { - assert_eq!(ColorAttribute::from_str("bold"), Ok(ColorAttribute::Bold)); - assert_eq!(ColorAttribute::from_str("dim"), Ok(ColorAttribute::Dim)); - assert_eq!(ColorAttribute::from_str("ul"), Ok(ColorAttribute::Ul)); - assert_eq!(ColorAttribute::from_str("blink"), Ok(ColorAttribute::Blink)); - assert_eq!(ColorAttribute::from_str("reverse"), Ok(ColorAttribute::Reverse)); - assert_eq!(ColorAttribute::from_str("italic"), Ok(ColorAttribute::Italic)); - assert_eq!(ColorAttribute::from_str("strike"), Ok(ColorAttribute::Strike)); -} - -#[test] -fn inverted_no_dash() { - assert_eq!(ColorAttribute::from_str("nobold"), Ok(ColorAttribute::NoBold)); - assert_eq!(ColorAttribute::from_str("nodim"), Ok(ColorAttribute::NoDim)); - assert_eq!(ColorAttribute::from_str("noul"), Ok(ColorAttribute::NoUl)); - assert_eq!(ColorAttribute::from_str("noblink"), Ok(ColorAttribute::NoBlink)); - assert_eq!(ColorAttribute::from_str("noreverse"), Ok(ColorAttribute::NoReverse)); - assert_eq!(ColorAttribute::from_str("noitalic"), Ok(ColorAttribute::NoItalic)); - assert_eq!(ColorAttribute::from_str("nostrike"), Ok(ColorAttribute::NoStrike)); -} - -#[test] -fn inverted_dashed() { - assert_eq!(ColorAttribute::from_str("no-bold"), Ok(ColorAttribute::NoBold)); - assert_eq!(ColorAttribute::from_str("no-dim"), Ok(ColorAttribute::NoDim)); - assert_eq!(ColorAttribute::from_str("no-ul"), Ok(ColorAttribute::NoUl)); - assert_eq!(ColorAttribute::from_str("no-blink"), Ok(ColorAttribute::NoBlink)); - assert_eq!(ColorAttribute::from_str("no-reverse"), Ok(ColorAttribute::NoReverse)); - assert_eq!(ColorAttribute::from_str("no-italic"), Ok(ColorAttribute::NoItalic)); - assert_eq!(ColorAttribute::from_str("no-strike"), Ok(ColorAttribute::NoStrike)); -} - -#[test] -fn invalid() { - assert!(ColorAttribute::from_str("a").is_err()); - assert!(ColorAttribute::from_str("no bold").is_err()); - assert!(ColorAttribute::from_str("").is_err()); - assert!(ColorAttribute::from_str("no").is_err()); - assert!(ColorAttribute::from_str("no-").is_err()); -} diff --git a/git-config/tests/value/color_value.rs b/git-config/tests/value/color_value.rs deleted file mode 100644 index 514582702d3..00000000000 --- a/git-config/tests/value/color_value.rs +++ /dev/null @@ -1,52 +0,0 @@ -use std::str::FromStr; - -use git_config::value::ColorValue; - -#[test] -fn non_bright() { - assert_eq!(ColorValue::from_str("normal"), Ok(ColorValue::Normal)); - assert_eq!(ColorValue::from_str("black"), Ok(ColorValue::Black)); - assert_eq!(ColorValue::from_str("red"), Ok(ColorValue::Red)); - assert_eq!(ColorValue::from_str("green"), Ok(ColorValue::Green)); - assert_eq!(ColorValue::from_str("yellow"), Ok(ColorValue::Yellow)); - assert_eq!(ColorValue::from_str("blue"), Ok(ColorValue::Blue)); - assert_eq!(ColorValue::from_str("magenta"), Ok(ColorValue::Magenta)); - assert_eq!(ColorValue::from_str("cyan"), Ok(ColorValue::Cyan)); - assert_eq!(ColorValue::from_str("white"), Ok(ColorValue::White)); -} - -#[test] -fn bright() { - assert_eq!(ColorValue::from_str("brightblack"), Ok(ColorValue::BrightBlack)); - assert_eq!(ColorValue::from_str("brightred"), Ok(ColorValue::BrightRed)); - assert_eq!(ColorValue::from_str("brightgreen"), Ok(ColorValue::BrightGreen)); - assert_eq!(ColorValue::from_str("brightyellow"), Ok(ColorValue::BrightYellow)); - assert_eq!(ColorValue::from_str("brightblue"), Ok(ColorValue::BrightBlue)); - assert_eq!(ColorValue::from_str("brightmagenta"), Ok(ColorValue::BrightMagenta)); - assert_eq!(ColorValue::from_str("brightcyan"), Ok(ColorValue::BrightCyan)); - assert_eq!(ColorValue::from_str("brightwhite"), Ok(ColorValue::BrightWhite)); -} - -#[test] -fn ansi() { - assert_eq!(ColorValue::from_str("255"), Ok(ColorValue::Ansi(255))); - assert_eq!(ColorValue::from_str("0"), Ok(ColorValue::Ansi(0))); -} - -#[test] -fn hex() { - assert_eq!(ColorValue::from_str("#ff0010"), Ok(ColorValue::Rgb(255, 0, 16))); - assert_eq!(ColorValue::from_str("#ffffff"), Ok(ColorValue::Rgb(255, 255, 255))); - assert_eq!(ColorValue::from_str("#000000"), Ok(ColorValue::Rgb(0, 0, 0))); -} - -#[test] -fn invalid() { - assert!(ColorValue::from_str("brightnormal").is_err()); - assert!(ColorValue::from_str("").is_err()); - assert!(ColorValue::from_str("bright").is_err()); - assert!(ColorValue::from_str("256").is_err()); - assert!(ColorValue::from_str("#").is_err()); - assert!(ColorValue::from_str("#fff").is_err()); - assert!(ColorValue::from_str("#gggggg").is_err()); -} diff --git a/git-config/tests/value/mod.rs b/git-config/tests/value/mod.rs index c73ba9769ea..ea81affa7e1 100644 --- a/git-config/tests/value/mod.rs +++ b/git-config/tests/value/mod.rs @@ -11,8 +11,6 @@ mod boolean; mod integer; -mod color_value; - -mod color_attribute; +mod color; mod path; From 8bcaec0599cf085a73b344f4f53fc023f6e31430 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 09:02:44 +0800 Subject: [PATCH 057/366] change!: rename `IntegerSuffix` to `integer::Suffix` (#331) --- git-config/src/value/integer.rs | 39 +++++++---------------- git-config/src/value/mod.rs | 22 +++++++++++-- git-config/tests/file/access/read_only.rs | 4 +-- git-config/tests/value/integer.rs | 8 ++--- 4 files changed, 37 insertions(+), 36 deletions(-) diff --git a/git-config/src/value/integer.rs b/git-config/src/value/integer.rs index aa61fa2dc82..1a4925f3327 100644 --- a/git-config/src/value/integer.rs +++ b/git-config/src/value/integer.rs @@ -5,24 +5,7 @@ use std::convert::TryFrom; use std::fmt::Display; use std::str::FromStr; -/// Any value that can be interpreted as an integer. -/// -/// This supports any numeric value that can fit in a [`i64`], excluding the -/// suffix. The suffix is parsed separately from the value itself, so if you -/// wish to obtain the true value of the integer, you must account for the -/// suffix after fetching the value. [`IntegerSuffix`] provides -/// [`bitwise_offset`] to help with the math, but do be warned that if the value -/// is very large, you may run into overflows. -/// -/// [`BStr`]: bstr::BStr -/// [`bitwise_offset`]: IntegerSuffix::bitwise_offset -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] -pub struct Integer { - /// The value, without any suffix modification - pub value: i64, - /// A provided suffix, if any. - pub suffix: Option, -} +use value::Integer; impl Integer { /// Generates a byte representation of the value. This should be used when @@ -42,9 +25,9 @@ impl Integer { match self.suffix { None => Some(self.value), Some(suffix) => match suffix { - IntegerSuffix::Kibi => self.value.checked_mul(1024), - IntegerSuffix::Mebi => self.value.checked_mul(1024 * 1024), - IntegerSuffix::Gibi => self.value.checked_mul(1024 * 1024 * 1024), + Suffix::Kibi => self.value.checked_mul(1024), + Suffix::Mebi => self.value.checked_mul(1024 * 1024), + Suffix::Gibi => self.value.checked_mul(1024 * 1024 * 1024), }, } } @@ -145,13 +128,13 @@ impl From<&Integer> for BString { /// These values are base-2 unit of measurements, not the base-10 variants. #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] #[allow(missing_docs)] -pub enum IntegerSuffix { +pub enum Suffix { Kibi, Mebi, Gibi, } -impl IntegerSuffix { +impl Suffix { /// Returns the number of bits that the suffix shifts left by. #[must_use] pub const fn bitwise_offset(self) -> usize { @@ -163,7 +146,7 @@ impl IntegerSuffix { } } -impl Display for IntegerSuffix { +impl Display for Suffix { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Kibi => write!(f, "k"), @@ -174,7 +157,7 @@ impl Display for IntegerSuffix { } #[cfg(feature = "serde")] -impl serde::Serialize for IntegerSuffix { +impl serde::Serialize for Suffix { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, @@ -187,7 +170,7 @@ impl serde::Serialize for IntegerSuffix { } } -impl FromStr for IntegerSuffix { +impl FromStr for Suffix { type Err = (); fn from_str(s: &str) -> Result { @@ -200,7 +183,7 @@ impl FromStr for IntegerSuffix { } } -impl TryFrom<&[u8]> for IntegerSuffix { +impl TryFrom<&[u8]> for Suffix { type Error = (); fn try_from(s: &[u8]) -> Result { @@ -208,7 +191,7 @@ impl TryFrom<&[u8]> for IntegerSuffix { } } -impl TryFrom for IntegerSuffix { +impl TryFrom for Suffix { type Error = (); fn try_from(value: BString) -> Result { diff --git a/git-config/src/value/mod.rs b/git-config/src/value/mod.rs index e44b318c6c5..ff0fd6eaf5b 100644 --- a/git-config/src/value/mod.rs +++ b/git-config/src/value/mod.rs @@ -22,8 +22,26 @@ pub struct Color { } pub mod color; -mod integer; -pub use integer::{Integer, IntegerSuffix}; +/// Any value that can be interpreted as an integer. +/// +/// This supports any numeric value that can fit in a [`i64`], excluding the +/// suffix. The suffix is parsed separately from the value itself, so if you +/// wish to obtain the true value of the integer, you must account for the +/// suffix after fetching the value. [`IntegerSuffix`] provides +/// [`bitwise_offset`] to help with the math, but do be warned that if the value +/// is very large, you may run into overflows. +/// +/// [`BStr`]: bstr::BStr +/// [`bitwise_offset`]: IntegerSuffix::bitwise_offset +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub struct Integer { + /// The value, without any suffix modification + pub value: i64, + /// A provided suffix, if any. + pub suffix: Option, +} + +pub mod integer; mod boolean; pub use boolean::{Boolean, TrueVariant}; diff --git a/git-config/tests/file/access/read_only.rs b/git-config/tests/file/access/read_only.rs index 9dd6f5a6026..b9bfcc16097 100644 --- a/git-config/tests/file/access/read_only.rs +++ b/git-config/tests/file/access/read_only.rs @@ -1,6 +1,6 @@ use crate::file::cow_str; use bstr::BStr; -use git_config::value::{color, Boolean, Color, Integer, IntegerSuffix, String, TrueVariant}; +use git_config::value::{color, integer, Boolean, Color, Integer, String, TrueVariant}; use git_config::File; use std::{borrow::Cow, convert::TryFrom, error::Error}; @@ -61,7 +61,7 @@ fn get_value_for_all_provided_values() -> crate::Result { file.value::("core", None, "integer-prefix")?, Integer { value: 10, - suffix: Some(IntegerSuffix::Gibi), + suffix: Some(integer::Suffix::Gibi), } ); diff --git a/git-config/tests/value/integer.rs b/git-config/tests/value/integer.rs index 6f44eb27b6f..c75ac6a5d43 100644 --- a/git-config/tests/value/integer.rs +++ b/git-config/tests/value/integer.rs @@ -1,6 +1,6 @@ use std::convert::TryFrom; -use git_config::{value::Integer, value::IntegerSuffix}; +use git_config::value::{integer::Suffix, Integer}; use crate::value::b; @@ -23,7 +23,7 @@ fn from_str_with_suffix() { Integer::try_from(b("1k")).unwrap(), Integer { value: 1, - suffix: Some(IntegerSuffix::Kibi), + suffix: Some(Suffix::Kibi), } ); @@ -31,7 +31,7 @@ fn from_str_with_suffix() { Integer::try_from(b("1m")).unwrap(), Integer { value: 1, - suffix: Some(IntegerSuffix::Mebi), + suffix: Some(Suffix::Mebi), } ); @@ -39,7 +39,7 @@ fn from_str_with_suffix() { Integer::try_from(b("1g")).unwrap(), Integer { value: 1, - suffix: Some(IntegerSuffix::Gibi), + suffix: Some(Suffix::Gibi), } ); } From 7e8a22590297f2f4aab76b53be512353637fb651 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 09:07:38 +0800 Subject: [PATCH 058/366] change!: rename `value::TrueVariant` to `value::boolean::True` (#331) --- .../src/file/access/low_level/read_only.rs | 8 ++--- git-config/src/value/boolean.rs | 35 +++++++------------ git-config/src/value/mod.rs | 22 +++++++++--- git-config/tests/file/access/read_only.rs | 10 +++--- git-config/tests/value/boolean.rs | 10 +++--- 5 files changed, 43 insertions(+), 42 deletions(-) diff --git a/git-config/src/file/access/low_level/read_only.rs b/git-config/src/file/access/low_level/read_only.rs index 56c6c58c81a..f9b93844c8b 100644 --- a/git-config/src/file/access/low_level/read_only.rs +++ b/git-config/src/file/access/low_level/read_only.rs @@ -76,7 +76,7 @@ impl<'a> File<'a> { /// /// ``` /// # use git_config::File; - /// # use git_config::value::{Integer, String, Boolean, TrueVariant}; + /// # use git_config::value::{Integer, String, Boolean, boolean::True}; /// # use std::borrow::Cow; /// # use std::convert::TryFrom; /// let config = r#" @@ -93,8 +93,8 @@ impl<'a> File<'a> { /// assert_eq!( /// a_value, /// vec![ - /// Boolean::True(TrueVariant::Explicit(Cow::Borrowed("true".into()))), - /// Boolean::True(TrueVariant::Implicit), + /// Boolean::True(True::Explicit(Cow::Borrowed("true".into()))), + /// Boolean::True(True::Implicit), /// Boolean::False(Cow::Borrowed("false".into())), /// ] /// ); @@ -160,7 +160,7 @@ impl<'a> File<'a> { /// /// ``` /// # use git_config::File; - /// # use git_config::value::{Integer, Boolean, TrueVariant}; + /// # use git_config::value::{Integer, Boolean}; /// # use std::borrow::Cow; /// # use std::convert::TryFrom; /// let config = r#" diff --git a/git-config/src/value/boolean.rs b/git-config/src/value/boolean.rs index baa08761883..e2037033384 100644 --- a/git-config/src/value/boolean.rs +++ b/git-config/src/value/boolean.rs @@ -4,18 +4,7 @@ use std::borrow::Cow; use std::convert::TryFrom; use std::fmt::Display; -/// Any value that can be interpreted as a boolean. -/// -/// Note that while values can effectively be any byte string, the `git-config` -/// documentation has a strict subset of values that may be interpreted as a -/// boolean value, all of which are ASCII and thus UTF-8 representable. -/// Consequently, variants hold [`str`]s rather than [`[u8]`]s. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] -#[allow(missing_docs)] -pub enum Boolean<'a> { - True(TrueVariant<'a>), - False(Cow<'a, BStr>), -} +use value::Boolean; impl Boolean<'_> { /// Return ourselves as plain bool. @@ -45,7 +34,7 @@ impl<'a> TryFrom<&'a BStr> for Boolean<'a> { type Error = value::parse::Error; fn try_from(value: &'a BStr) -> Result { - if let Ok(v) = TrueVariant::try_from(value) { + if let Ok(v) = True::try_from(value) { return Ok(Self::True(v)); } @@ -75,7 +64,7 @@ impl TryFrom for Boolean<'_> { return Ok(Self::False(Cow::Owned(value))); } - TrueVariant::try_from(value).map(Self::True) + True::try_from(value).map(Self::True) } } @@ -146,13 +135,13 @@ impl serde::Serialize for Boolean<'_> { /// This enum is part of the [`Boolean`] struct. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] #[allow(missing_docs)] -pub enum TrueVariant<'a> { +pub enum True<'a> { Explicit(Cow<'a, BStr>), /// For values defined without a `= `. Implicit, } -impl<'a> TryFrom<&'a BStr> for TrueVariant<'a> { +impl<'a> TryFrom<&'a BStr> for True<'a> { type Error = value::parse::Error; fn try_from(value: &'a BStr) -> Result { @@ -170,7 +159,7 @@ impl<'a> TryFrom<&'a BStr> for TrueVariant<'a> { } } -impl TryFrom for TrueVariant<'_> { +impl TryFrom for True<'_> { type Error = value::parse::Error; fn try_from(value: BString) -> Result { @@ -188,7 +177,7 @@ impl TryFrom for TrueVariant<'_> { } } -impl Display for TrueVariant<'_> { +impl Display for True<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if let Self::Explicit(v) = self { write!(f, "{}", v) @@ -198,17 +187,17 @@ impl Display for TrueVariant<'_> { } } -impl<'a, 'b: 'a> From<&'b TrueVariant<'a>> for &'a BStr { - fn from(t: &'b TrueVariant<'a>) -> Self { +impl<'a, 'b: 'a> From<&'b True<'a>> for &'a BStr { + fn from(t: &'b True<'a>) -> Self { match t { - TrueVariant::Explicit(e) => e.as_ref(), - TrueVariant::Implicit => "".into(), + True::Explicit(e) => e.as_ref(), + True::Implicit => "".into(), } } } #[cfg(feature = "serde")] -impl serde::Serialize for TrueVariant<'_> { +impl serde::Serialize for True<'_> { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, diff --git a/git-config/src/value/mod.rs b/git-config/src/value/mod.rs index ff0fd6eaf5b..ca6dfcb1a0b 100644 --- a/git-config/src/value/mod.rs +++ b/git-config/src/value/mod.rs @@ -27,12 +27,12 @@ pub mod color; /// This supports any numeric value that can fit in a [`i64`], excluding the /// suffix. The suffix is parsed separately from the value itself, so if you /// wish to obtain the true value of the integer, you must account for the -/// suffix after fetching the value. [`IntegerSuffix`] provides +/// suffix after fetching the value. [`integer::Suffix`] provides /// [`bitwise_offset`] to help with the math, but do be warned that if the value /// is very large, you may run into overflows. /// /// [`BStr`]: bstr::BStr -/// [`bitwise_offset`]: IntegerSuffix::bitwise_offset +/// [`bitwise_offset`]: integer::Suffix::bitwise_offset #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] pub struct Integer { /// The value, without any suffix modification @@ -40,11 +40,23 @@ pub struct Integer { /// A provided suffix, if any. pub suffix: Option, } - +/// pub mod integer; -mod boolean; -pub use boolean::{Boolean, TrueVariant}; +/// Any value that can be interpreted as a boolean. +/// +/// Note that while values can effectively be any byte string, the `git-config` +/// documentation has a strict subset of values that may be interpreted as a +/// boolean value, all of which are ASCII and thus UTF-8 representable. +/// Consequently, variants hold [`str`]s rather than [`[u8]`]s. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +#[allow(missing_docs)] +pub enum Boolean<'a> { + True(boolean::True<'a>), + False(std::borrow::Cow<'a, bstr::BStr>), +} +/// +pub mod boolean; /// Any value that can be interpreted as a file path. /// diff --git a/git-config/tests/file/access/read_only.rs b/git-config/tests/file/access/read_only.rs index b9bfcc16097..7b6f71b9949 100644 --- a/git-config/tests/file/access/read_only.rs +++ b/git-config/tests/file/access/read_only.rs @@ -1,6 +1,6 @@ use crate::file::cow_str; use bstr::BStr; -use git_config::value::{color, integer, Boolean, Color, Integer, String, TrueVariant}; +use git_config::value::{boolean::True, color, integer, Boolean, Color, Integer, String}; use git_config::File; use std::{borrow::Cow, convert::TryFrom, error::Error}; @@ -30,12 +30,12 @@ fn get_value_for_all_provided_values() -> crate::Result { assert_eq!( file.value::("core", None, "bool-implicit")?, - Boolean::True(TrueVariant::Implicit) + Boolean::True(True::Implicit) ); assert_eq!( file.try_value::("core", None, "bool-implicit") .expect("exists")?, - Boolean::True(TrueVariant::Implicit) + Boolean::True(True::Implicit) ); assert!(file.boolean("core", None, "bool-implicit").expect("present")?); @@ -124,7 +124,7 @@ fn get_value_looks_up_all_sections_before_failing() -> crate::Result { // Checks that we check the last entry first still assert_eq!( file.value::("core", None, "bool-implicit")?, - Boolean::True(TrueVariant::Implicit) + Boolean::True(True::Implicit) ); assert_eq!( @@ -169,7 +169,7 @@ fn single_section() -> Result<(), Box> { let second_value: Boolean = config.value("core", None, "c")?; assert_eq!(first_value, String { value: cow_str("b") }); - assert_eq!(second_value, Boolean::True(TrueVariant::Implicit)); + assert_eq!(second_value, Boolean::True(True::Implicit)); Ok(()) } diff --git a/git-config/tests/value/boolean.rs b/git-config/tests/value/boolean.rs index cce6dba7e71..a7465cb2c64 100644 --- a/git-config/tests/value/boolean.rs +++ b/git-config/tests/value/boolean.rs @@ -1,7 +1,7 @@ use std::convert::TryFrom; use crate::file::cow_str; -use git_config::value::{Boolean, TrueVariant}; +use git_config::value::{boolean::True, Boolean}; use crate::value::b; @@ -18,19 +18,19 @@ fn from_str_false() { fn from_str_true() { assert_eq!( Boolean::try_from(b("yes")), - Ok(Boolean::True(TrueVariant::Explicit(cow_str("yes")))) + Ok(Boolean::True(True::Explicit(cow_str("yes")))) ); assert_eq!( Boolean::try_from(b("on")), - Ok(Boolean::True(TrueVariant::Explicit(cow_str("on")))) + Ok(Boolean::True(True::Explicit(cow_str("on")))) ); assert_eq!( Boolean::try_from(b("true")), - Ok(Boolean::True(TrueVariant::Explicit(cow_str("true")))) + Ok(Boolean::True(True::Explicit(cow_str("true")))) ); assert_eq!( Boolean::try_from(b("one")), - Ok(Boolean::True(TrueVariant::Explicit(cow_str("one")))) + Ok(Boolean::True(True::Explicit(cow_str("one")))) ); } From 748d921efd7469d5c19e40ddcb9099e2462e3bbc Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 09:13:37 +0800 Subject: [PATCH 059/366] change!: rename `value::parse::Error` to `value::Error`. (#331) --- git-config/src/file/access/comfort.rs | 10 +++++----- git-config/src/value/boolean.rs | 14 +++++++------- git-config/src/value/color.rs | 18 +++++++++--------- git-config/src/value/integer.rs | 10 +++++----- git-config/src/value/mod.rs | 5 +++-- 5 files changed, 29 insertions(+), 28 deletions(-) diff --git a/git-config/src/file/access/comfort.rs b/git-config/src/file/access/comfort.rs index 061060b085b..bdfe9e39ece 100644 --- a/git-config/src/file/access/comfort.rs +++ b/git-config/src/file/access/comfort.rs @@ -35,7 +35,7 @@ impl<'a> File<'a> { section_name: &str, subsection_name: Option<&str>, key: &str, - ) -> Option> { + ) -> Option> { self.raw_value(section_name, subsection_name, key) .ok() .map(|v| value::Boolean::try_from(v).map(|b| b.to_bool())) @@ -47,11 +47,11 @@ impl<'a> File<'a> { section_name: &str, subsection_name: Option<&str>, key: &str, - ) -> Option> { + ) -> Option> { let int = self.raw_value(section_name, subsection_name, key).ok()?; Some(value::Integer::try_from(int.as_ref()).and_then(|b| { b.to_decimal() - .ok_or_else(|| value::parse::Error::new("Integer overflow", int.into_owned())) + .ok_or_else(|| value::Error::new("Integer overflow", int.into_owned())) })) } @@ -69,7 +69,7 @@ impl<'a> File<'a> { section_name: &str, subsection_name: Option<&str>, key: &str, - ) -> Option, value::parse::Error>> { + ) -> Option, value::Error>> { self.raw_multi_value(section_name, subsection_name, key) .ok() .map(|values| { @@ -78,7 +78,7 @@ impl<'a> File<'a> { .map(|v| { value::Integer::try_from(v.as_ref()).and_then(|int| { int.to_decimal() - .ok_or_else(|| value::parse::Error::new("Integer overflow", v.into_owned())) + .ok_or_else(|| value::Error::new("Integer overflow", v.into_owned())) }) }) .collect() diff --git a/git-config/src/value/boolean.rs b/git-config/src/value/boolean.rs index e2037033384..53dc2b0b7bf 100644 --- a/git-config/src/value/boolean.rs +++ b/git-config/src/value/boolean.rs @@ -23,15 +23,15 @@ impl Boolean<'_> { } } -fn bool_err(input: impl Into) -> value::parse::Error { - value::parse::Error::new( +fn bool_err(input: impl Into) -> value::Error { + value::Error::new( "Booleans need to be 'no', 'off', 'false', 'zero' or 'yes', 'on', 'true', 'one'", input, ) } impl<'a> TryFrom<&'a BStr> for Boolean<'a> { - type Error = value::parse::Error; + type Error = value::Error; fn try_from(value: &'a BStr) -> Result { if let Ok(v) = True::try_from(value) { @@ -52,7 +52,7 @@ impl<'a> TryFrom<&'a BStr> for Boolean<'a> { } impl TryFrom for Boolean<'_> { - type Error = value::parse::Error; + type Error = value::Error; fn try_from(value: BString) -> Result { if value.eq_ignore_ascii_case(b"no") @@ -69,7 +69,7 @@ impl TryFrom for Boolean<'_> { } impl<'a> TryFrom> for Boolean<'a> { - type Error = value::parse::Error; + type Error = value::Error; fn try_from(c: Cow<'a, BStr>) -> Result { match c { Cow::Borrowed(c) => Self::try_from(c), @@ -142,7 +142,7 @@ pub enum True<'a> { } impl<'a> TryFrom<&'a BStr> for True<'a> { - type Error = value::parse::Error; + type Error = value::Error; fn try_from(value: &'a BStr) -> Result { if value.eq_ignore_ascii_case(b"yes") @@ -160,7 +160,7 @@ impl<'a> TryFrom<&'a BStr> for True<'a> { } impl TryFrom for True<'_> { - type Error = value::parse::Error; + type Error = value::Error; fn try_from(value: BString) -> Result { if value.eq_ignore_ascii_case(b"yes") diff --git a/git-config/src/value/color.rs b/git-config/src/value/color.rs index f1bd27511d5..88a655b16ec 100644 --- a/git-config/src/value/color.rs +++ b/git-config/src/value/color.rs @@ -45,15 +45,15 @@ impl serde::Serialize for Color { } } -fn color_err(input: impl Into) -> value::parse::Error { - value::parse::Error::new( +fn color_err(input: impl Into) -> value::Error { + value::Error::new( "Colors are specific color values and their attributes, like 'brightred', or 'blue'", input, ) } impl TryFrom<&BStr> for Color { - type Error = value::parse::Error; + type Error = value::Error; fn try_from(s: &BStr) -> Result { let s = std::str::from_utf8(s).map_err(|err| color_err(s).with_err(err))?; @@ -98,7 +98,7 @@ impl TryFrom<&BStr> for Color { } impl TryFrom for Color { - type Error = value::parse::Error; + type Error = value::Error; fn try_from(value: BString) -> Result { Self::try_from(value.as_ref()) @@ -106,7 +106,7 @@ impl TryFrom for Color { } impl TryFrom> for Color { - type Error = value::parse::Error; + type Error = value::Error; fn try_from(c: Cow<'_, BStr>) -> Result { match c { @@ -193,7 +193,7 @@ impl serde::Serialize for Name { } impl FromStr for Name { - type Err = value::parse::Error; + type Err = value::Error; fn from_str(s: &str) -> Result { let mut s = s; @@ -249,7 +249,7 @@ impl FromStr for Name { } impl TryFrom<&[u8]> for Name { - type Error = value::parse::Error; + type Error = value::Error; fn try_from(s: &[u8]) -> Result { Self::from_str(std::str::from_utf8(s).map_err(|err| color_err(s).with_err(err))?) @@ -327,7 +327,7 @@ impl serde::Serialize for Attribute { } impl FromStr for Attribute { - type Err = value::parse::Error; + type Err = value::Error; fn from_str(s: &str) -> Result { let inverted = s.starts_with("no"); @@ -362,7 +362,7 @@ impl FromStr for Attribute { } impl TryFrom<&[u8]> for Attribute { - type Error = value::parse::Error; + type Error = value::Error; fn try_from(s: &[u8]) -> Result { Self::from_str(std::str::from_utf8(s).map_err(|err| color_err(s).with_err(err))?) diff --git a/git-config/src/value/integer.rs b/git-config/src/value/integer.rs index 1a4925f3327..d3a2df7b428 100644 --- a/git-config/src/value/integer.rs +++ b/git-config/src/value/integer.rs @@ -58,15 +58,15 @@ impl serde::Serialize for Integer { } } -fn int_err(input: impl Into) -> value::parse::Error { - value::parse::Error::new( +fn int_err(input: impl Into) -> value::Error { + value::Error::new( "Integers needs to be positive or negative numbers which may have a suffix like 1k, 42, or 50G", input, ) } impl TryFrom<&BStr> for Integer { - type Error = value::parse::Error; + type Error = value::Error; fn try_from(s: &BStr) -> Result { let s = std::str::from_utf8(s).map_err(|err| int_err(s).with_err(err))?; @@ -93,7 +93,7 @@ impl TryFrom<&BStr> for Integer { } impl TryFrom for Integer { - type Error = value::parse::Error; + type Error = value::Error; fn try_from(value: BString) -> Result { Self::try_from(value.as_ref()) @@ -101,7 +101,7 @@ impl TryFrom for Integer { } impl TryFrom> for Integer { - type Error = value::parse::Error; + type Error = value::Error; fn try_from(c: Cow<'_, BStr>) -> Result { match c { diff --git a/git-config/src/value/mod.rs b/git-config/src/value/mod.rs index ca6dfcb1a0b..76544ffbbad 100644 --- a/git-config/src/value/mod.rs +++ b/git-config/src/value/mod.rs @@ -69,10 +69,10 @@ pub struct Path<'a> { /// pub mod path; -pub mod parse { +mod error { use bstr::BString; - /// The error returned when creating `Integer` from byte string. + /// The error returned when any config value couldn't be instantiated due to malformed input. #[derive(Debug, thiserror::Error, Eq, PartialEq)] #[allow(missing_docs)] #[error("Could not decode '{}': {}", .input, .message)] @@ -98,3 +98,4 @@ pub mod parse { } } } +pub use error::Error; From 3cdb0890b71e62cfa92b1ed1760c88cb547ec729 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 09:23:25 +0800 Subject: [PATCH 060/366] change!: move `value::*` into the crate root, except for `Error` and `normalize_*()`. (#331) --- git-config/src/file/access/comfort.rs | 10 +- .../src/file/access/low_level/read_only.rs | 6 +- git-config/src/file/from_env.rs | 2 +- git-config/src/file/from_paths.rs | 2 +- git-config/src/file/resolve_includes.rs | 10 +- git-config/src/lib.rs | 2 + git-config/src/value/mod.rs | 115 ++++-------------- git-config/src/{value => values}/boolean.rs | 3 +- git-config/src/{value => values}/color.rs | 2 +- git-config/src/{value => values}/integer.rs | 3 +- git-config/src/values/mod.rs | 67 ++++++++++ git-config/src/{value => values}/path.rs | 2 +- git-config/src/{value => values}/string.rs | 0 git-config/tests/file/access/read_only.rs | 4 +- git-config/tests/value/boolean.rs | 2 +- git-config/tests/value/color.rs | 4 +- git-config/tests/value/integer.rs | 2 +- git-config/tests/value/path.rs | 16 ++- 18 files changed, 122 insertions(+), 130 deletions(-) rename git-config/src/{value => values}/boolean.rs (99%) rename git-config/src/{value => values}/color.rs (99%) rename git-config/src/{value => values}/integer.rs (99%) create mode 100644 git-config/src/values/mod.rs rename git-config/src/{value => values}/path.rs (99%) rename git-config/src/{value => values}/string.rs (100%) diff --git a/git-config/src/file/access/comfort.rs b/git-config/src/file/access/comfort.rs index bdfe9e39ece..b13137f062d 100644 --- a/git-config/src/file/access/comfort.rs +++ b/git-config/src/file/access/comfort.rs @@ -23,10 +23,10 @@ impl<'a> File<'a> { // TODO: add `secure_path()` or similar to make use of our knowledge of the trust associated with each configuration // file, maybe even remove the insecure version to force every caller to ask themselves if the resource can // be used securely or not. - pub fn path(&'a self, section_name: &str, subsection_name: Option<&str>, key: &str) -> Option> { + pub fn path(&'a self, section_name: &str, subsection_name: Option<&str>, key: &str) -> Option> { self.raw_value(section_name, subsection_name, key) .ok() - .map(value::Path::from) + .map(crate::Path::from) } /// Like [`value()`][File::value()], but returning an `Option` if the boolean wasn't found. @@ -38,7 +38,7 @@ impl<'a> File<'a> { ) -> Option> { self.raw_value(section_name, subsection_name, key) .ok() - .map(|v| value::Boolean::try_from(v).map(|b| b.to_bool())) + .map(|v| crate::Boolean::try_from(v).map(|b| b.to_bool())) } /// Like [`value()`][File::value()], but returning an `Option` if the integer wasn't found. @@ -49,7 +49,7 @@ impl<'a> File<'a> { key: &str, ) -> Option> { let int = self.raw_value(section_name, subsection_name, key).ok()?; - Some(value::Integer::try_from(int.as_ref()).and_then(|b| { + Some(crate::Integer::try_from(int.as_ref()).and_then(|b| { b.to_decimal() .ok_or_else(|| value::Error::new("Integer overflow", int.into_owned())) })) @@ -76,7 +76,7 @@ impl<'a> File<'a> { values .into_iter() .map(|v| { - value::Integer::try_from(v.as_ref()).and_then(|int| { + crate::Integer::try_from(v.as_ref()).and_then(|int| { int.to_decimal() .ok_or_else(|| value::Error::new("Integer overflow", v.into_owned())) }) diff --git a/git-config/src/file/access/low_level/read_only.rs b/git-config/src/file/access/low_level/read_only.rs index f9b93844c8b..5bd60f4b8fc 100644 --- a/git-config/src/file/access/low_level/read_only.rs +++ b/git-config/src/file/access/low_level/read_only.rs @@ -19,7 +19,7 @@ impl<'a> File<'a> { /// /// ``` /// # use git_config::File; - /// # use git_config::value::{Integer, Boolean}; + /// # use git_config::{Integer, Boolean}; /// # use std::borrow::Cow; /// # use std::convert::TryFrom; /// let config = r#" @@ -76,7 +76,7 @@ impl<'a> File<'a> { /// /// ``` /// # use git_config::File; - /// # use git_config::value::{Integer, String, Boolean, boolean::True}; + /// # use git_config::{Integer, String, Boolean, boolean::True}; /// # use std::borrow::Cow; /// # use std::convert::TryFrom; /// let config = r#" @@ -160,7 +160,7 @@ impl<'a> File<'a> { /// /// ``` /// # use git_config::File; - /// # use git_config::value::{Integer, Boolean}; + /// # use git_config::{Integer, Boolean}; /// # use std::borrow::Cow; /// # use std::convert::TryFrom; /// let config = r#" diff --git a/git-config/src/file/from_env.rs b/git-config/src/file/from_env.rs index d25b1d8764d..d4e485edd08 100644 --- a/git-config/src/file/from_env.rs +++ b/git-config/src/file/from_env.rs @@ -4,7 +4,7 @@ use std::{borrow::Cow, path::PathBuf}; use crate::{ file::{from_paths, resolve_includes}, parse, - value::path::interpolate, + path::interpolate, File, }; diff --git a/git-config/src/file/from_paths.rs b/git-config/src/file/from_paths.rs index e6e94073810..77cf18aa01a 100644 --- a/git-config/src/file/from_paths.rs +++ b/git-config/src/file/from_paths.rs @@ -1,4 +1,4 @@ -use crate::{parse, value::path::interpolate}; +use crate::{parse, path::interpolate}; /// The error returned by [`File::from_paths()`][crate::File::from_paths()] and [`File::from_env_paths()`][crate::File::from_env_paths()]. #[derive(Debug, thiserror::Error)] diff --git a/git-config/src/file/resolve_includes.rs b/git-config/src/file/resolve_includes.rs index 884517b9b6e..77b50f7d629 100644 --- a/git-config/src/file/resolve_includes.rs +++ b/git-config/src/file/resolve_includes.rs @@ -10,7 +10,7 @@ use crate::file::from_paths::Options; use crate::{ file::{from_paths, SectionId}, parse::section, - value, File, + File, }; pub(crate) fn resolve_includes( @@ -85,10 +85,10 @@ fn resolve_includes_recursive( Ok(()) } -fn extract_include_path<'a>(target_config: &mut File<'a>, include_paths: &mut Vec>, id: SectionId) { +fn extract_include_path<'a>(target_config: &mut File<'a>, include_paths: &mut Vec>, id: SectionId) { if let Some(body) = target_config.sections.get(&id) { let paths = body.values(§ion::Key::from("path")); - let paths = paths.iter().map(|path| value::Path::from(path.clone())); + let paths = paths.iter().map(|path| crate::Path::from(path.clone())); include_paths.extend(paths); } } @@ -159,7 +159,7 @@ fn gitdir_matches( git_path::to_unix_separators_on_windows(git_path::into_bstr(git_dir.ok_or(from_paths::Error::MissingGitDir)?)); let mut pattern_path: Cow<'_, _> = { - let path = value::Path::from(Cow::Borrowed(condition_path)).interpolate(git_install_dir, home_dir)?; + let path = crate::Path::from(Cow::Borrowed(condition_path)).interpolate(git_install_dir, home_dir)?; git_path::into_bstr(path).into_owned().into() }; // NOTE: yes, only if we do path interpolation will the slashes be forced to unix separators on windows @@ -207,7 +207,7 @@ fn gitdir_matches( } fn resolve( - path: value::Path<'_>, + path: crate::Path<'_>, target_config_path: Option<&Path>, from_paths::Options { git_install_dir, diff --git a/git-config/src/lib.rs b/git-config/src/lib.rs index 1d587bb320a..98e14927446 100644 --- a/git-config/src/lib.rs +++ b/git-config/src/lib.rs @@ -58,6 +58,8 @@ pub mod fs; pub mod lookup; pub mod parse; pub mod value; +mod values; +pub use values::*; mod types; pub use types::File; diff --git a/git-config/src/value/mod.rs b/git-config/src/value/mod.rs index 76544ffbbad..6e38d185871 100644 --- a/git-config/src/value/mod.rs +++ b/git-config/src/value/mod.rs @@ -1,101 +1,28 @@ -mod normalize; -pub use normalize::{normalize, normalize_bstr, normalize_bstring}; - -mod string; -pub use string::String; - -/// Any value that may contain a foreground color, background color, a -/// collection of color (text) modifiers, or a combination of any of the -/// aforementioned values. -/// -/// Note that `git-config` allows color values to simply be a collection of -/// [`color::Attribute`]s, and does not require a [`color::Name`] for either the -/// foreground or background color. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] -pub struct Color { - /// A provided foreground color - pub foreground: Option, - /// A provided background color - pub background: Option, - /// A potentially empty list of text attributes - pub attributes: Vec, -} -pub mod color; - -/// Any value that can be interpreted as an integer. -/// -/// This supports any numeric value that can fit in a [`i64`], excluding the -/// suffix. The suffix is parsed separately from the value itself, so if you -/// wish to obtain the true value of the integer, you must account for the -/// suffix after fetching the value. [`integer::Suffix`] provides -/// [`bitwise_offset`] to help with the math, but do be warned that if the value -/// is very large, you may run into overflows. -/// -/// [`BStr`]: bstr::BStr -/// [`bitwise_offset`]: integer::Suffix::bitwise_offset -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] -pub struct Integer { - /// The value, without any suffix modification - pub value: i64, - /// A provided suffix, if any. - pub suffix: Option, -} -/// -pub mod integer; - -/// Any value that can be interpreted as a boolean. -/// -/// Note that while values can effectively be any byte string, the `git-config` -/// documentation has a strict subset of values that may be interpreted as a -/// boolean value, all of which are ASCII and thus UTF-8 representable. -/// Consequently, variants hold [`str`]s rather than [`[u8]`]s. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +/// The error returned when any config value couldn't be instantiated due to malformed input. +#[derive(Debug, thiserror::Error, Eq, PartialEq)] #[allow(missing_docs)] -pub enum Boolean<'a> { - True(boolean::True<'a>), - False(std::borrow::Cow<'a, bstr::BStr>), -} -/// -pub mod boolean; - -/// Any value that can be interpreted as a file path. -/// -/// Git represents file paths as byte arrays, modeled here as owned or borrowed byte sequences. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] -pub struct Path<'a> { - /// The path string, un-interpolated - pub value: std::borrow::Cow<'a, bstr::BStr>, +#[error("Could not decode '{}': {}", .input, .message)] +pub struct Error { + pub message: &'static str, + pub input: bstr::BString, + #[source] + pub utf8_err: Option, } -/// -pub mod path; - -mod error { - use bstr::BString; - /// The error returned when any config value couldn't be instantiated due to malformed input. - #[derive(Debug, thiserror::Error, Eq, PartialEq)] - #[allow(missing_docs)] - #[error("Could not decode '{}': {}", .input, .message)] - pub struct Error { - pub message: &'static str, - pub input: BString, - #[source] - pub utf8_err: Option, - } - - impl Error { - pub(crate) fn new(message: &'static str, input: impl Into) -> Self { - Error { - message, - input: input.into(), - utf8_err: None, - } +impl Error { + pub(crate) fn new(message: &'static str, input: impl Into) -> Self { + Error { + message, + input: input.into(), + utf8_err: None, } + } - pub(crate) fn with_err(mut self, err: std::str::Utf8Error) -> Self { - self.utf8_err = Some(err); - self - } + pub(crate) fn with_err(mut self, err: std::str::Utf8Error) -> Self { + self.utf8_err = Some(err); + self } } -pub use error::Error; + +mod normalize; +pub use normalize::{normalize, normalize_bstr, normalize_bstring}; diff --git a/git-config/src/value/boolean.rs b/git-config/src/values/boolean.rs similarity index 99% rename from git-config/src/value/boolean.rs rename to git-config/src/values/boolean.rs index 53dc2b0b7bf..55e805c2804 100644 --- a/git-config/src/value/boolean.rs +++ b/git-config/src/values/boolean.rs @@ -1,11 +1,10 @@ use crate::value; +use crate::Boolean; use bstr::{BStr, BString, ByteSlice}; use std::borrow::Cow; use std::convert::TryFrom; use std::fmt::Display; -use value::Boolean; - impl Boolean<'_> { /// Return ourselves as plain bool. pub fn to_bool(&self) -> bool { diff --git a/git-config/src/value/color.rs b/git-config/src/values/color.rs similarity index 99% rename from git-config/src/value/color.rs rename to git-config/src/values/color.rs index 88a655b16ec..6c0503a52de 100644 --- a/git-config/src/value/color.rs +++ b/git-config/src/values/color.rs @@ -1,5 +1,5 @@ use crate::value; -use crate::value::Color; +use crate::Color; use bstr::{BStr, BString}; use std::borrow::Cow; use std::convert::TryFrom; diff --git a/git-config/src/value/integer.rs b/git-config/src/values/integer.rs similarity index 99% rename from git-config/src/value/integer.rs rename to git-config/src/values/integer.rs index d3a2df7b428..f1c32885efc 100644 --- a/git-config/src/value/integer.rs +++ b/git-config/src/values/integer.rs @@ -1,12 +1,11 @@ use crate::value; +use crate::Integer; use bstr::{BStr, BString}; use std::borrow::Cow; use std::convert::TryFrom; use std::fmt::Display; use std::str::FromStr; -use value::Integer; - impl Integer { /// Generates a byte representation of the value. This should be used when /// non-UTF-8 sequences are present or a UTF-8 representation can't be diff --git a/git-config/src/values/mod.rs b/git-config/src/values/mod.rs new file mode 100644 index 00000000000..bcd9d1e1115 --- /dev/null +++ b/git-config/src/values/mod.rs @@ -0,0 +1,67 @@ +mod string; +pub use string::String; + +/// Any value that may contain a foreground color, background color, a +/// collection of color (text) modifiers, or a combination of any of the +/// aforementioned values. +/// +/// Note that `git-config` allows color values to simply be a collection of +/// [`color::Attribute`]s, and does not require a [`color::Name`] for either the +/// foreground or background color. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] +pub struct Color { + /// A provided foreground color + pub foreground: Option, + /// A provided background color + pub background: Option, + /// A potentially empty list of text attributes + pub attributes: Vec, +} +pub mod color; + +/// Any value that can be interpreted as an integer. +/// +/// This supports any numeric value that can fit in a [`i64`], excluding the +/// suffix. The suffix is parsed separately from the value itself, so if you +/// wish to obtain the true value of the integer, you must account for the +/// suffix after fetching the value. [`integer::Suffix`] provides +/// [`bitwise_offset`] to help with the math, but do be warned that if the value +/// is very large, you may run into overflows. +/// +/// [`BStr`]: bstr::BStr +/// [`bitwise_offset`]: integer::Suffix::bitwise_offset +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub struct Integer { + /// The value, without any suffix modification + pub value: i64, + /// A provided suffix, if any. + pub suffix: Option, +} +/// +pub mod integer; + +/// Any value that can be interpreted as a boolean. +/// +/// Note that while values can effectively be any byte string, the `git-config` +/// documentation has a strict subset of values that may be interpreted as a +/// boolean value, all of which are ASCII and thus UTF-8 representable. +/// Consequently, variants hold [`str`]s rather than [`[u8]`]s. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +#[allow(missing_docs)] +pub enum Boolean<'a> { + True(boolean::True<'a>), + False(std::borrow::Cow<'a, bstr::BStr>), +} +/// +pub mod boolean; + +/// Any value that can be interpreted as a file path. +/// +/// Git represents file paths as byte arrays, modeled here as owned or borrowed byte sequences. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub struct Path<'a> { + /// The path string, un-interpolated + pub value: std::borrow::Cow<'a, bstr::BStr>, +} +/// +pub mod path; diff --git a/git-config/src/value/path.rs b/git-config/src/values/path.rs similarity index 99% rename from git-config/src/value/path.rs rename to git-config/src/values/path.rs index 0a3fb5cf81a..d4227f5f49a 100644 --- a/git-config/src/value/path.rs +++ b/git-config/src/values/path.rs @@ -1,4 +1,4 @@ -use crate::value::Path; +use crate::Path; use bstr::BStr; use std::borrow::Cow; diff --git a/git-config/src/value/string.rs b/git-config/src/values/string.rs similarity index 100% rename from git-config/src/value/string.rs rename to git-config/src/values/string.rs diff --git a/git-config/tests/file/access/read_only.rs b/git-config/tests/file/access/read_only.rs index 7b6f71b9949..79694a733db 100644 --- a/git-config/tests/file/access/read_only.rs +++ b/git-config/tests/file/access/read_only.rs @@ -1,7 +1,7 @@ use crate::file::cow_str; use bstr::BStr; -use git_config::value::{boolean::True, color, integer, Boolean, Color, Integer, String}; use git_config::File; +use git_config::{boolean::True, color, integer, Boolean, Color, Integer, String}; use std::{borrow::Cow, convert::TryFrom, error::Error}; /// Asserts we can cast into all variants of our type @@ -94,7 +94,7 @@ fn get_value_for_all_provided_values() -> crate::Result { "hello world" ); - let actual = file.value::("core", None, "location")?; + let actual = file.value::("core", None, "location")?; assert_eq!(&*actual, "~/tmp", "no interpolation occurs when querying a path"); let home = std::env::current_dir()?; diff --git a/git-config/tests/value/boolean.rs b/git-config/tests/value/boolean.rs index a7465cb2c64..0491d429130 100644 --- a/git-config/tests/value/boolean.rs +++ b/git-config/tests/value/boolean.rs @@ -1,7 +1,7 @@ use std::convert::TryFrom; use crate::file::cow_str; -use git_config::value::{boolean::True, Boolean}; +use git_config::{boolean::True, Boolean}; use crate::value::b; diff --git a/git-config/tests/value/color.rs b/git-config/tests/value/color.rs index ddf252092fc..c8411f0e5a7 100644 --- a/git-config/tests/value/color.rs +++ b/git-config/tests/value/color.rs @@ -1,7 +1,7 @@ mod name { use std::str::FromStr; - use git_config::value::color::Name; + use git_config::color::Name; #[test] fn non_bright() { @@ -56,7 +56,7 @@ mod name { mod attribute { use std::str::FromStr; - use git_config::value::color::Attribute; + use git_config::color::Attribute; #[test] fn non_inverted() { diff --git a/git-config/tests/value/integer.rs b/git-config/tests/value/integer.rs index c75ac6a5d43..86478d6e16e 100644 --- a/git-config/tests/value/integer.rs +++ b/git-config/tests/value/integer.rs @@ -1,6 +1,6 @@ use std::convert::TryFrom; -use git_config::value::{integer::Suffix, Integer}; +use git_config::{integer::Suffix, Integer}; use crate::value::b; diff --git a/git-config/tests/value/path.rs b/git-config/tests/value/path.rs index 8cc47ce53d0..cb4d15d7549 100644 --- a/git-config/tests/value/path.rs +++ b/git-config/tests/value/path.rs @@ -2,16 +2,14 @@ mod interpolate { use std::borrow::Cow; use std::path::Path; - use git_config::value::Path as InterpolatingPath; - use crate::file::cow_str; use crate::value::b; - use git_config::value::path::interpolate::Error; + use git_config::path::interpolate::Error; #[test] fn backslash_is_not_special_and_they_are_not_escaping_anything() -> crate::Result { for path in ["C:\\foo\\bar", "/foo/bar"] { - let actual = InterpolatingPath::from(Cow::Borrowed(b(path))).interpolate(None, None)?; + let actual = git_config::Path::from(Cow::Borrowed(b(path))).interpolate(None, None)?; assert_eq!(actual, Path::new(path)); assert!( matches!(actual, Cow::Borrowed(_)), @@ -36,7 +34,7 @@ mod interpolate { let expected = std::path::PathBuf::from(format!("{}{}{}", git_install_dir, std::path::MAIN_SEPARATOR, expected)); assert_eq!( - InterpolatingPath::from(cow_str(val)) + git_config::Path::from(cow_str(val)) .interpolate(Path::new(git_install_dir).into(), None) .unwrap(), expected, @@ -51,7 +49,7 @@ mod interpolate { let path = "./%(prefix)/foo/bar"; let git_install_dir = "/tmp/git"; assert_eq!( - InterpolatingPath::from(Cow::Borrowed(b(path))) + git_config::Path::from(Cow::Borrowed(b(path))) .interpolate(Path::new(git_install_dir).into(), None) .unwrap(), Path::new(path) @@ -70,7 +68,7 @@ mod interpolate { let home = std::env::current_dir().unwrap(); let expected = format!("{}{}foo/bar", home.display(), std::path::MAIN_SEPARATOR); assert_eq!( - InterpolatingPath::from(cow_str(path)) + git_config::Path::from(cow_str(path)) .interpolate(None, Some(&home)) .unwrap() .as_ref(), @@ -109,7 +107,7 @@ mod interpolate { fn interpolate_without_context( path: impl AsRef, - ) -> Result, git_config::value::path::interpolate::Error> { - InterpolatingPath::from(Cow::Owned(path.as_ref().to_owned().into())).interpolate(None, None) + ) -> Result, git_config::path::interpolate::Error> { + git_config::Path::from(Cow::Owned(path.as_ref().to_owned().into())).interpolate(None, None) } } From 363a826144ad59518b5c1a3dbbc82d04e4fc062d Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 09:24:19 +0800 Subject: [PATCH 061/366] adapt to changes in `git-config` (#331) --- git-config/src/values/path.rs | 2 +- git-repository/src/config.rs | 4 ++-- git-repository/src/lib.rs | 4 ++-- git-repository/src/repository/snapshots.rs | 2 +- git-repository/src/repository/worktree.rs | 2 +- git-repository/src/worktree/mod.rs | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/git-config/src/values/path.rs b/git-config/src/values/path.rs index d4227f5f49a..370d79914cd 100644 --- a/git-config/src/values/path.rs +++ b/git-config/src/values/path.rs @@ -3,7 +3,7 @@ use bstr::BStr; use std::borrow::Cow; pub mod interpolate { - /// The error returned by [`Path::interpolate()`][crate::value::Path::interpolate()]. + /// The error returned by [`Path::interpolate()`][crate::Path::interpolate()]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { diff --git a/git-repository/src/config.rs b/git-repository/src/config.rs index 110ef112f22..3533c240fcd 100644 --- a/git-repository/src/config.rs +++ b/git-repository/src/config.rs @@ -13,7 +13,7 @@ pub enum Error { #[error("Value '{}' at key '{}' could not be decoded as boolean", .value, .key)] DecodeBoolean { key: String, value: BString }, #[error(transparent)] - PathInterpolation(#[from] git_config::value::path::interpolate::Error), + PathInterpolation(#[from] git_config::path::interpolate::Error), } /// Utility type to keep pre-obtained configuration values. @@ -46,7 +46,7 @@ pub(crate) struct Cache { mod cache { use std::{convert::TryFrom, path::PathBuf}; - use git_config::{value::Boolean, value::Integer, File}; + use git_config::{Boolean, File, Integer}; use super::{Cache, Error}; use crate::{bstr::ByteSlice, permission}; diff --git a/git-repository/src/lib.rs b/git-repository/src/lib.rs index e0a50bace5d..7840db71f78 100644 --- a/git-repository/src/lib.rs +++ b/git-repository/src/lib.rs @@ -293,7 +293,7 @@ pub mod mailmap { #[error("The configured mailmap.blob could not be parsed")] BlobSpec(#[from] git_hash::decode::Error), #[error(transparent)] - PathInterpolate(#[from] git_config::value::path::interpolate::Error), + PathInterpolate(#[from] git_config::path::interpolate::Error), #[error("Could not find object configured in `mailmap.blob`")] FindExisting(#[from] crate::object::find::existing::OdbError), } @@ -444,7 +444,7 @@ pub mod discover { if let Some(cross_fs) = std::env::var_os("GIT_DISCOVERY_ACROSS_FILESYSTEM") .and_then(|v| Vec::from_os_string(v).ok().map(BString::from)) { - if let Ok(b) = git_config::value::Boolean::try_from(cross_fs) { + if let Ok(b) = git_config::Boolean::try_from(cross_fs) { opts.cross_fs = b.to_bool(); } } diff --git a/git-repository/src/repository/snapshots.rs b/git-repository/src/repository/snapshots.rs index 529499aaed0..6b35bc0967a 100644 --- a/git-repository/src/repository/snapshots.rs +++ b/git-repository/src/repository/snapshots.rs @@ -93,7 +93,7 @@ impl crate::Repository { let configured_path = self .config .resolved - .value::>("mailmap", None, "file") + .value::>("mailmap", None, "file") .ok() .and_then(|path| { let install_dir = self.install_dir().ok()?; diff --git a/git-repository/src/repository/worktree.rs b/git-repository/src/repository/worktree.rs index ae3e0b7bb28..5826e0fde2b 100644 --- a/git-repository/src/repository/worktree.rs +++ b/git-repository/src/repository/worktree.rs @@ -77,7 +77,7 @@ impl crate::Repository { .boolean("index", None, "threads") .map(|res| { res.map(|value| if value { 0usize } else { 1 }).or_else(|err| { - git_config::value::Integer::try_from(err.input.as_ref()) + git_config::Integer::try_from(err.input.as_ref()) .map_err(|err| worktree::open_index::Error::ConfigIndexThreads { value: err.input.clone(), err, diff --git a/git-repository/src/worktree/mod.rs b/git-repository/src/worktree/mod.rs index 9ca9c254039..0eb61d749f6 100644 --- a/git-repository/src/worktree/mod.rs +++ b/git-repository/src/worktree/mod.rs @@ -78,7 +78,7 @@ pub mod open_index { ConfigIndexThreads { value: BString, #[source] - err: git_config::value::parse::Error, + err: git_config::value::Error, }, #[error(transparent)] IndexFile(#[from] git_index::file::init::Error), From e68448831a94574ee3ca2fa36788f603c91d57a0 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 09:37:48 +0800 Subject: [PATCH 062/366] avoid unnecessary clones (#331) --- git-config/src/file/access/raw.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/git-config/src/file/access/raw.rs b/git-config/src/file/access/raw.rs index a51f2b2c1d9..2c36e0512a7 100644 --- a/git-config/src/file/access/raw.rs +++ b/git-config/src/file/access/raw.rs @@ -44,7 +44,7 @@ impl<'a> File<'a> { .expect("sections does not have section id from section ids") .value(&key) { - return Ok(v.clone()); + return Ok(v); } } @@ -172,8 +172,7 @@ impl<'a> File<'a> { .get(§ion_id) .expect("sections does not have section id from section ids") .values(§ion::Key(Cow::::Borrowed(key.into()))) - .iter() - .cloned(), + .into_iter(), ); } From 91ba2ddcd3de63aa22dc6e863b26ce1893a36995 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 09:41:14 +0800 Subject: [PATCH 063/366] normalize values in all the right places (#331) Previously this was done in the value type itself, partially, which is wrong as it belongs on the `value()` level of the API, as well as the 'comfort' level. Previously it was only done in the `Section` type. --- git-config/src/file/access/comfort.rs | 2 +- git-config/src/file/section.rs | 2 +- git-config/src/values/string.rs | 4 +--- git-config/tests/file/access/read_only.rs | 26 ++++++++++++++++++++++- 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/git-config/src/file/access/comfort.rs b/git-config/src/file/access/comfort.rs index b13137f062d..4f748a3d90e 100644 --- a/git-config/src/file/access/comfort.rs +++ b/git-config/src/file/access/comfort.rs @@ -26,7 +26,7 @@ impl<'a> File<'a> { pub fn path(&'a self, section_name: &str, subsection_name: Option<&str>, key: &str) -> Option> { self.raw_value(section_name, subsection_name, key) .ok() - .map(crate::Path::from) + .map(|v| crate::Path::from(normalize(v))) } /// Like [`value()`][File::value()], but returning an `Option` if the boolean wasn't found. diff --git a/git-config/src/file/section.rs b/git-config/src/file/section.rs index 99299b72b17..ae320875995 100644 --- a/git-config/src/file/section.rs +++ b/git-config/src/file/section.rs @@ -249,7 +249,7 @@ impl<'event> SectionBody<'event> { if range.end - range.start == 1 { return self.0.get(range.start).map(|e| match e { - Event::Value(v) => v.clone(), + Event::Value(v) => normalize(v.to_owned()), // range only has one element so we know it's a value event, so // it's impossible to reach this code. _ => unreachable!(), diff --git a/git-config/src/values/string.rs b/git-config/src/values/string.rs index 28b96a728e7..3c5816eb59b 100644 --- a/git-config/src/values/string.rs +++ b/git-config/src/values/string.rs @@ -10,8 +10,6 @@ pub struct String<'a> { impl<'a> From> for String<'a> { fn from(c: Cow<'a, BStr>) -> Self { - String { - value: crate::value::normalize(c), - } + String { value: c } } } diff --git a/git-config/tests/file/access/read_only.rs b/git-config/tests/file/access/read_only.rs index 79694a733db..3f789feb86b 100644 --- a/git-config/tests/file/access/read_only.rs +++ b/git-config/tests/file/access/read_only.rs @@ -8,6 +8,8 @@ use std::{borrow::Cow, convert::TryFrom, error::Error}; #[test] fn get_value_for_all_provided_values() -> crate::Result { let config = r#" + [core] + other-quoted = "hello" [core] bool-explicit = false bool-implicit @@ -18,6 +20,7 @@ fn get_value_for_all_provided_values() -> crate::Result { other = hello world other-quoted = "hello world" location = ~/tmp + location-quoted = "~/quoted" "#; let file = File::try_from(config)?; @@ -84,6 +87,17 @@ fn get_value_for_all_provided_values() -> crate::Result { value: cow_str("hello world") } ); + assert_eq!( + file.multi_value::("core", None, "other-quoted")?, + vec![ + String { + value: cow_str("hello") + }, + String { + value: cow_str("hello world") + }, + ] + ); assert_eq!( file.string("core", None, "other").expect("present").as_ref(), @@ -93,6 +107,10 @@ fn get_value_for_all_provided_values() -> crate::Result { file.string("core", None, "other-quoted").expect("present").as_ref(), "hello world" ); + assert_eq!( + file.strings("core", None, "other-quoted").expect("present").as_ref(), + vec![cow_str("hello"), cow_str("hello world")] + ); let actual = file.value::("core", None, "location")?; assert_eq!(&*actual, "~/tmp", "no interpolation occurs when querying a path"); @@ -102,7 +120,13 @@ fn get_value_for_all_provided_values() -> crate::Result { assert_eq!(actual.interpolate(None, home.as_path().into()).unwrap(), expected); let actual = file.path("core", None, "location").expect("present"); - assert_eq!(&*actual, "~/tmp",); + assert_eq!(&*actual, "~/tmp"); + + let actual = file.path("core", None, "location-quoted").expect("present"); + assert_eq!(&*actual, "~/quoted"); + + let actual = file.value::("core", None, "location-quoted")?; + assert_eq!(&*actual, "~/quoted", "but the path is unquoted"); Ok(()) } From ce069ca0b6b44cd734f4d8b4525916d1ddb0de0b Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 10:00:03 +0800 Subject: [PATCH 064/366] refactor; assure `normalize` doesn't copy unnecessarily (#331) --- git-config/src/file/section.rs | 2 +- git-config/tests/config.rs | 1 + git-config/tests/value/mod.rs | 12 +----------- git-config/tests/value/normalize.rs | 2 ++ git-config/tests/{value => values}/boolean.rs | 0 git-config/tests/{value => values}/color.rs | 0 git-config/tests/{value => values}/integer.rs | 0 git-config/tests/values/mod.rs | 7 +++++++ git-config/tests/{value => values}/path.rs | 0 9 files changed, 12 insertions(+), 12 deletions(-) rename git-config/tests/{value => values}/boolean.rs (100%) rename git-config/tests/{value => values}/color.rs (100%) rename git-config/tests/{value => values}/integer.rs (100%) create mode 100644 git-config/tests/values/mod.rs rename git-config/tests/{value => values}/path.rs (100%) diff --git a/git-config/src/file/section.rs b/git-config/src/file/section.rs index ae320875995..0aeac0b5e48 100644 --- a/git-config/src/file/section.rs +++ b/git-config/src/file/section.rs @@ -249,7 +249,7 @@ impl<'event> SectionBody<'event> { if range.end - range.start == 1 { return self.0.get(range.start).map(|e| match e { - Event::Value(v) => normalize(v.to_owned()), + Event::Value(v) => normalize(v.clone()), // range only has one element so we know it's a value event, so // it's impossible to reach this code. _ => unreachable!(), diff --git a/git-config/tests/config.rs b/git-config/tests/config.rs index 874365a2d06..dc7e905de8e 100644 --- a/git-config/tests/config.rs +++ b/git-config/tests/config.rs @@ -3,3 +3,4 @@ type Result = std::result::Result>; mod file; mod parse; mod value; +mod values; diff --git a/git-config/tests/value/mod.rs b/git-config/tests/value/mod.rs index ea81affa7e1..8ef588d9491 100644 --- a/git-config/tests/value/mod.rs +++ b/git-config/tests/value/mod.rs @@ -1,16 +1,6 @@ -use bstr::BStr; - /// Converts string to a bstr -fn b(s: &str) -> &BStr { +pub fn b(s: &str) -> &bstr::BStr { s.into() } mod normalize; - -mod boolean; - -mod integer; - -mod color; - -mod path; diff --git a/git-config/tests/value/normalize.rs b/git-config/tests/value/normalize.rs index 6ea0283a796..a3e9427afad 100644 --- a/git-config/tests/value/normalize.rs +++ b/git-config/tests/value/normalize.rs @@ -1,9 +1,11 @@ use crate::file::cow_str; use git_config::value::normalize_bstr; +use std::borrow::Cow; #[test] fn not_modified_is_borrowed() { assert_eq!(normalize_bstr("hello world"), cow_str("hello world")); + assert!(matches!(normalize_bstr("hello world"), Cow::Borrowed(_))); } #[test] diff --git a/git-config/tests/value/boolean.rs b/git-config/tests/values/boolean.rs similarity index 100% rename from git-config/tests/value/boolean.rs rename to git-config/tests/values/boolean.rs diff --git a/git-config/tests/value/color.rs b/git-config/tests/values/color.rs similarity index 100% rename from git-config/tests/value/color.rs rename to git-config/tests/values/color.rs diff --git a/git-config/tests/value/integer.rs b/git-config/tests/values/integer.rs similarity index 100% rename from git-config/tests/value/integer.rs rename to git-config/tests/values/integer.rs diff --git a/git-config/tests/values/mod.rs b/git-config/tests/values/mod.rs new file mode 100644 index 00000000000..6692791ab32 --- /dev/null +++ b/git-config/tests/values/mod.rs @@ -0,0 +1,7 @@ +mod boolean; + +mod integer; + +mod color; + +mod path; diff --git a/git-config/tests/value/path.rs b/git-config/tests/values/path.rs similarity index 100% rename from git-config/tests/value/path.rs rename to git-config/tests/values/path.rs From 4a01d983f54a7713dea523f6032cbf5bb2b9dde8 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 10:34:53 +0800 Subject: [PATCH 065/366] fix!: improve normalization; assure no extra copies are made on query. (#331) We now return our own content, rather than the originals with their lifetimes, meaning we bind lifetimes of returned values to our own `File` instance. This allows them to be referenced more often, and smarter normalization assures we don't copy in the simple cases either. More tests were added as well. This is breaking as lifetime changes can cause distruptions, and `values?_as()` was removed as well as it's somewhat duplicate to higher-level APIs and it wasn't tested at all. --- git-config/src/file/access/comfort.rs | 8 +-- git-config/src/file/resolve_includes.rs | 6 +- git-config/src/file/section.rs | 35 ++-------- git-config/src/value/normalize.rs | 8 +-- git-config/tests/file/access/read_only.rs | 79 ++++++++++++++--------- git-config/tests/value/normalize.rs | 44 ++++++++----- 6 files changed, 93 insertions(+), 87 deletions(-) diff --git a/git-config/src/file/access/comfort.rs b/git-config/src/file/access/comfort.rs index 4f748a3d90e..4f9d776fd7a 100644 --- a/git-config/src/file/access/comfort.rs +++ b/git-config/src/file/access/comfort.rs @@ -10,7 +10,7 @@ impl<'a> File<'a> { /// Like [`value()`][File::value()], but returning an `Option` if the string wasn't found. /// /// As strings perform no conversions, this will never fail. - pub fn string(&'a self, section_name: &str, subsection_name: Option<&str>, key: &str) -> Option> { + pub fn string(&self, section_name: &str, subsection_name: Option<&str>, key: &str) -> Option> { self.raw_value(section_name, subsection_name, key).ok().map(normalize) } @@ -23,7 +23,7 @@ impl<'a> File<'a> { // TODO: add `secure_path()` or similar to make use of our knowledge of the trust associated with each configuration // file, maybe even remove the insecure version to force every caller to ask themselves if the resource can // be used securely or not. - pub fn path(&'a self, section_name: &str, subsection_name: Option<&str>, key: &str) -> Option> { + pub fn path(&self, section_name: &str, subsection_name: Option<&str>, key: &str) -> Option> { self.raw_value(section_name, subsection_name, key) .ok() .map(|v| crate::Path::from(normalize(v))) @@ -31,7 +31,7 @@ impl<'a> File<'a> { /// Like [`value()`][File::value()], but returning an `Option` if the boolean wasn't found. pub fn boolean( - &'a self, + &self, section_name: &str, subsection_name: Option<&str>, key: &str, @@ -43,7 +43,7 @@ impl<'a> File<'a> { /// Like [`value()`][File::value()], but returning an `Option` if the integer wasn't found. pub fn integer( - &'a self, + &self, section_name: &str, subsection_name: Option<&str>, key: &str, diff --git a/git-config/src/file/resolve_includes.rs b/git-config/src/file/resolve_includes.rs index 77b50f7d629..e7d6de4b7d1 100644 --- a/git-config/src/file/resolve_includes.rs +++ b/git-config/src/file/resolve_includes.rs @@ -85,10 +85,12 @@ fn resolve_includes_recursive( Ok(()) } -fn extract_include_path<'a>(target_config: &mut File<'a>, include_paths: &mut Vec>, id: SectionId) { +fn extract_include_path(target_config: &mut File<'_>, include_paths: &mut Vec>, id: SectionId) { if let Some(body) = target_config.sections.get(&id) { let paths = body.values(§ion::Key::from("path")); - let paths = paths.iter().map(|path| crate::Path::from(path.clone())); + let paths = paths + .iter() + .map(|path| crate::Path::from(Cow::Owned(path.as_ref().to_owned()))); include_paths.extend(paths); } } diff --git a/git-config/src/file/section.rs b/git-config/src/file/section.rs index 0aeac0b5e48..da1a147b12b 100644 --- a/git-config/src/file/section.rs +++ b/git-config/src/file/section.rs @@ -2,11 +2,11 @@ use bstr::{BStr, BString, ByteVec}; use std::{ borrow::Cow, collections::VecDeque, - convert::TryFrom, iter::FusedIterator, ops::{Deref, Range}, }; +use crate::value::normalize_bstr; use crate::{ file::Index, lookup, @@ -241,7 +241,7 @@ impl<'event> SectionBody<'event> { // function. #[allow(clippy::missing_panics_doc)] #[must_use] - pub fn value(&self, key: &Key<'_>) -> Option> { + pub fn value(&self, key: &Key<'_>) -> Option> { let range = self.value_range_by_key(key); if range.is_empty() { return None; @@ -249,7 +249,7 @@ impl<'event> SectionBody<'event> { if range.end - range.start == 1 { return self.0.get(range.start).map(|e| match e { - Event::Value(v) => normalize(v.clone()), + Event::Value(v) => normalize_bstr(v.as_ref()), // range only has one element so we know it's a value event, so // it's impossible to reach this code. _ => unreachable!(), @@ -266,21 +266,10 @@ impl<'event> SectionBody<'event> { .into() } - /// Retrieves the last matching value in a section with the given key, and - /// attempts to convert the value into the provided type. - /// - /// # Errors - /// - /// Returns an error if the key was not found, or if the conversion failed. - pub fn value_as>>(&self, key: &Key<'_>) -> Result> { - T::try_from(self.value(key).ok_or(lookup::existing::Error::KeyMissing)?) - .map_err(lookup::Error::FailedConversion) - } - /// Retrieves all values that have the provided key name. This may return /// an empty vec, which implies there were no values with the provided key. #[must_use] - pub fn values(&self, key: &Key<'_>) -> Vec> { + pub fn values(&self, key: &Key<'_>) -> Vec> { let mut values = vec![]; let mut found_key = false; let mut partial_value = None; @@ -293,7 +282,7 @@ impl<'event> SectionBody<'event> { Event::Value(v) if found_key => { found_key = false; // Clones the Cow, doesn't copy underlying value if borrowed - values.push(normalize(v.clone())); + values.push(normalize(Cow::Borrowed(v.as_ref()))); partial_value = None; } Event::ValueNotDone(v) if found_key => { @@ -314,20 +303,6 @@ impl<'event> SectionBody<'event> { values } - /// Retrieves all values that have the provided key name. This may return - /// an empty vec, which implies there was values with the provided key. - /// - /// # Errors - /// - /// Returns an error if the conversion failed. - pub fn values_as>>(&self, key: &Key<'_>) -> Result, lookup::Error> { - self.values(key) - .into_iter() - .map(T::try_from) - .collect::, _>>() - .map_err(lookup::Error::FailedConversion) - } - /// Returns an iterator visiting all keys in order. pub fn keys(&self) -> impl Iterator> { self.0 diff --git a/git-config/src/value/normalize.rs b/git-config/src/value/normalize.rs index 34348770426..e42f291f2d6 100644 --- a/git-config/src/value/normalize.rs +++ b/git-config/src/value/normalize.rs @@ -58,14 +58,14 @@ use bstr::{BStr, BString}; /// ``` #[must_use] pub fn normalize(input: Cow<'_, BStr>) -> Cow<'_, BStr> { - let size = input.len(); if input.as_ref() == "\"\"" { - return Cow::default(); + return Cow::Borrowed("".into()); } - if size >= 3 && input[0] == b'=' && input[size - 1] == b'=' && input[size - 2] != b'\\' { + let size = input.len(); + if size >= 3 && input[0] == b'"' && input[size - 1] == b'"' && input[size - 2] != b'\\' { match input { - Cow::Borrowed(input) => return normalize_bstr(&input[1..size]), + Cow::Borrowed(input) => return normalize_bstr(&input[1..size - 1]), Cow::Owned(mut input) => { input.pop(); input.remove(0); diff --git a/git-config/tests/file/access/read_only.rs b/git-config/tests/file/access/read_only.rs index 3f789feb86b..22b2389c774 100644 --- a/git-config/tests/file/access/read_only.rs +++ b/git-config/tests/file/access/read_only.rs @@ -23,7 +23,7 @@ fn get_value_for_all_provided_values() -> crate::Result { location-quoted = "~/quoted" "#; - let file = File::try_from(config)?; + let file = git_config::parse::State::from_bytes_owned(config.as_bytes()).map(File::from)?; assert_eq!( file.value::("core", None, "bool-explicit")?, @@ -77,47 +77,64 @@ fn get_value_for_all_provided_values() -> crate::Result { } ); - assert_eq!( - file.value::>("core", None, "other")?, - cow_str("hello world") - ); + { + let string = file.value::>("core", None, "other")?; + assert_eq!(string, cow_str("hello world")); + assert!( + matches!(string, Cow::Borrowed(_)), + "no copy is made, we reference the `file` itself" + ); + } + assert_eq!( file.value::("core", None, "other-quoted")?, String { value: cow_str("hello world") } ); - assert_eq!( - file.multi_value::("core", None, "other-quoted")?, - vec![ - String { - value: cow_str("hello") - }, - String { - value: cow_str("hello world") - }, - ] - ); - assert_eq!( - file.string("core", None, "other").expect("present").as_ref(), - "hello world" - ); + { + let strings = file.multi_value::("core", None, "other-quoted")?; + assert_eq!( + strings, + vec![ + String { + value: cow_str("hello") + }, + String { + value: cow_str("hello world") + }, + ] + ); + assert!(matches!(strings[0].value, Cow::Borrowed(_))); + assert!(matches!(strings[1].value, Cow::Borrowed(_))); + } + + { + let cow = file.string("core", None, "other").expect("present"); + assert_eq!(cow.as_ref(), "hello world"); + assert!(matches!(cow, Cow::Borrowed(_))); + } assert_eq!( file.string("core", None, "other-quoted").expect("present").as_ref(), "hello world" ); - assert_eq!( - file.strings("core", None, "other-quoted").expect("present").as_ref(), - vec![cow_str("hello"), cow_str("hello world")] - ); - - let actual = file.value::("core", None, "location")?; - assert_eq!(&*actual, "~/tmp", "no interpolation occurs when querying a path"); - - let home = std::env::current_dir()?; - let expected = home.join("tmp"); - assert_eq!(actual.interpolate(None, home.as_path().into()).unwrap(), expected); + { + let strings = file.strings("core", None, "other-quoted").expect("present"); + assert_eq!(strings, vec![cow_str("hello"), cow_str("hello world")]); + assert!(matches!(strings[0], Cow::Borrowed(_))); + assert!(matches!(strings[1], Cow::Borrowed(_))); + } + + { + let actual = file.value::("core", None, "location")?; + assert_eq!(&*actual, "~/tmp", "no interpolation occurs when querying a path"); + + let home = std::env::current_dir()?; + let expected = home.join("tmp"); + assert!(matches!(actual.value, Cow::Borrowed(_))); + assert_eq!(actual.interpolate(None, home.as_path().into()).unwrap(), expected); + } let actual = file.path("core", None, "location").expect("present"); assert_eq!(&*actual, "~/tmp"); diff --git a/git-config/tests/value/normalize.rs b/git-config/tests/value/normalize.rs index a3e9427afad..c8e682590ef 100644 --- a/git-config/tests/value/normalize.rs +++ b/git-config/tests/value/normalize.rs @@ -4,44 +4,56 @@ use std::borrow::Cow; #[test] fn not_modified_is_borrowed() { - assert_eq!(normalize_bstr("hello world"), cow_str("hello world")); - assert!(matches!(normalize_bstr("hello world"), Cow::Borrowed(_))); + let cow = normalize_bstr("hello world"); + assert_eq!(cow, cow_str("hello world")); + assert!(matches!(cow, Cow::Borrowed(_))); } #[test] fn modified_is_owned() { - assert_eq!(normalize_bstr("hello \"world\""), cow_str("hello world").to_owned()); + let cow = normalize_bstr("hello \"world\""); + assert_eq!(cow, cow_str("hello world")); + assert!(matches!(cow, Cow::Owned(_))); +} + +#[test] +fn empty_quotes_are_zero_copy() { + let cow = normalize_bstr("\"\""); + assert_eq!(cow, cow_str("")); + assert!(matches!(cow, Cow::Borrowed(_))); } #[test] fn all_quoted_is_optimized() { - assert_eq!(normalize_bstr("\"hello world\""), cow_str("hello world")); + let cow = normalize_bstr("\"hello world\""); + assert_eq!(cow, cow_str("hello world")); + assert!(matches!(cow, Cow::Borrowed(_))); } #[test] fn all_quote_optimization_is_correct() { - assert_eq!(normalize_bstr(r#""hello" world\""#), cow_str("hello world\"")); + let cow = normalize_bstr(r#""hello" world\""#); + assert_eq!(cow, cow_str("hello world\"")); + assert!(matches!(cow, Cow::Owned(_))); } #[test] fn quotes_right_next_to_each_other() { - assert_eq!(normalize_bstr("\"hello\"\" world\""), cow_str("hello world").to_owned()); + let cow = normalize_bstr("\"hello\"\" world\""); + assert_eq!(cow, cow_str("hello world").to_owned()); + assert!(matches!(cow, Cow::Owned(_))); } #[test] fn escaped_quotes_are_kept() { - assert_eq!( - normalize_bstr(r#""hello \"\" world""#), - cow_str("hello \"\" world").to_owned(), - ); + let cow = normalize_bstr(r#""hello \"\" world""#); + assert_eq!(cow, cow_str("hello \"\" world").to_owned(),); + assert!(matches!(cow, Cow::Owned(_))); } #[test] fn empty_string() { - assert_eq!(normalize_bstr(""), cow_str("")); -} - -#[test] -fn empty_normalized_string_is_optimized() { - assert_eq!(normalize_bstr("\"\""), cow_str("")); + let cow = normalize_bstr(""); + assert_eq!(cow, cow_str("")); + assert!(matches!(cow, Cow::Borrowed(_))); } From 19300d5f37c201aba921a6bff9760996fec2108e Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 10:47:23 +0800 Subject: [PATCH 066/366] update fuzz instructions and make it work (#331) It found a crash real quickly, too! --- git-config/README.md | 4 ++-- git-config/fuzz/.gitignore | 1 + git-config/fuzz/Cargo.toml | 4 ++-- git-config/fuzz/fuzz_targets/{parser.rs => parse.rs} | 3 +-- 4 files changed, 6 insertions(+), 6 deletions(-) rename git-config/fuzz/fuzz_targets/{parser.rs => parse.rs} (64%) diff --git a/git-config/README.md b/git-config/README.md index 8f95706e411..671006c76a3 100644 --- a/git-config/README.md +++ b/git-config/README.md @@ -32,8 +32,8 @@ Since this is a performance oriented crate, in addition to well tested code via performance. We use [`criterion`] so benches can be run via `cargo bench` after installing it via `cargo install cargo-criterion`. -Changes to `parser.rs` may include a request to fuzz to ensure that it cannot -panic on inputs. This can be done by executing `cargo fuzz parser` after +Changes to parsing code may include a request to fuzz to ensure that it cannot +panic on inputs. This can be done by executing `cargo +nightly fuzz run parse` after installing the `fuzz` sub-command via `cargo install cargo-fuzz`. #### License diff --git a/git-config/fuzz/.gitignore b/git-config/fuzz/.gitignore index 572e03bdf32..bf9c8cb74d0 100644 --- a/git-config/fuzz/.gitignore +++ b/git-config/fuzz/.gitignore @@ -2,3 +2,4 @@ target corpus artifacts +Cargo.lock diff --git a/git-config/fuzz/Cargo.toml b/git-config/fuzz/Cargo.toml index aef6e91381f..b23854424e3 100644 --- a/git-config/fuzz/Cargo.toml +++ b/git-config/fuzz/Cargo.toml @@ -20,7 +20,7 @@ path = ".." members = ["."] [[bin]] -name = "parser" -path = "fuzz_targets/parser.rs" +name = "parse" +path = "fuzz_targets/parse.rs" test = false doc = false diff --git a/git-config/fuzz/fuzz_targets/parser.rs b/git-config/fuzz/fuzz_targets/parse.rs similarity index 64% rename from git-config/fuzz/fuzz_targets/parser.rs rename to git-config/fuzz/fuzz_targets/parse.rs index ffcbb9dbd93..a3fa0f5ccb2 100644 --- a/git-config/fuzz/fuzz_targets/parser.rs +++ b/git-config/fuzz/fuzz_targets/parse.rs @@ -1,9 +1,8 @@ #![no_main] -use git_config::parser::Parser; use libfuzzer_sys::fuzz_target; fuzz_target!(|data: &[u8]| { // Don't name this _; Rust may optimize it out. - let _a = Parser::from_bytes(data); + let _a = git_config::parse::State::from_bytes(data); }); From 89f5fca843d999c5bea35fb3fe2a03dc3588f74e Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 11:06:11 +0800 Subject: [PATCH 067/366] change!: rename `parse::State` to `parse::event::List` (#331) --- git-config/benches/large_config_file.rs | 4 +- git-config/fuzz/fuzz_targets/parse.rs | 2 +- git-config/src/file/from_paths.rs | 2 +- git-config/src/file/impls.rs | 37 ++- git-config/src/file/init.rs | 4 +- git-config/src/parse/event.rs | 81 ------ git-config/src/parse/event/list.rs | 159 ++++++++++++ git-config/src/parse/event/mod.rs | 298 ++++++++++++++++++++++ git-config/src/parse/mod.rs | 217 +--------------- git-config/src/parse/nom/mod.rs | 14 +- git-config/src/parse/state.rs | 158 ------------ git-config/src/parse/tests.rs | 18 +- git-config/tests/file/access/read_only.rs | 2 +- git-config/tests/file/from_paths/mod.rs | 2 +- git-config/tests/parse/mod.rs | 18 +- 15 files changed, 508 insertions(+), 508 deletions(-) delete mode 100644 git-config/src/parse/event.rs create mode 100644 git-config/src/parse/event/list.rs create mode 100644 git-config/src/parse/event/mod.rs diff --git a/git-config/benches/large_config_file.rs b/git-config/benches/large_config_file.rs index 192a4213e98..ba1bcf29b76 100644 --- a/git-config/benches/large_config_file.rs +++ b/git-config/benches/large_config_file.rs @@ -1,7 +1,7 @@ use std::convert::TryFrom; use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use git_config::{parse::State, File}; +use git_config::{parse::List, File}; fn git_config(c: &mut Criterion) { c.bench_function("GitConfig large config file", |b| { @@ -11,7 +11,7 @@ fn git_config(c: &mut Criterion) { fn parser(c: &mut Criterion) { c.bench_function("Parser large config file", |b| { - b.iter(|| State::try_from(black_box(CONFIG_FILE)).unwrap()) + b.iter(|| List::try_from(black_box(CONFIG_FILE)).unwrap()) }); } diff --git a/git-config/fuzz/fuzz_targets/parse.rs b/git-config/fuzz/fuzz_targets/parse.rs index a3fa0f5ccb2..d41e0fd3ed3 100644 --- a/git-config/fuzz/fuzz_targets/parse.rs +++ b/git-config/fuzz/fuzz_targets/parse.rs @@ -4,5 +4,5 @@ use libfuzzer_sys::fuzz_target; fuzz_target!(|data: &[u8]| { // Don't name this _; Rust may optimize it out. - let _a = git_config::parse::State::from_bytes(data); + let _a = git_config::parse::event::List::from_bytes(data); }); diff --git a/git-config/src/file/from_paths.rs b/git-config/src/file/from_paths.rs index 77cf18aa01a..a9d90578920 100644 --- a/git-config/src/file/from_paths.rs +++ b/git-config/src/file/from_paths.rs @@ -5,7 +5,7 @@ use crate::{parse, path::interpolate}; #[allow(missing_docs)] pub enum Error { #[error(transparent)] - Parse(#[from] parse::state::from_path::Error), + Parse(#[from] parse::event::list::from_path::Error), #[error(transparent)] Interpolate(#[from] interpolate::Error), #[error("The maximum allowed length {} of the file include chain built by following nested resolve_includes is exceeded", .max_depth)] diff --git a/git-config/src/file/impls.rs b/git-config/src/file/impls.rs index a61301f45eb..79dbf10f5cb 100644 --- a/git-config/src/file/impls.rs +++ b/git-config/src/file/impls.rs @@ -1,47 +1,42 @@ use bstr::{BString, ByteVec}; use std::{convert::TryFrom, fmt::Display}; -use crate::{ - file::SectionBody, - parse, - parse::{Error, Event, State}, - File, -}; +use crate::{file::SectionBody, parse, File}; impl<'a> TryFrom<&'a str> for File<'a> { - type Error = Error<'a>; + type Error = parse::Error<'a>; /// Convenience constructor. Attempts to parse the provided string into a /// [`File`]. See [`State::from_str()`] for more information. fn try_from(s: &'a str) -> Result, Self::Error> { - parse::State::from_str(s).map(Self::from) + parse::event::List::from_str(s).map(Self::from) } } impl<'a> TryFrom<&'a [u8]> for File<'a> { - type Error = Error<'a>; + type Error = parse::Error<'a>; /// Convenience constructor. Attempts to parse the provided byte string into //// a [`File`]. See [`parse_from_bytes`] for more information. /// /// [`parse_from_bytes`]: crate::parser::parse_from_bytes fn try_from(value: &'a [u8]) -> Result, Self::Error> { - parse::State::from_bytes(value).map(File::from) + parse::event::List::from_bytes(value).map(File::from) } } impl<'a> TryFrom<&'a BString> for File<'a> { - type Error = Error<'a>; + type Error = parse::Error<'a>; /// Convenience constructor. Attempts to parse the provided byte string into //// a [`File`]. See [`State::from_bytes()`] for more information. fn try_from(value: &'a BString) -> Result, Self::Error> { - parse::State::from_bytes(value.as_ref()).map(File::from) + parse::event::List::from_bytes(value.as_ref()).map(File::from) } } -impl<'a> From> for File<'a> { - fn from(parser: State<'a>) -> Self { +impl<'a> From> for File<'a> { + fn from(parser: parse::event::List<'a>) -> Self { let mut new_self = Self::default(); // Current section that we're building @@ -52,7 +47,7 @@ impl<'a> From> for File<'a> { // it's not really an iterator (yet), needs streaming iterator support for event in parser.into_iter() { match event { - Event::SectionHeader(header) => { + parse::Event::SectionHeader(header) => { if let Some(prev_header) = prev_section_header.take() { new_self.push_section_internal(prev_header, section_events); } else { @@ -61,12 +56,12 @@ impl<'a> From> for File<'a> { prev_section_header = Some(header); section_events = SectionBody::new(); } - e @ Event::SectionKey(_) - | e @ Event::Value(_) - | e @ Event::ValueNotDone(_) - | e @ Event::ValueDone(_) - | e @ Event::KeyValueSeparator => section_events.as_mut().push(e), - e @ Event::Comment(_) | e @ Event::Newline(_) | e @ Event::Whitespace(_) => { + e @ parse::Event::SectionKey(_) + | e @ parse::Event::Value(_) + | e @ parse::Event::ValueNotDone(_) + | e @ parse::Event::ValueDone(_) + | e @ parse::Event::KeyValueSeparator => section_events.as_mut().push(e), + e @ parse::Event::Comment(_) | e @ parse::Event::Newline(_) | e @ parse::Event::Whitespace(_) => { section_events.as_mut().push(e); } } diff --git a/git-config/src/file/init.rs b/git-config/src/file/init.rs index 4b7d522f8c3..4fcabf8ac5d 100644 --- a/git-config/src/file/init.rs +++ b/git-config/src/file/init.rs @@ -18,8 +18,8 @@ impl<'a> File<'a> { /// /// Returns an error if there was an IO error or if the file wasn't a valid /// git-config file. - pub fn at>(path: P) -> Result { - parse::State::from_path(path).map(Self::from) + pub fn at>(path: P) -> Result { + parse::event::List::from_path(path).map(Self::from) } /// Constructs a `git-config` file from the provided paths in the order provided. diff --git a/git-config/src/parse/event.rs b/git-config/src/parse/event.rs deleted file mode 100644 index 63dad9774f0..00000000000 --- a/git-config/src/parse/event.rs +++ /dev/null @@ -1,81 +0,0 @@ -use crate::parse::Event; -use bstr::BString; -use std::borrow::Cow; -use std::fmt::Display; - -impl Event<'_> { - /// Generates a byte representation of the value. This should be used when - /// non-UTF-8 sequences are present or a UTF-8 representation can't be - /// guaranteed. - #[must_use] - pub fn to_bstring(&self) -> BString { - self.into() - } - - /// Coerces into an owned instance. This differs from the standard [`clone`] - /// implementation as calling clone will _not_ copy the borrowed variant, - /// while this method will. In other words: - /// - /// | Borrow type | `.clone()` | `to_owned()` | - /// | ----------- | ---------- | ------------ | - /// | Borrowed | Borrowed | Owned | - /// | Owned | Owned | Owned | - /// - /// This can be most effectively seen by the differing lifetimes between the - /// two. This method guarantees a `'static` lifetime, while `clone` does - /// not. - /// - /// [`clone`]: Self::clone - #[must_use] - pub fn to_owned(&self) -> Event<'static> { - match self { - Event::Comment(e) => Event::Comment(e.to_owned()), - Event::SectionHeader(e) => Event::SectionHeader(e.to_owned()), - Event::SectionKey(e) => Event::SectionKey(e.to_owned()), - Event::Value(e) => Event::Value(Cow::Owned(e.clone().into_owned())), - Event::ValueNotDone(e) => Event::ValueNotDone(Cow::Owned(e.clone().into_owned())), - Event::ValueDone(e) => Event::ValueDone(Cow::Owned(e.clone().into_owned())), - Event::Newline(e) => Event::Newline(Cow::Owned(e.clone().into_owned())), - Event::Whitespace(e) => Event::Whitespace(Cow::Owned(e.clone().into_owned())), - Event::KeyValueSeparator => Event::KeyValueSeparator, - } - } -} - -impl Display for Event<'_> { - /// Note that this is a best-effort attempt at printing an `Event`. If - /// there are non UTF-8 values in your config, this will _NOT_ render - /// as read. - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Value(e) | Self::ValueNotDone(e) | Self::ValueDone(e) => match std::str::from_utf8(e) { - Ok(e) => e.fmt(f), - Err(_) => write!(f, "{:02x?}", e), - }, - Self::Comment(e) => e.fmt(f), - Self::SectionHeader(e) => e.fmt(f), - Self::SectionKey(e) => e.fmt(f), - Self::Newline(e) | Self::Whitespace(e) => e.fmt(f), - Self::KeyValueSeparator => write!(f, "="), - } - } -} - -impl From> for BString { - fn from(event: Event<'_>) -> Self { - event.into() - } -} - -impl From<&Event<'_>> for BString { - fn from(event: &Event<'_>) -> Self { - match event { - Event::Value(e) | Event::ValueNotDone(e) | Event::ValueDone(e) => e.as_ref().into(), - Event::Comment(e) => e.into(), - Event::SectionHeader(e) => e.into(), - Event::SectionKey(e) => e.0.as_ref().into(), - Event::Newline(e) | Event::Whitespace(e) => e.as_ref().into(), - Event::KeyValueSeparator => "=".into(), - } - } -} diff --git a/git-config/src/parse/event/list.rs b/git-config/src/parse/event/list.rs new file mode 100644 index 00000000000..dea7663d1b2 --- /dev/null +++ b/git-config/src/parse/event/list.rs @@ -0,0 +1,159 @@ +use crate::parse::{event::List, Error, Event, Section}; +use std::convert::TryFrom; +use std::io::Read; + +impl List<'static> { + /// Parses a git config located at the provided path. On success, returns a + /// [`State`] that provides methods to accessing leading comments and sections + /// of a `git-config` file and can be converted into an iterator of [`Event`] + /// for higher level processing. + /// + /// Note that since we accept a path rather than a reference to the actual + /// bytes, this function is _not_ zero-copy, as the Parser must own (and thus + /// copy) the bytes that it reads from. Consider one of the other variants if + /// performance is a concern. + /// + /// # Errors + /// + /// Returns an error if there was an IO error or the read file is not a valid + /// `git-config` This generally is due to either invalid names or if there's + /// extraneous data succeeding valid `git-config` data. + pub fn from_path>(path: P) -> Result, from_path::Error> { + let mut bytes = vec![]; + let mut file = std::fs::File::open(path)?; + file.read_to_end(&mut bytes)?; + crate::parse::nom::from_bytes_owned(&bytes).map_err(from_path::Error::Parse) + } + + /// Parses the provided bytes, returning an [`State`] that contains allocated + /// and owned events. This is similar to [`State::from_bytes()`], but performance + /// is degraded as it requires allocation for every event. However, this permits + /// the reference bytes to be dropped, allowing the parser to be passed around + /// without lifetime worries. + /// + /// # Errors + /// + /// Returns an error if the string provided is not a valid `git-config`. + /// This generally is due to either invalid names or if there's extraneous + /// data succeeding valid `git-config` data. + #[allow(clippy::shadow_unrelated)] + pub fn from_bytes_owned(input: &[u8]) -> Result, Error<'static>> { + crate::parse::nom::from_bytes_owned(input) + } +} + +impl<'a> List<'a> { + /// Attempt to zero-copy parse the provided `&str`. On success, returns a + /// [`State`] that provides methods to accessing leading comments and sections + /// of a `git-config` file and can be converted into an iterator of [`Event`] + /// for higher level processing. + /// + /// # Errors + /// + /// Returns an error if the string provided is not a valid `git-config`. + /// This generally is due to either invalid names or if there's extraneous + /// data succeeding valid `git-config` data. + #[allow(clippy::should_implement_trait)] + pub fn from_str(input: &'a str) -> Result, Error<'a>> { + crate::parse::nom::from_bytes(input.as_bytes()) + } + + /// Attempt to zero-copy parse the provided bytes. On success, returns a + /// [`State`] that provides methods to accessing leading comments and sections + /// of a `git-config` file and can be converted into an iterator of [`Event`] + /// for higher level processing. + /// + /// # Errors + /// + /// Returns an error if the string provided is not a valid `git-config`. + /// This generally is due to either invalid names or if there's extraneous + /// data succeeding valid `git-config` data. + #[allow(clippy::shadow_unrelated)] + pub fn from_bytes(input: &'a [u8]) -> Result, Error<'a>> { + crate::parse::nom::from_bytes(input) + } +} + +impl<'a> List<'a> { + /// Returns the leading events (any comments, whitespace, or newlines before + /// a section) from the parser. Consider [`State::take_frontmatter`] if + /// you need an owned copy only once. If that function was called, then this + /// will always return an empty slice. + #[must_use] + pub fn frontmatter(&self) -> &[Event<'a>] { + &self.frontmatter + } + + /// Takes the leading events (any comments, whitespace, or newlines before + /// a section) from the parser. Subsequent calls will return an empty vec. + /// Consider [`State::frontmatter`] if you only need a reference to the + /// frontmatter + pub fn take_frontmatter(&mut self) -> Vec> { + std::mem::take(&mut self.frontmatter) + } + + /// Returns the parsed sections from the parser. Consider + /// [`State::take_sections`] if you need an owned copy only once. If that + /// function was called, then this will always return an empty slice. + #[must_use] + pub fn sections(&self) -> &[Section<'a>] { + &self.sections + } + + /// Takes the parsed sections from the parser. Subsequent calls will return + /// an empty vec. Consider [`State::sections`] if you only need a reference + /// to the comments. + pub fn take_sections(&mut self) -> Vec> { + let mut to_return = vec![]; + std::mem::swap(&mut self.sections, &mut to_return); + to_return + } + + /// Consumes the parser to produce a Vec of Events. + #[must_use] + pub fn into_vec(self) -> Vec> { + self.into_iter().collect() + } + + /// Consumes the parser to produce an iterator of Events. + #[must_use = "iterators are lazy and do nothing unless consumed"] + #[allow(clippy::should_implement_trait)] + pub fn into_iter(self) -> impl Iterator> + std::iter::FusedIterator { + self.frontmatter.into_iter().chain( + self.sections.into_iter().flat_map(|section| { + std::iter::once(Event::SectionHeader(section.section_header)).chain(section.events) + }), + ) + } +} + +impl<'a> TryFrom<&'a str> for List<'a> { + type Error = Error<'a>; + + fn try_from(value: &'a str) -> Result { + Self::from_str(value) + } +} + +impl<'a> TryFrom<&'a [u8]> for List<'a> { + type Error = Error<'a>; + + fn try_from(value: &'a [u8]) -> Result { + crate::parse::nom::from_bytes(value) + } +} + +pub mod from_path { + /// An error type representing a Parser [`Error`] or an [`IO error`]. This is + /// returned from functions that will perform IO on top of standard parsing, + /// such as reading from a file. + /// + /// [`IO error`]: std::io::Error + #[derive(Debug, thiserror::Error)] + pub enum Error { + #[error(transparent)] + Parse(crate::parse::Error<'static>), + #[error(transparent)] + Io(#[from] std::io::Error), + } +} diff --git a/git-config/src/parse/event/mod.rs b/git-config/src/parse/event/mod.rs new file mode 100644 index 00000000000..33266e3d691 --- /dev/null +++ b/git-config/src/parse/event/mod.rs @@ -0,0 +1,298 @@ +use crate::parse::{Event, Section}; +use bstr::BString; +use std::borrow::Cow; +use std::fmt::Display; + +/// A zero-copy `git-config` file parser. +/// +/// This is parser exposes low-level syntactic events from a `git-config` file. +/// Generally speaking, you'll want to use [`File`] as it wraps +/// around the parser to provide a higher-level abstraction to a `git-config` +/// file, including querying, modifying, and updating values. +/// +/// This parser guarantees that the events emitted are sufficient to +/// reconstruct a `git-config` file identical to the source `git-config`. +/// +/// # Differences between a `.ini` parser +/// +/// While the `git-config` format closely resembles the [`.ini` file format], +/// there are subtle differences that make them incompatible. For one, the file +/// format is not well defined, and there exists no formal specification to +/// adhere to. Thus, attempting to use an `.ini` parser on a `git-config` file +/// may successfully parse invalid configuration files. +/// +/// For concrete examples, some notable differences are: +/// - `git-config` sections permit subsections via either a quoted string +/// (`[some-section "subsection"]`) or via the deprecated dot notation +/// (`[some-section.subsection]`). Successful parsing these section names is not +/// well defined in typical `.ini` parsers. This parser will handle these cases +/// perfectly. +/// - Comment markers are not strictly defined either. This parser will always +/// and only handle a semicolon or octothorpe (also known as a hash or number +/// sign). +/// - Global properties may be allowed in `.ini` parsers, but is strictly +/// disallowed by this parser. +/// - Only `\t`, `\n`, `\b` `\\` are valid escape characters. +/// - Quoted and semi-quoted values will be parsed (but quotes will be included +/// in event outputs). An example of a semi-quoted value is `5"hello world"`, +/// which should be interpreted as `5hello world`. +/// - Line continuations via a `\` character is supported. +/// - Whitespace handling similarly follows the `git-config` specification as +/// closely as possible, where excess whitespace after a non-quoted value are +/// trimmed, and line continuations onto a new line with excess spaces are kept. +/// - Only equal signs (optionally padded by spaces) are valid name/value +/// delimiters. +/// +/// Note that that things such as case-sensitivity or duplicate sections are +/// _not_ handled. This parser is a low level _syntactic_ interpreter (as a +/// parser should be), and higher level wrappers around this parser (which may +/// or may not be zero-copy) should handle _semantic_ values. This also means +/// that string-like values are not interpreted. For example, `hello"world"` +/// would be read at a high level as `helloworld` but this parser will return +/// the former instead, with the extra quotes. This is because it is not the +/// responsibility of the parser to interpret these values, and doing so would +/// necessarily require a copy, which this parser avoids. +/// +/// # Trait Implementations +/// +/// - This struct does _not_ implement [`FromStr`] due to lifetime +/// constraints implied on the required `from_str` method. Instead, it provides +/// [`From<&'_ str>`]. +/// +/// # Idioms +/// +/// If you do want to use this parser, there are some idioms that may help you +/// with interpreting sequences of events. +/// +/// ## `Value` events do not immediately follow `Key` events +/// +/// Consider the following `git-config` example: +/// +/// ```text +/// [core] +/// autocrlf = input +/// ``` +/// +/// Because this parser guarantees perfect reconstruction, there are many +/// non-significant events that occur in addition to the ones you may expect: +/// +/// ``` +/// # use git_config::parse::{Event, event, section}; +/// # use std::borrow::Cow; +/// # let section_header = section::Header { +/// # name: section::Name(Cow::Borrowed("core".into())), +/// # separator: None, +/// # subsection_name: None, +/// # }; +/// # let section_data = "[core]\n autocrlf = input"; +/// # assert_eq!(event::List::from_str(section_data).unwrap().into_vec(), vec![ +/// Event::SectionHeader(section_header), +/// Event::Newline(Cow::Borrowed("\n".into())), +/// Event::Whitespace(Cow::Borrowed(" ".into())), +/// Event::SectionKey(section::Key(Cow::Borrowed("autocrlf".into()))), +/// Event::Whitespace(Cow::Borrowed(" ".into())), +/// Event::KeyValueSeparator, +/// Event::Whitespace(Cow::Borrowed(" ".into())), +/// Event::Value(Cow::Borrowed("input".into())), +/// # ]); +/// ``` +/// +/// Note the two whitespace events between the key and value pair! Those two +/// events actually refer to the whitespace between the name and value and the +/// equal sign. So if the config instead had `autocrlf=input`, those whitespace +/// events would no longer be present. +/// +/// ## `KeyValueSeparator` event is not guaranteed to emit +/// +/// Consider the following `git-config` example: +/// +/// ```text +/// [core] +/// autocrlf +/// ``` +/// +/// This is a valid config with a `autocrlf` key having an implicit `true` +/// value. This means that there is not a `=` separating the key and value, +/// which means that the corresponding event won't appear either: +/// +/// ``` +/// # use git_config::parse::{Event, event, section}; +/// # use std::borrow::Cow; +/// # let section_header = section::Header { +/// # name: section::Name(Cow::Borrowed("core".into())), +/// # separator: None, +/// # subsection_name: None, +/// # }; +/// # let section_data = "[core]\n autocrlf"; +/// # assert_eq!(event::List::from_str(section_data).unwrap().into_vec(), vec![ +/// Event::SectionHeader(section_header), +/// Event::Newline(Cow::Borrowed("\n".into())), +/// Event::Whitespace(Cow::Borrowed(" ".into())), +/// Event::SectionKey(section::Key(Cow::Borrowed("autocrlf".into()))), +/// Event::Value(Cow::Borrowed("".into())), +/// # ]); +/// ``` +/// +/// ## Quoted values are not unquoted +/// +/// Consider the following `git-config` example: +/// +/// ```text +/// [core] +/// autocrlf=true"" +/// filemode=fa"lse" +/// ``` +/// +/// Both these events, when fully processed, should normally be `true` and +/// `false`. However, because this parser is zero-copy, we cannot process +/// partially quoted values, such as the `false` example. As a result, to +/// maintain consistency, the parser will just take all values as literals. The +/// relevant event stream emitted is thus emitted as: +/// +/// ``` +/// # use git_config::parse::{Event, event, section}; +/// # use std::borrow::Cow; +/// # let section_header = section::Header { +/// # name: section::Name(Cow::Borrowed("core".into())), +/// # separator: None, +/// # subsection_name: None, +/// # }; +/// # let section_data = "[core]\nautocrlf=true\"\"\nfilemode=fa\"lse\""; +/// # assert_eq!(event::from_str(section_data).unwrap().into_vec(), vec![ +/// Event::SectionHeader(section_header), +/// Event::Newline(Cow::Borrowed("\n".into())), +/// Event::SectionKey(section::Key(Cow::Borrowed("autocrlf".into()))), +/// Event::KeyValueSeparator, +/// Event::Value(Cow::Borrowed(r#"true"""#.into())), +/// Event::Newline(Cow::Borrowed("\n".into())), +/// Event::SectionKey(section::Key(Cow::Borrowed("filemode".into()))), +/// Event::KeyValueSeparator, +/// Event::Value(Cow::Borrowed(r#"fa"lse""#.into())), +/// # ]); +/// ``` +/// +/// ## Whitespace after line continuations are part of the value +/// +/// Consider the following `git-config` example: +/// +/// ```text +/// [some-section] +/// file=a\ +/// c +/// ``` +/// +/// Because how `git-config` treats continuations, the whitespace preceding `c` +/// are in fact part of the value of `file`. The fully interpreted key/value +/// pair is actually `file=a c`. As a result, the parser will provide this +/// split value accordingly: +/// +/// ``` +/// # use git_config::parse::{Event, event, section}; +/// # use std::borrow::Cow; +/// # let section_header = section::Header { +/// # name: section::Name(Cow::Borrowed("some-section".into())), +/// # separator: None, +/// # subsection_name: None, +/// # }; +/// # let section_data = "[some-section]\nfile=a\\\n c"; +/// # assert_eq!(event::from_str(section_data).unwrap().into_vec(), vec![ +/// Event::SectionHeader(section_header), +/// Event::Newline(Cow::Borrowed("\n".into())), +/// Event::SectionKey(section::Key(Cow::Borrowed("file".into()))), +/// Event::KeyValueSeparator, +/// Event::ValueNotDone(Cow::Borrowed("a".into())), +/// Event::Newline(Cow::Borrowed("\n".into())), +/// Event::ValueDone(Cow::Borrowed(" c".into())), +/// # ]); +/// ``` +/// +/// [`File`]: crate::File +/// [`.ini` file format]: https://en.wikipedia.org/wiki/INI_file +/// [`git`'s documentation]: https://git-scm.com/docs/git-config#_configuration_file +/// [`FromStr`]: std::str::FromStr +/// [`From<&'_ str>`]: std::convert::From +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] +pub struct List<'a> { + pub(crate) frontmatter: Vec>, + pub(crate) sections: Vec>, +} + +/// +pub mod list; + +impl Event<'_> { + /// Generates a byte representation of the value. This should be used when + /// non-UTF-8 sequences are present or a UTF-8 representation can't be + /// guaranteed. + #[must_use] + pub fn to_bstring(&self) -> BString { + self.into() + } + + /// Coerces into an owned instance. This differs from the standard [`clone`] + /// implementation as calling clone will _not_ copy the borrowed variant, + /// while this method will. In other words: + /// + /// | Borrow type | `.clone()` | `to_owned()` | + /// | ----------- | ---------- | ------------ | + /// | Borrowed | Borrowed | Owned | + /// | Owned | Owned | Owned | + /// + /// This can be most effectively seen by the differing lifetimes between the + /// two. This method guarantees a `'static` lifetime, while `clone` does + /// not. + /// + /// [`clone`]: Self::clone + #[must_use] + pub fn to_owned(&self) -> Event<'static> { + match self { + Event::Comment(e) => Event::Comment(e.to_owned()), + Event::SectionHeader(e) => Event::SectionHeader(e.to_owned()), + Event::SectionKey(e) => Event::SectionKey(e.to_owned()), + Event::Value(e) => Event::Value(Cow::Owned(e.clone().into_owned())), + Event::ValueNotDone(e) => Event::ValueNotDone(Cow::Owned(e.clone().into_owned())), + Event::ValueDone(e) => Event::ValueDone(Cow::Owned(e.clone().into_owned())), + Event::Newline(e) => Event::Newline(Cow::Owned(e.clone().into_owned())), + Event::Whitespace(e) => Event::Whitespace(Cow::Owned(e.clone().into_owned())), + Event::KeyValueSeparator => Event::KeyValueSeparator, + } + } +} + +impl Display for Event<'_> { + /// Note that this is a best-effort attempt at printing an `Event`. If + /// there are non UTF-8 values in your config, this will _NOT_ render + /// as read. + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Value(e) | Self::ValueNotDone(e) | Self::ValueDone(e) => match std::str::from_utf8(e) { + Ok(e) => e.fmt(f), + Err(_) => write!(f, "{:02x?}", e), + }, + Self::Comment(e) => e.fmt(f), + Self::SectionHeader(e) => e.fmt(f), + Self::SectionKey(e) => e.fmt(f), + Self::Newline(e) | Self::Whitespace(e) => e.fmt(f), + Self::KeyValueSeparator => write!(f, "="), + } + } +} + +impl From> for BString { + fn from(event: Event<'_>) -> Self { + event.into() + } +} + +impl From<&Event<'_>> for BString { + fn from(event: &Event<'_>) -> Self { + match event { + Event::Value(e) | Event::ValueNotDone(e) | Event::ValueDone(e) => e.as_ref().into(), + Event::Comment(e) => e.into(), + Event::SectionHeader(e) => e.into(), + Event::SectionKey(e) => e.0.as_ref().into(), + Event::Newline(e) | Event::Whitespace(e) => e.as_ref().into(), + Event::KeyValueSeparator => "=".into(), + } + } +} diff --git a/git-config/src/parse/mod.rs b/git-config/src/parse/mod.rs index 2970c079287..e6d293afcb0 100644 --- a/git-config/src/parse/mod.rs +++ b/git-config/src/parse/mod.rs @@ -12,220 +12,6 @@ use bstr::BStr; use std::{borrow::Cow, hash::Hash}; -/// A zero-copy `git-config` file parser. -/// -/// This is parser exposes low-level syntactic events from a `git-config` file. -/// Generally speaking, you'll want to use [`File`] as it wraps -/// around the parser to provide a higher-level abstraction to a `git-config` -/// file, including querying, modifying, and updating values. -/// -/// This parser guarantees that the events emitted are sufficient to -/// reconstruct a `git-config` file identical to the source `git-config`. -/// -/// # Differences between a `.ini` parser -/// -/// While the `git-config` format closely resembles the [`.ini` file format], -/// there are subtle differences that make them incompatible. For one, the file -/// format is not well defined, and there exists no formal specification to -/// adhere to. Thus, attempting to use an `.ini` parser on a `git-config` file -/// may successfully parse invalid configuration files. -/// -/// For concrete examples, some notable differences are: -/// - `git-config` sections permit subsections via either a quoted string -/// (`[some-section "subsection"]`) or via the deprecated dot notation -/// (`[some-section.subsection]`). Successful parsing these section names is not -/// well defined in typical `.ini` parsers. This parser will handle these cases -/// perfectly. -/// - Comment markers are not strictly defined either. This parser will always -/// and only handle a semicolon or octothorpe (also known as a hash or number -/// sign). -/// - Global properties may be allowed in `.ini` parsers, but is strictly -/// disallowed by this parser. -/// - Only `\t`, `\n`, `\b` `\\` are valid escape characters. -/// - Quoted and semi-quoted values will be parsed (but quotes will be included -/// in event outputs). An example of a semi-quoted value is `5"hello world"`, -/// which should be interpreted as `5hello world`. -/// - Line continuations via a `\` character is supported. -/// - Whitespace handling similarly follows the `git-config` specification as -/// closely as possible, where excess whitespace after a non-quoted value are -/// trimmed, and line continuations onto a new line with excess spaces are kept. -/// - Only equal signs (optionally padded by spaces) are valid name/value -/// delimiters. -/// -/// Note that that things such as case-sensitivity or duplicate sections are -/// _not_ handled. This parser is a low level _syntactic_ interpreter (as a -/// parser should be), and higher level wrappers around this parser (which may -/// or may not be zero-copy) should handle _semantic_ values. This also means -/// that string-like values are not interpreted. For example, `hello"world"` -/// would be read at a high level as `helloworld` but this parser will return -/// the former instead, with the extra quotes. This is because it is not the -/// responsibility of the parser to interpret these values, and doing so would -/// necessarily require a copy, which this parser avoids. -/// -/// # Trait Implementations -/// -/// - This struct does _not_ implement [`FromStr`] due to lifetime -/// constraints implied on the required `from_str` method. Instead, it provides -/// [`From<&'_ str>`]. -/// -/// # Idioms -/// -/// If you do want to use this parser, there are some idioms that may help you -/// with interpreting sequences of events. -/// -/// ## `Value` events do not immediately follow `Key` events -/// -/// Consider the following `git-config` example: -/// -/// ```text -/// [core] -/// autocrlf = input -/// ``` -/// -/// Because this parser guarantees perfect reconstruction, there are many -/// non-significant events that occur in addition to the ones you may expect: -/// -/// ``` -/// # use git_config::parse::{Event, State, section}; -/// # use std::borrow::Cow; -/// # let section_header = section::Header { -/// # name: section::Name(Cow::Borrowed("core".into())), -/// # separator: None, -/// # subsection_name: None, -/// # }; -/// # let section_data = "[core]\n autocrlf = input"; -/// # assert_eq!(State::from_str(section_data).unwrap().into_vec(), vec![ -/// Event::SectionHeader(section_header), -/// Event::Newline(Cow::Borrowed("\n".into())), -/// Event::Whitespace(Cow::Borrowed(" ".into())), -/// Event::SectionKey(section::Key(Cow::Borrowed("autocrlf".into()))), -/// Event::Whitespace(Cow::Borrowed(" ".into())), -/// Event::KeyValueSeparator, -/// Event::Whitespace(Cow::Borrowed(" ".into())), -/// Event::Value(Cow::Borrowed("input".into())), -/// # ]); -/// ``` -/// -/// Note the two whitespace events between the key and value pair! Those two -/// events actually refer to the whitespace between the name and value and the -/// equal sign. So if the config instead had `autocrlf=input`, those whitespace -/// events would no longer be present. -/// -/// ## `KeyValueSeparator` event is not guaranteed to emit -/// -/// Consider the following `git-config` example: -/// -/// ```text -/// [core] -/// autocrlf -/// ``` -/// -/// This is a valid config with a `autocrlf` key having an implicit `true` -/// value. This means that there is not a `=` separating the key and value, -/// which means that the corresponding event won't appear either: -/// -/// ``` -/// # use git_config::parse::{Event, State, section}; -/// # use std::borrow::Cow; -/// # let section_header = section::Header { -/// # name: section::Name(Cow::Borrowed("core".into())), -/// # separator: None, -/// # subsection_name: None, -/// # }; -/// # let section_data = "[core]\n autocrlf"; -/// # assert_eq!(State::from_str(section_data).unwrap().into_vec(), vec![ -/// Event::SectionHeader(section_header), -/// Event::Newline(Cow::Borrowed("\n".into())), -/// Event::Whitespace(Cow::Borrowed(" ".into())), -/// Event::SectionKey(section::Key(Cow::Borrowed("autocrlf".into()))), -/// Event::Value(Cow::Borrowed("".into())), -/// # ]); -/// ``` -/// -/// ## Quoted values are not unquoted -/// -/// Consider the following `git-config` example: -/// -/// ```text -/// [core] -/// autocrlf=true"" -/// filemode=fa"lse" -/// ``` -/// -/// Both these events, when fully processed, should normally be `true` and -/// `false`. However, because this parser is zero-copy, we cannot process -/// partially quoted values, such as the `false` example. As a result, to -/// maintain consistency, the parser will just take all values as literals. The -/// relevant event stream emitted is thus emitted as: -/// -/// ``` -/// # use git_config::parse::{Event, State, section}; -/// # use std::borrow::Cow; -/// # let section_header = section::Header { -/// # name: section::Name(Cow::Borrowed("core".into())), -/// # separator: None, -/// # subsection_name: None, -/// # }; -/// # let section_data = "[core]\nautocrlf=true\"\"\nfilemode=fa\"lse\""; -/// # assert_eq!(State::from_str(section_data).unwrap().into_vec(), vec![ -/// Event::SectionHeader(section_header), -/// Event::Newline(Cow::Borrowed("\n".into())), -/// Event::SectionKey(section::Key(Cow::Borrowed("autocrlf".into()))), -/// Event::KeyValueSeparator, -/// Event::Value(Cow::Borrowed(r#"true"""#.into())), -/// Event::Newline(Cow::Borrowed("\n".into())), -/// Event::SectionKey(section::Key(Cow::Borrowed("filemode".into()))), -/// Event::KeyValueSeparator, -/// Event::Value(Cow::Borrowed(r#"fa"lse""#.into())), -/// # ]); -/// ``` -/// -/// ## Whitespace after line continuations are part of the value -/// -/// Consider the following `git-config` example: -/// -/// ```text -/// [some-section] -/// file=a\ -/// c -/// ``` -/// -/// Because how `git-config` treats continuations, the whitespace preceding `c` -/// are in fact part of the value of `file`. The fully interpreted key/value -/// pair is actually `file=a c`. As a result, the parser will provide this -/// split value accordingly: -/// -/// ``` -/// # use git_config::parse::{Event, State, section}; -/// # use std::borrow::Cow; -/// # let section_header = section::Header { -/// # name: section::Name(Cow::Borrowed("some-section".into())), -/// # separator: None, -/// # subsection_name: None, -/// # }; -/// # let section_data = "[some-section]\nfile=a\\\n c"; -/// # assert_eq!(State::from_str(section_data).unwrap().into_vec(), vec![ -/// Event::SectionHeader(section_header), -/// Event::Newline(Cow::Borrowed("\n".into())), -/// Event::SectionKey(section::Key(Cow::Borrowed("file".into()))), -/// Event::KeyValueSeparator, -/// Event::ValueNotDone(Cow::Borrowed("a".into())), -/// Event::Newline(Cow::Borrowed("\n".into())), -/// Event::ValueDone(Cow::Borrowed(" c".into())), -/// # ]); -/// ``` -/// -/// [`File`]: crate::File -/// [`.ini` file format]: https://en.wikipedia.org/wiki/INI_file -/// [`git`'s documentation]: https://git-scm.com/docs/git-config#_configuration_file -/// [`FromStr`]: std::str::FromStr -/// [`From<&'_ str>`]: std::convert::From -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] -pub struct State<'a> { - frontmatter: Vec>, - sections: Vec>, -} - /// pub mod state; @@ -275,7 +61,8 @@ pub enum Event<'a> { KeyValueSeparator, } -mod event; +/// +pub mod event; /// A parsed section containing the header and the section events. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index 9ffd36d4c60..fb0f01fe511 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -1,4 +1,4 @@ -use crate::parse::{section, Comment, Error, Event, Section, State}; +use crate::parse::{event::List, section, Comment, Error, Event, Section}; use bstr::{BStr, BString, ByteSlice, ByteVec}; use std::borrow::Cow; @@ -28,7 +28,7 @@ use nom::{ /// This generally is due to either invalid names or if there's extraneous /// data succeeding valid `git-config` data. #[allow(clippy::shadow_unrelated)] -pub fn from_bytes(input: &[u8]) -> Result, Error<'_>> { +pub fn from_bytes(input: &[u8]) -> Result, Error<'_>> { let bom = unicode_bom::Bom::from(input); let mut newlines = 0; let (i, frontmatter) = many0(alt(( @@ -47,7 +47,7 @@ pub fn from_bytes(input: &[u8]) -> Result, Error<'_>> { .expect("many0(alt(...)) panicked. Likely a bug in one of the children parsers."); if i.is_empty() { - return Ok(State { + return Ok(List { frontmatter, sections: vec![], }); @@ -80,7 +80,7 @@ pub fn from_bytes(input: &[u8]) -> Result, Error<'_>> { }); } - Ok(State { frontmatter, sections }) + Ok(List { frontmatter, sections }) } /// Parses the provided bytes, returning an [`Parser`] that contains allocated @@ -95,7 +95,7 @@ pub fn from_bytes(input: &[u8]) -> Result, Error<'_>> { /// This generally is due to either invalid names or if there's extraneous /// data succeeding valid `git-config` data. #[allow(clippy::shadow_unrelated)] -pub fn from_bytes_owned(input: &[u8]) -> Result, Error<'static>> { +pub fn from_bytes_owned(input: &[u8]) -> Result, Error<'static>> { // FIXME: This is duplication is necessary until comment, take_spaces, and take_newlines // accept cows instead, since we don't want to unnecessarily copy the frontmatter // events in a hypothetical parse_from_cow function. @@ -119,7 +119,7 @@ pub fn from_bytes_owned(input: &[u8]) -> Result, Error<'static>> .expect("many0(alt(...)) panicked. Likely a bug in one of the children parsers."); let frontmatter = frontmatter.iter().map(Event::to_owned).collect(); if i.is_empty() { - return Ok(State { + return Ok(List { frontmatter, sections: vec![], }); @@ -152,7 +152,7 @@ pub fn from_bytes_owned(input: &[u8]) -> Result, Error<'static>> }); } - Ok(State { frontmatter, sections }) + Ok(List { frontmatter, sections }) } fn comment(i: &[u8]) -> IResult<&[u8], Comment<'_>> { diff --git a/git-config/src/parse/state.rs b/git-config/src/parse/state.rs index 01becf205b0..8b137891791 100644 --- a/git-config/src/parse/state.rs +++ b/git-config/src/parse/state.rs @@ -1,159 +1 @@ -use crate::parse::{Error, Event, Section, State}; -use std::convert::TryFrom; -use std::io::Read; -impl State<'static> { - /// Parses a git config located at the provided path. On success, returns a - /// [`State`] that provides methods to accessing leading comments and sections - /// of a `git-config` file and can be converted into an iterator of [`Event`] - /// for higher level processing. - /// - /// Note that since we accept a path rather than a reference to the actual - /// bytes, this function is _not_ zero-copy, as the Parser must own (and thus - /// copy) the bytes that it reads from. Consider one of the other variants if - /// performance is a concern. - /// - /// # Errors - /// - /// Returns an error if there was an IO error or the read file is not a valid - /// `git-config` This generally is due to either invalid names or if there's - /// extraneous data succeeding valid `git-config` data. - pub fn from_path>(path: P) -> Result, from_path::Error> { - let mut bytes = vec![]; - let mut file = std::fs::File::open(path)?; - file.read_to_end(&mut bytes)?; - crate::parse::nom::from_bytes_owned(&bytes).map_err(from_path::Error::Parse) - } - - /// Parses the provided bytes, returning an [`State`] that contains allocated - /// and owned events. This is similar to [`State::from_bytes()`], but performance - /// is degraded as it requires allocation for every event. However, this permits - /// the reference bytes to be dropped, allowing the parser to be passed around - /// without lifetime worries. - /// - /// # Errors - /// - /// Returns an error if the string provided is not a valid `git-config`. - /// This generally is due to either invalid names or if there's extraneous - /// data succeeding valid `git-config` data. - #[allow(clippy::shadow_unrelated)] - pub fn from_bytes_owned(input: &[u8]) -> Result, Error<'static>> { - crate::parse::nom::from_bytes_owned(input) - } -} - -impl<'a> State<'a> { - /// Attempt to zero-copy parse the provided `&str`. On success, returns a - /// [`State`] that provides methods to accessing leading comments and sections - /// of a `git-config` file and can be converted into an iterator of [`Event`] - /// for higher level processing. - /// - /// # Errors - /// - /// Returns an error if the string provided is not a valid `git-config`. - /// This generally is due to either invalid names or if there's extraneous - /// data succeeding valid `git-config` data. - #[allow(clippy::should_implement_trait)] - pub fn from_str(input: &'a str) -> Result, Error<'a>> { - crate::parse::nom::from_bytes(input.as_bytes()) - } - - /// Attempt to zero-copy parse the provided bytes. On success, returns a - /// [`State`] that provides methods to accessing leading comments and sections - /// of a `git-config` file and can be converted into an iterator of [`Event`] - /// for higher level processing. - /// - /// # Errors - /// - /// Returns an error if the string provided is not a valid `git-config`. - /// This generally is due to either invalid names or if there's extraneous - /// data succeeding valid `git-config` data. - #[allow(clippy::shadow_unrelated)] - pub fn from_bytes(input: &'a [u8]) -> Result, Error<'a>> { - crate::parse::nom::from_bytes(input) - } -} - -impl<'a> State<'a> { - /// Returns the leading events (any comments, whitespace, or newlines before - /// a section) from the parser. Consider [`State::take_frontmatter`] if - /// you need an owned copy only once. If that function was called, then this - /// will always return an empty slice. - #[must_use] - pub fn frontmatter(&self) -> &[Event<'a>] { - &self.frontmatter - } - - /// Takes the leading events (any comments, whitespace, or newlines before - /// a section) from the parser. Subsequent calls will return an empty vec. - /// Consider [`State::frontmatter`] if you only need a reference to the - /// frontmatter - pub fn take_frontmatter(&mut self) -> Vec> { - std::mem::take(&mut self.frontmatter) - } - - /// Returns the parsed sections from the parser. Consider - /// [`State::take_sections`] if you need an owned copy only once. If that - /// function was called, then this will always return an empty slice. - #[must_use] - pub fn sections(&self) -> &[Section<'a>] { - &self.sections - } - - /// Takes the parsed sections from the parser. Subsequent calls will return - /// an empty vec. Consider [`State::sections`] if you only need a reference - /// to the comments. - pub fn take_sections(&mut self) -> Vec> { - let mut to_return = vec![]; - std::mem::swap(&mut self.sections, &mut to_return); - to_return - } - - /// Consumes the parser to produce a Vec of Events. - #[must_use] - pub fn into_vec(self) -> Vec> { - self.into_iter().collect() - } - - /// Consumes the parser to produce an iterator of Events. - #[must_use = "iterators are lazy and do nothing unless consumed"] - #[allow(clippy::should_implement_trait)] - pub fn into_iter(self) -> impl Iterator> + std::iter::FusedIterator { - self.frontmatter.into_iter().chain( - self.sections.into_iter().flat_map(|section| { - std::iter::once(Event::SectionHeader(section.section_header)).chain(section.events) - }), - ) - } -} - -impl<'a> TryFrom<&'a str> for State<'a> { - type Error = Error<'a>; - - fn try_from(value: &'a str) -> Result { - Self::from_str(value) - } -} - -impl<'a> TryFrom<&'a [u8]> for State<'a> { - type Error = Error<'a>; - - fn try_from(value: &'a [u8]) -> Result { - crate::parse::nom::from_bytes(value) - } -} - -pub mod from_path { - /// An error type representing a Parser [`Error`] or an [`IO error`]. This is - /// returned from functions that will perform IO on top of standard parsing, - /// such as reading from a file. - /// - /// [`IO error`]: std::io::Error - #[derive(Debug, thiserror::Error)] - pub enum Error { - #[error(transparent)] - Parse(crate::parse::Error<'static>), - #[error(transparent)] - Io(#[from] std::io::Error), - } -} diff --git a/git-config/src/parse/tests.rs b/git-config/src/parse/tests.rs index f4b186afd2f..f684371d127 100644 --- a/git-config/src/parse/tests.rs +++ b/git-config/src/parse/tests.rs @@ -1,5 +1,5 @@ mod parse { - use crate::parse::State; + use crate::parse::event::List; #[test] fn parser_skips_bom() { @@ -13,34 +13,34 @@ mod parse { "; assert_eq!( - State::from_bytes(bytes), - State::from_bytes(bytes_with_gb18030_bom.as_bytes()) + List::from_bytes(bytes), + List::from_bytes(bytes_with_gb18030_bom.as_bytes()) ); assert_eq!( - State::from_bytes_owned(bytes), - State::from_bytes_owned(bytes_with_gb18030_bom.as_bytes()) + List::from_bytes_owned(bytes), + List::from_bytes_owned(bytes_with_gb18030_bom.as_bytes()) ); } } #[cfg(test)] mod error { - use crate::parse::State; + use crate::parse::event::List; #[test] fn line_no_is_one_indexed() { - assert_eq!(State::from_str("[hello").unwrap_err().line_number(), 1); + assert_eq!(List::from_str("[hello").unwrap_err().line_number(), 1); } #[test] fn remaining_data_contains_bad_tokens() { - assert_eq!(State::from_str("[hello").unwrap_err().remaining_data(), b"[hello"); + assert_eq!(List::from_str("[hello").unwrap_err().remaining_data(), b"[hello"); } #[test] fn to_string_truncates_extra_values() { assert_eq!( - State::from_str("[1234567890").unwrap_err().to_string(), + List::from_str("[1234567890").unwrap_err().to_string(), "Got an unexpected token on line 1 while trying to parse a section header: '[123456789' ... (1 characters omitted)" ); } diff --git a/git-config/tests/file/access/read_only.rs b/git-config/tests/file/access/read_only.rs index 22b2389c774..c3ffcd4d0ea 100644 --- a/git-config/tests/file/access/read_only.rs +++ b/git-config/tests/file/access/read_only.rs @@ -23,7 +23,7 @@ fn get_value_for_all_provided_values() -> crate::Result { location-quoted = "~/quoted" "#; - let file = git_config::parse::State::from_bytes_owned(config.as_bytes()).map(File::from)?; + let file = git_config::parse::event::List::from_bytes_owned(config.as_bytes()).map(File::from)?; assert_eq!( file.value::("core", None, "bool-explicit")?, diff --git a/git-config/tests/file/from_paths/mod.rs b/git-config/tests/file/from_paths/mod.rs index dfe097a596a..85e1020f51c 100644 --- a/git-config/tests/file/from_paths/mod.rs +++ b/git-config/tests/file/from_paths/mod.rs @@ -18,7 +18,7 @@ fn file_not_found() { let paths = vec![config_path]; let error = File::from_paths(paths, Default::default()).unwrap_err(); assert!( - matches!(error, Error::Parse(parse::state::from_path::Error::Io(io_error)) if io_error.kind() == io::ErrorKind::NotFound) + matches!(error, Error::Parse(parse::event::list::from_path::Error::Io(io_error)) if io_error.kind() == io::ErrorKind::NotFound) ); } diff --git a/git-config/tests/parse/mod.rs b/git-config/tests/parse/mod.rs index 31f0b9dd65e..e9bf9770c48 100644 --- a/git-config/tests/parse/mod.rs +++ b/git-config/tests/parse/mod.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use git_config::parse::{section, Event, State}; +use git_config::parse::{event, section, Event}; pub fn section_header_event(name: &str, subsection: impl Into>) -> Event<'_> { Event::SectionHeader(section_header(name, subsection)) @@ -71,7 +71,7 @@ fn personal_config() { defaultBranch = master"#; assert_eq!( - State::from_str(config) + event::List::from_str(config) .unwrap() .into_vec(), vec![ @@ -186,13 +186,13 @@ fn personal_config() { #[test] fn parse_empty() { - assert_eq!(State::from_str("").unwrap().into_vec(), vec![]); + assert_eq!(event::List::from_str("").unwrap().into_vec(), vec![]); } #[test] fn parse_whitespace() { assert_eq!( - State::from_str("\n \n \n").unwrap().into_vec(), + event::List::from_str("\n \n \n").unwrap().into_vec(), vec![newline(), whitespace(" "), newline(), whitespace(" "), newline()] ) } @@ -200,7 +200,7 @@ fn parse_whitespace() { #[test] fn newline_events_are_merged() { assert_eq!( - State::from_str("\n\n\n\n\n").unwrap().into_vec(), + event::List::from_str("\n\n\n\n\n").unwrap().into_vec(), vec![newline_custom("\n\n\n\n\n")] ); } @@ -209,23 +209,23 @@ fn newline_events_are_merged() { fn error() { let input = "[a_b]\n c=d"; assert_eq!( - State::from_str(input).unwrap_err().to_string(), + event::List::from_str(input).unwrap_err().to_string(), "Got an unexpected token on line 1 while trying to parse a section header: '[a_b]\n c=d'", "underscores in section names aren't allowed and will be rejected by git" ); let input = "[core] a=b\n 4a=3"; assert_eq!( - State::from_str(input).unwrap_err().to_string(), + event::List::from_str(input).unwrap_err().to_string(), "Got an unexpected token on line 2 while trying to parse a config name: '4a=3'" ); let input = "[core] a=b\n =3"; assert_eq!( - State::from_str(input).unwrap_err().to_string(), + event::List::from_str(input).unwrap_err().to_string(), "Got an unexpected token on line 2 while trying to parse a config name: '=3'" ); let input = "[core"; assert_eq!( - State::from_str(input).unwrap_err().to_string(), + event::List::from_str(input).unwrap_err().to_string(), "Got an unexpected token on line 1 while trying to parse a section header: '[core'" ); } From ea6765093b5475912ba1aa81d4440cbf5dd49fb6 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 11:18:10 +0800 Subject: [PATCH 068/366] change!: rename `parse::event::List` to `parse::Events` (#331) --- git-config/benches/large_config_file.rs | 4 +- git-config/fuzz/fuzz_targets/parse.rs | 2 +- git-config/src/file/from_paths.rs | 2 +- git-config/src/file/impls.rs | 10 +- git-config/src/file/init.rs | 4 +- git-config/src/parse/event.rs | 81 +++++++ git-config/src/parse/event/list.rs | 159 ------------- .../src/parse/{event/mod.rs => events.rs} | 210 +++++++++++------- git-config/src/parse/mod.rs | 22 ++ git-config/src/parse/nom/mod.rs | 14 +- git-config/src/parse/tests.rs | 18 +- git-config/tests/file/access/read_only.rs | 2 +- git-config/tests/file/from_paths/mod.rs | 2 +- git-config/tests/parse/mod.rs | 18 +- 14 files changed, 276 insertions(+), 272 deletions(-) create mode 100644 git-config/src/parse/event.rs delete mode 100644 git-config/src/parse/event/list.rs rename git-config/src/parse/{event/mod.rs => events.rs} (56%) diff --git a/git-config/benches/large_config_file.rs b/git-config/benches/large_config_file.rs index ba1bcf29b76..abf65531e8b 100644 --- a/git-config/benches/large_config_file.rs +++ b/git-config/benches/large_config_file.rs @@ -1,7 +1,7 @@ use std::convert::TryFrom; use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use git_config::{parse::List, File}; +use git_config::{parse::Events, File}; fn git_config(c: &mut Criterion) { c.bench_function("GitConfig large config file", |b| { @@ -11,7 +11,7 @@ fn git_config(c: &mut Criterion) { fn parser(c: &mut Criterion) { c.bench_function("Parser large config file", |b| { - b.iter(|| List::try_from(black_box(CONFIG_FILE)).unwrap()) + b.iter(|| Events::try_from(black_box(CONFIG_FILE)).unwrap()) }); } diff --git a/git-config/fuzz/fuzz_targets/parse.rs b/git-config/fuzz/fuzz_targets/parse.rs index d41e0fd3ed3..8343b3217e7 100644 --- a/git-config/fuzz/fuzz_targets/parse.rs +++ b/git-config/fuzz/fuzz_targets/parse.rs @@ -4,5 +4,5 @@ use libfuzzer_sys::fuzz_target; fuzz_target!(|data: &[u8]| { // Don't name this _; Rust may optimize it out. - let _a = git_config::parse::event::List::from_bytes(data); + let _a = git_config::parse::Events::from_bytes(data); }); diff --git a/git-config/src/file/from_paths.rs b/git-config/src/file/from_paths.rs index a9d90578920..f89ca25c6a5 100644 --- a/git-config/src/file/from_paths.rs +++ b/git-config/src/file/from_paths.rs @@ -5,7 +5,7 @@ use crate::{parse, path::interpolate}; #[allow(missing_docs)] pub enum Error { #[error(transparent)] - Parse(#[from] parse::event::list::from_path::Error), + Parse(#[from] parse::events::from_path::Error), #[error(transparent)] Interpolate(#[from] interpolate::Error), #[error("The maximum allowed length {} of the file include chain built by following nested resolve_includes is exceeded", .max_depth)] diff --git a/git-config/src/file/impls.rs b/git-config/src/file/impls.rs index 79dbf10f5cb..79d5a236e36 100644 --- a/git-config/src/file/impls.rs +++ b/git-config/src/file/impls.rs @@ -9,7 +9,7 @@ impl<'a> TryFrom<&'a str> for File<'a> { /// Convenience constructor. Attempts to parse the provided string into a /// [`File`]. See [`State::from_str()`] for more information. fn try_from(s: &'a str) -> Result, Self::Error> { - parse::event::List::from_str(s).map(Self::from) + parse::Events::from_str(s).map(Self::from) } } @@ -21,7 +21,7 @@ impl<'a> TryFrom<&'a [u8]> for File<'a> { /// /// [`parse_from_bytes`]: crate::parser::parse_from_bytes fn try_from(value: &'a [u8]) -> Result, Self::Error> { - parse::event::List::from_bytes(value).map(File::from) + parse::Events::from_bytes(value).map(File::from) } } @@ -31,12 +31,12 @@ impl<'a> TryFrom<&'a BString> for File<'a> { /// Convenience constructor. Attempts to parse the provided byte string into //// a [`File`]. See [`State::from_bytes()`] for more information. fn try_from(value: &'a BString) -> Result, Self::Error> { - parse::event::List::from_bytes(value.as_ref()).map(File::from) + parse::Events::from_bytes(value.as_ref()).map(File::from) } } -impl<'a> From> for File<'a> { - fn from(parser: parse::event::List<'a>) -> Self { +impl<'a> From> for File<'a> { + fn from(parser: parse::Events<'a>) -> Self { let mut new_self = Self::default(); // Current section that we're building diff --git a/git-config/src/file/init.rs b/git-config/src/file/init.rs index 4fcabf8ac5d..742a09bb61b 100644 --- a/git-config/src/file/init.rs +++ b/git-config/src/file/init.rs @@ -18,8 +18,8 @@ impl<'a> File<'a> { /// /// Returns an error if there was an IO error or if the file wasn't a valid /// git-config file. - pub fn at>(path: P) -> Result { - parse::event::List::from_path(path).map(Self::from) + pub fn at>(path: P) -> Result { + parse::Events::from_path(path).map(Self::from) } /// Constructs a `git-config` file from the provided paths in the order provided. diff --git a/git-config/src/parse/event.rs b/git-config/src/parse/event.rs new file mode 100644 index 00000000000..63dad9774f0 --- /dev/null +++ b/git-config/src/parse/event.rs @@ -0,0 +1,81 @@ +use crate::parse::Event; +use bstr::BString; +use std::borrow::Cow; +use std::fmt::Display; + +impl Event<'_> { + /// Generates a byte representation of the value. This should be used when + /// non-UTF-8 sequences are present or a UTF-8 representation can't be + /// guaranteed. + #[must_use] + pub fn to_bstring(&self) -> BString { + self.into() + } + + /// Coerces into an owned instance. This differs from the standard [`clone`] + /// implementation as calling clone will _not_ copy the borrowed variant, + /// while this method will. In other words: + /// + /// | Borrow type | `.clone()` | `to_owned()` | + /// | ----------- | ---------- | ------------ | + /// | Borrowed | Borrowed | Owned | + /// | Owned | Owned | Owned | + /// + /// This can be most effectively seen by the differing lifetimes between the + /// two. This method guarantees a `'static` lifetime, while `clone` does + /// not. + /// + /// [`clone`]: Self::clone + #[must_use] + pub fn to_owned(&self) -> Event<'static> { + match self { + Event::Comment(e) => Event::Comment(e.to_owned()), + Event::SectionHeader(e) => Event::SectionHeader(e.to_owned()), + Event::SectionKey(e) => Event::SectionKey(e.to_owned()), + Event::Value(e) => Event::Value(Cow::Owned(e.clone().into_owned())), + Event::ValueNotDone(e) => Event::ValueNotDone(Cow::Owned(e.clone().into_owned())), + Event::ValueDone(e) => Event::ValueDone(Cow::Owned(e.clone().into_owned())), + Event::Newline(e) => Event::Newline(Cow::Owned(e.clone().into_owned())), + Event::Whitespace(e) => Event::Whitespace(Cow::Owned(e.clone().into_owned())), + Event::KeyValueSeparator => Event::KeyValueSeparator, + } + } +} + +impl Display for Event<'_> { + /// Note that this is a best-effort attempt at printing an `Event`. If + /// there are non UTF-8 values in your config, this will _NOT_ render + /// as read. + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Value(e) | Self::ValueNotDone(e) | Self::ValueDone(e) => match std::str::from_utf8(e) { + Ok(e) => e.fmt(f), + Err(_) => write!(f, "{:02x?}", e), + }, + Self::Comment(e) => e.fmt(f), + Self::SectionHeader(e) => e.fmt(f), + Self::SectionKey(e) => e.fmt(f), + Self::Newline(e) | Self::Whitespace(e) => e.fmt(f), + Self::KeyValueSeparator => write!(f, "="), + } + } +} + +impl From> for BString { + fn from(event: Event<'_>) -> Self { + event.into() + } +} + +impl From<&Event<'_>> for BString { + fn from(event: &Event<'_>) -> Self { + match event { + Event::Value(e) | Event::ValueNotDone(e) | Event::ValueDone(e) => e.as_ref().into(), + Event::Comment(e) => e.into(), + Event::SectionHeader(e) => e.into(), + Event::SectionKey(e) => e.0.as_ref().into(), + Event::Newline(e) | Event::Whitespace(e) => e.as_ref().into(), + Event::KeyValueSeparator => "=".into(), + } + } +} diff --git a/git-config/src/parse/event/list.rs b/git-config/src/parse/event/list.rs deleted file mode 100644 index dea7663d1b2..00000000000 --- a/git-config/src/parse/event/list.rs +++ /dev/null @@ -1,159 +0,0 @@ -use crate::parse::{event::List, Error, Event, Section}; -use std::convert::TryFrom; -use std::io::Read; - -impl List<'static> { - /// Parses a git config located at the provided path. On success, returns a - /// [`State`] that provides methods to accessing leading comments and sections - /// of a `git-config` file and can be converted into an iterator of [`Event`] - /// for higher level processing. - /// - /// Note that since we accept a path rather than a reference to the actual - /// bytes, this function is _not_ zero-copy, as the Parser must own (and thus - /// copy) the bytes that it reads from. Consider one of the other variants if - /// performance is a concern. - /// - /// # Errors - /// - /// Returns an error if there was an IO error or the read file is not a valid - /// `git-config` This generally is due to either invalid names or if there's - /// extraneous data succeeding valid `git-config` data. - pub fn from_path>(path: P) -> Result, from_path::Error> { - let mut bytes = vec![]; - let mut file = std::fs::File::open(path)?; - file.read_to_end(&mut bytes)?; - crate::parse::nom::from_bytes_owned(&bytes).map_err(from_path::Error::Parse) - } - - /// Parses the provided bytes, returning an [`State`] that contains allocated - /// and owned events. This is similar to [`State::from_bytes()`], but performance - /// is degraded as it requires allocation for every event. However, this permits - /// the reference bytes to be dropped, allowing the parser to be passed around - /// without lifetime worries. - /// - /// # Errors - /// - /// Returns an error if the string provided is not a valid `git-config`. - /// This generally is due to either invalid names or if there's extraneous - /// data succeeding valid `git-config` data. - #[allow(clippy::shadow_unrelated)] - pub fn from_bytes_owned(input: &[u8]) -> Result, Error<'static>> { - crate::parse::nom::from_bytes_owned(input) - } -} - -impl<'a> List<'a> { - /// Attempt to zero-copy parse the provided `&str`. On success, returns a - /// [`State`] that provides methods to accessing leading comments and sections - /// of a `git-config` file and can be converted into an iterator of [`Event`] - /// for higher level processing. - /// - /// # Errors - /// - /// Returns an error if the string provided is not a valid `git-config`. - /// This generally is due to either invalid names or if there's extraneous - /// data succeeding valid `git-config` data. - #[allow(clippy::should_implement_trait)] - pub fn from_str(input: &'a str) -> Result, Error<'a>> { - crate::parse::nom::from_bytes(input.as_bytes()) - } - - /// Attempt to zero-copy parse the provided bytes. On success, returns a - /// [`State`] that provides methods to accessing leading comments and sections - /// of a `git-config` file and can be converted into an iterator of [`Event`] - /// for higher level processing. - /// - /// # Errors - /// - /// Returns an error if the string provided is not a valid `git-config`. - /// This generally is due to either invalid names or if there's extraneous - /// data succeeding valid `git-config` data. - #[allow(clippy::shadow_unrelated)] - pub fn from_bytes(input: &'a [u8]) -> Result, Error<'a>> { - crate::parse::nom::from_bytes(input) - } -} - -impl<'a> List<'a> { - /// Returns the leading events (any comments, whitespace, or newlines before - /// a section) from the parser. Consider [`State::take_frontmatter`] if - /// you need an owned copy only once. If that function was called, then this - /// will always return an empty slice. - #[must_use] - pub fn frontmatter(&self) -> &[Event<'a>] { - &self.frontmatter - } - - /// Takes the leading events (any comments, whitespace, or newlines before - /// a section) from the parser. Subsequent calls will return an empty vec. - /// Consider [`State::frontmatter`] if you only need a reference to the - /// frontmatter - pub fn take_frontmatter(&mut self) -> Vec> { - std::mem::take(&mut self.frontmatter) - } - - /// Returns the parsed sections from the parser. Consider - /// [`State::take_sections`] if you need an owned copy only once. If that - /// function was called, then this will always return an empty slice. - #[must_use] - pub fn sections(&self) -> &[Section<'a>] { - &self.sections - } - - /// Takes the parsed sections from the parser. Subsequent calls will return - /// an empty vec. Consider [`State::sections`] if you only need a reference - /// to the comments. - pub fn take_sections(&mut self) -> Vec> { - let mut to_return = vec![]; - std::mem::swap(&mut self.sections, &mut to_return); - to_return - } - - /// Consumes the parser to produce a Vec of Events. - #[must_use] - pub fn into_vec(self) -> Vec> { - self.into_iter().collect() - } - - /// Consumes the parser to produce an iterator of Events. - #[must_use = "iterators are lazy and do nothing unless consumed"] - #[allow(clippy::should_implement_trait)] - pub fn into_iter(self) -> impl Iterator> + std::iter::FusedIterator { - self.frontmatter.into_iter().chain( - self.sections.into_iter().flat_map(|section| { - std::iter::once(Event::SectionHeader(section.section_header)).chain(section.events) - }), - ) - } -} - -impl<'a> TryFrom<&'a str> for List<'a> { - type Error = Error<'a>; - - fn try_from(value: &'a str) -> Result { - Self::from_str(value) - } -} - -impl<'a> TryFrom<&'a [u8]> for List<'a> { - type Error = Error<'a>; - - fn try_from(value: &'a [u8]) -> Result { - crate::parse::nom::from_bytes(value) - } -} - -pub mod from_path { - /// An error type representing a Parser [`Error`] or an [`IO error`]. This is - /// returned from functions that will perform IO on top of standard parsing, - /// such as reading from a file. - /// - /// [`IO error`]: std::io::Error - #[derive(Debug, thiserror::Error)] - pub enum Error { - #[error(transparent)] - Parse(crate::parse::Error<'static>), - #[error(transparent)] - Io(#[from] std::io::Error), - } -} diff --git a/git-config/src/parse/event/mod.rs b/git-config/src/parse/events.rs similarity index 56% rename from git-config/src/parse/event/mod.rs rename to git-config/src/parse/events.rs index 33266e3d691..25c0a07b55b 100644 --- a/git-config/src/parse/event/mod.rs +++ b/git-config/src/parse/events.rs @@ -1,7 +1,6 @@ -use crate::parse::{Event, Section}; -use bstr::BString; -use std::borrow::Cow; -use std::fmt::Display; +use crate::parse::{events::from_path, Error, Event, Section}; +use std::convert::TryFrom; +use std::io::Read; /// A zero-copy `git-config` file parser. /// @@ -77,7 +76,7 @@ use std::fmt::Display; /// non-significant events that occur in addition to the ones you may expect: /// /// ``` -/// # use git_config::parse::{Event, event, section}; +/// # use git_config::parse::{Event, Events, section}; /// # use std::borrow::Cow; /// # let section_header = section::Header { /// # name: section::Name(Cow::Borrowed("core".into())), @@ -85,7 +84,7 @@ use std::fmt::Display; /// # subsection_name: None, /// # }; /// # let section_data = "[core]\n autocrlf = input"; -/// # assert_eq!(event::List::from_str(section_data).unwrap().into_vec(), vec![ +/// # assert_eq!(Events::from_str(section_data).unwrap().into_vec(), vec![ /// Event::SectionHeader(section_header), /// Event::Newline(Cow::Borrowed("\n".into())), /// Event::Whitespace(Cow::Borrowed(" ".into())), @@ -116,7 +115,7 @@ use std::fmt::Display; /// which means that the corresponding event won't appear either: /// /// ``` -/// # use git_config::parse::{Event, event, section}; +/// # use git_config::parse::{Event, Events, section}; /// # use std::borrow::Cow; /// # let section_header = section::Header { /// # name: section::Name(Cow::Borrowed("core".into())), @@ -124,7 +123,7 @@ use std::fmt::Display; /// # subsection_name: None, /// # }; /// # let section_data = "[core]\n autocrlf"; -/// # assert_eq!(event::List::from_str(section_data).unwrap().into_vec(), vec![ +/// # assert_eq!(Events::from_str(section_data).unwrap().into_vec(), vec![ /// Event::SectionHeader(section_header), /// Event::Newline(Cow::Borrowed("\n".into())), /// Event::Whitespace(Cow::Borrowed(" ".into())), @@ -150,7 +149,7 @@ use std::fmt::Display; /// relevant event stream emitted is thus emitted as: /// /// ``` -/// # use git_config::parse::{Event, event, section}; +/// # use git_config::parse::{Event, Events, section}; /// # use std::borrow::Cow; /// # let section_header = section::Header { /// # name: section::Name(Cow::Borrowed("core".into())), @@ -158,7 +157,7 @@ use std::fmt::Display; /// # subsection_name: None, /// # }; /// # let section_data = "[core]\nautocrlf=true\"\"\nfilemode=fa\"lse\""; -/// # assert_eq!(event::from_str(section_data).unwrap().into_vec(), vec![ +/// # assert_eq!(Events::from_str(section_data).unwrap().into_vec(), vec![ /// Event::SectionHeader(section_header), /// Event::Newline(Cow::Borrowed("\n".into())), /// Event::SectionKey(section::Key(Cow::Borrowed("autocrlf".into()))), @@ -187,7 +186,7 @@ use std::fmt::Display; /// split value accordingly: /// /// ``` -/// # use git_config::parse::{Event, event, section}; +/// # use git_config::parse::{Event, Events, section}; /// # use std::borrow::Cow; /// # let section_header = section::Header { /// # name: section::Name(Cow::Borrowed("some-section".into())), @@ -195,7 +194,7 @@ use std::fmt::Display; /// # subsection_name: None, /// # }; /// # let section_data = "[some-section]\nfile=a\\\n c"; -/// # assert_eq!(event::from_str(section_data).unwrap().into_vec(), vec![ +/// # assert_eq!(Events::from_str(section_data).unwrap().into_vec(), vec![ /// Event::SectionHeader(section_header), /// Event::Newline(Cow::Borrowed("\n".into())), /// Event::SectionKey(section::Key(Cow::Borrowed("file".into()))), @@ -212,87 +211,148 @@ use std::fmt::Display; /// [`FromStr`]: std::str::FromStr /// [`From<&'_ str>`]: std::convert::From #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] -pub struct List<'a> { +pub struct Events<'a> { pub(crate) frontmatter: Vec>, pub(crate) sections: Vec>, } -/// -pub mod list; +impl Events<'static> { + /// Parses a git config located at the provided path. On success, returns a + /// [`State`] that provides methods to accessing leading comments and sections + /// of a `git-config` file and can be converted into an iterator of [`Event`] + /// for higher level processing. + /// + /// Note that since we accept a path rather than a reference to the actual + /// bytes, this function is _not_ zero-copy, as the Parser must own (and thus + /// copy) the bytes that it reads from. Consider one of the other variants if + /// performance is a concern. + /// + /// # Errors + /// + /// Returns an error if there was an IO error or the read file is not a valid + /// `git-config` This generally is due to either invalid names or if there's + /// extraneous data succeeding valid `git-config` data. + pub fn from_path>(path: P) -> Result, from_path::Error> { + let mut bytes = vec![]; + let mut file = std::fs::File::open(path)?; + file.read_to_end(&mut bytes)?; + crate::parse::nom::from_bytes_owned(&bytes).map_err(from_path::Error::Parse) + } -impl Event<'_> { - /// Generates a byte representation of the value. This should be used when - /// non-UTF-8 sequences are present or a UTF-8 representation can't be - /// guaranteed. - #[must_use] - pub fn to_bstring(&self) -> BString { - self.into() + /// Parses the provided bytes, returning an [`State`] that contains allocated + /// and owned events. This is similar to [`State::from_bytes()`], but performance + /// is degraded as it requires allocation for every event. However, this permits + /// the reference bytes to be dropped, allowing the parser to be passed around + /// without lifetime worries. + /// + /// # Errors + /// + /// Returns an error if the string provided is not a valid `git-config`. + /// This generally is due to either invalid names or if there's extraneous + /// data succeeding valid `git-config` data. + #[allow(clippy::shadow_unrelated)] + pub fn from_bytes_owned(input: &[u8]) -> Result, Error<'static>> { + crate::parse::nom::from_bytes_owned(input) } +} - /// Coerces into an owned instance. This differs from the standard [`clone`] - /// implementation as calling clone will _not_ copy the borrowed variant, - /// while this method will. In other words: +impl<'a> Events<'a> { + /// Attempt to zero-copy parse the provided `&str`. On success, returns a + /// [`State`] that provides methods to accessing leading comments and sections + /// of a `git-config` file and can be converted into an iterator of [`Event`] + /// for higher level processing. /// - /// | Borrow type | `.clone()` | `to_owned()` | - /// | ----------- | ---------- | ------------ | - /// | Borrowed | Borrowed | Owned | - /// | Owned | Owned | Owned | + /// # Errors /// - /// This can be most effectively seen by the differing lifetimes between the - /// two. This method guarantees a `'static` lifetime, while `clone` does - /// not. + /// Returns an error if the string provided is not a valid `git-config`. + /// This generally is due to either invalid names or if there's extraneous + /// data succeeding valid `git-config` data. + #[allow(clippy::should_implement_trait)] + pub fn from_str(input: &'a str) -> Result, Error<'a>> { + crate::parse::nom::from_bytes(input.as_bytes()) + } + + /// Attempt to zero-copy parse the provided bytes. On success, returns a + /// [`State`] that provides methods to accessing leading comments and sections + /// of a `git-config` file and can be converted into an iterator of [`Event`] + /// for higher level processing. /// - /// [`clone`]: Self::clone - #[must_use] - pub fn to_owned(&self) -> Event<'static> { - match self { - Event::Comment(e) => Event::Comment(e.to_owned()), - Event::SectionHeader(e) => Event::SectionHeader(e.to_owned()), - Event::SectionKey(e) => Event::SectionKey(e.to_owned()), - Event::Value(e) => Event::Value(Cow::Owned(e.clone().into_owned())), - Event::ValueNotDone(e) => Event::ValueNotDone(Cow::Owned(e.clone().into_owned())), - Event::ValueDone(e) => Event::ValueDone(Cow::Owned(e.clone().into_owned())), - Event::Newline(e) => Event::Newline(Cow::Owned(e.clone().into_owned())), - Event::Whitespace(e) => Event::Whitespace(Cow::Owned(e.clone().into_owned())), - Event::KeyValueSeparator => Event::KeyValueSeparator, - } + /// # Errors + /// + /// Returns an error if the string provided is not a valid `git-config`. + /// This generally is due to either invalid names or if there's extraneous + /// data succeeding valid `git-config` data. + #[allow(clippy::shadow_unrelated)] + pub fn from_bytes(input: &'a [u8]) -> Result, Error<'a>> { + crate::parse::nom::from_bytes(input) } } -impl Display for Event<'_> { - /// Note that this is a best-effort attempt at printing an `Event`. If - /// there are non UTF-8 values in your config, this will _NOT_ render - /// as read. - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Value(e) | Self::ValueNotDone(e) | Self::ValueDone(e) => match std::str::from_utf8(e) { - Ok(e) => e.fmt(f), - Err(_) => write!(f, "{:02x?}", e), - }, - Self::Comment(e) => e.fmt(f), - Self::SectionHeader(e) => e.fmt(f), - Self::SectionKey(e) => e.fmt(f), - Self::Newline(e) | Self::Whitespace(e) => e.fmt(f), - Self::KeyValueSeparator => write!(f, "="), - } +impl<'a> Events<'a> { + /// Returns the leading events (any comments, whitespace, or newlines before + /// a section) from the parser. Consider [`State::take_frontmatter`] if + /// you need an owned copy only once. If that function was called, then this + /// will always return an empty slice. + #[must_use] + pub fn frontmatter(&self) -> &[Event<'a>] { + &self.frontmatter + } + + /// Takes the leading events (any comments, whitespace, or newlines before + /// a section) from the parser. Subsequent calls will return an empty vec. + /// Consider [`State::frontmatter`] if you only need a reference to the + /// frontmatter + pub fn take_frontmatter(&mut self) -> Vec> { + std::mem::take(&mut self.frontmatter) + } + + /// Returns the parsed sections from the parser. Consider + /// [`State::take_sections`] if you need an owned copy only once. If that + /// function was called, then this will always return an empty slice. + #[must_use] + pub fn sections(&self) -> &[Section<'a>] { + &self.sections + } + + /// Takes the parsed sections from the parser. Subsequent calls will return + /// an empty vec. Consider [`State::sections`] if you only need a reference + /// to the comments. + pub fn take_sections(&mut self) -> Vec> { + let mut to_return = vec![]; + std::mem::swap(&mut self.sections, &mut to_return); + to_return + } + + /// Consumes the parser to produce a Vec of Events. + #[must_use] + pub fn into_vec(self) -> Vec> { + self.into_iter().collect() + } + + /// Consumes the parser to produce an iterator of Events. + #[must_use = "iterators are lazy and do nothing unless consumed"] + #[allow(clippy::should_implement_trait)] + pub fn into_iter(self) -> impl Iterator> + std::iter::FusedIterator { + self.frontmatter.into_iter().chain( + self.sections.into_iter().flat_map(|section| { + std::iter::once(Event::SectionHeader(section.section_header)).chain(section.events) + }), + ) } } -impl From> for BString { - fn from(event: Event<'_>) -> Self { - event.into() +impl<'a> TryFrom<&'a str> for Events<'a> { + type Error = Error<'a>; + + fn try_from(value: &'a str) -> Result { + Self::from_str(value) } } -impl From<&Event<'_>> for BString { - fn from(event: &Event<'_>) -> Self { - match event { - Event::Value(e) | Event::ValueNotDone(e) | Event::ValueDone(e) => e.as_ref().into(), - Event::Comment(e) => e.into(), - Event::SectionHeader(e) => e.into(), - Event::SectionKey(e) => e.0.as_ref().into(), - Event::Newline(e) | Event::Whitespace(e) => e.as_ref().into(), - Event::KeyValueSeparator => "=".into(), - } +impl<'a> TryFrom<&'a [u8]> for Events<'a> { + type Error = Error<'a>; + + fn try_from(value: &'a [u8]) -> Result { + crate::parse::nom::from_bytes(value) } } diff --git a/git-config/src/parse/mod.rs b/git-config/src/parse/mod.rs index e6d293afcb0..f25b509909e 100644 --- a/git-config/src/parse/mod.rs +++ b/git-config/src/parse/mod.rs @@ -63,6 +63,28 @@ pub enum Event<'a> { /// pub mod event; +#[path = "events.rs"] +mod events_type; +pub use events_type::Events; + +/// +pub mod events { + /// + pub mod from_path { + /// An error type representing a Parser [`Error`] or an [`IO error`]. This is + /// returned from functions that will perform IO on top of standard parsing, + /// such as reading from a file. + /// + /// [`IO error`]: std::io::Error + #[derive(Debug, thiserror::Error)] + pub enum Error { + #[error(transparent)] + Parse(crate::parse::Error<'static>), + #[error(transparent)] + Io(#[from] std::io::Error), + } + } +} /// A parsed section containing the header and the section events. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index fb0f01fe511..5e5c268bcce 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -1,4 +1,4 @@ -use crate::parse::{event::List, section, Comment, Error, Event, Section}; +use crate::parse::{section, Comment, Error, Event, Events, Section}; use bstr::{BStr, BString, ByteSlice, ByteVec}; use std::borrow::Cow; @@ -28,7 +28,7 @@ use nom::{ /// This generally is due to either invalid names or if there's extraneous /// data succeeding valid `git-config` data. #[allow(clippy::shadow_unrelated)] -pub fn from_bytes(input: &[u8]) -> Result, Error<'_>> { +pub fn from_bytes(input: &[u8]) -> Result, Error<'_>> { let bom = unicode_bom::Bom::from(input); let mut newlines = 0; let (i, frontmatter) = many0(alt(( @@ -47,7 +47,7 @@ pub fn from_bytes(input: &[u8]) -> Result, Error<'_>> { .expect("many0(alt(...)) panicked. Likely a bug in one of the children parsers."); if i.is_empty() { - return Ok(List { + return Ok(Events { frontmatter, sections: vec![], }); @@ -80,7 +80,7 @@ pub fn from_bytes(input: &[u8]) -> Result, Error<'_>> { }); } - Ok(List { frontmatter, sections }) + Ok(Events { frontmatter, sections }) } /// Parses the provided bytes, returning an [`Parser`] that contains allocated @@ -95,7 +95,7 @@ pub fn from_bytes(input: &[u8]) -> Result, Error<'_>> { /// This generally is due to either invalid names or if there's extraneous /// data succeeding valid `git-config` data. #[allow(clippy::shadow_unrelated)] -pub fn from_bytes_owned(input: &[u8]) -> Result, Error<'static>> { +pub fn from_bytes_owned(input: &[u8]) -> Result, Error<'static>> { // FIXME: This is duplication is necessary until comment, take_spaces, and take_newlines // accept cows instead, since we don't want to unnecessarily copy the frontmatter // events in a hypothetical parse_from_cow function. @@ -119,7 +119,7 @@ pub fn from_bytes_owned(input: &[u8]) -> Result, Error<'static>> { .expect("many0(alt(...)) panicked. Likely a bug in one of the children parsers."); let frontmatter = frontmatter.iter().map(Event::to_owned).collect(); if i.is_empty() { - return Ok(List { + return Ok(Events { frontmatter, sections: vec![], }); @@ -152,7 +152,7 @@ pub fn from_bytes_owned(input: &[u8]) -> Result, Error<'static>> { }); } - Ok(List { frontmatter, sections }) + Ok(Events { frontmatter, sections }) } fn comment(i: &[u8]) -> IResult<&[u8], Comment<'_>> { diff --git a/git-config/src/parse/tests.rs b/git-config/src/parse/tests.rs index f684371d127..db50b048946 100644 --- a/git-config/src/parse/tests.rs +++ b/git-config/src/parse/tests.rs @@ -1,5 +1,5 @@ mod parse { - use crate::parse::event::List; + use crate::parse::Events; #[test] fn parser_skips_bom() { @@ -13,34 +13,34 @@ mod parse { "; assert_eq!( - List::from_bytes(bytes), - List::from_bytes(bytes_with_gb18030_bom.as_bytes()) + Events::from_bytes(bytes), + Events::from_bytes(bytes_with_gb18030_bom.as_bytes()) ); assert_eq!( - List::from_bytes_owned(bytes), - List::from_bytes_owned(bytes_with_gb18030_bom.as_bytes()) + Events::from_bytes_owned(bytes), + Events::from_bytes_owned(bytes_with_gb18030_bom.as_bytes()) ); } } #[cfg(test)] mod error { - use crate::parse::event::List; + use crate::parse::Events; #[test] fn line_no_is_one_indexed() { - assert_eq!(List::from_str("[hello").unwrap_err().line_number(), 1); + assert_eq!(Events::from_str("[hello").unwrap_err().line_number(), 1); } #[test] fn remaining_data_contains_bad_tokens() { - assert_eq!(List::from_str("[hello").unwrap_err().remaining_data(), b"[hello"); + assert_eq!(Events::from_str("[hello").unwrap_err().remaining_data(), b"[hello"); } #[test] fn to_string_truncates_extra_values() { assert_eq!( - List::from_str("[1234567890").unwrap_err().to_string(), + Events::from_str("[1234567890").unwrap_err().to_string(), "Got an unexpected token on line 1 while trying to parse a section header: '[123456789' ... (1 characters omitted)" ); } diff --git a/git-config/tests/file/access/read_only.rs b/git-config/tests/file/access/read_only.rs index c3ffcd4d0ea..9feb3b075e2 100644 --- a/git-config/tests/file/access/read_only.rs +++ b/git-config/tests/file/access/read_only.rs @@ -23,7 +23,7 @@ fn get_value_for_all_provided_values() -> crate::Result { location-quoted = "~/quoted" "#; - let file = git_config::parse::event::List::from_bytes_owned(config.as_bytes()).map(File::from)?; + let file = git_config::parse::Events::from_bytes_owned(config.as_bytes()).map(File::from)?; assert_eq!( file.value::("core", None, "bool-explicit")?, diff --git a/git-config/tests/file/from_paths/mod.rs b/git-config/tests/file/from_paths/mod.rs index 85e1020f51c..ceb254cd0bc 100644 --- a/git-config/tests/file/from_paths/mod.rs +++ b/git-config/tests/file/from_paths/mod.rs @@ -18,7 +18,7 @@ fn file_not_found() { let paths = vec![config_path]; let error = File::from_paths(paths, Default::default()).unwrap_err(); assert!( - matches!(error, Error::Parse(parse::event::list::from_path::Error::Io(io_error)) if io_error.kind() == io::ErrorKind::NotFound) + matches!(error, Error::Parse(parse::events::from_path::Error::Io(io_error)) if io_error.kind() == io::ErrorKind::NotFound) ); } diff --git a/git-config/tests/parse/mod.rs b/git-config/tests/parse/mod.rs index e9bf9770c48..ee53851637f 100644 --- a/git-config/tests/parse/mod.rs +++ b/git-config/tests/parse/mod.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use git_config::parse::{event, section, Event}; +use git_config::parse::{section, Event, Events}; pub fn section_header_event(name: &str, subsection: impl Into>) -> Event<'_> { Event::SectionHeader(section_header(name, subsection)) @@ -71,7 +71,7 @@ fn personal_config() { defaultBranch = master"#; assert_eq!( - event::List::from_str(config) + Events::from_str(config) .unwrap() .into_vec(), vec![ @@ -186,13 +186,13 @@ fn personal_config() { #[test] fn parse_empty() { - assert_eq!(event::List::from_str("").unwrap().into_vec(), vec![]); + assert_eq!(Events::from_str("").unwrap().into_vec(), vec![]); } #[test] fn parse_whitespace() { assert_eq!( - event::List::from_str("\n \n \n").unwrap().into_vec(), + Events::from_str("\n \n \n").unwrap().into_vec(), vec![newline(), whitespace(" "), newline(), whitespace(" "), newline()] ) } @@ -200,7 +200,7 @@ fn parse_whitespace() { #[test] fn newline_events_are_merged() { assert_eq!( - event::List::from_str("\n\n\n\n\n").unwrap().into_vec(), + Events::from_str("\n\n\n\n\n").unwrap().into_vec(), vec![newline_custom("\n\n\n\n\n")] ); } @@ -209,23 +209,23 @@ fn newline_events_are_merged() { fn error() { let input = "[a_b]\n c=d"; assert_eq!( - event::List::from_str(input).unwrap_err().to_string(), + Events::from_str(input).unwrap_err().to_string(), "Got an unexpected token on line 1 while trying to parse a section header: '[a_b]\n c=d'", "underscores in section names aren't allowed and will be rejected by git" ); let input = "[core] a=b\n 4a=3"; assert_eq!( - event::List::from_str(input).unwrap_err().to_string(), + Events::from_str(input).unwrap_err().to_string(), "Got an unexpected token on line 2 while trying to parse a config name: '4a=3'" ); let input = "[core] a=b\n =3"; assert_eq!( - event::List::from_str(input).unwrap_err().to_string(), + Events::from_str(input).unwrap_err().to_string(), "Got an unexpected token on line 2 while trying to parse a config name: '=3'" ); let input = "[core"; assert_eq!( - event::List::from_str(input).unwrap_err().to_string(), + Events::from_str(input).unwrap_err().to_string(), "Got an unexpected token on line 1 while trying to parse a section header: '[core'" ); } From 07bf647c788afbe5a595ed3091744459e3623f13 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 11:19:35 +0800 Subject: [PATCH 069/366] adjustments due to breaking changes in `git-config` (#331) --- git-repository/src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-repository/src/config.rs b/git-repository/src/config.rs index 3533c240fcd..bf9b99587e4 100644 --- a/git-repository/src/config.rs +++ b/git-repository/src/config.rs @@ -3,7 +3,7 @@ use crate::{bstr::BString, permission}; #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Could not open repository conifguration file")] - Open(#[from] git_config::parse::state::from_path::Error), + Open(#[from] git_config::parse::events::from_path::Error), #[error("Cannot handle objects formatted as {:?}", .name)] UnsupportedObjectFormat { name: crate::bstr::BString }, #[error("The value for '{}' cannot be empty", .key)] From 86e1a76484be50f83d06d6c8a176107f8cb3dea6 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 11:25:45 +0800 Subject: [PATCH 070/366] fix fuzz crash in parser (#331) --- git-config/src/parse/nom/mod.rs | 10 +++++++++- git-config/src/parse/tests.rs | 5 +++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index 5e5c268bcce..f715e5df3cc 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -432,7 +432,15 @@ fn value_impl<'a, 'b>(i: &'a [u8], events: &'b mut Vec>) -> IResult<&' // Didn't parse anything at all, newline straight away. events.push(Event::Value(Cow::Owned(BString::default()))); events.push(Event::Newline(Cow::Borrowed("\n".into()))); - return Ok((&i[1..], ())); + return Ok(( + i.get(1..).ok_or_else(|| { + nom::Err::Error(NomError { + input: i, + code: ErrorKind::Eof, + }) + })?, + (), + )); } } diff --git a/git-config/src/parse/tests.rs b/git-config/src/parse/tests.rs index db50b048946..e28cc220248 100644 --- a/git-config/src/parse/tests.rs +++ b/git-config/src/parse/tests.rs @@ -44,6 +44,11 @@ mod error { "Got an unexpected token on line 1 while trying to parse a section header: '[123456789' ... (1 characters omitted)" ); } + + #[test] + fn detected_by_fuzz() { + assert!(Events::from_str("[]I=").is_err()); + } } pub(crate) mod util { From f7be3b0f79bf19faf5a3b68032f764c0b7a12d7e Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 11:27:48 +0800 Subject: [PATCH 071/366] thanks clippy --- git-config/src/parse/nom/mod.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index f715e5df3cc..1ee6828d547 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -433,12 +433,10 @@ fn value_impl<'a, 'b>(i: &'a [u8], events: &'b mut Vec>) -> IResult<&' events.push(Event::Value(Cow::Owned(BString::default()))); events.push(Event::Newline(Cow::Borrowed("\n".into()))); return Ok(( - i.get(1..).ok_or_else(|| { - nom::Err::Error(NomError { - input: i, - code: ErrorKind::Eof, - }) - })?, + i.get(1..).ok_or(nom::Err::Error(NomError { + input: i, + code: ErrorKind::Eof, + }))?, (), )); } From 218645618429258e48cb0fdb2bbfba3daa32ee2d Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 11:30:12 +0800 Subject: [PATCH 072/366] fix docs (#331) --- git-config/src/file/impls.rs | 2 +- git-config/src/lib.rs | 2 +- git-config/src/parse/events.rs | 18 +++++++++--------- git-config/src/parse/mod.rs | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/git-config/src/file/impls.rs b/git-config/src/file/impls.rs index 79d5a236e36..4692836bf60 100644 --- a/git-config/src/file/impls.rs +++ b/git-config/src/file/impls.rs @@ -7,7 +7,7 @@ impl<'a> TryFrom<&'a str> for File<'a> { type Error = parse::Error<'a>; /// Convenience constructor. Attempts to parse the provided string into a - /// [`File`]. See [`State::from_str()`] for more information. + /// [`File`]. See [`Events::from_str()`][crate::parse::Events::from_str()] for more information. fn try_from(s: &'a str) -> Result, Self::Error> { parse::Events::from_str(s).map(Self::from) } diff --git a/git-config/src/lib.rs b/git-config/src/lib.rs index 98e14927446..b7c11cd5c77 100644 --- a/git-config/src/lib.rs +++ b/git-config/src/lib.rs @@ -43,7 +43,7 @@ //! [`git-config` files]: https://git-scm.com/docs/git-config#_configuration_file //! [INI file format]: https://en.wikipedia.org/wiki/INI_file //! [`File`]: crate::File -//! [`parse::State`]: crate::parse::State +//! [`parse::State`]: crate::parse::Events //! [`value`]: crate::value //! [`nom`]: https://github.com/Geal/nom //! diff --git a/git-config/src/parse/events.rs b/git-config/src/parse/events.rs index 25c0a07b55b..ddae8c49b23 100644 --- a/git-config/src/parse/events.rs +++ b/git-config/src/parse/events.rs @@ -218,7 +218,7 @@ pub struct Events<'a> { impl Events<'static> { /// Parses a git config located at the provided path. On success, returns a - /// [`State`] that provides methods to accessing leading comments and sections + /// [`Events`] that provides methods to accessing leading comments and sections /// of a `git-config` file and can be converted into an iterator of [`Event`] /// for higher level processing. /// @@ -239,8 +239,8 @@ impl Events<'static> { crate::parse::nom::from_bytes_owned(&bytes).map_err(from_path::Error::Parse) } - /// Parses the provided bytes, returning an [`State`] that contains allocated - /// and owned events. This is similar to [`State::from_bytes()`], but performance + /// Parses the provided bytes, returning an [`Events`] that contains allocated + /// and owned events. This is similar to [`Events::from_bytes()`], but performance /// is degraded as it requires allocation for every event. However, this permits /// the reference bytes to be dropped, allowing the parser to be passed around /// without lifetime worries. @@ -258,7 +258,7 @@ impl Events<'static> { impl<'a> Events<'a> { /// Attempt to zero-copy parse the provided `&str`. On success, returns a - /// [`State`] that provides methods to accessing leading comments and sections + /// [`Events`] that provides methods to accessing leading comments and sections /// of a `git-config` file and can be converted into an iterator of [`Event`] /// for higher level processing. /// @@ -273,7 +273,7 @@ impl<'a> Events<'a> { } /// Attempt to zero-copy parse the provided bytes. On success, returns a - /// [`State`] that provides methods to accessing leading comments and sections + /// [`Events`] that provides methods to accessing leading comments and sections /// of a `git-config` file and can be converted into an iterator of [`Event`] /// for higher level processing. /// @@ -290,7 +290,7 @@ impl<'a> Events<'a> { impl<'a> Events<'a> { /// Returns the leading events (any comments, whitespace, or newlines before - /// a section) from the parser. Consider [`State::take_frontmatter`] if + /// a section) from the parser. Consider [`Events::take_frontmatter`] if /// you need an owned copy only once. If that function was called, then this /// will always return an empty slice. #[must_use] @@ -300,14 +300,14 @@ impl<'a> Events<'a> { /// Takes the leading events (any comments, whitespace, or newlines before /// a section) from the parser. Subsequent calls will return an empty vec. - /// Consider [`State::frontmatter`] if you only need a reference to the + /// Consider [`Events::frontmatter`] if you only need a reference to the /// frontmatter pub fn take_frontmatter(&mut self) -> Vec> { std::mem::take(&mut self.frontmatter) } /// Returns the parsed sections from the parser. Consider - /// [`State::take_sections`] if you need an owned copy only once. If that + /// [`Events::take_sections`] if you need an owned copy only once. If that /// function was called, then this will always return an empty slice. #[must_use] pub fn sections(&self) -> &[Section<'a>] { @@ -315,7 +315,7 @@ impl<'a> Events<'a> { } /// Takes the parsed sections from the parser. Subsequent calls will return - /// an empty vec. Consider [`State::sections`] if you only need a reference + /// an empty vec. Consider [`Events::sections`] if you only need a reference /// to the comments. pub fn take_sections(&mut self) -> Vec> { let mut to_return = vec![]; diff --git a/git-config/src/parse/mod.rs b/git-config/src/parse/mod.rs index f25b509909e..83a97a172bf 100644 --- a/git-config/src/parse/mod.rs +++ b/git-config/src/parse/mod.rs @@ -3,8 +3,8 @@ //! explicit reason to work with events instead. //! //! The general workflow for interacting with this is to use one of the -//! `parse_from_*` function variants. These will return a [`State`] on success, -//! which can be converted into an [`Event`] iterator. The [`State`] also has +//! `parse_from_*` function variants. These will return a [`Events`] on success, +//! which can be converted into an [`Event`] iterator. The [`Events`] also has //! additional methods for accessing leading comments or events by section. //! //! [`File`]: crate::File From 5958ffbfec7724c1a47be8db210df03cf54c9374 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 13:34:41 +0800 Subject: [PATCH 073/366] a minimally invasive sketch of a parse Delegate (#331) --- git-config/src/parse/mod.rs | 11 +++++ git-config/src/parse/nom/mod.rs | 71 ++++++++++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/git-config/src/parse/mod.rs b/git-config/src/parse/mod.rs index 83a97a172bf..f8ead82e281 100644 --- a/git-config/src/parse/mod.rs +++ b/git-config/src/parse/mod.rs @@ -116,12 +116,23 @@ mod comment; pub struct Error<'a> { line_number: usize, last_attempted_parser: error::ParseNode, + // TODO: use simple reference parsed_until: Cow<'a, BStr>, } mod error; mod nom; +pub use self::nom::from_bytes_1; + +/// A receive for events and sections +pub trait Delegate { + /// Anything parsed before the first section. + fn front_matter(&mut self, event: Event<'_>); + + /// A completely parsed section. + fn section(&mut self, section: Section<'_>); +} #[cfg(test)] pub(crate) mod tests; diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index 1ee6828d547..b8400445ef3 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -1,4 +1,4 @@ -use crate::parse::{section, Comment, Error, Event, Events, Section}; +use crate::parse::{section, Comment, Delegate, Error, Event, Events, Section}; use bstr::{BStr, BString, ByteSlice, ByteVec}; use std::borrow::Cow; @@ -83,6 +83,75 @@ pub fn from_bytes(input: &[u8]) -> Result, Error<'_>> { Ok(Events { frontmatter, sections }) } +/// Attempt to zero-copy parse the provided bytes. On success, returns a +/// [`Parser`] that provides methods to accessing leading comments and sections +/// of a `git-config` file and can be converted into an iterator of [`Event`] +/// for higher level processing. +/// +/// # Errors +/// +/// Returns an error if the string provided is not a valid `git-config`. +/// This generally is due to either invalid names or if there's extraneous +/// data succeeding valid `git-config` data. +#[allow(clippy::shadow_unrelated)] +pub fn from_bytes_1<'a>(input: &'a [u8], delegate: &mut dyn Delegate) -> Result<(), Error<'a>> { + let bom = unicode_bom::Bom::from(input); + let mut newlines = 0; + let (i, frontmatter) = many0(alt(( + map(comment, Event::Comment), + map(take_spaces, |whitespace| Event::Whitespace(Cow::Borrowed(whitespace))), + map(take_newlines, |(newline, counter)| { + newlines += counter; + Event::Newline(Cow::Borrowed(newline)) + }), + )))(&input[bom.len()..]) + // I don't think this can panic. many0 errors if the child parser returns + // a success where the input was not consumed, but alt will only return Ok + // if one of its children succeed. However, all of it's children are + // guaranteed to consume something if they succeed, so the Ok(i) == i case + // can never occur. + .expect("many0(alt(...)) panicked. Likely a bug in one of the children parsers."); + for event in frontmatter { + delegate.front_matter(event); + } + + if i.is_empty() { + return Ok(()); + } + + let mut node = ParseNode::SectionHeader; + + let maybe_sections = many1(|i| section(i, &mut node))(i); + let (i, sections) = maybe_sections.map_err(|_| Error { + line_number: newlines, + last_attempted_parser: node, + parsed_until: i.as_bstr().into(), + })?; + + let sections: Vec<_> = sections + .into_iter() + .map(|(section, additional_newlines)| { + newlines += additional_newlines; + section + }) + .collect(); + for section in sections { + delegate.section(section); + } + + // This needs to happen after we collect sections, otherwise the line number + // will be off. + if !i.is_empty() { + return Err(Error { + line_number: newlines, + last_attempted_parser: node, + parsed_until: i.as_bstr().into(), + }); + } + + Ok(()) +} + /// Parses the provided bytes, returning an [`Parser`] that contains allocated /// and owned events. This is similar to [`parse_from_bytes`], but performance /// is degraded as it requires allocation for every event. However, this permits From 0f5c99bffdb61e4665e83472275c5c8b0383650b Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 13:57:51 +0800 Subject: [PATCH 074/366] remove duplication of top-level parser (#331) --- git-config/src/parse/events.rs | 43 ++++++++++++++------ git-config/src/parse/mod.rs | 2 +- git-config/src/parse/nom/mod.rs | 72 --------------------------------- 3 files changed, 32 insertions(+), 85 deletions(-) diff --git a/git-config/src/parse/events.rs b/git-config/src/parse/events.rs index ddae8c49b23..afc296a21ca 100644 --- a/git-config/src/parse/events.rs +++ b/git-config/src/parse/events.rs @@ -1,6 +1,8 @@ -use crate::parse::{events::from_path, Error, Event, Section}; +use crate::{ + parse, + parse::{events::from_path, Event, Section}, +}; use std::convert::TryFrom; -use std::io::Read; /// A zero-copy `git-config` file parser. /// @@ -216,6 +218,16 @@ pub struct Events<'a> { pub(crate) sections: Vec>, } +impl parse::Delegate for Events<'static> { + fn front_matter(&mut self, event: Event<'_>) { + self.frontmatter.push(event.to_owned()); + } + + fn section(&mut self, section: Section<'_>) { + self.sections.push(section.to_owned()); + } +} + impl Events<'static> { /// Parses a git config located at the provided path. On success, returns a /// [`Events`] that provides methods to accessing leading comments and sections @@ -233,10 +245,8 @@ impl Events<'static> { /// `git-config` This generally is due to either invalid names or if there's /// extraneous data succeeding valid `git-config` data. pub fn from_path>(path: P) -> Result, from_path::Error> { - let mut bytes = vec![]; - let mut file = std::fs::File::open(path)?; - file.read_to_end(&mut bytes)?; - crate::parse::nom::from_bytes_owned(&bytes).map_err(from_path::Error::Parse) + let bytes = std::fs::read(path)?; + Ok(Self::from_bytes_owned(&bytes)?) } /// Parses the provided bytes, returning an [`Events`] that contains allocated @@ -251,8 +261,17 @@ impl Events<'static> { /// This generally is due to either invalid names or if there's extraneous /// data succeeding valid `git-config` data. #[allow(clippy::shadow_unrelated)] - pub fn from_bytes_owned(input: &[u8]) -> Result, Error<'static>> { - crate::parse::nom::from_bytes_owned(input) + pub fn from_bytes_owned(bytes: &[u8]) -> Result, parse::Error<'static>> { + let mut events = Events::default(); + { + // SAFETY: we don't actually keep the bytes around for 'static, but help the borrow checker who + // cannot see that this is fine to do. Fortunately it's not possible to use these as 'static + // either in the delegate. + #[allow(unsafe_code)] + let bytes: &'static [u8] = unsafe { std::mem::transmute(bytes) }; + parse::from_bytes_1(bytes, &mut events)?; + } + Ok(events) } } @@ -268,7 +287,7 @@ impl<'a> Events<'a> { /// This generally is due to either invalid names or if there's extraneous /// data succeeding valid `git-config` data. #[allow(clippy::should_implement_trait)] - pub fn from_str(input: &'a str) -> Result, Error<'a>> { + pub fn from_str(input: &'a str) -> Result, parse::Error<'a>> { crate::parse::nom::from_bytes(input.as_bytes()) } @@ -283,7 +302,7 @@ impl<'a> Events<'a> { /// This generally is due to either invalid names or if there's extraneous /// data succeeding valid `git-config` data. #[allow(clippy::shadow_unrelated)] - pub fn from_bytes(input: &'a [u8]) -> Result, Error<'a>> { + pub fn from_bytes(input: &'a [u8]) -> Result, parse::Error<'a>> { crate::parse::nom::from_bytes(input) } } @@ -342,7 +361,7 @@ impl<'a> Events<'a> { } impl<'a> TryFrom<&'a str> for Events<'a> { - type Error = Error<'a>; + type Error = parse::Error<'a>; fn try_from(value: &'a str) -> Result { Self::from_str(value) @@ -350,7 +369,7 @@ impl<'a> TryFrom<&'a str> for Events<'a> { } impl<'a> TryFrom<&'a [u8]> for Events<'a> { - type Error = Error<'a>; + type Error = parse::Error<'a>; fn try_from(value: &'a [u8]) -> Result { crate::parse::nom::from_bytes(value) diff --git a/git-config/src/parse/mod.rs b/git-config/src/parse/mod.rs index f8ead82e281..ccf6da5d693 100644 --- a/git-config/src/parse/mod.rs +++ b/git-config/src/parse/mod.rs @@ -79,7 +79,7 @@ pub mod events { #[derive(Debug, thiserror::Error)] pub enum Error { #[error(transparent)] - Parse(crate::parse::Error<'static>), + Parse(#[from] crate::parse::Error<'static>), #[error(transparent)] Io(#[from] std::io::Error), } diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index b8400445ef3..b58072c04d4 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -152,78 +152,6 @@ pub fn from_bytes_1<'a>(input: &'a [u8], delegate: &mut dyn Delegate) -> Result< Ok(()) } -/// Parses the provided bytes, returning an [`Parser`] that contains allocated -/// and owned events. This is similar to [`parse_from_bytes`], but performance -/// is degraded as it requires allocation for every event. However, this permits -/// the reference bytes to be dropped, allowing the parser to be passed around -/// without lifetime worries. -/// -/// # Errors -/// -/// Returns an error if the string provided is not a valid `git-config`. -/// This generally is due to either invalid names or if there's extraneous -/// data succeeding valid `git-config` data. -#[allow(clippy::shadow_unrelated)] -pub fn from_bytes_owned(input: &[u8]) -> Result, Error<'static>> { - // FIXME: This is duplication is necessary until comment, take_spaces, and take_newlines - // accept cows instead, since we don't want to unnecessarily copy the frontmatter - // events in a hypothetical parse_from_cow function. - let mut newlines = 0; - let bom = unicode_bom::Bom::from(input); - let (i, frontmatter) = many0(alt(( - map(comment, Event::Comment), - map(take_spaces, |whitespace| { - Event::Whitespace(Cow::Borrowed(whitespace.as_bstr())) - }), - map(take_newlines, |(newline, counter)| { - newlines += counter; - Event::Newline(Cow::Borrowed(newline.as_bstr())) - }), - )))(&input[bom.len()..]) - // I don't think this can panic. many0 errors if the child parser returns - // a success where the input was not consumed, but alt will only return Ok - // if one of its children succeed. However, all of it's children are - // guaranteed to consume something if they succeed, so the Ok(i) == i case - // can never occur. - .expect("many0(alt(...)) panicked. Likely a bug in one of the children parsers."); - let frontmatter = frontmatter.iter().map(Event::to_owned).collect(); - if i.is_empty() { - return Ok(Events { - frontmatter, - sections: vec![], - }); - } - - let mut node = ParseNode::SectionHeader; - - let maybe_sections = many1(|i| section(i, &mut node))(i); - let (i, sections) = maybe_sections.map_err(|_| Error { - line_number: newlines, - last_attempted_parser: node, - parsed_until: Cow::Owned(i.into()), - })?; - - let sections = sections - .into_iter() - .map(|(section, additional_newlines)| { - newlines += additional_newlines; - section.to_owned() - }) - .collect(); - - // This needs to happen after we collect sections, otherwise the line number - // will be off. - if !i.is_empty() { - return Err(Error { - line_number: newlines, - last_attempted_parser: node, - parsed_until: Cow::Owned(i.into()), - }); - } - - Ok(Events { frontmatter, sections }) -} - fn comment(i: &[u8]) -> IResult<&[u8], Comment<'_>> { let (i, comment_tag) = one_of(";#")(i)?; let (i, comment) = take_till(|c| c == b'\n')(i)?; From 4fb327c247f1c0260cb3a3443d81063b71e87fe4 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 14:35:36 +0800 Subject: [PATCH 075/366] workaround lack of GAT! (#331) With impl traits :) - note that this doesn't work with dyn traits either. --- git-config/src/file/impls.rs | 6 ++--- git-config/src/parse/error.rs | 29 +++------------------ git-config/src/parse/events.rs | 45 +++++++++++++-------------------- git-config/src/parse/mod.rs | 16 +++--------- git-config/src/parse/nom/mod.rs | 16 ++++++++---- 5 files changed, 38 insertions(+), 74 deletions(-) diff --git a/git-config/src/file/impls.rs b/git-config/src/file/impls.rs index 4692836bf60..81bc04349c1 100644 --- a/git-config/src/file/impls.rs +++ b/git-config/src/file/impls.rs @@ -4,7 +4,7 @@ use std::{convert::TryFrom, fmt::Display}; use crate::{file::SectionBody, parse, File}; impl<'a> TryFrom<&'a str> for File<'a> { - type Error = parse::Error<'a>; + type Error = parse::Error; /// Convenience constructor. Attempts to parse the provided string into a /// [`File`]. See [`Events::from_str()`][crate::parse::Events::from_str()] for more information. @@ -14,7 +14,7 @@ impl<'a> TryFrom<&'a str> for File<'a> { } impl<'a> TryFrom<&'a [u8]> for File<'a> { - type Error = parse::Error<'a>; + type Error = parse::Error; /// Convenience constructor. Attempts to parse the provided byte string into //// a [`File`]. See [`parse_from_bytes`] for more information. @@ -26,7 +26,7 @@ impl<'a> TryFrom<&'a [u8]> for File<'a> { } impl<'a> TryFrom<&'a BString> for File<'a> { - type Error = parse::Error<'a>; + type Error = parse::Error; /// Convenience constructor. Attempts to parse the provided byte string into //// a [`File`]. See [`State::from_bytes()`] for more information. diff --git a/git-config/src/parse/error.rs b/git-config/src/parse/error.rs index 755e9c3766e..f43824a21c1 100644 --- a/git-config/src/parse/error.rs +++ b/git-config/src/parse/error.rs @@ -18,7 +18,7 @@ impl Display for ParseNode { } } -impl Error<'_> { +impl Error { /// The one-indexed line number where the error occurred. This is determined /// by the number of newlines that were successfully parsed. #[must_use] @@ -31,32 +31,9 @@ impl Error<'_> { pub fn remaining_data(&self) -> &[u8] { &self.parsed_until } - - /// Coerces into an owned instance. This differs from the standard [`clone`] - /// implementation as calling clone will _not_ copy the borrowed variant, - /// while this method will. In other words: - /// - /// | Borrow type | `.clone()` | `to_owned()` | - /// | ----------- | ---------- | ------------ | - /// | Borrowed | Borrowed | Owned | - /// | Owned | Owned | Owned | - /// - /// This can be most effectively seen by the differing lifetimes between the - /// two. This method guarantees a `'static` lifetime, while `clone` does - /// not. - /// - /// [`clone`]: std::clone::Clone::clone - #[must_use] - pub fn to_owned(&self) -> Error<'static> { - Error { - line_number: self.line_number, - last_attempted_parser: self.last_attempted_parser, - parsed_until: self.parsed_until.clone().into_owned().into(), - } - } } -impl Display for Error<'_> { +impl Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let data_size = self.parsed_until.len(); let data = std::str::from_utf8(&self.parsed_until); @@ -83,4 +60,4 @@ impl Display for Error<'_> { } } -impl std::error::Error for Error<'_> {} +impl std::error::Error for Error {} diff --git a/git-config/src/parse/events.rs b/git-config/src/parse/events.rs index afc296a21ca..9681477dba0 100644 --- a/git-config/src/parse/events.rs +++ b/git-config/src/parse/events.rs @@ -218,16 +218,6 @@ pub struct Events<'a> { pub(crate) sections: Vec>, } -impl parse::Delegate for Events<'static> { - fn front_matter(&mut self, event: Event<'_>) { - self.frontmatter.push(event.to_owned()); - } - - fn section(&mut self, section: Section<'_>) { - self.sections.push(section.to_owned()); - } -} - impl Events<'static> { /// Parses a git config located at the provided path. On success, returns a /// [`Events`] that provides methods to accessing leading comments and sections @@ -261,17 +251,15 @@ impl Events<'static> { /// This generally is due to either invalid names or if there's extraneous /// data succeeding valid `git-config` data. #[allow(clippy::shadow_unrelated)] - pub fn from_bytes_owned(bytes: &[u8]) -> Result, parse::Error<'static>> { - let mut events = Events::default(); - { - // SAFETY: we don't actually keep the bytes around for 'static, but help the borrow checker who - // cannot see that this is fine to do. Fortunately it's not possible to use these as 'static - // either in the delegate. - #[allow(unsafe_code)] - let bytes: &'static [u8] = unsafe { std::mem::transmute(bytes) }; - parse::from_bytes_1(bytes, &mut events)?; - } - Ok(events) + pub fn from_bytes_owned(input: &[u8]) -> Result, parse::Error> { + let mut frontmatter = Vec::new(); + let mut sections = Vec::new(); + parse::from_bytes_1( + input, + |e| frontmatter.push(e.to_owned()), + |s: Section<'_>| sections.push(s.to_owned()), + )?; + Ok(Events { frontmatter, sections }) } } @@ -287,8 +275,8 @@ impl<'a> Events<'a> { /// This generally is due to either invalid names or if there's extraneous /// data succeeding valid `git-config` data. #[allow(clippy::should_implement_trait)] - pub fn from_str(input: &'a str) -> Result, parse::Error<'a>> { - crate::parse::nom::from_bytes(input.as_bytes()) + pub fn from_str(input: &'a str) -> Result, parse::Error> { + Self::from_bytes(input.as_bytes()) } /// Attempt to zero-copy parse the provided bytes. On success, returns a @@ -302,8 +290,11 @@ impl<'a> Events<'a> { /// This generally is due to either invalid names or if there's extraneous /// data succeeding valid `git-config` data. #[allow(clippy::shadow_unrelated)] - pub fn from_bytes(input: &'a [u8]) -> Result, parse::Error<'a>> { - crate::parse::nom::from_bytes(input) + pub fn from_bytes(input: &'a [u8]) -> Result, parse::Error> { + let mut frontmatter = Vec::new(); + let mut sections = Vec::new(); + parse::from_bytes_1(input, |e| frontmatter.push(e), |s: Section<'_>| sections.push(s))?; + Ok(Events { frontmatter, sections }) } } @@ -361,7 +352,7 @@ impl<'a> Events<'a> { } impl<'a> TryFrom<&'a str> for Events<'a> { - type Error = parse::Error<'a>; + type Error = parse::Error; fn try_from(value: &'a str) -> Result { Self::from_str(value) @@ -369,7 +360,7 @@ impl<'a> TryFrom<&'a str> for Events<'a> { } impl<'a> TryFrom<&'a [u8]> for Events<'a> { - type Error = parse::Error<'a>; + type Error = parse::Error; fn try_from(value: &'a [u8]) -> Result { crate::parse::nom::from_bytes(value) diff --git a/git-config/src/parse/mod.rs b/git-config/src/parse/mod.rs index ccf6da5d693..f4fab16b623 100644 --- a/git-config/src/parse/mod.rs +++ b/git-config/src/parse/mod.rs @@ -79,7 +79,7 @@ pub mod events { #[derive(Debug, thiserror::Error)] pub enum Error { #[error(transparent)] - Parse(#[from] crate::parse::Error<'static>), + Parse(#[from] crate::parse::Error), #[error(transparent)] Io(#[from] std::io::Error), } @@ -113,11 +113,10 @@ mod comment; /// occurred, as well as the last parser node and the remaining data to be /// parsed. #[derive(PartialEq, Debug)] -pub struct Error<'a> { +pub struct Error { line_number: usize, last_attempted_parser: error::ParseNode, - // TODO: use simple reference - parsed_until: Cow<'a, BStr>, + parsed_until: bstr::BString, } mod error; @@ -125,14 +124,5 @@ mod error; mod nom; pub use self::nom::from_bytes_1; -/// A receive for events and sections -pub trait Delegate { - /// Anything parsed before the first section. - fn front_matter(&mut self, event: Event<'_>); - - /// A completely parsed section. - fn section(&mut self, section: Section<'_>); -} - #[cfg(test)] pub(crate) mod tests; diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index b58072c04d4..d7e319b6f5d 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -1,4 +1,4 @@ -use crate::parse::{section, Comment, Delegate, Error, Event, Events, Section}; +use crate::parse::{section, Comment, Error, Event, Events, Section}; use bstr::{BStr, BString, ByteSlice, ByteVec}; use std::borrow::Cow; @@ -28,7 +28,7 @@ use nom::{ /// This generally is due to either invalid names or if there's extraneous /// data succeeding valid `git-config` data. #[allow(clippy::shadow_unrelated)] -pub fn from_bytes(input: &[u8]) -> Result, Error<'_>> { +pub fn from_bytes(input: &[u8]) -> Result, Error> { let bom = unicode_bom::Bom::from(input); let mut newlines = 0; let (i, frontmatter) = many0(alt(( @@ -94,7 +94,13 @@ pub fn from_bytes(input: &[u8]) -> Result, Error<'_>> { /// This generally is due to either invalid names or if there's extraneous /// data succeeding valid `git-config` data. #[allow(clippy::shadow_unrelated)] -pub fn from_bytes_1<'a>(input: &'a [u8], delegate: &mut dyn Delegate) -> Result<(), Error<'a>> { +pub fn from_bytes_1<'a>( + input: &'a [u8], + // receive_frontmatter: &mut FnFrontMatter<'a>, + // receive_section: &mut FnSection<'a>, + mut receive_frontmatter: impl FnMut(Event<'a>), + mut receive_section: impl FnMut(Section<'a>), +) -> Result<(), Error> { let bom = unicode_bom::Bom::from(input); let mut newlines = 0; let (i, frontmatter) = many0(alt(( @@ -112,7 +118,7 @@ pub fn from_bytes_1<'a>(input: &'a [u8], delegate: &mut dyn Delegate) -> Result< // can never occur. .expect("many0(alt(...)) panicked. Likely a bug in one of the children parsers."); for event in frontmatter { - delegate.front_matter(event); + receive_frontmatter(event); } if i.is_empty() { @@ -136,7 +142,7 @@ pub fn from_bytes_1<'a>(input: &'a [u8], delegate: &mut dyn Delegate) -> Result< }) .collect(); for section in sections { - delegate.section(section); + receive_section(section); } // This needs to happen after we collect sections, otherwise the line number From cd7a21f8381385833f5353925dc57c05c07e718d Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 14:40:22 +0800 Subject: [PATCH 076/366] remove last duplicate of top-level parse function (#331) --- git-config/src/parse/events.rs | 6 +-- git-config/src/parse/mod.rs | 2 +- git-config/src/parse/nom/mod.rs | 70 +-------------------------------- 3 files changed, 6 insertions(+), 72 deletions(-) diff --git a/git-config/src/parse/events.rs b/git-config/src/parse/events.rs index 9681477dba0..c077bb1d48e 100644 --- a/git-config/src/parse/events.rs +++ b/git-config/src/parse/events.rs @@ -254,7 +254,7 @@ impl Events<'static> { pub fn from_bytes_owned(input: &[u8]) -> Result, parse::Error> { let mut frontmatter = Vec::new(); let mut sections = Vec::new(); - parse::from_bytes_1( + parse::from_bytes( input, |e| frontmatter.push(e.to_owned()), |s: Section<'_>| sections.push(s.to_owned()), @@ -293,7 +293,7 @@ impl<'a> Events<'a> { pub fn from_bytes(input: &'a [u8]) -> Result, parse::Error> { let mut frontmatter = Vec::new(); let mut sections = Vec::new(); - parse::from_bytes_1(input, |e| frontmatter.push(e), |s: Section<'_>| sections.push(s))?; + parse::from_bytes(input, |e| frontmatter.push(e), |s: Section<'_>| sections.push(s))?; Ok(Events { frontmatter, sections }) } } @@ -363,6 +363,6 @@ impl<'a> TryFrom<&'a [u8]> for Events<'a> { type Error = parse::Error; fn try_from(value: &'a [u8]) -> Result { - crate::parse::nom::from_bytes(value) + Events::from_bytes(value) } } diff --git a/git-config/src/parse/mod.rs b/git-config/src/parse/mod.rs index f4fab16b623..110014b9dd4 100644 --- a/git-config/src/parse/mod.rs +++ b/git-config/src/parse/mod.rs @@ -122,7 +122,7 @@ pub struct Error { mod error; mod nom; -pub use self::nom::from_bytes_1; +pub use self::nom::from_bytes; #[cfg(test)] pub(crate) mod tests; diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index d7e319b6f5d..1f93fff8041 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -1,4 +1,4 @@ -use crate::parse::{section, Comment, Error, Event, Events, Section}; +use crate::parse::{section, Comment, Error, Event, Section}; use bstr::{BStr, BString, ByteSlice, ByteVec}; use std::borrow::Cow; @@ -28,73 +28,7 @@ use nom::{ /// This generally is due to either invalid names or if there's extraneous /// data succeeding valid `git-config` data. #[allow(clippy::shadow_unrelated)] -pub fn from_bytes(input: &[u8]) -> Result, Error> { - let bom = unicode_bom::Bom::from(input); - let mut newlines = 0; - let (i, frontmatter) = many0(alt(( - map(comment, Event::Comment), - map(take_spaces, |whitespace| Event::Whitespace(Cow::Borrowed(whitespace))), - map(take_newlines, |(newline, counter)| { - newlines += counter; - Event::Newline(Cow::Borrowed(newline)) - }), - )))(&input[bom.len()..]) - // I don't think this can panic. many0 errors if the child parser returns - // a success where the input was not consumed, but alt will only return Ok - // if one of its children succeed. However, all of it's children are - // guaranteed to consume something if they succeed, so the Ok(i) == i case - // can never occur. - .expect("many0(alt(...)) panicked. Likely a bug in one of the children parsers."); - - if i.is_empty() { - return Ok(Events { - frontmatter, - sections: vec![], - }); - } - - let mut node = ParseNode::SectionHeader; - - let maybe_sections = many1(|i| section(i, &mut node))(i); - let (i, sections) = maybe_sections.map_err(|_| Error { - line_number: newlines, - last_attempted_parser: node, - parsed_until: i.as_bstr().into(), - })?; - - let sections = sections - .into_iter() - .map(|(section, additional_newlines)| { - newlines += additional_newlines; - section - }) - .collect(); - - // This needs to happen after we collect sections, otherwise the line number - // will be off. - if !i.is_empty() { - return Err(Error { - line_number: newlines, - last_attempted_parser: node, - parsed_until: i.as_bstr().into(), - }); - } - - Ok(Events { frontmatter, sections }) -} - -/// Attempt to zero-copy parse the provided bytes. On success, returns a -/// [`Parser`] that provides methods to accessing leading comments and sections -/// of a `git-config` file and can be converted into an iterator of [`Event`] -/// for higher level processing. -/// -/// # Errors -/// -/// Returns an error if the string provided is not a valid `git-config`. -/// This generally is due to either invalid names or if there's extraneous -/// data succeeding valid `git-config` data. -#[allow(clippy::shadow_unrelated)] -pub fn from_bytes_1<'a>( +pub fn from_bytes<'a>( input: &'a [u8], // receive_frontmatter: &mut FnFrontMatter<'a>, // receive_section: &mut FnSection<'a>, From 6c3f3264911042e88afa0819414eb543a3626d11 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 14:47:08 +0800 Subject: [PATCH 077/366] allocation-free frontmatter (#331) --- git-config/src/parse/nom/mod.rs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index 1f93fff8041..c25c8bea90c 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -3,6 +3,7 @@ use bstr::{BStr, BString, ByteSlice, ByteVec}; use std::borrow::Cow; use crate::parse::error::ParseNode; +use nom::multi::fold_many0; use nom::{ branch::alt, bytes::complete::{tag, take_till, take_while}, @@ -12,7 +13,7 @@ use nom::{ }, combinator::{map, opt}, error::{Error as NomError, ErrorKind}, - multi::{many0, many1}, + multi::many1, sequence::delimited, IResult, }; @@ -37,23 +38,24 @@ pub fn from_bytes<'a>( ) -> Result<(), Error> { let bom = unicode_bom::Bom::from(input); let mut newlines = 0; - let (i, frontmatter) = many0(alt(( - map(comment, Event::Comment), - map(take_spaces, |whitespace| Event::Whitespace(Cow::Borrowed(whitespace))), - map(take_newlines, |(newline, counter)| { - newlines += counter; - Event::Newline(Cow::Borrowed(newline)) - }), - )))(&input[bom.len()..]) + let (i, ()) = fold_many0( + alt(( + map(comment, Event::Comment), + map(take_spaces, |whitespace| Event::Whitespace(Cow::Borrowed(whitespace))), + map(take_newlines, |(newline, counter)| { + newlines += counter; + Event::Newline(Cow::Borrowed(newline)) + }), + )), + || (), + |_acc, event| receive_frontmatter(event), + )(&input[bom.len()..]) // I don't think this can panic. many0 errors if the child parser returns // a success where the input was not consumed, but alt will only return Ok // if one of its children succeed. However, all of it's children are // guaranteed to consume something if they succeed, so the Ok(i) == i case // can never occur. .expect("many0(alt(...)) panicked. Likely a bug in one of the children parsers."); - for event in frontmatter { - receive_frontmatter(event); - } if i.is_empty() { return Ok(()); From d3a0c53864ccc9f8d2851d06f0154b9e8f9bcda7 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 14:49:21 +0800 Subject: [PATCH 078/366] allocation-free sections (#331) --- git-config/src/parse/nom/mod.rs | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index c25c8bea90c..643c701a6dc 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -3,7 +3,7 @@ use bstr::{BStr, BString, ByteSlice, ByteVec}; use std::borrow::Cow; use crate::parse::error::ParseNode; -use nom::multi::fold_many0; +use nom::multi::{fold_many0, fold_many1}; use nom::{ branch::alt, bytes::complete::{tag, take_till, take_while}, @@ -13,7 +13,6 @@ use nom::{ }, combinator::{map, opt}, error::{Error as NomError, ErrorKind}, - multi::many1, sequence::delimited, IResult, }; @@ -38,7 +37,7 @@ pub fn from_bytes<'a>( ) -> Result<(), Error> { let bom = unicode_bom::Bom::from(input); let mut newlines = 0; - let (i, ()) = fold_many0( + let (i, _) = fold_many0( alt(( map(comment, Event::Comment), map(take_spaces, |whitespace| Event::Whitespace(Cow::Borrowed(whitespace))), @@ -63,24 +62,20 @@ pub fn from_bytes<'a>( let mut node = ParseNode::SectionHeader; - let maybe_sections = many1(|i| section(i, &mut node))(i); - let (i, sections) = maybe_sections.map_err(|_| Error { + let res = fold_many1( + |i| section(i, &mut node), + || (), + |_acc, (section, additional_newlines)| { + newlines += additional_newlines; + receive_section(section) + }, + )(i); + let (i, _) = res.map_err(|_| Error { line_number: newlines, last_attempted_parser: node, parsed_until: i.as_bstr().into(), })?; - let sections: Vec<_> = sections - .into_iter() - .map(|(section, additional_newlines)| { - newlines += additional_newlines; - section - }) - .collect(); - for section in sections { - receive_section(section); - } - // This needs to happen after we collect sections, otherwise the line number // will be off. if !i.is_empty() { From 2e149b982ec57689c161924dd1d0b22c4fcb681f Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 14:55:07 +0800 Subject: [PATCH 079/366] allocation-free fuzzing, with optimized footprints (#331) --- git-config/fuzz/fuzz_targets/parse.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-config/fuzz/fuzz_targets/parse.rs b/git-config/fuzz/fuzz_targets/parse.rs index 8343b3217e7..69d26bb28fd 100644 --- a/git-config/fuzz/fuzz_targets/parse.rs +++ b/git-config/fuzz/fuzz_targets/parse.rs @@ -4,5 +4,5 @@ use libfuzzer_sys::fuzz_target; fuzz_target!(|data: &[u8]| { // Don't name this _; Rust may optimize it out. - let _a = git_config::parse::Events::from_bytes(data); + let _a = git_config::parse::from_bytes(data, |_e| (), |_s| ()); }); From 050d0f0dee9a64597855e85417460f6e84672b02 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 15:28:23 +0800 Subject: [PATCH 080/366] about 30% faster parsing due to doing no less allocations for section events (#331) --- Cargo.lock | 1 + git-config/Cargo.toml | 1 + git-config/src/file/init.rs | 16 ++--- git-config/src/parse/mod.rs | 2 +- git-config/src/parse/nom/mod.rs | 9 +-- git-config/src/parse/nom/tests.rs | 98 +++++++++++++++++-------------- git-config/src/parse/section.rs | 4 ++ git-config/src/parse/tests.rs | 4 ++ git-config/tests/parse/mod.rs | 16 ++++- 9 files changed, 95 insertions(+), 56 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4b4d5548eba..bb427c602d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1098,6 +1098,7 @@ dependencies = [ "serde", "serde_derive", "serial_test", + "smallvec", "tempfile", "thiserror", "unicode-bom", diff --git a/git-config/Cargo.toml b/git-config/Cargo.toml index ad4c325dd5b..cae9cad4624 100644 --- a/git-config/Cargo.toml +++ b/git-config/Cargo.toml @@ -27,6 +27,7 @@ thiserror = "1.0.26" unicode-bom = "1.1.4" bstr = { version = "0.2.13", default-features = false, features = ["std"] } serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]} +smallvec = "1.9.0" document-features = { version = "0.2.0", optional = true } diff --git a/git-config/src/file/init.rs b/git-config/src/file/init.rs index 742a09bb61b..af9aa497a3d 100644 --- a/git-config/src/file/init.rs +++ b/git-config/src/file/init.rs @@ -5,13 +5,7 @@ use crate::{ parse, File, }; -impl<'a> File<'a> { - /// Constructs an empty `git-config` file. - #[must_use] - pub fn new() -> Self { - Self::default() - } - +impl File<'static> { /// Constructs a `git-config` file from the provided path. /// /// # Errors @@ -45,3 +39,11 @@ impl<'a> File<'a> { Ok(target) } } + +impl<'a> File<'a> { + /// Constructs an empty `git-config` file. + #[must_use] + pub fn new() -> Self { + Self::default() + } +} diff --git a/git-config/src/parse/mod.rs b/git-config/src/parse/mod.rs index 110014b9dd4..2f1252ade53 100644 --- a/git-config/src/parse/mod.rs +++ b/git-config/src/parse/mod.rs @@ -92,7 +92,7 @@ pub struct Section<'a> { /// The section name and subsection name, if any. pub section_header: section::Header<'a>, /// The syntactic events found in this section. - pub events: Vec>, + pub events: section::Events<'a>, } /// diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index 643c701a6dc..1939950c849 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -16,6 +16,7 @@ use nom::{ sequence::delimited, IResult, }; +use smallvec::SmallVec; /// Attempt to zero-copy parse the provided bytes. On success, returns a /// [`Parser`] that provides methods to accessing leading comments and sections @@ -108,7 +109,7 @@ fn section<'a, 'b>(i: &'a [u8], node: &'b mut ParseNode) -> IResult<&'a [u8], (S let (mut i, section_header) = section_header(i)?; let mut newlines = 0; - let mut items = vec![]; + let mut items = SmallVec::default(); // This would usually be a many0(alt(...)), the manual loop allows us to // optimize vec insertions @@ -248,7 +249,7 @@ fn sub_section(i: &[u8]) -> IResult<&[u8], BString> { fn section_body<'a, 'b, 'c>( i: &'a [u8], node: &'b mut ParseNode, - items: &'c mut Vec>, + items: &'c mut section::Events<'a>, ) -> IResult<&'a [u8], ()> { // maybe need to check for [ here *node = ParseNode::ConfigName; @@ -287,7 +288,7 @@ fn config_name(i: &[u8]) -> IResult<&[u8], &BStr> { Ok((i, v.as_bstr())) } -fn config_value<'a, 'b>(i: &'a [u8], events: &'b mut Vec>) -> IResult<&'a [u8], ()> { +fn config_value<'a, 'b>(i: &'a [u8], events: &'b mut section::Events<'a>) -> IResult<&'a [u8], ()> { if let (i, Some(_)) = opt(char('='))(i)? { events.push(Event::KeyValueSeparator); let (i, whitespace) = opt(take_spaces)(i)?; @@ -309,7 +310,7 @@ fn config_value<'a, 'b>(i: &'a [u8], events: &'b mut Vec>) -> IResult< /// /// Returns an error if an invalid escape was used, if there was an unfinished /// quote, or there was an escape but there is nothing left to escape. -fn value_impl<'a, 'b>(i: &'a [u8], events: &'b mut Vec>) -> IResult<&'a [u8], ()> { +fn value_impl<'a, 'b>(i: &'a [u8], events: &'b mut section::Events<'a>) -> IResult<&'a [u8], ()> { let mut parsed_index: usize = 0; let mut offset: usize = 0; diff --git a/git-config/src/parse/nom/tests.rs b/git-config/src/parse/nom/tests.rs index 6f4ee1e3bfa..9ac4a46fc5a 100644 --- a/git-config/src/parse/nom/tests.rs +++ b/git-config/src/parse/nom/tests.rs @@ -108,7 +108,7 @@ mod section { fully_consumed(( Section { section_header: parsed_section_header("test", None), - events: vec![] + events: Default::default() }, 0 )), @@ -147,6 +147,7 @@ mod section { whitespace_event(" "), value_event("\"lol\"") ] + .into() }, 3 )) @@ -186,6 +187,7 @@ mod section { whitespace_event(" "), value_event("\"lol\"") ] + .into() }, 2 )) @@ -200,7 +202,7 @@ mod section { fully_consumed(( Section { section_header: parsed_section_header("hello", None), - events: vec![whitespace_event(" "), name_event("c"), value_event("")] + events: vec![whitespace_event(" "), name_event("c"), value_event("")].into() }, 0 )) @@ -246,6 +248,7 @@ mod section { whitespace_event(" "), value_event("d"), ] + .into() }, 4 )) @@ -275,6 +278,7 @@ mod section { whitespace_event(" "), comment_event('#', " \"b\t ; c"), ] + .into() }, 0 )) @@ -299,6 +303,7 @@ mod section { value_done_event(";\""), comment_event(';', "a"), ] + .into() }, 0 )) @@ -319,6 +324,7 @@ mod section { value_event(""), comment_event('#', "world"), ] + .into() }, 0 )) @@ -328,39 +334,40 @@ mod section { mod value_continuation { use super::value_impl; - use crate::parse::tests::util::{newline_event, value_done_event, value_not_done_event}; + use crate::parse::section; + use crate::parse::tests::util::{into_events, newline_event, value_done_event, value_not_done_event}; #[test] fn simple_continuation() { - let mut events = vec![]; + let mut events = section::Events::default(); assert_eq!(value_impl(b"hello\\\nworld", &mut events).unwrap().0, b""); assert_eq!( events, - vec![ + into_events(vec![ value_not_done_event("hello"), newline_event(), value_done_event("world") - ] + ]) ); } #[test] fn continuation_with_whitespace() { - let mut events = vec![]; + let mut events = section::Events::default(); assert_eq!(value_impl(b"hello\\\n world", &mut events).unwrap().0, b""); assert_eq!( events, - vec![ + into_events(vec![ value_not_done_event("hello"), newline_event(), value_done_event(" world") - ] + ]) ); } #[test] fn complex_continuation_with_leftover_comment() { - let mut events = vec![]; + let mut events = section::Events::default(); assert_eq!( value_impl(b"1 \"\\\"\\\na ; e \"\\\"\\\nd # \"b\t ; c", &mut events) .unwrap() @@ -369,146 +376,151 @@ mod value_continuation { ); assert_eq!( events, - vec![ + into_events(vec![ value_not_done_event(r#"1 "\""#), newline_event(), value_not_done_event(r#"a ; e "\""#), newline_event(), value_done_event("d") - ] + ]) ); } #[test] fn quote_split_over_two_lines_with_leftover_comment() { - let mut events = vec![]; + let mut events = section::Events::default(); assert_eq!(value_impl(b"\"\\\n;\";a", &mut events).unwrap().0, b";a"); assert_eq!( events, - vec![value_not_done_event("\""), newline_event(), value_done_event(";\"")] + into_events(vec![ + value_not_done_event("\""), + newline_event(), + value_done_event(";\"") + ]) ); } } mod value_no_continuation { use super::value_impl; - use crate::parse::tests::util::value_event; + use crate::parse::section; + use crate::parse::tests::util::{into_events, value_event}; #[test] fn no_comment() { - let mut events = vec![]; + let mut events = section::Events::default(); assert_eq!(value_impl(b"hello", &mut events).unwrap().0, b""); - assert_eq!(events, vec![value_event("hello")]); + assert_eq!(events, into_events(vec![value_event("hello")])); } #[test] fn no_comment_newline() { - let mut events = vec![]; + let mut events = section::Events::default(); assert_eq!(value_impl(b"hello\na", &mut events).unwrap().0, b"\na"); - assert_eq!(events, vec![value_event("hello")]); + assert_eq!(events, into_events(vec![value_event("hello")])); } #[test] fn semicolon_comment_not_consumed() { - let mut events = vec![]; + let mut events = section::Events::default(); assert_eq!(value_impl(b"hello;world", &mut events).unwrap().0, b";world"); - assert_eq!(events, vec![value_event("hello")]); + assert_eq!(events, into_events(vec![value_event("hello")])); } #[test] fn octothorpe_comment_not_consumed() { - let mut events = vec![]; + let mut events = section::Events::default(); assert_eq!(value_impl(b"hello#world", &mut events).unwrap().0, b"#world"); - assert_eq!(events, vec![value_event("hello")]); + assert_eq!(events, into_events(vec![value_event("hello")])); } #[test] fn values_with_extraneous_whitespace_without_comment() { - let mut events = vec![]; + let mut events = section::Events::default(); assert_eq!( value_impl(b"hello ", &mut events).unwrap().0, b" " ); - assert_eq!(events, vec![value_event("hello")]); + assert_eq!(events, into_events(vec![value_event("hello")])); } #[test] fn values_with_extraneous_whitespace_before_comment() { - let mut events = vec![]; + let mut events = section::Events::default(); assert_eq!( value_impl(b"hello #world", &mut events).unwrap().0, b" #world" ); - assert_eq!(events, vec![value_event("hello")]); + assert_eq!(events, into_events(vec![value_event("hello")])); - let mut events = vec![]; + let mut events = section::Events::default(); assert_eq!( value_impl(b"hello ;world", &mut events).unwrap().0, b" ;world" ); - assert_eq!(events, vec![value_event("hello")]); + assert_eq!(events, into_events(vec![value_event("hello")])); } #[test] fn trans_escaped_comment_marker_not_consumed() { - let mut events = vec![]; + let mut events = section::Events::default(); assert_eq!(value_impl(br##"hello"#"world; a"##, &mut events).unwrap().0, b"; a"); - assert_eq!(events, vec![value_event(r##"hello"#"world"##)]); + assert_eq!(events, into_events(vec![value_event(r##"hello"#"world"##)])); } #[test] fn complex_test() { - let mut events = vec![]; + let mut events = section::Events::default(); assert_eq!(value_impl(br#"value";";ahhhh"#, &mut events).unwrap().0, b";ahhhh"); - assert_eq!(events, vec![value_event(r#"value";""#)]); + assert_eq!(events, into_events(vec![value_event(r#"value";""#)])); } #[test] fn garbage_after_continution_is_err() { - assert!(value_impl(b"hello \\afwjdls", &mut vec![]).is_err()); + assert!(value_impl(b"hello \\afwjdls", &mut Default::default()).is_err()); } #[test] fn incomplete_quote() { - assert!(value_impl(br#"hello "world"#, &mut vec![]).is_err()); + assert!(value_impl(br#"hello "world"#, &mut Default::default()).is_err()); } #[test] fn incomplete_escape() { - assert!(value_impl(br#"hello world\"#, &mut vec![]).is_err()); + assert!(value_impl(br#"hello world\"#, &mut Default::default()).is_err()); } } mod section_body { use super::section_body; - use crate::parse::tests::util::{name_event, value_event, whitespace_event}; + use crate::parse::tests::util::{into_events, name_event, value_event, whitespace_event}; use crate::parse::{error::ParseNode, Event}; #[test] fn whitespace_is_not_ambigious() { let mut node = ParseNode::SectionHeader; - let mut vec = vec![]; + let mut vec = Default::default(); assert!(section_body(b"a =b", &mut node, &mut vec).is_ok()); assert_eq!( vec, - vec![ + into_events(vec![ name_event("a"), whitespace_event(" "), Event::KeyValueSeparator, value_event("b") - ] + ]) ); - let mut vec = vec![]; + let mut vec = Default::default(); assert!(section_body(b"a= b", &mut node, &mut vec).is_ok()); assert_eq!( vec, - vec![ + into_events(vec![ name_event("a"), Event::KeyValueSeparator, whitespace_event(" "), value_event("b") - ] + ]) ); } } diff --git a/git-config/src/parse/section.rs b/git-config/src/parse/section.rs index 79f2459fcc2..a70f11bedf4 100644 --- a/git-config/src/parse/section.rs +++ b/git-config/src/parse/section.rs @@ -1,8 +1,12 @@ use crate::parse::{Event, Section}; use bstr::{BStr, BString}; +use smallvec::SmallVec; use std::borrow::Cow; use std::fmt::Display; +/// A container for events, avoiding heap allocations in typical files. +pub type Events<'a> = SmallVec<[Event<'a>; 32]>; + /// A parsed section header, containing a name and optionally a subsection name. /// /// Note that section headers must be parsed as valid ASCII, and thus all valid diff --git a/git-config/src/parse/tests.rs b/git-config/src/parse/tests.rs index e28cc220248..c2e9bd66e66 100644 --- a/git-config/src/parse/tests.rs +++ b/git-config/src/parse/tests.rs @@ -59,6 +59,10 @@ pub(crate) mod util { use crate::parse::{section, Comment, Event}; + pub fn into_events(events: Vec>) -> section::Events<'_> { + events.into() + } + pub fn section_header( name: &str, subsection: impl Into>, diff --git a/git-config/tests/parse/mod.rs b/git-config/tests/parse/mod.rs index ee53851637f..f2d35a511cf 100644 --- a/git-config/tests/parse/mod.rs +++ b/git-config/tests/parse/mod.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use git_config::parse::{section, Event, Events}; +use git_config::parse::{section, Event, Events, Section}; pub fn section_header_event(name: &str, subsection: impl Into>) -> Event<'_> { Event::SectionHeader(section_header(name, subsection)) @@ -47,6 +47,20 @@ fn separator() -> Event<'static> { Event::KeyValueSeparator } +#[test] +fn size_in_memory() { + assert_eq!( + std::mem::size_of::>(), + 3440, + "This shouldn't change without us noticing" + ); + assert_eq!( + std::mem::size_of::>(), + 104, + "This shouldn't change without us noticing" + ); +} + #[test] #[rustfmt::skip] fn personal_config() { From 5022be3bb7fa54c97e5110f74aaded9e2f1b6ca5 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 15:34:07 +0800 Subject: [PATCH 081/366] fix docs (#331) --- git-config/src/parse/nom/mod.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index 1939950c849..9decb52919a 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -18,10 +18,8 @@ use nom::{ }; use smallvec::SmallVec; -/// Attempt to zero-copy parse the provided bytes. On success, returns a -/// [`Parser`] that provides methods to accessing leading comments and sections -/// of a `git-config` file and can be converted into an iterator of [`Event`] -/// for higher level processing. +/// Attempt to zero-copy parse the provided bytes, passing results to `receive_frontmatter` and +/// `receive_section` respectively. /// /// # Errors /// From c5c4398a56d4300c83c5be2ba66664bd11f49d5e Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 16:27:16 +0800 Subject: [PATCH 082/366] a greatly simplified Events->File conversion (#331) --- .../src/file/access/low_level/mutating.rs | 2 +- git-config/src/file/impls.rs | 43 +++---------------- git-config/src/file/section.rs | 7 +-- git-config/src/parse/section.rs | 2 +- git-config/tests/parse/mod.rs | 2 +- 5 files changed, 10 insertions(+), 46 deletions(-) diff --git a/git-config/src/file/access/low_level/mutating.rs b/git-config/src/file/access/low_level/mutating.rs index 11fa4ee1beb..668bed53803 100644 --- a/git-config/src/file/access/low_level/mutating.rs +++ b/git-config/src/file/access/low_level/mutating.rs @@ -64,7 +64,7 @@ impl<'a> File<'a> { section_name: impl Into>, subsection_name: impl Into>>, ) -> MutableSection<'_, 'a> { - let mut section = self.push_section(section_name, subsection_name, SectionBody::new()); + let mut section = self.push_section(section_name, subsection_name, SectionBody::default()); section.push_newline(); section } diff --git a/git-config/src/file/impls.rs b/git-config/src/file/impls.rs index 81bc04349c1..71e85d4a699 100644 --- a/git-config/src/file/impls.rs +++ b/git-config/src/file/impls.rs @@ -36,46 +36,15 @@ impl<'a> TryFrom<&'a BString> for File<'a> { } impl<'a> From> for File<'a> { - fn from(parser: parse::Events<'a>) -> Self { - let mut new_self = Self::default(); - - // Current section that we're building - let mut prev_section_header = None; - let mut section_events = SectionBody::new(); - - #[allow(clippy::explicit_into_iter_loop)] - // it's not really an iterator (yet), needs streaming iterator support - for event in parser.into_iter() { - match event { - parse::Event::SectionHeader(header) => { - if let Some(prev_header) = prev_section_header.take() { - new_self.push_section_internal(prev_header, section_events); - } else { - new_self.frontmatter_events = section_events; - } - prev_section_header = Some(header); - section_events = SectionBody::new(); - } - e @ parse::Event::SectionKey(_) - | e @ parse::Event::Value(_) - | e @ parse::Event::ValueNotDone(_) - | e @ parse::Event::ValueDone(_) - | e @ parse::Event::KeyValueSeparator => section_events.as_mut().push(e), - e @ parse::Event::Comment(_) | e @ parse::Event::Newline(_) | e @ parse::Event::Whitespace(_) => { - section_events.as_mut().push(e); - } - } - } + fn from(events: parse::Events<'a>) -> Self { + let mut this = Self::default(); + this.frontmatter_events = SectionBody(events.frontmatter); - // The last section doesn't get pushed since we only push if there's a - // new section header, so we need to call push one more time. - if let Some(header) = prev_section_header { - new_self.push_section_internal(header, section_events); - } else { - new_self.frontmatter_events = section_events; + for section in events.sections { + this.push_section_internal(section.section_header, SectionBody(section.events.into_vec())); } - new_self + this } } diff --git a/git-config/src/file/section.rs b/git-config/src/file/section.rs index da1a147b12b..32a161f51d9 100644 --- a/git-config/src/file/section.rs +++ b/git-config/src/file/section.rs @@ -218,7 +218,7 @@ impl<'event> Deref for MutableSection<'_, 'event> { /// A opaque type that represents a section body. #[allow(clippy::module_name_repetitions)] #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Clone, Debug, Default)] -pub struct SectionBody<'event>(Vec>); +pub struct SectionBody<'event>(pub(crate) Vec>); impl<'event> SectionBody<'event> { pub(crate) fn as_ref(&self) -> &[Event<'_>] { @@ -229,11 +229,6 @@ impl<'event> SectionBody<'event> { &mut self.0 } - /// Constructs a new empty section body. - pub(crate) fn new() -> Self { - Self::default() - } - /// Retrieves the last matching value in a section with the given key. /// Returns None if the key was not found. // We hit this lint because of the unreachable!() call may panic, but this diff --git a/git-config/src/parse/section.rs b/git-config/src/parse/section.rs index a70f11bedf4..08880cf3abc 100644 --- a/git-config/src/parse/section.rs +++ b/git-config/src/parse/section.rs @@ -5,7 +5,7 @@ use std::borrow::Cow; use std::fmt::Display; /// A container for events, avoiding heap allocations in typical files. -pub type Events<'a> = SmallVec<[Event<'a>; 32]>; +pub type Events<'a> = SmallVec<[Event<'a>; 64]>; /// A parsed section header, containing a name and optionally a subsection name. /// diff --git a/git-config/tests/parse/mod.rs b/git-config/tests/parse/mod.rs index f2d35a511cf..74a4c379df0 100644 --- a/git-config/tests/parse/mod.rs +++ b/git-config/tests/parse/mod.rs @@ -51,7 +51,7 @@ fn separator() -> Event<'static> { fn size_in_memory() { assert_eq!( std::mem::size_of::>(), - 3440, + 6768, "This shouldn't change without us noticing" ); assert_eq!( From 2e47167e4a963743494b2df6b0c15800cb876dd0 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 16:44:47 +0800 Subject: [PATCH 083/366] change!: remove `File::new()` method in favor of `File::default()`. (#331) Also: re-use smallvec in `File` type, and cleanup of a couple of lifetimes that prevented proper use. --- .../src/file/access/low_level/mutating.rs | 6 +- .../src/file/access/low_level/read_only.rs | 20 ++-- git-config/src/file/from_env.rs | 6 +- git-config/src/file/impls.rs | 2 +- git-config/src/file/init.rs | 10 +- git-config/src/file/resolve_includes.rs | 4 +- git-config/src/file/section.rs | 17 ++- git-config/src/file/tests.rs | 100 ++++++++++-------- git-config/src/fs.rs | 4 +- git-config/src/parse/events.rs | 11 +- 10 files changed, 90 insertions(+), 90 deletions(-) diff --git a/git-config/src/file/access/low_level/mutating.rs b/git-config/src/file/access/low_level/mutating.rs index 668bed53803..0dedc96a409 100644 --- a/git-config/src/file/access/low_level/mutating.rs +++ b/git-config/src/file/access/low_level/mutating.rs @@ -41,7 +41,7 @@ impl<'a> File<'a> { /// ``` /// # use git_config::File; /// # use std::convert::TryFrom; - /// let mut git_config = git_config::File::new(); + /// let mut git_config = git_config::File::default(); /// let _section = git_config.new_section("hello", Some("world".into())); /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n"); /// ``` @@ -52,7 +52,7 @@ impl<'a> File<'a> { /// # use git_config::File; /// # use std::convert::TryFrom; /// # use bstr::ByteSlice; - /// let mut git_config = git_config::File::new(); + /// let mut git_config = git_config::File::default(); /// let mut section = git_config.new_section("hello", Some("world".into())); /// section.push("a".into(), b"b".as_bstr().into()); /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n a=b\n"); @@ -108,7 +108,7 @@ impl<'a> File<'a> { &mut self, section_name: &'lookup str, subsection_name: impl Into>, - ) -> Option> { + ) -> Option> { let id = self .section_ids_by_name_and_subname(section_name, subsection_name.into()) .ok()? diff --git a/git-config/src/file/access/low_level/read_only.rs b/git-config/src/file/access/low_level/read_only.rs index 5bd60f4b8fc..f9891298613 100644 --- a/git-config/src/file/access/low_level/read_only.rs +++ b/git-config/src/file/access/low_level/read_only.rs @@ -4,7 +4,7 @@ use std::{borrow::Cow, convert::TryFrom}; use crate::{file::SectionBody, lookup, parse::section, File}; /// Read-only low-level access methods. -impl<'a> File<'a> { +impl<'event> File<'event> { /// Returns an interpreted value given a section, an optional subsection and /// key. /// @@ -43,7 +43,7 @@ impl<'a> File<'a> { /// /// [`value`]: crate::value /// [`TryFrom`]: std::convert::TryFrom - pub fn value>>( + pub fn value<'a, T: TryFrom>>( &'a self, section_name: &str, subsection_name: Option<&str>, @@ -53,7 +53,7 @@ impl<'a> File<'a> { } /// Like [`value()`][File::value()], but returning an `Option` if the value wasn't found. - pub fn try_value>>( + pub fn try_value<'a, T: TryFrom>>( &'a self, section_name: &str, subsection_name: Option<&str>, @@ -112,11 +112,11 @@ impl<'a> File<'a> { /// /// [`value`]: crate::value /// [`TryFrom`]: std::convert::TryFrom - pub fn multi_value<'lookup, T: TryFrom>>( + pub fn multi_value<'a, T: TryFrom>>( &'a self, - section_name: &'lookup str, - subsection_name: Option<&'lookup str>, - key: &'lookup str, + section_name: &str, + subsection_name: Option<&str>, + key: &str, ) -> Result, lookup::Error> { self.raw_multi_value(section_name, subsection_name, key)? .into_iter() @@ -135,7 +135,7 @@ impl<'a> File<'a> { &mut self, section_name: &'lookup str, subsection_name: Option<&'lookup str>, - ) -> Result<&SectionBody<'a>, lookup::existing::Error> { + ) -> Result<&SectionBody<'event>, lookup::existing::Error> { let section_ids = self.section_ids_by_name_and_subname(section_name, subsection_name)?; let id = section_ids.last().expect("BUG: Section lookup vec was empty"); Ok(self.sections.get(id).expect("BUG: Section did not have id from lookup")) @@ -175,7 +175,7 @@ impl<'a> File<'a> { /// assert_eq!(git_config.sections_by_name("core").len(), 3); /// ``` #[must_use] - pub fn sections_by_name<'lookup>(&self, section_name: &'lookup str) -> Vec<&SectionBody<'a>> { + pub fn sections_by_name<'lookup>(&self, section_name: &'lookup str) -> Vec<&SectionBody<'event>> { self.section_ids_by_name(section_name) .unwrap_or_default() .into_iter() @@ -236,7 +236,7 @@ impl<'a> File<'a> { pub fn sections_by_name_with_header<'lookup>( &self, section_name: &'lookup str, - ) -> Vec<(§ion::Header<'a>, &SectionBody<'a>)> { + ) -> Vec<(§ion::Header<'event>, &SectionBody<'event>)> { self.section_ids_by_name(section_name) .unwrap_or_default() .into_iter() diff --git a/git-config/src/file/from_env.rs b/git-config/src/file/from_env.rs index d4e485edd08..fb513429242 100644 --- a/git-config/src/file/from_env.rs +++ b/git-config/src/file/from_env.rs @@ -26,7 +26,7 @@ pub enum Error { FromPathsError(#[from] from_paths::Error), } -impl<'a> File<'a> { +impl File<'static> { /// Constructs a `git-config` from the default cascading sequence. /// This is neither zero-alloc nor zero-copy. /// @@ -85,14 +85,14 @@ impl<'a> File<'a> { /// there was an invalid key value pair. /// /// [`git-config`'s documentation]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-GITCONFIGCOUNT - pub fn from_env(options: from_paths::Options<'_>) -> Result>, Error> { + pub fn from_env(options: from_paths::Options<'_>) -> Result>, Error> { use std::env; let count: usize = match env::var("GIT_CONFIG_COUNT") { Ok(v) => v.parse().map_err(|_| Error::ParseError { input: v })?, Err(_) => return Ok(None), }; - let mut config = File::new(); + let mut config = File::default(); for i in 0..count { let key = env::var(format!("GIT_CONFIG_KEY_{}", i)).map_err(|_| Error::InvalidKeyId { key_id: i })?; let value = env::var_os(format!("GIT_CONFIG_VALUE_{}", i)).ok_or(Error::InvalidValueId { value_id: i })?; diff --git a/git-config/src/file/impls.rs b/git-config/src/file/impls.rs index 71e85d4a699..a6bd369ed10 100644 --- a/git-config/src/file/impls.rs +++ b/git-config/src/file/impls.rs @@ -41,7 +41,7 @@ impl<'a> From> for File<'a> { this.frontmatter_events = SectionBody(events.frontmatter); for section in events.sections { - this.push_section_internal(section.section_header, SectionBody(section.events.into_vec())); + this.push_section_internal(section.section_header, SectionBody(section.events)); } this diff --git a/git-config/src/file/init.rs b/git-config/src/file/init.rs index af9aa497a3d..62ede02317e 100644 --- a/git-config/src/file/init.rs +++ b/git-config/src/file/init.rs @@ -29,7 +29,7 @@ impl File<'static> { paths: impl IntoIterator>, options: from_paths::Options<'_>, ) -> Result { - let mut target = Self::new(); + let mut target = Self::default(); for path in paths { let path = path.as_ref(); let mut config = Self::at(path)?; @@ -39,11 +39,3 @@ impl File<'static> { Ok(target) } } - -impl<'a> File<'a> { - /// Constructs an empty `git-config` file. - #[must_use] - pub fn new() -> Self { - Self::default() - } -} diff --git a/git-config/src/file/resolve_includes.rs b/git-config/src/file/resolve_includes.rs index e7d6de4b7d1..26d58f519f1 100644 --- a/git-config/src/file/resolve_includes.rs +++ b/git-config/src/file/resolve_includes.rs @@ -14,7 +14,7 @@ use crate::{ }; pub(crate) fn resolve_includes( - conf: &mut File<'_>, + conf: &mut File<'static>, config_path: Option<&std::path::Path>, options: from_paths::Options<'_>, ) -> Result<(), from_paths::Error> { @@ -22,7 +22,7 @@ pub(crate) fn resolve_includes( } fn resolve_includes_recursive( - target_config: &mut File<'_>, + target_config: &mut File<'static>, target_config_path: Option<&Path>, depth: u8, options: from_paths::Options<'_>, diff --git a/git-config/src/file/section.rs b/git-config/src/file/section.rs index 32a161f51d9..2d9abb57c13 100644 --- a/git-config/src/file/section.rs +++ b/git-config/src/file/section.rs @@ -9,7 +9,7 @@ use std::{ use crate::value::normalize_bstr; use crate::{ file::Index, - lookup, + lookup, parse, parse::{section::Key, Event}, value::{normalize, normalize_bstring}, }; @@ -218,14 +218,14 @@ impl<'event> Deref for MutableSection<'_, 'event> { /// A opaque type that represents a section body. #[allow(clippy::module_name_repetitions)] #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Clone, Debug, Default)] -pub struct SectionBody<'event>(pub(crate) Vec>); +pub struct SectionBody<'event>(pub(crate) parse::section::Events<'event>); impl<'event> SectionBody<'event> { pub(crate) fn as_ref(&self) -> &[Event<'_>] { &self.0 } - pub(crate) fn as_mut(&mut self) -> &mut Vec> { + pub(crate) fn as_mut(&mut self) -> &mut parse::section::Events<'event> { &mut self.0 } @@ -329,7 +329,7 @@ impl<'event> SectionBody<'event> { /// Returns the the range containing the value events for the section. /// If the value is not found, then this returns an empty range. - fn value_range_by_key(&self, key: &Key<'event>) -> Range { + fn value_range_by_key(&self, key: &Key<'_>) -> Range { let mut values_start = 0; // value end needs to be offset by one so that the last value's index // is included in the range @@ -367,8 +367,9 @@ impl<'event> IntoIterator for SectionBody<'event> { type IntoIter = SectionBodyIter<'event>; + // TODO: see if this is used at all fn into_iter(self) -> Self::IntoIter { - SectionBodyIter(self.0.into()) + SectionBodyIter(self.0.into_vec().into()) } } @@ -406,9 +407,3 @@ impl<'event> Iterator for SectionBodyIter<'event> { } impl FusedIterator for SectionBodyIter<'_> {} - -impl<'event> From>> for SectionBody<'event> { - fn from(e: Vec>) -> Self { - Self(e) - } -} diff --git a/git-config/src/file/tests.rs b/git-config/src/file/tests.rs index 403b8af040c..7906a080fd5 100644 --- a/git-config/src/file/tests.rs +++ b/git-config/src/file/tests.rs @@ -47,16 +47,19 @@ mod try_from { let mut sections = HashMap::new(); sections.insert( SectionId(0), - SectionBody::from(vec![ - newline_event(), - name_event("a"), - Event::KeyValueSeparator, - value_event("b"), - newline_event(), - name_event("c"), - Event::KeyValueSeparator, - value_event("d"), - ]), + SectionBody( + vec![ + newline_event(), + name_event("a"), + Event::KeyValueSeparator, + value_event("b"), + newline_event(), + name_event("c"), + Event::KeyValueSeparator, + value_event("d"), + ] + .into(), + ), ); sections }; @@ -89,16 +92,19 @@ mod try_from { let mut sections = HashMap::new(); sections.insert( SectionId(0), - SectionBody::from(vec![ - newline_event(), - name_event("a"), - Event::KeyValueSeparator, - value_event("b"), - newline_event(), - name_event("c"), - Event::KeyValueSeparator, - value_event("d"), - ]), + SectionBody( + vec![ + newline_event(), + name_event("a"), + Event::KeyValueSeparator, + value_event("b"), + newline_event(), + name_event("c"), + Event::KeyValueSeparator, + value_event("d"), + ] + .into(), + ), ); sections }; @@ -134,21 +140,24 @@ mod try_from { let mut sections = HashMap::new(); sections.insert( SectionId(0), - SectionBody::from(vec![ - newline_event(), - name_event("a"), - Event::KeyValueSeparator, - value_event("b"), - newline_event(), - name_event("c"), - Event::KeyValueSeparator, - value_event("d"), - newline_event(), - ]), + SectionBody( + vec![ + newline_event(), + name_event("a"), + Event::KeyValueSeparator, + value_event("b"), + newline_event(), + name_event("c"), + Event::KeyValueSeparator, + value_event("d"), + newline_event(), + ] + .into(), + ), ); sections.insert( SectionId(1), - SectionBody::from(vec![name_event("e"), Event::KeyValueSeparator, value_event("f")]), + SectionBody(vec![name_event("e"), Event::KeyValueSeparator, value_event("f")].into()), ); sections }; @@ -180,21 +189,24 @@ mod try_from { let mut sections = HashMap::new(); sections.insert( SectionId(0), - SectionBody::from(vec![ - newline_event(), - name_event("a"), - Event::KeyValueSeparator, - value_event("b"), - newline_event(), - name_event("c"), - Event::KeyValueSeparator, - value_event("d"), - newline_event(), - ]), + SectionBody( + vec![ + newline_event(), + name_event("a"), + Event::KeyValueSeparator, + value_event("b"), + newline_event(), + name_event("c"), + Event::KeyValueSeparator, + value_event("d"), + newline_event(), + ] + .into(), + ), ); sections.insert( SectionId(1), - SectionBody::from(vec![name_event("e"), Event::KeyValueSeparator, value_event("f")]), + SectionBody(vec![name_event("e"), Event::KeyValueSeparator, value_event("f")].into()), ); sections }; diff --git a/git-config/src/fs.rs b/git-config/src/fs.rs index 1005979aadf..1fd33f2161f 100644 --- a/git-config/src/fs.rs +++ b/git-config/src/fs.rs @@ -96,7 +96,7 @@ impl ConfigBuilder { /// Builds a config, ignoring any failed configuration files. #[must_use] - pub fn build(&self) -> Config<'_> { + pub fn build(&self) -> Config<'static> { let system_conf = if self.no_system { None } else { todo!() }; let global_conf = { @@ -211,7 +211,7 @@ impl<'a> Config<'a> { } /// Returns a mapping from [`File`] to [`ConfigSource`] - const fn mapping(&self) -> [(&Option>, ConfigSource); 6] { + const fn mapping(&self) -> [(&Option>, ConfigSource); 6] { [ (&self.cli_conf, ConfigSource::Cli), (&self.env_conf, ConfigSource::Env), diff --git a/git-config/src/parse/events.rs b/git-config/src/parse/events.rs index c077bb1d48e..5dd5ba72157 100644 --- a/git-config/src/parse/events.rs +++ b/git-config/src/parse/events.rs @@ -1,3 +1,4 @@ +use crate::parse::section; use crate::{ parse, parse::{events::from_path, Event, Section}, @@ -214,7 +215,7 @@ use std::convert::TryFrom; /// [`From<&'_ str>`]: std::convert::From #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] pub struct Events<'a> { - pub(crate) frontmatter: Vec>, + pub(crate) frontmatter: parse::section::Events<'a>, pub(crate) sections: Vec>, } @@ -252,8 +253,8 @@ impl Events<'static> { /// data succeeding valid `git-config` data. #[allow(clippy::shadow_unrelated)] pub fn from_bytes_owned(input: &[u8]) -> Result, parse::Error> { - let mut frontmatter = Vec::new(); - let mut sections = Vec::new(); + let mut frontmatter = section::Events::default(); + let mut sections = Vec::new(); // TODO: maybe smallvec here as well? This favors `File` to be on the heap for sure parse::from_bytes( input, |e| frontmatter.push(e.to_owned()), @@ -291,7 +292,7 @@ impl<'a> Events<'a> { /// data succeeding valid `git-config` data. #[allow(clippy::shadow_unrelated)] pub fn from_bytes(input: &'a [u8]) -> Result, parse::Error> { - let mut frontmatter = Vec::new(); + let mut frontmatter = section::Events::default(); let mut sections = Vec::new(); parse::from_bytes(input, |e| frontmatter.push(e), |s: Section<'_>| sections.push(s))?; Ok(Events { frontmatter, sections }) @@ -312,7 +313,7 @@ impl<'a> Events<'a> { /// a section) from the parser. Subsequent calls will return an empty vec. /// Consider [`Events::frontmatter`] if you only need a reference to the /// frontmatter - pub fn take_frontmatter(&mut self) -> Vec> { + pub fn take_frontmatter(&mut self) -> section::Events<'a> { std::mem::take(&mut self.frontmatter) } From cff6e018a8f0c3b6c78425f99a204d29d72a65aa Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 16:59:49 +0800 Subject: [PATCH 084/366] thanks clippy --- git-config/src/file/impls.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/git-config/src/file/impls.rs b/git-config/src/file/impls.rs index a6bd369ed10..eecc4c98296 100644 --- a/git-config/src/file/impls.rs +++ b/git-config/src/file/impls.rs @@ -37,8 +37,10 @@ impl<'a> TryFrom<&'a BString> for File<'a> { impl<'a> From> for File<'a> { fn from(events: parse::Events<'a>) -> Self { - let mut this = Self::default(); - this.frontmatter_events = SectionBody(events.frontmatter); + let mut this = File { + frontmatter_events: SectionBody(events.frontmatter), + ..Default::default() + }; for section in events.sections { this.push_section_internal(section.section_header, SectionBody(section.events)); From 73adceeae12270c0d470d4b7271c1fd6089d5c2d Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 17:11:16 +0800 Subject: [PATCH 085/366] change!: Slim down API surface of `parse::Events`. (#331) It's more of a 'dumb' structure now than before, merely present to facilitate typical parsing than something special on its own. --- git-config/src/parse/events.rs | 70 ++++++++-------------------------- git-config/tests/parse/mod.rs | 9 +++-- 2 files changed, 20 insertions(+), 59 deletions(-) diff --git a/git-config/src/parse/events.rs b/git-config/src/parse/events.rs index 5dd5ba72157..bca183a62c3 100644 --- a/git-config/src/parse/events.rs +++ b/git-config/src/parse/events.rs @@ -1,7 +1,7 @@ use crate::parse::section; use crate::{ parse, - parse::{events::from_path, Event, Section}, + parse::{events::from_path, Section}, }; use std::convert::TryFrom; @@ -87,7 +87,7 @@ use std::convert::TryFrom; /// # subsection_name: None, /// # }; /// # let section_data = "[core]\n autocrlf = input"; -/// # assert_eq!(Events::from_str(section_data).unwrap().into_vec(), vec![ +/// # assert_eq!(Events::from_str(section_data).unwrap().into_iter().collect::>(), vec![ /// Event::SectionHeader(section_header), /// Event::Newline(Cow::Borrowed("\n".into())), /// Event::Whitespace(Cow::Borrowed(" ".into())), @@ -126,7 +126,7 @@ use std::convert::TryFrom; /// # subsection_name: None, /// # }; /// # let section_data = "[core]\n autocrlf"; -/// # assert_eq!(Events::from_str(section_data).unwrap().into_vec(), vec![ +/// # assert_eq!(Events::from_str(section_data).unwrap().into_iter().collect::>(), vec![ /// Event::SectionHeader(section_header), /// Event::Newline(Cow::Borrowed("\n".into())), /// Event::Whitespace(Cow::Borrowed(" ".into())), @@ -160,7 +160,7 @@ use std::convert::TryFrom; /// # subsection_name: None, /// # }; /// # let section_data = "[core]\nautocrlf=true\"\"\nfilemode=fa\"lse\""; -/// # assert_eq!(Events::from_str(section_data).unwrap().into_vec(), vec![ +/// # assert_eq!(Events::from_str(section_data).unwrap().into_iter().collect::>(), vec![ /// Event::SectionHeader(section_header), /// Event::Newline(Cow::Borrowed("\n".into())), /// Event::SectionKey(section::Key(Cow::Borrowed("autocrlf".into()))), @@ -197,7 +197,7 @@ use std::convert::TryFrom; /// # subsection_name: None, /// # }; /// # let section_data = "[some-section]\nfile=a\\\n c"; -/// # assert_eq!(Events::from_str(section_data).unwrap().into_vec(), vec![ +/// # assert_eq!(Events::from_str(section_data).unwrap().into_iter().collect::>(), vec![ /// Event::SectionHeader(section_header), /// Event::Newline(Cow::Borrowed("\n".into())), /// Event::SectionKey(section::Key(Cow::Borrowed("file".into()))), @@ -215,8 +215,10 @@ use std::convert::TryFrom; /// [`From<&'_ str>`]: std::convert::From #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] pub struct Events<'a> { - pub(crate) frontmatter: parse::section::Events<'a>, - pub(crate) sections: Vec>, + /// Events seen before the first section. + pub frontmatter: parse::section::Events<'a>, + /// All parsed sections. + pub sections: Vec>, } impl Events<'static> { @@ -297,58 +299,16 @@ impl<'a> Events<'a> { parse::from_bytes(input, |e| frontmatter.push(e), |s: Section<'_>| sections.push(s))?; Ok(Events { frontmatter, sections }) } -} - -impl<'a> Events<'a> { - /// Returns the leading events (any comments, whitespace, or newlines before - /// a section) from the parser. Consider [`Events::take_frontmatter`] if - /// you need an owned copy only once. If that function was called, then this - /// will always return an empty slice. - #[must_use] - pub fn frontmatter(&self) -> &[Event<'a>] { - &self.frontmatter - } - - /// Takes the leading events (any comments, whitespace, or newlines before - /// a section) from the parser. Subsequent calls will return an empty vec. - /// Consider [`Events::frontmatter`] if you only need a reference to the - /// frontmatter - pub fn take_frontmatter(&mut self) -> section::Events<'a> { - std::mem::take(&mut self.frontmatter) - } - - /// Returns the parsed sections from the parser. Consider - /// [`Events::take_sections`] if you need an owned copy only once. If that - /// function was called, then this will always return an empty slice. - #[must_use] - pub fn sections(&self) -> &[Section<'a>] { - &self.sections - } - - /// Takes the parsed sections from the parser. Subsequent calls will return - /// an empty vec. Consider [`Events::sections`] if you only need a reference - /// to the comments. - pub fn take_sections(&mut self) -> Vec> { - let mut to_return = vec![]; - std::mem::swap(&mut self.sections, &mut to_return); - to_return - } - - /// Consumes the parser to produce a Vec of Events. - #[must_use] - pub fn into_vec(self) -> Vec> { - self.into_iter().collect() - } /// Consumes the parser to produce an iterator of Events. #[must_use = "iterators are lazy and do nothing unless consumed"] #[allow(clippy::should_implement_trait)] - pub fn into_iter(self) -> impl Iterator> + std::iter::FusedIterator { - self.frontmatter.into_iter().chain( - self.sections.into_iter().flat_map(|section| { - std::iter::once(Event::SectionHeader(section.section_header)).chain(section.events) - }), - ) + pub fn into_iter(self) -> impl Iterator> + std::iter::FusedIterator { + self.frontmatter + .into_iter() + .chain(self.sections.into_iter().flat_map(|section| { + std::iter::once(parse::Event::SectionHeader(section.section_header)).chain(section.events) + })) } } diff --git a/git-config/tests/parse/mod.rs b/git-config/tests/parse/mod.rs index 74a4c379df0..8fdb8b6b75c 100644 --- a/git-config/tests/parse/mod.rs +++ b/git-config/tests/parse/mod.rs @@ -87,7 +87,8 @@ fn personal_config() { assert_eq!( Events::from_str(config) .unwrap() - .into_vec(), + .into_iter() + .collect::>(), vec![ section_header_event("user", None), newline(), @@ -200,13 +201,13 @@ fn personal_config() { #[test] fn parse_empty() { - assert_eq!(Events::from_str("").unwrap().into_vec(), vec![]); + assert_eq!(Events::from_str("").unwrap().into_iter().collect::>(), vec![]); } #[test] fn parse_whitespace() { assert_eq!( - Events::from_str("\n \n \n").unwrap().into_vec(), + Events::from_str("\n \n \n").unwrap().into_iter().collect::>(), vec![newline(), whitespace(" "), newline(), whitespace(" "), newline()] ) } @@ -214,7 +215,7 @@ fn parse_whitespace() { #[test] fn newline_events_are_merged() { assert_eq!( - Events::from_str("\n\n\n\n\n").unwrap().into_vec(), + Events::from_str("\n\n\n\n\n").unwrap().into_iter().collect::>(), vec![newline_custom("\n\n\n\n\n")] ); } From 307c1afebfba952a4931a69796686b8a998c4cd9 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 18:08:39 +0800 Subject: [PATCH 086/366] foundation for allocation free (and smallvec free) parsing (#331) --- git-config/fuzz/fuzz_targets/parse.rs | 2 +- git-config/src/parse/events.rs | 77 +++++++++++++++++++++++---- git-config/src/parse/nom/mod.rs | 19 +++---- 3 files changed, 75 insertions(+), 23 deletions(-) diff --git a/git-config/fuzz/fuzz_targets/parse.rs b/git-config/fuzz/fuzz_targets/parse.rs index 69d26bb28fd..a3df417066b 100644 --- a/git-config/fuzz/fuzz_targets/parse.rs +++ b/git-config/fuzz/fuzz_targets/parse.rs @@ -4,5 +4,5 @@ use libfuzzer_sys::fuzz_target; fuzz_target!(|data: &[u8]| { // Don't name this _; Rust may optimize it out. - let _a = git_config::parse::from_bytes(data, |_e| (), |_s| ()); + let _a = git_config::parse::from_bytes(data, |_e| ()); }); diff --git a/git-config/src/parse/events.rs b/git-config/src/parse/events.rs index bca183a62c3..000bd5d3345 100644 --- a/git-config/src/parse/events.rs +++ b/git-config/src/parse/events.rs @@ -1,4 +1,4 @@ -use crate::parse::section; +use crate::parse::{section, Event}; use crate::{ parse, parse::{events::from_path, Section}, @@ -221,6 +221,8 @@ pub struct Events<'a> { pub sections: Vec>, } +type Sections<'a> = Vec>; + impl Events<'static> { /// Parses a git config located at the provided path. On success, returns a /// [`Events`] that provides methods to accessing leading comments and sections @@ -255,13 +257,39 @@ impl Events<'static> { /// data succeeding valid `git-config` data. #[allow(clippy::shadow_unrelated)] pub fn from_bytes_owned(input: &[u8]) -> Result, parse::Error> { + let mut header = None; + let mut events = section::Events::default(); let mut frontmatter = section::Events::default(); - let mut sections = Vec::new(); // TODO: maybe smallvec here as well? This favors `File` to be on the heap for sure - parse::from_bytes( - input, - |e| frontmatter.push(e.to_owned()), - |s: Section<'_>| sections.push(s.to_owned()), - )?; + let mut sections = Sections::default(); // TODO: maybe smallvec here as well? This favors `File` to be on the heap for sure + parse::from_bytes(input, |e: Event<'_>| match e { + Event::SectionHeader(next_header) => { + match header.take() { + None => { + frontmatter = std::mem::take(&mut events); + } + Some(prev_header) => { + sections.push(parse::Section { + section_header: prev_header, + events: std::mem::take(&mut events), + }); + } + }; + header = next_header.to_owned().into(); + } + event => events.push(event.to_owned()), + })?; + + match header { + None => { + frontmatter = std::mem::take(&mut events); + } + Some(prev_header) => { + sections.push(parse::Section { + section_header: prev_header, + events: std::mem::take(&mut events), + }); + } + } Ok(Events { frontmatter, sections }) } } @@ -292,11 +320,40 @@ impl<'a> Events<'a> { /// Returns an error if the string provided is not a valid `git-config`. /// This generally is due to either invalid names or if there's extraneous /// data succeeding valid `git-config` data. - #[allow(clippy::shadow_unrelated)] pub fn from_bytes(input: &'a [u8]) -> Result, parse::Error> { + let mut header = None; + let mut events = section::Events::default(); let mut frontmatter = section::Events::default(); - let mut sections = Vec::new(); - parse::from_bytes(input, |e| frontmatter.push(e), |s: Section<'_>| sections.push(s))?; + let mut sections = Sections::default(); // TODO: maybe smallvec here as well? This favors `File` to be on the heap for sure + parse::from_bytes(input, |e: Event<'_>| match e { + Event::SectionHeader(next_header) => { + match header.take() { + None => { + frontmatter = std::mem::take(&mut events); + } + Some(prev_header) => { + sections.push(parse::Section { + section_header: prev_header, + events: std::mem::take(&mut events), + }); + } + }; + header = next_header.into(); + } + event => events.push(event), + })?; + + match header { + None => { + frontmatter = std::mem::take(&mut events); + } + Some(prev_header) => { + sections.push(parse::Section { + section_header: prev_header, + events: std::mem::take(&mut events), + }); + } + } Ok(Events { frontmatter, sections }) } diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index 9decb52919a..738ffe449f4 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -18,22 +18,14 @@ use nom::{ }; use smallvec::SmallVec; -/// Attempt to zero-copy parse the provided bytes, passing results to `receive_frontmatter` and -/// `receive_section` respectively. +/// Attempt to zero-copy parse the provided bytes, passing results to `receive_event`. /// /// # Errors /// /// Returns an error if the string provided is not a valid `git-config`. /// This generally is due to either invalid names or if there's extraneous /// data succeeding valid `git-config` data. -#[allow(clippy::shadow_unrelated)] -pub fn from_bytes<'a>( - input: &'a [u8], - // receive_frontmatter: &mut FnFrontMatter<'a>, - // receive_section: &mut FnSection<'a>, - mut receive_frontmatter: impl FnMut(Event<'a>), - mut receive_section: impl FnMut(Section<'a>), -) -> Result<(), Error> { +pub fn from_bytes<'a>(input: &'a [u8], mut receive_event: impl FnMut(Event<'a>)) -> Result<(), Error> { let bom = unicode_bom::Bom::from(input); let mut newlines = 0; let (i, _) = fold_many0( @@ -46,7 +38,7 @@ pub fn from_bytes<'a>( }), )), || (), - |_acc, event| receive_frontmatter(event), + |_acc, event| receive_event(event), )(&input[bom.len()..]) // I don't think this can panic. many0 errors if the child parser returns // a success where the input was not consumed, but alt will only return Ok @@ -66,7 +58,10 @@ pub fn from_bytes<'a>( || (), |_acc, (section, additional_newlines)| { newlines += additional_newlines; - receive_section(section) + receive_event(Event::SectionHeader(section.section_header)); + for event in section.events { + receive_event(event); + } }, )(i); let (i, _) = res.map_err(|_| Error { From ed00e22cbdfea1d69d1d4c2b829effc26b493185 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 18:40:52 +0800 Subject: [PATCH 087/366] allocation-free parsing as callback is passed through (#331) --- git-config/src/parse/nom/mod.rs | 76 ++++++++++++++----------------- git-config/src/parse/nom/tests.rs | 48 ++++++++++++++++--- 2 files changed, 75 insertions(+), 49 deletions(-) diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index 738ffe449f4..ba77c601612 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -1,4 +1,4 @@ -use crate::parse::{section, Comment, Error, Event, Section}; +use crate::parse::{section, Comment, Error, Event}; use bstr::{BStr, BString, ByteSlice, ByteVec}; use std::borrow::Cow; @@ -16,7 +16,6 @@ use nom::{ sequence::delimited, IResult, }; -use smallvec::SmallVec; /// Attempt to zero-copy parse the provided bytes, passing results to `receive_event`. /// @@ -54,14 +53,10 @@ pub fn from_bytes<'a>(input: &'a [u8], mut receive_event: impl FnMut(Event<'a>)) let mut node = ParseNode::SectionHeader; let res = fold_many1( - |i| section(i, &mut node), + |i| section(i, &mut node, &mut receive_event), || (), - |_acc, (section, additional_newlines)| { + |_acc, additional_newlines| { newlines += additional_newlines; - receive_event(Event::SectionHeader(section.section_header)); - for event in section.events { - receive_event(event); - } }, )(i); let (i, _) = res.map_err(|_| Error { @@ -98,11 +93,15 @@ fn comment(i: &[u8]) -> IResult<&[u8], Comment<'_>> { #[cfg(test)] mod tests; -fn section<'a, 'b>(i: &'a [u8], node: &'b mut ParseNode) -> IResult<&'a [u8], (Section<'a>, usize)> { - let (mut i, section_header) = section_header(i)?; +fn section<'a>( + i: &'a [u8], + node: &mut ParseNode, + receive_event: &mut impl FnMut(Event<'a>), +) -> IResult<&'a [u8], usize> { + let (mut i, header) = section_header(i)?; + receive_event(Event::SectionHeader(header)); let mut newlines = 0; - let mut items = SmallVec::default(); // This would usually be a many0(alt(...)), the manual loop allows us to // optimize vec insertions @@ -112,7 +111,7 @@ fn section<'a, 'b>(i: &'a [u8], node: &'b mut ParseNode) -> IResult<&'a [u8], (S if let Ok((new_i, v)) = take_spaces(i) { if old_i != new_i { i = new_i; - items.push(Event::Whitespace(Cow::Borrowed(v.as_bstr()))); + receive_event(Event::Whitespace(Cow::Borrowed(v.as_bstr()))); } } @@ -120,11 +119,11 @@ fn section<'a, 'b>(i: &'a [u8], node: &'b mut ParseNode) -> IResult<&'a [u8], (S if old_i != new_i { i = new_i; newlines += new_newlines; - items.push(Event::Newline(Cow::Borrowed(v.as_bstr()))); + receive_event(Event::Newline(Cow::Borrowed(v.as_bstr()))); } } - if let Ok((new_i, _)) = section_body(i, node, &mut items) { + if let Ok((new_i, _)) = section_body(i, node, receive_event) { if old_i != new_i { i = new_i; } @@ -133,7 +132,7 @@ fn section<'a, 'b>(i: &'a [u8], node: &'b mut ParseNode) -> IResult<&'a [u8], (S if let Ok((new_i, comment)) = comment(i) { if old_i != new_i { i = new_i; - items.push(Event::Comment(comment)); + receive_event(Event::Comment(comment)); } } @@ -142,16 +141,7 @@ fn section<'a, 'b>(i: &'a [u8], node: &'b mut ParseNode) -> IResult<&'a [u8], (S } } - Ok(( - i, - ( - Section { - section_header, - events: items, - }, - newlines, - ), - )) + Ok((i, newlines)) } fn section_header(i: &[u8]) -> IResult<&[u8], section::Header<'_>> { @@ -239,24 +229,24 @@ fn sub_section(i: &[u8]) -> IResult<&[u8], BString> { Ok((&i[cursor - 1..], buf)) } -fn section_body<'a, 'b, 'c>( +fn section_body<'a>( i: &'a [u8], - node: &'b mut ParseNode, - items: &'c mut section::Events<'a>, + node: &mut ParseNode, + receive_event: &mut impl FnMut(Event<'a>), ) -> IResult<&'a [u8], ()> { // maybe need to check for [ here *node = ParseNode::ConfigName; let (i, name) = config_name(i)?; - items.push(Event::SectionKey(section::Key(Cow::Borrowed(name)))); + receive_event(Event::SectionKey(section::Key(Cow::Borrowed(name)))); let (i, whitespace) = opt(take_spaces)(i)?; if let Some(whitespace) = whitespace { - items.push(Event::Whitespace(Cow::Borrowed(whitespace))); + receive_event(Event::Whitespace(Cow::Borrowed(whitespace))); } - let (i, _) = config_value(i, items)?; + let (i, _) = config_value(i, receive_event)?; Ok((i, ())) } @@ -281,17 +271,17 @@ fn config_name(i: &[u8]) -> IResult<&[u8], &BStr> { Ok((i, v.as_bstr())) } -fn config_value<'a, 'b>(i: &'a [u8], events: &'b mut section::Events<'a>) -> IResult<&'a [u8], ()> { +fn config_value<'a>(i: &'a [u8], receive_event: &mut impl FnMut(Event<'a>)) -> IResult<&'a [u8], ()> { if let (i, Some(_)) = opt(char('='))(i)? { - events.push(Event::KeyValueSeparator); + receive_event(Event::KeyValueSeparator); let (i, whitespace) = opt(take_spaces)(i)?; if let Some(whitespace) = whitespace { - events.push(Event::Whitespace(Cow::Borrowed(whitespace))); + receive_event(Event::Whitespace(Cow::Borrowed(whitespace))); } - let (i, _) = value_impl(i, events)?; + let (i, _) = value_impl(i, receive_event)?; Ok((i, ())) } else { - events.push(Event::Value(Cow::Borrowed("".into()))); + receive_event(Event::Value(Cow::Borrowed("".into()))); Ok((i, ())) } } @@ -303,7 +293,7 @@ fn config_value<'a, 'b>(i: &'a [u8], events: &'b mut section::Events<'a>) -> IRe /// /// Returns an error if an invalid escape was used, if there was an unfinished /// quote, or there was an escape but there is nothing left to escape. -fn value_impl<'a, 'b>(i: &'a [u8], events: &'b mut section::Events<'a>) -> IResult<&'a [u8], ()> { +fn value_impl<'a>(i: &'a [u8], receive_event: &mut impl FnMut(Event<'a>)) -> IResult<&'a [u8], ()> { let mut parsed_index: usize = 0; let mut offset: usize = 0; @@ -322,8 +312,8 @@ fn value_impl<'a, 'b>(i: &'a [u8], events: &'b mut section::Events<'a>) -> IResu // continuation. b'\n' => { partial_value_found = true; - events.push(Event::ValueNotDone(Cow::Borrowed(i[offset..index - 1].as_bstr()))); - events.push(Event::Newline(Cow::Borrowed(i[index..=index].as_bstr()))); + receive_event(Event::ValueNotDone(Cow::Borrowed(i[offset..index - 1].as_bstr()))); + receive_event(Event::Newline(Cow::Borrowed(i[index..=index].as_bstr()))); offset = index + 1; parsed_index = 0; } @@ -358,8 +348,8 @@ fn value_impl<'a, 'b>(i: &'a [u8], events: &'b mut section::Events<'a>) -> IResu parsed_index = i.len(); } else { // Didn't parse anything at all, newline straight away. - events.push(Event::Value(Cow::Owned(BString::default()))); - events.push(Event::Newline(Cow::Borrowed("\n".into()))); + receive_event(Event::Value(Cow::Owned(BString::default()))); + receive_event(Event::Newline(Cow::Borrowed("\n".into()))); return Ok(( i.get(1..).ok_or(nom::Err::Error(NomError { input: i, @@ -398,9 +388,9 @@ fn value_impl<'a, 'b>(i: &'a [u8], events: &'b mut section::Events<'a>) -> IResu }; if partial_value_found { - events.push(Event::ValueDone(Cow::Borrowed(remainder_value.as_bstr()))); + receive_event(Event::ValueDone(Cow::Borrowed(remainder_value.as_bstr()))); } else { - events.push(Event::Value(Cow::Borrowed(remainder_value.as_bstr()))); + receive_event(Event::Value(Cow::Borrowed(remainder_value.as_bstr()))); } Ok((i, ())) diff --git a/git-config/src/parse/nom/tests.rs b/git-config/src/parse/nom/tests.rs index 9ac4a46fc5a..6778d06a3e1 100644 --- a/git-config/src/parse/nom/tests.rs +++ b/git-config/src/parse/nom/tests.rs @@ -93,12 +93,37 @@ mod config_name { } mod section { - use super::section; use crate::parse::tests::util::{ comment_event, fully_consumed, name_event, newline_event, section_header as parsed_section_header, value_done_event, value_event, value_not_done_event, whitespace_event, }; - use crate::parse::{error::ParseNode, Event, Section}; + use crate::parse::{error::ParseNode, section, Event, Section}; + + fn section<'a>(i: &'a [u8], node: &mut ParseNode) -> nom::IResult<&'a [u8], (Section<'a>, usize)> { + let mut header = None; + let mut events = section::Events::default(); + super::section(i, node, &mut |e| match &header { + None => { + header = Some(e); + } + Some(_) => events.push(e), + }) + .map(|(i, o)| { + ( + i, + ( + Section { + section_header: match header.expect("header set") { + Event::SectionHeader(header) => header, + _ => unreachable!("unexpected"), + }, + events, + }, + o, + ), + ) + }) + } #[test] fn empty_section() { @@ -333,10 +358,13 @@ mod section { } mod value_continuation { - use super::value_impl; use crate::parse::section; use crate::parse::tests::util::{into_events, newline_event, value_done_event, value_not_done_event}; + pub fn value_impl<'a>(i: &'a [u8], events: &mut section::Events<'a>) -> nom::IResult<&'a [u8], ()> { + super::value_impl(i, &mut |e| events.push(e)) + } + #[test] fn simple_continuation() { let mut events = section::Events::default(); @@ -402,10 +430,11 @@ mod value_continuation { } mod value_no_continuation { - use super::value_impl; use crate::parse::section; use crate::parse::tests::util::{into_events, value_event}; + use super::value_continuation::value_impl; + #[test] fn no_comment() { let mut events = section::Events::default(); @@ -492,9 +521,16 @@ mod value_no_continuation { } mod section_body { - use super::section_body; use crate::parse::tests::util::{into_events, name_event, value_event, whitespace_event}; - use crate::parse::{error::ParseNode, Event}; + use crate::parse::{error::ParseNode, section, Event}; + + fn section_body<'a>( + i: &'a [u8], + node: &mut ParseNode, + events: &mut section::Events<'a>, + ) -> nom::IResult<&'a [u8], ()> { + super::section_body(i, node, &mut |e| events.push(e)) + } #[test] fn whitespace_is_not_ambigious() { From 52bd1e7455d2b09811ea0ac5140c3693d3c1e1f7 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 19:22:10 +0800 Subject: [PATCH 088/366] try to strike a balance between allocations and memory footprint (#331) --- git-config/src/file/impls.rs | 2 +- git-config/src/parse/events.rs | 24 +++++++++++++----------- git-config/src/parse/mod.rs | 2 +- git-config/src/types.rs | 2 +- git-config/tests/file/mod.rs | 9 +++++++++ git-config/tests/parse/mod.rs | 5 +++++ 6 files changed, 30 insertions(+), 14 deletions(-) diff --git a/git-config/src/file/impls.rs b/git-config/src/file/impls.rs index eecc4c98296..b905d70fc93 100644 --- a/git-config/src/file/impls.rs +++ b/git-config/src/file/impls.rs @@ -38,7 +38,7 @@ impl<'a> TryFrom<&'a BString> for File<'a> { impl<'a> From> for File<'a> { fn from(events: parse::Events<'a>) -> Self { let mut this = File { - frontmatter_events: SectionBody(events.frontmatter), + frontmatter_events: events.frontmatter, ..Default::default() }; diff --git a/git-config/src/parse/events.rs b/git-config/src/parse/events.rs index 000bd5d3345..37698d30684 100644 --- a/git-config/src/parse/events.rs +++ b/git-config/src/parse/events.rs @@ -3,8 +3,12 @@ use crate::{ parse, parse::{events::from_path, Section}, }; +use smallvec::SmallVec; use std::convert::TryFrom; +/// A type store without allocation all events that are typicaly preceeding the first section. +pub type FrontMatterEvents<'a> = SmallVec<[Event<'a>; 8]>; + /// A zero-copy `git-config` file parser. /// /// This is parser exposes low-level syntactic events from a `git-config` file. @@ -216,13 +220,11 @@ use std::convert::TryFrom; #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] pub struct Events<'a> { /// Events seen before the first section. - pub frontmatter: parse::section::Events<'a>, + pub frontmatter: FrontMatterEvents<'a>, /// All parsed sections. pub sections: Vec>, } -type Sections<'a> = Vec>; - impl Events<'static> { /// Parses a git config located at the provided path. On success, returns a /// [`Events`] that provides methods to accessing leading comments and sections @@ -259,13 +261,13 @@ impl Events<'static> { pub fn from_bytes_owned(input: &[u8]) -> Result, parse::Error> { let mut header = None; let mut events = section::Events::default(); - let mut frontmatter = section::Events::default(); - let mut sections = Sections::default(); // TODO: maybe smallvec here as well? This favors `File` to be on the heap for sure + let mut frontmatter = FrontMatterEvents::default(); + let mut sections = Vec::new(); parse::from_bytes(input, |e: Event<'_>| match e { Event::SectionHeader(next_header) => { match header.take() { None => { - frontmatter = std::mem::take(&mut events); + frontmatter = std::mem::take(&mut events).into_iter().collect(); } Some(prev_header) => { sections.push(parse::Section { @@ -281,7 +283,7 @@ impl Events<'static> { match header { None => { - frontmatter = std::mem::take(&mut events); + frontmatter = events.into_iter().collect(); } Some(prev_header) => { sections.push(parse::Section { @@ -323,13 +325,13 @@ impl<'a> Events<'a> { pub fn from_bytes(input: &'a [u8]) -> Result, parse::Error> { let mut header = None; let mut events = section::Events::default(); - let mut frontmatter = section::Events::default(); - let mut sections = Sections::default(); // TODO: maybe smallvec here as well? This favors `File` to be on the heap for sure + let mut frontmatter = FrontMatterEvents::default(); + let mut sections = Vec::new(); parse::from_bytes(input, |e: Event<'_>| match e { Event::SectionHeader(next_header) => { match header.take() { None => { - frontmatter = std::mem::take(&mut events); + frontmatter = std::mem::take(&mut events).into_iter().collect(); } Some(prev_header) => { sections.push(parse::Section { @@ -345,7 +347,7 @@ impl<'a> Events<'a> { match header { None => { - frontmatter = std::mem::take(&mut events); + frontmatter = events.into_iter().collect(); } Some(prev_header) => { sections.push(parse::Section { diff --git a/git-config/src/parse/mod.rs b/git-config/src/parse/mod.rs index 2f1252ade53..2538e7b29d8 100644 --- a/git-config/src/parse/mod.rs +++ b/git-config/src/parse/mod.rs @@ -65,7 +65,7 @@ pub enum Event<'a> { pub mod event; #[path = "events.rs"] mod events_type; -pub use events_type::Events; +pub use events_type::{Events, FrontMatterEvents}; /// pub mod events { diff --git a/git-config/src/types.rs b/git-config/src/types.rs index 88280fe9e78..d96462a99bb 100644 --- a/git-config/src/types.rs +++ b/git-config/src/types.rs @@ -51,7 +51,7 @@ pub struct File<'event> { /// The list of events that occur before an actual section. Since a /// `git-config` file prohibits global values, this vec is limited to only /// comment, newline, and whitespace events. - pub(crate) frontmatter_events: SectionBody<'event>, + pub(crate) frontmatter_events: crate::parse::FrontMatterEvents<'event>, /// Section name and subsection name to section id lookup tree. This is /// effectively a n-tree (opposed to a binary tree) that can have a height /// of at most three (including an implicit root node). diff --git a/git-config/tests/file/mod.rs b/git-config/tests/file/mod.rs index bbfea8a4a0d..a044bbff341 100644 --- a/git-config/tests/file/mod.rs +++ b/git-config/tests/file/mod.rs @@ -6,6 +6,15 @@ pub fn cow_str(s: &str) -> Cow<'_, BStr> { Cow::Borrowed(s.as_bytes().as_bstr()) } +#[test] +fn size_in_memory() { + assert_eq!( + std::mem::size_of::>(), + 1032, + "This shouldn't change without us noticing" + ); +} + mod open { use git_config::File; use git_testtools::fixture_path; diff --git a/git-config/tests/parse/mod.rs b/git-config/tests/parse/mod.rs index 8fdb8b6b75c..25ee33945a5 100644 --- a/git-config/tests/parse/mod.rs +++ b/git-config/tests/parse/mod.rs @@ -59,6 +59,11 @@ fn size_in_memory() { 104, "This shouldn't change without us noticing" ); + assert_eq!( + std::mem::size_of::>(), + 872, + "This shouldn't change without us noticing" + ); } #[test] From 14149eea54e2e8a25ac0ccdb2f6efe624f6eaa22 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 8 Jul 2022 07:49:11 +0800 Subject: [PATCH 089/366] change!: remove `parse::Events::from_path` and `File::at` (#331) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The latter has been replaced with `File::from_path_with_buf(…)` and is a low-level way to load just a single config file, purposefully uncomfortable as it will not resolve includes. The initialization API will need some time to stabilize. --- git-config/src/file/from_env.rs | 3 +- git-config/src/file/from_paths.rs | 42 ++++++++++++++++++- git-config/src/file/init.rs | 40 ------------------ git-config/src/file/mod.rs | 1 - git-config/src/file/resolve_includes.rs | 8 ++-- git-config/src/fs.rs | 2 +- git-config/src/parse/events.rs | 25 +---------- git-config/src/parse/mod.rs | 19 --------- .../file/access/raw/mutable_multi_value.rs | 3 +- git-config/tests/file/from_paths/mod.rs | 6 +-- git-config/tests/file/mod.rs | 3 +- 11 files changed, 57 insertions(+), 95 deletions(-) diff --git a/git-config/src/file/from_env.rs b/git-config/src/file/from_env.rs index fb513429242..b4f0f28dfec 100644 --- a/git-config/src/file/from_env.rs +++ b/git-config/src/file/from_env.rs @@ -130,7 +130,8 @@ impl File<'static> { if config.is_empty() { Ok(None) } else { - resolve_includes(&mut config, None, options)?; + let mut buf = Vec::new(); + resolve_includes(&mut config, None, &mut buf, options)?; Ok(Some(config)) } } diff --git a/git-config/src/file/from_paths.rs b/git-config/src/file/from_paths.rs index f89ca25c6a5..09a20c1129d 100644 --- a/git-config/src/file/from_paths.rs +++ b/git-config/src/file/from_paths.rs @@ -1,3 +1,4 @@ +use crate::{file::resolve_includes, File}; use crate::{parse, path::interpolate}; /// The error returned by [`File::from_paths()`][crate::File::from_paths()] and [`File::from_env_paths()`][crate::File::from_env_paths()]. @@ -5,7 +6,9 @@ use crate::{parse, path::interpolate}; #[allow(missing_docs)] pub enum Error { #[error(transparent)] - Parse(#[from] parse::events::from_path::Error), + Io(#[from] std::io::Error), + #[error(transparent)] + Parse(#[from] parse::Error), #[error(transparent)] Interpolate(#[from] interpolate::Error), #[error("The maximum allowed length {} of the file include chain built by following nested resolve_includes is exceeded", .max_depth)] @@ -50,3 +53,40 @@ impl<'a> Default for Options<'a> { } } } + +impl File<'static> { + /// Open a single configuration file by reading `path` into `buf` and copying all contents from there, without resolving includes. + pub fn from_path_with_buf(path: &std::path::Path, buf: &mut Vec) -> Result { + buf.clear(); + std::io::copy(&mut std::fs::File::open(path)?, buf)?; + Self::from_bytes(&buf) + } + + pub(crate) fn from_bytes(input: &[u8]) -> Result { + Ok(parse::Events::from_bytes_owned(input)?.into()) + } + + /// Constructs a `git-config` file from the provided paths in the order provided. + /// This is neither zero-copy nor zero-alloc. + /// + /// # Errors + /// + /// Returns an error if there was an IO error or if a file wasn't a valid + /// git-config file. + /// + /// [`git-config`'s documentation]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-FILES + pub fn from_paths( + paths: impl IntoIterator>, + options: Options<'_>, + ) -> Result { + let mut target = Self::default(); + let mut buf = Vec::with_capacity(512); + for path in paths { + let path = path.as_ref(); + let mut config = Self::from_path_with_buf(path, &mut buf)?; + resolve_includes(&mut config, Some(path), &mut buf, options)?; + target.append(config); + } + Ok(target) + } +} diff --git a/git-config/src/file/init.rs b/git-config/src/file/init.rs index 62ede02317e..8b137891791 100644 --- a/git-config/src/file/init.rs +++ b/git-config/src/file/init.rs @@ -1,41 +1 @@ -use std::path::Path; -use crate::{ - file::{from_paths, resolve_includes}, - parse, File, -}; - -impl File<'static> { - /// Constructs a `git-config` file from the provided path. - /// - /// # Errors - /// - /// Returns an error if there was an IO error or if the file wasn't a valid - /// git-config file. - pub fn at>(path: P) -> Result { - parse::Events::from_path(path).map(Self::from) - } - - /// Constructs a `git-config` file from the provided paths in the order provided. - /// This is neither zero-copy nor zero-alloc. - /// - /// # Errors - /// - /// Returns an error if there was an IO error or if a file wasn't a valid - /// git-config file. - /// - /// [`git-config`'s documentation]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-FILES - pub fn from_paths( - paths: impl IntoIterator>, - options: from_paths::Options<'_>, - ) -> Result { - let mut target = Self::default(); - for path in paths { - let path = path.as_ref(); - let mut config = Self::at(path)?; - resolve_includes(&mut config, Some(path), options)?; - target.append(config); - } - Ok(target) - } -} diff --git a/git-config/src/file/mod.rs b/git-config/src/file/mod.rs index 6f9b038f531..69dac2c5f38 100644 --- a/git-config/src/file/mod.rs +++ b/git-config/src/file/mod.rs @@ -69,7 +69,6 @@ pub mod from_paths; mod access; mod impls; -mod init; mod utils; #[cfg(test)] diff --git a/git-config/src/file/resolve_includes.rs b/git-config/src/file/resolve_includes.rs index 26d58f519f1..3625ffaa813 100644 --- a/git-config/src/file/resolve_includes.rs +++ b/git-config/src/file/resolve_includes.rs @@ -16,15 +16,17 @@ use crate::{ pub(crate) fn resolve_includes( conf: &mut File<'static>, config_path: Option<&std::path::Path>, + buf: &mut Vec, options: from_paths::Options<'_>, ) -> Result<(), from_paths::Error> { - resolve_includes_recursive(conf, config_path, 0, options) + resolve_includes_recursive(conf, config_path, 0, buf, options) } fn resolve_includes_recursive( target_config: &mut File<'static>, target_config_path: Option<&Path>, depth: u8, + buf: &mut Vec, options: from_paths::Options<'_>, ) -> Result<(), from_paths::Error> { if depth == options.max_depth { @@ -78,8 +80,8 @@ fn resolve_includes_recursive( } for config_path in paths_to_include { - let mut include_config = File::at(&config_path)?; - resolve_includes_recursive(&mut include_config, Some(&config_path), depth + 1, options)?; + let mut include_config = File::from_path_with_buf(&config_path, buf)?; + resolve_includes_recursive(&mut include_config, Some(&config_path), depth + 1, buf, options)?; target_config.append(include_config); } Ok(()) diff --git a/git-config/src/fs.rs b/git-config/src/fs.rs index 1fd33f2161f..8c82e5354dd 100644 --- a/git-config/src/fs.rs +++ b/git-config/src/fs.rs @@ -105,7 +105,7 @@ impl ConfigBuilder { .as_ref() .map_or_else(|| Path::new(".git/config"), PathBuf::as_path); - File::at(path).ok() + File::from_paths(Some(path), Default::default()).ok() }; let env_conf = if self.load_env_conf { diff --git a/git-config/src/parse/events.rs b/git-config/src/parse/events.rs index 37698d30684..e3b1735bf67 100644 --- a/git-config/src/parse/events.rs +++ b/git-config/src/parse/events.rs @@ -1,8 +1,5 @@ use crate::parse::{section, Event}; -use crate::{ - parse, - parse::{events::from_path, Section}, -}; +use crate::{parse, parse::Section}; use smallvec::SmallVec; use std::convert::TryFrom; @@ -226,26 +223,6 @@ pub struct Events<'a> { } impl Events<'static> { - /// Parses a git config located at the provided path. On success, returns a - /// [`Events`] that provides methods to accessing leading comments and sections - /// of a `git-config` file and can be converted into an iterator of [`Event`] - /// for higher level processing. - /// - /// Note that since we accept a path rather than a reference to the actual - /// bytes, this function is _not_ zero-copy, as the Parser must own (and thus - /// copy) the bytes that it reads from. Consider one of the other variants if - /// performance is a concern. - /// - /// # Errors - /// - /// Returns an error if there was an IO error or the read file is not a valid - /// `git-config` This generally is due to either invalid names or if there's - /// extraneous data succeeding valid `git-config` data. - pub fn from_path>(path: P) -> Result, from_path::Error> { - let bytes = std::fs::read(path)?; - Ok(Self::from_bytes_owned(&bytes)?) - } - /// Parses the provided bytes, returning an [`Events`] that contains allocated /// and owned events. This is similar to [`Events::from_bytes()`], but performance /// is degraded as it requires allocation for every event. However, this permits diff --git a/git-config/src/parse/mod.rs b/git-config/src/parse/mod.rs index 2538e7b29d8..b607372882d 100644 --- a/git-config/src/parse/mod.rs +++ b/git-config/src/parse/mod.rs @@ -67,25 +67,6 @@ pub mod event; mod events_type; pub use events_type::{Events, FrontMatterEvents}; -/// -pub mod events { - /// - pub mod from_path { - /// An error type representing a Parser [`Error`] or an [`IO error`]. This is - /// returned from functions that will perform IO on top of standard parsing, - /// such as reading from a file. - /// - /// [`IO error`]: std::io::Error - #[derive(Debug, thiserror::Error)] - pub enum Error { - #[error(transparent)] - Parse(#[from] crate::parse::Error), - #[error(transparent)] - Io(#[from] std::io::Error), - } - } -} - /// A parsed section containing the header and the section events. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] pub struct Section<'a> { diff --git a/git-config/tests/file/access/raw/mutable_multi_value.rs b/git-config/tests/file/access/raw/mutable_multi_value.rs index 5343caa31d3..01a5bbfa345 100644 --- a/git-config/tests/file/access/raw/mutable_multi_value.rs +++ b/git-config/tests/file/access/raw/mutable_multi_value.rs @@ -4,7 +4,8 @@ use git_config::File; use git_testtools::fixture_path; fn init_config() -> File<'static> { - File::at(fixture_path("multi-core.txt")).unwrap() + let mut buf = Vec::new(); + File::from_path_with_buf(&fixture_path("multi-core.txt"), &mut buf).unwrap() } #[test] diff --git a/git-config/tests/file/from_paths/mod.rs b/git-config/tests/file/from_paths/mod.rs index ceb254cd0bc..7db59052962 100644 --- a/git-config/tests/file/from_paths/mod.rs +++ b/git-config/tests/file/from_paths/mod.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, fs, io}; -use git_config::{file::from_paths::Error, parse, File}; +use git_config::File; use tempfile::tempdir; use crate::file::cow_str; @@ -16,9 +16,9 @@ fn file_not_found() { let config_path = dir.path().join("config"); let paths = vec![config_path]; - let error = File::from_paths(paths, Default::default()).unwrap_err(); + let err = File::from_paths(paths, Default::default()).unwrap_err(); assert!( - matches!(error, Error::Parse(parse::events::from_path::Error::Io(io_error)) if io_error.kind() == io::ErrorKind::NotFound) + matches!(err, git_config::file::from_paths::Error::Io(io_error) if io_error.kind() == io::ErrorKind::NotFound) ); } diff --git a/git-config/tests/file/mod.rs b/git-config/tests/file/mod.rs index a044bbff341..f5709f2596e 100644 --- a/git-config/tests/file/mod.rs +++ b/git-config/tests/file/mod.rs @@ -21,7 +21,8 @@ mod open { #[test] fn parse_config_with_windows_line_endings_successfully() { - File::at(fixture_path("repo-config.crlf")).unwrap(); + let mut buf = Vec::new(); + File::from_path_with_buf(&fixture_path("repo-config.crlf"), &mut buf).unwrap(); } } From c52cb958f85b533e791ec6b38166a9d819f12dd4 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 8 Jul 2022 07:51:09 +0800 Subject: [PATCH 090/366] adjust to changes in `git-config` (#331) --- git-config/src/file/from_paths.rs | 7 ------- git-repository/src/config.rs | 9 ++++++--- gitoxide-core/src/organize.rs | 3 ++- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/git-config/src/file/from_paths.rs b/git-config/src/file/from_paths.rs index 09a20c1129d..685d9f33fcc 100644 --- a/git-config/src/file/from_paths.rs +++ b/git-config/src/file/from_paths.rs @@ -68,13 +68,6 @@ impl File<'static> { /// Constructs a `git-config` file from the provided paths in the order provided. /// This is neither zero-copy nor zero-alloc. - /// - /// # Errors - /// - /// Returns an error if there was an IO error or if a file wasn't a valid - /// git-config file. - /// - /// [`git-config`'s documentation]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-FILES pub fn from_paths( paths: impl IntoIterator>, options: Options<'_>, diff --git a/git-repository/src/config.rs b/git-repository/src/config.rs index bf9b99587e4..e4d7147c83b 100644 --- a/git-repository/src/config.rs +++ b/git-repository/src/config.rs @@ -3,9 +3,9 @@ use crate::{bstr::BString, permission}; #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Could not open repository conifguration file")] - Open(#[from] git_config::parse::events::from_path::Error), + Open(#[from] git_config::file::from_paths::Error), #[error("Cannot handle objects formatted as {:?}", .name)] - UnsupportedObjectFormat { name: crate::bstr::BString }, + UnsupportedObjectFormat { name: BString }, #[error("The value for '{}' cannot be empty", .key)] EmptyValue { key: &'static str }, #[error("Invalid value for 'core.abbrev' = '{}'. It must be between 4 and {}", .value, .max)] @@ -63,7 +63,10 @@ mod cache { .and_then(|home| home_env.check(home).ok().flatten()); // TODO: don't forget to use the canonicalized home for initializing the stacked config. // like git here: https://github.com/git/git/blob/master/config.c#L208:L208 - let config = File::at(git_dir.join("config"))?; + let config = { + let mut buf = Vec::with_capacity(512); + File::from_path_with_buf(&git_dir.join("config"), &mut buf)? + }; let is_bare = config_bool(&config, "core.bare", false)?; let use_multi_pack_index = config_bool(&config, "core.multiPackIndex", true)?; diff --git a/gitoxide-core/src/organize.rs b/gitoxide-core/src/organize.rs index 21ddf1a37a0..d60f6cbe883 100644 --- a/gitoxide-core/src/organize.rs +++ b/gitoxide-core/src/organize.rs @@ -101,7 +101,8 @@ where fn find_origin_remote(repo: &Path) -> anyhow::Result> { let non_bare = repo.join(".git").join("config"); - let config = File::at(non_bare.as_path()).or_else(|_| File::at(repo.join("config").as_path()))?; + let config = File::from_path_with_buf(non_bare.as_path(), &mut Vec::new()) + .or_else(|_| File::from_path_with_buf(repo.join("config").as_path(), &mut Vec::new()))?; Ok(config .string("remote", Some("origin"), "url") .map(|url| git_url::Url::from_bytes(url.as_ref())) From 8b29ddaa627048b9ca130b52221709a575f50d3a Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 8 Jul 2022 07:52:12 +0800 Subject: [PATCH 091/366] thanks clippy --- git-config/src/file/from_paths.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-config/src/file/from_paths.rs b/git-config/src/file/from_paths.rs index 685d9f33fcc..84f5b64d1b6 100644 --- a/git-config/src/file/from_paths.rs +++ b/git-config/src/file/from_paths.rs @@ -59,7 +59,7 @@ impl File<'static> { pub fn from_path_with_buf(path: &std::path::Path, buf: &mut Vec) -> Result { buf.clear(); std::io::copy(&mut std::fs::File::open(path)?, buf)?; - Self::from_bytes(&buf) + Self::from_bytes(buf) } pub(crate) fn from_bytes(input: &[u8]) -> Result { From 183c7ae0d5f44bb468954a7ad18cc02a01d717bc Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 8 Jul 2022 08:31:14 +0800 Subject: [PATCH 092/366] remove redundant documentation about errors (#331) The error type documents everything. --- .../src/file/access/low_level/mutating.rs | 9 ------ .../src/file/access/low_level/read_only.rs | 17 ----------- git-config/src/file/access/raw.rs | 30 ------------------- git-config/src/file/from_env.rs | 5 ---- git-config/src/file/from_paths.rs | 8 ++--- git-config/src/file/init.rs | 1 - git-config/src/file/value.rs | 8 ----- git-config/src/parse/events.rs | 18 ----------- git-config/src/parse/nom/mod.rs | 11 ------- 9 files changed, 4 insertions(+), 103 deletions(-) delete mode 100644 git-config/src/file/init.rs diff --git a/git-config/src/file/access/low_level/mutating.rs b/git-config/src/file/access/low_level/mutating.rs index 0dedc96a409..dc27a516814 100644 --- a/git-config/src/file/access/low_level/mutating.rs +++ b/git-config/src/file/access/low_level/mutating.rs @@ -11,11 +11,6 @@ use crate::{ /// Mutating low-level access methods. impl<'a> File<'a> { /// Returns an mutable section reference. - /// - /// # Errors - /// - /// This function will return an error if the section and optional - /// subsection do not exist. pub fn section_mut<'lookup>( &mut self, section_name: &'lookup str, @@ -142,10 +137,6 @@ impl<'a> File<'a> { } /// Renames a section, modifying the last matching section. - /// - /// # Errors - /// - /// Returns an error if the lookup doesn't exist pub fn rename_section<'lookup>( &mut self, section_name: &'lookup str, diff --git a/git-config/src/file/access/low_level/read_only.rs b/git-config/src/file/access/low_level/read_only.rs index f9891298613..9205fad3e56 100644 --- a/git-config/src/file/access/low_level/read_only.rs +++ b/git-config/src/file/access/low_level/read_only.rs @@ -35,12 +35,6 @@ impl<'event> File<'event> { /// # Ok::<(), Box>(()) /// ``` /// - /// # Errors - /// - /// This function will return an error if the key is not in the requested - /// section and subsection, if the section and subsection do not exist, or - /// if there was an issue converting the type into the requested variant. - /// /// [`value`]: crate::value /// [`TryFrom`]: std::convert::TryFrom pub fn value<'a, T: TryFrom>>( @@ -104,12 +98,6 @@ impl<'event> File<'event> { /// # Ok::<(), Box>(()) /// ``` /// - /// # Errors - /// - /// This function will return an error if the key is not in the requested - /// section and subsection, if the section and subsection do not exist, or - /// if there was an issue converting the type into the requested variant. - /// /// [`value`]: crate::value /// [`TryFrom`]: std::convert::TryFrom pub fn multi_value<'a, T: TryFrom>>( @@ -126,11 +114,6 @@ impl<'event> File<'event> { } /// Returns an immutable section reference. - /// - /// # Errors - /// - /// This function will return an error if the section and optional - /// subsection do not exist. pub fn section<'lookup>( &mut self, section_name: &'lookup str, diff --git a/git-config/src/file/access/raw.rs b/git-config/src/file/access/raw.rs index 2c36e0512a7..b695d95b200 100644 --- a/git-config/src/file/access/raw.rs +++ b/git-config/src/file/access/raw.rs @@ -18,11 +18,6 @@ impl<'a> File<'a> { /// /// Consider [`Self::raw_multi_value`] if you want to get all values of /// a multivar instead. - /// - /// # Errors - /// - /// This function will return an error if the key is not in the requested - /// section and subsection, or if the section and subsection do not exist. pub fn raw_value<'lookup>( &self, section_name: &'lookup str, @@ -56,11 +51,6 @@ impl<'a> File<'a> { /// /// Consider [`Self::raw_multi_value_mut`] if you want to get mutable /// references to all values of a multivar instead. - /// - /// # Errors - /// - /// This function will return an error if the key is not in the requested - /// section and subsection, or if the section and subsection do not exist. pub fn raw_value_mut<'lookup>( &mut self, section_name: &'lookup str, @@ -153,12 +143,6 @@ impl<'a> File<'a> { /// /// Consider [`Self::raw_value`] if you want to get the resolved single /// value for a given key, if your key does not support multi-valued values. - /// - /// # Errors - /// - /// This function will return an error if the key is not in any requested - /// section and subsection, or if no instance of the section and subsections - /// exist. pub fn raw_multi_value( &self, section_name: &str, @@ -233,12 +217,6 @@ impl<'a> File<'a> { /// /// Note that this operation is relatively expensive, requiring a full /// traversal of the config. - /// - /// # Errors - /// - /// This function will return an error if the key is not in any requested - /// section and subsection, or if no instance of the section and subsections - /// exist. pub fn raw_multi_value_mut<'lookup>( &mut self, section_name: &'lookup str, @@ -318,10 +296,6 @@ impl<'a> File<'a> { /// assert_eq!(git_config.raw_value("core", None, "a")?, Cow::::Borrowed("e".into())); /// # Ok::<(), Box>(()) /// ``` - /// - /// # Errors - /// - /// This errors if any lookup input (section, subsection, and key value) fails. pub fn set_raw_value<'lookup>( &mut self, section_name: &'lookup str, @@ -418,10 +392,6 @@ impl<'a> File<'a> { /// # Ok::<(), git_config::lookup::existing::Error>(()) /// ``` /// - /// # Errors - /// - /// This errors if any lookup input (section, subsection, and key value) fails. - /// /// [`raw_multi_value_mut`]: Self::raw_multi_value_mut pub fn set_raw_multi_value<'lookup>( &mut self, diff --git a/git-config/src/file/from_env.rs b/git-config/src/file/from_env.rs index b4f0f28dfec..bafa8c46864 100644 --- a/git-config/src/file/from_env.rs +++ b/git-config/src/file/from_env.rs @@ -79,11 +79,6 @@ impl File<'static> { /// zero-copy nor zero-alloc. See [`git-config`'s documentation] on /// environment variable for more information. /// - /// # Errors - /// - /// Returns an error if `GIT_CONFIG_COUNT` set and is not a number, or if - /// there was an invalid key value pair. - /// /// [`git-config`'s documentation]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-GITCONFIGCOUNT pub fn from_env(options: from_paths::Options<'_>) -> Result>, Error> { use std::env; diff --git a/git-config/src/file/from_paths.rs b/git-config/src/file/from_paths.rs index 84f5b64d1b6..12448af17c4 100644 --- a/git-config/src/file/from_paths.rs +++ b/git-config/src/file/from_paths.rs @@ -62,10 +62,6 @@ impl File<'static> { Self::from_bytes(buf) } - pub(crate) fn from_bytes(input: &[u8]) -> Result { - Ok(parse::Events::from_bytes_owned(input)?.into()) - } - /// Constructs a `git-config` file from the provided paths in the order provided. /// This is neither zero-copy nor zero-alloc. pub fn from_paths( @@ -82,4 +78,8 @@ impl File<'static> { } Ok(target) } + + pub(crate) fn from_bytes(input: &[u8]) -> Result { + Ok(parse::Events::from_bytes_owned(input)?.into()) + } } diff --git a/git-config/src/file/init.rs b/git-config/src/file/init.rs deleted file mode 100644 index 8b137891791..00000000000 --- a/git-config/src/file/init.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/git-config/src/file/value.rs b/git-config/src/file/value.rs index 5aa11300d51..fd341db07e8 100644 --- a/git-config/src/file/value.rs +++ b/git-config/src/file/value.rs @@ -46,10 +46,6 @@ impl<'borrow, 'lookup, 'event> MutableValue<'borrow, 'lookup, 'event> { /// Returns the actual value. This is computed each time this is called, so /// it's best to reuse this value or own it if an allocation is acceptable. - /// - /// # Errors - /// - /// Returns an error if the lookup failed. pub fn get(&self) -> Result, lookup::existing::Error> { self.section.get(&self.key, self.index, self.index + self.size) } @@ -138,10 +134,6 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { } /// Returns the actual values. This is computed each time this is called. - /// - /// # Errors - /// - /// Returns an error if the lookup failed. pub fn get(&self) -> Result>, lookup::existing::Error> { let mut found_key = false; let mut values = vec![]; diff --git a/git-config/src/parse/events.rs b/git-config/src/parse/events.rs index e3b1735bf67..f4676a4d49f 100644 --- a/git-config/src/parse/events.rs +++ b/git-config/src/parse/events.rs @@ -228,12 +228,6 @@ impl Events<'static> { /// is degraded as it requires allocation for every event. However, this permits /// the reference bytes to be dropped, allowing the parser to be passed around /// without lifetime worries. - /// - /// # Errors - /// - /// Returns an error if the string provided is not a valid `git-config`. - /// This generally is due to either invalid names or if there's extraneous - /// data succeeding valid `git-config` data. #[allow(clippy::shadow_unrelated)] pub fn from_bytes_owned(input: &[u8]) -> Result, parse::Error> { let mut header = None; @@ -278,12 +272,6 @@ impl<'a> Events<'a> { /// [`Events`] that provides methods to accessing leading comments and sections /// of a `git-config` file and can be converted into an iterator of [`Event`] /// for higher level processing. - /// - /// # Errors - /// - /// Returns an error if the string provided is not a valid `git-config`. - /// This generally is due to either invalid names or if there's extraneous - /// data succeeding valid `git-config` data. #[allow(clippy::should_implement_trait)] pub fn from_str(input: &'a str) -> Result, parse::Error> { Self::from_bytes(input.as_bytes()) @@ -293,12 +281,6 @@ impl<'a> Events<'a> { /// [`Events`] that provides methods to accessing leading comments and sections /// of a `git-config` file and can be converted into an iterator of [`Event`] /// for higher level processing. - /// - /// # Errors - /// - /// Returns an error if the string provided is not a valid `git-config`. - /// This generally is due to either invalid names or if there's extraneous - /// data succeeding valid `git-config` data. pub fn from_bytes(input: &'a [u8]) -> Result, parse::Error> { let mut header = None; let mut events = section::Events::default(); diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index ba77c601612..db93f731527 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -18,12 +18,6 @@ use nom::{ }; /// Attempt to zero-copy parse the provided bytes, passing results to `receive_event`. -/// -/// # Errors -/// -/// Returns an error if the string provided is not a valid `git-config`. -/// This generally is due to either invalid names or if there's extraneous -/// data succeeding valid `git-config` data. pub fn from_bytes<'a>(input: &'a [u8], mut receive_event: impl FnMut(Event<'a>)) -> Result<(), Error> { let bom = unicode_bom::Bom::from(input); let mut newlines = 0; @@ -288,11 +282,6 @@ fn config_value<'a>(i: &'a [u8], receive_event: &mut impl FnMut(Event<'a>)) -> I /// Handles parsing of known-to-be values. This function handles both single /// line values as well as values that are continuations. -/// -/// # Errors -/// -/// Returns an error if an invalid escape was used, if there was an unfinished -/// quote, or there was an escape but there is nothing left to escape. fn value_impl<'a>(i: &'a [u8], receive_event: &mut impl FnMut(Event<'a>)) -> IResult<&'a [u8], ()> { let mut parsed_index: usize = 0; let mut offset: usize = 0; From e571fdb4630ff373ece02efcd963724c05978ede Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 8 Jul 2022 08:43:40 +0800 Subject: [PATCH 093/366] unclutter lifetime declarations (#331) Only use what we actually need. --- .../src/file/access/low_level/mutating.rs | 44 +++++++++---------- .../src/file/access/low_level/read_only.rs | 12 ++--- git-config/src/file/access/raw.rs | 32 +++++++------- git-config/src/file/utils.rs | 10 ++--- git-config/src/fs.rs | 16 +++---- 5 files changed, 57 insertions(+), 57 deletions(-) diff --git a/git-config/src/file/access/low_level/mutating.rs b/git-config/src/file/access/low_level/mutating.rs index dc27a516814..b7007b45e38 100644 --- a/git-config/src/file/access/low_level/mutating.rs +++ b/git-config/src/file/access/low_level/mutating.rs @@ -9,13 +9,13 @@ use crate::{ }; /// Mutating low-level access methods. -impl<'a> File<'a> { +impl<'event> File<'event> { /// Returns an mutable section reference. - pub fn section_mut<'lookup>( - &mut self, - section_name: &'lookup str, - subsection_name: Option<&'lookup str>, - ) -> Result, lookup::existing::Error> { + pub fn section_mut<'a>( + &'a mut self, + section_name: &str, + subsection_name: Option<&str>, + ) -> Result, lookup::existing::Error> { let section_ids = self.section_ids_by_name_and_subname(section_name, subsection_name)?; let id = section_ids.last().expect("BUG: Section lookup vec was empty"); Ok(MutableSection::new( @@ -56,9 +56,9 @@ impl<'a> File<'a> { /// ``` pub fn new_section( &mut self, - section_name: impl Into>, - subsection_name: impl Into>>, - ) -> MutableSection<'_, 'a> { + section_name: impl Into>, + subsection_name: impl Into>>, + ) -> MutableSection<'_, 'event> { let mut section = self.push_section(section_name, subsection_name, SectionBody::default()); section.push_newline(); section @@ -99,11 +99,11 @@ impl<'a> File<'a> { /// let events = git_config.remove_section("hello", Some("world".into())); /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n some-value = 4\n"); /// ``` - pub fn remove_section<'lookup>( + pub fn remove_section<'a>( &mut self, - section_name: &'lookup str, - subsection_name: impl Into>, - ) -> Option> { + section_name: &str, + subsection_name: impl Into>, + ) -> Option> { let id = self .section_ids_by_name_and_subname(section_name, subsection_name.into()) .ok()? @@ -121,10 +121,10 @@ impl<'a> File<'a> { /// to it. pub fn push_section( &mut self, - section_name: impl Into>, - subsection_name: impl Into>>, - section: SectionBody<'a>, - ) -> MutableSection<'_, 'a> { + section_name: impl Into>, + subsection_name: impl Into>>, + section: SectionBody<'event>, + ) -> MutableSection<'_, 'event> { let subsection_name = subsection_name.into().map(into_cow_bstr); self.push_section_internal( section::Header { @@ -137,12 +137,12 @@ impl<'a> File<'a> { } /// Renames a section, modifying the last matching section. - pub fn rename_section<'lookup>( + pub fn rename_section<'a>( &mut self, - section_name: &'lookup str, - subsection_name: impl Into>, - new_section_name: impl Into>, - new_subsection_name: impl Into>>, + section_name: &str, + subsection_name: impl Into>, + new_section_name: impl Into>, + new_subsection_name: impl Into>>, ) -> Result<(), lookup::existing::Error> { let id = self.section_ids_by_name_and_subname(section_name, subsection_name.into())?; let id = id diff --git a/git-config/src/file/access/low_level/read_only.rs b/git-config/src/file/access/low_level/read_only.rs index 9205fad3e56..24147dc76dc 100644 --- a/git-config/src/file/access/low_level/read_only.rs +++ b/git-config/src/file/access/low_level/read_only.rs @@ -114,10 +114,10 @@ impl<'event> File<'event> { } /// Returns an immutable section reference. - pub fn section<'lookup>( + pub fn section( &mut self, - section_name: &'lookup str, - subsection_name: Option<&'lookup str>, + section_name: &str, + subsection_name: Option<&str>, ) -> Result<&SectionBody<'event>, lookup::existing::Error> { let section_ids = self.section_ids_by_name_and_subname(section_name, subsection_name)?; let id = section_ids.last().expect("BUG: Section lookup vec was empty"); @@ -158,7 +158,7 @@ impl<'event> File<'event> { /// assert_eq!(git_config.sections_by_name("core").len(), 3); /// ``` #[must_use] - pub fn sections_by_name<'lookup>(&self, section_name: &'lookup str) -> Vec<&SectionBody<'event>> { + pub fn sections_by_name(&self, section_name: &str) -> Vec<&SectionBody<'event>> { self.section_ids_by_name(section_name) .unwrap_or_default() .into_iter() @@ -216,9 +216,9 @@ impl<'event> File<'event> { /// } /// } /// ``` - pub fn sections_by_name_with_header<'lookup>( + pub fn sections_by_name_with_header( &self, - section_name: &'lookup str, + section_name: &str, ) -> Vec<(§ion::Header<'event>, &SectionBody<'event>)> { self.section_ids_by_name(section_name) .unwrap_or_default() diff --git a/git-config/src/file/access/raw.rs b/git-config/src/file/access/raw.rs index b695d95b200..3b617461373 100644 --- a/git-config/src/file/access/raw.rs +++ b/git-config/src/file/access/raw.rs @@ -12,17 +12,17 @@ use crate::{ /// /// These functions are the raw value API. Instead of returning Rust structures, /// these functions return bytes which may or may not be owned. -impl<'a> File<'a> { +impl<'event> File<'event> { /// Returns an uninterpreted value given a section, an optional subsection /// and key. /// /// Consider [`Self::raw_multi_value`] if you want to get all values of /// a multivar instead. - pub fn raw_value<'lookup>( + pub fn raw_value( &self, - section_name: &'lookup str, - subsection_name: Option<&'lookup str>, - key: &'lookup str, + section_name: &str, + subsection_name: Option<&str>, + key: &str, ) -> Result, lookup::existing::Error> { // Note: cannot wrap around the raw_multi_value method because we need // to guarantee that the highest section id is used (so that we follow @@ -56,7 +56,7 @@ impl<'a> File<'a> { section_name: &'lookup str, subsection_name: Option<&'lookup str>, key: &'lookup str, - ) -> Result, lookup::existing::Error> { + ) -> Result, lookup::existing::Error> { let section_ids = self.section_ids_by_name_and_subname(section_name, subsection_name)?; let key = section::Key(Cow::::Borrowed(key.into())); @@ -222,7 +222,7 @@ impl<'a> File<'a> { section_name: &'lookup str, subsection_name: Option<&'lookup str>, key: &'lookup str, - ) -> Result, lookup::existing::Error> { + ) -> Result, lookup::existing::Error> { let section_ids = self.section_ids_by_name_and_subname(section_name, subsection_name)?; let key = section::Key(Cow::::Borrowed(key.into())); @@ -296,11 +296,11 @@ impl<'a> File<'a> { /// assert_eq!(git_config.raw_value("core", None, "a")?, Cow::::Borrowed("e".into())); /// # Ok::<(), Box>(()) /// ``` - pub fn set_raw_value<'lookup>( + pub fn set_raw_value( &mut self, - section_name: &'lookup str, - subsection_name: Option<&'lookup str>, - key: &'lookup str, + section_name: &str, + subsection_name: Option<&str>, + key: &str, new_value: BString, ) -> Result<(), lookup::existing::Error> { self.raw_value_mut(section_name, subsection_name, key) @@ -393,12 +393,12 @@ impl<'a> File<'a> { /// ``` /// /// [`raw_multi_value_mut`]: Self::raw_multi_value_mut - pub fn set_raw_multi_value<'lookup>( + pub fn set_raw_multi_value( &mut self, - section_name: &'lookup str, - subsection_name: Option<&'lookup str>, - key: &'lookup str, - new_values: impl Iterator>, + section_name: &str, + subsection_name: Option<&str>, + key: &str, + new_values: impl Iterator>, ) -> Result<(), lookup::existing::Error> { self.raw_multi_value_mut(section_name, subsection_name, key) .map(|mut v| v.set_values(new_values)) diff --git a/git-config/src/file/utils.rs b/git-config/src/file/utils.rs index 0e87d59161b..066c3303ac3 100644 --- a/git-config/src/file/utils.rs +++ b/git-config/src/file/utils.rs @@ -57,10 +57,10 @@ impl<'event> File<'event> { } /// Returns the mapping between section and subsection name to section ids. - pub(crate) fn section_ids_by_name_and_subname<'lookup>( + pub(crate) fn section_ids_by_name_and_subname<'a>( &self, - section_name: impl Into>, - subsection_name: Option<&'lookup str>, + section_name: impl Into>, + subsection_name: Option<&str>, ) -> Result, lookup::existing::Error> { let section_name = section_name.into(); let section_ids = self @@ -92,9 +92,9 @@ impl<'event> File<'event> { .ok_or(lookup::existing::Error::SubSectionMissing) } - pub(crate) fn section_ids_by_name<'lookup>( + pub(crate) fn section_ids_by_name<'a>( &self, - section_name: impl Into>, + section_name: impl Into>, ) -> Result, lookup::existing::Error> { let section_name = section_name.into(); self.section_lookup_tree diff --git a/git-config/src/fs.rs b/git-config/src/fs.rs index 8c82e5354dd..4ba1012c26a 100644 --- a/git-config/src/fs.rs +++ b/git-config/src/fs.rs @@ -179,11 +179,11 @@ impl<'a> Config<'a> { None } - pub fn try_value<'lookup, T: TryFrom>>( + pub fn try_value>>( &'a self, - section_name: &'lookup str, - subsection_name: Option<&'lookup str>, - key: &'lookup str, + section_name: &str, + subsection_name: Option<&str>, + key: &str, ) -> Result, lookup::Error> { self.try_value_with_source(section_name, subsection_name, key) .map(|res| res.map(|(value, _)| value)) @@ -193,11 +193,11 @@ impl<'a> Config<'a> { /// if the key was not found. On a successful parse, the value will be /// returned as well as the source location. This respects the priority of /// the various configuration files. - pub fn try_value_with_source<'lookup, T: TryFrom>>( + pub fn try_value_with_source>>( &'a self, - section_name: &'lookup str, - subsection_name: Option<&'lookup str>, - key: &'lookup str, + section_name: &str, + subsection_name: Option<&str>, + key: &str, ) -> Result, lookup::Error> { let mapping = self.mapping(); From ead757c2a4b737d2f617cf23c370e2ca5c46b08b Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 8 Jul 2022 08:53:23 +0800 Subject: [PATCH 094/366] deduplicate events instantiation (#331) --- git-config/src/parse/events.rs | 112 +++++++++++++-------------------- 1 file changed, 43 insertions(+), 69 deletions(-) diff --git a/git-config/src/parse/events.rs b/git-config/src/parse/events.rs index f4676a4d49f..ea228b4c887 100644 --- a/git-config/src/parse/events.rs +++ b/git-config/src/parse/events.rs @@ -228,42 +228,8 @@ impl Events<'static> { /// is degraded as it requires allocation for every event. However, this permits /// the reference bytes to be dropped, allowing the parser to be passed around /// without lifetime worries. - #[allow(clippy::shadow_unrelated)] pub fn from_bytes_owned(input: &[u8]) -> Result, parse::Error> { - let mut header = None; - let mut events = section::Events::default(); - let mut frontmatter = FrontMatterEvents::default(); - let mut sections = Vec::new(); - parse::from_bytes(input, |e: Event<'_>| match e { - Event::SectionHeader(next_header) => { - match header.take() { - None => { - frontmatter = std::mem::take(&mut events).into_iter().collect(); - } - Some(prev_header) => { - sections.push(parse::Section { - section_header: prev_header, - events: std::mem::take(&mut events), - }); - } - }; - header = next_header.to_owned().into(); - } - event => events.push(event.to_owned()), - })?; - - match header { - None => { - frontmatter = events.into_iter().collect(); - } - Some(prev_header) => { - sections.push(parse::Section { - section_header: prev_header, - events: std::mem::take(&mut events), - }); - } - } - Ok(Events { frontmatter, sections }) + from_bytes(input, |e| e.to_owned()) } } @@ -282,40 +248,7 @@ impl<'a> Events<'a> { /// of a `git-config` file and can be converted into an iterator of [`Event`] /// for higher level processing. pub fn from_bytes(input: &'a [u8]) -> Result, parse::Error> { - let mut header = None; - let mut events = section::Events::default(); - let mut frontmatter = FrontMatterEvents::default(); - let mut sections = Vec::new(); - parse::from_bytes(input, |e: Event<'_>| match e { - Event::SectionHeader(next_header) => { - match header.take() { - None => { - frontmatter = std::mem::take(&mut events).into_iter().collect(); - } - Some(prev_header) => { - sections.push(parse::Section { - section_header: prev_header, - events: std::mem::take(&mut events), - }); - } - }; - header = next_header.into(); - } - event => events.push(event), - })?; - - match header { - None => { - frontmatter = events.into_iter().collect(); - } - Some(prev_header) => { - sections.push(parse::Section { - section_header: prev_header, - events: std::mem::take(&mut events), - }); - } - } - Ok(Events { frontmatter, sections }) + from_bytes(input, std::convert::identity) } /// Consumes the parser to produce an iterator of Events. @@ -345,3 +278,44 @@ impl<'a> TryFrom<&'a [u8]> for Events<'a> { Events::from_bytes(value) } } + +fn from_bytes<'a, 'b>(input: &'a [u8], convert: impl Fn(Event<'a>) -> Event<'b>) -> Result, parse::Error> { + let mut header = None; + let mut events = section::Events::default(); + let mut frontmatter = FrontMatterEvents::default(); + let mut sections = Vec::new(); + parse::from_bytes(input, |e: Event<'_>| match e { + Event::SectionHeader(next_header) => { + match header.take() { + None => { + frontmatter = std::mem::take(&mut events).into_iter().collect(); + } + Some(prev_header) => { + sections.push(parse::Section { + section_header: prev_header, + events: std::mem::take(&mut events), + }); + } + }; + header = match convert(Event::SectionHeader(next_header)) { + Event::SectionHeader(h) => h, + _ => unreachable!("BUG: convert must not change the event type, just the lifetime"), + } + .into(); + } + event => events.push(convert(event)), + })?; + + match header { + None => { + frontmatter = events.into_iter().collect(); + } + Some(prev_header) => { + sections.push(parse::Section { + section_header: prev_header, + events: std::mem::take(&mut events), + }); + } + } + Ok(Events { frontmatter, sections }) +} From 6ba2f8060768978ad7204e162fb2253ca8843879 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 8 Jul 2022 09:03:28 +0800 Subject: [PATCH 095/366] feat!: filtering supportort for `parse::Events`. (#331) That way it's possible to construct Files which are not destined to be written back as they only keep events necessary for value access, greatly reducing allocations. --- git-config/src/file/from_paths.rs | 2 +- git-config/src/parse/events.rs | 21 ++++++++++++++++----- git-config/src/parse/tests.rs | 4 ++-- git-config/tests/file/access/read_only.rs | 2 +- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/git-config/src/file/from_paths.rs b/git-config/src/file/from_paths.rs index 12448af17c4..23c930938fa 100644 --- a/git-config/src/file/from_paths.rs +++ b/git-config/src/file/from_paths.rs @@ -80,6 +80,6 @@ impl File<'static> { } pub(crate) fn from_bytes(input: &[u8]) -> Result { - Ok(parse::Events::from_bytes_owned(input)?.into()) + Ok(parse::Events::from_bytes_owned(input, None)?.into()) } } diff --git a/git-config/src/parse/events.rs b/git-config/src/parse/events.rs index ea228b4c887..e9b462945c0 100644 --- a/git-config/src/parse/events.rs +++ b/git-config/src/parse/events.rs @@ -228,8 +228,11 @@ impl Events<'static> { /// is degraded as it requires allocation for every event. However, this permits /// the reference bytes to be dropped, allowing the parser to be passed around /// without lifetime worries. - pub fn from_bytes_owned(input: &[u8]) -> Result, parse::Error> { - from_bytes(input, |e| e.to_owned()) + pub fn from_bytes_owned<'a>( + input: &'a [u8], + filter: Option) -> bool>, + ) -> Result, parse::Error> { + from_bytes(input, |e| e.to_owned(), filter) } } @@ -248,7 +251,7 @@ impl<'a> Events<'a> { /// of a `git-config` file and can be converted into an iterator of [`Event`] /// for higher level processing. pub fn from_bytes(input: &'a [u8]) -> Result, parse::Error> { - from_bytes(input, std::convert::identity) + from_bytes(input, std::convert::identity, None) } /// Consumes the parser to produce an iterator of Events. @@ -279,7 +282,11 @@ impl<'a> TryFrom<&'a [u8]> for Events<'a> { } } -fn from_bytes<'a, 'b>(input: &'a [u8], convert: impl Fn(Event<'a>) -> Event<'b>) -> Result, parse::Error> { +fn from_bytes<'a, 'b>( + input: &'a [u8], + convert: impl Fn(Event<'a>) -> Event<'b>, + filter: Option) -> bool>, +) -> Result, parse::Error> { let mut header = None; let mut events = section::Events::default(); let mut frontmatter = FrontMatterEvents::default(); @@ -303,7 +310,11 @@ fn from_bytes<'a, 'b>(input: &'a [u8], convert: impl Fn(Event<'a>) -> Event<'b>) } .into(); } - event => events.push(convert(event)), + event => { + if filter.map_or(true, |f| f(&event)) { + events.push(convert(event)) + } + } })?; match header { diff --git a/git-config/src/parse/tests.rs b/git-config/src/parse/tests.rs index c2e9bd66e66..db8d1c56fb3 100644 --- a/git-config/src/parse/tests.rs +++ b/git-config/src/parse/tests.rs @@ -17,8 +17,8 @@ mod parse { Events::from_bytes(bytes_with_gb18030_bom.as_bytes()) ); assert_eq!( - Events::from_bytes_owned(bytes), - Events::from_bytes_owned(bytes_with_gb18030_bom.as_bytes()) + Events::from_bytes_owned(bytes, None), + Events::from_bytes_owned(bytes_with_gb18030_bom.as_bytes(), None) ); } } diff --git a/git-config/tests/file/access/read_only.rs b/git-config/tests/file/access/read_only.rs index 9feb3b075e2..d7dd165a185 100644 --- a/git-config/tests/file/access/read_only.rs +++ b/git-config/tests/file/access/read_only.rs @@ -23,7 +23,7 @@ fn get_value_for_all_provided_values() -> crate::Result { location-quoted = "~/quoted" "#; - let file = git_config::parse::Events::from_bytes_owned(config.as_bytes()).map(File::from)?; + let file = git_config::parse::Events::from_bytes_owned(config.as_bytes(), None).map(File::from)?; assert_eq!( file.value::("core", None, "bool-explicit")?, From f5026fb3b64bccf26bc8d5a74dbc5e89b98d9959 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 8 Jul 2022 10:05:07 +0800 Subject: [PATCH 096/366] assure no important docs are missed (#331) --- git-config/src/file/mod.rs | 2 ++ git-config/src/fs.rs | 1 + git-config/src/lib.rs | 7 ++++--- git-config/src/lookup.rs | 3 +++ git-config/src/values/mod.rs | 1 + git-config/src/values/path.rs | 1 + 6 files changed, 12 insertions(+), 3 deletions(-) diff --git a/git-config/src/file/mod.rs b/git-config/src/file/mod.rs index 69dac2c5f38..89203667474 100644 --- a/git-config/src/file/mod.rs +++ b/git-config/src/file/mod.rs @@ -60,11 +60,13 @@ pub(crate) enum LookupTreeNode<'a> { NonTerminal(HashMap, Vec>), } +/// pub mod from_env; mod resolve_includes; pub(crate) use resolve_includes::resolve_includes; +/// pub mod from_paths; mod access; diff --git a/git-config/src/fs.rs b/git-config/src/fs.rs index 4ba1012c26a..e0411834010 100644 --- a/git-config/src/fs.rs +++ b/git-config/src/fs.rs @@ -1,3 +1,4 @@ +#![allow(missing_docs)] #![allow(unused)] #![allow(clippy::result_unit_err)] diff --git a/git-config/src/lib.rs b/git-config/src/lib.rs index b7c11cd5c77..efc9527d86e 100644 --- a/git-config/src/lib.rs +++ b/git-config/src/lib.rs @@ -1,6 +1,4 @@ -#![deny(unsafe_code, rust_2018_idioms)] -// #![warn(missing_docs)] -// #![warn(clippy::pedantic, clippy::nursery)] +#![deny(missing_docs, unsafe_code, rust_2018_idioms)] //! # `git_config` //! @@ -54,9 +52,12 @@ cfg_attr(doc, doc = ::document_features::document_features!()) )] pub mod file; +/// pub mod fs; +/// pub mod lookup; pub mod parse; +/// pub mod value; mod values; pub use values::*; diff --git a/git-config/src/lookup.rs b/git-config/src/lookup.rs index 6ae8ecce117..5a3163fbe23 100644 --- a/git-config/src/lookup.rs +++ b/git-config/src/lookup.rs @@ -1,5 +1,6 @@ /// The error when looking up a value. #[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] pub enum Error { #[error(transparent)] ValueMissing(#[from] crate::lookup::existing::Error), @@ -7,9 +8,11 @@ pub enum Error { FailedConversion(E), } +/// pub mod existing { /// The error when looking up a value that doesn't exist. #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] pub enum Error { #[error("The requested section does not exist")] SectionMissing, diff --git a/git-config/src/values/mod.rs b/git-config/src/values/mod.rs index bcd9d1e1115..312411ad865 100644 --- a/git-config/src/values/mod.rs +++ b/git-config/src/values/mod.rs @@ -17,6 +17,7 @@ pub struct Color { /// A potentially empty list of text attributes pub attributes: Vec, } +/// pub mod color; /// Any value that can be interpreted as an integer. diff --git a/git-config/src/values/path.rs b/git-config/src/values/path.rs index 370d79914cd..30810878548 100644 --- a/git-config/src/values/path.rs +++ b/git-config/src/values/path.rs @@ -2,6 +2,7 @@ use crate::Path; use bstr::BStr; use std::borrow::Cow; +/// pub mod interpolate { /// The error returned by [`Path::interpolate()`][crate::Path::interpolate()]. #[derive(Debug, thiserror::Error)] From c95e0b9331282e029ef6188880d11a892ed1b4bf Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 8 Jul 2022 10:21:53 +0800 Subject: [PATCH 097/366] refactor; remove unnecessary docs (#331) --- git-config/src/parse/comment.rs | 15 +---------- git-config/src/parse/event.rs | 15 +---------- git-config/src/parse/mod.rs | 33 ++++++++++-------------- git-config/src/parse/section.rs | 45 +++------------------------------ git-config/src/parse/state.rs | 1 - git-config/src/values/mod.rs | 17 +++++++------ 6 files changed, 27 insertions(+), 99 deletions(-) delete mode 100644 git-config/src/parse/state.rs diff --git a/git-config/src/parse/comment.rs b/git-config/src/parse/comment.rs index 1512ae9f80e..0aede1fc771 100644 --- a/git-config/src/parse/comment.rs +++ b/git-config/src/parse/comment.rs @@ -4,20 +4,7 @@ use std::borrow::Cow; use std::fmt::Display; impl Comment<'_> { - /// Coerces into an owned instance. This differs from the standard [`clone`] - /// implementation as calling clone will _not_ copy the borrowed variant, - /// while this method will. In other words: - /// - /// | Borrow type | `.clone()` | `to_owned()` | - /// | ----------- | ---------- | ------------ | - /// | Borrowed | Borrowed | Owned | - /// | Owned | Owned | Owned | - /// - /// This can be most effectively seen by the differing lifetimes between the - /// two. This method guarantees a `'static` lifetime, while `clone` does - /// not. - /// - /// [`clone`]: Self::clone + /// Turn this instance into a fully owned one with `'static` lifetime. #[must_use] pub fn to_owned(&self) -> Comment<'static> { Comment { diff --git a/git-config/src/parse/event.rs b/git-config/src/parse/event.rs index 63dad9774f0..9f972f18348 100644 --- a/git-config/src/parse/event.rs +++ b/git-config/src/parse/event.rs @@ -12,20 +12,7 @@ impl Event<'_> { self.into() } - /// Coerces into an owned instance. This differs from the standard [`clone`] - /// implementation as calling clone will _not_ copy the borrowed variant, - /// while this method will. In other words: - /// - /// | Borrow type | `.clone()` | `to_owned()` | - /// | ----------- | ---------- | ------------ | - /// | Borrowed | Borrowed | Owned | - /// | Owned | Owned | Owned | - /// - /// This can be most effectively seen by the differing lifetimes between the - /// two. This method guarantees a `'static` lifetime, while `clone` does - /// not. - /// - /// [`clone`]: Self::clone + /// Turn this instance into a fully owned one with `'static` lifetime. #[must_use] pub fn to_owned(&self) -> Event<'static> { match self { diff --git a/git-config/src/parse/mod.rs b/git-config/src/parse/mod.rs index b607372882d..a74f8bef78b 100644 --- a/git-config/src/parse/mod.rs +++ b/git-config/src/parse/mod.rs @@ -12,8 +12,20 @@ use bstr::BStr; use std::{borrow::Cow, hash::Hash}; +mod nom; +pub use self::nom::from_bytes; +/// +pub mod event; +#[path = "events.rs"] +mod events_type; +pub use events_type::{Events, FrontMatterEvents}; +mod comment; +mod error; /// -pub mod state; +pub mod section; + +#[cfg(test)] +pub(crate) mod tests; /// Syntactic events that occurs in the config. Despite all these variants /// holding a [`Cow`] instead over a simple reference, the parser will only emit @@ -61,12 +73,6 @@ pub enum Event<'a> { KeyValueSeparator, } -/// -pub mod event; -#[path = "events.rs"] -mod events_type; -pub use events_type::{Events, FrontMatterEvents}; - /// A parsed section containing the header and the section events. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] pub struct Section<'a> { @@ -76,9 +82,6 @@ pub struct Section<'a> { pub events: section::Events<'a>, } -/// -pub mod section; - /// A parsed comment event containing the comment marker and comment. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] pub struct Comment<'a> { @@ -88,8 +91,6 @@ pub struct Comment<'a> { pub comment: Cow<'a, BStr>, } -mod comment; - /// A parser error reports the one-indexed line number where the parsing error /// occurred, as well as the last parser node and the remaining data to be /// parsed. @@ -99,11 +100,3 @@ pub struct Error { last_attempted_parser: error::ParseNode, parsed_until: bstr::BString, } - -mod error; - -mod nom; -pub use self::nom::from_bytes; - -#[cfg(test)] -pub(crate) mod tests; diff --git a/git-config/src/parse/section.rs b/git-config/src/parse/section.rs index 08880cf3abc..12e15395d26 100644 --- a/git-config/src/parse/section.rs +++ b/git-config/src/parse/section.rs @@ -26,20 +26,7 @@ pub struct Header<'a> { } impl Section<'_> { - /// Coerces into an owned instance. This differs from the standard [`clone`] - /// implementation as calling clone will _not_ copy the borrowed variant, - /// while this method will. In other words: - /// - /// | Borrow type | `.clone()` | `to_owned()` | - /// | ----------- | ---------- | ------------ | - /// | Borrowed | Borrowed | Owned | - /// | Owned | Owned | Owned | - /// - /// This can be most effectively seen by the differing lifetimes between the - /// two. This method guarantees a `'static` lifetime, while `clone` does - /// not. - /// - /// [`clone`]: Self::clone + /// Turn this instance into a fully owned one with `'static` lifetime. #[must_use] pub fn to_owned(&self) -> Section<'static> { Section { @@ -68,20 +55,7 @@ impl Header<'_> { self.into() } - /// Coerces into an owned instance. This differs from the standard [`clone`] - /// implementation as calling clone will _not_ copy the borrowed variant, - /// while this method will. In other words: - /// - /// | Borrow type | `.clone()` | `to_owned()` | - /// | ----------- | ---------- | ------------ | - /// | Borrowed | Borrowed | Owned | - /// | Owned | Owned | Owned | - /// - /// This can be most effectively seen by the differing lifetimes between the - /// two. This method guarantees a `'static` lifetime, while `clone` does - /// not. - /// - /// [`clone`]: Self::clone + /// Turn this instance into a fully owned one with `'static` lifetime. #[must_use] pub fn to_owned(&self) -> Header<'static> { Header { @@ -137,20 +111,7 @@ mod types { pub struct $name<'a>(pub std::borrow::Cow<'a, $cow_inner_type>); impl $name<'_> { - /// Coerces into an owned instance. This differs from the standard - /// [`clone`] implementation as calling clone will _not_ copy the - /// borrowed variant, while this method will. In other words: - /// - /// | Borrow type | `.clone()` | `to_owned()` | - /// | ----------- | ---------- | ------------ | - /// | Borrowed | Borrowed | Owned | - /// | Owned | Owned | Owned | - /// - /// This can be most effectively seen by the differing lifetimes - /// between the two. This method guarantees a `'static` lifetime, - /// while `clone` does not. - /// - /// [`clone`]: Self::clone + /// Turn this instance into a fully owned one with `'static` lifetime. #[must_use] pub fn to_owned(&self) -> $name<'static> { $name(std::borrow::Cow::Owned(self.0.clone().into_owned())) diff --git a/git-config/src/parse/state.rs b/git-config/src/parse/state.rs deleted file mode 100644 index 8b137891791..00000000000 --- a/git-config/src/parse/state.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/git-config/src/values/mod.rs b/git-config/src/values/mod.rs index 312411ad865..081e635031c 100644 --- a/git-config/src/values/mod.rs +++ b/git-config/src/values/mod.rs @@ -1,6 +1,15 @@ mod string; pub use string::String; +/// +pub mod boolean; +/// +pub mod color; +/// +pub mod integer; +/// +pub mod path; + /// Any value that may contain a foreground color, background color, a /// collection of color (text) modifiers, or a combination of any of the /// aforementioned values. @@ -17,8 +26,6 @@ pub struct Color { /// A potentially empty list of text attributes pub attributes: Vec, } -/// -pub mod color; /// Any value that can be interpreted as an integer. /// @@ -38,8 +45,6 @@ pub struct Integer { /// A provided suffix, if any. pub suffix: Option, } -/// -pub mod integer; /// Any value that can be interpreted as a boolean. /// @@ -53,8 +58,6 @@ pub enum Boolean<'a> { True(boolean::True<'a>), False(std::borrow::Cow<'a, bstr::BStr>), } -/// -pub mod boolean; /// Any value that can be interpreted as a file path. /// @@ -64,5 +67,3 @@ pub struct Path<'a> { /// The path string, un-interpolated pub value: std::borrow::Cow<'a, bstr::BStr>, } -/// -pub mod path; From cdfb13f5984c92c8e7f234e7751b66930291b461 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 8 Jul 2022 10:29:20 +0800 Subject: [PATCH 098/366] reduce top-level docs (#331) No need to pitch zero copy, but a good hint at sub-sections still being allocated in any case, even if there is no need for that. --- git-config/src/lib.rs | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/git-config/src/lib.rs b/git-config/src/lib.rs index efc9527d86e..9582313a722 100644 --- a/git-config/src/lib.rs +++ b/git-config/src/lib.rs @@ -3,8 +3,7 @@ //! # `git_config` //! //! This crate is a high performance `git-config` file reader and writer. It -//! exposes a high level API to parse, read, and write [`git-config` files], -//! which are loosely based on the [INI file format]. +//! exposes a high level API to parse, read, and write [`git-config` files]. //! //! This crate has a few primary offerings and various accessory functions. The //! table below gives a brief explanation of all offerings, loosely in order @@ -14,35 +13,17 @@ //! | ------------- | --------------------------------------------------- | ----------------- | //! | [`File`] | Accelerated wrapper for reading and writing values. | On some reads[^1] | //! | [`parse::State`] | Syntactic events for `git-config` files. | Yes | -//! | [`value`] | Wrappers for `git-config` value types. | Yes | +//! | value wrappers | Wrappers for `git-config` value types. | Yes | //! //! This crate also exposes efficient value normalization which unescapes //! characters and removes quotes through the `normalize_*` family of functions, //! located in the [`value`] module. //! -//! # Zero-copy versus zero-alloc -//! -//! We follow [`nom`]'s definition of "zero-copy": -//! -//! > If a parser returns a subset of its input data, it will return a slice of -//! > that input, without copying. -//! -//! Due to the syntax of `git-config`, we must allocate at the parsing level -//! (and thus higher level abstractions must allocate as well) in order to -//! provide a meaningful event stream. That being said, all operations with the -//! parser is still zero-copy. Higher level abstractions may have operations -//! that are zero-copy, but are not guaranteed to do so. -//! -//! However, we intend to be performant as possible, so allocations are -//! limited restricted and we attempt to avoid copying whenever possible. -//! //! [^1]: When read values do not need normalization. //! //! [`git-config` files]: https://git-scm.com/docs/git-config#_configuration_file -//! [INI file format]: https://en.wikipedia.org/wiki/INI_file //! [`File`]: crate::File //! [`parse::State`]: crate::parse::Events -//! [`value`]: crate::value //! [`nom`]: https://github.com/Geal/nom //! //! ## Feature Flags From 910af94fe11bc6e1c270c5512af9124f8a2e0049 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 8 Jul 2022 11:01:43 +0800 Subject: [PATCH 099/366] more correctness for sub-section parsing (#331) --- git-config/src/lib.rs | 5 +++++ git-config/src/parse/nom/mod.rs | 2 +- git-config/src/parse/nom/tests.rs | 36 +++++++++++++++++++++++++++++-- git-config/src/value/normalize.rs | 15 +++---------- 4 files changed, 43 insertions(+), 15 deletions(-) diff --git a/git-config/src/lib.rs b/git-config/src/lib.rs index 9582313a722..f63407fc3c6 100644 --- a/git-config/src/lib.rs +++ b/git-config/src/lib.rs @@ -19,6 +19,11 @@ //! characters and removes quotes through the `normalize_*` family of functions, //! located in the [`value`] module. //! +//! # Known differences to the `git config` specification +//! +//! - Legacy headers like `[section.subsection]` are supposed to be turned into to lower case and compared +//! case-sensitively. We keep its case and compare case-insensitively. +//! //! [^1]: When read values do not need normalization. //! //! [`git-config` files]: https://git-scm.com/docs/git-config#_configuration_file diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index db93f731527..53177c54400 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -185,7 +185,7 @@ fn sub_section(i: &[u8]) -> IResult<&[u8], BString> { let mut buf = BString::default(); while let Some(mut b) = bytes.next() { cursor += 1; - if b == b'\n' { + if b == b'\n' || b == 0 { return Err(nom::Err::Error(NomError { input: &i[cursor..], code: ErrorKind::NonEmpty, diff --git a/git-config/src/parse/nom/tests.rs b/git-config/src/parse/nom/tests.rs index 6778d06a3e1..fd37c5f39c5 100644 --- a/git-config/src/parse/nom/tests.rs +++ b/git-config/src/parse/nom/tests.rs @@ -34,13 +34,17 @@ mod section_headers { section_header(br#"[hello.world]"#).unwrap(), fully_consumed(parsed_section_header("hello", (".", "world"))) ); + assert_eq!( + section_header(br#"[Hello.World]"#).unwrap(), + fully_consumed(parsed_section_header("Hello", (".", "World"))) + ); } #[test] fn empty_legacy_subsection_name() { assert_eq!( - section_header(br#"[hello.]"#).unwrap(), - fully_consumed(parsed_section_header("hello", (".", ""))) + section_header(br#"[hello-world.]"#).unwrap(), + fully_consumed(parsed_section_header("hello-world", (".", ""))) ); } @@ -52,11 +56,39 @@ mod section_headers { ); } + #[test] + fn backslashes_in_subsections_do_not_escape_newlines_or_tabs() { + assert_eq!( + section_header(br#"[hello "single \ \\ \t \n \0"]"#).unwrap(), + fully_consumed(parsed_section_header("hello", (" ", r#"single \ t n 0"#))) + ); + } + #[test] fn newline_in_header() { assert!(section_header(b"[hello\n]").is_err()); } + #[test] + fn newline_in_sub_section() { + assert!(section_header(b"[hello \"hello\n\"]").is_err()); + } + + #[test] + fn null_byt_in_sub_section() { + assert!(section_header(b"[hello \"hello\0\"]").is_err()); + } + + #[test] + fn escaped_newline_in_sub_section() { + assert!(section_header(b"[hello \"hello\\\n\"]").is_err()); + } + + #[test] + fn eof_after_escape_in_sub_section() { + assert!(section_header(b"[hello \"hello\\").is_err()); + } + #[test] fn null_byte_in_header() { assert!(section_header(b"[hello\0]").is_err()); diff --git a/git-config/src/value/normalize.rs b/git-config/src/value/normalize.rs index e42f291f2d6..829093bca10 100644 --- a/git-config/src/value/normalize.rs +++ b/git-config/src/value/normalize.rs @@ -27,25 +27,16 @@ use bstr::{BStr, BString}; /// # use std::borrow::Cow; /// # use bstr::ByteSlice; /// # use git_config::value::normalize_bstr; -/// assert_eq!(normalize_bstr("hello world"), Cow::Borrowed(b"hello world".as_bstr())); +/// assert!(matches!(normalize_bstr("hello world"), Cow::Borrowed(_))); /// ``` /// -/// Fully quoted values are optimized to not need allocations. -/// -/// ``` -/// # use std::borrow::Cow; -/// # use bstr::ByteSlice; -/// # use git_config::value::normalize_bstr; -/// assert_eq!(normalize_bstr("\"hello world\""), Cow::Borrowed(b"hello world".as_bstr())); -/// ``` -/// -/// Quoted values are unwrapped as an owned variant. +/// Internally quoted values are turned into owned variant with quotes removed. /// /// ``` /// # use std::borrow::Cow; /// # use bstr::{BStr, BString}; /// # use git_config::value::{normalize_bstr}; -/// assert_eq!(normalize_bstr("hello \"world\""), Cow::::Owned(BString::from( "hello world" ))); +/// assert_eq!(normalize_bstr("hello \"world\""), Cow::::Owned(BString::from("hello world"))); /// ``` /// /// Escaped quotes are unescaped. From 5a8b111b9a3bba2c01d7d5e32fc58fd8a64b81ad Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 8 Jul 2022 13:25:44 +0800 Subject: [PATCH 100/366] review `git-config::File` docs and rename some internal symbols (#331) --- git-config/src/file/mod.rs | 17 ++++---- git-config/src/file/resolve_includes.rs | 8 +++- git-config/src/file/tests.rs | 52 ++++++++++++++----------- git-config/src/file/utils.rs | 27 +++++++------ git-config/src/file/value.rs | 26 ++++++------- git-config/src/types.rs | 33 +++++++--------- 6 files changed, 85 insertions(+), 78 deletions(-) diff --git a/git-config/src/file/mod.rs b/git-config/src/file/mod.rs index 89203667474..9fbb9eb87ed 100644 --- a/git-config/src/file/mod.rs +++ b/git-config/src/file/mod.rs @@ -36,7 +36,7 @@ impl AddAssign for Size { } } -/// The section ID is a monotonically increasing ID used to refer to sections. +/// The section ID is a monotonically increasing ID used to refer to section bodies. /// This value does not imply any ordering between sections, as new sections /// with higher section IDs may be in between lower ID sections. /// @@ -48,16 +48,19 @@ impl AddAssign for Size { /// words, it's possible that a section may have an ID of 3 but the next section /// has an ID of 5. #[derive(PartialEq, Eq, Hash, Copy, Clone, PartialOrd, Ord, Debug)] -pub(crate) struct SectionId(pub(crate) usize); +pub(crate) struct SectionBodyId(pub(crate) usize); -/// Internal data structure for the section id lookup tree used by -/// [`File`]. Note that order in Vec matters as it represents the order +/// All section body ids referred to by a section name. +/// +/// Note that order in Vec matters as it represents the order /// of section ids with the matched section and name, and is used for precedence /// management. #[derive(PartialEq, Eq, Clone, Debug)] -pub(crate) enum LookupTreeNode<'a> { - Terminal(Vec), - NonTerminal(HashMap, Vec>), +pub(crate) enum SectionBodyIds<'a> { + /// The list of section ids to use for obtaining the section body. + Terminal(Vec), + /// A hashmap from sub-section names to section ids. + NonTerminal(HashMap, Vec>), } /// diff --git a/git-config/src/file/resolve_includes.rs b/git-config/src/file/resolve_includes.rs index 3625ffaa813..6fedcd7afbf 100644 --- a/git-config/src/file/resolve_includes.rs +++ b/git-config/src/file/resolve_includes.rs @@ -8,7 +8,7 @@ use git_ref::Category; use crate::file::from_paths::Options; use crate::{ - file::{from_paths, SectionId}, + file::{from_paths, SectionBodyId}, parse::section, File, }; @@ -87,7 +87,11 @@ fn resolve_includes_recursive( Ok(()) } -fn extract_include_path(target_config: &mut File<'_>, include_paths: &mut Vec>, id: SectionId) { +fn extract_include_path( + target_config: &mut File<'_>, + include_paths: &mut Vec>, + id: SectionBodyId, +) { if let Some(body) = target_config.sections.get(&id) { let paths = body.values(§ion::Key::from("path")); let paths = paths diff --git a/git-config/src/file/tests.rs b/git-config/src/file/tests.rs index 7906a080fd5..02d009e35ae 100644 --- a/git-config/src/file/tests.rs +++ b/git-config/src/file/tests.rs @@ -1,5 +1,5 @@ mod try_from { - use crate::file::{LookupTreeNode, SectionId}; + use crate::file::{SectionBodyId, SectionBodyIds}; use std::borrow::Cow; use std::collections::HashMap; use std::convert::TryFrom; @@ -29,7 +29,7 @@ mod try_from { let mut config = File::try_from("[core]\na=b\nc=d").unwrap(); let expected_separators = { let mut map = HashMap::new(); - map.insert(SectionId(0), section_header("core", None)); + map.insert(SectionBodyId(0), section_header("core", None)); map }; assert_eq!(config.section_headers, expected_separators); @@ -38,7 +38,7 @@ mod try_from { let mut tree = HashMap::new(); tree.insert( section::Name(Cow::Borrowed("core".into())), - vec![LookupTreeNode::Terminal(vec![SectionId(0)])], + vec![SectionBodyIds::Terminal(vec![SectionBodyId(0)])], ); tree }; @@ -46,7 +46,7 @@ mod try_from { let expected_sections = { let mut sections = HashMap::new(); sections.insert( - SectionId(0), + SectionBodyId(0), SectionBody( vec![ newline_event(), @@ -64,7 +64,7 @@ mod try_from { sections }; assert_eq!(config.sections, expected_sections); - assert_eq!(config.section_order.make_contiguous(), &[SectionId(0)]); + assert_eq!(config.section_order.make_contiguous(), &[SectionBodyId(0)]); } #[test] @@ -72,7 +72,7 @@ mod try_from { let mut config = File::try_from("[core.sub]\na=b\nc=d").unwrap(); let expected_separators = { let mut map = HashMap::new(); - map.insert(SectionId(0), section_header("core", (".", "sub"))); + map.insert(SectionBodyId(0), section_header("core", (".", "sub"))); map }; assert_eq!(config.section_headers, expected_separators); @@ -80,10 +80,10 @@ mod try_from { let expected_lookup_tree = { let mut tree = HashMap::new(); let mut inner_tree = HashMap::new(); - inner_tree.insert(Cow::Borrowed("sub".into()), vec![SectionId(0)]); + inner_tree.insert(Cow::Borrowed("sub".into()), vec![SectionBodyId(0)]); tree.insert( section::Name(Cow::Borrowed("core".into())), - vec![LookupTreeNode::NonTerminal(inner_tree)], + vec![SectionBodyIds::NonTerminal(inner_tree)], ); tree }; @@ -91,7 +91,7 @@ mod try_from { let expected_sections = { let mut sections = HashMap::new(); sections.insert( - SectionId(0), + SectionBodyId(0), SectionBody( vec![ newline_event(), @@ -109,7 +109,7 @@ mod try_from { sections }; assert_eq!(config.sections, expected_sections); - assert_eq!(config.section_order.make_contiguous(), &[SectionId(0)]); + assert_eq!(config.section_order.make_contiguous(), &[SectionBodyId(0)]); } #[test] @@ -117,8 +117,8 @@ mod try_from { let mut config = File::try_from("[core]\na=b\nc=d\n[other]e=f").unwrap(); let expected_separators = { let mut map = HashMap::new(); - map.insert(SectionId(0), section_header("core", None)); - map.insert(SectionId(1), section_header("other", None)); + map.insert(SectionBodyId(0), section_header("core", None)); + map.insert(SectionBodyId(1), section_header("other", None)); map }; assert_eq!(config.section_headers, expected_separators); @@ -127,11 +127,11 @@ mod try_from { let mut tree = HashMap::new(); tree.insert( section::Name(Cow::Borrowed("core".into())), - vec![LookupTreeNode::Terminal(vec![SectionId(0)])], + vec![SectionBodyIds::Terminal(vec![SectionBodyId(0)])], ); tree.insert( section::Name(Cow::Borrowed("other".into())), - vec![LookupTreeNode::Terminal(vec![SectionId(1)])], + vec![SectionBodyIds::Terminal(vec![SectionBodyId(1)])], ); tree }; @@ -139,7 +139,7 @@ mod try_from { let expected_sections = { let mut sections = HashMap::new(); sections.insert( - SectionId(0), + SectionBodyId(0), SectionBody( vec![ newline_event(), @@ -156,13 +156,16 @@ mod try_from { ), ); sections.insert( - SectionId(1), + SectionBodyId(1), SectionBody(vec![name_event("e"), Event::KeyValueSeparator, value_event("f")].into()), ); sections }; assert_eq!(config.sections, expected_sections); - assert_eq!(config.section_order.make_contiguous(), &[SectionId(0), SectionId(1)]); + assert_eq!( + config.section_order.make_contiguous(), + &[SectionBodyId(0), SectionBodyId(1)] + ); } #[test] @@ -170,8 +173,8 @@ mod try_from { let mut config = File::try_from("[core]\na=b\nc=d\n[core]e=f").unwrap(); let expected_separators = { let mut map = HashMap::new(); - map.insert(SectionId(0), section_header("core", None)); - map.insert(SectionId(1), section_header("core", None)); + map.insert(SectionBodyId(0), section_header("core", None)); + map.insert(SectionBodyId(1), section_header("core", None)); map }; assert_eq!(config.section_headers, expected_separators); @@ -180,7 +183,7 @@ mod try_from { let mut tree = HashMap::new(); tree.insert( section::Name(Cow::Borrowed("core".into())), - vec![LookupTreeNode::Terminal(vec![SectionId(0), SectionId(1)])], + vec![SectionBodyIds::Terminal(vec![SectionBodyId(0), SectionBodyId(1)])], ); tree }; @@ -188,7 +191,7 @@ mod try_from { let expected_sections = { let mut sections = HashMap::new(); sections.insert( - SectionId(0), + SectionBodyId(0), SectionBody( vec![ newline_event(), @@ -205,12 +208,15 @@ mod try_from { ), ); sections.insert( - SectionId(1), + SectionBodyId(1), SectionBody(vec![name_event("e"), Event::KeyValueSeparator, value_event("f")].into()), ); sections }; assert_eq!(config.sections, expected_sections); - assert_eq!(config.section_order.make_contiguous(), &[SectionId(0), SectionId(1)]); + assert_eq!( + config.section_order.make_contiguous(), + &[SectionBodyId(0), SectionBodyId(1)] + ); } } diff --git a/git-config/src/file/utils.rs b/git-config/src/file/utils.rs index 066c3303ac3..5bd1414e719 100644 --- a/git-config/src/file/utils.rs +++ b/git-config/src/file/utils.rs @@ -2,7 +2,7 @@ use bstr::BStr; use std::collections::HashMap; use crate::{ - file::{LookupTreeNode, MutableSection, SectionBody, SectionId}, + file::{MutableSection, SectionBody, SectionBodyId, SectionBodyIds}, lookup, parse::section, File, @@ -16,7 +16,7 @@ impl<'event> File<'event> { header: section::Header<'event>, section: SectionBody<'event>, ) -> MutableSection<'_, 'event> { - let new_section_id = SectionId(self.section_id_counter); + let new_section_id = SectionBodyId(self.section_id_counter); self.section_headers.insert(new_section_id, header.clone()); self.sections.insert(new_section_id, section); let lookup = self.section_lookup_tree.entry(header.name).or_default(); @@ -24,10 +24,9 @@ impl<'event> File<'event> { let mut found_node = false; if let Some(subsection_name) = header.subsection_name { for node in lookup.iter_mut() { - if let LookupTreeNode::NonTerminal(subsection) = node { + if let SectionBodyIds::NonTerminal(subsections) = node { found_node = true; - subsection - // Clones the cow, not the inner borrowed str. + subsections .entry(subsection_name.clone()) .or_default() .push(new_section_id); @@ -37,18 +36,18 @@ impl<'event> File<'event> { if !found_node { let mut map = HashMap::new(); map.insert(subsection_name, vec![new_section_id]); - lookup.push(LookupTreeNode::NonTerminal(map)); + lookup.push(SectionBodyIds::NonTerminal(map)); } } else { for node in lookup.iter_mut() { - if let LookupTreeNode::Terminal(vec) = node { + if let SectionBodyIds::Terminal(vec) = node { found_node = true; vec.push(new_section_id); break; } } if !found_node { - lookup.push(LookupTreeNode::Terminal(vec![new_section_id])); + lookup.push(SectionBodyIds::Terminal(vec![new_section_id])); } } self.section_order.push_back(new_section_id); @@ -61,7 +60,7 @@ impl<'event> File<'event> { &self, section_name: impl Into>, subsection_name: Option<&str>, - ) -> Result, lookup::existing::Error> { + ) -> Result, lookup::existing::Error> { let section_name = section_name.into(); let section_ids = self .section_lookup_tree @@ -74,14 +73,14 @@ impl<'event> File<'event> { if let Some(subsection_name) = subsection_name { let subsection_name: &BStr = subsection_name.into(); for node in section_ids { - if let LookupTreeNode::NonTerminal(subsection_lookup) = node { + if let SectionBodyIds::NonTerminal(subsection_lookup) = node { maybe_ids = subsection_lookup.get(subsection_name); break; } } } else { for node in section_ids { - if let LookupTreeNode::Terminal(subsection_lookup) = node { + if let SectionBodyIds::Terminal(subsection_lookup) = node { maybe_ids = Some(subsection_lookup); break; } @@ -95,7 +94,7 @@ impl<'event> File<'event> { pub(crate) fn section_ids_by_name<'a>( &self, section_name: impl Into>, - ) -> Result, lookup::existing::Error> { + ) -> Result, lookup::existing::Error> { let section_name = section_name.into(); self.section_lookup_tree .get(§ion_name) @@ -103,8 +102,8 @@ impl<'event> File<'event> { lookup .iter() .flat_map(|node| match node { - LookupTreeNode::Terminal(v) => v.clone(), - LookupTreeNode::NonTerminal(v) => v.values().flatten().copied().collect(), + SectionBodyIds::Terminal(v) => v.clone(), + SectionBodyIds::NonTerminal(v) => v.values().flatten().copied().collect(), }) .collect() }) diff --git a/git-config/src/file/value.rs b/git-config/src/file/value.rs index fd341db07e8..42ae1ea75c9 100644 --- a/git-config/src/file/value.rs +++ b/git-config/src/file/value.rs @@ -4,7 +4,7 @@ use std::{borrow::Cow, collections::HashMap, ops::DerefMut}; use crate::{ file::{ section::{MutableSection, SectionBody}, - Index, SectionId, Size, + Index, SectionBodyId, Size, }, lookup, parse::{section, Event}, @@ -81,12 +81,12 @@ impl<'borrow, 'lookup, 'event> MutableValue<'borrow, 'lookup, 'event> { /// Internal data structure for [`MutableMultiValue`] #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] pub(crate) struct EntryData { - section_id: SectionId, + section_id: SectionBodyId, offset_index: usize, } impl EntryData { - pub(crate) const fn new(section_id: SectionId, offset_index: usize) -> Self { + pub(crate) const fn new(section_id: SectionBodyId, offset_index: usize) -> Self { Self { section_id, offset_index, @@ -106,7 +106,7 @@ impl EntryData { #[allow(clippy::module_name_repetitions)] #[derive(PartialEq, Eq, Debug)] pub struct MutableMultiValue<'borrow, 'lookup, 'event> { - section: &'borrow mut HashMap>, + section: &'borrow mut HashMap>, key: section::Key<'lookup>, /// Each entry data struct provides sufficient information to index into /// [`Self::offsets`]. This layer of indirection is used for users to index @@ -115,15 +115,15 @@ pub struct MutableMultiValue<'borrow, 'lookup, 'event> { /// Each offset represents the size of a event slice and whether or not the /// event slice is significant or not. This is used to index into the /// actual section. - offsets: HashMap>, + offsets: HashMap>, } impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { pub(crate) fn new( - section: &'borrow mut HashMap>, + section: &'borrow mut HashMap>, key: section::Key<'lookup>, indices_and_sizes: Vec, - offsets: HashMap>, + offsets: HashMap>, ) -> Self { Self { section, @@ -319,9 +319,9 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { fn set_value_inner<'a: 'event>( key: §ion::Key<'lookup>, - offsets: &mut HashMap>, + offsets: &mut HashMap>, section: &mut SectionBody<'event>, - section_id: SectionId, + section_id: SectionBodyId, offset_index: usize, input: Cow<'a, BStr>, ) { @@ -382,8 +382,8 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { // SectionId is the same size as a reference, which means it's just as // efficient passing in a value instead of a reference. fn index_and_size( - offsets: &'lookup HashMap>, - section_id: SectionId, + offsets: &'lookup HashMap>, + section_id: SectionBodyId, offset_index: usize, ) -> (usize, usize) { offsets @@ -400,8 +400,8 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { // SectionId is the same size as a reference, which means it's just as // efficient passing in a value instead of a reference. fn set_offset( - offsets: &mut HashMap>, - section_id: SectionId, + offsets: &mut HashMap>, + section_id: SectionBodyId, offset_index: usize, value: usize, ) { diff --git a/git-config/src/types.rs b/git-config/src/types.rs index d96462a99bb..09195bfcab2 100644 --- a/git-config/src/types.rs +++ b/git-config/src/types.rs @@ -1,7 +1,7 @@ use std::collections::{HashMap, VecDeque}; use crate::{ - file::{LookupTreeNode, SectionBody, SectionId}, + file::{SectionBody, SectionBodyId, SectionBodyIds}, parse::section, }; @@ -10,13 +10,12 @@ use crate::{ /// This is the full-featured implementation that can deserialize, serialize, /// and edit `git-config` files without loss of whitespace or comments. /// -/// # Multivar behavior +/// # 'multivar' behavior /// /// `git` is flexible enough to allow users to set a key multiple times in /// any number of identically named sections. When this is the case, the key -/// is known as a "multivar". In this case, `raw_value` follows the -/// "last one wins" approach that `git-config` internally uses for multivar -/// resolution. +/// is known as a _"multivar"_. In this case, [`raw_value()`] follows the +/// "last one wins". /// /// Concretely, the following config has a multivar, `a`, with the values /// of `b`, `c`, and `d`, while `e` is a single variable with the value @@ -31,7 +30,7 @@ use crate::{ /// e = f g h /// ``` /// -/// Calling methods that fetch or set only one value (such as [`raw_value`]) +/// Calling methods that fetch or set only one value (such as [`raw_value()`]) /// key `a` with the above config will fetch `d` or replace `d`, since the last /// valid config key/value pair is `a = d`: /// @@ -39,32 +38,28 @@ use crate::{ /// # use std::borrow::Cow; /// # use std::convert::TryFrom; /// # let git_config = git_config::File::try_from("[core]a=b\n[core]\na=c\na=d").unwrap(); -/// assert_eq!(git_config.raw_value("core", None, "a").unwrap(), Cow::Borrowed("d".as_bytes())); +/// assert_eq!(git_config.raw_value("core", None, "a").unwrap().as_ref(), "d"); /// ``` /// /// Consider the `multi` variants of the methods instead, if you want to work -/// with all values instead. +/// with all values. /// -/// [`raw_value`]: Self::raw_value +/// [`raw_value()`]: Self::raw_value #[derive(PartialEq, Eq, Clone, Debug, Default)] pub struct File<'event> { /// The list of events that occur before an actual section. Since a /// `git-config` file prohibits global values, this vec is limited to only /// comment, newline, and whitespace events. pub(crate) frontmatter_events: crate::parse::FrontMatterEvents<'event>, - /// Section name and subsection name to section id lookup tree. This is - /// effectively a n-tree (opposed to a binary tree) that can have a height - /// of at most three (including an implicit root node). - pub(crate) section_lookup_tree: HashMap, Vec>>, - /// SectionId to section mapping. The value of this HashMap contains actual - /// events. - /// + /// Section name and subsection name to section id lookup tree. + pub(crate) section_lookup_tree: HashMap, Vec>>, /// This indirection with the SectionId as the key is critical to flexibly /// supporting `git-config` sections, as duplicated keys are permitted. - pub(crate) sections: HashMap>, - pub(crate) section_headers: HashMap>, + pub(crate) sections: HashMap>, + /// A way to reconstruct the complete section being a header and a body. + pub(crate) section_headers: HashMap>, /// Internal monotonically increasing counter for section ids. pub(crate) section_id_counter: usize, /// Section order for output ordering. - pub(crate) section_order: VecDeque, + pub(crate) section_order: VecDeque, } From 117401ddb9dea1d78b867ddbafe57c2b37ec10f4 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 8 Jul 2022 16:26:39 +0800 Subject: [PATCH 101/366] found failing test with complex multi-line value (#331) there is also some questions about whether normalization is done correctly. --- git-config/src/parse/nom/mod.rs | 4 +-- git-config/src/parse/nom/tests.rs | 19 +++++++++- git-config/tests/file/access/read_only.rs | 43 +++++++++++++++++++++++ 3 files changed, 63 insertions(+), 3 deletions(-) diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index 53177c54400..9a3965a34d7 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -261,8 +261,8 @@ fn config_name(i: &[u8]) -> IResult<&[u8], &BStr> { })); } - let (i, v) = take_while(|c: u8| c.is_ascii_alphanumeric() || c == b'-')(i)?; - Ok((i, v.as_bstr())) + let (i, name) = take_while(|c: u8| c.is_ascii_alphanumeric() || c == b'-')(i)?; + Ok((i, name.as_bstr())) } fn config_value<'a>(i: &'a [u8], receive_event: &mut impl FnMut(Event<'a>)) -> IResult<&'a [u8], ()> { diff --git a/git-config/src/parse/nom/tests.rs b/git-config/src/parse/nom/tests.rs index fd37c5f39c5..d776e2ea6d1 100644 --- a/git-config/src/parse/nom/tests.rs +++ b/git-config/src/parse/nom/tests.rs @@ -94,6 +94,16 @@ mod section_headers { assert!(section_header(b"[hello\0]").is_err()); } + #[test] + fn invalid_characters_in_section() { + assert!(section_header(b"[$]").is_err()); + } + #[test] + fn invalid_characters_in_legacy_sub_section() { + assert!(section_header(b"[hello.$]").is_err()); + assert!(section_header(b"[hello. world]").is_err()); + } + #[test] fn right_brace_in_subsection_name() { assert_eq!( @@ -106,6 +116,7 @@ mod section_headers { mod config_name { use super::config_name; use crate::parse::tests::util::fully_consumed; + use nom::combinator::all_consuming; #[test] fn just_name() { @@ -118,6 +129,12 @@ mod config_name { assert!(config_name(b"-aaa").is_err()); } + #[test] + fn only_a_subset_of_characters_is_allowed() { + assert!(all_consuming(config_name)(b"Name$_").is_err()); + assert!(all_consuming(config_name)(b"other#").is_err()); + } + #[test] fn cannot_be_empty() { assert!(config_name(b"").is_err()); @@ -537,7 +554,7 @@ mod value_no_continuation { } #[test] - fn garbage_after_continution_is_err() { + fn garbage_after_continuation_is_err() { assert!(value_impl(b"hello \\afwjdls", &mut Default::default()).is_err()); } diff --git a/git-config/tests/file/access/read_only.rs b/git-config/tests/file/access/read_only.rs index d7dd165a185..bbed6a0db12 100644 --- a/git-config/tests/file/access/read_only.rs +++ b/git-config/tests/file/access/read_only.rs @@ -237,3 +237,46 @@ fn sections_by_name() { } ); } + +#[test] +fn multi_line_value_plain() { + let config = r#" +[alias] + save = !git status \ + && git add -A \ + && git commit -m \"$1\" \ + && git push -f \ + && git log -1 \ + && : # comment + "#; + + let config = File::try_from(config).unwrap(); + + let expected = r#"!git status && git add -A && git commit -m "$1" && git push -f && git log -1 && :"#; + assert_eq!( + config.raw_value("alias", None, "save").unwrap().as_ref(), + expected, + "only the un-normalized original value currently matches git's result" + ); + assert_ne!(config.string("alias", None, "save").unwrap().as_ref(), expected); +} + +#[test] +#[ignore] +fn multi_line_value_outer_quotes() { + let config = r#" +[alias] + save = "!f() { \ + git status; \ + git add -A; \ + git commit -m "$1"; \ + git push -f; \ + git log -1; \ + }; \ + f; \ + unset f" +"#; + let config = File::try_from(config).unwrap(); + let expected = r#"!f() { git status; git add -A; git commit -m $1; git push -f; git log -1; }; f; unset f"#; + assert_eq!(config.raw_value("alias", None, "save").unwrap().as_ref(), expected); +} From 3c2932167aa45a89974be79123932bc964fe3ea9 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 8 Jul 2022 16:53:38 +0800 Subject: [PATCH 102/366] another failing test pointing at issues with normalization/escaping in parser (#331) --- git-config/src/parse/error.rs | 6 ++- git-config/src/parse/nom/mod.rs | 6 +-- git-config/tests/file/access/read_only.rs | 64 +++++++++++++++-------- 3 files changed, 50 insertions(+), 26 deletions(-) diff --git a/git-config/src/parse/error.rs b/git-config/src/parse/error.rs index f43824a21c1..e90ccdf514e 100644 --- a/git-config/src/parse/error.rs +++ b/git-config/src/parse/error.rs @@ -6,14 +6,16 @@ use std::fmt::Display; #[derive(PartialEq, Debug, Clone, Copy)] pub(crate) enum ParseNode { SectionHeader, - ConfigName, + Name, + Value, } impl Display for ParseNode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::SectionHeader => write!(f, "section header"), - Self::ConfigName => write!(f, "config name"), + Self::Name => write!(f, "name"), + Self::Value => write!(f, "value"), } } } diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index 9a3965a34d7..05c82f4624c 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -228,8 +228,7 @@ fn section_body<'a>( node: &mut ParseNode, receive_event: &mut impl FnMut(Event<'a>), ) -> IResult<&'a [u8], ()> { - // maybe need to check for [ here - *node = ParseNode::ConfigName; + *node = ParseNode::Name; let (i, name) = config_name(i)?; receive_event(Event::SectionKey(section::Key(Cow::Borrowed(name)))); @@ -240,6 +239,7 @@ fn section_body<'a>( receive_event(Event::Whitespace(Cow::Borrowed(whitespace))); } + *node = ParseNode::Value; let (i, _) = config_value(i, receive_event)?; Ok((i, ())) } @@ -337,7 +337,7 @@ fn value_impl<'a>(i: &'a [u8], receive_event: &mut impl FnMut(Event<'a>)) -> IRe parsed_index = i.len(); } else { // Didn't parse anything at all, newline straight away. - receive_event(Event::Value(Cow::Owned(BString::default()))); + receive_event(Event::Value(Cow::Borrowed("".into()))); receive_event(Event::Newline(Cow::Borrowed("\n".into()))); return Ok(( i.get(1..).ok_or(nom::Err::Error(NomError { diff --git a/git-config/tests/file/access/read_only.rs b/git-config/tests/file/access/read_only.rs index bbed6a0db12..5cd1536a2ba 100644 --- a/git-config/tests/file/access/read_only.rs +++ b/git-config/tests/file/access/read_only.rs @@ -23,29 +23,30 @@ fn get_value_for_all_provided_values() -> crate::Result { location-quoted = "~/quoted" "#; - let file = git_config::parse::Events::from_bytes_owned(config.as_bytes(), None).map(File::from)?; + let config = git_config::parse::Events::from_bytes_owned(config.as_bytes(), None).map(File::from)?; assert_eq!( - file.value::("core", None, "bool-explicit")?, + config.value::("core", None, "bool-explicit")?, Boolean::False(Cow::Borrowed("false".into())) ); - assert!(!file.boolean("core", None, "bool-explicit").expect("exists")?); + assert!(!config.boolean("core", None, "bool-explicit").expect("exists")?); assert_eq!( - file.value::("core", None, "bool-implicit")?, + config.value::("core", None, "bool-implicit")?, Boolean::True(True::Implicit) ); assert_eq!( - file.try_value::("core", None, "bool-implicit") + config + .try_value::("core", None, "bool-implicit") .expect("exists")?, Boolean::True(True::Implicit) ); - assert!(file.boolean("core", None, "bool-implicit").expect("present")?); - assert_eq!(file.try_value::("doesnt", None, "exist"), None); + assert!(config.boolean("core", None, "bool-implicit").expect("present")?); + assert_eq!(config.try_value::("doesnt", None, "exist"), None); assert_eq!( - file.value::("core", None, "integer-no-prefix")?, + config.value::("core", None, "integer-no-prefix")?, Integer { value: 10, suffix: None @@ -53,7 +54,7 @@ fn get_value_for_all_provided_values() -> crate::Result { ); assert_eq!( - file.value::("core", None, "integer-no-prefix")?, + config.value::("core", None, "integer-no-prefix")?, Integer { value: 10, suffix: None @@ -61,7 +62,7 @@ fn get_value_for_all_provided_values() -> crate::Result { ); assert_eq!( - file.value::("core", None, "integer-prefix")?, + config.value::("core", None, "integer-prefix")?, Integer { value: 10, suffix: Some(integer::Suffix::Gibi), @@ -69,7 +70,7 @@ fn get_value_for_all_provided_values() -> crate::Result { ); assert_eq!( - file.value::("core", None, "color")?, + config.value::("core", None, "color")?, Color { foreground: Some(color::Name::BrightGreen), background: Some(color::Name::Red), @@ -78,7 +79,7 @@ fn get_value_for_all_provided_values() -> crate::Result { ); { - let string = file.value::>("core", None, "other")?; + let string = config.value::>("core", None, "other")?; assert_eq!(string, cow_str("hello world")); assert!( matches!(string, Cow::Borrowed(_)), @@ -87,14 +88,14 @@ fn get_value_for_all_provided_values() -> crate::Result { } assert_eq!( - file.value::("core", None, "other-quoted")?, + config.value::("core", None, "other-quoted")?, String { value: cow_str("hello world") } ); { - let strings = file.multi_value::("core", None, "other-quoted")?; + let strings = config.multi_value::("core", None, "other-quoted")?; assert_eq!( strings, vec![ @@ -111,23 +112,23 @@ fn get_value_for_all_provided_values() -> crate::Result { } { - let cow = file.string("core", None, "other").expect("present"); + let cow = config.string("core", None, "other").expect("present"); assert_eq!(cow.as_ref(), "hello world"); assert!(matches!(cow, Cow::Borrowed(_))); } assert_eq!( - file.string("core", None, "other-quoted").expect("present").as_ref(), + config.string("core", None, "other-quoted").expect("present").as_ref(), "hello world" ); { - let strings = file.strings("core", None, "other-quoted").expect("present"); + let strings = config.strings("core", None, "other-quoted").expect("present"); assert_eq!(strings, vec![cow_str("hello"), cow_str("hello world")]); assert!(matches!(strings[0], Cow::Borrowed(_))); assert!(matches!(strings[1], Cow::Borrowed(_))); } { - let actual = file.value::("core", None, "location")?; + let actual = config.value::("core", None, "location")?; assert_eq!(&*actual, "~/tmp", "no interpolation occurs when querying a path"); let home = std::env::current_dir()?; @@ -136,13 +137,13 @@ fn get_value_for_all_provided_values() -> crate::Result { assert_eq!(actual.interpolate(None, home.as_path().into()).unwrap(), expected); } - let actual = file.path("core", None, "location").expect("present"); + let actual = config.path("core", None, "location").expect("present"); assert_eq!(&*actual, "~/tmp"); - let actual = file.path("core", None, "location-quoted").expect("present"); + let actual = config.path("core", None, "location-quoted").expect("present"); assert_eq!(&*actual, "~/quoted"); - let actual = file.value::("core", None, "location-quoted")?; + let actual = config.value::("core", None, "location-quoted")?; assert_eq!(&*actual, "~/quoted", "but the path is unquoted"); Ok(()) @@ -261,6 +262,27 @@ fn multi_line_value_plain() { assert_ne!(config.string("alias", None, "save").unwrap().as_ref(), expected); } +#[test] +#[ignore] +fn complex_quoted_values() { + let config = r#" + [core] + escape-sequence = "hi\nho\tthere" + # escape-sequence = "hi\nho\tthere\b" # <- \b isn't allowed even though it should +"#; + let config = File::try_from(config).unwrap(); + + assert_eq!( + config.raw_value("core", None, "escape-sequence").unwrap().as_ref(), + "hi\\nho\\tthere" + ); + assert_eq!( + config.string("core", None, "escape-sequence").unwrap().as_ref(), + "hi\nho\tthere", + "normalization is what resolves these values" + ); +} + #[test] #[ignore] fn multi_line_value_outer_quotes() { From 199e5461cb85b11ce0b9a0e727fab40a49b78456 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 8 Jul 2022 17:10:32 +0800 Subject: [PATCH 103/366] allow backspaces in value parser (#331) They literally delete the previous characters during normalization, once implemented. --- git-config/src/parse/nom/mod.rs | 2 +- git-config/src/parse/nom/tests.rs | 5 +++++ git-config/tests/file/access/read_only.rs | 7 +++---- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index 05c82f4624c..cd031b65901 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -306,7 +306,7 @@ fn value_impl<'a>(i: &'a [u8], receive_event: &mut impl FnMut(Event<'a>)) -> IRe offset = index + 1; parsed_index = 0; } - b't' | b'\\' | b'n' | b'"' => (), + b'n' | b't' | b'\\' | b'b' | b'"' => (), _ => { return Err(nom::Err::Error(NomError { input: i, diff --git a/git-config/src/parse/nom/tests.rs b/git-config/src/parse/nom/tests.rs index d776e2ea6d1..5d1ef871722 100644 --- a/git-config/src/parse/nom/tests.rs +++ b/git-config/src/parse/nom/tests.rs @@ -558,6 +558,11 @@ mod value_no_continuation { assert!(value_impl(b"hello \\afwjdls", &mut Default::default()).is_err()); } + #[test] + fn invalid_escape() { + assert!(value_impl(br#"\x"#, &mut Default::default()).is_err()); + } + #[test] fn incomplete_quote() { assert!(value_impl(br#"hello "world"#, &mut Default::default()).is_err()); diff --git a/git-config/tests/file/access/read_only.rs b/git-config/tests/file/access/read_only.rs index 5cd1536a2ba..d1890cbec9e 100644 --- a/git-config/tests/file/access/read_only.rs +++ b/git-config/tests/file/access/read_only.rs @@ -267,18 +267,17 @@ fn multi_line_value_plain() { fn complex_quoted_values() { let config = r#" [core] - escape-sequence = "hi\nho\tthere" - # escape-sequence = "hi\nho\tthere\b" # <- \b isn't allowed even though it should + escape-sequence = "hi\nho\tthere\bi" "#; let config = File::try_from(config).unwrap(); assert_eq!( config.raw_value("core", None, "escape-sequence").unwrap().as_ref(), - "hi\\nho\\tthere" + "hi\\nho\\tthere\\b" ); assert_eq!( config.string("core", None, "escape-sequence").unwrap().as_ref(), - "hi\nho\tthere", + "hi\nho\ttheri", "normalization is what resolves these values" ); } From 637fe8fca2ce36e07ad671a4454da512b709045c Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 8 Jul 2022 17:16:59 +0800 Subject: [PATCH 104/366] another normalization case (#331) It's a bit high-level, but probably a good spot to validate parsing + normalization interplay. --- git-config/tests/file/access/read_only.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/git-config/tests/file/access/read_only.rs b/git-config/tests/file/access/read_only.rs index d1890cbec9e..08024b1d6b1 100644 --- a/git-config/tests/file/access/read_only.rs +++ b/git-config/tests/file/access/read_only.rs @@ -284,7 +284,7 @@ fn complex_quoted_values() { #[test] #[ignore] -fn multi_line_value_outer_quotes() { +fn multi_line_value_outer_quotes_unescaped_inner_quotes() { let config = r#" [alias] save = "!f() { \ @@ -301,3 +301,23 @@ fn multi_line_value_outer_quotes() { let expected = r#"!f() { git status; git add -A; git commit -m $1; git push -f; git log -1; }; f; unset f"#; assert_eq!(config.raw_value("alias", None, "save").unwrap().as_ref(), expected); } + +#[test] +#[ignore] +fn multi_line_value_outer_quotes_escaped_inner_quotes() { + let config = r#" +[alias] + save = "!f() { \ + git status; \ + git add -A; \ + git commit -m \"$1\"; \ + git push -f; \ + git log -1; \ + }; \ + f; \ + unset f" +"#; + let config = File::try_from(config).unwrap(); + let expected = r#"!f() { git status; git add -A; git commit -m "$1"; git push -f; git log -1; }; f; unset f"#; + assert_eq!(config.raw_value("alias", None, "save").unwrap().as_ref(), expected); +} From 7474997216df2616a034fb9adc0938590f3ab7ed Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 8 Jul 2022 17:23:42 +0800 Subject: [PATCH 105/366] prepare for copy-on-write subsections (#331) --- git-config/src/parse/nom/mod.rs | 6 +++--- git-config/src/parse/section.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index cd031b65901..6db0e4b32b4 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -173,12 +173,12 @@ fn section_header(i: &[u8]) -> IResult<&[u8], section::Header<'_>> { section::Header { name: section::Name(Cow::Borrowed(name)), separator: Some(Cow::Borrowed(whitespace)), - subsection_name: subsection_name.map(Cow::Owned), + subsection_name, }, )) } -fn sub_section(i: &[u8]) -> IResult<&[u8], BString> { +fn sub_section(i: &[u8]) -> IResult<&[u8], Cow<'_, BStr>> { let mut cursor = 0; let mut bytes = i.iter().copied(); let mut found_terminator = false; @@ -220,7 +220,7 @@ fn sub_section(i: &[u8]) -> IResult<&[u8], BString> { })); } - Ok((&i[cursor - 1..], buf)) + Ok((&i[cursor - 1..], buf.into())) } fn section_body<'a>( diff --git a/git-config/src/parse/section.rs b/git-config/src/parse/section.rs index 12e15395d26..91781c9e0b7 100644 --- a/git-config/src/parse/section.rs +++ b/git-config/src/parse/section.rs @@ -77,7 +77,7 @@ impl Display for Header<'_> { if v.as_ref() == "." { subsection_name.fmt(f)?; } else { - write!(f, "\"{}\"", subsection_name)?; + write!(f, "\"{}\"", subsection_name)?; // TODO: proper escaping of special characters } } From 25b9760f9a6a79c6e28393f032150e37d5ae831e Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 8 Jul 2022 17:43:55 +0800 Subject: [PATCH 106/366] zero-copy for section names (#331) This includes a note saying that we currently don't escape them correctly when writing them. --- git-config/src/parse/nom/mod.rs | 17 ++++++++++++++--- git-config/src/parse/nom/tests.rs | 19 +++++++++++++++++++ git-config/tests/parse/mod.rs | 4 ++-- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index 6db0e4b32b4..354d57e6790 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -179,10 +179,20 @@ fn section_header(i: &[u8]) -> IResult<&[u8], section::Header<'_>> { } fn sub_section(i: &[u8]) -> IResult<&[u8], Cow<'_, BStr>> { + let (rest, (found_escape, consumed)) = sub_section_delegate(i, &mut |_| ())?; + if found_escape { + let mut buf = BString::default(); + sub_section_delegate(i, &mut |b| buf.push_byte(b)).map(|(i, _)| (i, buf.into())) + } else { + Ok((rest, i[..consumed].as_bstr().into())) + } +} + +fn sub_section_delegate<'a>(i: &'a [u8], push_byte: &mut dyn FnMut(u8)) -> IResult<&'a [u8], (bool, usize)> { let mut cursor = 0; let mut bytes = i.iter().copied(); let mut found_terminator = false; - let mut buf = BString::default(); + let mut found_escape = false; while let Some(mut b) = bytes.next() { cursor += 1; if b == b'\n' || b == 0 { @@ -202,6 +212,7 @@ fn sub_section(i: &[u8]) -> IResult<&[u8], Cow<'_, BStr>> { code: ErrorKind::NonEmpty, }) })?; + found_escape = true; cursor += 1; if b == b'\n' { return Err(nom::Err::Error(NomError { @@ -210,7 +221,7 @@ fn sub_section(i: &[u8]) -> IResult<&[u8], Cow<'_, BStr>> { })); } } - buf.push_byte(b); + push_byte(b); } if !found_terminator { @@ -220,7 +231,7 @@ fn sub_section(i: &[u8]) -> IResult<&[u8], Cow<'_, BStr>> { })); } - Ok((&i[cursor - 1..], buf.into())) + Ok((&i[cursor - 1..], (found_escape, cursor - 1))) } fn section_body<'a>( diff --git a/git-config/src/parse/nom/tests.rs b/git-config/src/parse/nom/tests.rs index 5d1ef871722..d67fa1746fa 100644 --- a/git-config/src/parse/nom/tests.rs +++ b/git-config/src/parse/nom/tests.rs @@ -113,6 +113,25 @@ mod section_headers { } } +mod sub_section { + use super::sub_section; + use std::borrow::Cow; + + #[test] + fn zero_copy_simple() { + let actual = sub_section(b"name\"").unwrap().1; + assert_eq!(actual.as_ref(), "name"); + assert!(matches!(actual, Cow::Borrowed(_))); + } + + #[test] + fn escapes_need_allocation() { + let actual = sub_section(br#"\x\t\n\0\\\"""#).unwrap().1; + assert_eq!(actual.as_ref(), r#"xtn0\""#); + assert!(matches!(actual, Cow::Owned(_))); + } +} + mod config_name { use super::config_name; use crate::parse::tests::util::fully_consumed; diff --git a/git-config/tests/parse/mod.rs b/git-config/tests/parse/mod.rs index 25ee33945a5..e1de2a74835 100644 --- a/git-config/tests/parse/mod.rs +++ b/git-config/tests/parse/mod.rs @@ -236,12 +236,12 @@ fn error() { let input = "[core] a=b\n 4a=3"; assert_eq!( Events::from_str(input).unwrap_err().to_string(), - "Got an unexpected token on line 2 while trying to parse a config name: '4a=3'" + "Got an unexpected token on line 2 while trying to parse a name: '4a=3'" ); let input = "[core] a=b\n =3"; assert_eq!( Events::from_str(input).unwrap_err().to_string(), - "Got an unexpected token on line 2 while trying to parse a config name: '=3'" + "Got an unexpected token on line 2 while trying to parse a name: '=3'" ); let input = "[core"; assert_eq!( From 1ea919d5ff81ab7b01b8201386ef63c7e081b537 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 8 Jul 2022 20:10:13 +0800 Subject: [PATCH 107/366] fix: count newlines (for error display) in multi-line values as well (#331) --- git-config/src/parse/nom/mod.rs | 26 +++++++++++++++----------- git-config/src/parse/nom/tests.rs | 10 +++++----- git-config/tests/parse/mod.rs | 10 ++++++++++ 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index 354d57e6790..29ddcbd2a88 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -117,9 +117,10 @@ fn section<'a>( } } - if let Ok((new_i, _)) = section_body(i, node, receive_event) { + if let Ok((new_i, new_newlines)) = section_body(i, node, receive_event) { if old_i != new_i { i = new_i; + newlines += new_newlines; } } @@ -238,7 +239,7 @@ fn section_body<'a>( i: &'a [u8], node: &mut ParseNode, receive_event: &mut impl FnMut(Event<'a>), -) -> IResult<&'a [u8], ()> { +) -> IResult<&'a [u8], usize> { *node = ParseNode::Name; let (i, name) = config_name(i)?; @@ -251,8 +252,8 @@ fn section_body<'a>( } *node = ParseNode::Value; - let (i, _) = config_value(i, receive_event)?; - Ok((i, ())) + let (i, newlines) = config_value(i, receive_event)?; + Ok((i, newlines)) } /// Parses the config name of a config pair. Assumes the input has already been @@ -276,26 +277,27 @@ fn config_name(i: &[u8]) -> IResult<&[u8], &BStr> { Ok((i, name.as_bstr())) } -fn config_value<'a>(i: &'a [u8], receive_event: &mut impl FnMut(Event<'a>)) -> IResult<&'a [u8], ()> { +fn config_value<'a>(i: &'a [u8], receive_event: &mut impl FnMut(Event<'a>)) -> IResult<&'a [u8], usize> { if let (i, Some(_)) = opt(char('='))(i)? { receive_event(Event::KeyValueSeparator); let (i, whitespace) = opt(take_spaces)(i)?; if let Some(whitespace) = whitespace { receive_event(Event::Whitespace(Cow::Borrowed(whitespace))); } - let (i, _) = value_impl(i, receive_event)?; - Ok((i, ())) + let (i, newlines) = value_impl(i, receive_event)?; + Ok((i, newlines)) } else { receive_event(Event::Value(Cow::Borrowed("".into()))); - Ok((i, ())) + Ok((i, 0)) } } /// Handles parsing of known-to-be values. This function handles both single /// line values as well as values that are continuations. -fn value_impl<'a>(i: &'a [u8], receive_event: &mut impl FnMut(Event<'a>)) -> IResult<&'a [u8], ()> { +fn value_impl<'a>(i: &'a [u8], receive_event: &mut impl FnMut(Event<'a>)) -> IResult<&'a [u8], usize> { let mut parsed_index: usize = 0; let mut offset: usize = 0; + let mut newlines = 0; let mut was_prev_char_escape_char = false; // This is required to ignore comment markers if they're in a quote. @@ -316,6 +318,7 @@ fn value_impl<'a>(i: &'a [u8], receive_event: &mut impl FnMut(Event<'a>)) -> IRe receive_event(Event::Newline(Cow::Borrowed(i[index..=index].as_bstr()))); offset = index + 1; parsed_index = 0; + newlines += 1; } b'n' | b't' | b'\\' | b'b' | b'"' => (), _ => { @@ -350,12 +353,13 @@ fn value_impl<'a>(i: &'a [u8], receive_event: &mut impl FnMut(Event<'a>)) -> IRe // Didn't parse anything at all, newline straight away. receive_event(Event::Value(Cow::Borrowed("".into()))); receive_event(Event::Newline(Cow::Borrowed("\n".into()))); + newlines += 1; return Ok(( i.get(1..).ok_or(nom::Err::Error(NomError { input: i, code: ErrorKind::Eof, }))?, - (), + newlines, )); } } @@ -393,7 +397,7 @@ fn value_impl<'a>(i: &'a [u8], receive_event: &mut impl FnMut(Event<'a>)) -> IRe receive_event(Event::Value(Cow::Borrowed(remainder_value.as_bstr()))); } - Ok((i, ())) + Ok((i, newlines)) } fn take_spaces(i: &[u8]) -> IResult<&[u8], &BStr> { diff --git a/git-config/src/parse/nom/tests.rs b/git-config/src/parse/nom/tests.rs index d67fa1746fa..6082fae12c4 100644 --- a/git-config/src/parse/nom/tests.rs +++ b/git-config/src/parse/nom/tests.rs @@ -282,7 +282,7 @@ mod section { ] .into() }, - 2 + 3 )) ); } @@ -373,7 +373,7 @@ mod section { ] .into() }, - 0 + 2 )) ); } @@ -398,7 +398,7 @@ mod section { ] .into() }, - 0 + 1 )) ); } @@ -430,7 +430,7 @@ mod value_continuation { use crate::parse::tests::util::{into_events, newline_event, value_done_event, value_not_done_event}; pub fn value_impl<'a>(i: &'a [u8], events: &mut section::Events<'a>) -> nom::IResult<&'a [u8], ()> { - super::value_impl(i, &mut |e| events.push(e)) + super::value_impl(i, &mut |e| events.push(e)).map(|t| (t.0, ())) } #[test] @@ -602,7 +602,7 @@ mod section_body { node: &mut ParseNode, events: &mut section::Events<'a>, ) -> nom::IResult<&'a [u8], ()> { - super::section_body(i, node, &mut |e| events.push(e)) + super::section_body(i, node, &mut |e| events.push(e)).map(|t| (t.0, ())) } #[test] diff --git a/git-config/tests/parse/mod.rs b/git-config/tests/parse/mod.rs index e1de2a74835..aebbdc4014a 100644 --- a/git-config/tests/parse/mod.rs +++ b/git-config/tests/parse/mod.rs @@ -233,6 +233,16 @@ fn error() { "Got an unexpected token on line 1 while trying to parse a section header: '[a_b]\n c=d'", "underscores in section names aren't allowed and will be rejected by git" ); + let input = "[core] a=b\\\n cd\n[core]\n\n 4a=3"; + assert_eq!( + Events::from_str(input).unwrap_err().to_string(), + "Got an unexpected token on line 5 while trying to parse a name: '4a=3'" + ); + let input = "[core] a=b\\\n cd\n 4a=3"; + assert_eq!( + Events::from_str(input).unwrap_err().to_string(), + "Got an unexpected token on line 3 while trying to parse a name: '4a=3'" + ); let input = "[core] a=b\n 4a=3"; assert_eq!( Events::from_str(input).unwrap_err().to_string(), From e2bd0557d9ab68a02216c252ab20aaec2e4efd4e Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 9 Jul 2022 10:04:15 +0800 Subject: [PATCH 108/366] first tests for colors specifically; fix space between tokens (#331) Or so I hope, after all I don't really understand how this is supposed to be formatted. --- git-config/src/values/color.rs | 22 +++++++++++++++++----- git-config/src/values/mod.rs | 2 +- git-config/tests/values/color.rs | 25 +++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/git-config/src/values/color.rs b/git-config/src/values/color.rs index 6c0503a52de..58ba108f277 100644 --- a/git-config/src/values/color.rs +++ b/git-config/src/values/color.rs @@ -18,19 +18,31 @@ impl Color { impl Display for Color { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut write_space = None; if let Some(fg) = self.foreground { fg.fmt(f)?; + write_space = Some(()); } - write!(f, " ")?; - if let Some(bg) = self.background { + if write_space.take().is_some() { + write!(f, " ")?; + } bg.fmt(f)?; + write_space = Some(()) } - self.attributes - .iter() - .try_for_each(|attr| write!(f, " ").and_then(|_| attr.fmt(f))) + self.attributes.iter().try_for_each(|attr| { + if write_space.take().is_some() { + write!(f, " ") + } else { + Ok(()) + } + .and_then(|_| { + write_space = Some(()); + attr.fmt(f) + }) + }) } } diff --git a/git-config/src/values/mod.rs b/git-config/src/values/mod.rs index 081e635031c..a241d0f2ea4 100644 --- a/git-config/src/values/mod.rs +++ b/git-config/src/values/mod.rs @@ -12,7 +12,7 @@ pub mod path; /// Any value that may contain a foreground color, background color, a /// collection of color (text) modifiers, or a combination of any of the -/// aforementioned values. +/// aforementioned values, like `red` or `brightgreen`. /// /// Note that `git-config` allows color values to simply be a collection of /// [`color::Attribute`]s, and does not require a [`color::Name`] for either the diff --git a/git-config/tests/values/color.rs b/git-config/tests/values/color.rs index c8411f0e5a7..34aa7d6d3c5 100644 --- a/git-config/tests/values/color.rs +++ b/git-config/tests/values/color.rs @@ -100,3 +100,28 @@ mod attribute { assert!(Attribute::from_str("no-").is_err()); } } + +mod from_git { + use bstr::BStr; + use git_config::Color; + use std::convert::TryFrom; + + #[test] + #[ignore] + fn reset() { + assert_eq!(color("reset"), "[m"); + } + + #[test] + fn empty() { + assert_eq!(color(""), ""); + } + + fn color<'a>(name: impl Into<&'a BStr>) -> String { + try_color(name).expect("input color is expected to be valid") + } + + fn try_color<'a>(name: impl Into<&'a BStr>) -> crate::Result { + Ok(Color::try_from(name.into())?.to_string()) + } +} From c1b9cd443ec103a01daee8b8226a53f560d62498 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 9 Jul 2022 10:14:31 +0800 Subject: [PATCH 109/366] deduplicate (#331) At the expense of an allocation which we don't care about now. Noticed that it's unclear how colors are actually used, so tests should probably be adjusted accordingly. --- git-config/src/values/color.rs | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/git-config/src/values/color.rs b/git-config/src/values/color.rs index 58ba108f277..5f65d8b2620 100644 --- a/git-config/src/values/color.rs +++ b/git-config/src/values/color.rs @@ -319,22 +319,7 @@ impl serde::Serialize for Attribute { where S: serde::Serializer, { - serializer.serialize_str(match self { - Self::Bold => "bold", - Self::NoBold => "nobold", - Self::Dim => "dim", - Self::NoDim => "nodim", - Self::Ul => "ul", - Self::NoUl => "noul", - Self::Blink => "blink", - Self::NoBlink => "noblink", - Self::Reverse => "reverse", - Self::NoReverse => "noreverse", - Self::Italic => "italic", - Self::NoItalic => "noitalic", - Self::Strike => "strike", - Self::NoStrike => "nostrike", - }) + serializer.serialize_str(&self.to_string()) } } From 3fc4ac04f46f869c6e3a94ce4bb8a5737aa0c524 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 9 Jul 2022 10:57:54 +0800 Subject: [PATCH 110/366] change!: simplify `Color` API. (#331) For now we only parse and serialize for display, but more uses are enabled when needed and trivially. --- crate-status.md | 1 + git-config/src/values/color.rs | 55 +++++-------------------- git-config/tests/values/color.rs | 69 +++++++++++++++++++++++++++++++- 3 files changed, 79 insertions(+), 46 deletions(-) diff --git a/crate-status.md b/crate-status.md index c8bb05938c8..7b67713a973 100644 --- a/crate-status.md +++ b/crate-status.md @@ -394,6 +394,7 @@ See its [README.md](https://github.com/Byron/gitoxide/blob/main/git-lock/README. * [x] boolean * [x] integer * [x] color + * [ ] ANSI code output for terminal colors * [x] path (incl. resolution) * [x] include * **includeIf** diff --git a/git-config/src/values/color.rs b/git-config/src/values/color.rs index 5f65d8b2620..60e2af5646a 100644 --- a/git-config/src/values/color.rs +++ b/git-config/src/values/color.rs @@ -6,16 +6,6 @@ use std::convert::TryFrom; use std::fmt::Display; use std::str::FromStr; -impl Color { - /// Generates a byte representation of the value. This should be used when - /// non-UTF-8 sequences are present or a UTF-8 representation can't be - /// guaranteed. - #[must_use] - pub fn to_bstring(&self) -> BString { - self.into() - } -} - impl Display for Color { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut write_space = None; @@ -46,17 +36,6 @@ impl Display for Color { } } -#[cfg(feature = "serde")] -impl serde::Serialize for Color { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - // todo: maybe not? - serializer.serialize_str(&self.to_string()) - } -} - fn color_err(input: impl Into) -> value::Error { value::Error::new( "Colors are specific color values and their attributes, like 'brightred', or 'blue'", @@ -109,34 +88,11 @@ impl TryFrom<&BStr> for Color { } } -impl TryFrom for Color { - type Error = value::Error; - - fn try_from(value: BString) -> Result { - Self::try_from(value.as_ref()) - } -} - impl TryFrom> for Color { type Error = value::Error; fn try_from(c: Cow<'_, BStr>) -> Result { - match c { - Cow::Borrowed(c) => Self::try_from(c), - Cow::Owned(c) => Self::try_from(c), - } - } -} - -impl From for BString { - fn from(c: Color) -> Self { - c.into() - } -} - -impl From<&Color> for BString { - fn from(c: &Color) -> Self { - c.to_string().into() + Self::try_from(c.as_ref()) } } @@ -148,6 +104,7 @@ impl From<&Color> for BString { #[allow(missing_docs)] pub enum Name { Normal, + Default, Black, BrightBlack, Red, @@ -172,6 +129,7 @@ impl Display for Name { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Normal => write!(f, "normal"), + Self::Default => write!(f, "default"), Self::Black => write!(f, "black"), Self::BrightBlack => write!(f, "brightblack"), Self::Red => write!(f, "red"), @@ -218,7 +176,10 @@ impl FromStr for Name { match s { "normal" if !bright => return Ok(Self::Normal), + "-1" if !bright => return Ok(Self::Normal), "normal" if bright => return Err(color_err(s)), + "default" if !bright => return Ok(Self::Default), + "default" if bright => return Err(color_err(s)), "black" if !bright => return Ok(Self::Black), "black" if bright => return Ok(Self::BrightBlack), "red" if !bright => return Ok(Self::Red), @@ -276,6 +237,7 @@ impl TryFrom<&[u8]> for Name { #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] #[allow(missing_docs)] pub enum Attribute { + Reset, Bold, NoBold, Dim, @@ -295,6 +257,7 @@ pub enum Attribute { impl Display for Attribute { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { + Self::Reset => write!(f, "reset"), Self::Bold => write!(f, "bold"), Self::NoBold => write!(f, "nobold"), Self::Dim => write!(f, "dim"), @@ -339,6 +302,8 @@ impl FromStr for Attribute { } match parsed { + "reset" if !inverted => Ok(Self::Reset), + "reset" if inverted => Err(color_err(parsed)), "bold" if !inverted => Ok(Self::Bold), "bold" if inverted => Ok(Self::NoBold), "dim" if !inverted => Ok(Self::Dim), diff --git a/git-config/tests/values/color.rs b/git-config/tests/values/color.rs index 34aa7d6d3c5..a1d5a1605b7 100644 --- a/git-config/tests/values/color.rs +++ b/git-config/tests/values/color.rs @@ -6,6 +6,8 @@ mod name { #[test] fn non_bright() { assert_eq!(Name::from_str("normal"), Ok(Name::Normal)); + assert_eq!(Name::from_str("-1"), Ok(Name::Normal)); + assert_eq!(Name::from_str("default"), Ok(Name::Default)); assert_eq!(Name::from_str("black"), Ok(Name::Black)); assert_eq!(Name::from_str("red"), Ok(Name::Red)); assert_eq!(Name::from_str("green"), Ok(Name::Green)); @@ -43,7 +45,9 @@ mod name { #[test] fn invalid() { + assert!(Name::from_str("-2").is_err()); assert!(Name::from_str("brightnormal").is_err()); + assert!(Name::from_str("brightdefault").is_err()); assert!(Name::from_str("").is_err()); assert!(Name::from_str("bright").is_err()); assert!(Name::from_str("256").is_err()); @@ -60,6 +64,7 @@ mod attribute { #[test] fn non_inverted() { + assert_eq!(Attribute::from_str("reset"), Ok(Attribute::Reset)); assert_eq!(Attribute::from_str("bold"), Ok(Attribute::Bold)); assert_eq!(Attribute::from_str("dim"), Ok(Attribute::Dim)); assert_eq!(Attribute::from_str("ul"), Ok(Attribute::Ul)); @@ -93,6 +98,8 @@ mod attribute { #[test] fn invalid() { + assert!(Attribute::from_str("no-reset").is_err()); + assert!(Attribute::from_str("noreset").is_err()); assert!(Attribute::from_str("a").is_err()); assert!(Attribute::from_str("no bold").is_err()); assert!(Attribute::from_str("").is_err()); @@ -109,7 +116,7 @@ mod from_git { #[test] #[ignore] fn reset() { - assert_eq!(color("reset"), "[m"); + assert_eq!(color("reset"), "reset"); } #[test] @@ -117,6 +124,66 @@ mod from_git { assert_eq!(color(""), ""); } + #[test] + fn attribute_before_color_name() { + assert_eq!(color("bold red"), "red bold"); + } + + #[test] + fn color_name_before_attribute() { + assert_eq!(color("red bold"), "red bold"); + } + + #[test] + fn attribute_fg_bg() { + assert_eq!(color("ul blue red"), "blue red ul"); + } + + #[test] + fn fg_bg_attribute() { + assert_eq!(color("blue red ul"), "blue red ul"); + } + + #[test] + fn multiple_attributes() { + assert_eq!( + color("blue bold dim ul blink reverse"), + "blue bold dim ul blink reverse" + ); + } + + #[test] + fn reset_then_multiple_attributes() { + assert_eq!( + color("reset blue bold dim ul blink reverse"), + "blue reset bold dim ul blink reverse" + ); + } + + #[test] + fn long_color_spec() { + assert_eq!( + color("254 255 bold dim ul blink reverse"), + "254 255 bold dim ul blink reverse" + ); + let input = "#ffffff #ffffff bold nobold dim nodim italic noitalic ul noul blink noblink reverse noreverse strike nostrike"; + let expected = input; + assert_eq!(color(input), expected); + } + + #[test] + fn normal_default_can_clear_backgrounds() { + assert_eq!(color("normal default"), "normal default"); + } + + #[test] + fn default_can_combine_with_attributes() { + assert_eq!( + color("default default no-reverse bold"), + "default default noreverse bold" + ); + } + fn color<'a>(name: impl Into<&'a BStr>) -> String { try_color(name).expect("input color is expected to be valid") } From 4f21d1ed145bfd0d56d31be73fade25b104bab53 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 9 Jul 2022 11:02:19 +0800 Subject: [PATCH 111/366] refactor (#331) --- git-config/src/values/color.rs | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/git-config/src/values/color.rs b/git-config/src/values/color.rs index 60e2af5646a..37f41a610f5 100644 --- a/git-config/src/values/color.rs +++ b/git-config/src/values/color.rs @@ -165,10 +165,9 @@ impl serde::Serialize for Name { impl FromStr for Name { type Err = value::Error; - fn from_str(s: &str) -> Result { - let mut s = s; - let bright = if s.starts_with("bright") { - s = &s[6..]; + fn from_str(mut s: &str) -> Result { + let bright = if let Some(rest) = s.strip_prefix("bright") { + s = rest; true } else { false @@ -289,21 +288,17 @@ impl serde::Serialize for Attribute { impl FromStr for Attribute { type Err = value::Error; - fn from_str(s: &str) -> Result { - let inverted = s.starts_with("no"); - let mut parsed = s; - - if inverted { - parsed = &parsed[2..]; - - if parsed.starts_with('-') { - parsed = &parsed[1..]; - } - } + fn from_str(mut s: &str) -> Result { + let inverted = if let Some(rest) = s.strip_prefix("no-").or_else(|| s.strip_prefix("no")) { + s = rest; + true + } else { + false + }; - match parsed { + match s { "reset" if !inverted => Ok(Self::Reset), - "reset" if inverted => Err(color_err(parsed)), + "reset" if inverted => Err(color_err(s)), "bold" if !inverted => Ok(Self::Bold), "bold" if inverted => Ok(Self::NoBold), "dim" if !inverted => Ok(Self::Dim), @@ -318,7 +313,7 @@ impl FromStr for Attribute { "italic" if inverted => Ok(Self::NoItalic), "strike" if !inverted => Ok(Self::Strike), "strike" if inverted => Ok(Self::NoStrike), - _ => Err(color_err(parsed)), + _ => Err(color_err(s)), } } } From 23ec673baaf666fc38fda2f3b1ace9a8cf6816b8 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 9 Jul 2022 11:26:33 +0800 Subject: [PATCH 112/366] A bitflag version of color attributes (#331) --- Cargo.lock | 1 + git-config/Cargo.toml | 1 + git-config/src/values/color.rs | 102 +++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index bb427c602d4..73f7c32e584 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1082,6 +1082,7 @@ dependencies = [ name = "git-config" version = "0.6.0" dependencies = [ + "bitflags", "bstr", "criterion", "document-features", diff --git a/git-config/Cargo.toml b/git-config/Cargo.toml index cae9cad4624..6a918a43991 100644 --- a/git-config/Cargo.toml +++ b/git-config/Cargo.toml @@ -28,6 +28,7 @@ unicode-bom = "1.1.4" bstr = { version = "0.2.13", default-features = false, features = ["std"] } serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]} smallvec = "1.9.0" +bitflags = "1.3.2" document-features = { version = "0.2.0", optional = true } diff --git a/git-config/src/values/color.rs b/git-config/src/values/color.rs index 37f41a610f5..ead9d1231d6 100644 --- a/git-config/src/values/color.rs +++ b/git-config/src/values/color.rs @@ -1,3 +1,4 @@ +#![allow(missing_docs)] use crate::value; use crate::Color; use bstr::{BStr, BString}; @@ -253,6 +254,107 @@ pub enum Attribute { NoStrike, } +bitflags::bitflags! { + /// Discriminating enum for [`Color`] attributes. + /// + /// `git-config` supports modifiers and their negators. The negating color + /// attributes are equivalent to having a `no` or `no-` prefix to the normal + /// variant. + pub struct Attribute2: u32 { + const BOLD = 1 << 1; + const DIM = 1 << 2; + const ITALIC = 1 << 3; + const UL = 1 << 4; + const BLINK = 1 << 5; + const REVERSE = 1 << 6; + const STRIKE = 1 << 7; + /// Reset is special as we have to be able to parse it, without git actually doing anything with it + const RESET = 1 << 8; + + const NO_DIM = 1 << 21; + const NO_BOLD = 1 << 22; + const NO_ITALIC = 1 << 23; + const NO_UL = 1 << 24; + const NO_BLINK = 1 << 25; + const NO_REVERSE = 1 << 26; + const NO_STRIKE = 1 << 27; + } +} + +impl Display for Attribute2 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match *self { + Attribute2::RESET => write!(f, "reset"), + Attribute2::BOLD => write!(f, "bold"), + Attribute2::NO_BOLD => write!(f, "nobold"), + Attribute2::DIM => write!(f, "dim"), + Attribute2::NO_DIM => write!(f, "nodim"), + Attribute2::UL => write!(f, "ul"), + Attribute2::NO_UL => write!(f, "noul"), + Attribute2::BLINK => write!(f, "blink"), + Attribute2::NO_BLINK => write!(f, "noblink"), + Attribute2::REVERSE => write!(f, "reverse"), + Attribute2::NO_REVERSE => write!(f, "noreverse"), + Attribute2::ITALIC => write!(f, "italic"), + Attribute2::NO_ITALIC => write!(f, "noitalic"), + Attribute2::STRIKE => write!(f, "strike"), + Attribute2::NO_STRIKE => write!(f, "nostrike"), + _ => unreachable!("BUG: add new attribute flag"), + } + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for Attribute2 { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +impl FromStr for Attribute2 { + type Err = value::Error; + + fn from_str(mut s: &str) -> Result { + let inverted = if let Some(rest) = s.strip_prefix("no-").or_else(|| s.strip_prefix("no")) { + s = rest; + true + } else { + false + }; + + match s { + "reset" if !inverted => Ok(Attribute2::RESET), + "reset" if inverted => Err(color_err(s)), + "bold" if !inverted => Ok(Attribute2::BOLD), + "bold" if inverted => Ok(Attribute2::NO_BOLD), + "dim" if !inverted => Ok(Attribute2::DIM), + "dim" if inverted => Ok(Attribute2::NO_DIM), + "ul" if !inverted => Ok(Attribute2::UL), + "ul" if inverted => Ok(Attribute2::NO_UL), + "blink" if !inverted => Ok(Attribute2::BLINK), + "blink" if inverted => Ok(Attribute2::NO_BLINK), + "reverse" if !inverted => Ok(Attribute2::REVERSE), + "reverse" if inverted => Ok(Attribute2::NO_REVERSE), + "italic" if !inverted => Ok(Attribute2::ITALIC), + "italic" if inverted => Ok(Attribute2::NO_ITALIC), + "strike" if !inverted => Ok(Attribute2::STRIKE), + "strike" if inverted => Ok(Attribute2::NO_STRIKE), + _ => Err(color_err(s)), + } + } +} + +impl TryFrom<&[u8]> for Attribute2 { + type Error = value::Error; + + fn try_from(s: &[u8]) -> Result { + Self::from_str(std::str::from_utf8(s).map_err(|err| color_err(s).with_err(err))?) + } +} + impl Display for Attribute { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { From 703922dd4e1e5b27835298217ff4eb8ef1dc57ce Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 9 Jul 2022 11:54:43 +0800 Subject: [PATCH 113/366] change!: Use bitflags for `color::Attribute` instead of `Vec` of enums. (#331) This is less wasteful and sufficient for git, so it should be sufficient for us, especially since attributes are indeed a set and declaring one twice has no effect. --- git-config/src/parse/mod.rs | 2 +- git-config/src/values/color.rs | 215 +++++++--------------- git-config/src/values/mod.rs | 4 +- git-config/tests/file/access/read_only.rs | 2 +- git-config/tests/values/color.rs | 58 +++--- 5 files changed, 102 insertions(+), 179 deletions(-) diff --git a/git-config/src/parse/mod.rs b/git-config/src/parse/mod.rs index a74f8bef78b..73e1627b500 100644 --- a/git-config/src/parse/mod.rs +++ b/git-config/src/parse/mod.rs @@ -85,7 +85,7 @@ pub struct Section<'a> { /// A parsed comment event containing the comment marker and comment. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] pub struct Comment<'a> { - /// The comment marker used. This is either a semicolon or octothorpe. + /// The comment marker used. This is either a semicolon or octothorpe/hash. pub comment_tag: u8, /// The parsed comment. pub comment: Cow<'a, BStr>, diff --git a/git-config/src/values/color.rs b/git-config/src/values/color.rs index ead9d1231d6..54ae8d8283b 100644 --- a/git-config/src/values/color.rs +++ b/git-config/src/values/color.rs @@ -23,17 +23,13 @@ impl Display for Color { write_space = Some(()) } - self.attributes.iter().try_for_each(|attr| { + if !self.attributes.is_empty() { if write_space.take().is_some() { - write!(f, " ") - } else { - Ok(()) + write!(f, " ")?; } - .and_then(|_| { - write_space = Some(()); - attr.fmt(f) - }) - }) + self.attributes.fmt(f)?; + } + Ok(()) } } @@ -66,26 +62,32 @@ impl TryFrom<&BStr> for Color { ) }); - let mut new_self = Self::default(); + let mut foreground = None; + let mut background = None; + let mut attributes = Attribute::empty(); for item in items { match item { Ok(item) => match item { ColorItem::Value(v) => { - if new_self.foreground.is_none() { - new_self.foreground = Some(v); - } else if new_self.background.is_none() { - new_self.background = Some(v); + if foreground.is_none() { + foreground = Some(v); + } else if background.is_none() { + background = Some(v); } else { return Err(color_err(s)); } } - ColorItem::Attr(a) => new_self.attributes.push(a), + ColorItem::Attr(a) => attributes |= a, }, Err(_) => return Err(color_err(s)), } } - Ok(new_self) + Ok(Color { + foreground, + background, + attributes, + }) } } @@ -100,7 +102,7 @@ impl TryFrom> for Color { /// Discriminating enum for [`Color`] values. /// /// `git-config` supports the eight standard colors, their bright variants, an -/// ANSI color code, or a 24-bit hex value prefixed with an octothorpe. +/// ANSI color code, or a 24-bit hex value prefixed with an octothorpe/hash. #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] #[allow(missing_docs)] pub enum Name { @@ -229,38 +231,14 @@ impl TryFrom<&[u8]> for Name { } } -/// Discriminating enum for [`Color`] attributes. -/// -/// `git-config` supports modifiers and their negators. The negating color -/// attributes are equivalent to having a `no` or `no-` prefix to the normal -/// variant. -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] -#[allow(missing_docs)] -pub enum Attribute { - Reset, - Bold, - NoBold, - Dim, - NoDim, - Ul, - NoUl, - Blink, - NoBlink, - Reverse, - NoReverse, - Italic, - NoItalic, - Strike, - NoStrike, -} - bitflags::bitflags! { /// Discriminating enum for [`Color`] attributes. /// /// `git-config` supports modifiers and their negators. The negating color /// attributes are equivalent to having a `no` or `no-` prefix to the normal /// variant. - pub struct Attribute2: u32 { + #[derive(Default)] + pub struct Attribute: u32 { const BOLD = 1 << 1; const DIM = 1 << 2; const ITALIC = 1 << 3; @@ -281,99 +259,40 @@ bitflags::bitflags! { } } -impl Display for Attribute2 { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match *self { - Attribute2::RESET => write!(f, "reset"), - Attribute2::BOLD => write!(f, "bold"), - Attribute2::NO_BOLD => write!(f, "nobold"), - Attribute2::DIM => write!(f, "dim"), - Attribute2::NO_DIM => write!(f, "nodim"), - Attribute2::UL => write!(f, "ul"), - Attribute2::NO_UL => write!(f, "noul"), - Attribute2::BLINK => write!(f, "blink"), - Attribute2::NO_BLINK => write!(f, "noblink"), - Attribute2::REVERSE => write!(f, "reverse"), - Attribute2::NO_REVERSE => write!(f, "noreverse"), - Attribute2::ITALIC => write!(f, "italic"), - Attribute2::NO_ITALIC => write!(f, "noitalic"), - Attribute2::STRIKE => write!(f, "strike"), - Attribute2::NO_STRIKE => write!(f, "nostrike"), - _ => unreachable!("BUG: add new attribute flag"), - } - } -} - -#[cfg(feature = "serde")] -impl serde::Serialize for Attribute2 { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_str(&self.to_string()) - } -} - -impl FromStr for Attribute2 { - type Err = value::Error; - - fn from_str(mut s: &str) -> Result { - let inverted = if let Some(rest) = s.strip_prefix("no-").or_else(|| s.strip_prefix("no")) { - s = rest; - true - } else { - false - }; - - match s { - "reset" if !inverted => Ok(Attribute2::RESET), - "reset" if inverted => Err(color_err(s)), - "bold" if !inverted => Ok(Attribute2::BOLD), - "bold" if inverted => Ok(Attribute2::NO_BOLD), - "dim" if !inverted => Ok(Attribute2::DIM), - "dim" if inverted => Ok(Attribute2::NO_DIM), - "ul" if !inverted => Ok(Attribute2::UL), - "ul" if inverted => Ok(Attribute2::NO_UL), - "blink" if !inverted => Ok(Attribute2::BLINK), - "blink" if inverted => Ok(Attribute2::NO_BLINK), - "reverse" if !inverted => Ok(Attribute2::REVERSE), - "reverse" if inverted => Ok(Attribute2::NO_REVERSE), - "italic" if !inverted => Ok(Attribute2::ITALIC), - "italic" if inverted => Ok(Attribute2::NO_ITALIC), - "strike" if !inverted => Ok(Attribute2::STRIKE), - "strike" if inverted => Ok(Attribute2::NO_STRIKE), - _ => Err(color_err(s)), - } - } -} - -impl TryFrom<&[u8]> for Attribute2 { - type Error = value::Error; - - fn try_from(s: &[u8]) -> Result { - Self::from_str(std::str::from_utf8(s).map_err(|err| color_err(s).with_err(err))?) - } -} - impl Display for Attribute { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Reset => write!(f, "reset"), - Self::Bold => write!(f, "bold"), - Self::NoBold => write!(f, "nobold"), - Self::Dim => write!(f, "dim"), - Self::NoDim => write!(f, "nodim"), - Self::Ul => write!(f, "ul"), - Self::NoUl => write!(f, "noul"), - Self::Blink => write!(f, "blink"), - Self::NoBlink => write!(f, "noblink"), - Self::Reverse => write!(f, "reverse"), - Self::NoReverse => write!(f, "noreverse"), - Self::Italic => write!(f, "italic"), - Self::NoItalic => write!(f, "noitalic"), - Self::Strike => write!(f, "strike"), - Self::NoStrike => write!(f, "nostrike"), + let mut write_space = None; + for bit in 1..std::mem::size_of::() * 8 { + let attr = match Attribute::from_bits(1 << bit) { + Some(attr) => attr, + None => continue, + }; + if self.contains(attr) { + if write_space.take().is_some() { + write!(f, " ")? + } + match attr { + Attribute::RESET => write!(f, "reset"), + Attribute::BOLD => write!(f, "bold"), + Attribute::NO_BOLD => write!(f, "nobold"), + Attribute::DIM => write!(f, "dim"), + Attribute::NO_DIM => write!(f, "nodim"), + Attribute::UL => write!(f, "ul"), + Attribute::NO_UL => write!(f, "noul"), + Attribute::BLINK => write!(f, "blink"), + Attribute::NO_BLINK => write!(f, "noblink"), + Attribute::REVERSE => write!(f, "reverse"), + Attribute::NO_REVERSE => write!(f, "noreverse"), + Attribute::ITALIC => write!(f, "italic"), + Attribute::NO_ITALIC => write!(f, "noitalic"), + Attribute::STRIKE => write!(f, "strike"), + Attribute::NO_STRIKE => write!(f, "nostrike"), + _ => unreachable!("BUG: add new attribute flag"), + }?; + write_space = Some(()); + } } + Ok(()) } } @@ -399,22 +318,22 @@ impl FromStr for Attribute { }; match s { - "reset" if !inverted => Ok(Self::Reset), + "reset" if !inverted => Ok(Attribute::RESET), "reset" if inverted => Err(color_err(s)), - "bold" if !inverted => Ok(Self::Bold), - "bold" if inverted => Ok(Self::NoBold), - "dim" if !inverted => Ok(Self::Dim), - "dim" if inverted => Ok(Self::NoDim), - "ul" if !inverted => Ok(Self::Ul), - "ul" if inverted => Ok(Self::NoUl), - "blink" if !inverted => Ok(Self::Blink), - "blink" if inverted => Ok(Self::NoBlink), - "reverse" if !inverted => Ok(Self::Reverse), - "reverse" if inverted => Ok(Self::NoReverse), - "italic" if !inverted => Ok(Self::Italic), - "italic" if inverted => Ok(Self::NoItalic), - "strike" if !inverted => Ok(Self::Strike), - "strike" if inverted => Ok(Self::NoStrike), + "bold" if !inverted => Ok(Attribute::BOLD), + "bold" if inverted => Ok(Attribute::NO_BOLD), + "dim" if !inverted => Ok(Attribute::DIM), + "dim" if inverted => Ok(Attribute::NO_DIM), + "ul" if !inverted => Ok(Attribute::UL), + "ul" if inverted => Ok(Attribute::NO_UL), + "blink" if !inverted => Ok(Attribute::BLINK), + "blink" if inverted => Ok(Attribute::NO_BLINK), + "reverse" if !inverted => Ok(Attribute::REVERSE), + "reverse" if inverted => Ok(Attribute::NO_REVERSE), + "italic" if !inverted => Ok(Attribute::ITALIC), + "italic" if inverted => Ok(Attribute::NO_ITALIC), + "strike" if !inverted => Ok(Attribute::STRIKE), + "strike" if inverted => Ok(Attribute::NO_STRIKE), _ => Err(color_err(s)), } } diff --git a/git-config/src/values/mod.rs b/git-config/src/values/mod.rs index a241d0f2ea4..882f342459a 100644 --- a/git-config/src/values/mod.rs +++ b/git-config/src/values/mod.rs @@ -23,8 +23,8 @@ pub struct Color { pub foreground: Option, /// A provided background color pub background: Option, - /// A potentially empty list of text attributes - pub attributes: Vec, + /// A potentially empty set of text attributes + pub attributes: color::Attribute, } /// Any value that can be interpreted as an integer. diff --git a/git-config/tests/file/access/read_only.rs b/git-config/tests/file/access/read_only.rs index 08024b1d6b1..cb78db83a96 100644 --- a/git-config/tests/file/access/read_only.rs +++ b/git-config/tests/file/access/read_only.rs @@ -74,7 +74,7 @@ fn get_value_for_all_provided_values() -> crate::Result { Color { foreground: Some(color::Name::BrightGreen), background: Some(color::Name::Red), - attributes: vec![color::Attribute::Bold] + attributes: color::Attribute::BOLD } ); diff --git a/git-config/tests/values/color.rs b/git-config/tests/values/color.rs index a1d5a1605b7..7e1d5cea73b 100644 --- a/git-config/tests/values/color.rs +++ b/git-config/tests/values/color.rs @@ -64,36 +64,36 @@ mod attribute { #[test] fn non_inverted() { - assert_eq!(Attribute::from_str("reset"), Ok(Attribute::Reset)); - assert_eq!(Attribute::from_str("bold"), Ok(Attribute::Bold)); - assert_eq!(Attribute::from_str("dim"), Ok(Attribute::Dim)); - assert_eq!(Attribute::from_str("ul"), Ok(Attribute::Ul)); - assert_eq!(Attribute::from_str("blink"), Ok(Attribute::Blink)); - assert_eq!(Attribute::from_str("reverse"), Ok(Attribute::Reverse)); - assert_eq!(Attribute::from_str("italic"), Ok(Attribute::Italic)); - assert_eq!(Attribute::from_str("strike"), Ok(Attribute::Strike)); + assert_eq!(Attribute::from_str("reset"), Ok(Attribute::RESET)); + assert_eq!(Attribute::from_str("bold"), Ok(Attribute::BOLD)); + assert_eq!(Attribute::from_str("dim"), Ok(Attribute::DIM)); + assert_eq!(Attribute::from_str("ul"), Ok(Attribute::UL)); + assert_eq!(Attribute::from_str("blink"), Ok(Attribute::BLINK)); + assert_eq!(Attribute::from_str("reverse"), Ok(Attribute::REVERSE)); + assert_eq!(Attribute::from_str("italic"), Ok(Attribute::ITALIC)); + assert_eq!(Attribute::from_str("strike"), Ok(Attribute::STRIKE)); } #[test] fn inverted_no_dash() { - assert_eq!(Attribute::from_str("nobold"), Ok(Attribute::NoBold)); - assert_eq!(Attribute::from_str("nodim"), Ok(Attribute::NoDim)); - assert_eq!(Attribute::from_str("noul"), Ok(Attribute::NoUl)); - assert_eq!(Attribute::from_str("noblink"), Ok(Attribute::NoBlink)); - assert_eq!(Attribute::from_str("noreverse"), Ok(Attribute::NoReverse)); - assert_eq!(Attribute::from_str("noitalic"), Ok(Attribute::NoItalic)); - assert_eq!(Attribute::from_str("nostrike"), Ok(Attribute::NoStrike)); + assert_eq!(Attribute::from_str("nobold"), Ok(Attribute::NO_BOLD)); + assert_eq!(Attribute::from_str("nodim"), Ok(Attribute::NO_DIM)); + assert_eq!(Attribute::from_str("noul"), Ok(Attribute::NO_UL)); + assert_eq!(Attribute::from_str("noblink"), Ok(Attribute::NO_BLINK)); + assert_eq!(Attribute::from_str("noreverse"), Ok(Attribute::NO_REVERSE)); + assert_eq!(Attribute::from_str("noitalic"), Ok(Attribute::NO_ITALIC)); + assert_eq!(Attribute::from_str("nostrike"), Ok(Attribute::NO_STRIKE)); } #[test] fn inverted_dashed() { - assert_eq!(Attribute::from_str("no-bold"), Ok(Attribute::NoBold)); - assert_eq!(Attribute::from_str("no-dim"), Ok(Attribute::NoDim)); - assert_eq!(Attribute::from_str("no-ul"), Ok(Attribute::NoUl)); - assert_eq!(Attribute::from_str("no-blink"), Ok(Attribute::NoBlink)); - assert_eq!(Attribute::from_str("no-reverse"), Ok(Attribute::NoReverse)); - assert_eq!(Attribute::from_str("no-italic"), Ok(Attribute::NoItalic)); - assert_eq!(Attribute::from_str("no-strike"), Ok(Attribute::NoStrike)); + assert_eq!(Attribute::from_str("no-bold"), Ok(Attribute::NO_BOLD)); + assert_eq!(Attribute::from_str("no-dim"), Ok(Attribute::NO_DIM)); + assert_eq!(Attribute::from_str("no-ul"), Ok(Attribute::NO_UL)); + assert_eq!(Attribute::from_str("no-blink"), Ok(Attribute::NO_BLINK)); + assert_eq!(Attribute::from_str("no-reverse"), Ok(Attribute::NO_REVERSE)); + assert_eq!(Attribute::from_str("no-italic"), Ok(Attribute::NO_ITALIC)); + assert_eq!(Attribute::from_str("no-strike"), Ok(Attribute::NO_STRIKE)); } #[test] @@ -114,7 +114,6 @@ mod from_git { use std::convert::TryFrom; #[test] - #[ignore] fn reset() { assert_eq!(color("reset"), "reset"); } @@ -124,6 +123,11 @@ mod from_git { assert_eq!(color(""), ""); } + #[test] + fn at_most_two_colors() { + assert!(try_color("red green blue").is_err()); + } + #[test] fn attribute_before_color_name() { assert_eq!(color("bold red"), "red bold"); @@ -155,8 +159,8 @@ mod from_git { #[test] fn reset_then_multiple_attributes() { assert_eq!( - color("reset blue bold dim ul blink reverse"), - "blue reset bold dim ul blink reverse" + color("blue bold dim ul blink reverse reset"), + "blue bold dim ul blink reverse reset" ); } @@ -167,7 +171,7 @@ mod from_git { "254 255 bold dim ul blink reverse" ); let input = "#ffffff #ffffff bold nobold dim nodim italic noitalic ul noul blink noblink reverse noreverse strike nostrike"; - let expected = input; + let expected = "#ffffff #ffffff bold dim italic ul blink reverse strike nodim nobold noitalic noul noblink noreverse nostrike"; assert_eq!(color(input), expected); } @@ -180,7 +184,7 @@ mod from_git { fn default_can_combine_with_attributes() { assert_eq!( color("default default no-reverse bold"), - "default default noreverse bold" + "default default bold noreverse" ); } From 9cadc6f0cbaad0ac23f5469db2f040aecfbfb82c Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 9 Jul 2022 13:15:56 +0800 Subject: [PATCH 114/366] change!: Simplify `Boolean` to be a wrapper around `bool`. (#331) Previously it tried hard not to degenerate information, making it a complicated type. However, in practice nobody cares about the exact makeup of the boolean, and there is no need to serialize a boolean faithfully either. Instead, those who want to set a value just set any value as a string, no need for type safety there, and we take care of escaping values properly on write. --- git-config/src/file/access/comfort.rs | 2 +- .../src/file/access/low_level/read_only.rs | 8 +- git-config/src/values/boolean.rs | 186 ++++-------------- git-config/src/values/integer.rs | 6 +- git-config/src/values/mod.rs | 15 +- git-config/tests/file/access/read_only.rs | 29 +-- git-config/tests/values/boolean.rs | 36 ++-- 7 files changed, 68 insertions(+), 214 deletions(-) diff --git a/git-config/src/file/access/comfort.rs b/git-config/src/file/access/comfort.rs index 4f9d776fd7a..99a70dbcedf 100644 --- a/git-config/src/file/access/comfort.rs +++ b/git-config/src/file/access/comfort.rs @@ -38,7 +38,7 @@ impl<'a> File<'a> { ) -> Option> { self.raw_value(section_name, subsection_name, key) .ok() - .map(|v| crate::Boolean::try_from(v).map(|b| b.to_bool())) + .map(|v| crate::Boolean::try_from(v).map(|b| b.into())) } /// Like [`value()`][File::value()], but returning an `Option` if the integer wasn't found. diff --git a/git-config/src/file/access/low_level/read_only.rs b/git-config/src/file/access/low_level/read_only.rs index 24147dc76dc..865aa78a3a1 100644 --- a/git-config/src/file/access/low_level/read_only.rs +++ b/git-config/src/file/access/low_level/read_only.rs @@ -70,7 +70,7 @@ impl<'event> File<'event> { /// /// ``` /// # use git_config::File; - /// # use git_config::{Integer, String, Boolean, boolean::True}; + /// # use git_config::{Integer, String, Boolean}; /// # use std::borrow::Cow; /// # use std::convert::TryFrom; /// let config = r#" @@ -87,9 +87,9 @@ impl<'event> File<'event> { /// assert_eq!( /// a_value, /// vec![ - /// Boolean::True(True::Explicit(Cow::Borrowed("true".into()))), - /// Boolean::True(True::Implicit), - /// Boolean::False(Cow::Borrowed("false".into())), + /// Boolean(true), + /// Boolean(true), + /// Boolean(false), /// ] /// ); /// // ... or explicitly declare the type to avoid the turbofish diff --git a/git-config/src/values/boolean.rs b/git-config/src/values/boolean.rs index 55e805c2804..d4db9a40c84 100644 --- a/git-config/src/values/boolean.rs +++ b/git-config/src/values/boolean.rs @@ -5,20 +5,13 @@ use std::borrow::Cow; use std::convert::TryFrom; use std::fmt::Display; -impl Boolean<'_> { - /// Return ourselves as plain bool. - pub fn to_bool(&self) -> bool { - match self { - Boolean::True(_) => true, - Boolean::False(_) => false, - } - } +impl Boolean { /// Generates a byte representation of the value. This should be used when /// non-UTF-8 sequences are present or a UTF-8 representation can't be /// guaranteed. #[must_use] - pub fn to_bstring(&self) -> BString { - self.into() + pub fn to_bstring(self) -> BString { + self.to_string().into() } } @@ -29,178 +22,69 @@ fn bool_err(input: impl Into) -> value::Error { ) } -impl<'a> TryFrom<&'a BStr> for Boolean<'a> { +impl TryFrom<&BStr> for Boolean { type Error = value::Error; - fn try_from(value: &'a BStr) -> Result { - if let Ok(v) = True::try_from(value) { - return Ok(Self::True(v)); - } - - if value.eq_ignore_ascii_case(b"no") - || value.eq_ignore_ascii_case(b"off") - || value.eq_ignore_ascii_case(b"false") - || value.eq_ignore_ascii_case(b"zero") - || value == "\"\"" - { - return Ok(Self::False(value.as_bstr().into())); + fn try_from(value: &BStr) -> Result { + if parse_true(value) { + Ok(Boolean(true)) + } else if parse_false(value) { + Ok(Boolean(false)) + } else { + Err(bool_err(value)) } - - Err(bool_err(value)) } } -impl TryFrom for Boolean<'_> { +impl TryFrom for Boolean { type Error = value::Error; fn try_from(value: BString) -> Result { - if value.eq_ignore_ascii_case(b"no") - || value.eq_ignore_ascii_case(b"off") - || value.eq_ignore_ascii_case(b"false") - || value.eq_ignore_ascii_case(b"zero") - || value == "\"\"" - { - return Ok(Self::False(Cow::Owned(value))); - } - - True::try_from(value).map(Self::True) + Self::try_from(value.as_bstr()) } } -impl<'a> TryFrom> for Boolean<'a> { +impl TryFrom> for Boolean { type Error = value::Error; - fn try_from(c: Cow<'a, BStr>) -> Result { - match c { - Cow::Borrowed(c) => Self::try_from(c), - Cow::Owned(c) => Self::try_from(c), - } + fn try_from(c: Cow<'_, BStr>) -> Result { + Self::try_from(c.as_ref()) } } -impl Display for Boolean<'_> { +impl Display for Boolean { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Boolean::True(v) => v.fmt(f), - Boolean::False(v) => write!(f, "{}", v), - } - } -} - -impl From> for bool { - fn from(b: Boolean<'_>) -> Self { - match b { - Boolean::True(_) => true, - Boolean::False(_) => false, - } - } -} - -impl<'a, 'b: 'a> From<&'b Boolean<'a>> for &'a BStr { - fn from(b: &'b Boolean<'_>) -> Self { - match b { - Boolean::True(t) => t.into(), - Boolean::False(f) => f.as_ref(), - } - } -} - -impl From> for BString { - fn from(b: Boolean<'_>) -> Self { - b.into() + self.0.fmt(f) } } -impl From<&Boolean<'_>> for BString { - fn from(b: &Boolean<'_>) -> Self { - b.to_string().into() +impl From for bool { + fn from(b: Boolean) -> Self { + b.0 } } #[cfg(feature = "serde")] -impl serde::Serialize for Boolean<'_> { +impl serde::Serialize for Boolean { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { - match self { - Boolean::True(_) => serializer.serialize_bool(true), - Boolean::False(_) => serializer.serialize_bool(false), - } - } -} - -/// Discriminating enum between implicit and explicit truthy values. -/// -/// This enum is part of the [`Boolean`] struct. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] -#[allow(missing_docs)] -pub enum True<'a> { - Explicit(Cow<'a, BStr>), - /// For values defined without a `= `. - Implicit, -} - -impl<'a> TryFrom<&'a BStr> for True<'a> { - type Error = value::Error; - - fn try_from(value: &'a BStr) -> Result { - if value.eq_ignore_ascii_case(b"yes") - || value.eq_ignore_ascii_case(b"on") - || value.eq_ignore_ascii_case(b"true") - || value.eq_ignore_ascii_case(b"one") - { - Ok(Self::Explicit(value.as_bstr().into())) - } else if value.is_empty() { - Ok(Self::Implicit) - } else { - Err(bool_err(value)) - } - } -} - -impl TryFrom for True<'_> { - type Error = value::Error; - - fn try_from(value: BString) -> Result { - if value.eq_ignore_ascii_case(b"yes") - || value.eq_ignore_ascii_case(b"on") - || value.eq_ignore_ascii_case(b"true") - || value.eq_ignore_ascii_case(b"one") - { - Ok(Self::Explicit(Cow::Owned(value))) - } else if value.is_empty() { - Ok(Self::Implicit) - } else { - Err(bool_err(value)) - } + serializer.serialize_bool(self.0) } } -impl Display for True<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if let Self::Explicit(v) = self { - write!(f, "{}", v) - } else { - Ok(()) - } - } -} - -impl<'a, 'b: 'a> From<&'b True<'a>> for &'a BStr { - fn from(t: &'b True<'a>) -> Self { - match t { - True::Explicit(e) => e.as_ref(), - True::Implicit => "".into(), - } - } +fn parse_true(value: &BStr) -> bool { + value.eq_ignore_ascii_case(b"yes") + || value.eq_ignore_ascii_case(b"on") + || value.eq_ignore_ascii_case(b"true") + || value.eq_ignore_ascii_case(b"one") + || value.is_empty() } -#[cfg(feature = "serde")] -impl serde::Serialize for True<'_> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_bool(true) - } +fn parse_false(value: &BStr) -> bool { + value.eq_ignore_ascii_case(b"no") + || value.eq_ignore_ascii_case(b"off") + || value.eq_ignore_ascii_case(b"false") + || value.eq_ignore_ascii_case(b"zero") + || value == "\"\"" } diff --git a/git-config/src/values/integer.rs b/git-config/src/values/integer.rs index f1c32885efc..af5b400bf36 100644 --- a/git-config/src/values/integer.rs +++ b/git-config/src/values/integer.rs @@ -16,10 +16,10 @@ impl Integer { } /// Canonicalize values as simple decimal numbers. - /// An optional suffix of k, m, or g (case-insensitive), upon creation, will cause the value to be multiplied by - /// 1024 (k), 1048576 (m), or 1073741824 (g) respectively. + /// An optional suffix of k, m, or g (case-insensitive), will cause the + /// value to be multiplied by 1024 (k), 1048576 (m), or 1073741824 (g) respectively. /// - /// Returns the result if no multiplication overflow. + /// Returns the result if there is no multiplication overflow. pub fn to_decimal(&self) -> Option { match self.suffix { None => Some(self.value), diff --git a/git-config/src/values/mod.rs b/git-config/src/values/mod.rs index 882f342459a..31d4c6d4436 100644 --- a/git-config/src/values/mod.rs +++ b/git-config/src/values/mod.rs @@ -33,11 +33,8 @@ pub struct Color { /// suffix. The suffix is parsed separately from the value itself, so if you /// wish to obtain the true value of the integer, you must account for the /// suffix after fetching the value. [`integer::Suffix`] provides -/// [`bitwise_offset`] to help with the math, but do be warned that if the value -/// is very large, you may run into overflows. -/// -/// [`BStr`]: bstr::BStr -/// [`bitwise_offset`]: integer::Suffix::bitwise_offset +/// [`bitwise_offset()`][integer::Suffix::bitwise_offset] to help with the +/// math, or [to_decimal()][Integer::to_decimal()] for obtaining a usable value in one step. #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] pub struct Integer { /// The value, without any suffix modification @@ -51,13 +48,9 @@ pub struct Integer { /// Note that while values can effectively be any byte string, the `git-config` /// documentation has a strict subset of values that may be interpreted as a /// boolean value, all of which are ASCII and thus UTF-8 representable. -/// Consequently, variants hold [`str`]s rather than [`[u8]`]s. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] #[allow(missing_docs)] -pub enum Boolean<'a> { - True(boolean::True<'a>), - False(std::borrow::Cow<'a, bstr::BStr>), -} +pub struct Boolean(pub bool); /// Any value that can be interpreted as a file path. /// diff --git a/git-config/tests/file/access/read_only.rs b/git-config/tests/file/access/read_only.rs index cb78db83a96..0bd9bb410c3 100644 --- a/git-config/tests/file/access/read_only.rs +++ b/git-config/tests/file/access/read_only.rs @@ -1,7 +1,7 @@ use crate::file::cow_str; use bstr::BStr; use git_config::File; -use git_config::{boolean::True, color, integer, Boolean, Color, Integer, String}; +use git_config::{color, integer, Boolean, Color, Integer, String}; use std::{borrow::Cow, convert::TryFrom, error::Error}; /// Asserts we can cast into all variants of our type @@ -25,21 +25,16 @@ fn get_value_for_all_provided_values() -> crate::Result { let config = git_config::parse::Events::from_bytes_owned(config.as_bytes(), None).map(File::from)?; - assert_eq!( - config.value::("core", None, "bool-explicit")?, - Boolean::False(Cow::Borrowed("false".into())) - ); + assert_eq!(config.value::("core", None, "bool-explicit")?.0, false); assert!(!config.boolean("core", None, "bool-explicit").expect("exists")?); - assert_eq!( - config.value::("core", None, "bool-implicit")?, - Boolean::True(True::Implicit) - ); + assert_eq!(config.value::("core", None, "bool-implicit")?.0, true); assert_eq!( config .try_value::("core", None, "bool-implicit") - .expect("exists")?, - Boolean::True(True::Implicit) + .expect("exists")? + .0, + true ); assert!(config.boolean("core", None, "bool-implicit").expect("present")?); @@ -164,15 +159,9 @@ fn get_value_looks_up_all_sections_before_failing() -> crate::Result { let file = File::try_from(config)?; // Checks that we check the last entry first still - assert_eq!( - file.value::("core", None, "bool-implicit")?, - Boolean::True(True::Implicit) - ); + assert_eq!(file.value::("core", None, "bool-implicit")?.0, true); - assert_eq!( - file.value::("core", None, "bool-explicit")?, - Boolean::False(cow_str("false")) - ); + assert_eq!(file.value::("core", None, "bool-explicit")?.0, false); Ok(()) } @@ -211,7 +200,7 @@ fn single_section() -> Result<(), Box> { let second_value: Boolean = config.value("core", None, "c")?; assert_eq!(first_value, String { value: cow_str("b") }); - assert_eq!(second_value, Boolean::True(True::Implicit)); + assert_eq!(second_value.0, true); Ok(()) } diff --git a/git-config/tests/values/boolean.rs b/git-config/tests/values/boolean.rs index 0491d429130..f7675312a12 100644 --- a/git-config/tests/values/boolean.rs +++ b/git-config/tests/values/boolean.rs @@ -1,37 +1,25 @@ use std::convert::TryFrom; -use crate::file::cow_str; -use git_config::{boolean::True, Boolean}; +use git_config::Boolean; use crate::value::b; #[test] -fn from_str_false() { - assert_eq!(Boolean::try_from(b("no")), Ok(Boolean::False(cow_str("no")))); - assert_eq!(Boolean::try_from(b("off")), Ok(Boolean::False(cow_str("off")))); - assert_eq!(Boolean::try_from(b("false")), Ok(Boolean::False(cow_str("false")))); - assert_eq!(Boolean::try_from(b("zero")), Ok(Boolean::False(cow_str("zero")))); - assert_eq!(Boolean::try_from(b("\"\"")), Ok(Boolean::False(cow_str("\"\"")))); +fn from_str_false() -> crate::Result { + assert_eq!(Boolean::try_from(b("no"))?.0, false); + assert_eq!(Boolean::try_from(b("off"))?.0, false); + assert_eq!(Boolean::try_from(b("false"))?.0, false); + assert_eq!(Boolean::try_from(b("zero"))?.0, false); + assert_eq!(Boolean::try_from(b("\"\""))?.0, false); + Ok(()) } #[test] fn from_str_true() { - assert_eq!( - Boolean::try_from(b("yes")), - Ok(Boolean::True(True::Explicit(cow_str("yes")))) - ); - assert_eq!( - Boolean::try_from(b("on")), - Ok(Boolean::True(True::Explicit(cow_str("on")))) - ); - assert_eq!( - Boolean::try_from(b("true")), - Ok(Boolean::True(True::Explicit(cow_str("true")))) - ); - assert_eq!( - Boolean::try_from(b("one")), - Ok(Boolean::True(True::Explicit(cow_str("one")))) - ); + assert_eq!(Boolean::try_from(b("yes")).map(Into::into), Ok(true)); + assert_eq!(Boolean::try_from(b("on")), Ok(Boolean(true))); + assert_eq!(Boolean::try_from(b("true")), Ok(Boolean(true))); + assert_eq!(Boolean::try_from(b("one")), Ok(Boolean(true))); } #[test] From d3841ee752e426bf58130cde1e4e40215ccb8f33 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 9 Jul 2022 13:19:02 +0800 Subject: [PATCH 115/366] adjustments for breaking changes in `git-config` (#331) --- git-repository/src/config.rs | 2 +- git-repository/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/git-repository/src/config.rs b/git-repository/src/config.rs index e4d7147c83b..50e568c55b5 100644 --- a/git-repository/src/config.rs +++ b/git-repository/src/config.rs @@ -101,7 +101,7 @@ mod cache { } if hex_len_str.as_ref() != "auto" { let value_bytes = hex_len_str.as_ref(); - if let Ok(Boolean::False(_)) = Boolean::try_from(value_bytes) { + if let Ok(false) = Boolean::try_from(value_bytes).map(Into::into) { hex_len = object_hash.len_in_hex().into(); } else { let value = Integer::try_from(value_bytes) diff --git a/git-repository/src/lib.rs b/git-repository/src/lib.rs index 7840db71f78..7b0bcb99e54 100644 --- a/git-repository/src/lib.rs +++ b/git-repository/src/lib.rs @@ -445,7 +445,7 @@ pub mod discover { .and_then(|v| Vec::from_os_string(v).ok().map(BString::from)) { if let Ok(b) = git_config::Boolean::try_from(cross_fs) { - opts.cross_fs = b.to_bool(); + opts.cross_fs = b.into(); } } opts From 08441def5d1738bbf13b68979f2d1ff7ff3b4153 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 9 Jul 2022 13:20:47 +0800 Subject: [PATCH 116/366] thanks clippy --- git-config/tests/file/access/read_only.rs | 15 +++++++-------- git-config/tests/values/boolean.rs | 10 +++++----- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/git-config/tests/file/access/read_only.rs b/git-config/tests/file/access/read_only.rs index 0bd9bb410c3..59272e4644f 100644 --- a/git-config/tests/file/access/read_only.rs +++ b/git-config/tests/file/access/read_only.rs @@ -25,16 +25,15 @@ fn get_value_for_all_provided_values() -> crate::Result { let config = git_config::parse::Events::from_bytes_owned(config.as_bytes(), None).map(File::from)?; - assert_eq!(config.value::("core", None, "bool-explicit")?.0, false); + assert!(!config.value::("core", None, "bool-explicit")?.0); assert!(!config.boolean("core", None, "bool-explicit").expect("exists")?); - assert_eq!(config.value::("core", None, "bool-implicit")?.0, true); - assert_eq!( + assert!(config.value::("core", None, "bool-implicit")?.0); + assert!( config .try_value::("core", None, "bool-implicit") .expect("exists")? - .0, - true + .0 ); assert!(config.boolean("core", None, "bool-implicit").expect("present")?); @@ -159,9 +158,9 @@ fn get_value_looks_up_all_sections_before_failing() -> crate::Result { let file = File::try_from(config)?; // Checks that we check the last entry first still - assert_eq!(file.value::("core", None, "bool-implicit")?.0, true); + assert!(file.value::("core", None, "bool-implicit")?.0); - assert_eq!(file.value::("core", None, "bool-explicit")?.0, false); + assert!(!file.value::("core", None, "bool-explicit")?.0); Ok(()) } @@ -200,7 +199,7 @@ fn single_section() -> Result<(), Box> { let second_value: Boolean = config.value("core", None, "c")?; assert_eq!(first_value, String { value: cow_str("b") }); - assert_eq!(second_value.0, true); + assert!(second_value.0); Ok(()) } diff --git a/git-config/tests/values/boolean.rs b/git-config/tests/values/boolean.rs index f7675312a12..25637d3cd3b 100644 --- a/git-config/tests/values/boolean.rs +++ b/git-config/tests/values/boolean.rs @@ -6,11 +6,11 @@ use crate::value::b; #[test] fn from_str_false() -> crate::Result { - assert_eq!(Boolean::try_from(b("no"))?.0, false); - assert_eq!(Boolean::try_from(b("off"))?.0, false); - assert_eq!(Boolean::try_from(b("false"))?.0, false); - assert_eq!(Boolean::try_from(b("zero"))?.0, false); - assert_eq!(Boolean::try_from(b("\"\""))?.0, false); + assert!(!Boolean::try_from(b("no"))?.0); + assert!(!Boolean::try_from(b("off"))?.0); + assert!(!Boolean::try_from(b("false"))?.0); + assert!(!Boolean::try_from(b("zero"))?.0); + assert!(!Boolean::try_from(b("\"\""))?.0); Ok(()) } From f9e0ef38e97fbc1e123d310dc696270d496438b6 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 9 Jul 2022 15:19:41 +0800 Subject: [PATCH 117/366] =?UTF-8?q?change!=20Add=20`home=5Ffor=5Fuser`=20i?= =?UTF-8?q?n=20`Path::interpolate(=E2=80=A6)`.=20(#331)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit That way it's possible to turn that functionality off based on security settings of the caller for example. --- git-config/src/file/from_paths.rs | 16 ++++- git-config/src/file/resolve_includes.rs | 7 +- git-config/src/values/mod.rs | 2 +- git-config/src/values/path.rs | 80 +++++++++++++++-------- git-config/tests/file/access/read_only.rs | 2 +- git-config/tests/values/path.rs | 43 ++++++------ 6 files changed, 95 insertions(+), 55 deletions(-) diff --git a/git-config/src/file/from_paths.rs b/git-config/src/file/from_paths.rs index 23c930938fa..390d997a072 100644 --- a/git-config/src/file/from_paths.rs +++ b/git-config/src/file/from_paths.rs @@ -1,5 +1,6 @@ use crate::{file::resolve_includes, File}; use crate::{parse, path::interpolate}; +use std::path::PathBuf; /// The error returned by [`File::from_paths()`][crate::File::from_paths()] and [`File::from_env_paths()`][crate::File::from_env_paths()]. #[derive(Debug, thiserror::Error)] @@ -25,6 +26,8 @@ pub enum Error { #[derive(Clone, Copy)] pub struct Options<'a> { /// The location where gitoxide or git is installed + /// + /// Used during path interpolation. pub git_install_dir: Option<&'a std::path::Path>, /// The maximum allowed length of the file include chain built by following nested resolve_includes where base level is depth = 0. pub max_depth: u8, @@ -34,14 +37,24 @@ pub struct Options<'a> { /// Setting this value to false allows to read configuration with cycles, which otherwise always results in an error. pub error_on_max_depth_exceeded: bool, /// The location of the .git directory + /// + /// Used for conditional includes, e.g. `gitdir:` or `gitdir/i`. pub git_dir: Option<&'a std::path::Path>, /// The name of the branch that is currently checked out + /// + /// Used for conditional includes, e.g. `onbranch:` pub branch_name: Option<&'a git_ref::FullNameRef>, /// The home directory of the current user. + /// + /// Used during path interpolation. pub home_dir: Option<&'a std::path::Path>, + /// A function returning the home directory of a given user. + /// + /// Used during path interpolation. + pub home_for_user: Option Option>, } -impl<'a> Default for Options<'a> { +impl Default for Options<'_> { fn default() -> Self { Options { git_install_dir: None, @@ -50,6 +63,7 @@ impl<'a> Default for Options<'a> { git_dir: None, branch_name: None, home_dir: None, + home_for_user: Some(interpolate::home_for_user), // TODO: make this opt-in } } } diff --git a/git-config/src/file/resolve_includes.rs b/git-config/src/file/resolve_includes.rs index 6fedcd7afbf..0e8687879c4 100644 --- a/git-config/src/file/resolve_includes.rs +++ b/git-config/src/file/resolve_includes.rs @@ -159,6 +159,7 @@ fn gitdir_matches( git_install_dir, git_dir, home_dir, + home_for_user, .. }: from_paths::Options<'_>, wildmatch_mode: git_glob::wildmatch::Mode, @@ -167,7 +168,8 @@ fn gitdir_matches( git_path::to_unix_separators_on_windows(git_path::into_bstr(git_dir.ok_or(from_paths::Error::MissingGitDir)?)); let mut pattern_path: Cow<'_, _> = { - let path = crate::Path::from(Cow::Borrowed(condition_path)).interpolate(git_install_dir, home_dir)?; + let path = + crate::Path::from(Cow::Borrowed(condition_path)).interpolate(git_install_dir, home_dir, home_for_user)?; git_path::into_bstr(path).into_owned().into() }; // NOTE: yes, only if we do path interpolation will the slashes be forced to unix separators on windows @@ -220,10 +222,11 @@ fn resolve( from_paths::Options { git_install_dir, home_dir, + home_for_user, .. }: from_paths::Options<'_>, ) -> Result { - let path = path.interpolate(git_install_dir, home_dir)?; + let path = path.interpolate(git_install_dir, home_dir, home_for_user)?; let path: PathBuf = if path.is_relative() { target_config_path .ok_or(from_paths::Error::MissingConfigPath)? diff --git a/git-config/src/values/mod.rs b/git-config/src/values/mod.rs index 31d4c6d4436..ccb84b7c270 100644 --- a/git-config/src/values/mod.rs +++ b/git-config/src/values/mod.rs @@ -52,7 +52,7 @@ pub struct Integer { #[allow(missing_docs)] pub struct Boolean(pub bool); -/// Any value that can be interpreted as a file path. +/// Any value that can be interpreted as a path to a resource on disk. /// /// Git represents file paths as byte arrays, modeled here as owned or borrowed byte sequences. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] diff --git a/git-config/src/values/path.rs b/git-config/src/values/path.rs index 30810878548..8658a470652 100644 --- a/git-config/src/values/path.rs +++ b/git-config/src/values/path.rs @@ -1,9 +1,12 @@ use crate::Path; use bstr::BStr; use std::borrow::Cow; +use std::path::PathBuf; /// pub mod interpolate { + use std::path::PathBuf; + /// The error returned by [`Path::interpolate()`][crate::Path::interpolate()]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] @@ -21,6 +24,34 @@ pub mod interpolate { #[error("User interpolation is not available on this platform")] UserInterpolationUnsupported, } + + /// Obtain the home directory for the given user `name` or return `None` if the user wasn't found + /// or any other error occurred. + /// It can be used as `home_for_user` parameter in [`Path::interpolate()`]. + pub fn home_for_user(name: &str) -> Option { + #[cfg(not(any(target_os = "android", target_os = "windows")))] + { + let cname = std::ffi::CString::new(name).ok()?; + // SAFETY: calling this in a threaded program that modifies the pw database is not actually safe. + // TODO: use the `*_r` version, but it's much harder to use. + #[allow(unsafe_code)] + let pwd = unsafe { libc::getpwnam(cname.as_ptr()) }; + if pwd.is_null() { + None + } else { + use std::os::unix::ffi::OsStrExt; + // SAFETY: pw_dir is a cstr and it lives as long as… well, we hope nobody changes the pw database while we are at it + // from another thread. Otherwise it lives long enough. + #[allow(unsafe_code)] + let cstr = unsafe { std::ffi::CStr::from_ptr((*pwd).pw_dir) }; + Some(std::ffi::OsStr::from_bytes(cstr.to_bytes()).into()) + } + } + #[cfg(any(target_os = "android", target_os = "windows"))] + { + None + } + } } impl<'a> std::ops::Deref for Path<'a> { @@ -50,21 +81,25 @@ impl<'a> From> for Path<'a> { } impl<'a> Path<'a> { - /// Interpolates this path into a file system path. + /// Interpolates this path into a path usable on the file system. /// /// If this path starts with `~/` or `~user/` or `%(prefix)/` /// - `~/` is expanded to the value of `home_dir`. The caller can use the [dirs](https://crates.io/crates/dirs) crate to obtain it. /// It it is required but not set, an error is produced. - /// - `~user/` to the specified user’s home directory, e.g `~alice` might get expanded to `/home/alice` on linux. - /// The interpolation uses `getpwnam` sys call and is therefore not available on windows. See also [pwd](https://crates.io/crates/pwd). - /// - `%(prefix)/` is expanded to the location where gitoxide is installed. This location is not known at compile time and therefore need to be - /// optionally provided by the caller through `git_install_dir`. + /// - `~user/` to the specified user’s home directory, e.g `~alice` might get expanded to `/home/alice` on linux, but requires + /// the `home_for_user` function to be provided. + /// The interpolation uses `getpwnam` sys call and is therefore not available on windows. + /// - `%(prefix)/` is expanded to the location where `gitoxide` is installed. + /// This location is not known at compile time and therefore need to be + /// optionally provided by the caller through `git_install_dir`. /// - /// Any other, non-empty path value is returned unchanged and error is returned in case of an empty path value. + /// Any other, non-empty path value is returned unchanged and error is returned in case of an empty path value or if required input + /// wasn't provided. pub fn interpolate( self, git_install_dir: Option<&std::path::Path>, home_dir: Option<&std::path::Path>, + home_for_user: Option Option>, ) -> Result, interpolate::Error> { if self.is_empty() { return Err(interpolate::Error::Missing { what: "path" }); @@ -94,38 +129,27 @@ impl<'a> Path<'a> { })?; Ok(home_path.join(val).into()) } else if self.starts_with(b"~") && self.contains(&b'/') { - self.interpolate_user() + self.interpolate_user(home_for_user.ok_or(interpolate::Error::Missing { + what: "home for user lookup", + })?) } else { Ok(git_path::from_bstr(self.value)) } } #[cfg(any(target_os = "windows", target_os = "android"))] - fn interpolate_user(self) -> Result, interpolate::Error> { + fn interpolate_user( + self, + _home_for_user: fn(&str) -> Option, + ) -> Result, interpolate::Error> { Err(interpolate::Error::UserInterpolationUnsupported) } #[cfg(not(windows))] - fn interpolate_user(self) -> Result, interpolate::Error> { - #[cfg(not(any(target_os = "android", target_os = "windows")))] - fn home_for_user(name: &str) -> Option { - let cname = std::ffi::CString::new(name).ok()?; - // SAFETY: calling this in a threaded program that modifies the pw database is not actually safe. - // TODO: use the `*_r` version, but it's much harder to use. - #[allow(unsafe_code)] - let pwd = unsafe { libc::getpwnam(cname.as_ptr()) }; - if pwd.is_null() { - None - } else { - use std::os::unix::ffi::OsStrExt; - // SAFETY: pw_dir is a cstr and it lives as long as… well, we hope nobody changes the pw database while we are at it - // from another thread. Otherwise it lives long enough. - #[allow(unsafe_code)] - let cstr = unsafe { std::ffi::CStr::from_ptr((*pwd).pw_dir) }; - Some(std::ffi::OsStr::from_bytes(cstr.to_bytes()).into()) - } - } - + fn interpolate_user( + self, + home_for_user: fn(&str) -> Option, + ) -> Result, interpolate::Error> { let (_prefix, val) = self.split_at("/".len()); let i = val .iter() diff --git a/git-config/tests/file/access/read_only.rs b/git-config/tests/file/access/read_only.rs index 59272e4644f..ca5e25de8fd 100644 --- a/git-config/tests/file/access/read_only.rs +++ b/git-config/tests/file/access/read_only.rs @@ -128,7 +128,7 @@ fn get_value_for_all_provided_values() -> crate::Result { let home = std::env::current_dir()?; let expected = home.join("tmp"); assert!(matches!(actual.value, Cow::Borrowed(_))); - assert_eq!(actual.interpolate(None, home.as_path().into()).unwrap(), expected); + assert_eq!(actual.interpolate(None, home.as_path().into(), None).unwrap(), expected); } let actual = config.path("core", None, "location").expect("present"); diff --git a/git-config/tests/values/path.rs b/git-config/tests/values/path.rs index cb4d15d7549..96b4a4a2d92 100644 --- a/git-config/tests/values/path.rs +++ b/git-config/tests/values/path.rs @@ -1,6 +1,6 @@ mod interpolate { use std::borrow::Cow; - use std::path::Path; + use std::path::{Path, PathBuf}; use crate::file::cow_str; use crate::value::b; @@ -9,7 +9,7 @@ mod interpolate { #[test] fn backslash_is_not_special_and_they_are_not_escaping_anything() -> crate::Result { for path in ["C:\\foo\\bar", "/foo/bar"] { - let actual = git_config::Path::from(Cow::Borrowed(b(path))).interpolate(None, None)?; + let actual = git_config::Path::from(Cow::Borrowed(b(path))).interpolate(None, None, None)?; assert_eq!(actual, Path::new(path)); assert!( matches!(actual, Cow::Borrowed(_)), @@ -35,7 +35,7 @@ mod interpolate { std::path::PathBuf::from(format!("{}{}{}", git_install_dir, std::path::MAIN_SEPARATOR, expected)); assert_eq!( git_config::Path::from(cow_str(val)) - .interpolate(Path::new(git_install_dir).into(), None) + .interpolate(Path::new(git_install_dir).into(), None, None) .unwrap(), expected, "prefix interpolation keeps separators as they are" @@ -50,7 +50,7 @@ mod interpolate { let git_install_dir = "/tmp/git"; assert_eq!( git_config::Path::from(Cow::Borrowed(b(path))) - .interpolate(Path::new(git_install_dir).into(), None) + .interpolate(Path::new(git_install_dir).into(), None, None) .unwrap(), Path::new(path) ); @@ -63,18 +63,18 @@ mod interpolate { } #[test] - fn tilde_slash_substitutes_current_user() { - let path = "~/foo/bar"; - let home = std::env::current_dir().unwrap(); - let expected = format!("{}{}foo/bar", home.display(), std::path::MAIN_SEPARATOR); + fn tilde_slash_substitutes_current_user() -> crate::Result { + let path = "~/user/bar"; + let home = std::env::current_dir()?; + let expected = home.join("user").join("bar"); assert_eq!( git_config::Path::from(cow_str(path)) - .interpolate(None, Some(&home)) + .interpolate(None, Some(&home), Some(home_for_user)) .unwrap() .as_ref(), - Path::new(&expected), - "note that path separators are not turned into slashes as we work with `std::path::Path`" + expected ); + Ok(()) } #[cfg(windows)] @@ -89,18 +89,13 @@ mod interpolate { #[cfg(not(windows))] #[test] fn tilde_with_given_user() -> crate::Result { - let user = std::env::var("USER")?; - let home = std::env::var("HOME")?; - let specific_user_home = format!("~{}", user); + let home = std::env::current_dir()?; for path_suffix in &["foo/bar", "foo\\bar", ""] { - let path = format!("{}{}{}", specific_user_home, std::path::MAIN_SEPARATOR, path_suffix); - let expected = format!("{}{}{}", home, std::path::MAIN_SEPARATOR, path_suffix); - assert_eq!( - interpolate_without_context(path)?, - Path::new(&expected), - "it keeps path separators as is" - ); + let path = format!("~user{}{}", std::path::MAIN_SEPARATOR, path_suffix); + let expected = home.join("user").join(path_suffix); + + assert_eq!(interpolate_without_context(path)?, expected); } Ok(()) } @@ -108,6 +103,10 @@ mod interpolate { fn interpolate_without_context( path: impl AsRef, ) -> Result, git_config::path::interpolate::Error> { - git_config::Path::from(Cow::Owned(path.as_ref().to_owned().into())).interpolate(None, None) + git_config::Path::from(Cow::Owned(path.as_ref().to_owned().into())).interpolate(None, None, Some(home_for_user)) + } + + fn home_for_user(name: &str) -> Option { + std::env::current_dir().unwrap().join(name).into() } } From b78e3fa792fad4f3e3f9d5c668afccd75bc551e0 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 9 Jul 2022 15:26:04 +0800 Subject: [PATCH 118/366] refactor (#331) --- git-config/src/file/utils.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/git-config/src/file/utils.rs b/git-config/src/file/utils.rs index 5bd1414e719..75a93700082 100644 --- a/git-config/src/file/utils.rs +++ b/git-config/src/file/utils.rs @@ -113,13 +113,10 @@ impl<'event> File<'event> { // TODO: add note indicating that probably a lot if not all information about the original files is currently lost, // so can't be written back. This will probably change a lot during refactor, so it's not too important now. pub(crate) fn append(&mut self, mut other: Self) { - let mut section_indices: Vec<_> = other.section_headers.keys().cloned().collect(); - // header keys are numeric and ascend in insertion order, hence sorting them gives the order - // in which they appear in the config file. - section_indices.sort(); - for section_index in section_indices { - let section_header = other.section_headers.remove(§ion_index).expect("present"); - self.push_section_internal(section_header, other.sections.remove(§ion_index).expect("present")); + for id in std::mem::take(&mut other.section_order) { + let header = other.section_headers.remove(&id).expect("present"); + let body = other.sections.remove(&id).expect("present"); + self.push_section_internal(header, body); } } } From 924f14879bd14ca1ff13fdd6ccafe43d6de01b68 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 9 Jul 2022 15:26:20 +0800 Subject: [PATCH 119/366] adjustments due to breaking changes in `git-config` (#331) We also do a first interpolation adjustment based on trust when interpolating a path. This should become much easier though. --- git-repository/src/config.rs | 9 ++++++++- git-repository/src/open.rs | 8 ++++---- git-repository/src/repository/snapshots.rs | 10 +++++++++- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/git-repository/src/config.rs b/git-repository/src/config.rs index 50e568c55b5..01b2623a45a 100644 --- a/git-repository/src/config.rs +++ b/git-repository/src/config.rs @@ -73,7 +73,14 @@ mod cache { let ignore_case = config_bool(&config, "core.ignorecase", false)?; let excludes_file = config .path("core", None, "excludesFile") - .map(|p| p.interpolate(git_install_dir, home.as_deref()).map(|p| p.into_owned())) + .map(|p| { + p.interpolate( + git_install_dir, + home.as_deref(), + Some(git_config::path::interpolate::home_for_user), + ) + .map(|p| p.into_owned()) + }) .transpose()?; let repo_format_version = config .value::("core", None, "repositoryFormatVersion") diff --git a/git-repository/src/open.rs b/git-repository/src/open.rs index ce10b3436f0..ce8e7d40b41 100644 --- a/git-repository/src/open.rs +++ b/git-repository/src/open.rs @@ -63,9 +63,9 @@ impl ReplacementObjects { /// The options used in [`ThreadSafeRepository::open_opts`] #[derive(Default, Clone)] pub struct Options { - object_store_slots: git_odb::store::init::Slots, - replacement_objects: ReplacementObjects, - permissions: Permissions, + pub(crate) object_store_slots: git_odb::store::init::Slots, + pub(crate) replacement_objects: ReplacementObjects, + pub(crate) permissions: Permissions, } #[derive(Default, Clone)] @@ -112,7 +112,7 @@ impl Options { // TODO: tests /// Set the given permissions, which are typically derived by a `Trust` level. - pub fn permissions(mut self, permissions: crate::Permissions) -> Self { + pub fn permissions(mut self, permissions: Permissions) -> Self { self.permissions = permissions; self } diff --git a/git-repository/src/repository/snapshots.rs b/git-repository/src/repository/snapshots.rs index 6b35bc0967a..1d3e2ed5fc5 100644 --- a/git-repository/src/repository/snapshots.rs +++ b/git-repository/src/repository/snapshots.rs @@ -98,7 +98,15 @@ impl crate::Repository { .and_then(|path| { let install_dir = self.install_dir().ok()?; let home = self.config.home_dir(); - match path.interpolate(Some(install_dir.as_path()), home.as_deref()) { + match path.interpolate( + Some(install_dir.as_path()), + home.as_deref(), + if self.linked_worktree_options.permissions.git_dir.is_all() { + Some(git_config::path::interpolate::home_for_user) + } else { + None + }, + ) { Ok(path) => Some(path), Err(e) => { err.get_or_insert(e.into()); From 8fa7600847da6946784466213cea4c32ff9f7f92 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 9 Jul 2022 15:44:49 +0800 Subject: [PATCH 120/366] fix docs (#331) --- git-config/src/values/path.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/git-config/src/values/path.rs b/git-config/src/values/path.rs index 8658a470652..15fe3d29b70 100644 --- a/git-config/src/values/path.rs +++ b/git-config/src/values/path.rs @@ -27,7 +27,8 @@ pub mod interpolate { /// Obtain the home directory for the given user `name` or return `None` if the user wasn't found /// or any other error occurred. - /// It can be used as `home_for_user` parameter in [`Path::interpolate()`]. + /// It can be used as `home_for_user` parameter in [`Path::interpolate()`][crate::Path::interpolate()]. + #[cfg_attr(windows, allow(unused_variables))] pub fn home_for_user(name: &str) -> Option { #[cfg(not(any(target_os = "android", target_os = "windows")))] { From 0915051798dd782b40617a1aa16abd71f6db1175 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 9 Jul 2022 15:56:31 +0800 Subject: [PATCH 121/366] change!: remove `String` type in favor of referring to the `File::string()` method. (#331) The wrapper had no effect whatsoever except for adding complexity. --- .../src/file/access/low_level/read_only.rs | 11 +++-- git-config/src/values/mod.rs | 3 -- git-config/tests/file/access/read_only.rs | 47 +++++-------------- 3 files changed, 20 insertions(+), 41 deletions(-) diff --git a/git-config/src/file/access/low_level/read_only.rs b/git-config/src/file/access/low_level/read_only.rs index 865aa78a3a1..71e24b5f162 100644 --- a/git-config/src/file/access/low_level/read_only.rs +++ b/git-config/src/file/access/low_level/read_only.rs @@ -15,6 +15,8 @@ impl<'event> File<'event> { /// Consider [`Self::multi_value`] if you want to get all values of a /// multivar instead. /// + /// If a `string` is desired, use the [`string()`][Self::string()] method instead. + /// /// # Examples /// /// ``` @@ -66,13 +68,16 @@ impl<'event> File<'event> { /// Consider [`Self::value`] if you want to get a single value /// (following last-one-wins resolution) instead. /// + /// To access plain strings, use the [`strings()`][Self::strings()] method instead. + /// /// # Examples /// /// ``` /// # use git_config::File; - /// # use git_config::{Integer, String, Boolean}; + /// # use git_config::{Integer, Boolean}; /// # use std::borrow::Cow; /// # use std::convert::TryFrom; + /// # use bstr::ByteSlice; /// let config = r#" /// [core] /// a = true @@ -93,8 +98,8 @@ impl<'event> File<'event> { /// ] /// ); /// // ... or explicitly declare the type to avoid the turbofish - /// let c_value: Vec = git_config.multi_value("core", None, "c")?; - /// assert_eq!(c_value, vec![String { value: Cow::Borrowed("g".into()) }]); + /// let c_value = git_config.strings("core", None, "c").unwrap(); + /// assert_eq!(c_value, vec![Cow::Borrowed("g".as_bytes().as_bstr())]); /// # Ok::<(), Box>(()) /// ``` /// diff --git a/git-config/src/values/mod.rs b/git-config/src/values/mod.rs index ccb84b7c270..67ef802a868 100644 --- a/git-config/src/values/mod.rs +++ b/git-config/src/values/mod.rs @@ -1,6 +1,3 @@ -mod string; -pub use string::String; - /// pub mod boolean; /// diff --git a/git-config/tests/file/access/read_only.rs b/git-config/tests/file/access/read_only.rs index ca5e25de8fd..56391983da2 100644 --- a/git-config/tests/file/access/read_only.rs +++ b/git-config/tests/file/access/read_only.rs @@ -1,7 +1,7 @@ use crate::file::cow_str; use bstr::BStr; use git_config::File; -use git_config::{color, integer, Boolean, Color, Integer, String}; +use git_config::{color, integer, Boolean, Color, Integer}; use std::{borrow::Cow, convert::TryFrom, error::Error}; /// Asserts we can cast into all variants of our type @@ -37,7 +37,7 @@ fn get_value_for_all_provided_values() -> crate::Result { ); assert!(config.boolean("core", None, "bool-implicit").expect("present")?); - assert_eq!(config.try_value::("doesnt", None, "exist"), None); + assert_eq!(config.string("doesnt", None, "exist"), None); assert_eq!( config.value::("core", None, "integer-no-prefix")?, @@ -82,27 +82,15 @@ fn get_value_for_all_provided_values() -> crate::Result { } assert_eq!( - config.value::("core", None, "other-quoted")?, - String { - value: cow_str("hello world") - } + config.string("core", None, "other-quoted").unwrap(), + cow_str("hello world") ); { - let strings = config.multi_value::("core", None, "other-quoted")?; - assert_eq!( - strings, - vec![ - String { - value: cow_str("hello") - }, - String { - value: cow_str("hello world") - }, - ] - ); - assert!(matches!(strings[0].value, Cow::Borrowed(_))); - assert!(matches!(strings[1].value, Cow::Borrowed(_))); + let strings = config.strings("core", None, "other-quoted").unwrap(); + assert_eq!(strings, vec![cow_str("hello"), cow_str("hello world")]); + assert!(matches!(strings[0], Cow::Borrowed(_))); + assert!(matches!(strings[1], Cow::Borrowed(_))); } { @@ -114,12 +102,6 @@ fn get_value_for_all_provided_values() -> crate::Result { config.string("core", None, "other-quoted").expect("present").as_ref(), "hello world" ); - { - let strings = config.strings("core", None, "other-quoted").expect("present"); - assert_eq!(strings, vec![cow_str("hello"), cow_str("hello world")]); - assert!(matches!(strings[0], Cow::Borrowed(_))); - assert!(matches!(strings[1], Cow::Borrowed(_))); - } { let actual = config.value::("core", None, "location")?; @@ -195,10 +177,10 @@ fn value_names_are_case_insensitive() -> crate::Result { #[test] fn single_section() -> Result<(), Box> { let config = File::try_from("[core]\na=b\nc").unwrap(); - let first_value: String = config.value("core", None, "a")?; + let first_value = config.string("core", None, "a").unwrap(); let second_value: Boolean = config.value("core", None, "c")?; - assert_eq!(first_value, String { value: cow_str("b") }); + assert_eq!(first_value, cow_str("b")); assert!(second_value.0); Ok(()) @@ -218,13 +200,8 @@ fn sections_by_name() { "#; let config = File::try_from(config).unwrap(); - let value = config.value::("remote", Some("origin"), "url").unwrap(); - assert_eq!( - value, - String { - value: cow_str("git@github.com:Byron/gitoxide.git") - } - ); + let value = config.string("remote", Some("origin"), "url").unwrap(); + assert_eq!(value, cow_str("git@github.com:Byron/gitoxide.git")); } #[test] From a8604a237782f8d60a185d4730db57bad81424a6 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 9 Jul 2022 16:00:08 +0800 Subject: [PATCH 122/366] rename!: `File::multi_value()` to `File::values()`. (#331) The latter is better in line with `string()/strings()` --- git-config/src/file/access/comfort.rs | 4 ++-- git-config/src/file/access/low_level/read_only.rs | 7 +++---- git-config/tests/file/access/read_only.rs | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/git-config/src/file/access/comfort.rs b/git-config/src/file/access/comfort.rs index 99a70dbcedf..82b4ada2c8e 100644 --- a/git-config/src/file/access/comfort.rs +++ b/git-config/src/file/access/comfort.rs @@ -55,14 +55,14 @@ impl<'a> File<'a> { })) } - /// Similar to [`multi_value(…)`][File::multi_value()] but returning strings if at least one of them was found. + /// Similar to [`values(…)`][File::values()] but returning strings if at least one of them was found. pub fn strings(&self, section_name: &str, subsection_name: Option<&str>, key: &str) -> Option>> { self.raw_multi_value(section_name, subsection_name, key) .ok() .map(|values| values.into_iter().map(normalize).collect()) } - /// Similar to [`multi_value(…)`][File::multi_value()] but returning integers if at least one of them was found + /// Similar to [`values(…)`][File::values()] but returning integers if at least one of them was found /// and if none of them overflows. pub fn integers( &self, diff --git a/git-config/src/file/access/low_level/read_only.rs b/git-config/src/file/access/low_level/read_only.rs index 71e24b5f162..10e3123e6b5 100644 --- a/git-config/src/file/access/low_level/read_only.rs +++ b/git-config/src/file/access/low_level/read_only.rs @@ -12,8 +12,7 @@ impl<'event> File<'event> { /// the conversion is already implemented, but this function is flexible and /// will accept any type that implements [`TryFrom<&[u8]>`][`TryFrom`]. /// - /// Consider [`Self::multi_value`] if you want to get all values of a - /// multivar instead. + /// Consider [`Self::values`] if you want to get all values of a multivar instead. /// /// If a `string` is desired, use the [`string()`][Self::string()] method instead. /// @@ -88,7 +87,7 @@ impl<'event> File<'event> { /// "#; /// let git_config = git_config::File::try_from(config).unwrap(); /// // You can either use the turbofish to determine the type... - /// let a_value = git_config.multi_value::("core", None, "a")?; + /// let a_value = git_config.values::("core", None, "a")?; /// assert_eq!( /// a_value, /// vec![ @@ -105,7 +104,7 @@ impl<'event> File<'event> { /// /// [`value`]: crate::value /// [`TryFrom`]: std::convert::TryFrom - pub fn multi_value<'a, T: TryFrom>>( + pub fn values<'a, T: TryFrom>>( &'a self, section_name: &str, subsection_name: Option<&str>, diff --git a/git-config/tests/file/access/read_only.rs b/git-config/tests/file/access/read_only.rs index 56391983da2..2b8ae8b570a 100644 --- a/git-config/tests/file/access/read_only.rs +++ b/git-config/tests/file/access/read_only.rs @@ -165,7 +165,7 @@ fn value_names_are_case_insensitive() -> crate::Result { a = true A = false"; let file = File::try_from(config)?; - assert_eq!(file.multi_value::("core", None, "a")?.len(), 2); + assert_eq!(file.values::("core", None, "a")?.len(), 2); assert_eq!( file.value::("core", None, "a").unwrap(), file.value::("core", None, "A").unwrap() From 0076dcf9b37f1d633bdad5573b40d34a9fbaba90 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 9 Jul 2022 16:02:10 +0800 Subject: [PATCH 123/366] rename!: `File::raw_multi_value_mut()` to `File::raw_values_mut()` (#331) --- git-config/src/file/access/raw.rs | 10 +++++----- .../file/access/raw/mutable_multi_value.rs | 18 +++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/git-config/src/file/access/raw.rs b/git-config/src/file/access/raw.rs index 3b617461373..433a904f2f3 100644 --- a/git-config/src/file/access/raw.rs +++ b/git-config/src/file/access/raw.rs @@ -49,7 +49,7 @@ impl<'event> File<'event> { /// Returns a mutable reference to an uninterpreted value given a section, /// an optional subsection and key. /// - /// Consider [`Self::raw_multi_value_mut`] if you want to get mutable + /// Consider [`Self::raw_values_mut`] if you want to get mutable /// references to all values of a multivar instead. pub fn raw_value_mut<'lookup>( &mut self, @@ -199,7 +199,7 @@ impl<'event> File<'event> { /// ] /// ); /// - /// git_config.raw_multi_value_mut("core", None, "a")?.set_str_all("g"); + /// git_config.raw_values_mut("core", None, "a")?.set_str_all("g"); /// /// assert_eq!( /// git_config.raw_multi_value("core", None, "a")?, @@ -217,7 +217,7 @@ impl<'event> File<'event> { /// /// Note that this operation is relatively expensive, requiring a full /// traversal of the config. - pub fn raw_multi_value_mut<'lookup>( + pub fn raw_values_mut<'lookup>( &mut self, section_name: &'lookup str, subsection_name: Option<&'lookup str>, @@ -392,7 +392,7 @@ impl<'event> File<'event> { /// # Ok::<(), git_config::lookup::existing::Error>(()) /// ``` /// - /// [`raw_multi_value_mut`]: Self::raw_multi_value_mut + /// [`raw_multi_value_mut`]: Self::raw_values_mut pub fn set_raw_multi_value( &mut self, section_name: &str, @@ -400,7 +400,7 @@ impl<'event> File<'event> { key: &str, new_values: impl Iterator>, ) -> Result<(), lookup::existing::Error> { - self.raw_multi_value_mut(section_name, subsection_name, key) + self.raw_values_mut(section_name, subsection_name, key) .map(|mut v| v.set_values(new_values)) } } diff --git a/git-config/tests/file/access/raw/mutable_multi_value.rs b/git-config/tests/file/access/raw/mutable_multi_value.rs index 01a5bbfa345..34d7d0e0368 100644 --- a/git-config/tests/file/access/raw/mutable_multi_value.rs +++ b/git-config/tests/file/access/raw/mutable_multi_value.rs @@ -12,7 +12,7 @@ fn init_config() -> File<'static> { fn value_is_correct() { let mut git_config = init_config(); - let value = git_config.raw_multi_value_mut("core", None, "a").unwrap(); + let value = git_config.raw_values_mut("core", None, "a").unwrap(); assert_eq!( &*value.get().unwrap(), vec![ @@ -26,14 +26,14 @@ fn value_is_correct() { #[test] fn non_empty_sizes_are_correct() { let mut git_config = init_config(); - assert_eq!(git_config.raw_multi_value_mut("core", None, "a").unwrap().len(), 3); - assert!(!git_config.raw_multi_value_mut("core", None, "a").unwrap().is_empty()); + assert_eq!(git_config.raw_values_mut("core", None, "a").unwrap().len(), 3); + assert!(!git_config.raw_values_mut("core", None, "a").unwrap().is_empty()); } #[test] fn set_value_at_start() { let mut git_config = init_config(); - let mut values = git_config.raw_multi_value_mut("core", None, "a").unwrap(); + let mut values = git_config.raw_values_mut("core", None, "a").unwrap(); values.set_string(0, "Hello".to_string()); assert_eq!( git_config.to_string(), @@ -44,7 +44,7 @@ fn set_value_at_start() { #[test] fn set_value_at_end() { let mut git_config = init_config(); - let mut values = git_config.raw_multi_value_mut("core", None, "a").unwrap(); + let mut values = git_config.raw_values_mut("core", None, "a").unwrap(); values.set_string(2, "Hello".to_string()); assert_eq!( git_config.to_string(), @@ -55,7 +55,7 @@ fn set_value_at_end() { #[test] fn set_values_all() { let mut git_config = init_config(); - let mut values = git_config.raw_multi_value_mut("core", None, "a").unwrap(); + let mut values = git_config.raw_values_mut("core", None, "a").unwrap(); values.set_owned_values_all("Hello"); assert_eq!( git_config.to_string(), @@ -66,7 +66,7 @@ fn set_values_all() { #[test] fn delete() { let mut git_config = init_config(); - let mut values = git_config.raw_multi_value_mut("core", None, "a").unwrap(); + let mut values = git_config.raw_values_mut("core", None, "a").unwrap(); values.delete(0); assert_eq!( git_config.to_string(), @@ -77,7 +77,7 @@ fn delete() { #[test] fn delete_all() { let mut git_config = init_config(); - let mut values = git_config.raw_multi_value_mut("core", None, "a").unwrap(); + let mut values = git_config.raw_values_mut("core", None, "a").unwrap(); values.delete_all(); assert!(values.get().is_err()); assert_eq!(git_config.to_string(), "[core]\n \n [core]\n \n ",); @@ -96,7 +96,7 @@ b a"#, ) .unwrap(); - let mut values = git_config.raw_multi_value_mut("core", None, "a").unwrap(); + let mut values = git_config.raw_values_mut("core", None, "a").unwrap(); assert_eq!( &*values.get().unwrap(), From 9cd99337333f5ef4b30e0ec9461fc087699576e6 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 9 Jul 2022 16:03:52 +0800 Subject: [PATCH 124/366] rename!: `File::raw_multi_value()` to `File::raw_values()` (#331) --- git-config/src/file/access/comfort.rs | 24 +++++++++---------- .../src/file/access/low_level/read_only.rs | 2 +- git-config/src/file/access/raw.rs | 16 ++++++------- .../tests/file/access/raw/raw_multi_value.rs | 21 +++++++--------- 4 files changed, 29 insertions(+), 34 deletions(-) diff --git a/git-config/src/file/access/comfort.rs b/git-config/src/file/access/comfort.rs index 82b4ada2c8e..e323f50fd46 100644 --- a/git-config/src/file/access/comfort.rs +++ b/git-config/src/file/access/comfort.rs @@ -57,7 +57,7 @@ impl<'a> File<'a> { /// Similar to [`values(…)`][File::values()] but returning strings if at least one of them was found. pub fn strings(&self, section_name: &str, subsection_name: Option<&str>, key: &str) -> Option>> { - self.raw_multi_value(section_name, subsection_name, key) + self.raw_values(section_name, subsection_name, key) .ok() .map(|values| values.into_iter().map(normalize).collect()) } @@ -70,18 +70,16 @@ impl<'a> File<'a> { subsection_name: Option<&str>, key: &str, ) -> Option, value::Error>> { - self.raw_multi_value(section_name, subsection_name, key) - .ok() - .map(|values| { - values - .into_iter() - .map(|v| { - crate::Integer::try_from(v.as_ref()).and_then(|int| { - int.to_decimal() - .ok_or_else(|| value::Error::new("Integer overflow", v.into_owned())) - }) + self.raw_values(section_name, subsection_name, key).ok().map(|values| { + values + .into_iter() + .map(|v| { + crate::Integer::try_from(v.as_ref()).and_then(|int| { + int.to_decimal() + .ok_or_else(|| value::Error::new("Integer overflow", v.into_owned())) }) - .collect() - }) + }) + .collect() + }) } } diff --git a/git-config/src/file/access/low_level/read_only.rs b/git-config/src/file/access/low_level/read_only.rs index 10e3123e6b5..c02cad68fd3 100644 --- a/git-config/src/file/access/low_level/read_only.rs +++ b/git-config/src/file/access/low_level/read_only.rs @@ -110,7 +110,7 @@ impl<'event> File<'event> { subsection_name: Option<&str>, key: &str, ) -> Result, lookup::Error> { - self.raw_multi_value(section_name, subsection_name, key)? + self.raw_values(section_name, subsection_name, key)? .into_iter() .map(T::try_from) .collect::, _>>() diff --git a/git-config/src/file/access/raw.rs b/git-config/src/file/access/raw.rs index 433a904f2f3..c07737ba266 100644 --- a/git-config/src/file/access/raw.rs +++ b/git-config/src/file/access/raw.rs @@ -16,7 +16,7 @@ impl<'event> File<'event> { /// Returns an uninterpreted value given a section, an optional subsection /// and key. /// - /// Consider [`Self::raw_multi_value`] if you want to get all values of + /// Consider [`Self::raw_values()`] if you want to get all values of /// a multivar instead. pub fn raw_value( &self, @@ -132,7 +132,7 @@ impl<'event> File<'event> { /// # use std::convert::TryFrom; /// # let git_config = git_config::File::try_from("[core]a=b\n[core]\na=c\na=d").unwrap(); /// assert_eq!( - /// git_config.raw_multi_value("core", None, "a").unwrap(), + /// git_config.raw_values("core", None, "a").unwrap(), /// vec![ /// Cow::<[u8]>::Borrowed(b"b"), /// Cow::<[u8]>::Borrowed(b"c"), @@ -143,7 +143,7 @@ impl<'event> File<'event> { /// /// Consider [`Self::raw_value`] if you want to get the resolved single /// value for a given key, if your key does not support multi-valued values. - pub fn raw_multi_value( + pub fn raw_values( &self, section_name: &str, subsection_name: Option<&str>, @@ -191,7 +191,7 @@ impl<'event> File<'event> { /// # use bstr::BStr; /// # let mut git_config = git_config::File::try_from("[core]a=b\n[core]\na=c\na=d").unwrap(); /// assert_eq!( - /// git_config.raw_multi_value("core", None, "a")?, + /// git_config.raw_values("core", None, "a")?, /// vec![ /// Cow::::Borrowed("b".into()), /// Cow::::Borrowed("c".into()), @@ -202,7 +202,7 @@ impl<'event> File<'event> { /// git_config.raw_values_mut("core", None, "a")?.set_str_all("g"); /// /// assert_eq!( - /// git_config.raw_multi_value("core", None, "a")?, + /// git_config.raw_values("core", None, "a")?, /// vec![ /// Cow::::Borrowed("g".into()), /// Cow::::Borrowed("g".into()), @@ -347,7 +347,7 @@ impl<'event> File<'event> { /// Cow::::Borrowed("z".into()), /// ]; /// git_config.set_raw_multi_value("core", None, "a", new_values.into_iter())?; - /// let fetched_config = git_config.raw_multi_value("core", None, "a")?; + /// let fetched_config = git_config.raw_values("core", None, "a")?; /// assert!(fetched_config.contains(&Cow::::Borrowed("x".into()))); /// assert!(fetched_config.contains(&Cow::::Borrowed("y".into()))); /// assert!(fetched_config.contains(&Cow::::Borrowed("z".into()))); @@ -367,7 +367,7 @@ impl<'event> File<'event> { /// Cow::::Borrowed("y".into()), /// ]; /// git_config.set_raw_multi_value("core", None, "a", new_values.into_iter())?; - /// let fetched_config = git_config.raw_multi_value("core", None, "a")?; + /// let fetched_config = git_config.raw_values("core", None, "a")?; /// assert!(fetched_config.contains(&Cow::::Borrowed("x".into()))); /// assert!(fetched_config.contains(&Cow::::Borrowed("y".into()))); /// # Ok::<(), git_config::lookup::existing::Error>(()) @@ -388,7 +388,7 @@ impl<'event> File<'event> { /// Cow::::Borrowed("discarded".into()), /// ]; /// git_config.set_raw_multi_value("core", None, "a", new_values.into_iter())?; - /// assert!(!git_config.raw_multi_value("core", None, "a")?.contains(&Cow::::Borrowed("discarded".into()))); + /// assert!(!git_config.raw_values("core", None, "a")?.contains(&Cow::::Borrowed("discarded".into()))); /// # Ok::<(), git_config::lookup::existing::Error>(()) /// ``` /// diff --git a/git-config/tests/file/access/raw/raw_multi_value.rs b/git-config/tests/file/access/raw/raw_multi_value.rs index 3023acfc67f..3d9a2dd18d0 100644 --- a/git-config/tests/file/access/raw/raw_multi_value.rs +++ b/git-config/tests/file/access/raw/raw_multi_value.rs @@ -8,7 +8,7 @@ fn single_value_is_identical_to_single_value_query() { let config = File::try_from("[core]\na=b\nc=d").unwrap(); assert_eq!( vec![config.raw_value("core", None, "a").unwrap()], - config.raw_multi_value("core", None, "a").unwrap() + config.raw_values("core", None, "a").unwrap() ); } @@ -16,7 +16,7 @@ fn single_value_is_identical_to_single_value_query() { fn multi_value_in_section() { let config = File::try_from("[core]\na=b\na=c").unwrap(); assert_eq!( - config.raw_multi_value("core", None, "a").unwrap(), + config.raw_values("core", None, "a").unwrap(), vec![cow_str("b"), cow_str("c")] ); } @@ -25,7 +25,7 @@ fn multi_value_in_section() { fn multi_value_across_sections() { let config = File::try_from("[core]\na=b\na=c\n[core]a=d").unwrap(); assert_eq!( - config.raw_multi_value("core", None, "a").unwrap(), + config.raw_values("core", None, "a").unwrap(), vec![cow_str("b"), cow_str("c"), cow_str("d")] ); } @@ -34,7 +34,7 @@ fn multi_value_across_sections() { fn section_not_found() { let config = File::try_from("[core]\na=b\nc=d").unwrap(); assert!(matches!( - config.raw_multi_value("foo", None, "a"), + config.raw_values("foo", None, "a"), Err(lookup::existing::Error::SectionMissing) )); } @@ -43,7 +43,7 @@ fn section_not_found() { fn subsection_not_found() { let config = File::try_from("[core]\na=b\nc=d").unwrap(); assert!(matches!( - config.raw_multi_value("core", Some("a"), "a"), + config.raw_values("core", Some("a"), "a"), Err(lookup::existing::Error::SubSectionMissing) )); } @@ -52,7 +52,7 @@ fn subsection_not_found() { fn key_not_found() { let config = File::try_from("[core]\na=b\nc=d").unwrap(); assert!(matches!( - config.raw_multi_value("core", None, "aaaaaa"), + config.raw_values("core", None, "aaaaaa"), Err(lookup::existing::Error::KeyMissing) )); } @@ -60,18 +60,15 @@ fn key_not_found() { #[test] fn subsection_must_be_respected() { let config = File::try_from("[core]a=b\n[core.a]a=c").unwrap(); - assert_eq!(config.raw_multi_value("core", None, "a").unwrap(), vec![cow_str("b")]); - assert_eq!( - config.raw_multi_value("core", Some("a"), "a").unwrap(), - vec![cow_str("c")] - ); + assert_eq!(config.raw_values("core", None, "a").unwrap(), vec![cow_str("b")]); + assert_eq!(config.raw_values("core", Some("a"), "a").unwrap(), vec![cow_str("c")]); } #[test] fn non_relevant_subsection_is_ignored() { let config = File::try_from("[core]\na=b\na=c\n[core]a=d\n[core]g=g").unwrap(); assert_eq!( - config.raw_multi_value("core", None, "a").unwrap(), + config.raw_values("core", None, "a").unwrap(), vec![cow_str("b"), cow_str("c"), cow_str("d")] ); } From 8e84fdadfc49ba61f258286acb0a707bfb2a396b Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 9 Jul 2022 16:11:24 +0800 Subject: [PATCH 125/366] refactor (#331) --- git-config/src/lib.rs | 4 +-- git-config/src/types.rs | 53 +++++++++++++++++++++++++++++++++ git-config/src/value/mod.rs | 2 +- git-config/src/values/mod.rs | 51 ------------------------------- git-config/src/values/string.rs | 15 ---------- 5 files changed, 56 insertions(+), 69 deletions(-) delete mode 100644 git-config/src/values/string.rs diff --git a/git-config/src/lib.rs b/git-config/src/lib.rs index f63407fc3c6..cb03decee78 100644 --- a/git-config/src/lib.rs +++ b/git-config/src/lib.rs @@ -46,10 +46,10 @@ pub mod parse; /// pub mod value; mod values; -pub use values::*; +pub use values::{boolean, color, integer, path}; mod types; -pub use types::File; +pub use types::{Boolean, Color, File, Integer, Path}; mod permissions; pub use permissions::Permissions; diff --git a/git-config/src/types.rs b/git-config/src/types.rs index 09195bfcab2..68130b9f23c 100644 --- a/git-config/src/types.rs +++ b/git-config/src/types.rs @@ -1,7 +1,9 @@ use std::collections::{HashMap, VecDeque}; use crate::{ + color, file::{SectionBody, SectionBodyId, SectionBodyIds}, + integer, parse::section, }; @@ -63,3 +65,54 @@ pub struct File<'event> { /// Section order for output ordering. pub(crate) section_order: VecDeque, } + +/// Any value that may contain a foreground color, background color, a +/// collection of color (text) modifiers, or a combination of any of the +/// aforementioned values, like `red` or `brightgreen`. +/// +/// Note that `git-config` allows color values to simply be a collection of +/// [`color::Attribute`]s, and does not require a [`color::Name`] for either the +/// foreground or background color. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] +pub struct Color { + /// A provided foreground color + pub foreground: Option, + /// A provided background color + pub background: Option, + /// A potentially empty set of text attributes + pub attributes: color::Attribute, +} + +/// Any value that can be interpreted as an integer. +/// +/// This supports any numeric value that can fit in a [`i64`], excluding the +/// suffix. The suffix is parsed separately from the value itself, so if you +/// wish to obtain the true value of the integer, you must account for the +/// suffix after fetching the value. [`integer::Suffix`] provides +/// [`bitwise_offset()`][integer::Suffix::bitwise_offset] to help with the +/// math, or [to_decimal()][Integer::to_decimal()] for obtaining a usable value in one step. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub struct Integer { + /// The value, without any suffix modification + pub value: i64, + /// A provided suffix, if any. + pub suffix: Option, +} + +/// Any value that can be interpreted as a boolean. +/// +/// Note that while values can effectively be any byte string, the `git-config` +/// documentation has a strict subset of values that may be interpreted as a +/// boolean value, all of which are ASCII and thus UTF-8 representable. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +#[allow(missing_docs)] +pub struct Boolean(pub bool); + +/// Any value that can be interpreted as a path to a resource on disk. +/// +/// Git represents file paths as byte arrays, modeled here as owned or borrowed byte sequences. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub struct Path<'a> { + /// The path string, un-interpolated + pub value: std::borrow::Cow<'a, bstr::BStr>, +} diff --git a/git-config/src/value/mod.rs b/git-config/src/value/mod.rs index 6e38d185871..7ce0ecb6bcd 100644 --- a/git-config/src/value/mod.rs +++ b/git-config/src/value/mod.rs @@ -1,7 +1,7 @@ /// The error returned when any config value couldn't be instantiated due to malformed input. #[derive(Debug, thiserror::Error, Eq, PartialEq)] #[allow(missing_docs)] -#[error("Could not decode '{}': {}", .input, .message)] +#[error("Could not decode '{input}': {message}")] pub struct Error { pub message: &'static str, pub input: bstr::BString, diff --git a/git-config/src/values/mod.rs b/git-config/src/values/mod.rs index 67ef802a868..3b65c2dd407 100644 --- a/git-config/src/values/mod.rs +++ b/git-config/src/values/mod.rs @@ -6,54 +6,3 @@ pub mod color; pub mod integer; /// pub mod path; - -/// Any value that may contain a foreground color, background color, a -/// collection of color (text) modifiers, or a combination of any of the -/// aforementioned values, like `red` or `brightgreen`. -/// -/// Note that `git-config` allows color values to simply be a collection of -/// [`color::Attribute`]s, and does not require a [`color::Name`] for either the -/// foreground or background color. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] -pub struct Color { - /// A provided foreground color - pub foreground: Option, - /// A provided background color - pub background: Option, - /// A potentially empty set of text attributes - pub attributes: color::Attribute, -} - -/// Any value that can be interpreted as an integer. -/// -/// This supports any numeric value that can fit in a [`i64`], excluding the -/// suffix. The suffix is parsed separately from the value itself, so if you -/// wish to obtain the true value of the integer, you must account for the -/// suffix after fetching the value. [`integer::Suffix`] provides -/// [`bitwise_offset()`][integer::Suffix::bitwise_offset] to help with the -/// math, or [to_decimal()][Integer::to_decimal()] for obtaining a usable value in one step. -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] -pub struct Integer { - /// The value, without any suffix modification - pub value: i64, - /// A provided suffix, if any. - pub suffix: Option, -} - -/// Any value that can be interpreted as a boolean. -/// -/// Note that while values can effectively be any byte string, the `git-config` -/// documentation has a strict subset of values that may be interpreted as a -/// boolean value, all of which are ASCII and thus UTF-8 representable. -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] -#[allow(missing_docs)] -pub struct Boolean(pub bool); - -/// Any value that can be interpreted as a path to a resource on disk. -/// -/// Git represents file paths as byte arrays, modeled here as owned or borrowed byte sequences. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] -pub struct Path<'a> { - /// The path string, un-interpolated - pub value: std::borrow::Cow<'a, bstr::BStr>, -} diff --git a/git-config/src/values/string.rs b/git-config/src/values/string.rs deleted file mode 100644 index 3c5816eb59b..00000000000 --- a/git-config/src/values/string.rs +++ /dev/null @@ -1,15 +0,0 @@ -use bstr::BStr; -use std::borrow::Cow; - -/// Any string value -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] -pub struct String<'a> { - /// The string value - pub value: Cow<'a, BStr>, -} - -impl<'a> From> for String<'a> { - fn from(c: Cow<'a, BStr>) -> Self { - String { value: c } - } -} From a361c7ff290cdae071a12351330013ad0043b517 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 9 Jul 2022 18:01:00 +0800 Subject: [PATCH 126/366] review docs of 'parse' module; refactor (#331) --- git-config/src/parse/comment.rs | 3 - git-config/src/parse/error.rs | 24 ++++---- git-config/src/parse/event.rs | 3 - git-config/src/parse/events.rs | 52 +++++++++-------- git-config/src/parse/mod.rs | 54 ++++++++++-------- git-config/src/parse/section.rs | 36 ++---------- git-config/src/parse/tests.rs | 53 ------------------ git-config/tests/parse/mod.rs | 86 ++++++++++++++++++++++++++++- git-config/tests/value/normalize.rs | 7 +++ 9 files changed, 166 insertions(+), 152 deletions(-) diff --git a/git-config/src/parse/comment.rs b/git-config/src/parse/comment.rs index 0aede1fc771..1d802f0f41e 100644 --- a/git-config/src/parse/comment.rs +++ b/git-config/src/parse/comment.rs @@ -15,9 +15,6 @@ impl Comment<'_> { } impl Display for Comment<'_> { - /// Note that this is a best-effort attempt at printing an comment. If - /// there are non UTF-8 values in your config, this will _NOT_ render - /// as read. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.comment_tag.fmt(f)?; if let Ok(s) = std::str::from_utf8(&self.comment) { diff --git a/git-config/src/parse/error.rs b/git-config/src/parse/error.rs index e90ccdf514e..4b047f4dcc2 100644 --- a/git-config/src/parse/error.rs +++ b/git-config/src/parse/error.rs @@ -1,8 +1,7 @@ use crate::parse::Error; use std::fmt::Display; -/// A list of parsers that parsing can fail on. This is used for pretty-printing -/// errors +/// A list of parsers that parsing can fail on. This is used for pretty-printing errors #[derive(PartialEq, Debug, Clone, Copy)] pub(crate) enum ParseNode { SectionHeader, @@ -28,7 +27,7 @@ impl Error { self.line_number + 1 } - /// The remaining data that was left unparsed. + /// The data that was left unparsed, which contains the cause of the parse error. #[must_use] pub fn remaining_data(&self) -> &[u8] { &self.parsed_until @@ -37,8 +36,6 @@ impl Error { impl Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let data_size = self.parsed_until.len(); - let data = std::str::from_utf8(&self.parsed_until); write!( f, "Got an unexpected token on line {} while trying to parse a {}: ", @@ -46,18 +43,19 @@ impl Display for Error { self.last_attempted_parser, )?; + let data_size = self.parsed_until.len(); + let data = std::str::from_utf8(&self.parsed_until); match (data, data_size) { (Ok(data), _) if data_size > 10 => { - write!(f, "'{}' ... ({} characters omitted)", &data[..10], data_size - 10) + write!( + f, + "'{}' ... ({} characters omitted)", + &data.chars().take(10).collect::(), + data_size - 10 + ) } (Ok(data), _) => write!(f, "'{}'", data), - (Err(_), _) if data_size > 10 => write!( - f, - "'{:02x?}' ... ({} characters omitted)", - &self.parsed_until[..10], - data_size - 10 - ), - (Err(_), _) => write!(f, "'{:02x?}'", self.parsed_until), + (Err(_), _) => self.parsed_until.fmt(f), } } } diff --git a/git-config/src/parse/event.rs b/git-config/src/parse/event.rs index 9f972f18348..714bd7c8bc4 100644 --- a/git-config/src/parse/event.rs +++ b/git-config/src/parse/event.rs @@ -30,9 +30,6 @@ impl Event<'_> { } impl Display for Event<'_> { - /// Note that this is a best-effort attempt at printing an `Event`. If - /// there are non UTF-8 values in your config, this will _NOT_ render - /// as read. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Value(e) | Self::ValueNotDone(e) | Self::ValueDone(e) => match std::str::from_utf8(e) { diff --git a/git-config/src/parse/events.rs b/git-config/src/parse/events.rs index e9b462945c0..5e0fc6f6568 100644 --- a/git-config/src/parse/events.rs +++ b/git-config/src/parse/events.rs @@ -14,15 +14,15 @@ pub type FrontMatterEvents<'a> = SmallVec<[Event<'a>; 8]>; /// file, including querying, modifying, and updating values. /// /// This parser guarantees that the events emitted are sufficient to -/// reconstruct a `git-config` file identical to the source `git-config`. +/// reconstruct a `git-config` file identical to the source `git-config` +/// when writing it. /// /// # Differences between a `.ini` parser /// /// While the `git-config` format closely resembles the [`.ini` file format], /// there are subtle differences that make them incompatible. For one, the file /// format is not well defined, and there exists no formal specification to -/// adhere to. Thus, attempting to use an `.ini` parser on a `git-config` file -/// may successfully parse invalid configuration files. +/// adhere to. /// /// For concrete examples, some notable differences are: /// - `git-config` sections permit subsections via either a quoted string @@ -38,8 +38,9 @@ pub type FrontMatterEvents<'a> = SmallVec<[Event<'a>; 8]>; /// - Only `\t`, `\n`, `\b` `\\` are valid escape characters. /// - Quoted and semi-quoted values will be parsed (but quotes will be included /// in event outputs). An example of a semi-quoted value is `5"hello world"`, -/// which should be interpreted as `5hello world`. -/// - Line continuations via a `\` character is supported. +/// which should be interpreted as `5hello world` after +/// [normalization][crate::value::normalize()]. +/// - Line continuations via a `\` character is supported (inside or outside of quotes) /// - Whitespace handling similarly follows the `git-config` specification as /// closely as possible, where excess whitespace after a non-quoted value are /// trimmed, and line continuations onto a new line with excess spaces are kept. @@ -47,9 +48,9 @@ pub type FrontMatterEvents<'a> = SmallVec<[Event<'a>; 8]>; /// delimiters. /// /// Note that that things such as case-sensitivity or duplicate sections are -/// _not_ handled. This parser is a low level _syntactic_ interpreter (as a -/// parser should be), and higher level wrappers around this parser (which may -/// or may not be zero-copy) should handle _semantic_ values. This also means +/// _not_ handled. This parser is a low level _syntactic_ interpreter +/// and higher level wrappers around this parser, which may +/// or may not be zero-copy, should handle _semantic_ values. This also means /// that string-like values are not interpreted. For example, `hello"world"` /// would be read at a high level as `helloworld` but this parser will return /// the former instead, with the extra quotes. This is because it is not the @@ -88,7 +89,7 @@ pub type FrontMatterEvents<'a> = SmallVec<[Event<'a>; 8]>; /// # subsection_name: None, /// # }; /// # let section_data = "[core]\n autocrlf = input"; -/// # assert_eq!(Events::from_str(section_data).unwrap().into_iter().collect::>(), vec![ +/// # assert_eq!(Events::from_str(section_data).unwrap().into_vec(), vec![ /// Event::SectionHeader(section_header), /// Event::Newline(Cow::Borrowed("\n".into())), /// Event::Whitespace(Cow::Borrowed(" ".into())), @@ -127,7 +128,7 @@ pub type FrontMatterEvents<'a> = SmallVec<[Event<'a>; 8]>; /// # subsection_name: None, /// # }; /// # let section_data = "[core]\n autocrlf"; -/// # assert_eq!(Events::from_str(section_data).unwrap().into_iter().collect::>(), vec![ +/// # assert_eq!(Events::from_str(section_data).unwrap().into_vec(), vec![ /// Event::SectionHeader(section_header), /// Event::Newline(Cow::Borrowed("\n".into())), /// Event::Whitespace(Cow::Borrowed(" ".into())), @@ -161,7 +162,7 @@ pub type FrontMatterEvents<'a> = SmallVec<[Event<'a>; 8]>; /// # subsection_name: None, /// # }; /// # let section_data = "[core]\nautocrlf=true\"\"\nfilemode=fa\"lse\""; -/// # assert_eq!(Events::from_str(section_data).unwrap().into_iter().collect::>(), vec![ +/// # assert_eq!(Events::from_str(section_data).unwrap().into_vec(), vec![ /// Event::SectionHeader(section_header), /// Event::Newline(Cow::Borrowed("\n".into())), /// Event::SectionKey(section::Key(Cow::Borrowed("autocrlf".into()))), @@ -198,7 +199,7 @@ pub type FrontMatterEvents<'a> = SmallVec<[Event<'a>; 8]>; /// # subsection_name: None, /// # }; /// # let section_data = "[some-section]\nfile=a\\\n c"; -/// # assert_eq!(Events::from_str(section_data).unwrap().into_iter().collect::>(), vec![ +/// # assert_eq!(Events::from_str(section_data).unwrap().into_vec(), vec![ /// Event::SectionHeader(section_header), /// Event::Newline(Cow::Borrowed("\n".into())), /// Event::SectionKey(section::Key(Cow::Borrowed("file".into()))), @@ -226,7 +227,7 @@ impl Events<'static> { /// Parses the provided bytes, returning an [`Events`] that contains allocated /// and owned events. This is similar to [`Events::from_bytes()`], but performance /// is degraded as it requires allocation for every event. However, this permits - /// the reference bytes to be dropped, allowing the parser to be passed around + /// the `input` bytes to be dropped and he parser to be passed around /// without lifetime worries. pub fn from_bytes_owned<'a>( input: &'a [u8], @@ -237,15 +238,6 @@ impl Events<'static> { } impl<'a> Events<'a> { - /// Attempt to zero-copy parse the provided `&str`. On success, returns a - /// [`Events`] that provides methods to accessing leading comments and sections - /// of a `git-config` file and can be converted into an iterator of [`Event`] - /// for higher level processing. - #[allow(clippy::should_implement_trait)] - pub fn from_str(input: &'a str) -> Result, parse::Error> { - Self::from_bytes(input.as_bytes()) - } - /// Attempt to zero-copy parse the provided bytes. On success, returns a /// [`Events`] that provides methods to accessing leading comments and sections /// of a `git-config` file and can be converted into an iterator of [`Event`] @@ -254,7 +246,16 @@ impl<'a> Events<'a> { from_bytes(input, std::convert::identity, None) } - /// Consumes the parser to produce an iterator of Events. + /// Attempt to zero-copy parse the provided `input` string. + /// + /// Prefer the [`from_bytes()`][Self::from_bytes()] method if UTF8 encoding + /// isn't guaranteed. + #[allow(clippy::should_implement_trait)] + pub fn from_str(input: &'a str) -> Result, parse::Error> { + Self::from_bytes(input.as_bytes()) + } + + /// Consumes the parser to produce an iterator of all contained events. #[must_use = "iterators are lazy and do nothing unless consumed"] #[allow(clippy::should_implement_trait)] pub fn into_iter(self) -> impl Iterator> + std::iter::FusedIterator { @@ -264,6 +265,11 @@ impl<'a> Events<'a> { std::iter::once(parse::Event::SectionHeader(section.section_header)).chain(section.events) })) } + + /// Place all contained events into a single `Vec`. + pub fn into_vec(self) -> Vec> { + self.into_iter().collect() + } } impl<'a> TryFrom<&'a str> for Events<'a> { diff --git a/git-config/src/parse/mod.rs b/git-config/src/parse/mod.rs index 73e1627b500..a2fc73c602e 100644 --- a/git-config/src/parse/mod.rs +++ b/git-config/src/parse/mod.rs @@ -2,10 +2,11 @@ //! want to use a higher abstraction such as [`File`] unless you have some //! explicit reason to work with events instead. //! -//! The general workflow for interacting with this is to use one of the -//! `parse_from_*` function variants. These will return a [`Events`] on success, -//! which can be converted into an [`Event`] iterator. The [`Events`] also has -//! additional methods for accessing leading comments or events by section. +//! The workflow for interacting with this is to use +//! [`from_bytes()`] to obtain all parse events or tokens of the given input. +//! +//! On a higher level, one can use [`Events`] to parse all evnets into a set +//! of easily interpretable data type, similar to what [`File`] does. //! //! [`File`]: crate::File @@ -31,9 +32,9 @@ pub(crate) mod tests; /// holding a [`Cow`] instead over a simple reference, the parser will only emit /// borrowed `Cow` variants. /// -/// The `Cow` smart pointer is used here for ease of inserting events in a -/// middle of an Event iterator. This is used, for example, in the [`File`] -/// struct when adding values. +/// The `Cow` is used here for ease of inserting new, typically owned events as used +/// in the [`File`] struct when adding values, allowing a mix of owned and borrowed +/// values. /// /// [`Cow`]: std::borrow::Cow /// [`File`]: crate::File @@ -41,39 +42,48 @@ pub(crate) mod tests; pub enum Event<'a> { /// A comment with a comment tag and the comment itself. Note that the /// comment itself may contain additional whitespace and comment markers - /// at the beginning. + /// at the beginning, like `# comment` or `; comment`. Comment(Comment<'a>), /// A section header containing the section name and a subsection, if it - /// exists, like `remote "origin"`. + /// exists. For instance, `remote "origin"` is parsed to `remote` as section + /// name and `origin` as subsection name. SectionHeader(section::Header<'a>), /// A name to a value in a section, like `url` in `remote.origin.url`. SectionKey(section::Key<'a>), - /// A completed value. This may be any string, including the empty string, - /// if an implicit boolean value is used. Note that these values may contain - /// spaces and any special character. This value is also unprocessed, so it - /// it may contain double quotes that should be replaced. + /// A completed value. This may be any single-line string, including the empty string + /// if an implicit boolean value is used. + /// Note that these values may contain spaces and any special character. This value is + /// also unprocessed, so it it may contain double quotes that should be + /// [normalized][crate::value::normalize()] before interpretation. Value(Cow<'a, BStr>), - /// Represents any token used to signify a new line character. On Unix + /// Represents any token used to signify a newline character. On Unix /// platforms, this is typically just `\n`, but can be any valid newline /// sequence. Multiple newlines (such as `\n\n`) will be merged as a single - /// newline event. + /// newline event containing a string of multiple newline characters. Newline(Cow<'a, BStr>), /// Any value that isn't completed. This occurs when the value is continued - /// onto the next line. A Newline event is guaranteed after, followed by + /// onto the next line by ending it with a backslash. + /// A [`Newline`][Self::Newline] event is guaranteed after, followed by /// either a ValueDone, a Whitespace, or another ValueNotDone. ValueNotDone(Cow<'a, BStr>), /// The last line of a value which was continued onto another line. + /// With this it's possible to obtain the complete value by concatenating + /// the prior [`ValueNotDone`][Self::ValueNotDone] events. ValueDone(Cow<'a, BStr>), - /// A continuous section of insignificant whitespace. Values with internal - /// spaces will not be separated by this event. + /// A continuous section of insignificant whitespace. + /// + /// Note that values with internal whitespace will not be separated by this event, + /// hence interior whitespace there is always part of the value. Whitespace(Cow<'a, BStr>), /// This event is emitted when the parser counters a valid `=` character - /// separating the key and value. This event is necessary as it eliminates - /// the ambiguity for whitespace events between a key and value event. + /// separating the key and value. + /// This event is necessary as it eliminates the ambiguity for whitespace + /// events between a key and value event. KeyValueSeparator, } -/// A parsed section containing the header and the section events. +/// A parsed section containing the header and the section events, typically +/// comprising the keys and their values. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] pub struct Section<'a> { /// The section name and subsection name, if any. @@ -82,7 +92,7 @@ pub struct Section<'a> { pub events: section::Events<'a>, } -/// A parsed comment event containing the comment marker and comment. +/// A parsed comment containing the comment marker and comment. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] pub struct Comment<'a> { /// The comment marker used. This is either a semicolon or octothorpe/hash. diff --git a/git-config/src/parse/section.rs b/git-config/src/parse/section.rs index 91781c9e0b7..e3b11337b3f 100644 --- a/git-config/src/parse/section.rs +++ b/git-config/src/parse/section.rs @@ -8,9 +8,6 @@ use std::fmt::Display; pub type Events<'a> = SmallVec<[Event<'a>; 64]>; /// A parsed section header, containing a name and optionally a subsection name. -/// -/// Note that section headers must be parsed as valid ASCII, and thus all valid -/// instances must also necessarily be valid UTF-8. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] pub struct Header<'a> { /// The name of the header. @@ -21,7 +18,10 @@ pub struct Header<'a> { /// is all whitespace, then the subsection name needs to be surrounded by /// quotes to have perfect reconstruction. pub separator: Option>, - /// The subsection name without quotes if any exist. + /// The subsection name without quotes if any exist, and with escapes folded + /// into their resulting characters. + /// Thus during serialization, escapes and quotes must be re-added. + /// This makes it possible to use [`Event`] data for lookups directly. pub subsection_name: Option>, } @@ -184,33 +184,5 @@ mod types { bstr::BStr, "Wrapper struct for key names, like `path` in `include.path`, since keys are case-insensitive." ); - - #[cfg(test)] - mod tests { - use super::*; - use std::cmp::Ordering; - - #[test] - fn case_insentive_eq() { - assert_eq!(Key::from("aBc"), Key::from("AbC")); - } - - #[test] - fn case_insentive_ord() { - assert_eq!(Key::from("a").cmp(&Key::from("a")), Ordering::Equal); - assert_eq!(Key::from("aBc").cmp(&Key::from("AbC")), Ordering::Equal); - } - - #[test] - fn case_insentive_hash() { - fn calculate_hash(t: T) -> u64 { - use std::hash::Hasher; - let mut s = std::collections::hash_map::DefaultHasher::new(); - t.hash(&mut s); - s.finish() - } - assert_eq!(calculate_hash(Key::from("aBc")), calculate_hash(Key::from("AbC"))); - } - } } pub use types::{Key, Name}; diff --git a/git-config/src/parse/tests.rs b/git-config/src/parse/tests.rs index db8d1c56fb3..7bbf90fb44e 100644 --- a/git-config/src/parse/tests.rs +++ b/git-config/src/parse/tests.rs @@ -1,56 +1,3 @@ -mod parse { - use crate::parse::Events; - - #[test] - fn parser_skips_bom() { - let bytes = b" - [core] - a = 1 - "; - let bytes_with_gb18030_bom = "\u{feff} - [core] - a = 1 - "; - - assert_eq!( - Events::from_bytes(bytes), - Events::from_bytes(bytes_with_gb18030_bom.as_bytes()) - ); - assert_eq!( - Events::from_bytes_owned(bytes, None), - Events::from_bytes_owned(bytes_with_gb18030_bom.as_bytes(), None) - ); - } -} - -#[cfg(test)] -mod error { - use crate::parse::Events; - - #[test] - fn line_no_is_one_indexed() { - assert_eq!(Events::from_str("[hello").unwrap_err().line_number(), 1); - } - - #[test] - fn remaining_data_contains_bad_tokens() { - assert_eq!(Events::from_str("[hello").unwrap_err().remaining_data(), b"[hello"); - } - - #[test] - fn to_string_truncates_extra_values() { - assert_eq!( - Events::from_str("[1234567890").unwrap_err().to_string(), - "Got an unexpected token on line 1 while trying to parse a section header: '[123456789' ... (1 characters omitted)" - ); - } - - #[test] - fn detected_by_fuzz() { - assert!(Events::from_str("[]I=").is_err()); - } -} - pub(crate) mod util { //! This module is only included for tests, and contains common unit test helper //! functions. diff --git a/git-config/tests/parse/mod.rs b/git-config/tests/parse/mod.rs index aebbdc4014a..4ec048fe54e 100644 --- a/git-config/tests/parse/mod.rs +++ b/git-config/tests/parse/mod.rs @@ -206,13 +206,13 @@ fn personal_config() { #[test] fn parse_empty() { - assert_eq!(Events::from_str("").unwrap().into_iter().collect::>(), vec![]); + assert_eq!(Events::from_str("").unwrap().into_vec(), vec![]); } #[test] fn parse_whitespace() { assert_eq!( - Events::from_str("\n \n \n").unwrap().into_iter().collect::>(), + Events::from_str("\n \n \n").unwrap().into_vec(), vec![newline(), whitespace(" "), newline(), whitespace(" "), newline()] ) } @@ -220,7 +220,7 @@ fn parse_whitespace() { #[test] fn newline_events_are_merged() { assert_eq!( - Events::from_str("\n\n\n\n\n").unwrap().into_iter().collect::>(), + Events::from_str("\n\n\n\n\n").unwrap().into_vec(), vec![newline_custom("\n\n\n\n\n")] ); } @@ -259,3 +259,83 @@ fn error() { "Got an unexpected token on line 1 while trying to parse a section header: '[core'" ); } + +mod key { + use crate::parse::section::Key; + use std::cmp::Ordering; + + #[test] + fn case_insentive_eq() { + assert_eq!(Key::from("aBc"), Key::from("AbC")); + } + + #[test] + fn case_insentive_ord() { + assert_eq!(Key::from("a").cmp(&Key::from("a")), Ordering::Equal); + assert_eq!(Key::from("aBc").cmp(&Key::from("AbC")), Ordering::Equal); + } + + #[test] + fn case_insentive_hash() { + fn calculate_hash(t: T) -> u64 { + use std::hash::Hasher; + let mut s = std::collections::hash_map::DefaultHasher::new(); + t.hash(&mut s); + s.finish() + } + assert_eq!(calculate_hash(Key::from("aBc")), calculate_hash(Key::from("AbC"))); + } +} + +mod events { + use crate::parse::Events; + + #[test] + fn parser_skips_bom() { + let bytes = b" + [core] + a = 1 + "; + let bytes_with_gb18030_bom = "\u{feff} + [core] + a = 1 + "; + + assert_eq!( + Events::from_bytes(bytes), + Events::from_bytes(bytes_with_gb18030_bom.as_bytes()) + ); + assert_eq!( + Events::from_bytes_owned(bytes, None), + Events::from_bytes_owned(bytes_with_gb18030_bom.as_bytes(), None) + ); + } +} + +#[cfg(test)] +mod error { + use crate::parse::Events; + + #[test] + fn line_no_is_one_indexed() { + assert_eq!(Events::from_str("[hello").unwrap_err().line_number(), 1); + } + + #[test] + fn remaining_data_contains_bad_tokens() { + assert_eq!(Events::from_str("[hello").unwrap_err().remaining_data(), b"[hello"); + } + + #[test] + fn to_string_truncates_extra_values() { + assert_eq!( + Events::from_str("[1234567890").unwrap_err().to_string(), + "Got an unexpected token on line 1 while trying to parse a section header: '[123456789' ... (1 characters omitted)" + ); + } + + #[test] + fn detected_by_fuzz() { + assert!(Events::from_str("[]I=").is_err()); + } +} diff --git a/git-config/tests/value/normalize.rs b/git-config/tests/value/normalize.rs index c8e682590ef..c2b73338d55 100644 --- a/git-config/tests/value/normalize.rs +++ b/git-config/tests/value/normalize.rs @@ -57,3 +57,10 @@ fn empty_string() { assert_eq!(cow, cow_str("")); assert!(matches!(cow, Cow::Borrowed(_))); } + +#[test] +fn inner_quotes_are_removed() { + assert_eq!(normalize_bstr(r#"5"hello world""#), cow_str("5hello world")); + assert_eq!(normalize_bstr(r#"true"""#), cow_str("true")); + assert_eq!(normalize_bstr(r#"fa"lse""#), cow_str("false")); +} From 95fc20a377aeb914d6b527c1d1b8e75d8c42c608 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 10 Jul 2022 09:39:51 +0800 Subject: [PATCH 127/366] more doc adjustments (#331) --- git-config/src/lib.rs | 4 ++-- git-config/src/lookup.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/git-config/src/lib.rs b/git-config/src/lib.rs index cb03decee78..cd6712544c0 100644 --- a/git-config/src/lib.rs +++ b/git-config/src/lib.rs @@ -33,8 +33,8 @@ //! //! ## Feature Flags #![cfg_attr( -feature = "document-features", -cfg_attr(doc, doc = ::document_features::document_features!()) + feature = "document-features", + cfg_attr(doc, doc = ::document_features::document_features!()) )] pub mod file; diff --git a/git-config/src/lookup.rs b/git-config/src/lookup.rs index 5a3163fbe23..7814978125d 100644 --- a/git-config/src/lookup.rs +++ b/git-config/src/lookup.rs @@ -1,16 +1,16 @@ -/// The error when looking up a value. +/// The error when looking up a value, for example via [`File::try_value()`][crate::File::try_value()]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { #[error(transparent)] - ValueMissing(#[from] crate::lookup::existing::Error), + ValueMissing(#[from] existing::Error), #[error(transparent)] FailedConversion(E), } /// pub mod existing { - /// The error when looking up a value that doesn't exist. + /// The error when looking up a value that doesn't exist, for example via [`File::value()`][crate::File::value()]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { From 238581cc46c7288691eed37dc7de5069e3d86721 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 10 Jul 2022 09:40:14 +0800 Subject: [PATCH 128/366] assure document-features are available in all 'usable' and 'early' crates For completeness, finally. Let's make sure we keep this up. --- Cargo.lock | 10 ++++++++++ git-attributes/Cargo.toml | 6 ++++++ git-attributes/src/lib.rs | 5 +++++ git-commitgraph/Cargo.toml | 6 ++++++ git-commitgraph/src/lib.rs | 5 +++++ git-credentials/Cargo.toml | 3 +++ git-credentials/src/lib.rs | 8 +++++++- git-glob/Cargo.toml | 6 ++++++ git-glob/src/lib.rs | 7 ++++++- git-hash/Cargo.toml | 3 +++ git-hash/src/lib.rs | 5 +++++ git-odb/Cargo.toml | 11 ++++++++--- git-odb/src/lib.rs | 5 +++++ git-pack/Cargo.toml | 3 ++- git-ref/Cargo.toml | 7 +++++++ git-ref/src/lib.rs | 8 ++++++-- git-revision/Cargo.toml | 5 +++++ git-revision/src/lib.rs | 5 +++++ git-sec/Cargo.toml | 6 ++++++ git-sec/src/lib.rs | 8 +++++++- git-url/Cargo.toml | 6 ++++++ git-url/src/lib.rs | 5 +++++ 22 files changed, 124 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 73f7c32e584..1a5f65bad4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1039,6 +1039,7 @@ version = "0.3.0" dependencies = [ "bstr", "compact_str", + "document-features", "git-features", "git-glob", "git-path", @@ -1069,6 +1070,7 @@ name = "git-commitgraph" version = "0.8.0" dependencies = [ "bstr", + "document-features", "git-chunk", "git-features", "git-hash", @@ -1121,6 +1123,7 @@ name = "git-credentials" version = "0.3.0" dependencies = [ "bstr", + "document-features", "git-sec", "quick-error", "serde", @@ -1199,6 +1202,7 @@ version = "0.3.0" dependencies = [ "bitflags", "bstr", + "document-features", "git-testtools", "serde", ] @@ -1207,6 +1211,7 @@ dependencies = [ name = "git-hash" version = "0.9.5" dependencies = [ + "document-features", "git-testtools", "hex", "quick-error", @@ -1288,6 +1293,7 @@ name = "git-odb" version = "0.31.0" dependencies = [ "arc-swap", + "document-features", "filetime", "git-actor", "git-features", @@ -1405,6 +1411,7 @@ version = "0.0.0" name = "git-ref" version = "0.15.0" dependencies = [ + "document-features", "git-actor", "git-discover", "git-features", @@ -1471,6 +1478,7 @@ name = "git-revision" version = "0.2.1" dependencies = [ "bstr", + "document-features", "git-date", "git-hash", "git-object", @@ -1487,6 +1495,7 @@ version = "0.3.0" dependencies = [ "bitflags", "dirs", + "document-features", "git-path", "libc", "serde", @@ -1588,6 +1597,7 @@ name = "git-url" version = "0.7.0" dependencies = [ "bstr", + "document-features", "git-features", "git-path", "home", diff --git a/git-attributes/Cargo.toml b/git-attributes/Cargo.toml index f3ead4df284..4c5c2d64f71 100644 --- a/git-attributes/Cargo.toml +++ b/git-attributes/Cargo.toml @@ -29,5 +29,11 @@ quick-error = "2.0.0" serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]} compact_str = "0.4" +document-features = { version = "0.2.1", optional = true } + [dev-dependencies] git-testtools = { path = "../tests/tools"} + +[package.metadata.docs.rs] +all-features = true +features = ["document-features"] diff --git a/git-attributes/src/lib.rs b/git-attributes/src/lib.rs index 2c54d3c3557..ab396eae4b2 100644 --- a/git-attributes/src/lib.rs +++ b/git-attributes/src/lib.rs @@ -1,3 +1,8 @@ +//! ## Feature Flags +#![cfg_attr( + feature = "document-features", + cfg_attr(doc, doc = ::document_features::document_features!()) +)] #![forbid(unsafe_code)] #![deny(rust_2018_idioms)] diff --git a/git-commitgraph/Cargo.toml b/git-commitgraph/Cargo.toml index 0bd1941ffee..cb7f7d80b43 100644 --- a/git-commitgraph/Cargo.toml +++ b/git-commitgraph/Cargo.toml @@ -26,5 +26,11 @@ memmap2 = "0.5.0" serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"] } thiserror = "1.0.26" +document-features = { version = "0.2.0", optional = true } + [dev-dependencies] git-testtools = { path = "../tests/tools" } + +[package.metadata.docs.rs] +features = ["document-features"] +all-features = true diff --git a/git-commitgraph/src/lib.rs b/git-commitgraph/src/lib.rs index b943050f22c..0055c66c218 100644 --- a/git-commitgraph/src/lib.rs +++ b/git-commitgraph/src/lib.rs @@ -7,6 +7,11 @@ //! As generating the full commit graph from scratch can take some time, git may write new commits //! to separate [files][file::File] instead of overwriting the original file. //! Eventually, git will merge these files together as the number of files grows. +//! ## Feature Flags +#![cfg_attr( + feature = "document-features", + cfg_attr(doc, doc = ::document_features::document_features!()) +)] #![deny(unsafe_code, rust_2018_idioms, missing_docs)] pub mod file; diff --git a/git-credentials/Cargo.toml b/git-credentials/Cargo.toml index 6e6282cf5d1..5f680dc1c2c 100644 --- a/git-credentials/Cargo.toml +++ b/git-credentials/Cargo.toml @@ -22,5 +22,8 @@ quick-error = "2.0.0" serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"] } bstr = { version = "0.2.13", default-features = false, features = ["std"]} +document-features = { version = "0.2.1", optional = true } + [package.metadata.docs.rs] all-features = true +features = ["document-features"] diff --git a/git-credentials/src/lib.rs b/git-credentials/src/lib.rs index b6ff3d34fb1..6c6c1380a92 100644 --- a/git-credentials/src/lib.rs +++ b/git-credentials/src/lib.rs @@ -1,6 +1,12 @@ +//! Interact with git credentials in various ways and launch helper programs. +//! +//! ## Feature Flags +#![cfg_attr( + feature = "document-features", + cfg_attr(doc, doc = ::document_features::document_features!()) +)] #![forbid(unsafe_code)] #![deny(missing_docs, rust_2018_idioms)] -//! Interact with git credentials in various ways and launch helper programs. /// pub mod helper; diff --git a/git-glob/Cargo.toml b/git-glob/Cargo.toml index 1befc8729e2..32459266332 100644 --- a/git-glob/Cargo.toml +++ b/git-glob/Cargo.toml @@ -21,5 +21,11 @@ bstr = { version = "0.2.13", default-features = false, features = ["std"]} bitflags = "1.3.2" serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]} +document-features = { version = "0.2.0", optional = true } + [dev-dependencies] git-testtools = { path = "../tests/tools"} + +[package.metadata.docs.rs] +features = ["document-features"] +all-features = true diff --git a/git-glob/src/lib.rs b/git-glob/src/lib.rs index cfafeac7c3a..17cdf1c3010 100644 --- a/git-glob/src/lib.rs +++ b/git-glob/src/lib.rs @@ -1,6 +1,11 @@ +//! Provide glob [`Patterns`][Pattern] for matching against paths or anything else. +//! ## Feature Flags +#![cfg_attr( + feature = "document-features", + cfg_attr(doc, doc = ::document_features::document_features!()) +)] #![forbid(unsafe_code)] #![deny(rust_2018_idioms, missing_docs)] -//! Provide glob [`Patterns`][Pattern] for matching against paths or anything else. use bstr::BString; diff --git a/git-hash/Cargo.toml b/git-hash/Cargo.toml index bc24b6da6fc..a50ca1542f6 100644 --- a/git-hash/Cargo.toml +++ b/git-hash/Cargo.toml @@ -21,8 +21,11 @@ quick-error = "2.0.0" hex = "0.4.2" serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"] } +document-features = { version = "0.2.0", optional = true } + [dev-dependencies] git-testtools = { path = "../tests/tools"} [package.metadata.docs.rs] +features = ["document-features"] all-features = true diff --git a/git-hash/src/lib.rs b/git-hash/src/lib.rs index 33bd5b5eed3..11d08cd0c29 100644 --- a/git-hash/src/lib.rs +++ b/git-hash/src/lib.rs @@ -1,6 +1,11 @@ //! This crate provides types for identifying git objects using a hash digest. //! //! These are provided in borrowed versions as well as owned ones. +//! ## Feature Flags +#![cfg_attr( +feature = "document-features", +cfg_attr(doc, doc = ::document_features::document_features!()) +)] #![deny(unsafe_code)] #![deny(rust_2018_idioms, missing_docs)] diff --git a/git-odb/Cargo.toml b/git-odb/Cargo.toml index dbb5488bcd1..c873fcc650c 100644 --- a/git-odb/Cargo.toml +++ b/git-odb/Cargo.toml @@ -26,9 +26,6 @@ name = "single-threaded" path = "tests/odb-single-threaded.rs" required-features = [] -[package.metadata.docs.rs] -all-features = true - [dependencies] git-features = { version = "^0.21.1", path = "../git-features", features = ["rustsha1", "walkdir", "zlib", "crc32" ] } git-path = { version = "^0.3.0", path = "../git-path" } @@ -43,8 +40,16 @@ thiserror = "1.0.26" parking_lot = { version = "0.12.0" } arc-swap = "1.5.0" +document-features = { version = "0.2.0", optional = true } + [dev-dependencies] git-testtools = { path = "../tests/tools"} git-actor = { path = "../git-actor" } pretty_assertions = "1.0.0" filetime = "0.2.15" + +[package.metadata.docs.rs] +all-features = true +features = ["document-features"] + + diff --git a/git-odb/src/lib.rs b/git-odb/src/lib.rs index 07351fd9cf9..0c256c78566 100644 --- a/git-odb/src/lib.rs +++ b/git-odb/src/lib.rs @@ -9,6 +9,11 @@ //! * loose object reading and writing //! * access to packed objects //! * multiple loose objects and pack locations as gathered from `alternates` files. +//! ## Feature Flags +#![cfg_attr( + feature = "document-features", + cfg_attr(doc, doc = ::document_features::document_features!()) +)] use std::{ cell::RefCell, diff --git a/git-pack/Cargo.toml b/git-pack/Cargo.toml index 7f7b7a9caea..0c56ec907f9 100644 --- a/git-pack/Cargo.toml +++ b/git-pack/Cargo.toml @@ -55,9 +55,10 @@ thiserror = "1.0.26" uluru = { version = "3.0.0", optional = true } clru = { version = "0.5.0", optional = true } dashmap = "5.1.0" -document-features = { version = "0.2.0", optional = true } hash_hasher = "2.0.3" +document-features = { version = "0.2.0", optional = true } + [dev-dependencies] git-testtools = { path = "../tests/tools"} git-odb = { path = "../git-odb" } diff --git a/git-ref/Cargo.toml b/git-ref/Cargo.toml index 728a5c05223..5c53df38570 100644 --- a/git-ref/Cargo.toml +++ b/git-ref/Cargo.toml @@ -41,8 +41,15 @@ serde = { version = "1.0.114", optional = true, default-features = false, featur # packed refs memmap2 = "0.5.0" +document-features = { version = "0.2.1", optional = true } + [dev-dependencies] git-testtools = { path = "../tests/tools" } git-discover = { path = "../git-discover" } git-odb = { path = "../git-odb" } tempfile = "3.2.0" + + +[package.metadata.docs.rs] +features = ["document-features"] +all-features = true diff --git a/git-ref/src/lib.rs b/git-ref/src/lib.rs index 940aaeebd96..cde0dad6c36 100644 --- a/git-ref/src/lib.rs +++ b/git-ref/src/lib.rs @@ -14,8 +14,12 @@ //! * one reference maps to a file on disk //! * **packed** //! * references are stored in a single human-readable file, along with their targets if they are symbolic. -//! * **ref-table** -//! * supersedes all of the above to allow handling hundreds of thousands of references. +//! +//! ## Feature Flags +#![cfg_attr( + feature = "document-features", + cfg_attr(doc, doc = ::document_features::document_features!()) +)] #![deny(unsafe_code, missing_docs, rust_2018_idioms)] use std::borrow::Cow; diff --git a/git-revision/Cargo.toml b/git-revision/Cargo.toml index bc27d0cbb07..2d2e11636ef 100644 --- a/git-revision/Cargo.toml +++ b/git-revision/Cargo.toml @@ -25,7 +25,12 @@ bstr = { version = "0.2.13", default-features = false, features = ["std"]} hash_hasher = "2.0.3" thiserror = "1.0.26" serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"] } +document-features = { version = "0.2.1", optional = true } [dev-dependencies] git-testtools = { path = "../tests/tools" } git-repository = { path = "../git-repository", default-features = false, features = ["unstable"] } + +[package.metadata.docs.rs] +all-features = true +features = ["document-features"] diff --git a/git-revision/src/lib.rs b/git-revision/src/lib.rs index 95171b2c6a6..a682c2e6cc7 100644 --- a/git-revision/src/lib.rs +++ b/git-revision/src/lib.rs @@ -1,6 +1,11 @@ //! Interact with git revisions by parsing them from rev-specs and turning them into rev-specs. //! //! One can also describe revisions using a different algorithm. +//! ## Feature Flags +#![cfg_attr( + feature = "document-features", + cfg_attr(doc, doc = ::document_features::document_features!()) +)] #![forbid(unsafe_code)] #![deny(missing_docs, rust_2018_idioms)] diff --git a/git-sec/Cargo.toml b/git-sec/Cargo.toml index 5c27e43b7b0..17ee4c48a44 100644 --- a/git-sec/Cargo.toml +++ b/git-sec/Cargo.toml @@ -22,6 +22,8 @@ serde = { version = "1.0.114", optional = true, default-features = false, featur bitflags = "1.3.2" thiserror = { version = "1.0.26", optional = true } +document-features = { version = "0.2.1", optional = true } + [target.'cfg(not(windows))'.dependencies] libc = "0.2.123" @@ -38,3 +40,7 @@ windows = { version = "0.37.0", features = [ "alloc", [dev-dependencies] tempfile = "3.3.0" + +[package.metadata.docs.rs] +features = ["document-features"] +all-features = true diff --git a/git-sec/src/lib.rs b/git-sec/src/lib.rs index ae7636582be..b795af01b40 100644 --- a/git-sec/src/lib.rs +++ b/git-sec/src/lib.rs @@ -1,5 +1,11 @@ -#![deny(unsafe_code, rust_2018_idioms, missing_docs)] //! A shared trust model for `gitoxide` crates. +//! +//! ## Feature Flags +#![cfg_attr( + feature = "document-features", + cfg_attr(doc, doc = ::document_features::document_features!()) +)] +#![deny(unsafe_code, rust_2018_idioms, missing_docs)] use std::{ fmt::{Debug, Display, Formatter}, diff --git a/git-url/Cargo.toml b/git-url/Cargo.toml index 37d452f2425..47883dbc7fb 100644 --- a/git-url/Cargo.toml +++ b/git-url/Cargo.toml @@ -25,3 +25,9 @@ quick-error = "2.0.0" url = "2.1.1" bstr = { version = "0.2.13", default-features = false, features = ["std"] } home = "0.5.3" + +document-features = { version = "0.2.0", optional = true } + +[package.metadata.docs.rs] +features = ["document-features"] +all-features = true diff --git a/git-url/src/lib.rs b/git-url/src/lib.rs index 309a50bd8e0..f9dc123fff0 100644 --- a/git-url/src/lib.rs +++ b/git-url/src/lib.rs @@ -1,4 +1,9 @@ //! A library implementing a URL for use in git with access to its special capabilities. +//! ## Feature Flags +#![cfg_attr( +feature = "document-features", +cfg_attr(doc, doc = ::document_features::document_features!()) +)] #![forbid(unsafe_code)] #![deny(rust_2018_idioms)] From a7d7751822a1a8ac89930031707af57ad95d9cbd Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 10 Jul 2022 09:43:31 +0800 Subject: [PATCH 129/366] make fmt (#331) --- .../tests/changelog/write_and_parse/mod.rs | 3 +- git-config/src/file/access/comfort.rs | 3 +- .../src/file/access/low_level/mutating.rs | 3 +- .../src/file/access/low_level/read_only.rs | 3 +- git-config/src/file/access/raw.rs | 3 +- git-config/src/file/from_env.rs | 3 +- git-config/src/file/from_paths.rs | 4 +- git-config/src/file/impls.rs | 3 +- git-config/src/file/mod.rs | 6 ++- git-config/src/file/resolve_includes.rs | 3 +- git-config/src/file/section.rs | 6 +-- git-config/src/file/tests.rs | 7 +--- git-config/src/file/utils.rs | 3 +- git-config/src/file/value.rs | 3 +- git-config/src/fs.rs | 3 +- git-config/src/parse/comment.rs | 7 ++-- git-config/src/parse/error.rs | 3 +- git-config/src/parse/event.rs | 7 ++-- git-config/src/parse/events.rs | 10 +++-- git-config/src/parse/mod.rs | 3 +- git-config/src/parse/nom/mod.rs | 8 ++-- git-config/src/parse/nom/tests.rs | 39 ++++++++++++------- git-config/src/parse/section.rs | 7 ++-- git-config/src/values/boolean.rs | 9 ++--- git-config/src/values/color.rs | 10 ++--- git-config/src/values/integer.rs | 10 ++--- git-config/src/values/path.rs | 7 ++-- .../tests/file/access/raw/raw_multi_value.rs | 3 +- git-config/tests/file/access/read_only.rs | 9 +++-- .../includes/conditional/gitdir/mod.rs | 3 +- .../includes/conditional/gitdir/util.rs | 17 +++++--- .../from_paths/includes/conditional/mod.rs | 9 ++--- .../includes/conditional/onbranch.rs | 12 ++++-- git-config/tests/parse/mod.rs | 3 +- git-config/tests/value/normalize.rs | 6 ++- git-config/tests/values/color.rs | 3 +- git-config/tests/values/path.rs | 10 +++-- 37 files changed, 145 insertions(+), 106 deletions(-) diff --git a/cargo-smart-release/tests/changelog/write_and_parse/mod.rs b/cargo-smart-release/tests/changelog/write_and_parse/mod.rs index daca8d6b489..3faf89eed8d 100644 --- a/cargo-smart-release/tests/changelog/write_and_parse/mod.rs +++ b/cargo-smart-release/tests/changelog/write_and_parse/mod.rs @@ -5,8 +5,7 @@ use cargo_smart_release::{ changelog::{section, section::segment::conventional, Section}, ChangeLog, }; -use git_testtools::bstr::ByteSlice; -use git_testtools::hex_to_id; +use git_testtools::{bstr::ByteSlice, hex_to_id}; use crate::Result; diff --git a/git-config/src/file/access/comfort.rs b/git-config/src/file/access/comfort.rs index e323f50fd46..029e088947e 100644 --- a/git-config/src/file/access/comfort.rs +++ b/git-config/src/file/access/comfort.rs @@ -2,8 +2,7 @@ use std::{borrow::Cow, convert::TryFrom}; use bstr::BStr; -use crate::value::normalize; -use crate::{value, File}; +use crate::{value, value::normalize, File}; /// Comfortable API for accessing values impl<'a> File<'a> { diff --git a/git-config/src/file/access/low_level/mutating.rs b/git-config/src/file/access/low_level/mutating.rs index b7007b45e38..96511358e87 100644 --- a/git-config/src/file/access/low_level/mutating.rs +++ b/git-config/src/file/access/low_level/mutating.rs @@ -1,6 +1,7 @@ -use bstr::BStr; use std::borrow::Cow; +use bstr::BStr; + use crate::{ file::{MutableSection, SectionBody}, lookup, diff --git a/git-config/src/file/access/low_level/read_only.rs b/git-config/src/file/access/low_level/read_only.rs index c02cad68fd3..6d80d91de34 100644 --- a/git-config/src/file/access/low_level/read_only.rs +++ b/git-config/src/file/access/low_level/read_only.rs @@ -1,6 +1,7 @@ -use bstr::BStr; use std::{borrow::Cow, convert::TryFrom}; +use bstr::BStr; + use crate::{file::SectionBody, lookup, parse::section, File}; /// Read-only low-level access methods. diff --git a/git-config/src/file/access/raw.rs b/git-config/src/file/access/raw.rs index c07737ba266..0ec5215b79d 100644 --- a/git-config/src/file/access/raw.rs +++ b/git-config/src/file/access/raw.rs @@ -1,6 +1,7 @@ -use bstr::{BStr, BString}; use std::{borrow::Cow, collections::HashMap}; +use bstr::{BStr, BString}; + use crate::{ file::{EntryData, Index, MutableMultiValue, MutableSection, MutableValue, Size}, lookup, diff --git a/git-config/src/file/from_env.rs b/git-config/src/file/from_env.rs index bafa8c46864..cb00c4524f6 100644 --- a/git-config/src/file/from_env.rs +++ b/git-config/src/file/from_env.rs @@ -1,6 +1,7 @@ -use bstr::BString; use std::{borrow::Cow, path::PathBuf}; +use bstr::BString; + use crate::{ file::{from_paths, resolve_includes}, parse, diff --git a/git-config/src/file/from_paths.rs b/git-config/src/file/from_paths.rs index 390d997a072..e89a3292eac 100644 --- a/git-config/src/file/from_paths.rs +++ b/git-config/src/file/from_paths.rs @@ -1,7 +1,7 @@ -use crate::{file::resolve_includes, File}; -use crate::{parse, path::interpolate}; use std::path::PathBuf; +use crate::{file::resolve_includes, parse, path::interpolate, File}; + /// The error returned by [`File::from_paths()`][crate::File::from_paths()] and [`File::from_env_paths()`][crate::File::from_env_paths()]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] diff --git a/git-config/src/file/impls.rs b/git-config/src/file/impls.rs index b905d70fc93..325a45b8f55 100644 --- a/git-config/src/file/impls.rs +++ b/git-config/src/file/impls.rs @@ -1,6 +1,7 @@ -use bstr::{BString, ByteVec}; use std::{convert::TryFrom, fmt::Display}; +use bstr::{BString, ByteVec}; + use crate::{file::SectionBody, parse, File}; impl<'a> TryFrom<&'a str> for File<'a> { diff --git a/git-config/src/file/mod.rs b/git-config/src/file/mod.rs index 9fbb9eb87ed..74fa4bf213a 100644 --- a/git-config/src/file/mod.rs +++ b/git-config/src/file/mod.rs @@ -1,11 +1,13 @@ -//! This module provides a high level wrapper around a single `git-config` file. -use bstr::BStr; +//! This module provides a high level wrapper around a single `git-config` file, +//! or multiple concatenated `git-config` files. use std::{ borrow::Cow, collections::HashMap, ops::{Add, AddAssign}, }; +use bstr::BStr; + mod section; pub use section::*; diff --git a/git-config/src/file/resolve_includes.rs b/git-config/src/file/resolve_includes.rs index 0e8687879c4..2833dd22e0c 100644 --- a/git-config/src/file/resolve_includes.rs +++ b/git-config/src/file/resolve_includes.rs @@ -6,9 +6,8 @@ use std::{ use bstr::{BStr, BString, ByteSlice, ByteVec}; use git_ref::Category; -use crate::file::from_paths::Options; use crate::{ - file::{from_paths, SectionBodyId}, + file::{from_paths, from_paths::Options, SectionBodyId}, parse::section, File, }; diff --git a/git-config/src/file/section.rs b/git-config/src/file/section.rs index 2d9abb57c13..a602eb900cb 100644 --- a/git-config/src/file/section.rs +++ b/git-config/src/file/section.rs @@ -1,4 +1,3 @@ -use bstr::{BStr, BString, ByteVec}; use std::{ borrow::Cow, collections::VecDeque, @@ -6,12 +5,13 @@ use std::{ ops::{Deref, Range}, }; -use crate::value::normalize_bstr; +use bstr::{BStr, BString, ByteVec}; + use crate::{ file::Index, lookup, parse, parse::{section::Key, Event}, - value::{normalize, normalize_bstring}, + value::{normalize, normalize_bstr, normalize_bstring}, }; /// A opaque type that represents a mutable reference to a section. diff --git a/git-config/src/file/tests.rs b/git-config/src/file/tests.rs index 02d009e35ae..fffc4d2a92e 100644 --- a/git-config/src/file/tests.rs +++ b/git-config/src/file/tests.rs @@ -1,11 +1,8 @@ mod try_from { - use crate::file::{SectionBodyId, SectionBodyIds}; - use std::borrow::Cow; - use std::collections::HashMap; - use std::convert::TryFrom; + use std::{borrow::Cow, collections::HashMap, convert::TryFrom}; use crate::{ - file::SectionBody, + file::{SectionBody, SectionBodyId, SectionBodyIds}, parse::{ section, tests::util::{name_event, newline_event, section_header, value_event}, diff --git a/git-config/src/file/utils.rs b/git-config/src/file/utils.rs index 75a93700082..e390c6e6ede 100644 --- a/git-config/src/file/utils.rs +++ b/git-config/src/file/utils.rs @@ -1,6 +1,7 @@ -use bstr::BStr; use std::collections::HashMap; +use bstr::BStr; + use crate::{ file::{MutableSection, SectionBody, SectionBodyId, SectionBodyIds}, lookup, diff --git a/git-config/src/file/value.rs b/git-config/src/file/value.rs index 42ae1ea75c9..2a02cd26099 100644 --- a/git-config/src/file/value.rs +++ b/git-config/src/file/value.rs @@ -1,6 +1,7 @@ -use bstr::{BStr, BString}; use std::{borrow::Cow, collections::HashMap, ops::DerefMut}; +use bstr::{BStr, BString}; + use crate::{ file::{ section::{MutableSection, SectionBody}, diff --git a/git-config/src/fs.rs b/git-config/src/fs.rs index e0411834010..530fe1cbd1d 100644 --- a/git-config/src/fs.rs +++ b/git-config/src/fs.rs @@ -2,13 +2,14 @@ #![allow(unused)] #![allow(clippy::result_unit_err)] -use bstr::BStr; use std::{ borrow::Cow, convert::TryFrom, path::{Path, PathBuf}, }; +use bstr::BStr; + use crate::{ file::{from_env, from_paths}, lookup, File, diff --git a/git-config/src/parse/comment.rs b/git-config/src/parse/comment.rs index 1d802f0f41e..f302ff8cb5e 100644 --- a/git-config/src/parse/comment.rs +++ b/git-config/src/parse/comment.rs @@ -1,7 +1,8 @@ -use crate::parse::Comment; +use std::{borrow::Cow, fmt::Display}; + use bstr::{BString, ByteVec}; -use std::borrow::Cow; -use std::fmt::Display; + +use crate::parse::Comment; impl Comment<'_> { /// Turn this instance into a fully owned one with `'static` lifetime. diff --git a/git-config/src/parse/error.rs b/git-config/src/parse/error.rs index 4b047f4dcc2..9dde724ff43 100644 --- a/git-config/src/parse/error.rs +++ b/git-config/src/parse/error.rs @@ -1,6 +1,7 @@ -use crate::parse::Error; use std::fmt::Display; +use crate::parse::Error; + /// A list of parsers that parsing can fail on. This is used for pretty-printing errors #[derive(PartialEq, Debug, Clone, Copy)] pub(crate) enum ParseNode { diff --git a/git-config/src/parse/event.rs b/git-config/src/parse/event.rs index 714bd7c8bc4..9b5565b1fc2 100644 --- a/git-config/src/parse/event.rs +++ b/git-config/src/parse/event.rs @@ -1,7 +1,8 @@ -use crate::parse::Event; +use std::{borrow::Cow, fmt::Display}; + use bstr::BString; -use std::borrow::Cow; -use std::fmt::Display; + +use crate::parse::Event; impl Event<'_> { /// Generates a byte representation of the value. This should be used when diff --git a/git-config/src/parse/events.rs b/git-config/src/parse/events.rs index 5e0fc6f6568..79a3a325788 100644 --- a/git-config/src/parse/events.rs +++ b/git-config/src/parse/events.rs @@ -1,8 +1,12 @@ -use crate::parse::{section, Event}; -use crate::{parse, parse::Section}; -use smallvec::SmallVec; use std::convert::TryFrom; +use smallvec::SmallVec; + +use crate::{ + parse, + parse::{section, Event, Section}, +}; + /// A type store without allocation all events that are typicaly preceeding the first section. pub type FrontMatterEvents<'a> = SmallVec<[Event<'a>; 8]>; diff --git a/git-config/src/parse/mod.rs b/git-config/src/parse/mod.rs index a2fc73c602e..a48f8eb9da6 100644 --- a/git-config/src/parse/mod.rs +++ b/git-config/src/parse/mod.rs @@ -10,9 +10,10 @@ //! //! [`File`]: crate::File -use bstr::BStr; use std::{borrow::Cow, hash::Hash}; +use bstr::BStr; + mod nom; pub use self::nom::from_bytes; /// diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index 29ddcbd2a88..2adc811970f 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -1,9 +1,6 @@ -use crate::parse::{section, Comment, Error, Event}; -use bstr::{BStr, BString, ByteSlice, ByteVec}; use std::borrow::Cow; -use crate::parse::error::ParseNode; -use nom::multi::{fold_many0, fold_many1}; +use bstr::{BStr, BString, ByteSlice, ByteVec}; use nom::{ branch::alt, bytes::complete::{tag, take_till, take_while}, @@ -13,10 +10,13 @@ use nom::{ }, combinator::{map, opt}, error::{Error as NomError, ErrorKind}, + multi::{fold_many0, fold_many1}, sequence::delimited, IResult, }; +use crate::parse::{error::ParseNode, section, Comment, Error, Event}; + /// Attempt to zero-copy parse the provided bytes, passing results to `receive_event`. pub fn from_bytes<'a>(input: &'a [u8], mut receive_event: impl FnMut(Event<'a>)) -> Result<(), Error> { let bom = unicode_bom::Bom::from(input); diff --git a/git-config/src/parse/nom/tests.rs b/git-config/src/parse/nom/tests.rs index 6082fae12c4..075679f9f90 100644 --- a/git-config/src/parse/nom/tests.rs +++ b/git-config/src/parse/nom/tests.rs @@ -114,9 +114,10 @@ mod section_headers { } mod sub_section { - use super::sub_section; use std::borrow::Cow; + use super::sub_section; + #[test] fn zero_copy_simple() { let actual = sub_section(b"name\"").unwrap().1; @@ -133,9 +134,10 @@ mod sub_section { } mod config_name { + use nom::combinator::all_consuming; + use super::config_name; use crate::parse::tests::util::fully_consumed; - use nom::combinator::all_consuming; #[test] fn just_name() { @@ -161,11 +163,15 @@ mod config_name { } mod section { - use crate::parse::tests::util::{ - comment_event, fully_consumed, name_event, newline_event, section_header as parsed_section_header, - value_done_event, value_event, value_not_done_event, whitespace_event, + use crate::parse::{ + error::ParseNode, + section, + tests::util::{ + comment_event, fully_consumed, name_event, newline_event, section_header as parsed_section_header, + value_done_event, value_event, value_not_done_event, whitespace_event, + }, + Event, Section, }; - use crate::parse::{error::ParseNode, section, Event, Section}; fn section<'a>(i: &'a [u8], node: &mut ParseNode) -> nom::IResult<&'a [u8], (Section<'a>, usize)> { let mut header = None; @@ -426,8 +432,10 @@ mod section { } mod value_continuation { - use crate::parse::section; - use crate::parse::tests::util::{into_events, newline_event, value_done_event, value_not_done_event}; + use crate::parse::{ + section, + tests::util::{into_events, newline_event, value_done_event, value_not_done_event}, + }; pub fn value_impl<'a>(i: &'a [u8], events: &mut section::Events<'a>) -> nom::IResult<&'a [u8], ()> { super::value_impl(i, &mut |e| events.push(e)).map(|t| (t.0, ())) @@ -498,10 +506,11 @@ mod value_continuation { } mod value_no_continuation { - use crate::parse::section; - use crate::parse::tests::util::{into_events, value_event}; - use super::value_continuation::value_impl; + use crate::parse::{ + section, + tests::util::{into_events, value_event}, + }; #[test] fn no_comment() { @@ -594,8 +603,12 @@ mod value_no_continuation { } mod section_body { - use crate::parse::tests::util::{into_events, name_event, value_event, whitespace_event}; - use crate::parse::{error::ParseNode, section, Event}; + use crate::parse::{ + error::ParseNode, + section, + tests::util::{into_events, name_event, value_event, whitespace_event}, + Event, + }; fn section_body<'a>( i: &'a [u8], diff --git a/git-config/src/parse/section.rs b/git-config/src/parse/section.rs index e3b11337b3f..44d8f4a4bd1 100644 --- a/git-config/src/parse/section.rs +++ b/git-config/src/parse/section.rs @@ -1,8 +1,9 @@ -use crate::parse::{Event, Section}; +use std::{borrow::Cow, fmt::Display}; + use bstr::{BStr, BString}; use smallvec::SmallVec; -use std::borrow::Cow; -use std::fmt::Display; + +use crate::parse::{Event, Section}; /// A container for events, avoiding heap allocations in typical files. pub type Events<'a> = SmallVec<[Event<'a>; 64]>; diff --git a/git-config/src/values/boolean.rs b/git-config/src/values/boolean.rs index d4db9a40c84..3540e085bfc 100644 --- a/git-config/src/values/boolean.rs +++ b/git-config/src/values/boolean.rs @@ -1,9 +1,8 @@ -use crate::value; -use crate::Boolean; +use std::{borrow::Cow, convert::TryFrom, fmt::Display}; + use bstr::{BStr, BString, ByteSlice}; -use std::borrow::Cow; -use std::convert::TryFrom; -use std::fmt::Display; + +use crate::{value, Boolean}; impl Boolean { /// Generates a byte representation of the value. This should be used when diff --git a/git-config/src/values/color.rs b/git-config/src/values/color.rs index 54ae8d8283b..15001f9731a 100644 --- a/git-config/src/values/color.rs +++ b/git-config/src/values/color.rs @@ -1,11 +1,9 @@ #![allow(missing_docs)] -use crate::value; -use crate::Color; +use std::{borrow::Cow, convert::TryFrom, fmt::Display, str::FromStr}; + use bstr::{BStr, BString}; -use std::borrow::Cow; -use std::convert::TryFrom; -use std::fmt::Display; -use std::str::FromStr; + +use crate::{value, Color}; impl Display for Color { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/git-config/src/values/integer.rs b/git-config/src/values/integer.rs index af5b400bf36..a5e99e4686a 100644 --- a/git-config/src/values/integer.rs +++ b/git-config/src/values/integer.rs @@ -1,10 +1,8 @@ -use crate::value; -use crate::Integer; +use std::{borrow::Cow, convert::TryFrom, fmt::Display, str::FromStr}; + use bstr::{BStr, BString}; -use std::borrow::Cow; -use std::convert::TryFrom; -use std::fmt::Display; -use std::str::FromStr; + +use crate::{value, Integer}; impl Integer { /// Generates a byte representation of the value. This should be used when diff --git a/git-config/src/values/path.rs b/git-config/src/values/path.rs index 15fe3d29b70..7e84cda304f 100644 --- a/git-config/src/values/path.rs +++ b/git-config/src/values/path.rs @@ -1,7 +1,8 @@ -use crate::Path; +use std::{borrow::Cow, path::PathBuf}; + use bstr::BStr; -use std::borrow::Cow; -use std::path::PathBuf; + +use crate::Path; /// pub mod interpolate { diff --git a/git-config/tests/file/access/raw/raw_multi_value.rs b/git-config/tests/file/access/raw/raw_multi_value.rs index 3d9a2dd18d0..78630feb1bc 100644 --- a/git-config/tests/file/access/raw/raw_multi_value.rs +++ b/git-config/tests/file/access/raw/raw_multi_value.rs @@ -1,8 +1,9 @@ use std::convert::TryFrom; -use crate::file::cow_str; use git_config::{lookup, File}; +use crate::file::cow_str; + #[test] fn single_value_is_identical_to_single_value_query() { let config = File::try_from("[core]\na=b\nc=d").unwrap(); diff --git a/git-config/tests/file/access/read_only.rs b/git-config/tests/file/access/read_only.rs index 2b8ae8b570a..de09131e4b4 100644 --- a/git-config/tests/file/access/read_only.rs +++ b/git-config/tests/file/access/read_only.rs @@ -1,9 +1,10 @@ -use crate::file::cow_str; -use bstr::BStr; -use git_config::File; -use git_config::{color, integer, Boolean, Color, Integer}; use std::{borrow::Cow, convert::TryFrom, error::Error}; +use bstr::BStr; +use git_config::{color, integer, Boolean, Color, File, Integer}; + +use crate::file::cow_str; + /// Asserts we can cast into all variants of our type #[test] fn get_value_for_all_provided_values() -> crate::Result { diff --git a/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs b/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs index 4745f37af90..78fef319110 100644 --- a/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs +++ b/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs @@ -1,9 +1,10 @@ mod util; -use crate::file::from_paths::escape_backslashes; use serial_test::serial; use util::{assert_section_value, Condition, GitEnv}; +use crate::file::from_paths::escape_backslashes; + #[test] fn relative_path_with_trailing_slash_matches_like_star_star() -> crate::Result { assert_section_value(Condition::new("gitdir:worktree/"), GitEnv::repo_name("worktree")?) diff --git a/git-config/tests/file/from_paths/includes/conditional/gitdir/util.rs b/git-config/tests/file/from_paths/includes/conditional/gitdir/util.rs index 99a89371c2c..6300476caf1 100644 --- a/git-config/tests/file/from_paths/includes/conditional/gitdir/util.rs +++ b/git-config/tests/file/from_paths/includes/conditional/gitdir/util.rs @@ -1,12 +1,17 @@ #![cfg_attr(windows, allow(dead_code))] -use crate::file::cow_str; -use crate::file::from_paths::escape_backslashes; -use crate::file::from_paths::includes::conditional::options_with_git_dir; +use std::{ + io::Write, + path::{Path, PathBuf}, + process::Command, +}; + use bstr::{BString, ByteSlice}; -use std::io::Write; -use std::path::{Path, PathBuf}; -use std::process::Command; + +use crate::file::{ + cow_str, + from_paths::{escape_backslashes, includes::conditional::options_with_git_dir}, +}; #[derive(Debug)] pub struct GitEnv { diff --git a/git-config/tests/file/from_paths/includes/conditional/mod.rs b/git-config/tests/file/from_paths/includes/conditional/mod.rs index 28f56df34c1..4609d96a85d 100644 --- a/git-config/tests/file/from_paths/includes/conditional/mod.rs +++ b/git-config/tests/file/from_paths/includes/conditional/mod.rs @@ -1,11 +1,10 @@ -use std::fs; -use std::path::Path; +use std::{fs, path::Path}; -use crate::file::{cow_str, from_paths::escape_backslashes}; -use git_config::file::from_paths; -use git_config::File; +use git_config::{file::from_paths, File}; use tempfile::tempdir; +use crate::file::{cow_str, from_paths::escape_backslashes}; + mod gitdir; mod onbranch; diff --git a/git-config/tests/file/from_paths/includes/conditional/onbranch.rs b/git-config/tests/file/from_paths/includes/conditional/onbranch.rs index dababa65273..819cb33066a 100644 --- a/git-config/tests/file/from_paths/includes/conditional/onbranch.rs +++ b/git-config/tests/file/from_paths/includes/conditional/onbranch.rs @@ -1,10 +1,14 @@ -use std::convert::TryInto; -use std::{convert::TryFrom, fs}; +use std::{ + convert::{TryFrom, TryInto}, + fs, +}; use bstr::{BString, ByteSlice}; use git_config::file::from_paths; -use git_ref::transaction::{Change, PreviousValue, RefEdit}; -use git_ref::{FullName, Target}; +use git_ref::{ + transaction::{Change, PreviousValue, RefEdit}, + FullName, Target, +}; use git_repository as git; use tempfile::tempdir; diff --git a/git-config/tests/parse/mod.rs b/git-config/tests/parse/mod.rs index 4ec048fe54e..5962ac8ccd6 100644 --- a/git-config/tests/parse/mod.rs +++ b/git-config/tests/parse/mod.rs @@ -261,9 +261,10 @@ fn error() { } mod key { - use crate::parse::section::Key; use std::cmp::Ordering; + use crate::parse::section::Key; + #[test] fn case_insentive_eq() { assert_eq!(Key::from("aBc"), Key::from("AbC")); diff --git a/git-config/tests/value/normalize.rs b/git-config/tests/value/normalize.rs index c2b73338d55..f547879213e 100644 --- a/git-config/tests/value/normalize.rs +++ b/git-config/tests/value/normalize.rs @@ -1,7 +1,9 @@ -use crate::file::cow_str; -use git_config::value::normalize_bstr; use std::borrow::Cow; +use git_config::value::normalize_bstr; + +use crate::file::cow_str; + #[test] fn not_modified_is_borrowed() { let cow = normalize_bstr("hello world"); diff --git a/git-config/tests/values/color.rs b/git-config/tests/values/color.rs index 7e1d5cea73b..f6cc1c0e4b1 100644 --- a/git-config/tests/values/color.rs +++ b/git-config/tests/values/color.rs @@ -109,9 +109,10 @@ mod attribute { } mod from_git { + use std::convert::TryFrom; + use bstr::BStr; use git_config::Color; - use std::convert::TryFrom; #[test] fn reset() { diff --git a/git-config/tests/values/path.rs b/git-config/tests/values/path.rs index 96b4a4a2d92..6a65a4230b2 100644 --- a/git-config/tests/values/path.rs +++ b/git-config/tests/values/path.rs @@ -1,11 +1,13 @@ mod interpolate { - use std::borrow::Cow; - use std::path::{Path, PathBuf}; + use std::{ + borrow::Cow, + path::{Path, PathBuf}, + }; - use crate::file::cow_str; - use crate::value::b; use git_config::path::interpolate::Error; + use crate::{file::cow_str, value::b}; + #[test] fn backslash_is_not_special_and_they_are_not_escaping_anything() -> crate::Result { for path in ["C:\\foo\\bar", "/foo/bar"] { From c8693f9058765671804c93ead1eea1175a94f87c Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 10 Jul 2022 10:04:43 +0800 Subject: [PATCH 130/366] refactor `from_env` (#331) --- git-config/src/file/from_env.rs | 87 +++++++++++++-------------- git-config/src/file/mod.rs | 13 ++-- git-config/src/fs.rs | 1 + git-config/tests/file/from_env/mod.rs | 2 +- 4 files changed, 48 insertions(+), 55 deletions(-) diff --git a/git-config/src/file/from_env.rs b/git-config/src/file/from_env.rs index cb00c4524f6..587e4181d84 100644 --- a/git-config/src/file/from_env.rs +++ b/git-config/src/file/from_env.rs @@ -4,7 +4,7 @@ use bstr::BString; use crate::{ file::{from_paths, resolve_includes}, - parse, + parse::section, path::interpolate, File, }; @@ -14,7 +14,7 @@ use crate::{ #[allow(missing_docs)] pub enum Error { #[error("GIT_CONFIG_COUNT was not a positive integer: {}", .input)] - ParseError { input: String }, + InvalidConfigCount { input: String }, #[error("GIT_CONFIG_KEY_{} was not set", .key_id)] InvalidKeyId { key_id: usize }, #[error("GIT_CONFIG_KEY_{} was set to an invalid value: {}", .key_id, .key_val)] @@ -28,22 +28,19 @@ pub enum Error { } impl File<'static> { - /// Constructs a `git-config` from the default cascading sequence. - /// This is neither zero-alloc nor zero-copy. + /// Constructs a `git-config` from the default cascading sequence of global configuration files, + /// excluding any repository-local configuration. /// /// See for details. + // TODO: how does this relate to the `fs` module? Have a feeling options should contain instructions on which files to use. pub fn from_env_paths(options: from_paths::Options<'_>) -> Result, from_paths::Error> { use std::env; let mut paths = vec![]; if env::var("GIT_CONFIG_NO_SYSTEM").is_err() { - if let Some(git_config_system) = env::var_os("GIT_CONFIG_SYSTEM") { - paths.push(PathBuf::from(git_config_system)) - } else { - // In git the fallback is set to a build time macro which defaults to /etc/gitconfig - paths.push(PathBuf::from("/etc/gitconfig")); - } + let git_config_system_path = env::var_os("GIT_CONFIG_SYSTEM").unwrap_or_else(|| "/etc/gitconfig".into()); + paths.push(PathBuf::from(git_config_system_path)); } if let Some(git_config_global) = env::var_os("GIT_CONFIG_GLOBAL") { @@ -84,51 +81,49 @@ impl File<'static> { pub fn from_env(options: from_paths::Options<'_>) -> Result>, Error> { use std::env; let count: usize = match env::var("GIT_CONFIG_COUNT") { - Ok(v) => v.parse().map_err(|_| Error::ParseError { input: v })?, + Ok(v) => v.parse().map_err(|_| Error::InvalidConfigCount { input: v })?, Err(_) => return Ok(None), }; + if count == 0 { + return Ok(None); + } + let mut config = File::default(); for i in 0..count { let key = env::var(format!("GIT_CONFIG_KEY_{}", i)).map_err(|_| Error::InvalidKeyId { key_id: i })?; let value = env::var_os(format!("GIT_CONFIG_VALUE_{}", i)).ok_or(Error::InvalidValueId { value_id: i })?; - if let Some((section_name, maybe_subsection)) = key.split_once('.') { - let (subsection, key) = if let Some((subsection, key)) = maybe_subsection.rsplit_once('.') { - (Some(subsection), key) - } else { - (None, maybe_subsection) - }; - - let mut section = if let Ok(section) = config.section_mut(section_name, subsection) { - section - } else { - // Need to have config own the section and subsection names - // else they get dropped at the end of the loop. - config.new_section( - section_name.to_string(), - subsection.map(|subsection| Cow::Owned(subsection.to_string())), - ) - }; - - section.push( - parse::section::Key(Cow::Owned(BString::from(key))), - Cow::Owned(git_path::into_bstr(PathBuf::from(value)).into_owned()), - ); - } else { - return Err(Error::InvalidKeyValue { - key_id: i, - key_val: key.to_string(), - }); + match key.split_once('.') { + Some((section_name, maybe_subsection)) => { + let (subsection, key) = match maybe_subsection.rsplit_once('.') { + Some((subsection, key)) => (Some(subsection), key), + None => (None, maybe_subsection), + }; + + let mut section = match config.section_mut(section_name, subsection) { + Ok(section) => section, + Err(_) => config.new_section( + section_name.to_string(), + subsection.map(|subsection| Cow::Owned(subsection.to_string())), + ), + }; + + section.push( + section::Key(BString::from(key).into()), + git_path::into_bstr(PathBuf::from(value)).into_owned().into(), + ); + } + None => { + return Err(Error::InvalidKeyValue { + key_id: i, + key_val: key.to_string(), + }) + } } } - // This occurs when `GIT_CONFIG_COUNT` is set to zero. - if config.is_empty() { - Ok(None) - } else { - let mut buf = Vec::new(); - resolve_includes(&mut config, None, &mut buf, options)?; - Ok(Some(config)) - } + let mut buf = Vec::new(); + resolve_includes(&mut config, None, &mut buf, options)?; + Ok(Some(config)) } } diff --git a/git-config/src/file/mod.rs b/git-config/src/file/mod.rs index 74fa4bf213a..ab6abef414a 100644 --- a/git-config/src/file/mod.rs +++ b/git-config/src/file/mod.rs @@ -14,8 +14,7 @@ pub use section::*; mod value; pub use value::*; -/// Newtype to represent an index into some range. This is to differentiate -/// between raw usizes when multiple are present. +/// A strongly typed index into some range. #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Clone, Copy)] pub(crate) struct Index(pub(crate) usize); @@ -27,8 +26,7 @@ impl Add for Index { } } -/// Newtype to represent a size. This is to differentiate between raw usizes -/// when multiple are present. +/// A stronlgy typed a size. #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Clone, Copy)] pub(crate) struct Size(pub(crate) usize); @@ -40,15 +38,14 @@ impl AddAssign for Size { /// The section ID is a monotonically increasing ID used to refer to section bodies. /// This value does not imply any ordering between sections, as new sections -/// with higher section IDs may be in between lower ID sections. +/// with higher section IDs may be in between lower ID sections after `File` mutation. /// /// We need to use a section id because `git-config` permits sections with -/// identical names. As a result, we can't simply use the section name as a key -/// in a map. +/// identical names, making it ambiguous when used in maps, for instance. /// /// This id guaranteed to be unique, but not guaranteed to be compact. In other /// words, it's possible that a section may have an ID of 3 but the next section -/// has an ID of 5. +/// has an ID of 5 as 4 was deleted. #[derive(PartialEq, Eq, Hash, Copy, Clone, PartialOrd, Ord, Debug)] pub(crate) struct SectionBodyId(pub(crate) usize); diff --git a/git-config/src/fs.rs b/git-config/src/fs.rs index 530fe1cbd1d..cb5f7ce5d4c 100644 --- a/git-config/src/fs.rs +++ b/git-config/src/fs.rs @@ -15,6 +15,7 @@ use crate::{ lookup, File, }; +// TODO: how does this relate to `File::from_env_paths()`? #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] pub enum ConfigSource { /// System-wide configuration path. This is defined as diff --git a/git-config/tests/file/from_env/mod.rs b/git-config/tests/file/from_env/mod.rs index d0d77bccbe4..8b9261855a4 100644 --- a/git-config/tests/file/from_env/mod.rs +++ b/git-config/tests/file/from_env/mod.rs @@ -53,7 +53,7 @@ fn empty_with_zero_count() { fn parse_error_with_invalid_count() { let _env = Env::new().set("GIT_CONFIG_COUNT", "invalid"); let err = File::from_env(Options::default()).unwrap_err(); - assert!(matches!(err, from_env::Error::ParseError { .. })); + assert!(matches!(err, from_env::Error::InvalidConfigCount { .. })); } #[test] From ac57c4479e7b6867e8b8e71f7cf76de759dc64a2 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 10 Jul 2022 10:24:45 +0800 Subject: [PATCH 131/366] change!: `Path::interpolate()` now takes `path::interpolate::Options` instead of three parameters. (#331) --- git-config/src/file/from_paths.rs | 18 ++------------ git-config/src/file/resolve_includes.rs | 13 ++++------ git-config/src/values/path.rs | 23 +++++++++++++++--- git-config/tests/file/access/read_only.rs | 12 ++++++++-- .../includes/conditional/gitdir/util.rs | 2 +- .../from_paths/includes/conditional/mod.rs | 7 ++++-- git-config/tests/values/path.rs | 24 +++++++++++++++---- 7 files changed, 61 insertions(+), 38 deletions(-) diff --git a/git-config/src/file/from_paths.rs b/git-config/src/file/from_paths.rs index e89a3292eac..aaef5b80969 100644 --- a/git-config/src/file/from_paths.rs +++ b/git-config/src/file/from_paths.rs @@ -1,5 +1,3 @@ -use std::path::PathBuf; - use crate::{file::resolve_includes, parse, path::interpolate, File}; /// The error returned by [`File::from_paths()`][crate::File::from_paths()] and [`File::from_env_paths()`][crate::File::from_env_paths()]. @@ -25,10 +23,8 @@ pub enum Error { /// Options when loading git config using [`File::from_paths()`][crate::File::from_paths()]. #[derive(Clone, Copy)] pub struct Options<'a> { - /// The location where gitoxide or git is installed - /// /// Used during path interpolation. - pub git_install_dir: Option<&'a std::path::Path>, + pub interpolate: interpolate::Options<'a>, /// The maximum allowed length of the file include chain built by following nested resolve_includes where base level is depth = 0. pub max_depth: u8, /// When max depth is exceeded while following nested included, return an error if true or silently stop following @@ -44,26 +40,16 @@ pub struct Options<'a> { /// /// Used for conditional includes, e.g. `onbranch:` pub branch_name: Option<&'a git_ref::FullNameRef>, - /// The home directory of the current user. - /// - /// Used during path interpolation. - pub home_dir: Option<&'a std::path::Path>, - /// A function returning the home directory of a given user. - /// - /// Used during path interpolation. - pub home_for_user: Option Option>, } impl Default for Options<'_> { fn default() -> Self { Options { - git_install_dir: None, + interpolate: Default::default(), max_depth: 10, error_on_max_depth_exceeded: true, git_dir: None, branch_name: None, - home_dir: None, - home_for_user: Some(interpolate::home_for_user), // TODO: make this opt-in } } } diff --git a/git-config/src/file/resolve_includes.rs b/git-config/src/file/resolve_includes.rs index 2833dd22e0c..6a5354cbd9d 100644 --- a/git-config/src/file/resolve_includes.rs +++ b/git-config/src/file/resolve_includes.rs @@ -155,10 +155,8 @@ fn gitdir_matches( condition_path: &BStr, target_config_path: Option<&Path>, from_paths::Options { - git_install_dir, git_dir, - home_dir, - home_for_user, + interpolate: interpolate_options, .. }: from_paths::Options<'_>, wildmatch_mode: git_glob::wildmatch::Mode, @@ -167,8 +165,7 @@ fn gitdir_matches( git_path::to_unix_separators_on_windows(git_path::into_bstr(git_dir.ok_or(from_paths::Error::MissingGitDir)?)); let mut pattern_path: Cow<'_, _> = { - let path = - crate::Path::from(Cow::Borrowed(condition_path)).interpolate(git_install_dir, home_dir, home_for_user)?; + let path = crate::Path::from(Cow::Borrowed(condition_path)).interpolate(interpolate_options)?; git_path::into_bstr(path).into_owned().into() }; // NOTE: yes, only if we do path interpolation will the slashes be forced to unix separators on windows @@ -219,13 +216,11 @@ fn resolve( path: crate::Path<'_>, target_config_path: Option<&Path>, from_paths::Options { - git_install_dir, - home_dir, - home_for_user, + interpolate: interpolate_options, .. }: from_paths::Options<'_>, ) -> Result { - let path = path.interpolate(git_install_dir, home_dir, home_for_user)?; + let path = path.interpolate(interpolate_options)?; let path: PathBuf = if path.is_relative() { target_config_path .ok_or(from_paths::Error::MissingConfigPath)? diff --git a/git-config/src/values/path.rs b/git-config/src/values/path.rs index 7e84cda304f..00ae0410ad4 100644 --- a/git-config/src/values/path.rs +++ b/git-config/src/values/path.rs @@ -8,6 +8,21 @@ use crate::Path; pub mod interpolate { use std::path::PathBuf; + /// Options for interpolating paths with [`Path::interpolate()`][crate::Path::interpolate()]. + #[derive(Clone, Copy, Default)] + pub struct Options<'a> { + /// The location where gitoxide or git is installed + pub git_install_dir: Option<&'a std::path::Path>, + /// The home directory of the current user. + /// + /// Used during path interpolation. + pub home_dir: Option<&'a std::path::Path>, + /// A function returning the home directory of a given user. + /// + /// Used during path interpolation. + pub home_for_user: Option Option>, + } + /// The error returned by [`Path::interpolate()`][crate::Path::interpolate()]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] @@ -99,9 +114,11 @@ impl<'a> Path<'a> { /// wasn't provided. pub fn interpolate( self, - git_install_dir: Option<&std::path::Path>, - home_dir: Option<&std::path::Path>, - home_for_user: Option Option>, + interpolate::Options { + git_install_dir, + home_dir, + home_for_user, + }: interpolate::Options<'_>, ) -> Result, interpolate::Error> { if self.is_empty() { return Err(interpolate::Error::Missing { what: "path" }); diff --git a/git-config/tests/file/access/read_only.rs b/git-config/tests/file/access/read_only.rs index de09131e4b4..6bf9193c85f 100644 --- a/git-config/tests/file/access/read_only.rs +++ b/git-config/tests/file/access/read_only.rs @@ -1,7 +1,7 @@ use std::{borrow::Cow, convert::TryFrom, error::Error}; use bstr::BStr; -use git_config::{color, integer, Boolean, Color, File, Integer}; +use git_config::{color, integer, path, Boolean, Color, File, Integer}; use crate::file::cow_str; @@ -111,7 +111,15 @@ fn get_value_for_all_provided_values() -> crate::Result { let home = std::env::current_dir()?; let expected = home.join("tmp"); assert!(matches!(actual.value, Cow::Borrowed(_))); - assert_eq!(actual.interpolate(None, home.as_path().into(), None).unwrap(), expected); + assert_eq!( + actual + .interpolate(path::interpolate::Options { + home_dir: home.as_path().into(), + ..Default::default() + }) + .unwrap(), + expected + ); } let actual = config.path("core", None, "location").expect("present"); diff --git a/git-config/tests/file/from_paths/includes/conditional/gitdir/util.rs b/git-config/tests/file/from_paths/includes/conditional/gitdir/util.rs index 6300476caf1..04a471abb06 100644 --- a/git-config/tests/file/from_paths/includes/conditional/gitdir/util.rs +++ b/git-config/tests/file/from_paths/includes/conditional/gitdir/util.rs @@ -85,7 +85,7 @@ impl GitEnv { impl GitEnv { pub fn include_options(&self) -> git_config::file::from_paths::Options { let mut opts = options_with_git_dir(self.git_dir()); - opts.home_dir = Some(self.home_dir()); + opts.interpolate.home_dir = Some(self.home_dir()); opts } diff --git a/git-config/tests/file/from_paths/includes/conditional/mod.rs b/git-config/tests/file/from_paths/includes/conditional/mod.rs index 4609d96a85d..1d6d2bd8fad 100644 --- a/git-config/tests/file/from_paths/includes/conditional/mod.rs +++ b/git-config/tests/file/from_paths/includes/conditional/mod.rs @@ -1,6 +1,6 @@ use std::{fs, path::Path}; -use git_config::{file::from_paths, File}; +use git_config::{file::from_paths, path, File}; use tempfile::tempdir; use crate::file::{cow_str, from_paths::escape_backslashes}; @@ -79,7 +79,10 @@ fn include_and_includeif_correct_inclusion_order() { fn options_with_git_dir(git_dir: &Path) -> from_paths::Options<'_> { from_paths::Options { git_dir: Some(git_dir), - home_dir: Some(git_dir.parent().unwrap()), + interpolate: path::interpolate::Options { + home_dir: Some(git_dir.parent().unwrap()), + ..Default::default() + }, ..Default::default() } } diff --git a/git-config/tests/values/path.rs b/git-config/tests/values/path.rs index 6a65a4230b2..fd64a0d77ab 100644 --- a/git-config/tests/values/path.rs +++ b/git-config/tests/values/path.rs @@ -1,4 +1,5 @@ mod interpolate { + use git_config::path; use std::{ borrow::Cow, path::{Path, PathBuf}, @@ -11,7 +12,7 @@ mod interpolate { #[test] fn backslash_is_not_special_and_they_are_not_escaping_anything() -> crate::Result { for path in ["C:\\foo\\bar", "/foo/bar"] { - let actual = git_config::Path::from(Cow::Borrowed(b(path))).interpolate(None, None, None)?; + let actual = git_config::Path::from(Cow::Borrowed(b(path))).interpolate(Default::default())?; assert_eq!(actual, Path::new(path)); assert!( matches!(actual, Cow::Borrowed(_)), @@ -37,7 +38,10 @@ mod interpolate { std::path::PathBuf::from(format!("{}{}{}", git_install_dir, std::path::MAIN_SEPARATOR, expected)); assert_eq!( git_config::Path::from(cow_str(val)) - .interpolate(Path::new(git_install_dir).into(), None, None) + .interpolate(path::interpolate::Options { + git_install_dir: Path::new(git_install_dir).into(), + ..Default::default() + }) .unwrap(), expected, "prefix interpolation keeps separators as they are" @@ -52,7 +56,10 @@ mod interpolate { let git_install_dir = "/tmp/git"; assert_eq!( git_config::Path::from(Cow::Borrowed(b(path))) - .interpolate(Path::new(git_install_dir).into(), None, None) + .interpolate(path::interpolate::Options { + git_install_dir: Path::new(git_install_dir).into(), + ..Default::default() + }) .unwrap(), Path::new(path) ); @@ -71,7 +78,11 @@ mod interpolate { let expected = home.join("user").join("bar"); assert_eq!( git_config::Path::from(cow_str(path)) - .interpolate(None, Some(&home), Some(home_for_user)) + .interpolate(path::interpolate::Options { + home_dir: Some(&home), + home_for_user: Some(home_for_user), + ..Default::default() + }) .unwrap() .as_ref(), expected @@ -105,7 +116,10 @@ mod interpolate { fn interpolate_without_context( path: impl AsRef, ) -> Result, git_config::path::interpolate::Error> { - git_config::Path::from(Cow::Owned(path.as_ref().to_owned().into())).interpolate(None, None, Some(home_for_user)) + git_config::Path::from(Cow::Owned(path.as_ref().to_owned().into())).interpolate(path::interpolate::Options { + home_for_user: Some(home_for_user), + ..Default::default() + }) } fn home_for_user(name: &str) -> Option { From 858dc8b1b721ce5a45a76d9a97935cb0daf61e1a Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 10 Jul 2022 10:26:16 +0800 Subject: [PATCH 132/366] adapt to changes in `git-config` (#331) --- git-repository/src/config.rs | 10 +++++----- git-repository/src/repository/snapshots.rs | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/git-repository/src/config.rs b/git-repository/src/config.rs index 01b2623a45a..749daf7fea2 100644 --- a/git-repository/src/config.rs +++ b/git-repository/src/config.rs @@ -46,7 +46,7 @@ pub(crate) struct Cache { mod cache { use std::{convert::TryFrom, path::PathBuf}; - use git_config::{Boolean, File, Integer}; + use git_config::{path, Boolean, File, Integer}; use super::{Cache, Error}; use crate::{bstr::ByteSlice, permission}; @@ -74,11 +74,11 @@ mod cache { let excludes_file = config .path("core", None, "excludesFile") .map(|p| { - p.interpolate( + p.interpolate(path::interpolate::Options { git_install_dir, - home.as_deref(), - Some(git_config::path::interpolate::home_for_user), - ) + home_dir: home.as_deref(), + home_for_user: Some(git_config::path::interpolate::home_for_user), + }) .map(|p| p.into_owned()) }) .transpose()?; diff --git a/git-repository/src/repository/snapshots.rs b/git-repository/src/repository/snapshots.rs index 1d3e2ed5fc5..ecd4e219c2a 100644 --- a/git-repository/src/repository/snapshots.rs +++ b/git-repository/src/repository/snapshots.rs @@ -98,15 +98,15 @@ impl crate::Repository { .and_then(|path| { let install_dir = self.install_dir().ok()?; let home = self.config.home_dir(); - match path.interpolate( - Some(install_dir.as_path()), - home.as_deref(), - if self.linked_worktree_options.permissions.git_dir.is_all() { + match path.interpolate(git_config::path::interpolate::Options { + git_install_dir: Some(install_dir.as_path()), + home_dir: home.as_deref(), + home_for_user: if self.linked_worktree_options.permissions.git_dir.is_all() { Some(git_config::path::interpolate::home_for_user) } else { None }, - ) { + }) { Ok(path) => Some(path), Err(e) => { err.get_or_insert(e.into()); From 700d6aa34f2604ee72e619afb15c1bb6ce1697f2 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 10 Jul 2022 11:38:23 +0800 Subject: [PATCH 133/366] docs and refactor (#331) --- git-config/src/file/from_paths.rs | 4 +- git-config/src/file/impls.rs | 15 ++- git-config/src/file/section.rs | 99 +++++++++---------- .../tests/file/access/raw/mutable_value.rs | 3 +- 4 files changed, 54 insertions(+), 67 deletions(-) diff --git a/git-config/src/file/from_paths.rs b/git-config/src/file/from_paths.rs index aaef5b80969..eff7e38d216 100644 --- a/git-config/src/file/from_paths.rs +++ b/git-config/src/file/from_paths.rs @@ -55,7 +55,8 @@ impl Default for Options<'_> { } impl File<'static> { - /// Open a single configuration file by reading `path` into `buf` and copying all contents from there, without resolving includes. + /// Open a single configuration file by reading all data at `path` into `buf` and + /// copying all contents from there, without resolving includes. pub fn from_path_with_buf(path: &std::path::Path, buf: &mut Vec) -> Result { buf.clear(); std::io::copy(&mut std::fs::File::open(path)?, buf)?; @@ -63,7 +64,6 @@ impl File<'static> { } /// Constructs a `git-config` file from the provided paths in the order provided. - /// This is neither zero-copy nor zero-alloc. pub fn from_paths( paths: impl IntoIterator>, options: Options<'_>, diff --git a/git-config/src/file/impls.rs b/git-config/src/file/impls.rs index 325a45b8f55..d181bc6daf6 100644 --- a/git-config/src/file/impls.rs +++ b/git-config/src/file/impls.rs @@ -1,6 +1,6 @@ use std::{convert::TryFrom, fmt::Display}; -use bstr::{BString, ByteVec}; +use bstr::{BStr, BString, ByteVec}; use crate::{file::SectionBody, parse, File}; @@ -18,7 +18,7 @@ impl<'a> TryFrom<&'a [u8]> for File<'a> { type Error = parse::Error; /// Convenience constructor. Attempts to parse the provided byte string into - //// a [`File`]. See [`parse_from_bytes`] for more information. + /// a [`File`]. See [`parse_from_bytes`] for more information. /// /// [`parse_from_bytes`]: crate::parser::parse_from_bytes fn try_from(value: &'a [u8]) -> Result, Self::Error> { @@ -26,13 +26,13 @@ impl<'a> TryFrom<&'a [u8]> for File<'a> { } } -impl<'a> TryFrom<&'a BString> for File<'a> { +impl<'a> TryFrom<&'a BStr> for File<'a> { type Error = parse::Error; /// Convenience constructor. Attempts to parse the provided byte string into - //// a [`File`]. See [`State::from_bytes()`] for more information. - fn try_from(value: &'a BString) -> Result, Self::Error> { - parse::Events::from_bytes(value.as_ref()).map(File::from) + /// a [`File`]. See [`State::from_bytes()`] for more information. + fn try_from(value: &'a BStr) -> Result, Self::Error> { + parse::Events::from_bytes(value).map(File::from) } } @@ -89,9 +89,6 @@ impl From<&File<'_>> for BString { } impl Display for File<'_> { - /// Note that this is a best-effort attempt at printing a `GitConfig`. If - /// there are non UTF-8 values in your config, this will _NOT_ render as - /// read. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { for front_matter in self.frontmatter_events.as_ref() { front_matter.fmt(f)?; diff --git a/git-config/src/file/section.rs b/git-config/src/file/section.rs index a602eb900cb..f366d9dbd7b 100644 --- a/git-config/src/file/section.rs +++ b/git-config/src/file/section.rs @@ -17,14 +17,16 @@ use crate::{ /// A opaque type that represents a mutable reference to a section. #[allow(clippy::module_name_repetitions)] #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] -pub struct MutableSection<'borrow, 'event> { - section: &'borrow mut SectionBody<'event>, +pub struct MutableSection<'a, 'event> { + section: &'a mut SectionBody<'event>, implicit_newline: bool, whitespace: usize, } -impl<'borrow, 'event> MutableSection<'borrow, 'event> { +/// Mutating methods. +impl<'a, 'event> MutableSection<'a, 'event> { /// Adds an entry to the end of this section. + // TODO: multi-line handling - maybe just escape it for now. pub fn push(&mut self, key: Key<'event>, value: Cow<'event, BStr>) { if self.whitespace > 0 { self.section.0.push(Event::Whitespace({ @@ -102,22 +104,16 @@ impl<'borrow, 'event> MutableSection<'borrow, 'event> { Some(self.remove_internal(range)) } - /// Performs the removal, assuming the range is valid. This is used to - /// avoid duplicating searching for the range in [`Self::set`]. + /// Performs the removal, assuming the range is valid. fn remove_internal(&mut self, range: Range) -> Cow<'event, BStr> { self.section .0 .drain(range) - .fold(Cow::Owned(BString::default()), |acc, e| match e { - Event::Value(v) | Event::ValueNotDone(v) | Event::ValueDone(v) => { - // This is fine because we start out with an owned - // variant, so we never actually clone the - // accumulator. - let mut acc = acc.into_owned(); - acc.extend(&**v); - Cow::Owned(acc) + .fold(Cow::Owned(BString::default()), |mut acc, e| { + if let Event::Value(v) | Event::ValueNotDone(v) | Event::ValueDone(v) = e { + acc.to_mut().extend(&**v); } - _ => acc, + acc }) } @@ -128,29 +124,29 @@ impl<'borrow, 'event> MutableSection<'borrow, 'event> { } /// Enables or disables automatically adding newline events after adding - /// a value. This is enabled by default. - pub fn implicit_newline(&mut self, on: bool) { + /// a value. This is _enabled by default_. + pub fn set_implicit_newline(&mut self, on: bool) { self.implicit_newline = on; } - /// Sets the number of spaces before the start of a key value. By default, - /// this is set to two. Set to 0 to disable adding whitespace before a key + /// Sets the number of spaces before the start of a key value. The _default + /// is 2_. Set to 0 to disable adding whitespace before a key /// value. - pub fn set_whitespace(&mut self, num: usize) { + pub fn set_leading_space(&mut self, num: usize) { self.whitespace = num; } - /// Returns the number of whitespace this section will insert before the + /// Returns the number of space characters this section will insert before the /// beginning of a key. #[must_use] - pub const fn whitespace(&self) -> usize { + pub const fn leading_space(&self) -> usize { self.whitespace } } // Internal methods that may require exact indices for faster operations. -impl<'borrow, 'event> MutableSection<'borrow, 'event> { - pub(crate) fn new(section: &'borrow mut SectionBody<'event>) -> Self { +impl<'a, 'event> MutableSection<'a, 'event> { + pub(crate) fn new(section: &'a mut SectionBody<'event>) -> Self { Self { section, implicit_newline: true, @@ -164,35 +160,35 @@ impl<'borrow, 'event> MutableSection<'borrow, 'event> { start: Index, end: Index, ) -> Result, lookup::existing::Error> { - let mut found_key = false; - let mut latest_value = None; - let mut partial_value = None; - // section_id is guaranteed to exist in self.sections, else we have a - // violated invariant. + let mut expect_value = false; + let mut simple_value = None; + let mut concatenated_value = None::; for event in &self.section.0[start.0..=end.0] { match event { - Event::SectionKey(event_key) if event_key == key => found_key = true, - Event::Value(v) if found_key => { - found_key = false; - // Clones the Cow, doesn't copy underlying value if borrowed - latest_value = Some(v.clone()); + Event::SectionKey(event_key) if event_key == key => expect_value = true, + Event::Value(v) if expect_value => { + simple_value = Some(v.as_ref().into()); + break; } - Event::ValueNotDone(v) if found_key => { - latest_value = None; - partial_value = Some(v.as_ref().to_owned()); + Event::ValueNotDone(v) if expect_value => { + concatenated_value + .get_or_insert_with(Default::default) + .push_str(v.as_ref()); } - Event::ValueDone(v) if found_key => { - found_key = false; - partial_value.as_mut().unwrap().push_str(v.as_ref()); + Event::ValueDone(v) if expect_value => { + concatenated_value + .get_or_insert_with(Default::default) + .push_str(v.as_ref()); + break; } _ => (), } } - latest_value + simple_value .map(normalize) - .or_else(|| partial_value.map(normalize_bstring)) + .or_else(|| concatenated_value.map(normalize_bstring)) .ok_or(lookup::existing::Error::KeyMissing) } @@ -201,7 +197,7 @@ impl<'borrow, 'event> MutableSection<'borrow, 'event> { } pub(crate) fn set_internal(&mut self, index: Index, key: Key<'event>, value: BString) { - self.section.0.insert(index.0, Event::Value(Cow::Owned(value))); + self.section.0.insert(index.0, Event::Value(value.into())); self.section.0.insert(index.0, Event::KeyValueSeparator); self.section.0.insert(index.0, Event::SectionKey(key)); } @@ -229,12 +225,7 @@ impl<'event> SectionBody<'event> { &mut self.0 } - /// Retrieves the last matching value in a section with the given key. - /// Returns None if the key was not found. - // We hit this lint because of the unreachable!() call may panic, but this - // is a clippy bug (rust-clippy#6699), so we allow this lint for this - // function. - #[allow(clippy::missing_panics_doc)] + /// Retrieves the last matching value in a section with the given key, if present. #[must_use] pub fn value(&self, key: &Key<'_>) -> Option> { let range = self.value_range_by_key(key); @@ -242,7 +233,7 @@ impl<'event> SectionBody<'event> { return None; } - if range.end - range.start == 1 { + if range.len() == 1 { return self.0.get(range.start).map(|e| match e { Event::Value(v) => normalize_bstr(v.as_ref()), // range only has one element so we know it's a value event, so @@ -251,12 +242,11 @@ impl<'event> SectionBody<'event> { }); } - normalize_bstring(self.0[range].iter().fold(BString::default(), |mut acc, e| match e { - Event::Value(v) | Event::ValueNotDone(v) | Event::ValueDone(v) => { + normalize_bstring(self.0[range].iter().fold(BString::default(), |mut acc, e| { + if let Event::Value(v) | Event::ValueNotDone(v) | Event::ValueDone(v) = e { acc.push_str(v.as_ref()); - acc } - _ => acc, + acc })) .into() } @@ -276,8 +266,7 @@ impl<'event> SectionBody<'event> { Event::SectionKey(event_key) if event_key == key => found_key = true, Event::Value(v) if found_key => { found_key = false; - // Clones the Cow, doesn't copy underlying value if borrowed - values.push(normalize(Cow::Borrowed(v.as_ref()))); + values.push(normalize(v.as_ref().into())); partial_value = None; } Event::ValueNotDone(v) if found_key => { diff --git a/git-config/tests/file/access/raw/mutable_value.rs b/git-config/tests/file/access/raw/mutable_value.rs index e768e35eae0..0d70dc7f976 100644 --- a/git-config/tests/file/access/raw/mutable_value.rs +++ b/git-config/tests/file/access/raw/mutable_value.rs @@ -112,6 +112,7 @@ fn partial_values_are_supported() { let mut git_config = File::try_from( r#"[core] a=b"100"\ +c\ b [core] c=d @@ -119,7 +120,7 @@ b ) .unwrap(); let mut value = git_config.raw_value_mut("core", None, "a").unwrap(); - assert_eq!(&*value.get().unwrap(), "b100b"); + assert_eq!(&*value.get().unwrap(), "b100cb"); value.delete(); assert_eq!( git_config.to_string(), From 33efef6de375e399fe33a02e7b6dace1a679ac7e Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 10 Jul 2022 13:38:39 +0800 Subject: [PATCH 134/366] refactor (#331) This probably also fixes issues with only partially assembled multi-line vallues, which can't happen anymore. --- git-config/src/file/section.rs | 127 +++++++++++++++------------------ 1 file changed, 56 insertions(+), 71 deletions(-) diff --git a/git-config/src/file/section.rs b/git-config/src/file/section.rs index f366d9dbd7b..c26cf3d40a4 100644 --- a/git-config/src/file/section.rs +++ b/git-config/src/file/section.rs @@ -7,11 +7,12 @@ use std::{ use bstr::{BStr, BString, ByteVec}; +use crate::value::normalize_bstr; use crate::{ file::Index, lookup, parse, parse::{section::Key, Event}, - value::{normalize, normalize_bstr, normalize_bstring}, + value::{normalize, normalize_bstring}, }; /// A opaque type that represents a mutable reference to a section. @@ -161,35 +162,24 @@ impl<'a, 'event> MutableSection<'a, 'event> { end: Index, ) -> Result, lookup::existing::Error> { let mut expect_value = false; - let mut simple_value = None; - let mut concatenated_value = None::; + let mut concatenated_value = BString::default(); for event in &self.section.0[start.0..=end.0] { match event { Event::SectionKey(event_key) if event_key == key => expect_value = true, - Event::Value(v) if expect_value => { - simple_value = Some(v.as_ref().into()); - break; - } + Event::Value(v) if expect_value => return Ok(normalize_bstr(v.as_ref())), Event::ValueNotDone(v) if expect_value => { - concatenated_value - .get_or_insert_with(Default::default) - .push_str(v.as_ref()); + concatenated_value.push_str(v.as_ref()); } Event::ValueDone(v) if expect_value => { - concatenated_value - .get_or_insert_with(Default::default) - .push_str(v.as_ref()); - break; + concatenated_value.push_str(v.as_ref()); + return Ok(normalize_bstring(concatenated_value)); } _ => (), } } - simple_value - .map(normalize) - .or_else(|| concatenated_value.map(normalize_bstring)) - .ok_or(lookup::existing::Error::KeyMissing) + Err(lookup::existing::Error::KeyMissing) } pub(crate) fn delete(&mut self, start: Index, end: Index) { @@ -224,7 +214,10 @@ impl<'event> SectionBody<'event> { pub(crate) fn as_mut(&mut self) -> &mut parse::section::Events<'event> { &mut self.0 } +} +/// Access +impl<'event> SectionBody<'event> { /// Retrieves the last matching value in a section with the given key, if present. #[must_use] pub fn value(&self, key: &Key<'_>) -> Option> { @@ -232,53 +225,48 @@ impl<'event> SectionBody<'event> { if range.is_empty() { return None; } + let mut concatenated = BString::default(); - if range.len() == 1 { - return self.0.get(range.start).map(|e| match e { - Event::Value(v) => normalize_bstr(v.as_ref()), - // range only has one element so we know it's a value event, so - // it's impossible to reach this code. - _ => unreachable!(), - }); - } - - normalize_bstring(self.0[range].iter().fold(BString::default(), |mut acc, e| { - if let Event::Value(v) | Event::ValueNotDone(v) | Event::ValueDone(v) = e { - acc.push_str(v.as_ref()); + for event in &self.0[range] { + match event { + Event::Value(v) => { + return Some(normalize_bstr(v.as_ref())); + } + Event::ValueNotDone(v) => { + concatenated.push_str(v.as_ref()); + } + Event::ValueDone(v) => { + concatenated.push_str(v.as_ref()); + return Some(normalize_bstring(concatenated)); + } + _ => (), } - acc - })) - .into() + } + None } /// Retrieves all values that have the provided key name. This may return /// an empty vec, which implies there were no values with the provided key. #[must_use] pub fn values(&self, key: &Key<'_>) -> Vec> { - let mut values = vec![]; - let mut found_key = false; - let mut partial_value = None; + let mut values = Vec::new(); + let mut expect_value = false; + let mut concatenated_value = BString::default(); - // This can iterate forwards because we need to iterate over the whole - // section anyways for event in &self.0 { match event { - Event::SectionKey(event_key) if event_key == key => found_key = true, - Event::Value(v) if found_key => { - found_key = false; - values.push(normalize(v.as_ref().into())); - partial_value = None; + Event::SectionKey(event_key) if event_key == key => expect_value = true, + Event::Value(v) if expect_value => { + expect_value = false; + values.push(normalize_bstr(v.as_ref())); } - Event::ValueNotDone(v) if found_key => { - partial_value = Some(v.as_ref().to_owned()); + Event::ValueNotDone(v) if expect_value => { + concatenated_value.push_str(v.as_ref()); } - Event::ValueDone(v) if found_key => { - found_key = false; - let mut value = partial_value - .take() - .expect("ValueDone event called before ValueNotDone"); - value.push_str(v.as_ref()); - values.push(normalize_bstring(value)); + Event::ValueDone(v) if expect_value => { + expect_value = false; + concatenated_value.push_str(v.as_ref()); + values.push(normalize_bstring(std::mem::take(&mut concatenated_value))); } _ => (), } @@ -294,7 +282,7 @@ impl<'event> SectionBody<'event> { .filter_map(|e| if let Event::SectionKey(k) = e { Some(k) } else { None }) } - /// Checks if the section contains the provided key. + /// Returns true if the section containss the provided key. #[must_use] pub fn contains_key(&self, key: &Key<'_>) -> bool { self.0.iter().any(|e| { @@ -316,41 +304,42 @@ impl<'event> SectionBody<'event> { self.0.is_empty() } - /// Returns the the range containing the value events for the section. + /// Returns the the range containing the value events for the `key`. /// If the value is not found, then this returns an empty range. fn value_range_by_key(&self, key: &Key<'_>) -> Range { - let mut values_start = 0; - // value end needs to be offset by one so that the last value's index - // is included in the range - let mut values_end = 0; + let mut range = Range::default(); for (i, e) in self.0.iter().enumerate().rev() { match e { Event::SectionKey(k) => { if k == key { break; } - values_start = 0; - values_end = 0; + range = Range::default(); } Event::Value(_) => { - values_end = i + 1; - values_start = i; + (range.start, range.end) = (i, i); } Event::ValueNotDone(_) | Event::ValueDone(_) => { - if values_end == 0 { - values_end = i + 1; + if range.end == 0 { + range.end = i } else { - values_start = i; - } + range.start = i + }; } _ => (), } } - values_start..values_end + // value end needs to be offset by one so that the last value's index + // is included in the range + range.start..range.end + 1 } } +/// An owning iterator of a section body. Created by [`SectionBody::into_iter`]. +#[allow(clippy::module_name_repetitions)] +pub struct SectionBodyIter<'event>(VecDeque>); + impl<'event> IntoIterator for SectionBody<'event> { type Item = (Key<'event>, Cow<'event, BStr>); @@ -362,10 +351,6 @@ impl<'event> IntoIterator for SectionBody<'event> { } } -/// An owning iterator of a section body. Created by [`SectionBody::into_iter`]. -#[allow(clippy::module_name_repetitions)] -pub struct SectionBodyIter<'event>(VecDeque>); - impl<'event> Iterator for SectionBodyIter<'event> { type Item = (Key<'event>, Cow<'event, BStr>); From ba691243778b3eb89452fd1277c50dfe83d0075f Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 10 Jul 2022 14:49:50 +0800 Subject: [PATCH 135/366] once again zero-allocation for SectionBodyIter (#331) --- git-config/src/file/section.rs | 77 ++++++++++++++++------------------ 1 file changed, 37 insertions(+), 40 deletions(-) diff --git a/git-config/src/file/section.rs b/git-config/src/file/section.rs index c26cf3d40a4..909226ffddb 100644 --- a/git-config/src/file/section.rs +++ b/git-config/src/file/section.rs @@ -1,6 +1,5 @@ use std::{ borrow::Cow, - collections::VecDeque, iter::FusedIterator, ops::{Deref, Range}, }; @@ -16,7 +15,6 @@ use crate::{ }; /// A opaque type that represents a mutable reference to a section. -#[allow(clippy::module_name_repetitions)] #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] pub struct MutableSection<'a, 'event> { section: &'a mut SectionBody<'event>, @@ -202,7 +200,6 @@ impl<'event> Deref for MutableSection<'_, 'event> { } /// A opaque type that represents a section body. -#[allow(clippy::module_name_repetitions)] #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Clone, Debug, Default)] pub struct SectionBody<'event>(pub(crate) parse::section::Events<'event>); @@ -214,6 +211,37 @@ impl<'event> SectionBody<'event> { pub(crate) fn as_mut(&mut self) -> &mut parse::section::Events<'event> { &mut self.0 } + + /// Returns the the range containing the value events for the `key`. + /// If the value is not found, then this returns an empty range. + fn value_range_by_key(&self, key: &Key<'_>) -> Range { + let mut range = Range::default(); + for (i, e) in self.0.iter().enumerate().rev() { + match e { + Event::SectionKey(k) => { + if k == key { + break; + } + range = Range::default(); + } + Event::Value(_) => { + (range.start, range.end) = (i, i); + } + Event::ValueNotDone(_) | Event::ValueDone(_) => { + if range.end == 0 { + range.end = i + } else { + range.start = i + }; + } + _ => (), + } + } + + // value end needs to be offset by one so that the last value's index + // is included in the range + range.start..range.end + 1 + } } /// Access @@ -303,51 +331,20 @@ impl<'event> SectionBody<'event> { pub fn is_empty(&self) -> bool { self.0.is_empty() } - - /// Returns the the range containing the value events for the `key`. - /// If the value is not found, then this returns an empty range. - fn value_range_by_key(&self, key: &Key<'_>) -> Range { - let mut range = Range::default(); - for (i, e) in self.0.iter().enumerate().rev() { - match e { - Event::SectionKey(k) => { - if k == key { - break; - } - range = Range::default(); - } - Event::Value(_) => { - (range.start, range.end) = (i, i); - } - Event::ValueNotDone(_) | Event::ValueDone(_) => { - if range.end == 0 { - range.end = i - } else { - range.start = i - }; - } - _ => (), - } - } - - // value end needs to be offset by one so that the last value's index - // is included in the range - range.start..range.end + 1 - } } -/// An owning iterator of a section body. Created by [`SectionBody::into_iter`]. -#[allow(clippy::module_name_repetitions)] -pub struct SectionBodyIter<'event>(VecDeque>); +/// An owning iterator of a section body. Created by [`SectionBody::into_iter`], yielding +/// un-normalized (`key`, `value`) pairs. +// TODO: tests +pub struct SectionBodyIter<'event>(smallvec::IntoIter<[Event<'event>; 64]>); impl<'event> IntoIterator for SectionBody<'event> { type Item = (Key<'event>, Cow<'event, BStr>); type IntoIter = SectionBodyIter<'event>; - // TODO: see if this is used at all fn into_iter(self) -> Self::IntoIter { - SectionBodyIter(self.0.into_vec().into()) + SectionBodyIter(self.0.into_iter()) } } @@ -359,7 +356,7 @@ impl<'event> Iterator for SectionBodyIter<'event> { let mut partial_value = BString::default(); let mut value = None; - while let Some(event) = self.0.pop_front() { + for event in self.0.by_ref() { match event { Event::SectionKey(k) => key = Some(k), Event::Value(v) => { From b2b82da6c6d3c71b249c9ff2055cd98a58f1d988 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 10 Jul 2022 14:55:49 +0800 Subject: [PATCH 136/366] fix docs (#331) --- git-config/src/file/impls.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/git-config/src/file/impls.rs b/git-config/src/file/impls.rs index d181bc6daf6..7585079a285 100644 --- a/git-config/src/file/impls.rs +++ b/git-config/src/file/impls.rs @@ -18,9 +18,7 @@ impl<'a> TryFrom<&'a [u8]> for File<'a> { type Error = parse::Error; /// Convenience constructor. Attempts to parse the provided byte string into - /// a [`File`]. See [`parse_from_bytes`] for more information. - /// - /// [`parse_from_bytes`]: crate::parser::parse_from_bytes + /// a [`File`]. See [`from_bytes()`][crate::parse::from_bytes()] for more information. fn try_from(value: &'a [u8]) -> Result, Self::Error> { parse::Events::from_bytes(value).map(File::from) } @@ -30,7 +28,7 @@ impl<'a> TryFrom<&'a BStr> for File<'a> { type Error = parse::Error; /// Convenience constructor. Attempts to parse the provided byte string into - /// a [`File`]. See [`State::from_bytes()`] for more information. + /// a [`File`]. See [`Events::from_bytes()`][parse::Events::from_bytes()] for more information. fn try_from(value: &'a BStr) -> Result, Self::Error> { parse::Events::from_bytes(value).map(File::from) } From 879fad5afdcd90e248934e9c3b973d7bd438d1f9 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 10 Jul 2022 16:14:40 +0800 Subject: [PATCH 137/366] refactor (#331) less allocation when asking for section ids --- git-config/src/file/utils.rs | 9 ++++++--- git-config/src/types.rs | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/git-config/src/file/utils.rs b/git-config/src/file/utils.rs index e390c6e6ede..e42ecdc274e 100644 --- a/git-config/src/file/utils.rs +++ b/git-config/src/file/utils.rs @@ -53,7 +53,10 @@ impl<'event> File<'event> { } self.section_order.push_back(new_section_id); self.section_id_counter += 1; - self.sections.get_mut(&new_section_id).map(MutableSection::new).unwrap() + self.sections + .get_mut(&new_section_id) + .map(MutableSection::new) + .expect("previously inserted section") } /// Returns the mapping between section and subsection name to section ids. @@ -103,8 +106,8 @@ impl<'event> File<'event> { lookup .iter() .flat_map(|node| match node { - SectionBodyIds::Terminal(v) => v.clone(), - SectionBodyIds::NonTerminal(v) => v.values().flatten().copied().collect(), + SectionBodyIds::Terminal(v) => Box::new(v.iter().copied()) as Box>, + SectionBodyIds::NonTerminal(v) => Box::new(v.values().flatten().copied()), }) .collect() }) diff --git a/git-config/src/types.rs b/git-config/src/types.rs index 68130b9f23c..69dfa86068e 100644 --- a/git-config/src/types.rs +++ b/git-config/src/types.rs @@ -53,7 +53,8 @@ pub struct File<'event> { /// `git-config` file prohibits global values, this vec is limited to only /// comment, newline, and whitespace events. pub(crate) frontmatter_events: crate::parse::FrontMatterEvents<'event>, - /// Section name and subsection name to section id lookup tree. + /// Section name to section id lookup tree, with section bodies for subsections being in a non-terminal + /// variant of `SectionBodyIds`. pub(crate) section_lookup_tree: HashMap, Vec>>, /// This indirection with the SectionId as the key is critical to flexibly /// supporting `git-config` sections, as duplicated keys are permitted. From 2599680f7479e18612b4379efbe918139dde2345 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 10 Jul 2022 16:28:49 +0800 Subject: [PATCH 138/366] refactor (#331) less allocations thanks to iteration --- .../src/file/access/low_level/read_only.rs | 40 ++++++++++--------- git-config/src/file/resolve_includes.rs | 20 +++++----- git-config/src/file/utils.rs | 17 ++++---- 3 files changed, 39 insertions(+), 38 deletions(-) diff --git a/git-config/src/file/access/low_level/read_only.rs b/git-config/src/file/access/low_level/read_only.rs index 6d80d91de34..1007848b062 100644 --- a/git-config/src/file/access/low_level/read_only.rs +++ b/git-config/src/file/access/low_level/read_only.rs @@ -165,14 +165,15 @@ impl<'event> File<'event> { #[must_use] pub fn sections_by_name(&self, section_name: &str) -> Vec<&SectionBody<'event>> { self.section_ids_by_name(section_name) - .unwrap_or_default() - .into_iter() - .map(|id| { - self.sections - .get(&id) - .expect("section doesn't have id from from lookup") + .map(|ids| { + ids.map(|id| { + self.sections + .get(&id) + .expect("section doesn't have id from from lookup") + }) + .collect() }) - .collect() + .unwrap_or_default() } /// Get all sections that match the `section_name`, returning all matching section header along with their body. @@ -226,19 +227,20 @@ impl<'event> File<'event> { section_name: &str, ) -> Vec<(§ion::Header<'event>, &SectionBody<'event>)> { self.section_ids_by_name(section_name) - .unwrap_or_default() - .into_iter() - .map(|id| { - ( - self.section_headers - .get(&id) - .expect("section doesn't have a section header??"), - self.sections - .get(&id) - .expect("section doesn't have id from from lookup"), - ) + .map(|ids| { + ids.map(|id| { + ( + self.section_headers + .get(&id) + .expect("section doesn't have a section header??"), + self.sections + .get(&id) + .expect("section doesn't have id from from lookup"), + ) + }) + .collect() }) - .collect() + .unwrap_or_default() } /// Returns the number of values in the config, no matter in which section. diff --git a/git-config/src/file/resolve_includes.rs b/git-config/src/file/resolve_includes.rs index 6a5354cbd9d..88a605eb6a9 100644 --- a/git-config/src/file/resolve_includes.rs +++ b/git-config/src/file/resolve_includes.rs @@ -42,15 +42,17 @@ fn resolve_includes_recursive( let mut incl_section_ids = Vec::new(); for name in ["include", "includeIf"] { - for id in target_config.section_ids_by_name(name).unwrap_or_default() { - incl_section_ids.push(( - id, - target_config - .section_order - .iter() - .position(|&e| e == id) - .expect("section id is from config"), - )); + if let Ok(ids) = target_config.section_ids_by_name(name) { + for id in ids { + incl_section_ids.push(( + id, + target_config + .section_order + .iter() + .position(|&e| e == id) + .expect("section id is from config"), + )); + } } } incl_section_ids.sort_by(|a, b| a.1.cmp(&b.1)); diff --git a/git-config/src/file/utils.rs b/git-config/src/file/utils.rs index e42ecdc274e..4862f585c35 100644 --- a/git-config/src/file/utils.rs +++ b/git-config/src/file/utils.rs @@ -95,21 +95,18 @@ impl<'event> File<'event> { .ok_or(lookup::existing::Error::SubSectionMissing) } - pub(crate) fn section_ids_by_name<'a>( - &self, + pub(crate) fn section_ids_by_name<'b, 'a: 'b>( + &'b self, section_name: impl Into>, - ) -> Result, lookup::existing::Error> { + ) -> Result + '_, lookup::existing::Error> { let section_name = section_name.into(); self.section_lookup_tree .get(§ion_name) .map(|lookup| { - lookup - .iter() - .flat_map(|node| match node { - SectionBodyIds::Terminal(v) => Box::new(v.iter().copied()) as Box>, - SectionBodyIds::NonTerminal(v) => Box::new(v.values().flatten().copied()), - }) - .collect() + lookup.iter().flat_map(|node| match node { + SectionBodyIds::Terminal(v) => Box::new(v.iter().copied()) as Box>, + SectionBodyIds::NonTerminal(v) => Box::new(v.values().flatten().copied()), + }) }) .ok_or(lookup::existing::Error::SectionMissing) } From f1668e9d9e94f166fa05164612eab9ee26357d12 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 10 Jul 2022 16:42:07 +0800 Subject: [PATCH 139/366] refactor (#331) even less allocations when finding values, and still not done. --- .../src/file/access/low_level/mutating.rs | 10 ++++----- .../src/file/access/low_level/read_only.rs | 5 ++++- git-config/src/file/access/raw.rs | 22 ++++++++++--------- git-config/src/file/utils.rs | 14 +++++------- 4 files changed, 27 insertions(+), 24 deletions(-) diff --git a/git-config/src/file/access/low_level/mutating.rs b/git-config/src/file/access/low_level/mutating.rs index 96511358e87..375ef8af0ce 100644 --- a/git-config/src/file/access/low_level/mutating.rs +++ b/git-config/src/file/access/low_level/mutating.rs @@ -21,7 +21,7 @@ impl<'event> File<'event> { let id = section_ids.last().expect("BUG: Section lookup vec was empty"); Ok(MutableSection::new( self.sections - .get_mut(id) + .get_mut(&id) .expect("BUG: Section did not have id from lookup"), )) } @@ -108,7 +108,7 @@ impl<'event> File<'event> { let id = self .section_ids_by_name_and_subname(section_name, subsection_name.into()) .ok()? - .pop()?; + .last()?; self.section_order.remove( self.section_order .iter() @@ -145,13 +145,13 @@ impl<'event> File<'event> { new_section_name: impl Into>, new_subsection_name: impl Into>>, ) -> Result<(), lookup::existing::Error> { - let id = self.section_ids_by_name_and_subname(section_name, subsection_name.into())?; - let id = id + let ids = self.section_ids_by_name_and_subname(section_name, subsection_name.into())?; + let id = ids .last() .expect("list of sections were empty, which violates invariant"); let header = self .section_headers - .get_mut(id) + .get_mut(&id) .expect("sections does not have section id from section ids"); header.name = new_section_name.into(); header.subsection_name = new_subsection_name.into().map(into_cow_bstr); diff --git a/git-config/src/file/access/low_level/read_only.rs b/git-config/src/file/access/low_level/read_only.rs index 1007848b062..2d702fafa70 100644 --- a/git-config/src/file/access/low_level/read_only.rs +++ b/git-config/src/file/access/low_level/read_only.rs @@ -126,7 +126,10 @@ impl<'event> File<'event> { ) -> Result<&SectionBody<'event>, lookup::existing::Error> { let section_ids = self.section_ids_by_name_and_subname(section_name, subsection_name)?; let id = section_ids.last().expect("BUG: Section lookup vec was empty"); - Ok(self.sections.get(id).expect("BUG: Section did not have id from lookup")) + Ok(self + .sections + .get(&id) + .expect("BUG: Section did not have id from lookup")) } /// Gets all sections that match the provided name, ignoring any subsections. diff --git a/git-config/src/file/access/raw.rs b/git-config/src/file/access/raw.rs index 0ec5215b79d..ba0beff092a 100644 --- a/git-config/src/file/access/raw.rs +++ b/git-config/src/file/access/raw.rs @@ -31,12 +31,11 @@ impl<'event> File<'event> { let key = section::Key(Cow::::Borrowed(key.into())); for section_id in self .section_ids_by_name_and_subname(section_name, subsection_name)? - .iter() .rev() { if let Some(v) = self .sections - .get(section_id) + .get(§ion_id) .expect("sections does not have section id from section ids") .value(&key) { @@ -58,17 +57,19 @@ impl<'event> File<'event> { subsection_name: Option<&'lookup str>, key: &'lookup str, ) -> Result, lookup::existing::Error> { - let section_ids = self.section_ids_by_name_and_subname(section_name, subsection_name)?; + let mut section_ids = self + .section_ids_by_name_and_subname(section_name, subsection_name)? + .rev(); let key = section::Key(Cow::::Borrowed(key.into())); - for section_id in section_ids.iter().rev() { + while let Some(section_id) = section_ids.next() { let mut size = Size(0); let mut index = Index(0); let mut found_key = false; // todo: iter backwards for (i, event) in self .sections - .get(section_id) + .get(§ion_id) .expect("sections does not have section id from section ids") .as_ref() .iter() @@ -95,10 +96,11 @@ impl<'event> File<'event> { continue; } + drop(section_ids); return Ok(MutableValue::new( MutableSection::new( self.sections - .get_mut(section_id) + .get_mut(§ion_id) .expect("sections does not have section id from section ids"), ), key, @@ -229,14 +231,14 @@ impl<'event> File<'event> { let mut offsets = HashMap::new(); let mut entries = vec![]; - for section_id in section_ids.iter().rev() { + for section_id in section_ids.rev() { let mut last_boundary = 0; let mut found_key = false; let mut offset_list = vec![]; let mut offset_index = 0; for (i, event) in self .sections - .get(section_id) + .get(§ion_id) .expect("sections does not have section id from section ids") .as_ref() .iter() @@ -251,7 +253,7 @@ impl<'event> File<'event> { } Event::Value(_) | Event::ValueDone(_) if found_key => { found_key = false; - entries.push(EntryData::new(*section_id, offset_index)); + entries.push(EntryData::new(section_id, offset_index)); offset_list.push(i - last_boundary + 1); offset_index += 1; last_boundary = i + 1; @@ -259,7 +261,7 @@ impl<'event> File<'event> { _ => (), } } - offsets.insert(*section_id, offset_list); + offsets.insert(section_id, offset_list); } entries.sort(); diff --git a/git-config/src/file/utils.rs b/git-config/src/file/utils.rs index 4862f585c35..633def5a002 100644 --- a/git-config/src/file/utils.rs +++ b/git-config/src/file/utils.rs @@ -60,11 +60,11 @@ impl<'event> File<'event> { } /// Returns the mapping between section and subsection name to section ids. - pub(crate) fn section_ids_by_name_and_subname<'a>( - &self, + pub(crate) fn section_ids_by_name_and_subname<'b, 'a: 'b>( + &'b self, section_name: impl Into>, subsection_name: Option<&str>, - ) -> Result, lookup::existing::Error> { + ) -> Result + DoubleEndedIterator + '_, lookup::existing::Error> { let section_name = section_name.into(); let section_ids = self .section_lookup_tree @@ -78,21 +78,19 @@ impl<'event> File<'event> { let subsection_name: &BStr = subsection_name.into(); for node in section_ids { if let SectionBodyIds::NonTerminal(subsection_lookup) = node { - maybe_ids = subsection_lookup.get(subsection_name); + maybe_ids = subsection_lookup.get(subsection_name).map(|v| v.iter().copied()); break; } } } else { for node in section_ids { if let SectionBodyIds::Terminal(subsection_lookup) = node { - maybe_ids = Some(subsection_lookup); + maybe_ids = Some(subsection_lookup.iter().copied()); break; } } } - maybe_ids - .map(Vec::to_owned) - .ok_or(lookup::existing::Error::SubSectionMissing) + maybe_ids.ok_or(lookup::existing::Error::SubSectionMissing) } pub(crate) fn section_ids_by_name<'b, 'a: 'b>( From 539c2f67bede1247478ce75429690c2904915a89 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 10 Jul 2022 16:47:22 +0800 Subject: [PATCH 140/366] refactor (#331) more efficient reverse lookup of individual values --- .../src/file/access/low_level/mutating.rs | 17 +++++++++++------ .../src/file/access/low_level/read_only.rs | 7 +++++-- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/git-config/src/file/access/low_level/mutating.rs b/git-config/src/file/access/low_level/mutating.rs index 375ef8af0ce..12425ed1efa 100644 --- a/git-config/src/file/access/low_level/mutating.rs +++ b/git-config/src/file/access/low_level/mutating.rs @@ -17,8 +17,11 @@ impl<'event> File<'event> { section_name: &str, subsection_name: Option<&str>, ) -> Result, lookup::existing::Error> { - let section_ids = self.section_ids_by_name_and_subname(section_name, subsection_name)?; - let id = section_ids.last().expect("BUG: Section lookup vec was empty"); + let id = self + .section_ids_by_name_and_subname(section_name, subsection_name)? + .rev() + .next() + .expect("BUG: Section lookup vec was empty"); Ok(MutableSection::new( self.sections .get_mut(&id) @@ -108,7 +111,8 @@ impl<'event> File<'event> { let id = self .section_ids_by_name_and_subname(section_name, subsection_name.into()) .ok()? - .last()?; + .rev() + .next()?; self.section_order.remove( self.section_order .iter() @@ -145,9 +149,10 @@ impl<'event> File<'event> { new_section_name: impl Into>, new_subsection_name: impl Into>>, ) -> Result<(), lookup::existing::Error> { - let ids = self.section_ids_by_name_and_subname(section_name, subsection_name.into())?; - let id = ids - .last() + let id = self + .section_ids_by_name_and_subname(section_name, subsection_name.into())? + .rev() + .next() .expect("list of sections were empty, which violates invariant"); let header = self .section_headers diff --git a/git-config/src/file/access/low_level/read_only.rs b/git-config/src/file/access/low_level/read_only.rs index 2d702fafa70..405684c030c 100644 --- a/git-config/src/file/access/low_level/read_only.rs +++ b/git-config/src/file/access/low_level/read_only.rs @@ -124,8 +124,11 @@ impl<'event> File<'event> { section_name: &str, subsection_name: Option<&str>, ) -> Result<&SectionBody<'event>, lookup::existing::Error> { - let section_ids = self.section_ids_by_name_and_subname(section_name, subsection_name)?; - let id = section_ids.last().expect("BUG: Section lookup vec was empty"); + let id = self + .section_ids_by_name_and_subname(section_name, subsection_name)? + .rev() + .next() + .expect("BUG: Section lookup vec was empty"); Ok(self .sections .get(&id) From 2abffd6f2224edd98f806b5dbd4fc0e1c60019c5 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 10 Jul 2022 17:00:20 +0800 Subject: [PATCH 141/366] refactor (#331) public API makes use of iterator methods. --- .../src/file/access/low_level/read_only.rs | 40 +++++++++---------- git-config/src/file/utils.rs | 13 +++--- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/git-config/src/file/access/low_level/read_only.rs b/git-config/src/file/access/low_level/read_only.rs index 405684c030c..ad5f2d6151a 100644 --- a/git-config/src/file/access/low_level/read_only.rs +++ b/git-config/src/file/access/low_level/read_only.rs @@ -166,20 +166,19 @@ impl<'event> File<'event> { /// e = f /// "#; /// let git_config = git_config::File::try_from(config).unwrap(); - /// assert_eq!(git_config.sections_by_name("core").len(), 3); + /// assert_eq!(git_config.sections_by_name("core").count(), 3); /// ``` #[must_use] - pub fn sections_by_name(&self, section_name: &str) -> Vec<&SectionBody<'event>> { + pub fn sections_by_name<'a>(&'a self, section_name: &'a str) -> impl Iterator> + '_ { self.section_ids_by_name(section_name) - .map(|ids| { - ids.map(|id| { + .map(move |ids| { + Box::new(ids.map(move |id| { self.sections .get(&id) .expect("section doesn't have id from from lookup") - }) - .collect() + })) as Box> }) - .unwrap_or_default() + .unwrap_or_else(|_| Box::new(std::iter::empty())) } /// Get all sections that match the `section_name`, returning all matching section header along with their body. @@ -212,29 +211,29 @@ impl<'event> File<'event> { /// "#; /// let config = git_config::File::try_from(input).unwrap(); /// let url = config.sections_by_name_with_header("url"); - /// assert_eq!(url.len(), 2); + /// assert_eq!(url.count(), 2); /// - /// for (i, (header, body)) in url.iter().enumerate() { + /// for (i, (header, body)) in config.sections_by_name_with_header("url").enumerate() { /// let url = header.subsection_name.as_ref(); /// let instead_of = body.value(§ion::Key::from("insteadOf")); /// /// // todo(unstable-order): the order is not always the same, so `i` cannot be used here - /// if instead_of.as_ref().unwrap().as_ref().as_bytes().eq("https://github.com/".as_bytes()) { - /// assert_eq!(instead_of.unwrap().as_ref(), "https://github.com/".as_bytes()); + /// if instead_of.as_ref().unwrap().as_ref() == "https://github.com/" { + /// assert_eq!(instead_of.unwrap().as_ref(), "https://github.com/"); /// assert_eq!(url.unwrap().as_ref(), "ssh://git@github.com/"); /// } else { - /// assert_eq!(instead_of.unwrap().as_ref(), "https://bitbucket.org/".as_bytes()); + /// assert_eq!(instead_of.unwrap().as_ref(), "https://bitbucket.org/"); /// assert_eq!(url.unwrap().as_ref(), "ssh://git@bitbucket.org"); /// } /// } /// ``` - pub fn sections_by_name_with_header( - &self, - section_name: &str, - ) -> Vec<(§ion::Header<'event>, &SectionBody<'event>)> { + pub fn sections_by_name_with_header<'a>( + &'a self, + section_name: &'a str, + ) -> impl Iterator, &SectionBody<'event>)> + '_ { self.section_ids_by_name(section_name) - .map(|ids| { - ids.map(|id| { + .map(move |ids| { + Box::new(ids.map(move |id| { ( self.section_headers .get(&id) @@ -243,10 +242,9 @@ impl<'event> File<'event> { .get(&id) .expect("section doesn't have id from from lookup"), ) - }) - .collect() + })) as Box> }) - .unwrap_or_default() + .unwrap_or_else(|_| Box::new(std::iter::empty())) } /// Returns the number of values in the config, no matter in which section. diff --git a/git-config/src/file/utils.rs b/git-config/src/file/utils.rs index 633def5a002..6edfcbd1add 100644 --- a/git-config/src/file/utils.rs +++ b/git-config/src/file/utils.rs @@ -60,11 +60,14 @@ impl<'event> File<'event> { } /// Returns the mapping between section and subsection name to section ids. - pub(crate) fn section_ids_by_name_and_subname<'b, 'a: 'b>( - &'b self, + pub(crate) fn section_ids_by_name_and_subname<'a>( + &'a self, section_name: impl Into>, subsection_name: Option<&str>, - ) -> Result + DoubleEndedIterator + '_, lookup::existing::Error> { + ) -> Result< + impl Iterator + ExactSizeIterator + DoubleEndedIterator + '_, + lookup::existing::Error, + > { let section_name = section_name.into(); let section_ids = self .section_lookup_tree @@ -93,8 +96,8 @@ impl<'event> File<'event> { maybe_ids.ok_or(lookup::existing::Error::SubSectionMissing) } - pub(crate) fn section_ids_by_name<'b, 'a: 'b>( - &'b self, + pub(crate) fn section_ids_by_name<'a>( + &'a self, section_name: impl Into>, ) -> Result + '_, lookup::existing::Error> { let section_name = section_name.into(); From 65c520c4de8187884f87059adf5cef9cbdcd90a2 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 10 Jul 2022 17:19:26 +0800 Subject: [PATCH 142/366] change!: allocation free `File::sections_by_name()` and `File::sections_by_name_with_header()`. (#331) --- .../src/file/access/low_level/read_only.rs | 51 +++++++++---------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/git-config/src/file/access/low_level/read_only.rs b/git-config/src/file/access/low_level/read_only.rs index ad5f2d6151a..2699d6fd8e9 100644 --- a/git-config/src/file/access/low_level/read_only.rs +++ b/git-config/src/file/access/low_level/read_only.rs @@ -166,19 +166,20 @@ impl<'event> File<'event> { /// e = f /// "#; /// let git_config = git_config::File::try_from(config).unwrap(); - /// assert_eq!(git_config.sections_by_name("core").count(), 3); + /// assert_eq!(git_config.sections_by_name("core").map_or(0, |s|s.count()), 3); /// ``` #[must_use] - pub fn sections_by_name<'a>(&'a self, section_name: &'a str) -> impl Iterator> + '_ { - self.section_ids_by_name(section_name) - .map(move |ids| { - Box::new(ids.map(move |id| { - self.sections - .get(&id) - .expect("section doesn't have id from from lookup") - })) as Box> + pub fn sections_by_name<'a>( + &'a self, + section_name: &'a str, + ) -> Option> + '_> { + self.section_ids_by_name(section_name).ok().map(move |ids| { + ids.map(move |id| { + self.sections + .get(&id) + .expect("section doesn't have id from from lookup") }) - .unwrap_or_else(|_| Box::new(std::iter::empty())) + }) } /// Get all sections that match the `section_name`, returning all matching section header along with their body. @@ -211,9 +212,9 @@ impl<'event> File<'event> { /// "#; /// let config = git_config::File::try_from(input).unwrap(); /// let url = config.sections_by_name_with_header("url"); - /// assert_eq!(url.count(), 2); + /// assert_eq!(url.map_or(0, |s| s.count()), 2); /// - /// for (i, (header, body)) in config.sections_by_name_with_header("url").enumerate() { + /// for (i, (header, body)) in config.sections_by_name_with_header("url").unwrap().enumerate() { /// let url = header.subsection_name.as_ref(); /// let instead_of = body.value(§ion::Key::from("insteadOf")); /// @@ -230,21 +231,19 @@ impl<'event> File<'event> { pub fn sections_by_name_with_header<'a>( &'a self, section_name: &'a str, - ) -> impl Iterator, &SectionBody<'event>)> + '_ { - self.section_ids_by_name(section_name) - .map(move |ids| { - Box::new(ids.map(move |id| { - ( - self.section_headers - .get(&id) - .expect("section doesn't have a section header??"), - self.sections - .get(&id) - .expect("section doesn't have id from from lookup"), - ) - })) as Box> + ) -> Option, &SectionBody<'event>)> + '_> { + self.section_ids_by_name(section_name).ok().map(move |ids| { + ids.map(move |id| { + ( + self.section_headers + .get(&id) + .expect("section doesn't have a section header??"), + self.sections + .get(&id) + .expect("section doesn't have id from from lookup"), + ) }) - .unwrap_or_else(|_| Box::new(std::iter::empty())) + }) } /// Returns the number of values in the config, no matter in which section. From 7aa8a0b66f3508336e8c20a1a0d2b481e7b9bde8 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 10 Jul 2022 19:28:44 +0800 Subject: [PATCH 143/366] review and refactor 'File::value' module (#331) --- git-config/src/file/access/raw.rs | 20 ++- git-config/src/file/mod.rs | 34 +++-- git-config/src/file/section.rs | 5 +- git-config/src/file/value.rs | 218 ++++++++++-------------------- 4 files changed, 104 insertions(+), 173 deletions(-) diff --git a/git-config/src/file/access/raw.rs b/git-config/src/file/access/raw.rs index ba0beff092a..cf77dd3fd46 100644 --- a/git-config/src/file/access/raw.rs +++ b/git-config/src/file/access/raw.rs @@ -3,7 +3,7 @@ use std::{borrow::Cow, collections::HashMap}; use bstr::{BStr, BString}; use crate::{ - file::{EntryData, Index, MutableMultiValue, MutableSection, MutableValue, Size}, + file::{value::EntryData, Index, MutableMultiValue, MutableSection, MutableValue, Size}, lookup, parse::{section, Event}, File, @@ -97,8 +97,8 @@ impl<'event> File<'event> { } drop(section_ids); - return Ok(MutableValue::new( - MutableSection::new( + return Ok(MutableValue { + section: MutableSection::new( self.sections .get_mut(§ion_id) .expect("sections does not have section id from section ids"), @@ -106,7 +106,7 @@ impl<'event> File<'event> { key, index, size, - )); + }); } Err(lookup::existing::Error::KeyMissing) @@ -253,7 +253,10 @@ impl<'event> File<'event> { } Event::Value(_) | Event::ValueDone(_) if found_key => { found_key = false; - entries.push(EntryData::new(section_id, offset_index)); + entries.push(EntryData { + section_id, + offset_index, + }); offset_list.push(i - last_boundary + 1); offset_index += 1; last_boundary = i + 1; @@ -269,7 +272,12 @@ impl<'event> File<'event> { if entries.is_empty() { Err(lookup::existing::Error::KeyMissing) } else { - Ok(MutableMultiValue::new(&mut self.sections, key, entries, offsets)) + Ok(MutableMultiValue { + section: &mut self.sections, + key, + indices_and_sizes: entries, + offsets, + }) } } diff --git a/git-config/src/file/mod.rs b/git-config/src/file/mod.rs index ab6abef414a..c3edb3ece26 100644 --- a/git-config/src/file/mod.rs +++ b/git-config/src/file/mod.rs @@ -1,5 +1,4 @@ -//! This module provides a high level wrapper around a single `git-config` file, -//! or multiple concatenated `git-config` files. +//! A high level wrapper around a single or multiple `git-config` file, for reading and mutation. use std::{ borrow::Cow, collections::HashMap, @@ -9,10 +8,23 @@ use std::{ use bstr::BStr; mod section; -pub use section::*; +pub use section::{MutableSection, SectionBody, SectionBodyIter}; mod value; -pub use value::*; +pub use value::{MutableMultiValue, MutableValue}; + +/// +pub mod from_env; + +/// +pub mod from_paths; + +mod access; +mod impls; +mod utils; + +mod resolve_includes; +pub(crate) use resolve_includes::resolve_includes; /// A strongly typed index into some range. #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Clone, Copy)] @@ -61,19 +73,5 @@ pub(crate) enum SectionBodyIds<'a> { /// A hashmap from sub-section names to section ids. NonTerminal(HashMap, Vec>), } - -/// -pub mod from_env; - -mod resolve_includes; -pub(crate) use resolve_includes::resolve_includes; - -/// -pub mod from_paths; - -mod access; -mod impls; -mod utils; - #[cfg(test)] mod tests; diff --git a/git-config/src/file/section.rs b/git-config/src/file/section.rs index 909226ffddb..bba7f3682f7 100644 --- a/git-config/src/file/section.rs +++ b/git-config/src/file/section.rs @@ -6,6 +6,7 @@ use std::{ use bstr::{BStr, BString, ByteVec}; +use crate::file::Size; use crate::value::normalize_bstr; use crate::{ file::Index, @@ -184,10 +185,12 @@ impl<'a, 'event> MutableSection<'a, 'event> { self.section.0.drain(start.0..=end.0); } - pub(crate) fn set_internal(&mut self, index: Index, key: Key<'event>, value: BString) { + pub(crate) fn set_internal(&mut self, index: Index, key: Key<'event>, value: BString) -> Size { self.section.0.insert(index.0, Event::Value(value.into())); self.section.0.insert(index.0, Event::KeyValueSeparator); self.section.0.insert(index.0, Event::SectionKey(key)); + + Size(3) } } diff --git a/git-config/src/file/value.rs b/git-config/src/file/value.rs index 2a02cd26099..d76af222859 100644 --- a/git-config/src/file/value.rs +++ b/git-config/src/file/value.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, collections::HashMap, ops::DerefMut}; -use bstr::{BStr, BString}; +use bstr::{BStr, BString, ByteVec}; use crate::{ file::{ @@ -12,41 +12,18 @@ use crate::{ value::{normalize_bstr, normalize_bstring}, }; -/// An intermediate representation of a mutable value obtained from -/// [`File`]. -/// -/// This holds a mutable reference to the underlying data structure of -/// [`File`], and thus guarantees through Rust's borrower checker that -/// multiple mutable references to [`File`] cannot be owned at the same -/// time. -/// -/// [`File`]: crate::File -#[allow(clippy::module_name_repetitions)] +/// An intermediate representation of a mutable value obtained from a [`File`][crate::File]. #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] pub struct MutableValue<'borrow, 'lookup, 'event> { - section: MutableSection<'borrow, 'event>, - key: section::Key<'lookup>, - index: Index, - size: Size, + pub(crate) section: MutableSection<'borrow, 'event>, + pub(crate) key: section::Key<'lookup>, + pub(crate) index: Index, + pub(crate) size: Size, } impl<'borrow, 'lookup, 'event> MutableValue<'borrow, 'lookup, 'event> { - pub(crate) const fn new( - section: MutableSection<'borrow, 'event>, - key: section::Key<'lookup>, - index: Index, - size: Size, - ) -> Self { - Self { - section, - key, - index, - size, - } - } - - /// Returns the actual value. This is computed each time this is called, so - /// it's best to reuse this value or own it if an allocation is acceptable. + /// Returns the actual value. This is computed each time this is called + /// requiring an allocation for multi-line values. pub fn get(&self) -> Result, lookup::existing::Error> { self.section.get(&self.key, self.index, self.index + self.size) } @@ -65,8 +42,7 @@ impl<'borrow, 'lookup, 'event> MutableValue<'borrow, 'lookup, 'event> { if self.size.0 > 0 { self.section.delete(self.index, self.index + self.size); } - self.size = Size(3); - self.section.set_internal(self.index, self.key.to_owned(), input); + self.size = self.section.set_internal(self.index, self.key.to_owned(), input); } /// Removes the value. Does nothing when called multiple times in @@ -82,93 +58,50 @@ impl<'borrow, 'lookup, 'event> MutableValue<'borrow, 'lookup, 'event> { /// Internal data structure for [`MutableMultiValue`] #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] pub(crate) struct EntryData { - section_id: SectionBodyId, - offset_index: usize, -} - -impl EntryData { - pub(crate) const fn new(section_id: SectionBodyId, offset_index: usize) -> Self { - Self { - section_id, - offset_index, - } - } + pub(crate) section_id: SectionBodyId, + pub(crate) offset_index: usize, } -/// An intermediate representation of a mutable multivar obtained from -/// [`File`]. -/// -/// This holds a mutable reference to the underlying data structure of -/// [`File`], and thus guarantees through Rust's borrower checker that -/// multiple mutable references to [`File`] cannot be owned at the same -/// time. -/// -/// [`File`]: crate::File -#[allow(clippy::module_name_repetitions)] +/// An intermediate representation of a mutable multivar obtained from a [`File`][crate::File]. #[derive(PartialEq, Eq, Debug)] pub struct MutableMultiValue<'borrow, 'lookup, 'event> { - section: &'borrow mut HashMap>, - key: section::Key<'lookup>, + pub(crate) section: &'borrow mut HashMap>, + pub(crate) key: section::Key<'lookup>, /// Each entry data struct provides sufficient information to index into /// [`Self::offsets`]. This layer of indirection is used for users to index /// into the offsets rather than leaking the internal data structures. - indices_and_sizes: Vec, + pub(crate) indices_and_sizes: Vec, /// Each offset represents the size of a event slice and whether or not the /// event slice is significant or not. This is used to index into the /// actual section. - offsets: HashMap>, + pub(crate) offsets: HashMap>, } impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { - pub(crate) fn new( - section: &'borrow mut HashMap>, - key: section::Key<'lookup>, - indices_and_sizes: Vec, - offsets: HashMap>, - ) -> Self { - Self { - section, - key, - indices_and_sizes, - offsets, - } - } - - /// Returns the actual values. This is computed each time this is called. + /// Returns the actual values. pub fn get(&self) -> Result>, lookup::existing::Error> { - let mut found_key = false; - let mut values = vec![]; - let mut partial_value = None; - // section_id is guaranteed to exist in self.sections, else we have a - // violated invariant. + let mut expect_value = false; + let mut values = Vec::new(); + let mut concatenated_value = BString::default(); + for EntryData { section_id, offset_index, } in &self.indices_and_sizes { let (offset, size) = MutableMultiValue::index_and_size(&self.offsets, *section_id, *offset_index); - for event in &self - .section - .get(section_id) - .expect("sections does not have section id from section ids") - .as_ref()[offset..offset + size] - { + for event in &self.section.get(section_id).expect("known section id").as_ref()[offset..offset + size] { match event { - Event::SectionKey(section_key) if *section_key == self.key => found_key = true, - Event::Value(v) if found_key => { - found_key = false; + Event::SectionKey(section_key) if *section_key == self.key => expect_value = true, + Event::Value(v) if expect_value => { + expect_value = false; values.push(normalize_bstr(v.as_ref())); } - Event::ValueNotDone(v) if found_key => { - partial_value = Some((*v).to_vec()); - } - Event::ValueDone(v) if found_key => { - found_key = false; - let mut value = partial_value - .take() - .expect("Somehow got ValueDone before ValueNotDone event"); - value.extend(&***v); - values.push(normalize_bstring(value)); + Event::ValueNotDone(v) if expect_value => concatenated_value.push_str(v.as_ref()), + Event::ValueDone(v) if expect_value => { + expect_value = false; + concatenated_value.push_str(v.as_ref()); + values.push(normalize_bstring(std::mem::take(&mut concatenated_value))); } _ => (), } @@ -182,14 +115,14 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { Ok(values) } - /// Returns the size of values the multivar has. + /// Returns the amount of values within this multivar. #[must_use] pub fn len(&self) -> usize { self.indices_and_sizes.len() } - /// Returns if the multivar has any values. This might occur if the value - /// was deleted but not set with a new value. + /// Returns true if the multivar does not have any values. + /// This might occur if the value was deleted but wasn't yet set with a new value. #[must_use] pub fn is_empty(&self) -> bool { self.indices_and_sizes.is_empty() @@ -218,7 +151,7 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { /// # Safety /// /// This will panic if the index is out of range. - pub fn set_value<'a: 'event>(&mut self, index: usize, input: Cow<'a, BStr>) { + pub fn set_value(&mut self, index: usize, input: Cow<'event, BStr>) { let EntryData { section_id, offset_index, @@ -226,23 +159,21 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { MutableMultiValue::set_value_inner( &self.key, &mut self.offsets, - self.section - .get_mut(§ion_id) - .expect("sections does not have section id from section ids"), + self.section.get_mut(§ion_id).expect("known section id"), section_id, offset_index, input, ); } - /// Sets all values to the provided values. Note that this follows [`zip`] + /// Sets all values to the provided ones. Note that this follows [`zip`] /// logic: if the number of values in the input is less than the number of /// values currently existing, then only the first `n` values are modified. /// If more values are provided than there currently are, then the /// remaining values are ignored. /// /// [`zip`]: std::iter::Iterator::zip - pub fn set_values<'a: 'event>(&mut self, input: impl Iterator>) { + pub fn set_values(&mut self, input: impl Iterator>) { for ( EntryData { section_id, @@ -254,9 +185,7 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { Self::set_value_inner( &self.key, &mut self.offsets, - self.section - .get_mut(section_id) - .expect("sections does not have section id from section ids"), + self.section.get_mut(section_id).expect("known section id"), *section_id, *offset_index, value, @@ -265,13 +194,13 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { } /// Sets all values in this multivar to the provided one by copying the - /// input for all values. + /// `input` string to all values. pub fn set_str_all(&mut self, input: &str) { self.set_owned_values_all(input); } /// Sets all values in this multivar to the provided one by copying the - /// input bytes for all values. + /// `input` bytes to all values. pub fn set_owned_values_all<'a>(&mut self, input: impl Into<&'a BStr>) { let input = input.into(); for EntryData { @@ -282,9 +211,7 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { Self::set_value_inner( &self.key, &mut self.offsets, - self.section - .get_mut(section_id) - .expect("sections does not have section id from section ids"), + self.section.get_mut(section_id).expect("known section id"), *section_id, *offset_index, Cow::Owned(input.to_owned()), @@ -293,13 +220,12 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { } /// Sets all values in this multivar to the provided one without owning the - /// provided input. Note that this requires `input` to last longer than - /// [`File`]. Consider using [`Self::set_owned_values_all`] or + /// provided input. Consider using [`Self::set_owned_values_all`] or /// [`Self::set_str_all`] unless you have a strict performance or memory /// need for a more ergonomic interface. /// /// [`File`]: crate::File - pub fn set_values_all<'a: 'event>(&mut self, input: &'a BStr) { + pub fn set_values_all(&mut self, input: &'event BStr) { for EntryData { section_id, offset_index, @@ -308,9 +234,7 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { Self::set_value_inner( &self.key, &mut self.offsets, - self.section - .get_mut(section_id) - .expect("sections does not have section id from section ids"), + self.section.get_mut(section_id).expect("known section id"), *section_id, *offset_index, Cow::Borrowed(input), @@ -327,12 +251,13 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { input: Cow<'a, BStr>, ) { let (offset, size) = MutableMultiValue::index_and_size(offsets, section_id, offset_index); - section.as_mut().drain(offset..offset + size); + let section = section.as_mut(); + section.drain(offset..offset + size); MutableMultiValue::set_offset(offsets, section_id, offset_index, 3); - section.as_mut().insert(offset, Event::Value(input)); - section.as_mut().insert(offset, Event::KeyValueSeparator); - section.as_mut().insert(offset, Event::SectionKey(key.to_owned())); + section.insert(offset, Event::Value(input)); + section.insert(offset, Event::KeyValueSeparator); + section.insert(offset, Event::SectionKey(key.to_owned())); } /// Removes the value at the given index. Does nothing when called multiple @@ -347,16 +272,17 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { offset_index, } = &self.indices_and_sizes[index]; let (offset, size) = MutableMultiValue::index_and_size(&self.offsets, *section_id, *offset_index); - if size > 0 { - self.section - .get_mut(section_id) - .expect("sections does not have section id from section ids") - .as_mut() - .drain(offset..offset + size); - - Self::set_offset(&mut self.offsets, *section_id, *offset_index, 0); - self.indices_and_sizes.remove(index); + if size == 0 { + return; } + self.section + .get_mut(section_id) + .expect("known section id") + .as_mut() + .drain(offset..offset + size); + + Self::set_offset(&mut self.offsets, *section_id, *offset_index, 0); + self.indices_and_sizes.remove(index); } /// Removes all values. Does nothing when called multiple times in @@ -368,20 +294,19 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { } in &self.indices_and_sizes { let (offset, size) = MutableMultiValue::index_and_size(&self.offsets, *section_id, *offset_index); - if size > 0 { - self.section - .get_mut(section_id) - .expect("sections does not have section id from section ids") - .as_mut() - .drain(offset..offset + size); - Self::set_offset(&mut self.offsets, *section_id, *offset_index, 0); + if size == 0 { + continue; } + self.section + .get_mut(section_id) + .expect("known section id") + .as_mut() + .drain(offset..offset + size); + Self::set_offset(&mut self.offsets, *section_id, *offset_index, 0); } self.indices_and_sizes.clear(); } - // SectionId is the same size as a reference, which means it's just as - // efficient passing in a value instead of a reference. fn index_and_size( offsets: &'lookup HashMap>, section_id: SectionBodyId, @@ -389,17 +314,14 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { ) -> (usize, usize) { offsets .get(§ion_id) - .expect("sections does not have section id from section ids") + .expect("known section id") .iter() .take(offset_index + 1) - .fold((0, 0), |(old, new), offset| (old + new, *offset)) + .fold((0, 0), |(total_ofs, ofs), size| (total_ofs + ofs, *size)) } // This must be an associated function rather than a method to allow Rust // to split mutable borrows. - // - // SectionId is the same size as a reference, which means it's just as - // efficient passing in a value instead of a reference. fn set_offset( offsets: &mut HashMap>, section_id: SectionBodyId, @@ -408,7 +330,7 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { ) { *offsets .get_mut(§ion_id) - .expect("sections does not have section id from section ids") + .expect("known section id") .get_mut(offset_index) .unwrap() .deref_mut() = value; From eafc6ce14a9f3d3dbc585e34e465609385f07f69 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 10 Jul 2022 20:35:43 +0800 Subject: [PATCH 144/366] docs for comfort level File API (#331) Along with the realization that normalization is still a bit general as the raw_value API enforces it, too. --- git-config/src/file/access/comfort.rs | 6 +++--- git-config/src/file/access/mod.rs | 2 -- git-config/src/file/access/raw.rs | 23 +++++++---------------- git-config/src/file/from_env.rs | 1 + git-config/src/file/from_paths.rs | 1 + 5 files changed, 12 insertions(+), 21 deletions(-) diff --git a/git-config/src/file/access/comfort.rs b/git-config/src/file/access/comfort.rs index 029e088947e..e454508b0e2 100644 --- a/git-config/src/file/access/comfort.rs +++ b/git-config/src/file/access/comfort.rs @@ -6,14 +6,14 @@ use crate::{value, value::normalize, File}; /// Comfortable API for accessing values impl<'a> File<'a> { - /// Like [`value()`][File::value()], but returning an `Option` if the string wasn't found. + /// Like [`value()`][File::value()], but returning an `None` if the string wasn't found. /// /// As strings perform no conversions, this will never fail. pub fn string(&self, section_name: &str, subsection_name: Option<&str>, key: &str) -> Option> { self.raw_value(section_name, subsection_name, key).ok().map(normalize) } - /// Like [`value()`][File::value()], but returning an `Option` if the path wasn't found. + /// Like [`value()`][File::value()], but returning `None` if the path wasn't found. /// /// Note that this path is not vetted and should only point to resources which can't be used /// to pose a security risk. @@ -28,7 +28,7 @@ impl<'a> File<'a> { .map(|v| crate::Path::from(normalize(v))) } - /// Like [`value()`][File::value()], but returning an `Option` if the boolean wasn't found. + /// Like [`value()`][File::value()], but returning `None` if the boolean value wasn't found. pub fn boolean( &self, section_name: &str, diff --git a/git-config/src/file/access/mod.rs b/git-config/src/file/access/mod.rs index 1cd40f3c389..58f8c7aaa30 100644 --- a/git-config/src/file/access/mod.rs +++ b/git-config/src/file/access/mod.rs @@ -1,5 +1,3 @@ -// /// Lower-level API for accessing values - mod comfort; mod low_level; mod raw; diff --git a/git-config/src/file/access/raw.rs b/git-config/src/file/access/raw.rs index cf77dd3fd46..68172ad81fb 100644 --- a/git-config/src/file/access/raw.rs +++ b/git-config/src/file/access/raw.rs @@ -11,9 +11,8 @@ use crate::{ /// # Raw value API /// -/// These functions are the raw value API. Instead of returning Rust structures, -/// these functions return bytes which may or may not be owned. -impl<'event> File<'event> { +/// These functions are the raw value API, returning un-normalized byte strings. +impl<'a> File<'a> { /// Returns an uninterpreted value given a section, an optional subsection /// and key. /// @@ -25,20 +24,12 @@ impl<'event> File<'event> { subsection_name: Option<&str>, key: &str, ) -> Result, lookup::existing::Error> { - // Note: cannot wrap around the raw_multi_value method because we need - // to guarantee that the highest section id is used (so that we follow - // the "last one wins" resolution strategy by `git-config`). let key = section::Key(Cow::::Borrowed(key.into())); for section_id in self .section_ids_by_name_and_subname(section_name, subsection_name)? .rev() { - if let Some(v) = self - .sections - .get(§ion_id) - .expect("sections does not have section id from section ids") - .value(&key) - { + if let Some(v) = self.sections.get(§ion_id).expect("known section id").value(&key) { return Ok(v); } } @@ -56,7 +47,7 @@ impl<'event> File<'event> { section_name: &'lookup str, subsection_name: Option<&'lookup str>, key: &'lookup str, - ) -> Result, lookup::existing::Error> { + ) -> Result, lookup::existing::Error> { let mut section_ids = self .section_ids_by_name_and_subname(section_name, subsection_name)? .rev(); @@ -157,7 +148,7 @@ impl<'event> File<'event> { values.extend( self.sections .get(§ion_id) - .expect("sections does not have section id from section ids") + .expect("known section id") .values(§ion::Key(Cow::::Borrowed(key.into()))) .into_iter(), ); @@ -225,7 +216,7 @@ impl<'event> File<'event> { section_name: &'lookup str, subsection_name: Option<&'lookup str>, key: &'lookup str, - ) -> Result, lookup::existing::Error> { + ) -> Result, lookup::existing::Error> { let section_ids = self.section_ids_by_name_and_subname(section_name, subsection_name)?; let key = section::Key(Cow::::Borrowed(key.into())); @@ -409,7 +400,7 @@ impl<'event> File<'event> { section_name: &str, subsection_name: Option<&str>, key: &str, - new_values: impl Iterator>, + new_values: impl Iterator>, ) -> Result<(), lookup::existing::Error> { self.raw_values_mut(section_name, subsection_name, key) .map(|mut v| v.set_values(new_values)) diff --git a/git-config/src/file/from_env.rs b/git-config/src/file/from_env.rs index 587e4181d84..c0792296b0f 100644 --- a/git-config/src/file/from_env.rs +++ b/git-config/src/file/from_env.rs @@ -27,6 +27,7 @@ pub enum Error { FromPathsError(#[from] from_paths::Error), } +/// Instantiation from environment variables impl File<'static> { /// Constructs a `git-config` from the default cascading sequence of global configuration files, /// excluding any repository-local configuration. diff --git a/git-config/src/file/from_paths.rs b/git-config/src/file/from_paths.rs index eff7e38d216..905890eb0b5 100644 --- a/git-config/src/file/from_paths.rs +++ b/git-config/src/file/from_paths.rs @@ -54,6 +54,7 @@ impl Default for Options<'_> { } } +/// Instantiation from one or more paths impl File<'static> { /// Open a single configuration file by reading all data at `path` into `buf` and /// copying all contents from there, without resolving includes. From b979a3b318faada23a6cf073953b13f7828398af Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 10 Jul 2022 21:12:27 +0800 Subject: [PATCH 145/366] don't over-normalize in comfort layer - all values are normalized now (#331) And if they weren't, it was due to a bug previously. It's fair to enforce normalization when retrieving a value. --- git-config/src/file/access/comfort.rs | 10 ++-- git-config/src/file/access/raw.rs | 2 +- git-config/src/parse/nom/tests.rs | 61 +++++++++++++++++++++++ git-config/tests/file/access/read_only.rs | 16 ++---- 4 files changed, 71 insertions(+), 18 deletions(-) diff --git a/git-config/src/file/access/comfort.rs b/git-config/src/file/access/comfort.rs index e454508b0e2..cb130e65dfc 100644 --- a/git-config/src/file/access/comfort.rs +++ b/git-config/src/file/access/comfort.rs @@ -2,7 +2,7 @@ use std::{borrow::Cow, convert::TryFrom}; use bstr::BStr; -use crate::{value, value::normalize, File}; +use crate::{value, File}; /// Comfortable API for accessing values impl<'a> File<'a> { @@ -10,7 +10,7 @@ impl<'a> File<'a> { /// /// As strings perform no conversions, this will never fail. pub fn string(&self, section_name: &str, subsection_name: Option<&str>, key: &str) -> Option> { - self.raw_value(section_name, subsection_name, key).ok().map(normalize) + self.raw_value(section_name, subsection_name, key).ok() } /// Like [`value()`][File::value()], but returning `None` if the path wasn't found. @@ -25,7 +25,7 @@ impl<'a> File<'a> { pub fn path(&self, section_name: &str, subsection_name: Option<&str>, key: &str) -> Option> { self.raw_value(section_name, subsection_name, key) .ok() - .map(|v| crate::Path::from(normalize(v))) + .map(|v| crate::Path::from(v)) } /// Like [`value()`][File::value()], but returning `None` if the boolean value wasn't found. @@ -56,9 +56,7 @@ impl<'a> File<'a> { /// Similar to [`values(…)`][File::values()] but returning strings if at least one of them was found. pub fn strings(&self, section_name: &str, subsection_name: Option<&str>, key: &str) -> Option>> { - self.raw_values(section_name, subsection_name, key) - .ok() - .map(|values| values.into_iter().map(normalize).collect()) + self.raw_values(section_name, subsection_name, key).ok() } /// Similar to [`values(…)`][File::values()] but returning integers if at least one of them was found diff --git a/git-config/src/file/access/raw.rs b/git-config/src/file/access/raw.rs index 68172ad81fb..093ba3d2844 100644 --- a/git-config/src/file/access/raw.rs +++ b/git-config/src/file/access/raw.rs @@ -11,7 +11,7 @@ use crate::{ /// # Raw value API /// -/// These functions are the raw value API, returning un-normalized byte strings. +/// These functions are the raw value API, returning normalized byte strings. impl<'a> File<'a> { /// Returns an uninterpreted value given a section, an optional subsection /// and key. diff --git a/git-config/src/parse/nom/tests.rs b/git-config/src/parse/nom/tests.rs index 075679f9f90..59c282cf270 100644 --- a/git-config/src/parse/nom/tests.rs +++ b/git-config/src/parse/nom/tests.rs @@ -436,6 +436,7 @@ mod value_continuation { section, tests::util::{into_events, newline_event, value_done_event, value_not_done_event}, }; + use bstr::ByteSlice; pub fn value_impl<'a>(i: &'a [u8], events: &mut section::Events<'a>) -> nom::IResult<&'a [u8], ()> { super::value_impl(i, &mut |e| events.push(e)).map(|t| (t.0, ())) @@ -503,6 +504,66 @@ mod value_continuation { ]) ); } + + #[test] + fn quote_split_over_multiple_lines_without_surrounding_quotes_but_inner_quotes() { + let mut events = section::Events::default(); + assert_eq!( + value_impl( + br#"1\ +"2" a\ +\"3 b\"\ +4 ; comment "#, + &mut events + ) + .unwrap() + .0 + .as_bstr(), + b" ; comment ".as_bstr() + ); + assert_eq!( + events, + into_events(vec![ + value_not_done_event("1"), + newline_event(), + value_not_done_event("\"2\" a"), + newline_event(), + value_not_done_event("\\\"3 b\\\""), + newline_event(), + value_done_event("4") + ]) + ); + } + + #[test] + fn quote_split_over_multiple_lines_with_surrounding_quotes() { + let mut events = section::Events::default(); + assert_eq!( + value_impl( + br#""1\ +"2" a\ +\"3 b\"\ +4 " ; comment "#, + &mut events + ) + .unwrap() + .0 + .as_bstr(), + b" ; comment ".as_bstr() + ); + assert_eq!( + events, + into_events(vec![ + value_not_done_event("\"1"), + newline_event(), + value_not_done_event("\"2\" a"), + newline_event(), + value_not_done_event("\\\"3 b\\\""), + newline_event(), + value_done_event("4 \"") + ]) + ); + } } mod value_no_continuation { diff --git a/git-config/tests/file/access/read_only.rs b/git-config/tests/file/access/read_only.rs index 6bf9193c85f..4979f54bfda 100644 --- a/git-config/tests/file/access/read_only.rs +++ b/git-config/tests/file/access/read_only.rs @@ -228,12 +228,8 @@ fn multi_line_value_plain() { let config = File::try_from(config).unwrap(); let expected = r#"!git status && git add -A && git commit -m "$1" && git push -f && git log -1 && :"#; - assert_eq!( - config.raw_value("alias", None, "save").unwrap().as_ref(), - expected, - "only the un-normalized original value currently matches git's result" - ); - assert_ne!(config.string("alias", None, "save").unwrap().as_ref(), expected); + assert_eq!(config.raw_value("alias", None, "save").unwrap().as_ref(), expected); + assert_eq!(config.string("alias", None, "save").unwrap().as_ref(), expected); } #[test] @@ -252,12 +248,11 @@ fn complex_quoted_values() { assert_eq!( config.string("core", None, "escape-sequence").unwrap().as_ref(), "hi\nho\ttheri", - "normalization is what resolves these values" + "normalization is what resolves these valuesi, even backspaces" ); } #[test] -#[ignore] fn multi_line_value_outer_quotes_unescaped_inner_quotes() { let config = r#" [alias] @@ -267,7 +262,7 @@ fn multi_line_value_outer_quotes_unescaped_inner_quotes() { git commit -m "$1"; \ git push -f; \ git log -1; \ - }; \ + }; \ f; \ unset f" "#; @@ -277,7 +272,6 @@ fn multi_line_value_outer_quotes_unescaped_inner_quotes() { } #[test] -#[ignore] fn multi_line_value_outer_quotes_escaped_inner_quotes() { let config = r#" [alias] @@ -287,7 +281,7 @@ fn multi_line_value_outer_quotes_escaped_inner_quotes() { git commit -m \"$1\"; \ git push -f; \ git log -1; \ - }; \ + }; \ f; \ unset f" "#; From 6acf4a43fd63c1c5e24b2e21702dc79827e3d11e Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 10 Jul 2022 21:42:52 +0800 Subject: [PATCH 146/366] review `file::raw` module (#331) --- git-config/src/file/access/raw.rs | 73 ++++++++++++++++--------------- git-config/src/file/value.rs | 2 +- 2 files changed, 39 insertions(+), 36 deletions(-) diff --git a/git-config/src/file/access/raw.rs b/git-config/src/file/access/raw.rs index 093ba3d2844..cbe73df4d97 100644 --- a/git-config/src/file/access/raw.rs +++ b/git-config/src/file/access/raw.rs @@ -25,10 +25,8 @@ impl<'a> File<'a> { key: &str, ) -> Result, lookup::existing::Error> { let key = section::Key(Cow::::Borrowed(key.into())); - for section_id in self - .section_ids_by_name_and_subname(section_name, subsection_name)? - .rev() - { + let section_ids = self.section_ids_by_name_and_subname(section_name, subsection_name)?; + for section_id in section_ids.rev() { if let Some(v) = self.sections.get(§ion_id).expect("known section id").value(&key) { return Ok(v); } @@ -54,23 +52,23 @@ impl<'a> File<'a> { let key = section::Key(Cow::::Borrowed(key.into())); while let Some(section_id) = section_ids.next() { - let mut size = Size(0); - let mut index = Index(0); + let mut size = 0; + let mut index = 0; let mut found_key = false; - // todo: iter backwards for (i, event) in self .sections .get(§ion_id) .expect("sections does not have section id from section ids") .as_ref() + // todo: iter backwards .iter() .enumerate() { match event { Event::SectionKey(event_key) if *event_key == key => { found_key = true; - size = Size(1); - index = Index(i); + size = 1; + index = i; } Event::Newline(_) | Event::Whitespace(_) | Event::ValueNotDone(_) if found_key => { size += 1; @@ -83,20 +81,16 @@ impl<'a> File<'a> { } } - if size.0 == 0 { + if size == 0 { continue; } drop(section_ids); return Ok(MutableValue { - section: MutableSection::new( - self.sections - .get_mut(§ion_id) - .expect("sections does not have section id from section ids"), - ), + section: MutableSection::new(self.sections.get_mut(§ion_id).expect("known section-id")), key, - index, - size, + index: Index(index), + size: Size(size), }); } @@ -124,13 +118,14 @@ impl<'a> File<'a> { /// # use git_config::File; /// # use std::borrow::Cow; /// # use std::convert::TryFrom; + /// # use bstr::BStr; /// # let git_config = git_config::File::try_from("[core]a=b\n[core]\na=c\na=d").unwrap(); /// assert_eq!( /// git_config.raw_values("core", None, "a").unwrap(), /// vec![ - /// Cow::<[u8]>::Borrowed(b"b"), - /// Cow::<[u8]>::Borrowed(b"c"), - /// Cow::<[u8]>::Borrowed(b"d"), + /// Cow::::Borrowed("b".into()), + /// Cow::::Borrowed("c".into()), + /// Cow::::Borrowed("d".into()), /// ], /// ); /// ``` @@ -143,14 +138,14 @@ impl<'a> File<'a> { subsection_name: Option<&str>, key: &str, ) -> Result>, lookup::existing::Error> { - let mut values = vec![]; - for section_id in self.section_ids_by_name_and_subname(section_name, subsection_name)? { + let mut values = Vec::new(); + let section_ids = self.section_ids_by_name_and_subname(section_name, subsection_name)?; + for section_id in section_ids { values.extend( self.sections .get(§ion_id) .expect("known section id") - .values(§ion::Key(Cow::::Borrowed(key.into()))) - .into_iter(), + .values(§ion::Key(Cow::::Borrowed(key.into()))), ); } @@ -221,29 +216,29 @@ impl<'a> File<'a> { let key = section::Key(Cow::::Borrowed(key.into())); let mut offsets = HashMap::new(); - let mut entries = vec![]; + let mut entries = Vec::new(); for section_id in section_ids.rev() { let mut last_boundary = 0; - let mut found_key = false; - let mut offset_list = vec![]; + let mut expect_value = false; + let mut offset_list = Vec::new(); let mut offset_index = 0; for (i, event) in self .sections .get(§ion_id) - .expect("sections does not have section id from section ids") + .expect("known section-id") .as_ref() .iter() .enumerate() { match event { Event::SectionKey(event_key) if *event_key == key => { - found_key = true; + expect_value = true; offset_list.push(i - last_boundary); offset_index += 1; last_boundary = i; } - Event::Value(_) | Event::ValueDone(_) if found_key => { - found_key = false; + Event::Value(_) | Event::ValueDone(_) if expect_value => { + expect_value = false; entries.push(EntryData { section_id, offset_index, @@ -296,6 +291,14 @@ impl<'a> File<'a> { /// # let mut git_config = git_config::File::try_from("[core]a=b\n[core]\na=c\na=d").unwrap(); /// git_config.set_raw_value("core", None, "a", "e".into())?; /// assert_eq!(git_config.raw_value("core", None, "a")?, Cow::::Borrowed("e".into())); + /// assert_eq!( + /// git_config.raw_values("core", None, "a")?, + /// vec![ + /// Cow::::Borrowed("b".into()), + /// Cow::::Borrowed("c".into()), + /// Cow::::Borrowed("e".into()) + /// ], + /// ); /// # Ok::<(), Box>(()) /// ``` pub fn set_raw_value( @@ -318,7 +321,7 @@ impl<'a> File<'a> { /// /// **Note**: Mutation order is _not_ guaranteed and is non-deterministic. /// If you need finer control over which values of the multivar are set, - /// consider using [`raw_multi_value_mut`], which will let you iterate + /// consider using [`raw_values_mut()`][Self::raw_values_mut()], which will let you iterate /// and check over the values instead. This is best used as a convenience /// function for setting multivars whose values should be treated as an /// unordered set. @@ -389,18 +392,18 @@ impl<'a> File<'a> { /// Cow::::Borrowed("z".into()), /// Cow::::Borrowed("discarded".into()), /// ]; - /// git_config.set_raw_multi_value("core", None, "a", new_values.into_iter())?; + /// git_config.set_raw_multi_value("core", None, "a", new_values)?; /// assert!(!git_config.raw_values("core", None, "a")?.contains(&Cow::::Borrowed("discarded".into()))); /// # Ok::<(), git_config::lookup::existing::Error>(()) /// ``` /// - /// [`raw_multi_value_mut`]: Self::raw_values_mut + /// [`raw_values_mut`]: pub fn set_raw_multi_value( &mut self, section_name: &str, subsection_name: Option<&str>, key: &str, - new_values: impl Iterator>, + new_values: impl IntoIterator>, ) -> Result<(), lookup::existing::Error> { self.raw_values_mut(section_name, subsection_name, key) .map(|mut v| v.set_values(new_values)) diff --git a/git-config/src/file/value.rs b/git-config/src/file/value.rs index d76af222859..4dada813b17 100644 --- a/git-config/src/file/value.rs +++ b/git-config/src/file/value.rs @@ -173,7 +173,7 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { /// remaining values are ignored. /// /// [`zip`]: std::iter::Iterator::zip - pub fn set_values(&mut self, input: impl Iterator>) { + pub fn set_values(&mut self, input: impl IntoIterator>) { for ( EntryData { section_id, From 44dfec07480cc2ac6fd01674b748cc03af51fed1 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 10 Jul 2022 22:57:15 +0800 Subject: [PATCH 147/366] fix: stable sort order for `File::sections_by_name_with_header()` (#331) --- git-config/src/file/access/comfort.rs | 2 +- .../src/file/access/low_level/read_only.rs | 44 +++++++++---------- git-config/src/file/access/raw.rs | 10 ++--- git-config/src/file/utils.rs | 20 +++++---- 4 files changed, 38 insertions(+), 38 deletions(-) diff --git a/git-config/src/file/access/comfort.rs b/git-config/src/file/access/comfort.rs index cb130e65dfc..549628e675a 100644 --- a/git-config/src/file/access/comfort.rs +++ b/git-config/src/file/access/comfort.rs @@ -5,7 +5,7 @@ use bstr::BStr; use crate::{value, File}; /// Comfortable API for accessing values -impl<'a> File<'a> { +impl<'event> File<'event> { /// Like [`value()`][File::value()], but returning an `None` if the string wasn't found. /// /// As strings perform no conversions, this will never fail. diff --git a/git-config/src/file/access/low_level/read_only.rs b/git-config/src/file/access/low_level/read_only.rs index 2699d6fd8e9..fe34d7ec040 100644 --- a/git-config/src/file/access/low_level/read_only.rs +++ b/git-config/src/file/access/low_level/read_only.rs @@ -4,14 +4,16 @@ use bstr::BStr; use crate::{file::SectionBody, lookup, parse::section, File}; -/// Read-only low-level access methods. +/// Read-only low-level access methods, as it requires generics for converting into +/// custom values defined in this crate like [`Integer`][crate::Integer] and +/// [`Color`][crate::Color]. impl<'event> File<'event> { /// Returns an interpreted value given a section, an optional subsection and /// key. /// - /// It's recommended to use one of the values in the [`value`] module as - /// the conversion is already implemented, but this function is flexible and - /// will accept any type that implements [`TryFrom<&[u8]>`][`TryFrom`]. + /// It's recommended to use one of the value types provide dby this crate + /// as they implement the conversion, but this function is flexible and + /// will accept any type that implements [`TryFrom<&BStr>`][std::convert::TryFrom]. /// /// Consider [`Self::values`] if you want to get all values of a multivar instead. /// @@ -36,9 +38,6 @@ impl<'event> File<'event> { /// let c_value: Boolean = git_config.value("core", None, "c")?; /// # Ok::<(), Box>(()) /// ``` - /// - /// [`value`]: crate::value - /// [`TryFrom`]: std::convert::TryFrom pub fn value<'a, T: TryFrom>>( &'a self, section_name: &str, @@ -48,7 +47,7 @@ impl<'event> File<'event> { T::try_from(self.raw_value(section_name, subsection_name, key)?).map_err(lookup::Error::FailedConversion) } - /// Like [`value()`][File::value()], but returning an `Option` if the value wasn't found. + /// Like [`value()`][File::value()], but returning an `None` if the value wasn't found at `section[.subsection].key` pub fn try_value<'a, T: TryFrom>>( &'a self, section_name: &str, @@ -61,9 +60,9 @@ impl<'event> File<'event> { /// Returns all interpreted values given a section, an optional subsection /// and key. /// - /// It's recommended to use one of the values in the [`value`] module as - /// the conversion is already implemented, but this function is flexible and - /// will accept any type that implements [`TryFrom<&[u8]>`][`TryFrom`]. + /// It's recommended to use one of the value types provide dby this crate + /// as they implement the conversion, but this function is flexible and + /// will accept any type that implements [`TryFrom<&BStr>`][std::convert::TryFrom]. /// /// Consider [`Self::value`] if you want to get a single value /// (following last-one-wins resolution) instead. @@ -81,7 +80,7 @@ impl<'event> File<'event> { /// let config = r#" /// [core] /// a = true - /// c = g + /// c /// [core] /// a /// a = false @@ -98,8 +97,8 @@ impl<'event> File<'event> { /// ] /// ); /// // ... or explicitly declare the type to avoid the turbofish - /// let c_value = git_config.strings("core", None, "c").unwrap(); - /// assert_eq!(c_value, vec![Cow::Borrowed("g".as_bytes().as_bstr())]); + /// let c_value: Vec = git_config.values("core", None, "c").unwrap(); + /// assert_eq!(c_value, vec![Boolean(true)]); /// # Ok::<(), Box>(()) /// ``` /// @@ -118,7 +117,7 @@ impl<'event> File<'event> { .map_err(lookup::Error::FailedConversion) } - /// Returns an immutable section reference. + /// Returns the last found immutable section with a given name and optional subsection name. pub fn section( &mut self, section_name: &str, @@ -165,8 +164,9 @@ impl<'event> File<'event> { /// [core "apple"] /// e = f /// "#; - /// let git_config = git_config::File::try_from(config).unwrap(); + /// let git_config = git_config::File::try_from(config)?; /// assert_eq!(git_config.sections_by_name("core").map_or(0, |s|s.count()), 3); + /// # Ok::<(), Box>(()) /// ``` #[must_use] pub fn sections_by_name<'a>( @@ -184,7 +184,7 @@ impl<'event> File<'event> { /// Get all sections that match the `section_name`, returning all matching section header along with their body. /// - /// An empty `Vec` is returned if there is no section with `section_name`. + /// `None` is returned if there is no section with `section_name`. /// /// # Example /// @@ -210,7 +210,7 @@ impl<'event> File<'event> { /// [url "ssh://git@bitbucket.org"] /// insteadOf = https://bitbucket.org/ /// "#; - /// let config = git_config::File::try_from(input).unwrap(); + /// let config = git_config::File::try_from(input)?; /// let url = config.sections_by_name_with_header("url"); /// assert_eq!(url.map_or(0, |s| s.count()), 2); /// @@ -218,8 +218,7 @@ impl<'event> File<'event> { /// let url = header.subsection_name.as_ref(); /// let instead_of = body.value(§ion::Key::from("insteadOf")); /// - /// // todo(unstable-order): the order is not always the same, so `i` cannot be used here - /// if instead_of.as_ref().unwrap().as_ref() == "https://github.com/" { + /// if i == 0 { /// assert_eq!(instead_of.unwrap().as_ref(), "https://github.com/"); /// assert_eq!(url.unwrap().as_ref(), "ssh://git@github.com/"); /// } else { @@ -227,6 +226,7 @@ impl<'event> File<'event> { /// assert_eq!(url.unwrap().as_ref(), "ssh://git@bitbucket.org"); /// } /// } + /// # Ok::<(), Box>(()) /// ``` pub fn sections_by_name_with_header<'a>( &'a self, @@ -252,9 +252,7 @@ impl<'event> File<'event> { /// This ignores any comments. #[must_use] pub fn num_values(&self) -> usize { - self.sections - .values() - .fold(0, |acc, section| acc + section.num_values()) + self.sections.values().map(|section| section.num_values()).sum() } /// Returns if there are no entries in the config. This will return true diff --git a/git-config/src/file/access/raw.rs b/git-config/src/file/access/raw.rs index cbe73df4d97..c431fb0303d 100644 --- a/git-config/src/file/access/raw.rs +++ b/git-config/src/file/access/raw.rs @@ -12,7 +12,7 @@ use crate::{ /// # Raw value API /// /// These functions are the raw value API, returning normalized byte strings. -impl<'a> File<'a> { +impl<'event> File<'event> { /// Returns an uninterpreted value given a section, an optional subsection /// and key. /// @@ -45,7 +45,7 @@ impl<'a> File<'a> { section_name: &'lookup str, subsection_name: Option<&'lookup str>, key: &'lookup str, - ) -> Result, lookup::existing::Error> { + ) -> Result, lookup::existing::Error> { let mut section_ids = self .section_ids_by_name_and_subname(section_name, subsection_name)? .rev(); @@ -211,7 +211,7 @@ impl<'a> File<'a> { section_name: &'lookup str, subsection_name: Option<&'lookup str>, key: &'lookup str, - ) -> Result, lookup::existing::Error> { + ) -> Result, lookup::existing::Error> { let section_ids = self.section_ids_by_name_and_subname(section_name, subsection_name)?; let key = section::Key(Cow::::Borrowed(key.into())); @@ -396,14 +396,12 @@ impl<'a> File<'a> { /// assert!(!git_config.raw_values("core", None, "a")?.contains(&Cow::::Borrowed("discarded".into()))); /// # Ok::<(), git_config::lookup::existing::Error>(()) /// ``` - /// - /// [`raw_values_mut`]: pub fn set_raw_multi_value( &mut self, section_name: &str, subsection_name: Option<&str>, key: &str, - new_values: impl IntoIterator>, + new_values: impl IntoIterator>, ) -> Result<(), lookup::existing::Error> { self.raw_values_mut(section_name, subsection_name, key) .map(|mut v| v.set_values(new_values)) diff --git a/git-config/src/file/utils.rs b/git-config/src/file/utils.rs index 6edfcbd1add..c75f8171e13 100644 --- a/git-config/src/file/utils.rs +++ b/git-config/src/file/utils.rs @@ -101,15 +101,19 @@ impl<'event> File<'event> { section_name: impl Into>, ) -> Result + '_, lookup::existing::Error> { let section_name = section_name.into(); - self.section_lookup_tree - .get(§ion_name) - .map(|lookup| { - lookup.iter().flat_map(|node| match node { + match self.section_lookup_tree.get(§ion_name) { + Some(lookup) => Ok(lookup.iter().flat_map({ + let section_order = &self.section_order; + move |node| match node { SectionBodyIds::Terminal(v) => Box::new(v.iter().copied()) as Box>, - SectionBodyIds::NonTerminal(v) => Box::new(v.values().flatten().copied()), - }) - }) - .ok_or(lookup::existing::Error::SectionMissing) + SectionBodyIds::NonTerminal(v) => Box::new({ + let v: Vec<_> = v.values().flatten().copied().collect(); + section_order.iter().filter(move |a| v.contains(a)).copied() + }), + } + })), + None => Err(lookup::existing::Error::SectionMissing), + } } // TODO: add note indicating that probably a lot if not all information about the original files is currently lost, From 2d5703e5909946e4327e0372097273facaeca759 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 10 Jul 2022 23:14:53 +0800 Subject: [PATCH 148/366] review docs of `file::mutating` (#331) --- .../src/file/access/low_level/mutating.rs | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/git-config/src/file/access/low_level/mutating.rs b/git-config/src/file/access/low_level/mutating.rs index 12425ed1efa..7d084e59c13 100644 --- a/git-config/src/file/access/low_level/mutating.rs +++ b/git-config/src/file/access/low_level/mutating.rs @@ -11,7 +11,7 @@ use crate::{ /// Mutating low-level access methods. impl<'event> File<'event> { - /// Returns an mutable section reference. + /// Returns an mutable section with a given name and optional subsection. pub fn section_mut<'a>( &'a mut self, section_name: &str, @@ -29,9 +29,9 @@ impl<'event> File<'event> { )) } - /// Adds a new section to config. If a subsection name was provided, then - /// the generated header will use the modern subsection syntax. Returns a - /// reference to the new section for immediate editing. + /// Adds a new section. If a subsection name was provided, then + /// the generated header will use the modern subsection syntax. + /// Returns a reference to the new section for immediate editing. /// /// # Examples /// @@ -82,10 +82,11 @@ impl<'event> File<'event> { /// let mut git_config = git_config::File::try_from( /// r#"[hello "world"] /// some-value = 4 - /// "#).unwrap(); + /// "#)?; /// /// let events = git_config.remove_section("hello", Some("world".into())); /// assert_eq!(git_config.to_string(), ""); + /// # Ok::<(), Box>(()) /// ``` /// /// Precedence example for removing sections with the same name: @@ -98,10 +99,11 @@ impl<'event> File<'event> { /// some-value = 4 /// [hello "world"] /// some-value = 5 - /// "#).unwrap(); + /// "#)?; /// /// let events = git_config.remove_section("hello", Some("world".into())); /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n some-value = 4\n"); + /// # Ok::<(), Box>(()) /// ``` pub fn remove_section<'a>( &mut self, @@ -117,13 +119,13 @@ impl<'event> File<'event> { self.section_order .iter() .position(|v| *v == id) - .expect("Section order does not contain section that we were trying to remove"), + .expect("known section id"), ); self.sections.remove(&id) } /// Adds the provided section to the config, returning a mutable reference - /// to it. + /// to it for immediate editing. pub fn push_section( &mut self, section_name: impl Into>, @@ -154,10 +156,7 @@ impl<'event> File<'event> { .rev() .next() .expect("list of sections were empty, which violates invariant"); - let header = self - .section_headers - .get_mut(&id) - .expect("sections does not have section id from section ids"); + let header = self.section_headers.get_mut(&id).expect("known section-id"); header.name = new_section_name.into(); header.subsection_name = new_subsection_name.into().map(into_cow_bstr); From b246f0ade5aa42413cc387470b35df357b1136bc Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 10 Jul 2022 23:15:44 +0800 Subject: [PATCH 149/366] thanks clippy --- git-config/src/file/access/comfort.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-config/src/file/access/comfort.rs b/git-config/src/file/access/comfort.rs index 549628e675a..f4dc5ba34a8 100644 --- a/git-config/src/file/access/comfort.rs +++ b/git-config/src/file/access/comfort.rs @@ -25,7 +25,7 @@ impl<'event> File<'event> { pub fn path(&self, section_name: &str, subsection_name: Option<&str>, key: &str) -> Option> { self.raw_value(section_name, subsection_name, key) .ok() - .map(|v| crate::Path::from(v)) + .map(crate::Path::from) } /// Like [`value()`][File::value()], but returning `None` if the boolean value wasn't found. From b92bd580de45cb58cd2b3c4af430273e96139c79 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 11 Jul 2022 12:03:45 +0800 Subject: [PATCH 150/366] more escape characters for normalization (#331) --- git-config/src/value/normalize.rs | 61 ++++++++++------------- git-config/tests/file/access/read_only.rs | 12 ++--- 2 files changed, 31 insertions(+), 42 deletions(-) diff --git a/git-config/src/value/normalize.rs b/git-config/src/value/normalize.rs index 829093bca10..3850e4b2a0d 100644 --- a/git-config/src/value/normalize.rs +++ b/git-config/src/value/normalize.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use bstr::{BStr, BString}; +use bstr::{BStr, BString, ByteSlice}; /// Removes quotes, if any, from the provided inputs. This assumes the input /// contains a even number of unescaped quotes, and will unescape escaped @@ -65,46 +65,35 @@ pub fn normalize(input: Cow<'_, BStr>) -> Cow<'_, BStr> { } } - let mut owned = BString::default(); + if input.find_byteset(b"\\\"").is_none() { + return input; + } - let mut first_index = 0; - let mut last_index = 0; - let mut was_escaped = false; - for (i, c) in input.iter().enumerate() { - if was_escaped { - was_escaped = false; - if *c == b'"' { - if first_index == 0 { - owned.extend(&*input[last_index..i - 1]); - last_index = i; - } else { - owned.extend(&*input[first_index..i - 1]); - first_index = i; - } - } - continue; - } + let mut out: BString = Vec::with_capacity(input.len()).into(); - if *c == b'\\' { - was_escaped = true; - } else if *c == b'"' { - if first_index == 0 { - owned.extend(&*input[last_index..i]); - first_index = i + 1; - } else { - owned.extend(&*input[first_index..i]); - first_index = 0; - last_index = i + 1; + let mut prev_was_backslash = false; + for c in input.iter().copied() { + if prev_was_backslash { + prev_was_backslash = false; + match c { + b'n' => out.push(b'\n'), + b't' => out.push(b'\t'), + b'b' => { + out.pop(); + } + _ => out.push(c), + }; + } else { + match c { + b'\\' => { + prev_was_backslash = true; + } + b'"' => {} + _ => out.push(c), } } } - - if last_index == 0 { - input - } else { - owned.extend(&*input[last_index..]); - Cow::Owned(owned) - } + Cow::Owned(out) } /// `&[u8]` variant of [`normalize`]. diff --git a/git-config/tests/file/access/read_only.rs b/git-config/tests/file/access/read_only.rs index 4979f54bfda..6d32697f32a 100644 --- a/git-config/tests/file/access/read_only.rs +++ b/git-config/tests/file/access/read_only.rs @@ -233,22 +233,22 @@ fn multi_line_value_plain() { } #[test] -#[ignore] fn complex_quoted_values() { let config = r#" [core] - escape-sequence = "hi\nho\tthere\bi" + escape-sequence = "hi\nho\n\tthere\bi\\\" \"" "#; let config = File::try_from(config).unwrap(); - + let expected = "hi\nho\n\ttheri\\\" \""; assert_eq!( config.raw_value("core", None, "escape-sequence").unwrap().as_ref(), - "hi\\nho\\tthere\\b" + expected, + "raw_value is normalized" ); assert_eq!( config.string("core", None, "escape-sequence").unwrap().as_ref(), - "hi\nho\ttheri", - "normalization is what resolves these valuesi, even backspaces" + expected, + "and so is the comfort API" ); } From cf3bf4a3bde6cdf20c63ffee1a5ae55a1f4e1742 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 11 Jul 2022 14:48:50 +0800 Subject: [PATCH 151/366] refactor normalization and more tests (#331) --- git-config/src/value/normalize.rs | 31 ++++++++++++----------------- git-config/tests/value/normalize.rs | 20 +++++++++++++++++++ 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/git-config/src/value/normalize.rs b/git-config/src/value/normalize.rs index 3850e4b2a0d..ac0405a9a73 100644 --- a/git-config/src/value/normalize.rs +++ b/git-config/src/value/normalize.rs @@ -70,27 +70,22 @@ pub fn normalize(input: Cow<'_, BStr>) -> Cow<'_, BStr> { } let mut out: BString = Vec::with_capacity(input.len()).into(); - - let mut prev_was_backslash = false; - for c in input.iter().copied() { - if prev_was_backslash { - prev_was_backslash = false; - match c { - b'n' => out.push(b'\n'), - b't' => out.push(b'\t'), - b'b' => { + let mut bytes = input.iter().copied(); + while let Some(c) = bytes.next() { + match c { + b'\\' => match bytes.next() { + Some(b'n') => out.push(b'\n'), + Some(b't') => out.push(b'\t'), + Some(b'b') => { out.pop(); } - _ => out.push(c), - }; - } else { - match c { - b'\\' => { - prev_was_backslash = true; + Some(c) => { + out.push(c); } - b'"' => {} - _ => out.push(c), - } + None => break, + }, + b'"' => {} + _ => out.push(c), } } Cow::Owned(out) diff --git a/git-config/tests/value/normalize.rs b/git-config/tests/value/normalize.rs index f547879213e..187cae0b794 100644 --- a/git-config/tests/value/normalize.rs +++ b/git-config/tests/value/normalize.rs @@ -66,3 +66,23 @@ fn inner_quotes_are_removed() { assert_eq!(normalize_bstr(r#"true"""#), cow_str("true")); assert_eq!(normalize_bstr(r#"fa"lse""#), cow_str("false")); } + +#[test] +fn newline_tab_backspace_are_escapeable() { + assert_eq!(normalize_bstr(r#"\n\ta\b"#), cow_str("\n\t")); +} + +#[test] +fn other_escapes_are_ignored_entirely() { + assert_eq!( + normalize_bstr(r#"\x"#), + cow_str("x"), + "however, these would cause failure on parsing level so we ignore it similar to subsections" + ); + assert_eq!(normalize_bstr(r#""\x""#), cow_str("x"), "same if within quotes"); + assert_eq!( + normalize_bstr(r#""\"#), + cow_str(""), + "freestanding escapes are ignored as well" + ); +} From f911707b455ba6f3800b85f667f91e4d56027b91 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 11 Jul 2022 14:51:59 +0800 Subject: [PATCH 152/366] fix: value normalization (via `value::normalize()` handles escape sequences. (#331) The latter ones are `\n`, `\t` and `\b` which are the only supported ones in values of git-config files. --- git-config/src/value/normalize.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/git-config/src/value/normalize.rs b/git-config/src/value/normalize.rs index ac0405a9a73..3262a4a5457 100644 --- a/git-config/src/value/normalize.rs +++ b/git-config/src/value/normalize.rs @@ -2,9 +2,15 @@ use std::borrow::Cow; use bstr::{BStr, BString, ByteSlice}; -/// Removes quotes, if any, from the provided inputs. This assumes the input -/// contains a even number of unescaped quotes, and will unescape escaped -/// quotes. The return values should be safe for value interpretation. +/// Removes quotes, if any, from the provided inputs, and transforms +/// the 3 escape sequences `\n`, `\t` and `\b` into newline and tab +/// respectively, while `\b` will remove the previous character. +/// +/// It assumes the input contains a even number of unescaped quotes, +/// and will unescape escaped quotes and everything else (even though the latter +/// would have been rejected in the parsing stage). +/// +/// The return values should be safe for value interpretation. /// /// This has optimizations for fully-quoted values, where the returned value /// will be a borrowed reference if the only mutation necessary is to unquote From 3d7fc188914337074775863acc1d6c15f47e913c Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 11 Jul 2022 17:19:38 +0800 Subject: [PATCH 153/366] fix tests on windows (#331) They need double-backslashes due to the escaping logic in git-config files. They aren't particularly friendly to windows, which seems like a fair trade-off. --- git-config/tests/file/from_env/mod.rs | 7 ++++--- .../file/from_paths/includes/conditional/gitdir/mod.rs | 8 ++------ git-config/tests/file/from_paths/mod.rs | 2 +- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/git-config/tests/file/from_env/mod.rs b/git-config/tests/file/from_env/mod.rs index 8b9261855a4..c73a1c57078 100644 --- a/git-config/tests/file/from_env/mod.rs +++ b/git-config/tests/file/from_env/mod.rs @@ -1,5 +1,6 @@ use std::{borrow::Cow, env, fs}; +use crate::file::from_paths::escape_backslashes; use git_config::{ file::{from_env, from_paths, from_paths::Options}, File, @@ -131,11 +132,11 @@ fn follow_include_paths() { .set("GIT_CONFIG_KEY_0", "core.key") .set("GIT_CONFIG_VALUE_0", "value") .set("GIT_CONFIG_KEY_1", "include.path") - .set("GIT_CONFIG_VALUE_1", a_path.to_str().unwrap()) + .set("GIT_CONFIG_VALUE_1", escape_backslashes(a_path)) .set("GIT_CONFIG_KEY_2", "other.path") - .set("GIT_CONFIG_VALUE_2", b_path.to_str().unwrap()) + .set("GIT_CONFIG_VALUE_2", escape_backslashes(&b_path)) .set("GIT_CONFIG_KEY_3", "include.origin.path") - .set("GIT_CONFIG_VALUE_3", b_path.to_str().unwrap()); + .set("GIT_CONFIG_VALUE_3", escape_backslashes(b_path)); let config = File::from_env(Options::default()).unwrap().unwrap(); diff --git a/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs b/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs index 78fef319110..cef4f2cb81e 100644 --- a/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs +++ b/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs @@ -25,7 +25,7 @@ fn relative_path_without_trailing_slash_and_dot_git_suffix_matches() -> crate::R #[test] fn tilde_slash_expands_the_current_user_home() -> crate::Result { - let env = GitEnv::repo_name(format!("subdir{}worktree", std::path::MAIN_SEPARATOR))?; + let env = GitEnv::repo_name(std::path::Path::new("subdir").join("worktree"))?; assert_section_value(Condition::new("gitdir:~/subdir/worktree/"), env) } @@ -111,11 +111,7 @@ fn dot_slash_from_environment_causes_error() -> crate::Result { ); } - let absolute_path = format!( - "{}{}include.config", - env.home_dir().display(), - std::path::MAIN_SEPARATOR - ); + let absolute_path = escape_backslashes(env.home_dir().join("include.config")); { let _environment = crate::file::from_env::Env::new() .set("GIT_CONFIG_COUNT", "1") diff --git a/git-config/tests/file/from_paths/mod.rs b/git-config/tests/file/from_paths/mod.rs index 7db59052962..b2b8d24fb78 100644 --- a/git-config/tests/file/from_paths/mod.rs +++ b/git-config/tests/file/from_paths/mod.rs @@ -6,7 +6,7 @@ use tempfile::tempdir; use crate::file::cow_str; /// Escapes backslash when writing a path as string so that it is a valid windows path -fn escape_backslashes(path: impl AsRef) -> String { +pub(crate) fn escape_backslashes(path: impl AsRef) -> String { path.as_ref().to_str().unwrap().replace('\\', "\\\\") } From 0700b09d6828849fa2470df89af1f75a67bfb27d Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 11 Jul 2022 17:22:32 +0800 Subject: [PATCH 154/366] make fmt --- git-config/src/file/section.rs | 6 ++---- git-config/src/parse/nom/tests.rs | 3 ++- git-config/tests/file/from_env/mod.rs | 3 ++- git-config/tests/values/path.rs | 3 +-- git-odb/src/alternate/mod.rs | 3 ++- git-path/src/realpath.rs | 2 +- git-path/tests/convert/absolutize.rs | 4 ++-- git-path/tests/realpath/mod.rs | 3 ++- git-repository/src/lib.rs | 3 +-- .../tests/spec/parse/navigate/tilde_symbol.rs | 3 +-- .../src/repository/revision/explain.rs | 18 ++++++++++++------ src/porcelain/options.rs | 2 +- 12 files changed, 29 insertions(+), 24 deletions(-) diff --git a/git-config/src/file/section.rs b/git-config/src/file/section.rs index bba7f3682f7..644c1d39d0e 100644 --- a/git-config/src/file/section.rs +++ b/git-config/src/file/section.rs @@ -6,13 +6,11 @@ use std::{ use bstr::{BStr, BString, ByteVec}; -use crate::file::Size; -use crate::value::normalize_bstr; use crate::{ - file::Index, + file::{Index, Size}, lookup, parse, parse::{section::Key, Event}, - value::{normalize, normalize_bstring}, + value::{normalize, normalize_bstr, normalize_bstring}, }; /// A opaque type that represents a mutable reference to a section. diff --git a/git-config/src/parse/nom/tests.rs b/git-config/src/parse/nom/tests.rs index 59c282cf270..d17d3628f92 100644 --- a/git-config/src/parse/nom/tests.rs +++ b/git-config/src/parse/nom/tests.rs @@ -432,11 +432,12 @@ mod section { } mod value_continuation { + use bstr::ByteSlice; + use crate::parse::{ section, tests::util::{into_events, newline_event, value_done_event, value_not_done_event}, }; - use bstr::ByteSlice; pub fn value_impl<'a>(i: &'a [u8], events: &mut section::Events<'a>) -> nom::IResult<&'a [u8], ()> { super::value_impl(i, &mut |e| events.push(e)).map(|t| (t.0, ())) diff --git a/git-config/tests/file/from_env/mod.rs b/git-config/tests/file/from_env/mod.rs index c73a1c57078..5930adc96b1 100644 --- a/git-config/tests/file/from_env/mod.rs +++ b/git-config/tests/file/from_env/mod.rs @@ -1,6 +1,5 @@ use std::{borrow::Cow, env, fs}; -use crate::file::from_paths::escape_backslashes; use git_config::{ file::{from_env, from_paths, from_paths::Options}, File, @@ -8,6 +7,8 @@ use git_config::{ use serial_test::serial; use tempfile::tempdir; +use crate::file::from_paths::escape_backslashes; + pub struct Env<'a> { altered_vars: Vec<&'a str>, } diff --git a/git-config/tests/values/path.rs b/git-config/tests/values/path.rs index fd64a0d77ab..c41dd464d3a 100644 --- a/git-config/tests/values/path.rs +++ b/git-config/tests/values/path.rs @@ -1,11 +1,10 @@ mod interpolate { - use git_config::path; use std::{ borrow::Cow, path::{Path, PathBuf}, }; - use git_config::path::interpolate::Error; + use git_config::{path, path::interpolate::Error}; use crate::{file::cow_str, value::b}; diff --git a/git-odb/src/alternate/mod.rs b/git-odb/src/alternate/mod.rs index ba55173f1f2..ec6763de44e 100644 --- a/git-odb/src/alternate/mod.rs +++ b/git-odb/src/alternate/mod.rs @@ -16,9 +16,10 @@ //! ``` //! //! Based on the [canonical implementation](https://github.com/git/git/blob/master/sha1-file.c#L598:L609). -use git_path::realpath::MAX_SYMLINKS; use std::{fs, io, path::PathBuf}; +use git_path::realpath::MAX_SYMLINKS; + /// pub mod parse; diff --git a/git-path/src/realpath.rs b/git-path/src/realpath.rs index c90e9229b47..807cb74ccf6 100644 --- a/git-path/src/realpath.rs +++ b/git-path/src/realpath.rs @@ -18,13 +18,13 @@ pub enum Error { pub const MAX_SYMLINKS: u8 = 32; pub(crate) mod function { - use crate::realpath::MAX_SYMLINKS; use std::path::{ Component::{CurDir, Normal, ParentDir, Prefix, RootDir}, Path, PathBuf, }; use super::Error; + use crate::realpath::MAX_SYMLINKS; /// Check each component of `path` and see if it is a symlink. If so, resolve it. /// Do not fail for non-existing components, but assume these are as is. diff --git a/git-path/tests/convert/absolutize.rs b/git-path/tests/convert/absolutize.rs index aae63762ad7..cc8f70efc72 100644 --- a/git-path/tests/convert/absolutize.rs +++ b/git-path/tests/convert/absolutize.rs @@ -1,6 +1,6 @@ +use std::{borrow::Cow, path::Path}; + use git_path::absolutize; -use std::borrow::Cow; -use std::path::Path; fn p(input: &str) -> &Path { Path::new(input) diff --git a/git-path/tests/realpath/mod.rs b/git-path/tests/realpath/mod.rs index 56a2596b73d..d04bdc406c1 100644 --- a/git-path/tests/realpath/mod.rs +++ b/git-path/tests/realpath/mod.rs @@ -1,5 +1,6 @@ -use git_path::{realpath::Error, realpath_opts}; use std::path::Path; + +use git_path::{realpath::Error, realpath_opts}; use tempfile::tempdir; #[test] diff --git a/git-repository/src/lib.rs b/git-repository/src/lib.rs index 7b0bcb99e54..60efea44e1c 100644 --- a/git-repository/src/lib.rs +++ b/git-repository/src/lib.rs @@ -380,10 +380,9 @@ pub mod state { pub mod discover { use std::path::Path; - use crate::bstr::BString; pub use git_discover::*; - use crate::ThreadSafeRepository; + use crate::{bstr::BString, ThreadSafeRepository}; /// The error returned by [`crate::discover()`]. #[derive(Debug, thiserror::Error)] diff --git a/git-revision/tests/spec/parse/navigate/tilde_symbol.rs b/git-revision/tests/spec/parse/navigate/tilde_symbol.rs index df04df74ba3..5f30877b015 100644 --- a/git-revision/tests/spec/parse/navigate/tilde_symbol.rs +++ b/git-revision/tests/spec/parse/navigate/tilde_symbol.rs @@ -1,5 +1,4 @@ -use git_revision::spec; -use git_revision::spec::parse::delegate::Traversal; +use git_revision::{spec, spec::parse::delegate::Traversal}; use crate::spec::parse::{parse, try_parse}; diff --git a/gitoxide-core/src/repository/revision/explain.rs b/gitoxide-core/src/repository/revision/explain.rs index 60ee57cd17f..f1183fbc097 100644 --- a/gitoxide-core/src/repository/revision/explain.rs +++ b/gitoxide-core/src/repository/revision/explain.rs @@ -1,14 +1,20 @@ #![allow(unused_variables)] +use std::ffi::OsString; + use anyhow::bail; -use git::bstr::{BStr, BString, ByteSlice}; -use git::revision::spec::parse::{delegate, Delegate}; -use git::revision::{ - spec, - spec::parse::delegate::{PeelTo, ReflogLookup, SiblingBranch, Traversal}, +use git::{ + bstr::{BStr, BString, ByteSlice}, + revision::{ + spec, + spec::parse::{ + delegate, + delegate::{PeelTo, ReflogLookup, SiblingBranch, Traversal}, + Delegate, + }, + }, }; use git_repository as git; -use std::ffi::OsString; pub fn explain(_repo: git::Repository, spec: OsString, mut out: impl std::io::Write) -> anyhow::Result<()> { let mut explain = Explain::new(&mut out); diff --git a/src/porcelain/options.rs b/src/porcelain/options.rs index 4e55689d487..9d3a25aaab6 100644 --- a/src/porcelain/options.rs +++ b/src/porcelain/options.rs @@ -106,10 +106,10 @@ pub struct EstimateHours { } mod validator { - use git_repository as git; use std::{ffi::OsStr, path::PathBuf}; use anyhow::Context; + use git_repository as git; fn is_repo_inner(dir: &OsStr) -> anyhow::Result<()> { let git_dir = PathBuf::from(dir).join(".git"); From d087f12eec73626eb327eaacef8ebb3836b02381 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 12 Jul 2022 10:36:49 +0800 Subject: [PATCH 155/366] =?UTF-8?q?feat:=20Add=20`parse::(Event|section::H?= =?UTF-8?q?eader|Comment)::write=5Fto(=E2=80=A6)`.=20(#331)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now it's possible to serialize these types in a streaming fashion and without arbitrarily enforcing UTF-8 on it --- git-config/src/parse/comment.rs | 29 +++++++++++++------- git-config/src/parse/event.rs | 45 +++++++++++++++---------------- git-config/src/parse/section.rs | 48 ++++++++++++++++++++------------- 3 files changed, 70 insertions(+), 52 deletions(-) diff --git a/git-config/src/parse/comment.rs b/git-config/src/parse/comment.rs index f302ff8cb5e..aba6d197085 100644 --- a/git-config/src/parse/comment.rs +++ b/git-config/src/parse/comment.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, fmt::Display}; -use bstr::{BString, ByteVec}; +use bstr::BString; use crate::parse::Comment; @@ -13,16 +13,27 @@ impl Comment<'_> { comment: Cow::Owned(self.comment.as_ref().into()), } } + + /// Serialize this type into a `BString` for convenience. + /// + /// Note that `to_string()` can also be used, but might not be lossless. + #[must_use] + pub fn to_bstring(&self) -> BString { + let mut buf = Vec::new(); + self.write_to(&mut buf).expect("io error impossible"); + buf.into() + } + + /// Stream ourselves to the given `out`, in order to reproduce this comment losslessly. + pub fn write_to(&self, mut out: impl std::io::Write) -> std::io::Result<()> { + out.write_all(&[self.comment_tag])?; + out.write_all(self.comment.as_ref()) + } } impl Display for Comment<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.comment_tag.fmt(f)?; - if let Ok(s) = std::str::from_utf8(&self.comment) { - s.fmt(f) - } else { - write!(f, "{:02x?}", self.comment) - } + Display::fmt(&self.to_bstring(), f) } } @@ -34,8 +45,6 @@ impl From> for BString { impl From<&Comment<'_>> for BString { fn from(c: &Comment<'_>) -> Self { - let mut values = BString::from(vec![c.comment_tag]); - values.push_str(c.comment.as_ref()); - values + c.to_bstring() } } diff --git a/git-config/src/parse/event.rs b/git-config/src/parse/event.rs index 9b5565b1fc2..de52d3a36fc 100644 --- a/git-config/src/parse/event.rs +++ b/git-config/src/parse/event.rs @@ -5,12 +5,28 @@ use bstr::BString; use crate::parse::Event; impl Event<'_> { - /// Generates a byte representation of the value. This should be used when - /// non-UTF-8 sequences are present or a UTF-8 representation can't be - /// guaranteed. + /// Serialize this type into a `BString` for convenience. + /// + /// Note that `to_string()` can also be used, but might not be lossless. #[must_use] pub fn to_bstring(&self) -> BString { - self.into() + let mut buf = Vec::new(); + self.write_to(&mut buf).expect("io error impossible"); + buf.into() + } + + /// Stream ourselves to the given `out`, in order to reproduce this event mostly losslessly + /// as it was parsed. + pub fn write_to(&self, mut out: impl std::io::Write) -> std::io::Result<()> { + match self { + Self::Whitespace(e) | Self::Newline(e) | Self::Value(e) | Self::ValueNotDone(e) | Self::ValueDone(e) => { + out.write_all(e.as_ref()) + } + Self::KeyValueSeparator => out.write_all(&[b'=']), + Self::SectionKey(k) => out.write_all(k.0.as_ref()), + Self::SectionHeader(h) => h.write_to(&mut out), + Self::Comment(c) => c.write_to(&mut out), + } } /// Turn this instance into a fully owned one with `'static` lifetime. @@ -32,17 +48,7 @@ impl Event<'_> { impl Display for Event<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Value(e) | Self::ValueNotDone(e) | Self::ValueDone(e) => match std::str::from_utf8(e) { - Ok(e) => e.fmt(f), - Err(_) => write!(f, "{:02x?}", e), - }, - Self::Comment(e) => e.fmt(f), - Self::SectionHeader(e) => e.fmt(f), - Self::SectionKey(e) => e.fmt(f), - Self::Newline(e) | Self::Whitespace(e) => e.fmt(f), - Self::KeyValueSeparator => write!(f, "="), - } + Display::fmt(&self.to_bstring(), f) } } @@ -54,13 +60,6 @@ impl From> for BString { impl From<&Event<'_>> for BString { fn from(event: &Event<'_>) -> Self { - match event { - Event::Value(e) | Event::ValueNotDone(e) | Event::ValueDone(e) => e.as_ref().into(), - Event::Comment(e) => e.into(), - Event::SectionHeader(e) => e.into(), - Event::SectionKey(e) => e.0.as_ref().into(), - Event::Newline(e) | Event::Whitespace(e) => e.as_ref().into(), - Event::KeyValueSeparator => "=".into(), - } + event.to_bstring() } } diff --git a/git-config/src/parse/section.rs b/git-config/src/parse/section.rs index 44d8f4a4bd1..37dcd4d7ec2 100644 --- a/git-config/src/parse/section.rs +++ b/git-config/src/parse/section.rs @@ -48,12 +48,35 @@ impl Display for Section<'_> { } impl Header<'_> { - /// Generates a byte representation of the value. This should be used when - /// non-UTF-8 sequences are present or a UTF-8 representation can't be - /// guaranteed. + /// Serialize this type into a `BString` for convenience. + /// + /// Note that `to_string()` can also be used, but might not be lossless. #[must_use] pub fn to_bstring(&self) -> BString { - self.into() + let mut buf = Vec::new(); + self.write_to(&mut buf).expect("io error impossible"); + buf.into() + } + + /// Stream ourselves to the given `out`, in order to reproduce this header mostly losslessly + /// as it was parsed. + pub fn write_to(&self, mut out: impl std::io::Write) -> std::io::Result<()> { + out.write_all(b"[")?; + out.write_all(self.name.as_ref())?; + + if let (Some(sep), Some(subsection)) = (&self.separator, &self.subsection_name) { + let sep = sep.as_ref(); + out.write_all(sep)?; + if sep == "." { + out.write_all(subsection.as_ref())?; + } else { + out.write_all(&[b'"'])?; + out.write_all(subsection.as_ref())?; + out.write_all(&[b'"'])?; + } + } + + out.write_all(b"]") } /// Turn this instance into a fully owned one with `'static` lifetime. @@ -69,20 +92,7 @@ impl Header<'_> { impl Display for Header<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "[{}", self.name)?; - - if let Some(v) = &self.separator { - // Separator must be utf-8 - v.fmt(f)?; - let subsection_name = self.subsection_name.as_ref().unwrap(); - if v.as_ref() == "." { - subsection_name.fmt(f)?; - } else { - write!(f, "\"{}\"", subsection_name)?; // TODO: proper escaping of special characters - } - } - - write!(f, "]") + Display::fmt(&self.to_bstring(), f) } } @@ -94,7 +104,7 @@ impl From> for BString { impl From<&Header<'_>> for BString { fn from(header: &Header<'_>) -> Self { - header.to_string().into() + header.to_bstring() } } From b22732a2ab17213c4a1020859ec41f25ccabfbfc Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 12 Jul 2022 10:56:55 +0800 Subject: [PATCH 156/366] change!: remove `Boolean::to_bstring()` along with a few `From` impls. (#331) These were superfluous and aren't useful in practice. Note that serialization is still implemented via `Display`. --- git-config/src/values/boolean.rs | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/git-config/src/values/boolean.rs b/git-config/src/values/boolean.rs index 3540e085bfc..83880085b98 100644 --- a/git-config/src/values/boolean.rs +++ b/git-config/src/values/boolean.rs @@ -1,19 +1,9 @@ use std::{borrow::Cow, convert::TryFrom, fmt::Display}; -use bstr::{BStr, BString, ByteSlice}; +use bstr::{BStr, BString}; use crate::{value, Boolean}; -impl Boolean { - /// Generates a byte representation of the value. This should be used when - /// non-UTF-8 sequences are present or a UTF-8 representation can't be - /// guaranteed. - #[must_use] - pub fn to_bstring(self) -> BString { - self.to_string().into() - } -} - fn bool_err(input: impl Into) -> value::Error { value::Error::new( "Booleans need to be 'no', 'off', 'false', 'zero' or 'yes', 'on', 'true', 'one'", @@ -35,14 +25,6 @@ impl TryFrom<&BStr> for Boolean { } } -impl TryFrom for Boolean { - type Error = value::Error; - - fn try_from(value: BString) -> Result { - Self::try_from(value.as_bstr()) - } -} - impl TryFrom> for Boolean { type Error = value::Error; fn try_from(c: Cow<'_, BStr>) -> Result { From 0e392f81e99c8c0ff29f41b9b86afd57cd99c245 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 12 Jul 2022 11:06:33 +0800 Subject: [PATCH 157/366] change!: remove `Integer::to_bstring()` as well as some `TryFrom` impls. (#331) Note that it can still display itself like before via `std::fmt::Display`. --- git-config/src/values/color.rs | 8 +++--- git-config/src/values/integer.rs | 45 +++----------------------------- 2 files changed, 7 insertions(+), 46 deletions(-) diff --git a/git-config/src/values/color.rs b/git-config/src/values/color.rs index 15001f9731a..3a37d72d23a 100644 --- a/git-config/src/values/color.rs +++ b/git-config/src/values/color.rs @@ -221,10 +221,10 @@ impl FromStr for Name { } } -impl TryFrom<&[u8]> for Name { +impl TryFrom<&BStr> for Name { type Error = value::Error; - fn try_from(s: &[u8]) -> Result { + fn try_from(s: &BStr) -> Result { Self::from_str(std::str::from_utf8(s).map_err(|err| color_err(s).with_err(err))?) } } @@ -337,10 +337,10 @@ impl FromStr for Attribute { } } -impl TryFrom<&[u8]> for Attribute { +impl TryFrom<&BStr> for Attribute { type Error = value::Error; - fn try_from(s: &[u8]) -> Result { + fn try_from(s: &BStr) -> Result { Self::from_str(std::str::from_utf8(s).map_err(|err| color_err(s).with_err(err))?) } } diff --git a/git-config/src/values/integer.rs b/git-config/src/values/integer.rs index a5e99e4686a..1734075679e 100644 --- a/git-config/src/values/integer.rs +++ b/git-config/src/values/integer.rs @@ -5,14 +5,6 @@ use bstr::{BStr, BString}; use crate::{value, Integer}; impl Integer { - /// Generates a byte representation of the value. This should be used when - /// non-UTF-8 sequences are present or a UTF-8 representation can't be - /// guaranteed. - #[must_use] - pub fn to_bstring(self) -> BString { - self.into() - } - /// Canonicalize values as simple decimal numbers. /// An optional suffix of k, m, or g (case-insensitive), will cause the /// value to be multiplied by 1024 (k), 1048576 (m), or 1073741824 (g) respectively. @@ -89,34 +81,11 @@ impl TryFrom<&BStr> for Integer { } } -impl TryFrom for Integer { - type Error = value::Error; - - fn try_from(value: BString) -> Result { - Self::try_from(value.as_ref()) - } -} - impl TryFrom> for Integer { type Error = value::Error; fn try_from(c: Cow<'_, BStr>) -> Result { - match c { - Cow::Borrowed(c) => Self::try_from(c), - Cow::Owned(c) => Self::try_from(c), - } - } -} - -impl From for BString { - fn from(i: Integer) -> Self { - i.into() - } -} - -impl From<&Integer> for BString { - fn from(i: &Integer) -> Self { - i.to_string().into() + Self::try_from(c.as_ref()) } } @@ -180,18 +149,10 @@ impl FromStr for Suffix { } } -impl TryFrom<&[u8]> for Suffix { +impl TryFrom<&BStr> for Suffix { type Error = (); - fn try_from(s: &[u8]) -> Result { + fn try_from(s: &BStr) -> Result { Self::from_str(std::str::from_utf8(s).map_err(|_| ())?) } } - -impl TryFrom for Suffix { - type Error = (); - - fn try_from(value: BString) -> Result { - Self::try_from(value.as_ref()) - } -} From a02d5759c14eb1d42fe24e61afc32a4cd463d1b7 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 12 Jul 2022 11:07:32 +0800 Subject: [PATCH 158/366] adapt to breaking changes in `git-config` (#331) --- git-repository/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-repository/src/lib.rs b/git-repository/src/lib.rs index 60efea44e1c..837bf0c1f40 100644 --- a/git-repository/src/lib.rs +++ b/git-repository/src/lib.rs @@ -443,7 +443,7 @@ pub mod discover { if let Some(cross_fs) = std::env::var_os("GIT_DISCOVERY_ACROSS_FILESYSTEM") .and_then(|v| Vec::from_os_string(v).ok().map(BString::from)) { - if let Ok(b) = git_config::Boolean::try_from(cross_fs) { + if let Ok(b) = git_config::Boolean::try_from(cross_fs.as_ref()) { opts.cross_fs = b.into(); } } From 4f6cd8cf65c2d8698bffe327a19031c342b229a6 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 12 Jul 2022 11:20:15 +0800 Subject: [PATCH 159/366] change!: Add `File::write_to()` and `File::to_bstring()`; remove some `TryFrom` impls. (#331) Now `File` can be serialized in a streaming fashion and without the possibility for UTF8 conversion issues. Note that `Display` is still imlpemented with the usual caveats. --- git-config/src/file/access/mod.rs | 1 + git-config/src/file/access/write.rs | 35 ++++++++++++++++++ git-config/src/file/impls.rs | 56 ++--------------------------- 3 files changed, 38 insertions(+), 54 deletions(-) create mode 100644 git-config/src/file/access/write.rs diff --git a/git-config/src/file/access/mod.rs b/git-config/src/file/access/mod.rs index 58f8c7aaa30..ce1571d2c1f 100644 --- a/git-config/src/file/access/mod.rs +++ b/git-config/src/file/access/mod.rs @@ -1,3 +1,4 @@ mod comfort; mod low_level; mod raw; +mod write; diff --git a/git-config/src/file/access/write.rs b/git-config/src/file/access/write.rs new file mode 100644 index 00000000000..59ed856b9cc --- /dev/null +++ b/git-config/src/file/access/write.rs @@ -0,0 +1,35 @@ +use crate::File; +use bstr::BString; + +impl File<'_> { + /// Serialize this type into a `BString` for convenience. + /// + /// Note that `to_string()` can also be used, but might not be lossless. + #[must_use] + pub fn to_bstring(&self) -> BString { + let mut buf = Vec::new(); + self.write_to(&mut buf).expect("io error impossible"); + buf.into() + } + + /// Stream ourselves to the given `out`, in order to reproduce this file mostly losslessly + /// as it was parsed. + pub fn write_to(&self, mut out: impl std::io::Write) -> std::io::Result<()> { + for event in self.frontmatter_events.as_ref() { + event.write_to(&mut out)?; + } + + for section_id in &self.section_order { + self.section_headers + .get(section_id) + .expect("known section-id") + .write_to(&mut out)?; + + for event in self.sections.get(section_id).expect("known section-id").as_ref() { + event.write_to(&mut out)?; + } + } + + Ok(()) + } +} diff --git a/git-config/src/file/impls.rs b/git-config/src/file/impls.rs index 7585079a285..c1bdbbdbdb4 100644 --- a/git-config/src/file/impls.rs +++ b/git-config/src/file/impls.rs @@ -1,6 +1,6 @@ use std::{convert::TryFrom, fmt::Display}; -use bstr::{BStr, BString, ByteVec}; +use bstr::{BStr, BString}; use crate::{file::SectionBody, parse, File}; @@ -14,16 +14,6 @@ impl<'a> TryFrom<&'a str> for File<'a> { } } -impl<'a> TryFrom<&'a [u8]> for File<'a> { - type Error = parse::Error; - - /// Convenience constructor. Attempts to parse the provided byte string into - /// a [`File`]. See [`from_bytes()`][crate::parse::from_bytes()] for more information. - fn try_from(value: &'a [u8]) -> Result, Self::Error> { - parse::Events::from_bytes(value).map(File::from) - } -} - impl<'a> TryFrom<&'a BStr> for File<'a> { type Error = parse::Error; @@ -55,50 +45,8 @@ impl From> for BString { } } -impl From<&File<'_>> for BString { - fn from(config: &File<'_>) -> Self { - let mut value = BString::default(); - - for events in config.frontmatter_events.as_ref() { - value.push_str(events.to_bstring()); - } - - for section_id in &config.section_order { - value.push_str( - config - .section_headers - .get(section_id) - .expect("section_header does not contain section id from section_order") - .to_bstring(), - ); - - for event in config - .sections - .get(section_id) - .expect("sections does not contain section id from section_order") - .as_ref() - { - value.push_str(event.to_bstring()); - } - } - - value - } -} - impl Display for File<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - for front_matter in self.frontmatter_events.as_ref() { - front_matter.fmt(f)?; - } - - for section_id in &self.section_order { - self.section_headers.get(section_id).unwrap().fmt(f)?; - for event in self.sections.get(section_id).unwrap().as_ref() { - event.fmt(f)?; - } - } - - Ok(()) + Display::fmt(&self.to_bstring(), f) } } From 78bb93cf35b6a990bac64bbfc56144799ad36243 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 12 Jul 2022 11:48:49 +0800 Subject: [PATCH 160/366] new roundtrip test on file level (#331) --- git-config/tests/file/access/write.rs | 47 +++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 git-config/tests/file/access/write.rs diff --git a/git-config/tests/file/access/write.rs b/git-config/tests/file/access/write.rs new file mode 100644 index 00000000000..a4e765b1087 --- /dev/null +++ b/git-config/tests/file/access/write.rs @@ -0,0 +1,47 @@ +use std::convert::TryFrom; + +#[test] +#[ignore] +fn complex_lossless_roundtrip() { + let input = r#" + [core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + + [remote "origin"] + url = git@github.com:Byron/gitoxide.git + fetch = +refs/heads/*:refs/remotes/origin/* + + [test] + other-quoted = "hello" + + [test "sub-section \"special\" C:\\root"] + bool-explicit = false + bool-implicit + integer-no-prefix = 10 + integer-prefix = 10g + color = brightgreen red \ + bold + other = hello world + other-quoted = "hello world" + location = ~/tmp + location-quoted = "~/quoted" + escaped = \n\thi\b + escaped-quoted = "\n\thi\b" + + [alias] + save = "!f() { \ + git status; \ + git add "-A"; \ + git commit -m \"$1\"; \ + git push -f; \ + git log -1; \ + }; \ + f; \ + unset f" + "#; + let config = git_config::File::try_from(input).unwrap(); + assert_eq!(config.to_bstring(), input); +} From 9fac8e0066c9b1845d9e06fb30b61ca9e9d64555 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 12 Jul 2022 11:49:05 +0800 Subject: [PATCH 161/366] refactor (#331) --- git-config/tests/file/access/mod.rs | 1 + git-config/tests/parse/error.rs | 59 +++++ git-config/tests/parse/from_bytes.rs | 161 ++++++++++++++ git-config/tests/parse/mod.rs | 316 ++------------------------- git-config/tests/parse/section.rs | 51 +++++ 5 files changed, 295 insertions(+), 293 deletions(-) create mode 100644 git-config/tests/parse/error.rs create mode 100644 git-config/tests/parse/from_bytes.rs create mode 100644 git-config/tests/parse/section.rs diff --git a/git-config/tests/file/access/mod.rs b/git-config/tests/file/access/mod.rs index 5b8341aa987..7dcd4a6d724 100644 --- a/git-config/tests/file/access/mod.rs +++ b/git-config/tests/file/access/mod.rs @@ -1,2 +1,3 @@ mod raw; mod read_only; +mod write; diff --git a/git-config/tests/parse/error.rs b/git-config/tests/parse/error.rs new file mode 100644 index 00000000000..47eec9f9eb0 --- /dev/null +++ b/git-config/tests/parse/error.rs @@ -0,0 +1,59 @@ +use crate::parse::Events; + +#[test] +fn line_no_is_one_indexed() { + assert_eq!(Events::from_str("[hello").unwrap_err().line_number(), 1); +} + +#[test] +fn remaining_data_contains_bad_tokens() { + assert_eq!(Events::from_str("[hello").unwrap_err().remaining_data(), b"[hello"); +} + +#[test] +fn to_string_truncates_extra_values() { + assert_eq!( + Events::from_str("[1234567890").unwrap_err().to_string(), + "Got an unexpected token on line 1 while trying to parse a section header: '[123456789' ... (1 characters omitted)" + ); +} + +#[test] +fn detected_by_fuzz() { + assert!(Events::from_str("[]I=").is_err()); +} + +#[test] +fn to_string() { + let input = "[a_b]\n c=d"; + assert_eq!( + Events::from_str(input).unwrap_err().to_string(), + "Got an unexpected token on line 1 while trying to parse a section header: '[a_b]\n c=d'", + "underscores in section names aren't allowed and will be rejected by git" + ); + let input = "[core] a=b\\\n cd\n[core]\n\n 4a=3"; + assert_eq!( + Events::from_str(input).unwrap_err().to_string(), + "Got an unexpected token on line 5 while trying to parse a name: '4a=3'" + ); + let input = "[core] a=b\\\n cd\n 4a=3"; + assert_eq!( + Events::from_str(input).unwrap_err().to_string(), + "Got an unexpected token on line 3 while trying to parse a name: '4a=3'" + ); + let input = "[core] a=b\n 4a=3"; + assert_eq!( + Events::from_str(input).unwrap_err().to_string(), + "Got an unexpected token on line 2 while trying to parse a name: '4a=3'" + ); + let input = "[core] a=b\n =3"; + assert_eq!( + Events::from_str(input).unwrap_err().to_string(), + "Got an unexpected token on line 2 while trying to parse a name: '=3'" + ); + let input = "[core"; + assert_eq!( + Events::from_str(input).unwrap_err().to_string(), + "Got an unexpected token on line 1 while trying to parse a section header: '[core'" + ); +} diff --git a/git-config/tests/parse/from_bytes.rs b/git-config/tests/parse/from_bytes.rs new file mode 100644 index 00000000000..5a8cb994a1c --- /dev/null +++ b/git-config/tests/parse/from_bytes.rs @@ -0,0 +1,161 @@ +use super::*; +use git_config::parse::Events; + +#[test] +#[rustfmt::skip] +fn complex() { + let config = r#"[user] + email = code@eddie.sh + name = Foo Bar +[core] + autocrlf = input +[push] + default = simple +[commit] + gpgsign = true +[gpg] + program = gpg +[url "ssh://git@github.com/"] + insteadOf = "github://" +[url "ssh://git@git.eddie.sh/edward/"] + insteadOf = "gitea://" +[pull] + ff = only +[init] + defaultBranch = master"#; + + assert_eq!( + Events::from_str(config) + .unwrap() + .into_iter() + .collect::>(), + vec![ + section::header_event("user", None), + newline(), + + whitespace(" "), + name("email"), + whitespace(" "), + separator(), + whitespace(" "), + value("code@eddie.sh"), + newline(), + + whitespace(" "), + name("name"), + whitespace(" "), + separator(), + whitespace(" "), + value("Foo Bar"), + newline(), + + section::header_event("core", None), + newline(), + + whitespace(" "), + name("autocrlf"), + whitespace(" "), + separator(), + whitespace(" "), + value("input"), + newline(), + + section::header_event("push", None), + newline(), + + whitespace(" "), + name("default"), + whitespace(" "), + separator(), + whitespace(" "), + value("simple"), + newline(), + + section::header_event("commit", None), + newline(), + + whitespace(" "), + name("gpgsign"), + whitespace(" "), + separator(), + whitespace(" "), + value("true"), + newline(), + + section::header_event("gpg", None), + newline(), + + whitespace(" "), + name("program"), + whitespace(" "), + separator(), + whitespace(" "), + value("gpg"), + newline(), + + section::header_event("url", (" ", "ssh://git@github.com/")), + newline(), + + whitespace(" "), + name("insteadOf"), + whitespace(" "), + separator(), + whitespace(" "), + value("\"github://\""), + newline(), + + section::header_event("url", (" ", "ssh://git@git.eddie.sh/edward/")), + newline(), + + whitespace(" "), + name("insteadOf"), + whitespace(" "), + separator(), + whitespace(" "), + value("\"gitea://\""), + newline(), + + section::header_event("pull", None), + newline(), + + whitespace(" "), + name("ff"), + whitespace(" "), + separator(), + whitespace(" "), + value("only"), + newline(), + + section::header_event("init", None), + newline(), + + whitespace(" "), + name("defaultBranch"), + whitespace(" "), + separator(), + whitespace(" "), + value("master"), + ] + ); +} + +#[test] +fn skips_bom() { + let bytes = b" + [core] + a = 1 +"; + let bytes_with_gb18030_bom = "\u{feff} + [core] + a = 1 +"; + + assert_eq!( + Events::from_bytes(bytes), + Events::from_bytes(bytes_with_gb18030_bom.as_bytes()) + ); + assert_eq!( + Events::from_bytes_owned(bytes, None), + Events::from_bytes_owned(bytes_with_gb18030_bom.as_bytes(), None) + ); +} diff --git a/git-config/tests/parse/mod.rs b/git-config/tests/parse/mod.rs index 5962ac8ccd6..ea2ac77af3b 100644 --- a/git-config/tests/parse/mod.rs +++ b/git-config/tests/parse/mod.rs @@ -1,51 +1,10 @@ use std::borrow::Cow; -use git_config::parse::{section, Event, Events, Section}; +use git_config::parse::{Event, Events, Section}; -pub fn section_header_event(name: &str, subsection: impl Into>) -> Event<'_> { - Event::SectionHeader(section_header(name, subsection)) -} - -pub fn section_header(name: &str, subsection: impl Into>) -> section::Header<'_> { - let name = section::Name(Cow::Borrowed(name.into())); - if let Some((separator, subsection_name)) = subsection.into() { - section::Header { - name, - separator: Some(Cow::Borrowed(separator.into())), - subsection_name: Some(Cow::Borrowed(subsection_name.into())), - } - } else { - section::Header { - name, - separator: None, - subsection_name: None, - } - } -} - -fn name(name: &'static str) -> Event<'static> { - Event::SectionKey(section::Key(Cow::Borrowed(name.into()))) -} - -fn value(value: &'static str) -> Event<'static> { - Event::Value(Cow::Borrowed(value.into())) -} - -fn newline() -> Event<'static> { - newline_custom("\n") -} - -fn newline_custom(value: &'static str) -> Event<'static> { - Event::Newline(Cow::Borrowed(value.into())) -} - -fn whitespace(value: &'static str) -> Event<'static> { - Event::Whitespace(Cow::Borrowed(value.into())) -} - -fn separator() -> Event<'static> { - Event::KeyValueSeparator -} +mod error; +mod from_bytes; +mod section; #[test] fn size_in_memory() { @@ -67,150 +26,12 @@ fn size_in_memory() { } #[test] -#[rustfmt::skip] -fn personal_config() { - let config = r#"[user] - email = code@eddie.sh - name = Foo Bar -[core] - autocrlf = input -[push] - default = simple -[commit] - gpgsign = true -[gpg] - program = gpg -[url "ssh://git@github.com/"] - insteadOf = "github://" -[url "ssh://git@git.eddie.sh/edward/"] - insteadOf = "gitea://" -[pull] - ff = only -[init] - defaultBranch = master"#; - - assert_eq!( - Events::from_str(config) - .unwrap() - .into_iter() - .collect::>(), - vec![ - section_header_event("user", None), - newline(), - - whitespace(" "), - name("email"), - whitespace(" "), - separator(), - whitespace(" "), - value("code@eddie.sh"), - newline(), - - whitespace(" "), - name("name"), - whitespace(" "), - separator(), - whitespace(" "), - value("Foo Bar"), - newline(), - - section_header_event("core", None), - newline(), - - whitespace(" "), - name("autocrlf"), - whitespace(" "), - separator(), - whitespace(" "), - value("input"), - newline(), - - section_header_event("push", None), - newline(), - - whitespace(" "), - name("default"), - whitespace(" "), - separator(), - whitespace(" "), - value("simple"), - newline(), - - section_header_event("commit", None), - newline(), - - whitespace(" "), - name("gpgsign"), - whitespace(" "), - separator(), - whitespace(" "), - value("true"), - newline(), - - section_header_event("gpg", None), - newline(), - - whitespace(" "), - name("program"), - whitespace(" "), - separator(), - whitespace(" "), - value("gpg"), - newline(), - - section_header_event("url", (" ", "ssh://git@github.com/")), - newline(), - - whitespace(" "), - name("insteadOf"), - whitespace(" "), - separator(), - whitespace(" "), - value("\"github://\""), - newline(), - - section_header_event("url", (" ", "ssh://git@git.eddie.sh/edward/")), - newline(), - - whitespace(" "), - name("insteadOf"), - whitespace(" "), - separator(), - whitespace(" "), - value("\"gitea://\""), - newline(), - - section_header_event("pull", None), - newline(), - - whitespace(" "), - name("ff"), - whitespace(" "), - separator(), - whitespace(" "), - value("only"), - newline(), - - section_header_event("init", None), - newline(), - - whitespace(" "), - name("defaultBranch"), - whitespace(" "), - separator(), - whitespace(" "), - value("master"), - ] - ); -} - -#[test] -fn parse_empty() { +fn empty() { assert_eq!(Events::from_str("").unwrap().into_vec(), vec![]); } #[test] -fn parse_whitespace() { +fn newlines_with_spaces() { assert_eq!( Events::from_str("\n \n \n").unwrap().into_vec(), vec![newline(), whitespace(" "), newline(), whitespace(" "), newline()] @@ -218,125 +39,34 @@ fn parse_whitespace() { } #[test] -fn newline_events_are_merged() { +fn consecutive_newlines() { assert_eq!( Events::from_str("\n\n\n\n\n").unwrap().into_vec(), - vec![newline_custom("\n\n\n\n\n")] + vec![newline_custom("\n\n\n\n\n")], + "multiple newlines are merged into a single event" ); } -#[test] -fn error() { - let input = "[a_b]\n c=d"; - assert_eq!( - Events::from_str(input).unwrap_err().to_string(), - "Got an unexpected token on line 1 while trying to parse a section header: '[a_b]\n c=d'", - "underscores in section names aren't allowed and will be rejected by git" - ); - let input = "[core] a=b\\\n cd\n[core]\n\n 4a=3"; - assert_eq!( - Events::from_str(input).unwrap_err().to_string(), - "Got an unexpected token on line 5 while trying to parse a name: '4a=3'" - ); - let input = "[core] a=b\\\n cd\n 4a=3"; - assert_eq!( - Events::from_str(input).unwrap_err().to_string(), - "Got an unexpected token on line 3 while trying to parse a name: '4a=3'" - ); - let input = "[core] a=b\n 4a=3"; - assert_eq!( - Events::from_str(input).unwrap_err().to_string(), - "Got an unexpected token on line 2 while trying to parse a name: '4a=3'" - ); - let input = "[core] a=b\n =3"; - assert_eq!( - Events::from_str(input).unwrap_err().to_string(), - "Got an unexpected token on line 2 while trying to parse a name: '=3'" - ); - let input = "[core"; - assert_eq!( - Events::from_str(input).unwrap_err().to_string(), - "Got an unexpected token on line 1 while trying to parse a section header: '[core'" - ); +fn name(name: &'static str) -> Event<'static> { + Event::SectionKey(git_config::parse::section::Key(Cow::Borrowed(name.into()))) } -mod key { - use std::cmp::Ordering; - - use crate::parse::section::Key; - - #[test] - fn case_insentive_eq() { - assert_eq!(Key::from("aBc"), Key::from("AbC")); - } - - #[test] - fn case_insentive_ord() { - assert_eq!(Key::from("a").cmp(&Key::from("a")), Ordering::Equal); - assert_eq!(Key::from("aBc").cmp(&Key::from("AbC")), Ordering::Equal); - } - - #[test] - fn case_insentive_hash() { - fn calculate_hash(t: T) -> u64 { - use std::hash::Hasher; - let mut s = std::collections::hash_map::DefaultHasher::new(); - t.hash(&mut s); - s.finish() - } - assert_eq!(calculate_hash(Key::from("aBc")), calculate_hash(Key::from("AbC"))); - } +fn value(value: &'static str) -> Event<'static> { + Event::Value(Cow::Borrowed(value.into())) } -mod events { - use crate::parse::Events; - - #[test] - fn parser_skips_bom() { - let bytes = b" - [core] - a = 1 - "; - let bytes_with_gb18030_bom = "\u{feff} - [core] - a = 1 - "; - - assert_eq!( - Events::from_bytes(bytes), - Events::from_bytes(bytes_with_gb18030_bom.as_bytes()) - ); - assert_eq!( - Events::from_bytes_owned(bytes, None), - Events::from_bytes_owned(bytes_with_gb18030_bom.as_bytes(), None) - ); - } +fn newline() -> Event<'static> { + newline_custom("\n") } -#[cfg(test)] -mod error { - use crate::parse::Events; - - #[test] - fn line_no_is_one_indexed() { - assert_eq!(Events::from_str("[hello").unwrap_err().line_number(), 1); - } - - #[test] - fn remaining_data_contains_bad_tokens() { - assert_eq!(Events::from_str("[hello").unwrap_err().remaining_data(), b"[hello"); - } +fn newline_custom(value: &'static str) -> Event<'static> { + Event::Newline(Cow::Borrowed(value.into())) +} - #[test] - fn to_string_truncates_extra_values() { - assert_eq!( - Events::from_str("[1234567890").unwrap_err().to_string(), - "Got an unexpected token on line 1 while trying to parse a section header: '[123456789' ... (1 characters omitted)" - ); - } +fn whitespace(value: &'static str) -> Event<'static> { + Event::Whitespace(Cow::Borrowed(value.into())) +} - #[test] - fn detected_by_fuzz() { - assert!(Events::from_str("[]I=").is_err()); - } +fn separator() -> Event<'static> { + Event::KeyValueSeparator } diff --git a/git-config/tests/parse/section.rs b/git-config/tests/parse/section.rs new file mode 100644 index 00000000000..fe8cb9dd7b6 --- /dev/null +++ b/git-config/tests/parse/section.rs @@ -0,0 +1,51 @@ +use git_config::parse::section; +use git_config::parse::Event; +use std::borrow::Cow; + +pub fn header_event(name: &str, subsection: impl Into>) -> Event<'_> { + Event::SectionHeader(header(name, subsection)) +} + +pub fn header(name: &str, subsection: impl Into>) -> section::Header<'_> { + let name = section::Name(Cow::Borrowed(name.into())); + if let Some((separator, subsection_name)) = subsection.into() { + section::Header { + name, + separator: Some(Cow::Borrowed(separator.into())), + subsection_name: Some(Cow::Borrowed(subsection_name.into())), + } + } else { + section::Header { + name, + separator: None, + subsection_name: None, + } + } +} + +mod key { + use git_config::parse::section::Key; + use std::cmp::Ordering; + + #[test] + fn case_insentive_eq() { + assert_eq!(Key::from("aBc"), Key::from("AbC")); + } + + #[test] + fn case_insentive_ord() { + assert_eq!(Key::from("a").cmp(&Key::from("a")), Ordering::Equal); + assert_eq!(Key::from("aBc").cmp(&Key::from("AbC")), Ordering::Equal); + } + + #[test] + fn case_insentive_hash() { + fn calculate_hash(t: T) -> u64 { + use std::hash::Hasher; + let mut s = std::collections::hash_map::DefaultHasher::new(); + t.hash(&mut s); + s.finish() + } + assert_eq!(calculate_hash(Key::from("aBc")), calculate_hash(Key::from("AbC"))); + } +} From 00d1a9b741845b49d8691262bef6e5c21876567e Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 12 Jul 2022 13:11:22 +0800 Subject: [PATCH 162/366] basic escaping of subsection names during serialization (#331) --- git-config/src/parse/section.rs | 19 +++++++++++++++++-- git-config/tests/parse/section.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/git-config/src/parse/section.rs b/git-config/src/parse/section.rs index 37dcd4d7ec2..7891c8a4706 100644 --- a/git-config/src/parse/section.rs +++ b/git-config/src/parse/section.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, fmt::Display}; -use bstr::{BStr, BString}; +use bstr::{BStr, BString, ByteSlice, ByteVec}; use smallvec::SmallVec; use crate::parse::{Event, Section}; @@ -71,7 +71,7 @@ impl Header<'_> { out.write_all(subsection.as_ref())?; } else { out.write_all(&[b'"'])?; - out.write_all(subsection.as_ref())?; + out.write_all(escape_subsection(subsection.as_ref()).as_ref())?; out.write_all(&[b'"'])?; } } @@ -90,6 +90,21 @@ impl Header<'_> { } } +fn escape_subsection(name: &BStr) -> Cow<'_, BStr> { + if name.find_byteset(b"\\\"").is_none() { + return name.into(); + } + let mut buf = Vec::with_capacity(name.len()); + for b in name.iter().copied() { + match b { + b'\\' => buf.push_str(br#"\\"#), + b'"' => buf.push_str(br#"\""#), + _ => buf.push(b), + } + } + BString::from(buf).into() +} + impl Display for Header<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Display::fmt(&self.to_bstring(), f) diff --git a/git-config/tests/parse/section.rs b/git-config/tests/parse/section.rs index fe8cb9dd7b6..062b124d9f4 100644 --- a/git-config/tests/parse/section.rs +++ b/git-config/tests/parse/section.rs @@ -23,6 +23,32 @@ pub fn header(name: &str, subsection: impl Into Date: Tue, 12 Jul 2022 13:45:25 +0800 Subject: [PATCH 163/366] prepare for validation of `parse::section::Header` (#331) --- .../src/file/access/low_level/mutating.rs | 20 +------- git-config/src/parse/section.rs | 43 +++++++++++++++++ git-config/src/parse/tests.rs | 47 +++++++++++++++++++ git-config/tests/parse/from_bytes.rs | 4 +- git-config/tests/parse/section.rs | 45 +++--------------- 5 files changed, 101 insertions(+), 58 deletions(-) diff --git a/git-config/src/file/access/low_level/mutating.rs b/git-config/src/file/access/low_level/mutating.rs index 7d084e59c13..c824a3d676d 100644 --- a/git-config/src/file/access/low_level/mutating.rs +++ b/git-config/src/file/access/low_level/mutating.rs @@ -1,7 +1,6 @@ use std::borrow::Cow; -use bstr::BStr; - +use crate::parse::section::into_cow_bstr; use crate::{ file::{MutableSection, SectionBody}, lookup, @@ -132,15 +131,7 @@ impl<'event> File<'event> { subsection_name: impl Into>>, section: SectionBody<'event>, ) -> MutableSection<'_, 'event> { - let subsection_name = subsection_name.into().map(into_cow_bstr); - self.push_section_internal( - section::Header { - name: section::Name(into_cow_bstr(section_name.into())), - separator: subsection_name.is_some().then(|| Cow::Borrowed(" ".into())), - subsection_name, - }, - section, - ) + self.push_section_internal(section::Header::new(section_name, subsection_name), section) } /// Renames a section, modifying the last matching section. @@ -163,10 +154,3 @@ impl<'event> File<'event> { Ok(()) } } - -fn into_cow_bstr(c: Cow<'_, str>) -> Cow<'_, BStr> { - match c { - Cow::Borrowed(s) => Cow::Borrowed(s.into()), - Cow::Owned(s) => Cow::Owned(s.into()), - } -} diff --git a/git-config/src/parse/section.rs b/git-config/src/parse/section.rs index 7891c8a4706..24410d0f324 100644 --- a/git-config/src/parse/section.rs +++ b/git-config/src/parse/section.rs @@ -47,7 +47,43 @@ impl Display for Section<'_> { } } +impl<'a> Header<'a> { + /// Instantiate a new header either with a section `name`, e.g. "core" serializing to `["core"]` + /// or `[remote "origin"]` for `subsection` being "origin" and `name` being "remote". + pub fn new(name: impl Into>, subsection: impl Into>>) -> Header<'a> { + let name = Name(into_cow_bstr(name.into())); + if let Some(subsection_name) = subsection.into() { + Header { + name, + separator: Some(Cow::Borrowed(" ".into())), + subsection_name: Some(into_cow_bstr(subsection_name)), + } + } else { + Header { + name, + separator: None, + subsection_name: None, + } + } + } +} + impl Header<'_> { + ///Return true if this is a header like `[legacy.subsection]`, or false otherwise. + pub fn is_legacy(&self) -> bool { + self.separator.as_deref().map_or(false, |n| n == ".") + } + + /// Return the subsection name, if present, i.e. "origin" in `[remote "origin"]`. + pub fn subsection_name(&self) -> Option<&BStr> { + self.subsection_name.as_deref() + } + + /// Return the name of the header, like "remote" in `[remote "origin"]`. + pub fn name(&self) -> &BStr { + self.name.as_ref().as_bstr() + } + /// Serialize this type into a `BString` for convenience. /// /// Note that `to_string()` can also be used, but might not be lossless. @@ -212,3 +248,10 @@ mod types { ); } pub use types::{Key, Name}; + +pub(crate) fn into_cow_bstr(c: Cow<'_, str>) -> Cow<'_, BStr> { + match c { + Cow::Borrowed(s) => Cow::Borrowed(s.into()), + Cow::Owned(s) => Cow::Owned(s.into()), + } +} diff --git a/git-config/src/parse/tests.rs b/git-config/src/parse/tests.rs index 7bbf90fb44e..07d4e460cfd 100644 --- a/git-config/src/parse/tests.rs +++ b/git-config/src/parse/tests.rs @@ -1,3 +1,50 @@ +mod section { + + mod header { + mod write_to { + use crate::parse::section; + use std::borrow::Cow; + + fn header(name: &str, subsection: impl Into>) -> section::Header<'_> { + let name = section::Name(Cow::Borrowed(name.into())); + if let Some((separator, subsection_name)) = subsection.into() { + section::Header { + name, + separator: Some(Cow::Borrowed(separator.into())), + subsection_name: Some(Cow::Borrowed(subsection_name.into())), + } + } else { + section::Header { + name, + separator: None, + subsection_name: None, + } + } + } + + #[test] + fn legacy_subsection_format_does_not_use_escapes() { + let invalid = header("invalid", Some((".", "\\ \""))); + assert_eq!( + invalid.to_bstring(), + "[invalid.\\ \"]", + "no escaping happens for legacy subsections" + ); + } + + #[test] + fn subsections_escape_two_characters_only() { + let invalid = header("invalid", Some((" ", "\\ \"\npost newline"))); + assert_eq!( + invalid.to_bstring(), + "[invalid \"\\\\ \\\"\npost newline\"]", + "newlines are actually invalid in subsection, but they are possible due to unvalidated instance creation" + ); + } + } + } +} + pub(crate) mod util { //! This module is only included for tests, and contains common unit test helper //! functions. diff --git a/git-config/tests/parse/from_bytes.rs b/git-config/tests/parse/from_bytes.rs index 5a8cb994a1c..5ddca2fce4c 100644 --- a/git-config/tests/parse/from_bytes.rs +++ b/git-config/tests/parse/from_bytes.rs @@ -93,7 +93,7 @@ fn complex() { value("gpg"), newline(), - section::header_event("url", (" ", "ssh://git@github.com/")), + section::header_event("url", "ssh://git@github.com/"), newline(), whitespace(" "), @@ -104,7 +104,7 @@ fn complex() { value("\"github://\""), newline(), - section::header_event("url", (" ", "ssh://git@git.eddie.sh/edward/")), + section::header_event("url", "ssh://git@git.eddie.sh/edward/"), newline(), whitespace(" "), diff --git a/git-config/tests/parse/section.rs b/git-config/tests/parse/section.rs index 062b124d9f4..da369f08e02 100644 --- a/git-config/tests/parse/section.rs +++ b/git-config/tests/parse/section.rs @@ -2,50 +2,19 @@ use git_config::parse::section; use git_config::parse::Event; use std::borrow::Cow; -pub fn header_event(name: &str, subsection: impl Into>) -> Event<'_> { - Event::SectionHeader(header(name, subsection)) -} - -pub fn header(name: &str, subsection: impl Into>) -> section::Header<'_> { - let name = section::Name(Cow::Borrowed(name.into())); - if let Some((separator, subsection_name)) = subsection.into() { - section::Header { - name, - separator: Some(Cow::Borrowed(separator.into())), - subsection_name: Some(Cow::Borrowed(subsection_name.into())), - } - } else { - section::Header { - name, - separator: None, - subsection_name: None, - } - } +pub fn header_event(name: &'static str, subsection: impl Into>) -> Event<'static> { + Event::SectionHeader(section::Header::new(name, subsection.into().map(Cow::Borrowed))) } mod header { - mod write_to { - use crate::parse::section::header; - + mod new { #[test] - fn legacy_subsection_format_does_not_use_escapes() { - let invalid = header("invalid", Some((".", "\\ \""))); - assert_eq!( - invalid.to_bstring(), - "[invalid.\\ \"]", - "no escaping happens for legacy subsections" - ); - } + #[ignore] + fn names_must_be_mostly_ascii() {} #[test] - fn subsections_escape_two_characters_only() { - let invalid = header("invalid", Some((" ", "\\ \"\npost newline"))); - assert_eq!( - invalid.to_bstring(), - "[invalid \"\\\\ \\\"\npost newline\"]", - "newlines are actually invalid in subsection, but they are possible due to unvalidated instance creation" - ); - } + #[ignore] + fn subsections_with_newlines_and_null_bytes_are_rejected() {} } } From cfd974f46d2cbb99e7784a05f5e358fed0d4bcab Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 12 Jul 2022 16:02:56 +0800 Subject: [PATCH 164/366] feat!: section names are now validated. (#331) --- .../src/file/access/low_level/mutating.rs | 13 +- git-config/src/file/from_env.rs | 4 +- git-config/src/parse/section/header.rs | 141 ++++++++++++++++++ .../src/parse/{section.rs => section/mod.rs} | 123 +-------------- git-config/tests/parse/section.rs | 24 ++- 5 files changed, 176 insertions(+), 129 deletions(-) create mode 100644 git-config/src/parse/section/header.rs rename git-config/src/parse/{section.rs => section/mod.rs} (55%) diff --git a/git-config/src/file/access/low_level/mutating.rs b/git-config/src/file/access/low_level/mutating.rs index c824a3d676d..9c9b6d13269 100644 --- a/git-config/src/file/access/low_level/mutating.rs +++ b/git-config/src/file/access/low_level/mutating.rs @@ -51,20 +51,21 @@ impl<'event> File<'event> { /// # use std::convert::TryFrom; /// # use bstr::ByteSlice; /// let mut git_config = git_config::File::default(); - /// let mut section = git_config.new_section("hello", Some("world".into())); + /// let mut section = git_config.new_section("hello", Some("world".into()))?; /// section.push("a".into(), b"b".as_bstr().into()); /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n a=b\n"); /// let _section = git_config.new_section("core", None); /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n a=b\n[core]\n"); + /// # Ok::<(), Box>(()) /// ``` pub fn new_section( &mut self, section_name: impl Into>, subsection_name: impl Into>>, - ) -> MutableSection<'_, 'event> { - let mut section = self.push_section(section_name, subsection_name, SectionBody::default()); + ) -> Result, section::header::Error> { + let mut section = self.push_section(section_name, subsection_name, SectionBody::default())?; section.push_newline(); - section + Ok(section) } /// Removes the section, returning the events it had, if any. If multiple @@ -130,8 +131,8 @@ impl<'event> File<'event> { section_name: impl Into>, subsection_name: impl Into>>, section: SectionBody<'event>, - ) -> MutableSection<'_, 'event> { - self.push_section_internal(section::Header::new(section_name, subsection_name), section) + ) -> Result, section::header::Error> { + Ok(self.push_section_internal(section::Header::new(section_name, subsection_name)?, section)) } /// Renames a section, modifying the last matching section. diff --git a/git-config/src/file/from_env.rs b/git-config/src/file/from_env.rs index c0792296b0f..0c82918b50e 100644 --- a/git-config/src/file/from_env.rs +++ b/git-config/src/file/from_env.rs @@ -25,6 +25,8 @@ pub enum Error { PathInterpolationError(#[from] interpolate::Error), #[error(transparent)] FromPathsError(#[from] from_paths::Error), + #[error(transparent)] + Section(#[from] section::header::Error), } /// Instantiation from environment variables @@ -106,7 +108,7 @@ impl File<'static> { Err(_) => config.new_section( section_name.to_string(), subsection.map(|subsection| Cow::Owned(subsection.to_string())), - ), + )?, }; section.push( diff --git a/git-config/src/parse/section/header.rs b/git-config/src/parse/section/header.rs new file mode 100644 index 00000000000..73dd9a506ea --- /dev/null +++ b/git-config/src/parse/section/header.rs @@ -0,0 +1,141 @@ +use crate::parse::section::{into_cow_bstr, Header, Name}; +use crate::parse::Event; +use bstr::{BStr, BString, ByteSlice, ByteVec}; +use std::borrow::Cow; +use std::fmt::Display; + +/// The error returned by [`Header::new(…)`][super::Header::new()]. +#[derive(Debug, PartialOrd, PartialEq, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error { + #[error("section names can only be ascii, '-'")] + InvalidName, +} + +impl<'a> Header<'a> { + /// Instantiate a new header either with a section `name`, e.g. "core" serializing to `["core"]` + /// or `[remote "origin"]` for `subsection` being "origin" and `name` being "remote". + pub fn new( + name: impl Into>, + subsection: impl Into>>, + ) -> Result, Error> { + let name = Name(validated_name(into_cow_bstr(name.into()))?); + if let Some(subsection_name) = subsection.into() { + Ok(Header { + name, + separator: Some(Cow::Borrowed(" ".into())), + subsection_name: Some(into_cow_bstr(subsection_name)), + }) + } else { + Ok(Header { + name, + separator: None, + subsection_name: None, + }) + } + } +} + +fn validated_name(name: Cow<'_, BStr>) -> Result, Error> { + name.iter() + .all(|b| b.is_ascii_alphanumeric() || *b == b'-') + .then(|| name) + .ok_or(Error::InvalidName) +} + +impl Header<'_> { + ///Return true if this is a header like `[legacy.subsection]`, or false otherwise. + pub fn is_legacy(&self) -> bool { + self.separator.as_deref().map_or(false, |n| n == ".") + } + + /// Return the subsection name, if present, i.e. "origin" in `[remote "origin"]`. + pub fn subsection_name(&self) -> Option<&BStr> { + self.subsection_name.as_deref() + } + + /// Return the name of the header, like "remote" in `[remote "origin"]`. + pub fn name(&self) -> &BStr { + self.name.as_ref().as_bstr() + } + + /// Serialize this type into a `BString` for convenience. + /// + /// Note that `to_string()` can also be used, but might not be lossless. + #[must_use] + pub fn to_bstring(&self) -> BString { + let mut buf = Vec::new(); + self.write_to(&mut buf).expect("io error impossible"); + buf.into() + } + + /// Stream ourselves to the given `out`, in order to reproduce this header mostly losslessly + /// as it was parsed. + pub fn write_to(&self, mut out: impl std::io::Write) -> std::io::Result<()> { + out.write_all(b"[")?; + out.write_all(self.name.as_ref())?; + + if let (Some(sep), Some(subsection)) = (&self.separator, &self.subsection_name) { + let sep = sep.as_ref(); + out.write_all(sep)?; + if sep == "." { + out.write_all(subsection.as_ref())?; + } else { + out.write_all(&[b'"'])?; + out.write_all(escape_subsection(subsection.as_ref()).as_ref())?; + out.write_all(&[b'"'])?; + } + } + + out.write_all(b"]") + } + + /// Turn this instance into a fully owned one with `'static` lifetime. + #[must_use] + pub fn to_owned(&self) -> Header<'static> { + Header { + name: self.name.to_owned(), + separator: self.separator.clone().map(|v| Cow::Owned(v.into_owned())), + subsection_name: self.subsection_name.clone().map(|v| Cow::Owned(v.into_owned())), + } + } +} + +fn escape_subsection(name: &BStr) -> Cow<'_, BStr> { + if name.find_byteset(b"\\\"").is_none() { + return name.into(); + } + let mut buf = Vec::with_capacity(name.len()); + for b in name.iter().copied() { + match b { + b'\\' => buf.push_str(br#"\\"#), + b'"' => buf.push_str(br#"\""#), + _ => buf.push(b), + } + } + BString::from(buf).into() +} + +impl Display for Header<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Display::fmt(&self.to_bstring(), f) + } +} + +impl From> for BString { + fn from(header: Header<'_>) -> Self { + header.into() + } +} + +impl From<&Header<'_>> for BString { + fn from(header: &Header<'_>) -> Self { + header.to_bstring() + } +} + +impl<'a> From> for Event<'a> { + fn from(header: Header<'_>) -> Event<'_> { + Event::SectionHeader(header) + } +} diff --git a/git-config/src/parse/section.rs b/git-config/src/parse/section/mod.rs similarity index 55% rename from git-config/src/parse/section.rs rename to git-config/src/parse/section/mod.rs index 24410d0f324..506733d92db 100644 --- a/git-config/src/parse/section.rs +++ b/git-config/src/parse/section/mod.rs @@ -1,10 +1,13 @@ use std::{borrow::Cow, fmt::Display}; -use bstr::{BStr, BString, ByteSlice, ByteVec}; +use bstr::BStr; use smallvec::SmallVec; use crate::parse::{Event, Section}; +/// +pub mod header; + /// A container for events, avoiding heap allocations in typical files. pub type Events<'a> = SmallVec<[Event<'a>; 64]>; @@ -47,124 +50,6 @@ impl Display for Section<'_> { } } -impl<'a> Header<'a> { - /// Instantiate a new header either with a section `name`, e.g. "core" serializing to `["core"]` - /// or `[remote "origin"]` for `subsection` being "origin" and `name` being "remote". - pub fn new(name: impl Into>, subsection: impl Into>>) -> Header<'a> { - let name = Name(into_cow_bstr(name.into())); - if let Some(subsection_name) = subsection.into() { - Header { - name, - separator: Some(Cow::Borrowed(" ".into())), - subsection_name: Some(into_cow_bstr(subsection_name)), - } - } else { - Header { - name, - separator: None, - subsection_name: None, - } - } - } -} - -impl Header<'_> { - ///Return true if this is a header like `[legacy.subsection]`, or false otherwise. - pub fn is_legacy(&self) -> bool { - self.separator.as_deref().map_or(false, |n| n == ".") - } - - /// Return the subsection name, if present, i.e. "origin" in `[remote "origin"]`. - pub fn subsection_name(&self) -> Option<&BStr> { - self.subsection_name.as_deref() - } - - /// Return the name of the header, like "remote" in `[remote "origin"]`. - pub fn name(&self) -> &BStr { - self.name.as_ref().as_bstr() - } - - /// Serialize this type into a `BString` for convenience. - /// - /// Note that `to_string()` can also be used, but might not be lossless. - #[must_use] - pub fn to_bstring(&self) -> BString { - let mut buf = Vec::new(); - self.write_to(&mut buf).expect("io error impossible"); - buf.into() - } - - /// Stream ourselves to the given `out`, in order to reproduce this header mostly losslessly - /// as it was parsed. - pub fn write_to(&self, mut out: impl std::io::Write) -> std::io::Result<()> { - out.write_all(b"[")?; - out.write_all(self.name.as_ref())?; - - if let (Some(sep), Some(subsection)) = (&self.separator, &self.subsection_name) { - let sep = sep.as_ref(); - out.write_all(sep)?; - if sep == "." { - out.write_all(subsection.as_ref())?; - } else { - out.write_all(&[b'"'])?; - out.write_all(escape_subsection(subsection.as_ref()).as_ref())?; - out.write_all(&[b'"'])?; - } - } - - out.write_all(b"]") - } - - /// Turn this instance into a fully owned one with `'static` lifetime. - #[must_use] - pub fn to_owned(&self) -> Header<'static> { - Header { - name: self.name.to_owned(), - separator: self.separator.clone().map(|v| Cow::Owned(v.into_owned())), - subsection_name: self.subsection_name.clone().map(|v| Cow::Owned(v.into_owned())), - } - } -} - -fn escape_subsection(name: &BStr) -> Cow<'_, BStr> { - if name.find_byteset(b"\\\"").is_none() { - return name.into(); - } - let mut buf = Vec::with_capacity(name.len()); - for b in name.iter().copied() { - match b { - b'\\' => buf.push_str(br#"\\"#), - b'"' => buf.push_str(br#"\""#), - _ => buf.push(b), - } - } - BString::from(buf).into() -} - -impl Display for Header<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Display::fmt(&self.to_bstring(), f) - } -} - -impl From> for BString { - fn from(header: Header<'_>) -> Self { - header.into() - } -} - -impl From<&Header<'_>> for BString { - fn from(header: &Header<'_>) -> Self { - header.to_bstring() - } -} - -impl<'a> From> for Event<'a> { - fn from(header: Header<'_>) -> Event<'_> { - Event::SectionHeader(header) - } -} - mod types { macro_rules! generate_case_insensitive { ($name:ident, $cow_inner_type:ty, $comment:literal) => { diff --git a/git-config/tests/parse/section.rs b/git-config/tests/parse/section.rs index da369f08e02..e05530f312e 100644 --- a/git-config/tests/parse/section.rs +++ b/git-config/tests/parse/section.rs @@ -3,14 +3,32 @@ use git_config::parse::Event; use std::borrow::Cow; pub fn header_event(name: &'static str, subsection: impl Into>) -> Event<'static> { - Event::SectionHeader(section::Header::new(name, subsection.into().map(Cow::Borrowed))) + Event::SectionHeader(section::Header::new(name, subsection.into().map(Cow::Borrowed)).unwrap()) } mod header { mod new { + use git_config::parse::section; + #[test] - #[ignore] - fn names_must_be_mostly_ascii() {} + fn names_must_be_mostly_ascii() { + assert_eq!( + section::Header::new("🤗", None), + Err(section::header::Error::InvalidName) + ); + assert_eq!( + section::Header::new("x.y", None), + Err(section::header::Error::InvalidName) + ); + assert_eq!( + section::Header::new("x y", None), + Err(section::header::Error::InvalidName) + ); + assert_eq!( + section::Header::new("x\ny", None), + Err(section::header::Error::InvalidName) + ); + } #[test] #[ignore] From ae3895c7882e0a543a44693faee5f760b49b54d7 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 12 Jul 2022 16:17:55 +0800 Subject: [PATCH 165/366] =?UTF-8?q?feat:=20`parse::Header::new(=E2=80=A6)`?= =?UTF-8?q?=20with=20sub-section=20name=20validation=20(#331)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- git-config/src/parse/section/header.rs | 11 ++++++++- git-config/tests/parse/section.rs | 31 +++++++++++++------------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/git-config/src/parse/section/header.rs b/git-config/src/parse/section/header.rs index 73dd9a506ea..b77bea66567 100644 --- a/git-config/src/parse/section/header.rs +++ b/git-config/src/parse/section/header.rs @@ -10,6 +10,8 @@ use std::fmt::Display; pub enum Error { #[error("section names can only be ascii, '-'")] InvalidName, + #[error("sub-section names must not contain newlines or null bytes")] + InvalidSubSection, } impl<'a> Header<'a> { @@ -24,7 +26,7 @@ impl<'a> Header<'a> { Ok(Header { name, separator: Some(Cow::Borrowed(" ".into())), - subsection_name: Some(into_cow_bstr(subsection_name)), + subsection_name: Some(validated_subsection(into_cow_bstr(subsection_name))?), }) } else { Ok(Header { @@ -36,6 +38,13 @@ impl<'a> Header<'a> { } } +fn validated_subsection(name: Cow<'_, BStr>) -> Result, Error> { + name.find_byteset(b"\n\0") + .is_none() + .then(|| name) + .ok_or(Error::InvalidSubSection) +} + fn validated_name(name: Cow<'_, BStr>) -> Result, Error> { name.iter() .all(|b| b.is_ascii_alphanumeric() || *b == b'-') diff --git a/git-config/tests/parse/section.rs b/git-config/tests/parse/section.rs index e05530f312e..c535cc6ddf2 100644 --- a/git-config/tests/parse/section.rs +++ b/git-config/tests/parse/section.rs @@ -9,30 +9,29 @@ pub fn header_event(name: &'static str, subsection: impl Into Date: Tue, 12 Jul 2022 16:26:04 +0800 Subject: [PATCH 166/366] change!: Enforce `parse::section::Header::new()` by making its fields private. (#331) --- .../src/file/access/low_level/read_only.rs | 12 +++++----- git-config/src/parse/events.rs | 24 ++++--------------- git-config/src/parse/section/header.rs | 5 ++++ git-config/src/parse/section/mod.rs | 10 +++----- git-config/src/parse/tests.rs | 2 ++ 5 files changed, 20 insertions(+), 33 deletions(-) diff --git a/git-config/src/file/access/low_level/read_only.rs b/git-config/src/file/access/low_level/read_only.rs index fe34d7ec040..99163b19b05 100644 --- a/git-config/src/file/access/low_level/read_only.rs +++ b/git-config/src/file/access/low_level/read_only.rs @@ -215,15 +215,15 @@ impl<'event> File<'event> { /// assert_eq!(url.map_or(0, |s| s.count()), 2); /// /// for (i, (header, body)) in config.sections_by_name_with_header("url").unwrap().enumerate() { - /// let url = header.subsection_name.as_ref(); - /// let instead_of = body.value(§ion::Key::from("insteadOf")); + /// let url = header.subsection_name().unwrap(); + /// let instead_of = body.value(§ion::Key::from("insteadOf")).unwrap(); /// /// if i == 0 { - /// assert_eq!(instead_of.unwrap().as_ref(), "https://github.com/"); - /// assert_eq!(url.unwrap().as_ref(), "ssh://git@github.com/"); + /// assert_eq!(instead_of.as_ref(), "https://github.com/"); + /// assert_eq!(url, "ssh://git@github.com/"); /// } else { - /// assert_eq!(instead_of.unwrap().as_ref(), "https://bitbucket.org/"); - /// assert_eq!(url.unwrap().as_ref(), "ssh://git@bitbucket.org"); + /// assert_eq!(instead_of.as_ref(), "https://bitbucket.org/"); + /// assert_eq!(url, "ssh://git@bitbucket.org"); /// } /// } /// # Ok::<(), Box>(()) diff --git a/git-config/src/parse/events.rs b/git-config/src/parse/events.rs index 79a3a325788..2c77bb79656 100644 --- a/git-config/src/parse/events.rs +++ b/git-config/src/parse/events.rs @@ -87,11 +87,7 @@ pub type FrontMatterEvents<'a> = SmallVec<[Event<'a>; 8]>; /// ``` /// # use git_config::parse::{Event, Events, section}; /// # use std::borrow::Cow; -/// # let section_header = section::Header { -/// # name: section::Name(Cow::Borrowed("core".into())), -/// # separator: None, -/// # subsection_name: None, -/// # }; +/// # let section_header = section::Header::new("core", None).unwrap(); /// # let section_data = "[core]\n autocrlf = input"; /// # assert_eq!(Events::from_str(section_data).unwrap().into_vec(), vec![ /// Event::SectionHeader(section_header), @@ -126,11 +122,7 @@ pub type FrontMatterEvents<'a> = SmallVec<[Event<'a>; 8]>; /// ``` /// # use git_config::parse::{Event, Events, section}; /// # use std::borrow::Cow; -/// # let section_header = section::Header { -/// # name: section::Name(Cow::Borrowed("core".into())), -/// # separator: None, -/// # subsection_name: None, -/// # }; +/// # let section_header = section::Header::new("core", None).unwrap(); /// # let section_data = "[core]\n autocrlf"; /// # assert_eq!(Events::from_str(section_data).unwrap().into_vec(), vec![ /// Event::SectionHeader(section_header), @@ -160,11 +152,7 @@ pub type FrontMatterEvents<'a> = SmallVec<[Event<'a>; 8]>; /// ``` /// # use git_config::parse::{Event, Events, section}; /// # use std::borrow::Cow; -/// # let section_header = section::Header { -/// # name: section::Name(Cow::Borrowed("core".into())), -/// # separator: None, -/// # subsection_name: None, -/// # }; +/// # let section_header = section::Header::new("core", None).unwrap(); /// # let section_data = "[core]\nautocrlf=true\"\"\nfilemode=fa\"lse\""; /// # assert_eq!(Events::from_str(section_data).unwrap().into_vec(), vec![ /// Event::SectionHeader(section_header), @@ -197,11 +185,7 @@ pub type FrontMatterEvents<'a> = SmallVec<[Event<'a>; 8]>; /// ``` /// # use git_config::parse::{Event, Events, section}; /// # use std::borrow::Cow; -/// # let section_header = section::Header { -/// # name: section::Name(Cow::Borrowed("some-section".into())), -/// # separator: None, -/// # subsection_name: None, -/// # }; +/// # let section_header = section::Header::new("some-section", None).unwrap(); /// # let section_data = "[some-section]\nfile=a\\\n c"; /// # assert_eq!(Events::from_str(section_data).unwrap().into_vec(), vec![ /// Event::SectionHeader(section_header), diff --git a/git-config/src/parse/section/header.rs b/git-config/src/parse/section/header.rs index b77bea66567..f9a2b8b4359 100644 --- a/git-config/src/parse/section/header.rs +++ b/git-config/src/parse/section/header.rs @@ -59,6 +59,11 @@ impl Header<'_> { } /// Return the subsection name, if present, i.e. "origin" in `[remote "origin"]`. + /// + /// It is parsed without quotes, and with escapes folded + /// into their resulting characters. + /// Thus during serialization, escapes and quotes must be re-added. + /// This makes it possible to use [`Event`] data for lookups directly. pub fn subsection_name(&self) -> Option<&BStr> { self.subsection_name.as_deref() } diff --git a/git-config/src/parse/section/mod.rs b/git-config/src/parse/section/mod.rs index 506733d92db..802042d72bb 100644 --- a/git-config/src/parse/section/mod.rs +++ b/git-config/src/parse/section/mod.rs @@ -15,18 +15,14 @@ pub type Events<'a> = SmallVec<[Event<'a>; 64]>; #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] pub struct Header<'a> { /// The name of the header. - pub name: Name<'a>, + pub(crate) name: Name<'a>, /// The separator used to determine if the section contains a subsection. /// This is either a period `.` or a string of whitespace. Note that /// reconstruction of subsection format is dependent on this value. If this /// is all whitespace, then the subsection name needs to be surrounded by /// quotes to have perfect reconstruction. - pub separator: Option>, - /// The subsection name without quotes if any exist, and with escapes folded - /// into their resulting characters. - /// Thus during serialization, escapes and quotes must be re-added. - /// This makes it possible to use [`Event`] data for lookups directly. - pub subsection_name: Option>, + pub(crate) separator: Option>, + pub(crate) subsection_name: Option>, } impl Section<'_> { diff --git a/git-config/src/parse/tests.rs b/git-config/src/parse/tests.rs index 07d4e460cfd..673dd9217d1 100644 --- a/git-config/src/parse/tests.rs +++ b/git-config/src/parse/tests.rs @@ -30,6 +30,7 @@ mod section { "[invalid.\\ \"]", "no escaping happens for legacy subsections" ); + assert!(invalid.is_legacy()); } #[test] @@ -40,6 +41,7 @@ mod section { "[invalid \"\\\\ \\\"\npost newline\"]", "newlines are actually invalid in subsection, but they are possible due to unvalidated instance creation" ); + assert!(!invalid.is_legacy()); } } } From 12cf0052d92ee5bee1926f50c879526b5903c175 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 12 Jul 2022 16:36:45 +0800 Subject: [PATCH 167/366] more header escaping tests (#331) --- git-config/tests/parse/section.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/git-config/tests/parse/section.rs b/git-config/tests/parse/section.rs index c535cc6ddf2..175e91fb6c0 100644 --- a/git-config/tests/parse/section.rs +++ b/git-config/tests/parse/section.rs @@ -7,6 +7,32 @@ pub fn header_event(name: &'static str, subsection: impl Into crate::Result { + assert_eq!( + section::Header::new("core", Cow::from(r#"a\b"#))?.to_bstring(), + r#"[core "a\\b"]"# + ); + assert_eq!( + section::Header::new("core", Cow::from(r#"a:"b""#))?.to_bstring(), + r#"[core "a:\"b\""]"# + ); + Ok(()) + } + + #[test] + fn everything_is_allowed() -> crate::Result { + assert_eq!( + section::Header::new("core", Cow::from("a/b \t\t a\\b"))?.to_bstring(), + "[core \"a/b \t\t a\\\\b\"]" + ); + Ok(()) + } + } mod new { use git_config::parse::section; use std::borrow::Cow; From 9a2f59742cf94643c5b9967b76042bcc7a4e1a71 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 12 Jul 2022 16:46:51 +0800 Subject: [PATCH 168/366] re-add newlines after multi-line values (#331) Note that this only works for parsed values, and user-added values still need to be translated into events properly. --- git-config/src/parse/event.rs | 8 +++++--- git-config/src/parse/section/header.rs | 4 ++-- git-config/tests/file/access/write.rs | 1 - 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/git-config/src/parse/event.rs b/git-config/src/parse/event.rs index de52d3a36fc..d2a57f4ab86 100644 --- a/git-config/src/parse/event.rs +++ b/git-config/src/parse/event.rs @@ -19,10 +19,12 @@ impl Event<'_> { /// as it was parsed. pub fn write_to(&self, mut out: impl std::io::Write) -> std::io::Result<()> { match self { - Self::Whitespace(e) | Self::Newline(e) | Self::Value(e) | Self::ValueNotDone(e) | Self::ValueDone(e) => { - out.write_all(e.as_ref()) + Self::ValueNotDone(e) => { + out.write_all(e.as_ref())?; + out.write_all(b"\\") } - Self::KeyValueSeparator => out.write_all(&[b'=']), + Self::Whitespace(e) | Self::Newline(e) | Self::Value(e) | Self::ValueDone(e) => out.write_all(e.as_ref()), + Self::KeyValueSeparator => out.write_all(b"="), Self::SectionKey(k) => out.write_all(k.0.as_ref()), Self::SectionHeader(h) => h.write_to(&mut out), Self::Comment(c) => c.write_to(&mut out), diff --git a/git-config/src/parse/section/header.rs b/git-config/src/parse/section/header.rs index f9a2b8b4359..1acbcc02919 100644 --- a/git-config/src/parse/section/header.rs +++ b/git-config/src/parse/section/header.rs @@ -95,9 +95,9 @@ impl Header<'_> { if sep == "." { out.write_all(subsection.as_ref())?; } else { - out.write_all(&[b'"'])?; + out.write_all(b"\"")?; out.write_all(escape_subsection(subsection.as_ref()).as_ref())?; - out.write_all(&[b'"'])?; + out.write_all(b"\"")?; } } diff --git a/git-config/tests/file/access/write.rs b/git-config/tests/file/access/write.rs index a4e765b1087..01bb1079453 100644 --- a/git-config/tests/file/access/write.rs +++ b/git-config/tests/file/access/write.rs @@ -1,7 +1,6 @@ use std::convert::TryFrom; #[test] -#[ignore] fn complex_lossless_roundtrip() { let input = r#" [core] From 895ce40aabbe6d6af5b681a0d0942303fd6549a2 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 12 Jul 2022 17:14:57 +0800 Subject: [PATCH 169/366] fix!: `File::rename_section()` with validation of input arguments. (#331) --- git-config/src/file/access/low_level/mod.rs | 2 -- git-config/src/file/access/mod.rs | 3 ++- .../file/access/{low_level => }/mutating.rs | 10 ++++----- .../file/access/{low_level => }/read_only.rs | 0 git-config/src/file/mod.rs | 13 +++++++++++ git-config/tests/file/access/mod.rs | 1 + git-config/tests/file/access/mutating.rs | 22 +++++++++++++++++++ 7 files changed, 42 insertions(+), 9 deletions(-) delete mode 100644 git-config/src/file/access/low_level/mod.rs rename git-config/src/file/access/{low_level => }/mutating.rs (95%) rename git-config/src/file/access/{low_level => }/read_only.rs (100%) create mode 100644 git-config/tests/file/access/mutating.rs diff --git a/git-config/src/file/access/low_level/mod.rs b/git-config/src/file/access/low_level/mod.rs deleted file mode 100644 index d05827a1856..00000000000 --- a/git-config/src/file/access/low_level/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod mutating; -mod read_only; diff --git a/git-config/src/file/access/mod.rs b/git-config/src/file/access/mod.rs index ce1571d2c1f..8b3dc0c69ad 100644 --- a/git-config/src/file/access/mod.rs +++ b/git-config/src/file/access/mod.rs @@ -1,4 +1,5 @@ mod comfort; -mod low_level; +mod mutating; mod raw; +mod read_only; mod write; diff --git a/git-config/src/file/access/low_level/mutating.rs b/git-config/src/file/access/mutating.rs similarity index 95% rename from git-config/src/file/access/low_level/mutating.rs rename to git-config/src/file/access/mutating.rs index 9c9b6d13269..188cd8d13d9 100644 --- a/git-config/src/file/access/low_level/mutating.rs +++ b/git-config/src/file/access/mutating.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use crate::parse::section::into_cow_bstr; +use crate::file::rename_section; use crate::{ file::{MutableSection, SectionBody}, lookup, @@ -140,18 +140,16 @@ impl<'event> File<'event> { &mut self, section_name: &str, subsection_name: impl Into>, - new_section_name: impl Into>, + new_section_name: impl Into>, new_subsection_name: impl Into>>, - ) -> Result<(), lookup::existing::Error> { + ) -> Result<(), rename_section::Error> { let id = self .section_ids_by_name_and_subname(section_name, subsection_name.into())? .rev() .next() .expect("list of sections were empty, which violates invariant"); let header = self.section_headers.get_mut(&id).expect("known section-id"); - header.name = new_section_name.into(); - header.subsection_name = new_subsection_name.into().map(into_cow_bstr); - + *header = section::Header::new(new_section_name, new_subsection_name)?; Ok(()) } } diff --git a/git-config/src/file/access/low_level/read_only.rs b/git-config/src/file/access/read_only.rs similarity index 100% rename from git-config/src/file/access/low_level/read_only.rs rename to git-config/src/file/access/read_only.rs diff --git a/git-config/src/file/mod.rs b/git-config/src/file/mod.rs index c3edb3ece26..9f6e9251f7e 100644 --- a/git-config/src/file/mod.rs +++ b/git-config/src/file/mod.rs @@ -19,6 +19,19 @@ pub mod from_env; /// pub mod from_paths; +/// +pub mod rename_section { + /// The error returned by [`File::rename_section(…)`][crate::File::rename_section()]. + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error(transparent)] + Lookup(#[from] crate::lookup::existing::Error), + #[error(transparent)] + Section(#[from] crate::parse::section::header::Error), + } +} + mod access; mod impls; mod utils; diff --git a/git-config/tests/file/access/mod.rs b/git-config/tests/file/access/mod.rs index 7dcd4a6d724..def38b55196 100644 --- a/git-config/tests/file/access/mod.rs +++ b/git-config/tests/file/access/mod.rs @@ -1,3 +1,4 @@ +mod mutating; mod raw; mod read_only; mod write; diff --git a/git-config/tests/file/access/mutating.rs b/git-config/tests/file/access/mutating.rs new file mode 100644 index 00000000000..0c74bd74d80 --- /dev/null +++ b/git-config/tests/file/access/mutating.rs @@ -0,0 +1,22 @@ +mod rename_section { + use git_config::file::rename_section; + use git_config::parse::section; + use std::borrow::Cow; + use std::convert::TryFrom; + + #[test] + fn section_renaming_validates_new_name() { + let mut file = git_config::File::try_from("[core] a = b").unwrap(); + assert!(matches!( + file.rename_section("core", None, "new_core", None), + Err(rename_section::Error::Section(section::header::Error::InvalidName)) + )); + + assert!(matches!( + file.rename_section("core", None, "new-core", Cow::from("a\nb")), + Err(rename_section::Error::Section( + section::header::Error::InvalidSubSection + )) + )); + } +} From 1d8dee2cc501e201c4b8df1580c66f8f9701f7cc Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 12 Jul 2022 17:20:40 +0800 Subject: [PATCH 170/366] remove unused makefile targets and add `nextest` target --- Makefile | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/Makefile b/Makefile index da1459d4906..a45d05926a9 100644 --- a/Makefile +++ b/Makefile @@ -4,17 +4,6 @@ help: ## Display this help always: -##@ Publishing & Versioning - -try-publish-all: ## Dry-run publish all crates in the currently set version if they are not published yet. - (cd cargo-smart-release && cargo build --bin cargo-smart-release) && cargo-smart-release/target/debug/cargo-smart-release smart-release gitoxide - -try-bump-minor-version: ## Show how updating the minor version of PACKAGE= would look like. - (cd cargo-smart-release && cargo build --bin cargo-smart-release) && cargo-smart-release/target/debug/cargo-smart-release smart-release --update-crates-index --bump minor --no-dependencies --no-publish --no-tag --no-push -v $(PACKAGE) - -bump-minor-version: ## Similar to try-bump-minor-version, but actually performs the operation on PACKAGE= - (cd cargo-smart-release && cargo build --bin cargo-smart-release) && cargo-smart-release/target/debug/cargo-smart-release smart-release --update-crates-index --bump minor --no-dependencies --skip-publish --skip-tag --skip-push -v $(PACKAGE) --execute - ##@ Release Builds release-all: release release-lean release-small ## all release builds @@ -67,9 +56,6 @@ clippy: ## Run cargo clippy on all crates check-msrv: ## run cargo msrv to validate the current msrv requirements, similar to what CI does cd git-repository && cargo check --package git-repository --no-default-features --features async-network-client,unstable,local-time-support,max-performance -check-win: ## see that windows compiles, provided the x86_64-pc-windows-msvc target and cargo-xwin are present. - cargo xwin build --target x86_64-pc-windows-msvc --no-default-features --features small - check: ## Build all code in suitable configurations cargo check --all cargo check --no-default-features --features small @@ -169,6 +155,9 @@ unit-tests: ## run all unit tests && cargo test cd gitoxide-core && cargo test --lib +nextest: ## run tests with `cargo nextest` (all unit-tests, no doc-tests, faster) + cargo nextest run --all + continuous-unit-tests: ## run all unit tests whenever something changes watchexec -w src $(MAKE) unit-tests @@ -323,3 +312,9 @@ check-size: ## Run cargo-diet on all crates to see that they are still in bound fmt: ## run nightly rustfmt for its extra features, but check that it won't upset stable rustfmt cargo +nightly fmt --all -- --config-path rustfmt-nightly.toml cargo +stable fmt --all -- --check + +##@ Publishing & Versioning + +try-publish-all: ## Dry-run publish all crates in the currently set version if they are not published yet. + (cd cargo-smart-release && cargo build --bin cargo-smart-release) && cargo-smart-release/target/debug/cargo-smart-release smart-release gitoxide + From a93a156655d640ae63ff7c35b0a1f5d67a5ca20f Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 12 Jul 2022 18:55:06 +0800 Subject: [PATCH 171/366] fix!: Simplify specifying keys when mutating config values. (#331) --- git-config/src/file/access/mutating.rs | 2 +- git-config/src/file/access/raw.rs | 10 ++---- git-config/src/file/access/read_only.rs | 2 +- git-config/src/file/resolve_includes.rs | 3 +- git-config/src/file/section.rs | 16 ++++++--- git-config/src/file/utils.rs | 8 ++--- git-config/src/parse/events.rs | 18 +++++++--- git-config/src/parse/section/header.rs | 4 +-- git-config/src/parse/section/mod.rs | 43 ++++++++++++++++++------ git-config/src/parse/tests.rs | 3 +- git-config/tests/file/access/mutating.rs | 2 ++ git-config/tests/parse/mod.rs | 3 +- git-config/tests/parse/section.rs | 13 ++++--- 13 files changed, 83 insertions(+), 44 deletions(-) diff --git a/git-config/src/file/access/mutating.rs b/git-config/src/file/access/mutating.rs index 188cd8d13d9..50dbe6a0034 100644 --- a/git-config/src/file/access/mutating.rs +++ b/git-config/src/file/access/mutating.rs @@ -52,7 +52,7 @@ impl<'event> File<'event> { /// # use bstr::ByteSlice; /// let mut git_config = git_config::File::default(); /// let mut section = git_config.new_section("hello", Some("world".into()))?; - /// section.push("a".into(), b"b".as_bstr().into()); + /// section.push("a", b"b".as_bstr().into()); /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n a=b\n"); /// let _section = git_config.new_section("core", None); /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n a=b\n[core]\n"); diff --git a/git-config/src/file/access/raw.rs b/git-config/src/file/access/raw.rs index c431fb0303d..0a27b8ea71c 100644 --- a/git-config/src/file/access/raw.rs +++ b/git-config/src/file/access/raw.rs @@ -24,10 +24,9 @@ impl<'event> File<'event> { subsection_name: Option<&str>, key: &str, ) -> Result, lookup::existing::Error> { - let key = section::Key(Cow::::Borrowed(key.into())); let section_ids = self.section_ids_by_name_and_subname(section_name, subsection_name)?; for section_id in section_ids.rev() { - if let Some(v) = self.sections.get(§ion_id).expect("known section id").value(&key) { + if let Some(v) = self.sections.get(§ion_id).expect("known section id").value(key) { return Ok(v); } } @@ -141,12 +140,7 @@ impl<'event> File<'event> { let mut values = Vec::new(); let section_ids = self.section_ids_by_name_and_subname(section_name, subsection_name)?; for section_id in section_ids { - values.extend( - self.sections - .get(§ion_id) - .expect("known section id") - .values(§ion::Key(Cow::::Borrowed(key.into()))), - ); + values.extend(self.sections.get(§ion_id).expect("known section id").values(key)); } if values.is_empty() { diff --git a/git-config/src/file/access/read_only.rs b/git-config/src/file/access/read_only.rs index 99163b19b05..57da8abfa69 100644 --- a/git-config/src/file/access/read_only.rs +++ b/git-config/src/file/access/read_only.rs @@ -216,7 +216,7 @@ impl<'event> File<'event> { /// /// for (i, (header, body)) in config.sections_by_name_with_header("url").unwrap().enumerate() { /// let url = header.subsection_name().unwrap(); - /// let instead_of = body.value(§ion::Key::from("insteadOf")).unwrap(); + /// let instead_of = body.value("insteadOf").unwrap(); /// /// if i == 0 { /// assert_eq!(instead_of.as_ref(), "https://github.com/"); diff --git a/git-config/src/file/resolve_includes.rs b/git-config/src/file/resolve_includes.rs index 88a605eb6a9..9aa536194ff 100644 --- a/git-config/src/file/resolve_includes.rs +++ b/git-config/src/file/resolve_includes.rs @@ -8,7 +8,6 @@ use git_ref::Category; use crate::{ file::{from_paths, from_paths::Options, SectionBodyId}, - parse::section, File, }; @@ -94,7 +93,7 @@ fn extract_include_path( id: SectionBodyId, ) { if let Some(body) = target_config.sections.get(&id) { - let paths = body.values(§ion::Key::from("path")); + let paths = body.values("path"); let paths = paths .iter() .map(|path| crate::Path::from(Cow::Owned(path.as_ref().to_owned()))); diff --git a/git-config/src/file/section.rs b/git-config/src/file/section.rs index 644c1d39d0e..6b29bc871d7 100644 --- a/git-config/src/file/section.rs +++ b/git-config/src/file/section.rs @@ -1,3 +1,4 @@ +use std::convert::TryInto; use std::{ borrow::Cow, iter::FusedIterator, @@ -6,6 +7,7 @@ use std::{ use bstr::{BStr, BString, ByteVec}; +use crate::file::section; use crate::{ file::{Index, Size}, lookup, parse, @@ -25,7 +27,8 @@ pub struct MutableSection<'a, 'event> { impl<'a, 'event> MutableSection<'a, 'event> { /// Adds an entry to the end of this section. // TODO: multi-line handling - maybe just escape it for now. - pub fn push(&mut self, key: Key<'event>, value: Cow<'event, BStr>) { + pub fn push(&mut self, key: impl TryInto>, value: Cow<'event, BStr>) { + let key = key.try_into().unwrap_or_else(|_| todo!("error handline")); if self.whitespace > 0 { self.section.0.push(Event::Whitespace({ let mut s = BString::default(); @@ -249,8 +252,9 @@ impl<'event> SectionBody<'event> { impl<'event> SectionBody<'event> { /// Retrieves the last matching value in a section with the given key, if present. #[must_use] - pub fn value(&self, key: &Key<'_>) -> Option> { - let range = self.value_range_by_key(key); + pub fn value(&self, key: &str) -> Option> { + let key = section::Key::from_str_unchecked(key); + let range = self.value_range_by_key(&key); if range.is_empty() { return None; } @@ -277,7 +281,8 @@ impl<'event> SectionBody<'event> { /// Retrieves all values that have the provided key name. This may return /// an empty vec, which implies there were no values with the provided key. #[must_use] - pub fn values(&self, key: &Key<'_>) -> Vec> { + pub fn values(&self, key: &str) -> Vec> { + let key = §ion::Key::from_str_unchecked(key); let mut values = Vec::new(); let mut expect_value = false; let mut concatenated_value = BString::default(); @@ -313,7 +318,8 @@ impl<'event> SectionBody<'event> { /// Returns true if the section containss the provided key. #[must_use] - pub fn contains_key(&self, key: &Key<'_>) -> bool { + pub fn contains_key(&self, key: &str) -> bool { + let key = §ion::Key::from_str_unchecked(key); self.0.iter().any(|e| { matches!(e, Event::SectionKey(k) if k == key diff --git a/git-config/src/file/utils.rs b/git-config/src/file/utils.rs index c75f8171e13..c3407f75f4c 100644 --- a/git-config/src/file/utils.rs +++ b/git-config/src/file/utils.rs @@ -62,13 +62,13 @@ impl<'event> File<'event> { /// Returns the mapping between section and subsection name to section ids. pub(crate) fn section_ids_by_name_and_subname<'a>( &'a self, - section_name: impl Into>, + section_name: &'a str, subsection_name: Option<&str>, ) -> Result< impl Iterator + ExactSizeIterator + DoubleEndedIterator + '_, lookup::existing::Error, > { - let section_name = section_name.into(); + let section_name = section::Name::from_str_unchecked(section_name); let section_ids = self .section_lookup_tree .get(§ion_name) @@ -98,9 +98,9 @@ impl<'event> File<'event> { pub(crate) fn section_ids_by_name<'a>( &'a self, - section_name: impl Into>, + section_name: &'a str, ) -> Result + '_, lookup::existing::Error> { - let section_name = section_name.into(); + let section_name = section::Name::from_str_unchecked(section_name); match self.section_lookup_tree.get(§ion_name) { Some(lookup) => Ok(lookup.iter().flat_map({ let section_order = &self.section_order; diff --git a/git-config/src/parse/events.rs b/git-config/src/parse/events.rs index 2c77bb79656..768c06769d7 100644 --- a/git-config/src/parse/events.rs +++ b/git-config/src/parse/events.rs @@ -87,18 +87,20 @@ pub type FrontMatterEvents<'a> = SmallVec<[Event<'a>; 8]>; /// ``` /// # use git_config::parse::{Event, Events, section}; /// # use std::borrow::Cow; +/// # use std::convert::TryFrom; /// # let section_header = section::Header::new("core", None).unwrap(); /// # let section_data = "[core]\n autocrlf = input"; /// # assert_eq!(Events::from_str(section_data).unwrap().into_vec(), vec![ /// Event::SectionHeader(section_header), /// Event::Newline(Cow::Borrowed("\n".into())), /// Event::Whitespace(Cow::Borrowed(" ".into())), -/// Event::SectionKey(section::Key(Cow::Borrowed("autocrlf".into()))), +/// Event::SectionKey(section::Key::try_from("autocrlf")?), /// Event::Whitespace(Cow::Borrowed(" ".into())), /// Event::KeyValueSeparator, /// Event::Whitespace(Cow::Borrowed(" ".into())), /// Event::Value(Cow::Borrowed("input".into())), /// # ]); +/// # Ok::<_, Box>(()) /// ``` /// /// Note the two whitespace events between the key and value pair! Those two @@ -122,15 +124,17 @@ pub type FrontMatterEvents<'a> = SmallVec<[Event<'a>; 8]>; /// ``` /// # use git_config::parse::{Event, Events, section}; /// # use std::borrow::Cow; +/// # use std::convert::TryFrom; /// # let section_header = section::Header::new("core", None).unwrap(); /// # let section_data = "[core]\n autocrlf"; /// # assert_eq!(Events::from_str(section_data).unwrap().into_vec(), vec![ /// Event::SectionHeader(section_header), /// Event::Newline(Cow::Borrowed("\n".into())), /// Event::Whitespace(Cow::Borrowed(" ".into())), -/// Event::SectionKey(section::Key(Cow::Borrowed("autocrlf".into()))), +/// Event::SectionKey(section::Key::try_from("autocrlf")?), /// Event::Value(Cow::Borrowed("".into())), /// # ]); +/// # Ok::<_, Box>(()) /// ``` /// /// ## Quoted values are not unquoted @@ -152,19 +156,21 @@ pub type FrontMatterEvents<'a> = SmallVec<[Event<'a>; 8]>; /// ``` /// # use git_config::parse::{Event, Events, section}; /// # use std::borrow::Cow; +/// # use std::convert::TryFrom; /// # let section_header = section::Header::new("core", None).unwrap(); /// # let section_data = "[core]\nautocrlf=true\"\"\nfilemode=fa\"lse\""; /// # assert_eq!(Events::from_str(section_data).unwrap().into_vec(), vec![ /// Event::SectionHeader(section_header), /// Event::Newline(Cow::Borrowed("\n".into())), -/// Event::SectionKey(section::Key(Cow::Borrowed("autocrlf".into()))), +/// Event::SectionKey(section::Key::try_from("autocrlf")?), /// Event::KeyValueSeparator, /// Event::Value(Cow::Borrowed(r#"true"""#.into())), /// Event::Newline(Cow::Borrowed("\n".into())), -/// Event::SectionKey(section::Key(Cow::Borrowed("filemode".into()))), +/// Event::SectionKey(section::Key::try_from("filemode")?), /// Event::KeyValueSeparator, /// Event::Value(Cow::Borrowed(r#"fa"lse""#.into())), /// # ]); +/// # Ok::<_, Box>(()) /// ``` /// /// ## Whitespace after line continuations are part of the value @@ -185,17 +191,19 @@ pub type FrontMatterEvents<'a> = SmallVec<[Event<'a>; 8]>; /// ``` /// # use git_config::parse::{Event, Events, section}; /// # use std::borrow::Cow; +/// # use std::convert::TryFrom; /// # let section_header = section::Header::new("some-section", None).unwrap(); /// # let section_data = "[some-section]\nfile=a\\\n c"; /// # assert_eq!(Events::from_str(section_data).unwrap().into_vec(), vec![ /// Event::SectionHeader(section_header), /// Event::Newline(Cow::Borrowed("\n".into())), -/// Event::SectionKey(section::Key(Cow::Borrowed("file".into()))), +/// Event::SectionKey(section::Key::try_from("file")?), /// Event::KeyValueSeparator, /// Event::ValueNotDone(Cow::Borrowed("a".into())), /// Event::Newline(Cow::Borrowed("\n".into())), /// Event::ValueDone(Cow::Borrowed(" c".into())), /// # ]); +/// # Ok::<_, Box>(()) /// ``` /// /// [`File`]: crate::File diff --git a/git-config/src/parse/section/header.rs b/git-config/src/parse/section/header.rs index 1acbcc02919..31e318386de 100644 --- a/git-config/src/parse/section/header.rs +++ b/git-config/src/parse/section/header.rs @@ -70,7 +70,7 @@ impl Header<'_> { /// Return the name of the header, like "remote" in `[remote "origin"]`. pub fn name(&self) -> &BStr { - self.name.as_ref().as_bstr() + &self.name } /// Serialize this type into a `BString` for convenience. @@ -87,7 +87,7 @@ impl Header<'_> { /// as it was parsed. pub fn write_to(&self, mut out: impl std::io::Write) -> std::io::Result<()> { out.write_all(b"[")?; - out.write_all(self.name.as_ref())?; + out.write_all(&self.name)?; if let (Some(sep), Some(subsection)) = (&self.separator, &self.subsection_name) { let sep = sep.as_ref(); diff --git a/git-config/src/parse/section/mod.rs b/git-config/src/parse/section/mod.rs index 802042d72bb..bb48d70c979 100644 --- a/git-config/src/parse/section/mod.rs +++ b/git-config/src/parse/section/mod.rs @@ -48,12 +48,23 @@ impl Display for Section<'_> { mod types { macro_rules! generate_case_insensitive { - ($name:ident, $cow_inner_type:ty, $comment:literal) => { + ($name:ident, $module:ident, $cow_inner_type:ty, $comment:literal) => { + /// + pub mod $module { + /// The error returned when `TryFrom` is invoked to create an instance. + #[derive(Debug, thiserror::Error, Copy, Clone)] + #[error("Valid names consist alphanumeric characters or dashes.")] + pub struct Error; + } + #[doc = $comment] #[derive(Clone, Eq, Debug, Default)] - pub struct $name<'a>(pub std::borrow::Cow<'a, $cow_inner_type>); + pub struct $name<'a>(pub(crate) std::borrow::Cow<'a, $cow_inner_type>); - impl $name<'_> { + impl<'a> $name<'a> { + pub(crate) fn from_str_unchecked(s: &'a str) -> Self { + $name(std::borrow::Cow::Borrowed(s.into())) + } /// Turn this instance into a fully owned one with `'static` lifetime. #[must_use] pub fn to_owned(&self) -> $name<'static> { @@ -95,15 +106,19 @@ mod types { } } - impl<'a> From<&'a str> for $name<'a> { - fn from(s: &'a str) -> Self { - Self(std::borrow::Cow::Borrowed(s.into())) + impl<'a> std::convert::TryFrom<&'a str> for $name<'a> { + type Error = $module::Error; + + fn try_from(s: &'a str) -> Result { + Ok(Self(std::borrow::Cow::Borrowed(s.into()))) } } - impl<'a> From> for $name<'a> { - fn from(s: std::borrow::Cow<'a, bstr::BStr>) -> Self { - Self(s) + impl<'a> std::convert::TryFrom> for $name<'a> { + type Error = $module::Error; + + fn try_from(s: std::borrow::Cow<'a, bstr::BStr>) -> Result { + Ok(Self(s)) } } @@ -114,21 +129,29 @@ mod types { &self.0 } } + + impl<'a> std::convert::AsRef for $name<'a> { + fn as_ref(&self) -> &str { + std::str::from_utf8(self.0.as_ref()).expect("only valid UTF8 makes it through our validation") + } + } }; } generate_case_insensitive!( Name, + name, bstr::BStr, "Wrapper struct for section header names, like `includeIf`, since these are case-insensitive." ); generate_case_insensitive!( Key, + key, bstr::BStr, "Wrapper struct for key names, like `path` in `include.path`, since keys are case-insensitive." ); } -pub use types::{Key, Name}; +pub use types::{key, name, Key, Name}; pub(crate) fn into_cow_bstr(c: Cow<'_, str>) -> Cow<'_, BStr> { match c { diff --git a/git-config/src/parse/tests.rs b/git-config/src/parse/tests.rs index 673dd9217d1..02494075341 100644 --- a/git-config/src/parse/tests.rs +++ b/git-config/src/parse/tests.rs @@ -52,6 +52,7 @@ pub(crate) mod util { //! functions. use std::borrow::Cow; + use std::convert::TryFrom; use crate::parse::{section, Comment, Event}; @@ -63,7 +64,7 @@ pub(crate) mod util { name: &str, subsection: impl Into>, ) -> section::Header<'_> { - let name = name.into(); + let name = section::Name::try_from(name).unwrap(); if let Some((separator, subsection_name)) = subsection.into() { section::Header { name, diff --git a/git-config/tests/file/access/mutating.rs b/git-config/tests/file/access/mutating.rs index 0c74bd74d80..7f02ace42c0 100644 --- a/git-config/tests/file/access/mutating.rs +++ b/git-config/tests/file/access/mutating.rs @@ -1,3 +1,5 @@ +mod mutable_section {} + mod rename_section { use git_config::file::rename_section; use git_config::parse::section; diff --git a/git-config/tests/parse/mod.rs b/git-config/tests/parse/mod.rs index ea2ac77af3b..34ac2b5b58b 100644 --- a/git-config/tests/parse/mod.rs +++ b/git-config/tests/parse/mod.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::convert::TryFrom; use git_config::parse::{Event, Events, Section}; @@ -48,7 +49,7 @@ fn consecutive_newlines() { } fn name(name: &'static str) -> Event<'static> { - Event::SectionKey(git_config::parse::section::Key(Cow::Borrowed(name.into()))) + Event::SectionKey(git_config::parse::section::Key::try_from(name).unwrap()) } fn value(value: &'static str) -> Event<'static> { diff --git a/git-config/tests/parse/section.rs b/git-config/tests/parse/section.rs index 175e91fb6c0..afb96e2e05b 100644 --- a/git-config/tests/parse/section.rs +++ b/git-config/tests/parse/section.rs @@ -64,16 +64,21 @@ mod header { mod key { use git_config::parse::section::Key; use std::cmp::Ordering; + use std::convert::TryFrom; + + fn key(k: &str) -> Key<'_> { + Key::try_from(k).unwrap() + } #[test] fn case_insentive_eq() { - assert_eq!(Key::from("aBc"), Key::from("AbC")); + assert_eq!(key("aBc"), key("AbC")); } #[test] fn case_insentive_ord() { - assert_eq!(Key::from("a").cmp(&Key::from("a")), Ordering::Equal); - assert_eq!(Key::from("aBc").cmp(&Key::from("AbC")), Ordering::Equal); + assert_eq!(key("a").cmp(&key("a")), Ordering::Equal); + assert_eq!(key("aBc").cmp(&key("AbC")), Ordering::Equal); } #[test] @@ -84,6 +89,6 @@ mod key { t.hash(&mut s); s.finish() } - assert_eq!(calculate_hash(Key::from("aBc")), calculate_hash(Key::from("AbC"))); + assert_eq!(calculate_hash(key("aBc")), calculate_hash(key("AbC"))); } } From 59ec7f7bf019d269573f8cc69f6d34b9458b1f1a Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 12 Jul 2022 22:44:58 +0800 Subject: [PATCH 172/366] validation for Keys and header names (#331) --- git-config/src/file/access/mutating.rs | 3 +- git-config/src/file/section.rs | 39 +++++++++++------------- git-config/src/parse/section/mod.rs | 24 ++++++++++++--- git-config/tests/file/access/mutating.rs | 16 +++++++++- git-config/tests/parse/section.rs | 29 +++++++++++++++++- 5 files changed, 83 insertions(+), 28 deletions(-) diff --git a/git-config/src/file/access/mutating.rs b/git-config/src/file/access/mutating.rs index 50dbe6a0034..3ddf27e0cdd 100644 --- a/git-config/src/file/access/mutating.rs +++ b/git-config/src/file/access/mutating.rs @@ -50,9 +50,10 @@ impl<'event> File<'event> { /// # use git_config::File; /// # use std::convert::TryFrom; /// # use bstr::ByteSlice; + /// # use git_config::parse::section; /// let mut git_config = git_config::File::default(); /// let mut section = git_config.new_section("hello", Some("world".into()))?; - /// section.push("a", b"b".as_bstr().into()); + /// section.push(section::Key::try_from("a")?, b"b".as_bstr().into()); /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n a=b\n"); /// let _section = git_config.new_section("core", None); /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n a=b\n[core]\n"); diff --git a/git-config/src/file/section.rs b/git-config/src/file/section.rs index 6b29bc871d7..df0698e7aec 100644 --- a/git-config/src/file/section.rs +++ b/git-config/src/file/section.rs @@ -1,4 +1,3 @@ -use std::convert::TryInto; use std::{ borrow::Cow, iter::FusedIterator, @@ -7,7 +6,6 @@ use std::{ use bstr::{BStr, BString, ByteVec}; -use crate::file::section; use crate::{ file::{Index, Size}, lookup, parse, @@ -27,8 +25,7 @@ pub struct MutableSection<'a, 'event> { impl<'a, 'event> MutableSection<'a, 'event> { /// Adds an entry to the end of this section. // TODO: multi-line handling - maybe just escape it for now. - pub fn push(&mut self, key: impl TryInto>, value: Cow<'event, BStr>) { - let key = key.try_into().unwrap_or_else(|_| todo!("error handline")); + pub fn push(&mut self, key: Key<'event>, value: Cow<'event, BStr>) { if self.whitespace > 0 { self.section.0.push(Event::Whitespace({ let mut s = BString::default(); @@ -48,7 +45,7 @@ impl<'a, 'event> MutableSection<'a, 'event> { /// Removes all events until a key value pair is removed. This will also /// remove the whitespace preceding the key value pair, if any is found. pub fn pop(&mut self) -> Option<(Key<'_>, Cow<'event, BStr>)> { - let mut values = vec![]; + let mut values = Vec::new(); // events are popped in reverse order while let Some(e) = self.section.0.pop() { match e { @@ -105,19 +102,6 @@ impl<'a, 'event> MutableSection<'a, 'event> { Some(self.remove_internal(range)) } - /// Performs the removal, assuming the range is valid. - fn remove_internal(&mut self, range: Range) -> Cow<'event, BStr> { - self.section - .0 - .drain(range) - .fold(Cow::Owned(BString::default()), |mut acc, e| { - if let Event::Value(v) | Event::ValueNotDone(v) | Event::ValueDone(v) = e { - acc.to_mut().extend(&**v); - } - acc - }) - } - /// Adds a new line event. Note that you don't need to call this unless /// you've disabled implicit newlines. pub fn push_newline(&mut self) { @@ -193,6 +177,19 @@ impl<'a, 'event> MutableSection<'a, 'event> { Size(3) } + + /// Performs the removal, assuming the range is valid. + fn remove_internal(&mut self, range: Range) -> Cow<'event, BStr> { + self.section + .0 + .drain(range) + .fold(Cow::Owned(BString::default()), |mut acc, e| { + if let Event::Value(v) | Event::ValueNotDone(v) | Event::ValueDone(v) = e { + acc.to_mut().extend(&**v); + } + acc + }) + } } impl<'event> Deref for MutableSection<'_, 'event> { @@ -253,7 +250,7 @@ impl<'event> SectionBody<'event> { /// Retrieves the last matching value in a section with the given key, if present. #[must_use] pub fn value(&self, key: &str) -> Option> { - let key = section::Key::from_str_unchecked(key); + let key = Key::from_str_unchecked(key); let range = self.value_range_by_key(&key); if range.is_empty() { return None; @@ -282,7 +279,7 @@ impl<'event> SectionBody<'event> { /// an empty vec, which implies there were no values with the provided key. #[must_use] pub fn values(&self, key: &str) -> Vec> { - let key = §ion::Key::from_str_unchecked(key); + let key = &Key::from_str_unchecked(key); let mut values = Vec::new(); let mut expect_value = false; let mut concatenated_value = BString::default(); @@ -319,7 +316,7 @@ impl<'event> SectionBody<'event> { /// Returns true if the section containss the provided key. #[must_use] pub fn contains_key(&self, key: &str) -> bool { - let key = §ion::Key::from_str_unchecked(key); + let key = &Key::from_str_unchecked(key); self.0.iter().any(|e| { matches!(e, Event::SectionKey(k) if k == key diff --git a/git-config/src/parse/section/mod.rs b/git-config/src/parse/section/mod.rs index bb48d70c979..cced60dca1c 100644 --- a/git-config/src/parse/section/mod.rs +++ b/git-config/src/parse/section/mod.rs @@ -48,12 +48,12 @@ impl Display for Section<'_> { mod types { macro_rules! generate_case_insensitive { - ($name:ident, $module:ident, $cow_inner_type:ty, $comment:literal) => { + ($name:ident, $module:ident, $err_doc:literal, $validate:ident, $cow_inner_type:ty, $comment:literal) => { /// pub mod $module { /// The error returned when `TryFrom` is invoked to create an instance. #[derive(Debug, thiserror::Error, Copy, Clone)] - #[error("Valid names consist alphanumeric characters or dashes.")] + #[error($err_doc)] pub struct Error; } @@ -110,7 +110,7 @@ mod types { type Error = $module::Error; fn try_from(s: &'a str) -> Result { - Ok(Self(std::borrow::Cow::Borrowed(s.into()))) + Self::try_from(std::borrow::Cow::Borrowed(bstr::ByteSlice::as_bstr(s.as_bytes()))) } } @@ -118,7 +118,11 @@ mod types { type Error = $module::Error; fn try_from(s: std::borrow::Cow<'a, bstr::BStr>) -> Result { - Ok(Self(s)) + if $validate(s.as_ref()) { + Ok(Self(s)) + } else { + Err($module::Error) + } } } @@ -137,9 +141,19 @@ mod types { } }; } + + fn is_valid_name(n: &bstr::BStr) -> bool { + !n.is_empty() && n.iter().all(|b| b.is_ascii_alphanumeric() || *b == b'-') + } + fn is_valid_key(n: &bstr::BStr) -> bool { + is_valid_name(n) && n[0].is_ascii_alphabetic() + } + generate_case_insensitive!( Name, name, + "Valid names consist alphanumeric characters or dashes.", + is_valid_name, bstr::BStr, "Wrapper struct for section header names, like `includeIf`, since these are case-insensitive." ); @@ -147,6 +161,8 @@ mod types { generate_case_insensitive!( Key, key, + "Valid keys consist alphanumeric characters or dashes, starting with an alphabetic character.", + is_valid_key, bstr::BStr, "Wrapper struct for key names, like `path` in `include.path`, since keys are case-insensitive." ); diff --git a/git-config/tests/file/access/mutating.rs b/git-config/tests/file/access/mutating.rs index 7f02ace42c0..d7c8c9935f1 100644 --- a/git-config/tests/file/access/mutating.rs +++ b/git-config/tests/file/access/mutating.rs @@ -1,4 +1,18 @@ -mod mutable_section {} +mod mutable_section { + mod push { + use git_config::parse::section::Key; + use std::borrow::Cow; + use std::convert::TryFrom; + + #[test] + fn push_splits_values_into_events() { + let mut file = git_config::File::default(); + let mut section = file.new_section("core", None).unwrap(); + section.push(Key::try_from("value").unwrap(), Cow::Borrowed("none".into())); + assert_eq!(file.to_bstring(), "[core]\n value=none\n"); + } + } +} mod rename_section { use git_config::file::rename_section; diff --git a/git-config/tests/parse/section.rs b/git-config/tests/parse/section.rs index afb96e2e05b..4a5eb3bfccc 100644 --- a/git-config/tests/parse/section.rs +++ b/git-config/tests/parse/section.rs @@ -60,6 +60,24 @@ mod header { } } } +mod name { + use git_config::parse::section::Name; + use std::convert::TryFrom; + + #[test] + fn alphanum_and_dash_are_valid() { + assert!(Name::try_from("1a").is_ok()); + assert!(Name::try_from("Hello-World").is_ok()); + } + + #[test] + fn rejects_invalid_format() { + assert!(Name::try_from("").is_err()); + assert!(Name::try_from("a.2").is_err()); + assert!(Name::try_from("\"").is_err()); + assert!(Name::try_from("##").is_err()); + } +} mod key { use git_config::parse::section::Key; @@ -70,9 +88,18 @@ mod key { Key::try_from(k).unwrap() } + #[test] + fn rejects_invalid_format() { + assert!(Key::try_from("").is_err()); + assert!(Key::try_from("1a").is_err()); + assert!(Key::try_from("a.2").is_err()); + assert!(Key::try_from("##").is_err()); + assert!(Key::try_from("\"").is_err()); + } + #[test] fn case_insentive_eq() { - assert_eq!(key("aBc"), key("AbC")); + assert_eq!(key("aB-c"), key("Ab-C")); } #[test] From ee9ac953180886cc483e1125b7f4e172af92c3ce Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 13 Jul 2022 08:10:36 +0800 Subject: [PATCH 173/366] auto-compute whitespace for sections, even though it probably needs to be better than that (#331) I am thinking, taking the whitespace directly will be more faithful, i.e. to handle tabs vs spaces which we can't decide. --- git-config/src/file/access/mutating.rs | 4 ++-- git-config/src/file/section.rs | 24 +++++++++++++++++++++++- git-config/tests/file/access/mutating.rs | 2 +- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/git-config/src/file/access/mutating.rs b/git-config/src/file/access/mutating.rs index 3ddf27e0cdd..54b1ea597dc 100644 --- a/git-config/src/file/access/mutating.rs +++ b/git-config/src/file/access/mutating.rs @@ -54,9 +54,9 @@ impl<'event> File<'event> { /// let mut git_config = git_config::File::default(); /// let mut section = git_config.new_section("hello", Some("world".into()))?; /// section.push(section::Key::try_from("a")?, b"b".as_bstr().into()); - /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n a=b\n"); + /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n a=b\n"); /// let _section = git_config.new_section("core", None); - /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n a=b\n[core]\n"); + /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n a=b\n[core]\n"); /// # Ok::<(), Box>(()) /// ``` pub fn new_section( diff --git a/git-config/src/file/section.rs b/git-config/src/file/section.rs index df0698e7aec..15b2165319e 100644 --- a/git-config/src/file/section.rs +++ b/git-config/src/file/section.rs @@ -132,10 +132,11 @@ impl<'a, 'event> MutableSection<'a, 'event> { // Internal methods that may require exact indices for faster operations. impl<'a, 'event> MutableSection<'a, 'event> { pub(crate) fn new(section: &'a mut SectionBody<'event>) -> Self { + let whitespace = compute_whitespace(section); Self { section, implicit_newline: true, - whitespace: 2, + whitespace, } } @@ -192,6 +193,27 @@ impl<'a, 'event> MutableSection<'a, 'event> { } } +fn compute_whitespace(s: &mut SectionBody<'_>) -> usize { + let mut saw_events = false; + let computed = + s.0.iter() + .take_while(|e| matches!(e, Event::Whitespace(_))) + .inspect(|_| saw_events = true) + .map(|e| match e { + Event::Whitespace(s) => s + .iter() + .filter_map(|b| match *b { + b'\t' => Some(4), + b' ' => Some(1), + _ => None, + }) + .sum::(), + _ => unreachable!(), + }) + .sum(); + saw_events.then(|| computed).unwrap_or(8) +} + impl<'event> Deref for MutableSection<'_, 'event> { type Target = SectionBody<'event>; diff --git a/git-config/tests/file/access/mutating.rs b/git-config/tests/file/access/mutating.rs index d7c8c9935f1..7a027f0c3e6 100644 --- a/git-config/tests/file/access/mutating.rs +++ b/git-config/tests/file/access/mutating.rs @@ -9,7 +9,7 @@ mod mutable_section { let mut file = git_config::File::default(); let mut section = file.new_section("core", None).unwrap(); section.push(Key::try_from("value").unwrap(), Cow::Borrowed("none".into())); - assert_eq!(file.to_bstring(), "[core]\n value=none\n"); + assert_eq!(file.to_bstring(), "[core]\n value=none\n"); } } } From a0d6caa243aa293386d4ad164e1604f0e71c2cf3 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 13 Jul 2022 08:16:40 +0800 Subject: [PATCH 174/366] refactor (#331) --- git-config/src/file/access/mod.rs | 2 +- .../src/file/access/{mutating.rs => mutate.rs} | 0 git-config/src/file/access/raw.rs | 3 ++- git-config/src/file/mod.rs | 9 +++++---- git-config/src/file/mutable/mod.rs | 2 ++ git-config/src/file/{ => mutable}/section.rs | 0 git-config/src/file/{ => mutable}/value.rs | 6 ++---- git-config/tests/file/access/mod.rs | 2 +- .../tests/file/access/{mutating.rs => mutate.rs} | 16 ---------------- git-config/tests/file/mod.rs | 1 + git-config/tests/file/mutable.rs | 15 +++++++++++++++ 11 files changed, 29 insertions(+), 27 deletions(-) rename git-config/src/file/access/{mutating.rs => mutate.rs} (100%) create mode 100644 git-config/src/file/mutable/mod.rs rename git-config/src/file/{ => mutable}/section.rs (100%) rename git-config/src/file/{ => mutable}/value.rs (99%) rename git-config/tests/file/access/{mutating.rs => mutate.rs} (58%) create mode 100644 git-config/tests/file/mutable.rs diff --git a/git-config/src/file/access/mod.rs b/git-config/src/file/access/mod.rs index 8b3dc0c69ad..1640081a16e 100644 --- a/git-config/src/file/access/mod.rs +++ b/git-config/src/file/access/mod.rs @@ -1,5 +1,5 @@ mod comfort; -mod mutating; +mod mutate; mod raw; mod read_only; mod write; diff --git a/git-config/src/file/access/mutating.rs b/git-config/src/file/access/mutate.rs similarity index 100% rename from git-config/src/file/access/mutating.rs rename to git-config/src/file/access/mutate.rs diff --git a/git-config/src/file/access/raw.rs b/git-config/src/file/access/raw.rs index 0a27b8ea71c..cb01dc3eb87 100644 --- a/git-config/src/file/access/raw.rs +++ b/git-config/src/file/access/raw.rs @@ -2,8 +2,9 @@ use std::{borrow::Cow, collections::HashMap}; use bstr::{BStr, BString}; +use crate::file::mutable::value::EntryData; use crate::{ - file::{value::EntryData, Index, MutableMultiValue, MutableSection, MutableValue, Size}, + file::{Index, MutableMultiValue, MutableSection, MutableValue, Size}, lookup, parse::{section, Event}, File, diff --git a/git-config/src/file/mod.rs b/git-config/src/file/mod.rs index 9f6e9251f7e..cea650c08e8 100644 --- a/git-config/src/file/mod.rs +++ b/git-config/src/file/mod.rs @@ -7,11 +7,12 @@ use std::{ use bstr::BStr; -mod section; -pub use section::{MutableSection, SectionBody, SectionBodyIter}; +mod mutable; -mod value; -pub use value::{MutableMultiValue, MutableValue}; +pub use mutable::{ + section::{MutableSection, SectionBody, SectionBodyIter}, + value::{MutableMultiValue, MutableValue}, +}; /// pub mod from_env; diff --git a/git-config/src/file/mutable/mod.rs b/git-config/src/file/mutable/mod.rs new file mode 100644 index 00000000000..0da513dea9f --- /dev/null +++ b/git-config/src/file/mutable/mod.rs @@ -0,0 +1,2 @@ +pub(crate) mod section; +pub(crate) mod value; diff --git a/git-config/src/file/section.rs b/git-config/src/file/mutable/section.rs similarity index 100% rename from git-config/src/file/section.rs rename to git-config/src/file/mutable/section.rs diff --git a/git-config/src/file/value.rs b/git-config/src/file/mutable/value.rs similarity index 99% rename from git-config/src/file/value.rs rename to git-config/src/file/mutable/value.rs index 4dada813b17..20e872fe145 100644 --- a/git-config/src/file/value.rs +++ b/git-config/src/file/mutable/value.rs @@ -2,11 +2,9 @@ use std::{borrow::Cow, collections::HashMap, ops::DerefMut}; use bstr::{BStr, BString, ByteVec}; +use crate::file::mutable::section::{MutableSection, SectionBody}; use crate::{ - file::{ - section::{MutableSection, SectionBody}, - Index, SectionBodyId, Size, - }, + file::{Index, SectionBodyId, Size}, lookup, parse::{section, Event}, value::{normalize_bstr, normalize_bstring}, diff --git a/git-config/tests/file/access/mod.rs b/git-config/tests/file/access/mod.rs index def38b55196..cebb3dfa59b 100644 --- a/git-config/tests/file/access/mod.rs +++ b/git-config/tests/file/access/mod.rs @@ -1,4 +1,4 @@ -mod mutating; +mod mutate; mod raw; mod read_only; mod write; diff --git a/git-config/tests/file/access/mutating.rs b/git-config/tests/file/access/mutate.rs similarity index 58% rename from git-config/tests/file/access/mutating.rs rename to git-config/tests/file/access/mutate.rs index 7a027f0c3e6..0c74bd74d80 100644 --- a/git-config/tests/file/access/mutating.rs +++ b/git-config/tests/file/access/mutate.rs @@ -1,19 +1,3 @@ -mod mutable_section { - mod push { - use git_config::parse::section::Key; - use std::borrow::Cow; - use std::convert::TryFrom; - - #[test] - fn push_splits_values_into_events() { - let mut file = git_config::File::default(); - let mut section = file.new_section("core", None).unwrap(); - section.push(Key::try_from("value").unwrap(), Cow::Borrowed("none".into())); - assert_eq!(file.to_bstring(), "[core]\n value=none\n"); - } - } -} - mod rename_section { use git_config::file::rename_section; use git_config::parse::section; diff --git a/git-config/tests/file/mod.rs b/git-config/tests/file/mod.rs index f5709f2596e..15a2b4f636b 100644 --- a/git-config/tests/file/mod.rs +++ b/git-config/tests/file/mod.rs @@ -30,3 +30,4 @@ mod access; mod from_env; mod from_paths; mod impls; +mod mutable; diff --git a/git-config/tests/file/mutable.rs b/git-config/tests/file/mutable.rs new file mode 100644 index 00000000000..474831d920c --- /dev/null +++ b/git-config/tests/file/mutable.rs @@ -0,0 +1,15 @@ +mod section { + mod push { + use git_config::parse::section::Key; + use std::borrow::Cow; + use std::convert::TryFrom; + + #[test] + fn push_splits_values_into_events() { + let mut file = git_config::File::default(); + let mut section = file.new_section("core", None).unwrap(); + section.push(Key::try_from("value").unwrap(), Cow::Borrowed("none".into())); + assert_eq!(file.to_bstring(), "[core]\n value=none\n"); + } + } +} From c88eea87d7ece807ca5b1753b47ce89d3ad6a502 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 13 Jul 2022 08:22:07 +0800 Subject: [PATCH 175/366] refactor (#331) --- git-config/src/file/{ => init}/from_env.rs | 8 ++------ git-config/src/file/{ => init}/from_paths.rs | 2 +- git-config/src/file/init/mod.rs | 7 +++++++ git-config/src/file/{ => init}/resolve_includes.rs | 7 +++---- git-config/src/file/mod.rs | 10 ++-------- git-config/src/fs.rs | 6 ++---- .../tests/file/{from_env/mod.rs => init/from_env.rs} | 9 ++++----- .../from_paths/includes/conditional/gitdir/mod.rs | 8 ++++---- .../from_paths/includes/conditional/gitdir/util.rs | 8 +++----- .../{ => init}/from_paths/includes/conditional/mod.rs | 6 ++++-- .../from_paths/includes/conditional/onbranch.rs | 0 .../{ => init}/from_paths/includes/unconditional.rs | 6 ++++-- git-config/tests/file/{ => init}/from_paths/mod.rs | 0 git-config/tests/file/init/mod.rs | 2 ++ git-config/tests/file/mod.rs | 3 +-- 15 files changed, 39 insertions(+), 43 deletions(-) rename git-config/src/file/{ => init}/from_env.rs (97%) rename git-config/src/file/{ => init}/from_paths.rs (97%) create mode 100644 git-config/src/file/init/mod.rs rename git-config/src/file/{ => init}/resolve_includes.rs (98%) rename git-config/tests/file/{from_env/mod.rs => init/from_env.rs} (95%) rename git-config/tests/file/{ => init}/from_paths/includes/conditional/gitdir/mod.rs (96%) rename git-config/tests/file/{ => init}/from_paths/includes/conditional/gitdir/util.rs (96%) rename git-config/tests/file/{ => init}/from_paths/includes/conditional/mod.rs (93%) rename git-config/tests/file/{ => init}/from_paths/includes/conditional/onbranch.rs (100%) rename git-config/tests/file/{ => init}/from_paths/includes/unconditional.rs (98%) rename git-config/tests/file/{ => init}/from_paths/mod.rs (100%) create mode 100644 git-config/tests/file/init/mod.rs diff --git a/git-config/src/file/from_env.rs b/git-config/src/file/init/from_env.rs similarity index 97% rename from git-config/src/file/from_env.rs rename to git-config/src/file/init/from_env.rs index 0c82918b50e..317ebf98b63 100644 --- a/git-config/src/file/from_env.rs +++ b/git-config/src/file/init/from_env.rs @@ -2,12 +2,8 @@ use std::{borrow::Cow, path::PathBuf}; use bstr::BString; -use crate::{ - file::{from_paths, resolve_includes}, - parse::section, - path::interpolate, - File, -}; +use crate::file::from_paths; +use crate::{file::init::resolve_includes, parse::section, path::interpolate, File}; /// Represents the errors that may occur when calling [`File::from_env`][crate::File::from_env()]. #[derive(Debug, thiserror::Error)] diff --git a/git-config/src/file/from_paths.rs b/git-config/src/file/init/from_paths.rs similarity index 97% rename from git-config/src/file/from_paths.rs rename to git-config/src/file/init/from_paths.rs index 905890eb0b5..2a3786351c6 100644 --- a/git-config/src/file/from_paths.rs +++ b/git-config/src/file/init/from_paths.rs @@ -1,4 +1,4 @@ -use crate::{file::resolve_includes, parse, path::interpolate, File}; +use crate::{file::init::resolve_includes, parse, path::interpolate, File}; /// The error returned by [`File::from_paths()`][crate::File::from_paths()] and [`File::from_env_paths()`][crate::File::from_env_paths()]. #[derive(Debug, thiserror::Error)] diff --git a/git-config/src/file/init/mod.rs b/git-config/src/file/init/mod.rs new file mode 100644 index 00000000000..76024ca5e73 --- /dev/null +++ b/git-config/src/file/init/mod.rs @@ -0,0 +1,7 @@ +/// +pub mod from_env; +/// +pub mod from_paths; + +mod resolve_includes; +pub(crate) use resolve_includes::resolve_includes; diff --git a/git-config/src/file/resolve_includes.rs b/git-config/src/file/init/resolve_includes.rs similarity index 98% rename from git-config/src/file/resolve_includes.rs rename to git-config/src/file/init/resolve_includes.rs index 9aa536194ff..a6dc59e0124 100644 --- a/git-config/src/file/resolve_includes.rs +++ b/git-config/src/file/init/resolve_includes.rs @@ -6,10 +6,9 @@ use std::{ use bstr::{BStr, BString, ByteSlice, ByteVec}; use git_ref::Category; -use crate::{ - file::{from_paths, from_paths::Options, SectionBodyId}, - File, -}; +use crate::file::init::from_paths; +use crate::file::init::from_paths::Options; +use crate::{file::SectionBodyId, File}; pub(crate) fn resolve_includes( conf: &mut File<'static>, diff --git a/git-config/src/file/mod.rs b/git-config/src/file/mod.rs index cea650c08e8..1bfc2bbb832 100644 --- a/git-config/src/file/mod.rs +++ b/git-config/src/file/mod.rs @@ -14,11 +14,8 @@ pub use mutable::{ value::{MutableMultiValue, MutableValue}, }; -/// -pub mod from_env; - -/// -pub mod from_paths; +mod init; +pub use init::{from_env, from_paths}; /// pub mod rename_section { @@ -37,9 +34,6 @@ mod access; mod impls; mod utils; -mod resolve_includes; -pub(crate) use resolve_includes::resolve_includes; - /// A strongly typed index into some range. #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Clone, Copy)] pub(crate) struct Index(pub(crate) usize); diff --git a/git-config/src/fs.rs b/git-config/src/fs.rs index cb5f7ce5d4c..1a11e1a21cf 100644 --- a/git-config/src/fs.rs +++ b/git-config/src/fs.rs @@ -10,10 +10,8 @@ use std::{ use bstr::BStr; -use crate::{ - file::{from_env, from_paths}, - lookup, File, -}; +use crate::file::{from_env, from_paths}; +use crate::{lookup, File}; // TODO: how does this relate to `File::from_env_paths()`? #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] diff --git a/git-config/tests/file/from_env/mod.rs b/git-config/tests/file/init/from_env.rs similarity index 95% rename from git-config/tests/file/from_env/mod.rs rename to git-config/tests/file/init/from_env.rs index 5930adc96b1..c4f3be1f5e2 100644 --- a/git-config/tests/file/from_env/mod.rs +++ b/git-config/tests/file/init/from_env.rs @@ -1,13 +1,12 @@ use std::{borrow::Cow, env, fs}; -use git_config::{ - file::{from_env, from_paths, from_paths::Options}, - File, -}; +use git_config::file::from_paths::Options; +use git_config::file::{from_env, from_paths}; +use git_config::File; use serial_test::serial; use tempfile::tempdir; -use crate::file::from_paths::escape_backslashes; +use crate::file::init::from_paths::escape_backslashes; pub struct Env<'a> { altered_vars: Vec<&'a str>, diff --git a/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs b/git-config/tests/file/init/from_paths/includes/conditional/gitdir/mod.rs similarity index 96% rename from git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs rename to git-config/tests/file/init/from_paths/includes/conditional/gitdir/mod.rs index cef4f2cb81e..07bc12f8e8f 100644 --- a/git-config/tests/file/from_paths/includes/conditional/gitdir/mod.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/gitdir/mod.rs @@ -3,7 +3,7 @@ mod util; use serial_test::serial; use util::{assert_section_value, Condition, GitEnv}; -use crate::file::from_paths::escape_backslashes; +use crate::file::init::from_paths::escape_backslashes; #[test] fn relative_path_with_trailing_slash_matches_like_star_star() -> crate::Result { @@ -91,7 +91,7 @@ fn dot_slash_from_environment_causes_error() -> crate::Result { let env = GitEnv::repo_name("worktree")?; { - let _environment = crate::file::from_env::Env::new() + let _environment = crate::file::init::from_env::Env::new() .set("GIT_CONFIG_COUNT", "1") .set( "GIT_CONFIG_KEY_0", @@ -113,7 +113,7 @@ fn dot_slash_from_environment_causes_error() -> crate::Result { let absolute_path = escape_backslashes(env.home_dir().join("include.config")); { - let _environment = crate::file::from_env::Env::new() + let _environment = crate::file::init::from_env::Env::new() .set("GIT_CONFIG_COUNT", "1") .set("GIT_CONFIG_KEY_0", "includeIf.gitdir:./worktree/.path") .set("GIT_CONFIG_VALUE_0", &absolute_path); @@ -131,7 +131,7 @@ fn dot_slash_from_environment_causes_error() -> crate::Result { } { - let _environment = crate::file::from_env::Env::new() + let _environment = crate::file::init::from_env::Env::new() .set("GIT_CONFIG_COUNT", "1") .set( "GIT_CONFIG_KEY_0", diff --git a/git-config/tests/file/from_paths/includes/conditional/gitdir/util.rs b/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs similarity index 96% rename from git-config/tests/file/from_paths/includes/conditional/gitdir/util.rs rename to git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs index 04a471abb06..4a467f2934d 100644 --- a/git-config/tests/file/from_paths/includes/conditional/gitdir/util.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs @@ -8,10 +8,8 @@ use std::{ use bstr::{BString, ByteSlice}; -use crate::file::{ - cow_str, - from_paths::{escape_backslashes, includes::conditional::options_with_git_dir}, -}; +use crate::file::cow_str; +use crate::file::init::from_paths::{escape_backslashes, includes::conditional::options_with_git_dir}; #[derive(Debug)] pub struct GitEnv { @@ -141,7 +139,7 @@ pub fn assert_section_value( pub fn git_env_with_symlinked_repo() -> crate::Result { let mut env = GitEnv::repo_name("worktree")?; let link_destination = env.root_dir().join("symlink-worktree"); - crate::file::from_paths::includes::conditional::create_symlink(&link_destination, env.worktree_dir()); + crate::file::init::from_paths::includes::conditional::create_symlink(&link_destination, env.worktree_dir()); let git_dir_through_symlink = link_destination.join(".git"); env.set_git_dir(git_dir_through_symlink); diff --git a/git-config/tests/file/from_paths/includes/conditional/mod.rs b/git-config/tests/file/init/from_paths/includes/conditional/mod.rs similarity index 93% rename from git-config/tests/file/from_paths/includes/conditional/mod.rs rename to git-config/tests/file/init/from_paths/includes/conditional/mod.rs index 1d6d2bd8fad..2643e31169f 100644 --- a/git-config/tests/file/from_paths/includes/conditional/mod.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/mod.rs @@ -1,9 +1,11 @@ use std::{fs, path::Path}; -use git_config::{file::from_paths, path, File}; +use git_config::file::from_paths; +use git_config::{path, File}; use tempfile::tempdir; -use crate::file::{cow_str, from_paths::escape_backslashes}; +use crate::file::cow_str; +use crate::file::init::from_paths::escape_backslashes; mod gitdir; mod onbranch; diff --git a/git-config/tests/file/from_paths/includes/conditional/onbranch.rs b/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs similarity index 100% rename from git-config/tests/file/from_paths/includes/conditional/onbranch.rs rename to git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs diff --git a/git-config/tests/file/from_paths/includes/unconditional.rs b/git-config/tests/file/init/from_paths/includes/unconditional.rs similarity index 98% rename from git-config/tests/file/from_paths/includes/unconditional.rs rename to git-config/tests/file/init/from_paths/includes/unconditional.rs index 8f2e4bfe55d..b9440a6db03 100644 --- a/git-config/tests/file/from_paths/includes/unconditional.rs +++ b/git-config/tests/file/init/from_paths/includes/unconditional.rs @@ -1,9 +1,11 @@ use std::fs; -use git_config::{file::from_paths, File}; +use git_config::file::from_paths; +use git_config::File; use tempfile::tempdir; -use crate::file::{cow_str, from_paths::escape_backslashes}; +use crate::file::cow_str; +use crate::file::init::from_paths::escape_backslashes; #[test] fn multiple() -> crate::Result { diff --git a/git-config/tests/file/from_paths/mod.rs b/git-config/tests/file/init/from_paths/mod.rs similarity index 100% rename from git-config/tests/file/from_paths/mod.rs rename to git-config/tests/file/init/from_paths/mod.rs diff --git a/git-config/tests/file/init/mod.rs b/git-config/tests/file/init/mod.rs new file mode 100644 index 00000000000..df0e32fe006 --- /dev/null +++ b/git-config/tests/file/init/mod.rs @@ -0,0 +1,2 @@ +pub mod from_env; +mod from_paths; diff --git a/git-config/tests/file/mod.rs b/git-config/tests/file/mod.rs index 15a2b4f636b..c016f0be7e5 100644 --- a/git-config/tests/file/mod.rs +++ b/git-config/tests/file/mod.rs @@ -27,7 +27,6 @@ mod open { } mod access; -mod from_env; -mod from_paths; mod impls; +mod init; mod mutable; From 9157717c2fb143b5decbdf60d18cc2bd99dde775 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 13 Jul 2022 08:34:01 +0800 Subject: [PATCH 176/366] feat: whitespace in mutable sections can be finely controlled, and is derived from existing sections (#331) --- git-config/src/file/mutable/section.rs | 37 +++++++++++++------------- git-config/src/parse/nom/mod.rs | 4 +-- git-config/src/parse/nom/tests.rs | 10 +++---- 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/git-config/src/file/mutable/section.rs b/git-config/src/file/mutable/section.rs index 15b2165319e..b18dd63eb70 100644 --- a/git-config/src/file/mutable/section.rs +++ b/git-config/src/file/mutable/section.rs @@ -18,7 +18,7 @@ use crate::{ pub struct MutableSection<'a, 'event> { section: &'a mut SectionBody<'event>, implicit_newline: bool, - whitespace: usize, + whitespace: Option>, } /// Mutating methods. @@ -26,12 +26,8 @@ impl<'a, 'event> MutableSection<'a, 'event> { /// Adds an entry to the end of this section. // TODO: multi-line handling - maybe just escape it for now. pub fn push(&mut self, key: Key<'event>, value: Cow<'event, BStr>) { - if self.whitespace > 0 { - self.section.0.push(Event::Whitespace({ - let mut s = BString::default(); - s.extend(std::iter::repeat(b' ').take(self.whitespace)); - s.into() - })); + if let Some(ws) = &self.whitespace { + self.section.0.push(Event::Whitespace(ws.clone())); } self.section.0.push(Event::SectionKey(key)); @@ -114,25 +110,25 @@ impl<'a, 'event> MutableSection<'a, 'event> { self.implicit_newline = on; } - /// Sets the number of spaces before the start of a key value. The _default - /// is 2_. Set to 0 to disable adding whitespace before a key - /// value. - pub fn set_leading_space(&mut self, num: usize) { - self.whitespace = num; + /// Sets the exact whitespace to use before each key-value pair. + /// The default is 2 tabs. + /// Set to `None` to disable adding whitespace before a key value. + pub fn set_leading_space(&mut self, whitespace: Option>) { + self.whitespace = whitespace; } - /// Returns the number of space characters this section will insert before the - /// beginning of a key. + /// Returns the whitespace this section will insert before the + /// beginning of a key, if any. #[must_use] - pub const fn leading_space(&self) -> usize { - self.whitespace + pub fn leading_space(&self) -> Option<&BStr> { + self.whitespace.as_deref() } } // Internal methods that may require exact indices for faster operations. impl<'a, 'event> MutableSection<'a, 'event> { pub(crate) fn new(section: &'a mut SectionBody<'event>) -> Self { - let whitespace = compute_whitespace(section); + let whitespace = Some(compute_whitespace(section)); Self { section, implicit_newline: true, @@ -193,7 +189,7 @@ impl<'a, 'event> MutableSection<'a, 'event> { } } -fn compute_whitespace(s: &mut SectionBody<'_>) -> usize { +fn compute_whitespace<'event>(s: &mut SectionBody<'event>) -> Cow<'event, BStr> { let mut saw_events = false; let computed = s.0.iter() @@ -211,7 +207,10 @@ fn compute_whitespace(s: &mut SectionBody<'_>) -> usize { _ => unreachable!(), }) .sum(); - saw_events.then(|| computed).unwrap_or(8) + + let mut buf = BString::default(); + buf.extend(std::iter::repeat(b' ').take(saw_events.then(|| computed).unwrap_or(8))); + buf.into() } impl<'event> Deref for MutableSection<'_, 'event> { diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index 2adc811970f..f2cc3a56a81 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -117,7 +117,7 @@ fn section<'a>( } } - if let Ok((new_i, new_newlines)) = section_body(i, node, receive_event) { + if let Ok((new_i, new_newlines)) = key_value_pair(i, node, receive_event) { if old_i != new_i { i = new_i; newlines += new_newlines; @@ -235,7 +235,7 @@ fn sub_section_delegate<'a>(i: &'a [u8], push_byte: &mut dyn FnMut(u8)) -> IResu Ok((&i[cursor - 1..], (found_escape, cursor - 1))) } -fn section_body<'a>( +fn key_value_pair<'a>( i: &'a [u8], node: &mut ParseNode, receive_event: &mut impl FnMut(Event<'a>), diff --git a/git-config/src/parse/nom/tests.rs b/git-config/src/parse/nom/tests.rs index d17d3628f92..a3b1fb9800f 100644 --- a/git-config/src/parse/nom/tests.rs +++ b/git-config/src/parse/nom/tests.rs @@ -664,7 +664,7 @@ mod value_no_continuation { } } -mod section_body { +mod key_value_pair { use crate::parse::{ error::ParseNode, section, @@ -672,19 +672,19 @@ mod section_body { Event, }; - fn section_body<'a>( + fn key_value<'a>( i: &'a [u8], node: &mut ParseNode, events: &mut section::Events<'a>, ) -> nom::IResult<&'a [u8], ()> { - super::section_body(i, node, &mut |e| events.push(e)).map(|t| (t.0, ())) + super::key_value_pair(i, node, &mut |e| events.push(e)).map(|t| (t.0, ())) } #[test] fn whitespace_is_not_ambigious() { let mut node = ParseNode::SectionHeader; let mut vec = Default::default(); - assert!(section_body(b"a =b", &mut node, &mut vec).is_ok()); + assert!(key_value(b"a =b", &mut node, &mut vec).is_ok()); assert_eq!( vec, into_events(vec![ @@ -696,7 +696,7 @@ mod section_body { ); let mut vec = Default::default(); - assert!(section_body(b"a= b", &mut node, &mut vec).is_ok()); + assert!(key_value(b"a= b", &mut node, &mut vec).is_ok()); assert_eq!( vec, into_events(vec![ From db1f34dfb855058ac08e97d4715876b5db712f61 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 13 Jul 2022 08:41:49 +0800 Subject: [PATCH 177/366] feat: `File::from_str()` implementation, to support `let config: File = "[core]".parse()?` (#331) --- git-config/src/file/impls.rs | 9 ++++++++ git-config/tests/file/mutable.rs | 15 ------------ git-config/tests/file/mutable/mod.rs | 1 + git-config/tests/file/mutable/section.rs | 29 ++++++++++++++++++++++++ 4 files changed, 39 insertions(+), 15 deletions(-) delete mode 100644 git-config/tests/file/mutable.rs create mode 100644 git-config/tests/file/mutable/mod.rs create mode 100644 git-config/tests/file/mutable/section.rs diff --git a/git-config/src/file/impls.rs b/git-config/src/file/impls.rs index c1bdbbdbdb4..ee67e33797b 100644 --- a/git-config/src/file/impls.rs +++ b/git-config/src/file/impls.rs @@ -1,9 +1,18 @@ +use std::str::FromStr; use std::{convert::TryFrom, fmt::Display}; use bstr::{BStr, BString}; use crate::{file::SectionBody, parse, File}; +impl FromStr for File<'static> { + type Err = parse::Error; + + fn from_str(s: &str) -> Result { + parse::Events::from_bytes_owned(s.as_bytes(), None).map(File::from) + } +} + impl<'a> TryFrom<&'a str> for File<'a> { type Error = parse::Error; diff --git a/git-config/tests/file/mutable.rs b/git-config/tests/file/mutable.rs deleted file mode 100644 index 474831d920c..00000000000 --- a/git-config/tests/file/mutable.rs +++ /dev/null @@ -1,15 +0,0 @@ -mod section { - mod push { - use git_config::parse::section::Key; - use std::borrow::Cow; - use std::convert::TryFrom; - - #[test] - fn push_splits_values_into_events() { - let mut file = git_config::File::default(); - let mut section = file.new_section("core", None).unwrap(); - section.push(Key::try_from("value").unwrap(), Cow::Borrowed("none".into())); - assert_eq!(file.to_bstring(), "[core]\n value=none\n"); - } - } -} diff --git a/git-config/tests/file/mutable/mod.rs b/git-config/tests/file/mutable/mod.rs new file mode 100644 index 00000000000..029ef3edcd3 --- /dev/null +++ b/git-config/tests/file/mutable/mod.rs @@ -0,0 +1 @@ +mod section; diff --git a/git-config/tests/file/mutable/section.rs b/git-config/tests/file/mutable/section.rs new file mode 100644 index 00000000000..d642d24462f --- /dev/null +++ b/git-config/tests/file/mutable/section.rs @@ -0,0 +1,29 @@ +mod push { + use git_config::parse::section::Key; + use std::borrow::Cow; + use std::convert::TryFrom; + + #[test] + #[ignore] + fn whitespace_is_derived_from_whitespace_before_first_value() -> crate::Result { + for (config, expected) in [("[a]\n\t\tb = c", "\t\t")] { + let mut file: git_config::File = config.parse()?; + assert_eq!( + file.section_mut("a", None)?.leading_space().expect("present"), + expected, + "{:?} should find {:?} as whitespace", + config, + expected + ) + } + Ok(()) + } + + #[test] + fn push_splits_values_into_events() { + let mut file = git_config::File::default(); + let mut section = file.new_section("core", None).unwrap(); + section.push(Key::try_from("value").unwrap(), Cow::Borrowed("none".into())); + assert_eq!(file.to_bstring(), "[core]\n value=none\n"); + } +} From 9f59356b4f6a1f5f7f35a62c9fbe4859bf8e8e5f Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 13 Jul 2022 09:00:11 +0800 Subject: [PATCH 178/366] feat: whitespace in newly pushed keys is derived from first section value. (#331) That way, newly added key-value pairs look like they should assuming all keys have the same indentation as the first key in the section. If there is no key, then the default whitespace will be double-tabs like what's commmon in git. --- git-config/src/file/access/mutate.rs | 4 +-- git-config/src/file/mutable/section.rs | 38 ++++++++++-------------- git-config/tests/file/init/from_str.rs | 15 ++++++++++ git-config/tests/file/init/mod.rs | 1 + git-config/tests/file/mutable/section.rs | 13 +++++--- 5 files changed, 42 insertions(+), 29 deletions(-) create mode 100644 git-config/tests/file/init/from_str.rs diff --git a/git-config/src/file/access/mutate.rs b/git-config/src/file/access/mutate.rs index 54b1ea597dc..821d0fabdde 100644 --- a/git-config/src/file/access/mutate.rs +++ b/git-config/src/file/access/mutate.rs @@ -54,9 +54,9 @@ impl<'event> File<'event> { /// let mut git_config = git_config::File::default(); /// let mut section = git_config.new_section("hello", Some("world".into()))?; /// section.push(section::Key::try_from("a")?, b"b".as_bstr().into()); - /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n a=b\n"); + /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n\t\ta=b\n"); /// let _section = git_config.new_section("core", None); - /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n a=b\n[core]\n"); + /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n\t\ta=b\n[core]\n"); /// # Ok::<(), Box>(()) /// ``` pub fn new_section( diff --git a/git-config/src/file/mutable/section.rs b/git-config/src/file/mutable/section.rs index b18dd63eb70..9774b483b49 100644 --- a/git-config/src/file/mutable/section.rs +++ b/git-config/src/file/mutable/section.rs @@ -128,7 +128,7 @@ impl<'a, 'event> MutableSection<'a, 'event> { // Internal methods that may require exact indices for faster operations. impl<'a, 'event> MutableSection<'a, 'event> { pub(crate) fn new(section: &'a mut SectionBody<'event>) -> Self { - let whitespace = Some(compute_whitespace(section)); + let whitespace = compute_whitespace(section); Self { section, implicit_newline: true, @@ -189,28 +189,20 @@ impl<'a, 'event> MutableSection<'a, 'event> { } } -fn compute_whitespace<'event>(s: &mut SectionBody<'event>) -> Cow<'event, BStr> { - let mut saw_events = false; - let computed = - s.0.iter() - .take_while(|e| matches!(e, Event::Whitespace(_))) - .inspect(|_| saw_events = true) - .map(|e| match e { - Event::Whitespace(s) => s - .iter() - .filter_map(|b| match *b { - b'\t' => Some(4), - b' ' => Some(1), - _ => None, - }) - .sum::(), - _ => unreachable!(), - }) - .sum(); - - let mut buf = BString::default(); - buf.extend(std::iter::repeat(b' ').take(saw_events.then(|| computed).unwrap_or(8))); - buf.into() +fn compute_whitespace<'event>(s: &mut SectionBody<'event>) -> Option> { + use bstr::ByteSlice; + + let first_value_pos = s.0.iter().enumerate().find_map(|(idx, e)| match e { + Event::SectionKey(_) => Some(idx), + _ => None, + }); + match first_value_pos { + Some(pos) => s.0[..pos].iter().rev().next().and_then(|e| match e { + Event::Whitespace(s) => Some(s.clone()), + _ => None, + }), + None => Some("\t\t".as_bytes().as_bstr().into()), + } } impl<'event> Deref for MutableSection<'_, 'event> { diff --git a/git-config/tests/file/init/from_str.rs b/git-config/tests/file/init/from_str.rs new file mode 100644 index 00000000000..86483435d2b --- /dev/null +++ b/git-config/tests/file/init/from_str.rs @@ -0,0 +1,15 @@ +#[test] +fn empty_yields_default_file() -> crate::Result { + let a: git_config::File = "".parse()?; + assert_eq!(a, git_config::File::default()); + assert_eq!(a.to_string(), ""); + Ok(()) +} + +#[test] +fn whitespace_without_section_contains_front_matter() -> crate::Result { + let input = " \t"; + let a: git_config::File = input.parse()?; + assert_eq!(a.to_string(), input); + Ok(()) +} diff --git a/git-config/tests/file/init/mod.rs b/git-config/tests/file/init/mod.rs index df0e32fe006..47eb4a8d09c 100644 --- a/git-config/tests/file/init/mod.rs +++ b/git-config/tests/file/init/mod.rs @@ -1,2 +1,3 @@ pub mod from_env; mod from_paths; +mod from_str; diff --git a/git-config/tests/file/mutable/section.rs b/git-config/tests/file/mutable/section.rs index d642d24462f..a9097c75c04 100644 --- a/git-config/tests/file/mutable/section.rs +++ b/git-config/tests/file/mutable/section.rs @@ -4,12 +4,17 @@ mod push { use std::convert::TryFrom; #[test] - #[ignore] fn whitespace_is_derived_from_whitespace_before_first_value() -> crate::Result { - for (config, expected) in [("[a]\n\t\tb = c", "\t\t")] { + for (config, expected) in [ + ("[a]\n\t\tb = c", Some("\t\t".into())), + ("[a]\nb = c", None), + ("[a]", Some("\t\t".into())), + ("[a]\t\tb = c", Some("\t\t".into())), + ("[a]\n\t\t \n \t b = c", Some(" \t ".into())), + ] { let mut file: git_config::File = config.parse()?; assert_eq!( - file.section_mut("a", None)?.leading_space().expect("present"), + file.section_mut("a", None)?.leading_space(), expected, "{:?} should find {:?} as whitespace", config, @@ -24,6 +29,6 @@ mod push { let mut file = git_config::File::default(); let mut section = file.new_section("core", None).unwrap(); section.push(Key::try_from("value").unwrap(), Cow::Borrowed("none".into())); - assert_eq!(file.to_bstring(), "[core]\n value=none\n"); + assert_eq!(file.to_bstring(), "[core]\n\t\tvalue=none\n"); } } From 83a0922f06081312b79908835dac2b7f4e849bb3 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 13 Jul 2022 09:06:04 +0800 Subject: [PATCH 179/366] change!: rename `file::MutableSection::set_leading_space()` to `set_leading_whitespace()`. (#331) The corresponding getter was renamed as well to `leading_whitespace()`. --- git-config/src/file/mutable/section.rs | 18 +++++++++++++++--- git-config/tests/file/mutable/section.rs | 2 +- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/git-config/src/file/mutable/section.rs b/git-config/src/file/mutable/section.rs index 9774b483b49..1ca5188e74c 100644 --- a/git-config/src/file/mutable/section.rs +++ b/git-config/src/file/mutable/section.rs @@ -110,17 +110,29 @@ impl<'a, 'event> MutableSection<'a, 'event> { self.implicit_newline = on; } - /// Sets the exact whitespace to use before each key-value pair. + /// Sets the exact whitespace to use before each key-value pair, with only whitespace characters + /// being permissible. /// The default is 2 tabs. /// Set to `None` to disable adding whitespace before a key value. - pub fn set_leading_space(&mut self, whitespace: Option>) { + /// + /// # Panics + /// + /// If non-whitespace characters are used. This makes the method only suitable for validated + /// or known input. + pub fn set_leading_whitespace(&mut self, whitespace: Option>) { + assert!( + whitespace + .as_deref() + .map_or(true, |ws| ws.iter().all(|b| b.is_ascii_whitespace())), + "input whitespace must only contain whitespace characters." + ); self.whitespace = whitespace; } /// Returns the whitespace this section will insert before the /// beginning of a key, if any. #[must_use] - pub fn leading_space(&self) -> Option<&BStr> { + pub fn leading_whitespace(&self) -> Option<&BStr> { self.whitespace.as_deref() } } diff --git a/git-config/tests/file/mutable/section.rs b/git-config/tests/file/mutable/section.rs index a9097c75c04..1fe00aa9015 100644 --- a/git-config/tests/file/mutable/section.rs +++ b/git-config/tests/file/mutable/section.rs @@ -14,7 +14,7 @@ mod push { ] { let mut file: git_config::File = config.parse()?; assert_eq!( - file.section_mut("a", None)?.leading_space(), + file.section_mut("a", None)?.leading_whitespace(), expected, "{:?} should find {:?} as whitespace", config, From 5e6f9d909db41926e829e464abc53ef05fbf620b Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 13 Jul 2022 09:38:07 +0800 Subject: [PATCH 180/366] a few tests for `MutableValue` showing that it's too buggy right now (#331) Since `MutableSection` can do all of it too, let's double-down on that one instead. So in order to set values, one needs a mutable section first. --- git-config/src/file/mutable/value.rs | 4 ++-- git-config/tests/file/mutable/mod.rs | 1 + git-config/tests/file/mutable/value.rs | 31 ++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 git-config/tests/file/mutable/value.rs diff --git a/git-config/src/file/mutable/value.rs b/git-config/src/file/mutable/value.rs index 20e872fe145..49c74c3a520 100644 --- a/git-config/src/file/mutable/value.rs +++ b/git-config/src/file/mutable/value.rs @@ -29,8 +29,8 @@ impl<'borrow, 'lookup, 'event> MutableValue<'borrow, 'lookup, 'event> { /// Update the value to the provided one. This modifies the value such that /// the Value event(s) are replaced with a single new event containing the /// new value. - pub fn set_string(&mut self, input: String) { - self.set_bytes(input.into()); + pub fn set_string(&mut self, input: impl Into) { + self.set_bytes(input.into().into()); } /// Update the value to the provided one. This modifies the value such that diff --git a/git-config/tests/file/mutable/mod.rs b/git-config/tests/file/mutable/mod.rs index 029ef3edcd3..dce8f6ed52f 100644 --- a/git-config/tests/file/mutable/mod.rs +++ b/git-config/tests/file/mutable/mod.rs @@ -1 +1,2 @@ mod section; +mod value; diff --git a/git-config/tests/file/mutable/value.rs b/git-config/tests/file/mutable/value.rs new file mode 100644 index 00000000000..604418ac559 --- /dev/null +++ b/git-config/tests/file/mutable/value.rs @@ -0,0 +1,31 @@ +mod get { + use bstr::BString; + + fn file_get(input: &str) -> BString { + let mut file: git_config::File = input.parse().unwrap(); + file.raw_value_mut("a", None, "k").unwrap().get().unwrap().into_owned() + } + + #[test] + #[ignore] + fn empty() { + assert_eq!(file_get("[a] k"), ""); + } +} + +mod set_string { + fn file() -> git_config::File<'static> { + "[a] k=v".parse().unwrap() + } + + #[test] + #[ignore] + fn leading_whitespace_causes_double_quotes() -> crate::Result { + let mut file = file(); + let mut v = file.raw_value_mut("a", None, "k")?; + assert_eq!(v.get()?.as_ref(), "v"); + v.set_string(" v"); + assert_eq!(v.get()?.as_ref(), " v"); + Ok(()) + } +} From afa736aba385bd52e7f11fd89538aea99787ac9d Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 13 Jul 2022 11:15:42 +0800 Subject: [PATCH 181/366] refactor (#331) --- git-config/src/file/access/raw.rs | 14 +- git-config/src/file/mutable/section.rs | 4 +- git-config/tests/file/access/raw/mod.rs | 3 +- .../file/access/raw/mutable_multi_value.rs | 112 ----------- .../tests/file/access/raw/mutable_value.rs | 129 ------------- .../tests/file/access/raw/raw_multi_value.rs | 57 +++--- git-config/tests/file/access/raw/raw_value.rs | 67 +++---- .../tests/file/access/raw/set_raw_value.rs | 11 ++ git-config/tests/file/mutable/mod.rs | 1 + git-config/tests/file/mutable/multi_value.rs | 131 +++++++++++++ git-config/tests/file/mutable/section.rs | 14 +- git-config/tests/file/mutable/value.rs | 174 +++++++++++++++++- 12 files changed, 390 insertions(+), 327 deletions(-) delete mode 100644 git-config/tests/file/access/raw/mutable_multi_value.rs delete mode 100644 git-config/tests/file/access/raw/mutable_value.rs create mode 100644 git-config/tests/file/access/raw/set_raw_value.rs create mode 100644 git-config/tests/file/mutable/multi_value.rs diff --git a/git-config/src/file/access/raw.rs b/git-config/src/file/access/raw.rs index cb01dc3eb87..3f7126ffc90 100644 --- a/git-config/src/file/access/raw.rs +++ b/git-config/src/file/access/raw.rs @@ -52,23 +52,22 @@ impl<'event> File<'event> { let key = section::Key(Cow::::Borrowed(key.into())); while let Some(section_id) = section_ids.next() { - let mut size = 0; let mut index = 0; + let mut size = 0; let mut found_key = false; for (i, event) in self .sections .get(§ion_id) - .expect("sections does not have section id from section ids") + .expect("known section id") .as_ref() - // todo: iter backwards .iter() .enumerate() { match event { Event::SectionKey(event_key) if *event_key == key => { found_key = true; - size = 1; index = i; + size = 1; } Event::Newline(_) | Event::Whitespace(_) | Event::ValueNotDone(_) if found_key => { size += 1; @@ -77,7 +76,10 @@ impl<'event> File<'event> { found_key = false; size += 1; } - _ => (), + Event::KeyValueSeparator if found_key => { + size += 1; + } + _ => {} } } @@ -304,7 +306,7 @@ impl<'event> File<'event> { new_value: BString, ) -> Result<(), lookup::existing::Error> { self.raw_value_mut(section_name, subsection_name, key) - .map(|mut entry| entry.set_bytes(new_value)) + .map(|mut entry| entry.set_bytes(new_value.into())) } /// Sets a multivar in a given section, optional subsection, and key value. diff --git a/git-config/src/file/mutable/section.rs b/git-config/src/file/mutable/section.rs index 1ca5188e74c..75957621f1f 100644 --- a/git-config/src/file/mutable/section.rs +++ b/git-config/src/file/mutable/section.rs @@ -157,7 +157,7 @@ impl<'a, 'event> MutableSection<'a, 'event> { let mut expect_value = false; let mut concatenated_value = BString::default(); - for event in &self.section.0[start.0..=end.0] { + for event in &self.section.0[start.0..end.0] { match event { Event::SectionKey(event_key) if event_key == key => expect_value = true, Event::Value(v) if expect_value => return Ok(normalize_bstr(v.as_ref())), @@ -176,7 +176,7 @@ impl<'a, 'event> MutableSection<'a, 'event> { } pub(crate) fn delete(&mut self, start: Index, end: Index) { - self.section.0.drain(start.0..=end.0); + self.section.0.drain(start.0..end.0); } pub(crate) fn set_internal(&mut self, index: Index, key: Key<'event>, value: BString) -> Size { diff --git a/git-config/tests/file/access/raw/mod.rs b/git-config/tests/file/access/raw/mod.rs index 7d52cd8f9fb..8198d29b1c9 100644 --- a/git-config/tests/file/access/raw/mod.rs +++ b/git-config/tests/file/access/raw/mod.rs @@ -1,4 +1,3 @@ -mod mutable_multi_value; -mod mutable_value; mod raw_multi_value; mod raw_value; +mod set_raw_value; diff --git a/git-config/tests/file/access/raw/mutable_multi_value.rs b/git-config/tests/file/access/raw/mutable_multi_value.rs deleted file mode 100644 index 34d7d0e0368..00000000000 --- a/git-config/tests/file/access/raw/mutable_multi_value.rs +++ /dev/null @@ -1,112 +0,0 @@ -use std::{borrow::Cow, convert::TryFrom}; - -use git_config::File; -use git_testtools::fixture_path; - -fn init_config() -> File<'static> { - let mut buf = Vec::new(); - File::from_path_with_buf(&fixture_path("multi-core.txt"), &mut buf).unwrap() -} - -#[test] -fn value_is_correct() { - let mut git_config = init_config(); - - let value = git_config.raw_values_mut("core", None, "a").unwrap(); - assert_eq!( - &*value.get().unwrap(), - vec![ - Cow::<[u8]>::Owned(b"b100".to_vec()), - Cow::<[u8]>::Borrowed(b"d"), - Cow::<[u8]>::Borrowed(b"f"), - ] - ); -} - -#[test] -fn non_empty_sizes_are_correct() { - let mut git_config = init_config(); - assert_eq!(git_config.raw_values_mut("core", None, "a").unwrap().len(), 3); - assert!(!git_config.raw_values_mut("core", None, "a").unwrap().is_empty()); -} - -#[test] -fn set_value_at_start() { - let mut git_config = init_config(); - let mut values = git_config.raw_values_mut("core", None, "a").unwrap(); - values.set_string(0, "Hello".to_string()); - assert_eq!( - git_config.to_string(), - "[core]\n a=Hello\n [core]\n a=d\n a=f" - ); -} - -#[test] -fn set_value_at_end() { - let mut git_config = init_config(); - let mut values = git_config.raw_values_mut("core", None, "a").unwrap(); - values.set_string(2, "Hello".to_string()); - assert_eq!( - git_config.to_string(), - "[core]\n a=b\"100\"\n [core]\n a=d\n a=Hello" - ); -} - -#[test] -fn set_values_all() { - let mut git_config = init_config(); - let mut values = git_config.raw_values_mut("core", None, "a").unwrap(); - values.set_owned_values_all("Hello"); - assert_eq!( - git_config.to_string(), - "[core]\n a=Hello\n [core]\n a=Hello\n a=Hello" - ); -} - -#[test] -fn delete() { - let mut git_config = init_config(); - let mut values = git_config.raw_values_mut("core", None, "a").unwrap(); - values.delete(0); - assert_eq!( - git_config.to_string(), - "[core]\n \n [core]\n a=d\n a=f", - ); -} - -#[test] -fn delete_all() { - let mut git_config = init_config(); - let mut values = git_config.raw_values_mut("core", None, "a").unwrap(); - values.delete_all(); - assert!(values.get().is_err()); - assert_eq!(git_config.to_string(), "[core]\n \n [core]\n \n ",); -} - -#[test] -fn partial_values_are_supported() { - let mut git_config = File::try_from( - r#"[core] - a=b\ -"100" - [core] - a=d\ -b - a=f\ -a"#, - ) - .unwrap(); - let mut values = git_config.raw_values_mut("core", None, "a").unwrap(); - - assert_eq!( - &*values.get().unwrap(), - vec![ - Cow::<[u8]>::Owned(b"b100".to_vec()), - Cow::<[u8]>::Borrowed(b"db"), - Cow::<[u8]>::Borrowed(b"fa"), - ] - ); - - values.delete_all(); - assert!(values.get().is_err()); -} diff --git a/git-config/tests/file/access/raw/mutable_value.rs b/git-config/tests/file/access/raw/mutable_value.rs deleted file mode 100644 index 0d70dc7f976..00000000000 --- a/git-config/tests/file/access/raw/mutable_value.rs +++ /dev/null @@ -1,129 +0,0 @@ -use std::convert::TryFrom; - -use git_config::File; - -fn init_config() -> File<'static> { - File::try_from( - r#"[core] - a=b"100" - [core] - c=d - e=f"#, - ) - .unwrap() -} - -#[test] -fn value_is_correct() { - let mut git_config = init_config(); - - let value = git_config.raw_value_mut("core", None, "a").unwrap(); - assert_eq!(&*value.get().unwrap(), "b100"); -} - -#[test] -fn set_string_cleanly_updates() { - let mut git_config = init_config(); - - let mut value = git_config.raw_value_mut("core", None, "a").unwrap(); - value.set_string("hello world".to_string()); - assert_eq!( - git_config.to_string(), - r#"[core] - a=hello world - [core] - c=d - e=f"#, - ); - - let mut value = git_config.raw_value_mut("core", None, "e").unwrap(); - value.set_string(String::new()); - assert_eq!( - git_config.to_string(), - r#"[core] - a=hello world - [core] - c=d - e="#, - ); -} - -#[test] -fn delete_value() { - let mut git_config = init_config(); - - let mut value = git_config.raw_value_mut("core", None, "a").unwrap(); - value.delete(); - assert_eq!( - git_config.to_string(), - "[core]\n \n [core]\n c=d\n e=f", - ); - - let mut value = git_config.raw_value_mut("core", None, "c").unwrap(); - value.delete(); - assert_eq!( - git_config.to_string(), - "[core]\n \n [core]\n \n e=f", - ); -} - -#[test] -fn get_value_after_deleted() { - let mut git_config = init_config(); - - let mut value = git_config.raw_value_mut("core", None, "a").unwrap(); - value.delete(); - assert!(value.get().is_err()); -} - -#[test] -fn set_string_after_deleted() { - let mut git_config = init_config(); - - let mut value = git_config.raw_value_mut("core", None, "a").unwrap(); - value.delete(); - value.set_string("hello world".to_string()); - assert_eq!( - git_config.to_string(), - r#"[core] - a=hello world - [core] - c=d - e=f"#, - ); -} - -#[test] -fn subsequent_delete_calls_are_noop() { - let mut git_config = init_config(); - - let mut value = git_config.raw_value_mut("core", None, "a").unwrap(); - for _ in 0..10 { - value.delete(); - } - assert_eq!( - git_config.to_string(), - "[core]\n \n [core]\n c=d\n e=f" - ); -} - -#[test] -fn partial_values_are_supported() { - let mut git_config = File::try_from( - r#"[core] - a=b"100"\ -c\ -b - [core] - c=d - e=f"#, - ) - .unwrap(); - let mut value = git_config.raw_value_mut("core", None, "a").unwrap(); - assert_eq!(&*value.get().unwrap(), "b100cb"); - value.delete(); - assert_eq!( - git_config.to_string(), - "[core]\n \n [core]\n c=d\n e=f" - ); -} diff --git a/git-config/tests/file/access/raw/raw_multi_value.rs b/git-config/tests/file/access/raw/raw_multi_value.rs index 78630feb1bc..7357d52150b 100644 --- a/git-config/tests/file/access/raw/raw_multi_value.rs +++ b/git-config/tests/file/access/raw/raw_multi_value.rs @@ -5,71 +5,76 @@ use git_config::{lookup, File}; use crate::file::cow_str; #[test] -fn single_value_is_identical_to_single_value_query() { - let config = File::try_from("[core]\na=b\nc=d").unwrap(); +fn single_value_is_identical_to_single_value_query() -> crate::Result { + let config = File::try_from("[core]\na=b\nc=d")?; assert_eq!( - vec![config.raw_value("core", None, "a").unwrap()], - config.raw_values("core", None, "a").unwrap() + vec![config.raw_value("core", None, "a")?], + config.raw_values("core", None, "a")? ); + Ok(()) } #[test] -fn multi_value_in_section() { - let config = File::try_from("[core]\na=b\na=c").unwrap(); - assert_eq!( - config.raw_values("core", None, "a").unwrap(), - vec![cow_str("b"), cow_str("c")] - ); +fn multi_value_in_section() -> crate::Result { + let config = File::try_from("[core]\na=b\na=c")?; + assert_eq!(config.raw_values("core", None, "a")?, vec![cow_str("b"), cow_str("c")]); + Ok(()) } #[test] -fn multi_value_across_sections() { - let config = File::try_from("[core]\na=b\na=c\n[core]a=d").unwrap(); +fn multi_value_across_sections() -> crate::Result { + let config = File::try_from("[core]\na=b\na=c\n[core]a=d")?; assert_eq!( - config.raw_values("core", None, "a").unwrap(), + config.raw_values("core", None, "a")?, vec![cow_str("b"), cow_str("c"), cow_str("d")] ); + Ok(()) } #[test] -fn section_not_found() { - let config = File::try_from("[core]\na=b\nc=d").unwrap(); +fn section_not_found() -> crate::Result { + let config = File::try_from("[core]\na=b\nc=d")?; assert!(matches!( config.raw_values("foo", None, "a"), Err(lookup::existing::Error::SectionMissing) )); + Ok(()) } #[test] -fn subsection_not_found() { - let config = File::try_from("[core]\na=b\nc=d").unwrap(); +fn subsection_not_found() -> crate::Result { + let config = File::try_from("[core]\na=b\nc=d")?; assert!(matches!( config.raw_values("core", Some("a"), "a"), Err(lookup::existing::Error::SubSectionMissing) )); + Ok(()) } #[test] -fn key_not_found() { - let config = File::try_from("[core]\na=b\nc=d").unwrap(); +fn key_not_found() -> crate::Result { + let config = File::try_from("[core]\na=b\nc=d")?; assert!(matches!( config.raw_values("core", None, "aaaaaa"), Err(lookup::existing::Error::KeyMissing) )); + Ok(()) } #[test] -fn subsection_must_be_respected() { - let config = File::try_from("[core]a=b\n[core.a]a=c").unwrap(); - assert_eq!(config.raw_values("core", None, "a").unwrap(), vec![cow_str("b")]); - assert_eq!(config.raw_values("core", Some("a"), "a").unwrap(), vec![cow_str("c")]); +fn subsection_must_be_respected() -> crate::Result { + let config = File::try_from("[core]a=b\n[core.a]a=c")?; + assert_eq!(config.raw_values("core", None, "a")?, vec![cow_str("b")]); + assert_eq!(config.raw_values("core", Some("a"), "a")?, vec![cow_str("c")]); + Ok(()) } #[test] -fn non_relevant_subsection_is_ignored() { - let config = File::try_from("[core]\na=b\na=c\n[core]a=d\n[core]g=g").unwrap(); +fn non_relevant_subsection_is_ignored() -> crate::Result { + let config = File::try_from("[core]\na=b\na=c\n[core]a=d\n[core]g=g")?; assert_eq!( - config.raw_values("core", None, "a").unwrap(), + config.raw_values("core", None, "a")?, vec![cow_str("b"), cow_str("c"), cow_str("d")] ); + Ok(()) } diff --git a/git-config/tests/file/access/raw/raw_value.rs b/git-config/tests/file/access/raw/raw_value.rs index 6f4ecd42209..70c5c1c7b76 100644 --- a/git-config/tests/file/access/raw/raw_value.rs +++ b/git-config/tests/file/access/raw/raw_value.rs @@ -1,74 +1,63 @@ -use std::{borrow::Cow, convert::TryFrom}; +use std::convert::TryFrom; use git_config::{lookup, File}; #[test] -fn single_section() { - let config = File::try_from("[core]\na=b\nc=d").unwrap(); - assert_eq!( - config.raw_value("core", None, "a").unwrap(), - Cow::<[u8]>::Borrowed(b"b") - ); - assert_eq!( - config.raw_value("core", None, "c").unwrap(), - Cow::<[u8]>::Borrowed(b"d") - ); +fn single_section() -> crate::Result { + let config = File::try_from("[core]\na=b\nc=d")?; + assert_eq!(config.raw_value("core", None, "a")?.as_ref(), "b"); + assert_eq!(config.raw_value("core", None, "c")?.as_ref(), "d"); + Ok(()) } #[test] -fn last_one_wins_respected_in_section() { - let config = File::try_from("[core]\na=b\na=d").unwrap(); - assert_eq!( - config.raw_value("core", None, "a").unwrap(), - Cow::<[u8]>::Borrowed(b"d") - ); +fn last_one_wins_respected_in_section() -> crate::Result { + let config = File::try_from("[core]\na=b\na=d")?; + assert_eq!(config.raw_value("core", None, "a")?.as_ref(), "d"); + Ok(()) } #[test] -fn last_one_wins_respected_across_section() { - let config = File::try_from("[core]\na=b\n[core]\na=d").unwrap(); - assert_eq!( - config.raw_value("core", None, "a").unwrap(), - Cow::<[u8]>::Borrowed(b"d") - ); +fn last_one_wins_respected_across_section() -> crate::Result { + let config = File::try_from("[core]\na=b\n[core]\na=d")?; + assert_eq!(config.raw_value("core", None, "a")?.as_ref(), "d"); + Ok(()) } #[test] -fn section_not_found() { - let config = File::try_from("[core]\na=b\nc=d").unwrap(); +fn section_not_found() -> crate::Result { + let config = File::try_from("[core]\na=b\nc=d")?; assert!(matches!( config.raw_value("foo", None, "a"), Err(lookup::existing::Error::SectionMissing) )); + Ok(()) } #[test] -fn subsection_not_found() { - let config = File::try_from("[core]\na=b\nc=d").unwrap(); +fn subsection_not_found() -> crate::Result { + let config = File::try_from("[core]\na=b\nc=d")?; assert!(matches!( config.raw_value("core", Some("a"), "a"), Err(lookup::existing::Error::SubSectionMissing) )); + Ok(()) } #[test] -fn key_not_found() { - let config = File::try_from("[core]\na=b\nc=d").unwrap(); +fn key_not_found() -> crate::Result { + let config = File::try_from("[core]\na=b\nc=d")?; assert!(matches!( config.raw_value("core", None, "aaaaaa"), Err(lookup::existing::Error::KeyMissing) )); + Ok(()) } #[test] -fn subsection_must_be_respected() { - let config = File::try_from("[core]a=b\n[core.a]a=c").unwrap(); - assert_eq!( - config.raw_value("core", None, "a").unwrap(), - Cow::<[u8]>::Borrowed(b"b") - ); - assert_eq!( - config.raw_value("core", Some("a"), "a").unwrap(), - Cow::<[u8]>::Borrowed(b"c") - ); +fn subsection_must_be_respected() -> crate::Result { + let config = File::try_from("[core]a=b\n[core.a]a=c")?; + assert_eq!(config.raw_value("core", None, "a")?.as_ref(), "b"); + assert_eq!(config.raw_value("core", Some("a"), "a")?.as_ref(), "c"); + Ok(()) } diff --git a/git-config/tests/file/access/raw/set_raw_value.rs b/git-config/tests/file/access/raw/set_raw_value.rs new file mode 100644 index 00000000000..2e8b5298549 --- /dev/null +++ b/git-config/tests/file/access/raw/set_raw_value.rs @@ -0,0 +1,11 @@ +fn file(input: &str) -> git_config::File<'static> { + input.parse().unwrap() +} + +#[test] +fn single_line() -> crate::Result { + let mut file = file("[a]k=b\n[a]\nk=c\nk=d"); + file.set_raw_value("a", None, "k", "e".into())?; + assert_eq!(file.raw_value("a", None, "k")?.as_ref(), "e"); + Ok(()) +} diff --git a/git-config/tests/file/mutable/mod.rs b/git-config/tests/file/mutable/mod.rs index dce8f6ed52f..b2e1af743e0 100644 --- a/git-config/tests/file/mutable/mod.rs +++ b/git-config/tests/file/mutable/mod.rs @@ -1,2 +1,3 @@ +mod multi_value; mod section; mod value; diff --git a/git-config/tests/file/mutable/multi_value.rs b/git-config/tests/file/mutable/multi_value.rs new file mode 100644 index 00000000000..3e4365ed94c --- /dev/null +++ b/git-config/tests/file/mutable/multi_value.rs @@ -0,0 +1,131 @@ +mod get { + use crate::file::cow_str; + use crate::file::mutable::multi_value::init_config; + + #[test] + fn single_lines() -> crate::Result { + let mut config = init_config(); + + let value = config.raw_values_mut("core", None, "a")?; + assert_eq!(&*value.get()?, vec![cow_str("b100"), cow_str("d"), cow_str("f"),]); + Ok(()) + } + + #[test] + fn multi_line() -> crate::Result { + let mut config: git_config::File = r#"[core] + a=b\ +"100" + [core] + a=d\ +b + a=f\ +a"# + .parse()?; + + let mut values = config.raw_values_mut("core", None, "a")?; + assert_eq!(&*values.get()?, vec![cow_str("b100"), cow_str("db"), cow_str("fa"),]); + + values.delete_all(); + assert!(values.get().is_err()); + + Ok(()) + } +} + +mod access { + use crate::file::mutable::multi_value::init_config; + + #[test] + fn non_empty_sizes() -> crate::Result { + let mut config = init_config(); + assert_eq!(config.raw_values_mut("core", None, "a")?.len(), 3); + assert!(!config.raw_values_mut("core", None, "a")?.is_empty()); + Ok(()) + } +} + +mod set { + use crate::file::mutable::multi_value::init_config; + + #[test] + fn single_at_start() -> crate::Result { + let mut config = init_config(); + let mut values = config.raw_values_mut("core", None, "a")?; + values.set_string(0, "Hello".into()); + assert_eq!( + config.to_string(), + "[core]\n a=Hello\n [core]\n a=d\n a=f" + ); + Ok(()) + } + + #[test] + fn single_at_end() -> crate::Result { + let mut config = init_config(); + let mut values = config.raw_values_mut("core", None, "a")?; + values.set_string(2, "Hello".into()); + assert_eq!( + config.to_string(), + "[core]\n a=b\"100\"\n [core]\n a=d\n a=Hello" + ); + Ok(()) + } + + #[test] + fn all() -> crate::Result { + let mut config = init_config(); + let mut values = config.raw_values_mut("core", None, "a")?; + values.set_owned_values_all("Hello"); + assert_eq!( + config.to_string(), + "[core]\n a=Hello\n [core]\n a=Hello\n a=Hello" + ); + Ok(()) + } +} + +mod delete { + use crate::file::mutable::multi_value::init_config; + + #[test] + fn single_at_start_and_end() -> crate::Result { + let mut config = init_config(); + { + let mut values = config.raw_values_mut("core", None, "a")?; + values.delete(0); + assert_eq!(config.to_string(), "[core]\n \n [core]\n a=d\n a=f",); + } + + let mut values = config.raw_values_mut("core", None, "a")?; + values.delete(1); + assert_eq!(config.to_string(), "[core]\n \n [core]\n a=d\n ",); + Ok(()) + } + + #[test] + fn all() -> crate::Result { + let mut config = init_config(); + let mut values = config.raw_values_mut("core", None, "a")?; + values.delete_all(); + assert!(values.get().is_err()); + assert_eq!(config.to_string(), "[core]\n \n [core]\n \n ",); + Ok(()) + } +} + +#[test] +#[ignore] +fn empty_value() { + todo!() +} + +fn init_config() -> git_config::File<'static> { + r#"[core] + a=b"100" + [core] + a=d + a=f"# + .parse() + .unwrap() +} diff --git a/git-config/tests/file/mutable/section.rs b/git-config/tests/file/mutable/section.rs index 1fe00aa9015..6e5626f4bda 100644 --- a/git-config/tests/file/mutable/section.rs +++ b/git-config/tests/file/mutable/section.rs @@ -5,19 +5,19 @@ mod push { #[test] fn whitespace_is_derived_from_whitespace_before_first_value() -> crate::Result { - for (config, expected) in [ + for (input, expected) in [ ("[a]\n\t\tb = c", Some("\t\t".into())), ("[a]\nb = c", None), ("[a]", Some("\t\t".into())), ("[a]\t\tb = c", Some("\t\t".into())), ("[a]\n\t\t \n \t b = c", Some(" \t ".into())), ] { - let mut file: git_config::File = config.parse()?; + let mut config: git_config::File = input.parse()?; assert_eq!( - file.section_mut("a", None)?.leading_whitespace(), + config.section_mut("a", None)?.leading_whitespace(), expected, "{:?} should find {:?} as whitespace", - config, + input, expected ) } @@ -26,9 +26,9 @@ mod push { #[test] fn push_splits_values_into_events() { - let mut file = git_config::File::default(); - let mut section = file.new_section("core", None).unwrap(); + let mut config = git_config::File::default(); + let mut section = config.new_section("core", None).unwrap(); section.push(Key::try_from("value").unwrap(), Cow::Borrowed("none".into())); - assert_eq!(file.to_bstring(), "[core]\n\t\tvalue=none\n"); + assert_eq!(config.to_bstring(), "[core]\n\t\tvalue=none\n"); } } diff --git a/git-config/tests/file/mutable/value.rs b/git-config/tests/file/mutable/value.rs index 604418ac559..833349b3c40 100644 --- a/git-config/tests/file/mutable/value.rs +++ b/git-config/tests/file/mutable/value.rs @@ -1,21 +1,52 @@ mod get { + use crate::file::mutable::value::init_config; use bstr::BString; - fn file_get(input: &str) -> BString { + fn config_get(input: &str) -> BString { let mut file: git_config::File = input.parse().unwrap(); file.raw_value_mut("a", None, "k").unwrap().get().unwrap().into_owned() } #[test] - #[ignore] fn empty() { - assert_eq!(file_get("[a] k"), ""); + assert_eq!(config_get("[a] k"), ""); + } + + #[test] + fn single_line_before_comment() { + assert_eq!(config_get("[a] k = hello there ; comment"), "hello there"); + } + + #[test] + fn quoted_single_line_before_comment() { + assert_eq!(config_get("[a] k = \" hello\tthere \"; comment"), " hello\tthere "); + } + + #[test] + fn multi_line_before_comment() { + assert_eq!(config_get("[a] k = a\\\n b\\\n c ; comment"), "a b c"); + } + + #[test] + fn value_is_correct() -> crate::Result { + let mut config = init_config(); + + let value = config.raw_value_mut("core", None, "a")?; + assert_eq!(&*value.get()?, "b100"); + Ok(()) } } mod set_string { + use crate::file::mutable::value::init_config; + fn file() -> git_config::File<'static> { - "[a] k=v".parse().unwrap() + "[a] k = v".parse().unwrap() + } + + fn assert_value(f: &git_config::File, expected: &str) { + let file: git_config::File = f.to_string().parse().unwrap(); + assert_eq!(file.raw_value("a", None, "k").expect("present").as_ref(), expected); } #[test] @@ -25,7 +56,142 @@ mod set_string { let mut v = file.raw_value_mut("a", None, "k")?; assert_eq!(v.get()?.as_ref(), "v"); v.set_string(" v"); + assert_eq!(v.get()?.as_ref(), " v"); + assert_value(&file, " v"); + Ok(()) + } + + #[test] + fn simple_value_and_empty_string() -> crate::Result { + let mut config = init_config(); + + let mut value = config.raw_value_mut("core", None, "a")?; + value.set_string("hello world".to_string()); + assert_eq!( + config.to_string(), + r#"[core] + a=hello world + [core] + c=d + e=f"#, + ); + + let mut value = config.raw_value_mut("core", None, "e")?; + value.set_string(String::new()); + assert_eq!( + config.to_string(), + r#"[core] + a=hello world + [core] + c=d + e="#, + ); + Ok(()) + } +} + +mod delete { + use crate::file::mutable::value::init_config; + + #[test] + fn single_line_value() -> crate::Result { + let mut config = init_config(); + + let mut value = config.raw_value_mut("core", None, "a")?; + value.delete(); + assert_eq!( + config.to_string(), + "[core]\n \n [core]\n c=d\n e=f", + ); + + let mut value = config.raw_value_mut("core", None, "c")?; + value.delete(); + assert_eq!( + config.to_string(), + "[core]\n \n [core]\n \n e=f", + ); + Ok(()) + } + + #[test] + fn get_value_after_deleted() -> crate::Result { + let mut config = init_config(); + + let mut value = config.raw_value_mut("core", None, "a")?; + value.delete(); + assert!(value.get().is_err()); + Ok(()) + } + + #[test] + fn set_string_after_deleted() -> crate::Result { + let mut config = init_config(); + + let mut value = config.raw_value_mut("core", None, "a")?; + value.delete(); + value.set_string("hello world".to_string()); + assert_eq!( + config.to_string(), + r#"[core] + a=hello world + [core] + c=d + e=f"#, + ); + Ok(()) + } + + #[test] + fn idempotency() -> crate::Result { + let mut config = init_config(); + + let mut value = config.raw_value_mut("core", None, "a")?; + for _ in 0..3 { + value.delete(); + } + assert_eq!( + config.to_string(), + "[core]\n \n [core]\n c=d\n e=f" + ); + Ok(()) + } + + #[test] + fn multi_line_value() -> crate::Result { + let mut config: git_config::File = r#"[core] + a=b"100"\ +c\ +b + [core] + c=d + e=f"# + .parse()?; + let mut value = config.raw_value_mut("core", None, "a")?; + assert_eq!(&*value.get()?, "b100cb"); + value.delete(); + assert_eq!( + config.to_string(), + "[core]\n \n [core]\n c=d\n e=f" + ); Ok(()) } } + +#[test] +#[ignore] +fn empty_values() { + todo!() +} + +fn init_config() -> git_config::File<'static> { + use std::convert::TryFrom; + git_config::File::try_from( + r#"[core] + a=b"100" + [core] + c=d + e=f"#, + ) + .unwrap() +} From 8118644625dc25b616e5f33c85f5100d600766e4 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 13 Jul 2022 13:01:27 +0800 Subject: [PATCH 182/366] feat: proper escaping of value bytes to allow round-tripping after mutation (#331) --- git-config/src/file/mutable/section.rs | 32 +++++++++-- .../tests/file/access/raw/set_raw_value.rs | 50 +++++++++++++++-- git-config/tests/file/access/read_only.rs | 4 +- git-config/tests/file/access/write.rs | 2 + git-config/tests/file/mutable/value.rs | 54 +++++++++++++++---- git-config/tests/value/normalize.rs | 2 +- 6 files changed, 121 insertions(+), 23 deletions(-) diff --git a/git-config/src/file/mutable/section.rs b/git-config/src/file/mutable/section.rs index 75957621f1f..f5384845c17 100644 --- a/git-config/src/file/mutable/section.rs +++ b/git-config/src/file/mutable/section.rs @@ -4,7 +4,7 @@ use std::{ ops::{Deref, Range}, }; -use bstr::{BStr, BString, ByteVec}; +use bstr::{BStr, BString, ByteSlice, ByteVec}; use crate::{ file::{Index, Size}, @@ -179,8 +179,34 @@ impl<'a, 'event> MutableSection<'a, 'event> { self.section.0.drain(start.0..end.0); } + // TODO: borrow value only to avoid extra copy pub(crate) fn set_internal(&mut self, index: Index, key: Key<'event>, value: BString) -> Size { - self.section.0.insert(index.0, Event::Value(value.into())); + let quote = value.get(0).map_or(false, |b| b.is_ascii_whitespace()) + || value + .get(value.len().saturating_sub(1)) + .map_or(false, |b| b.is_ascii_whitespace()) + || value.find_byteset(b";#").is_some(); + + let mut buf: BString = Vec::with_capacity(value.len()).into(); + if quote { + buf.push(b'"'); + } + + for b in value.iter().copied() { + match b { + b'\n' => buf.push_str("\\n"), + b'\t' => buf.push_str("\\t"), + b'"' => buf.push_str("\\\""), + b'\\' => buf.push_str("\\\\"), + _ => buf.push(b), + } + } + + if quote { + buf.push(b'"'); + } + + self.section.0.insert(index.0, Event::Value(buf.into())); self.section.0.insert(index.0, Event::KeyValueSeparator); self.section.0.insert(index.0, Event::SectionKey(key)); @@ -202,8 +228,6 @@ impl<'a, 'event> MutableSection<'a, 'event> { } fn compute_whitespace<'event>(s: &mut SectionBody<'event>) -> Option> { - use bstr::ByteSlice; - let first_value_pos = s.0.iter().enumerate().find_map(|(idx, e)| match e { Event::SectionKey(_) => Some(idx), _ => None, diff --git a/git-config/tests/file/access/raw/set_raw_value.rs b/git-config/tests/file/access/raw/set_raw_value.rs index 2e8b5298549..89cf772c5c1 100644 --- a/git-config/tests/file/access/raw/set_raw_value.rs +++ b/git-config/tests/file/access/raw/set_raw_value.rs @@ -2,10 +2,50 @@ fn file(input: &str) -> git_config::File<'static> { input.parse().unwrap() } -#[test] -fn single_line() -> crate::Result { +fn assert_set_value(value: &str) { let mut file = file("[a]k=b\n[a]\nk=c\nk=d"); - file.set_raw_value("a", None, "k", "e".into())?; - assert_eq!(file.raw_value("a", None, "k")?.as_ref(), "e"); - Ok(()) + file.set_raw_value("a", None, "k", value.into()).unwrap(); + assert_eq!(file.raw_value("a", None, "k").unwrap().as_ref(), value); + + let file: git_config::File = file.to_string().parse().unwrap(); + assert_eq!( + file.raw_value("a", None, "k").unwrap().as_ref(), + value, + "{:?} didn't have expected value {:?}", + file.to_string(), + value + ); +} + +#[test] +fn single_line() { + assert_set_value("hello world"); +} + +#[test] +fn starts_with_whitespace() { + assert_set_value("\ta"); + assert_set_value(" a"); +} + +#[test] +fn ends_with_whitespace() { + assert_set_value("a\t"); + assert_set_value("a "); +} + +#[test] +fn quotes_and_backslashes() { + assert_set_value(r#""hello"\"there"\\\b\x"#); +} + +#[test] +fn multi_line() { + assert_set_value("a\nb \n\t c"); +} + +#[test] +fn comment_included() { + assert_set_value(";hello "); + assert_set_value(" # hello"); } diff --git a/git-config/tests/file/access/read_only.rs b/git-config/tests/file/access/read_only.rs index 6d32697f32a..8a7fc9531a0 100644 --- a/git-config/tests/file/access/read_only.rs +++ b/git-config/tests/file/access/read_only.rs @@ -243,12 +243,12 @@ fn complex_quoted_values() { assert_eq!( config.raw_value("core", None, "escape-sequence").unwrap().as_ref(), expected, - "raw_value is normalized" + "raw_value is normalized…" ); assert_eq!( config.string("core", None, "escape-sequence").unwrap().as_ref(), expected, - "and so is the comfort API" + "…and so is the comfort API" ); } diff --git a/git-config/tests/file/access/write.rs b/git-config/tests/file/access/write.rs index 01bb1079453..e06d8263e1d 100644 --- a/git-config/tests/file/access/write.rs +++ b/git-config/tests/file/access/write.rs @@ -15,6 +15,8 @@ fn complex_lossless_roundtrip() { [test] other-quoted = "hello" + implicit + implicit-equal = [test "sub-section \"special\" C:\\root"] bool-explicit = false diff --git a/git-config/tests/file/mutable/value.rs b/git-config/tests/file/mutable/value.rs index 833349b3c40..de67a52170f 100644 --- a/git-config/tests/file/mutable/value.rs +++ b/git-config/tests/file/mutable/value.rs @@ -44,22 +44,54 @@ mod set_string { "[a] k = v".parse().unwrap() } - fn assert_value(f: &git_config::File, expected: &str) { - let file: git_config::File = f.to_string().parse().unwrap(); + fn assert_set_string(expected: &str) { + let mut file = file(); + let mut v = file.raw_value_mut("a", None, "k").unwrap(); + assert_eq!(v.get().unwrap().as_ref(), "v"); + v.set_string(expected); + + assert_eq!(v.get().unwrap().as_ref(), expected); + + let file: git_config::File = file.to_string().parse().unwrap(); assert_eq!(file.raw_value("a", None, "k").expect("present").as_ref(), expected); } #[test] - #[ignore] - fn leading_whitespace_causes_double_quotes() -> crate::Result { - let mut file = file(); - let mut v = file.raw_value_mut("a", None, "k")?; - assert_eq!(v.get()?.as_ref(), "v"); - v.set_string(" v"); + fn leading_whitespace_causes_double_quotes() { + assert_set_string(" v"); + } - assert_eq!(v.get()?.as_ref(), " v"); - assert_value(&file, " v"); - Ok(()) + #[test] + fn single_line() { + assert_set_string("hello world"); + } + + #[test] + fn starts_with_whitespace() { + assert_set_string("\ta"); + assert_set_string(" a"); + } + + #[test] + fn ends_with_whitespace() { + assert_set_string("a\t"); + assert_set_string("a "); + } + + #[test] + fn quotes_and_backslashes() { + assert_set_string(r#""hello"\"there"\\\b\x"#); + } + + #[test] + fn multi_line() { + assert_set_string("a\nb \n\t c"); + } + + #[test] + fn comment_included() { + assert_set_string(";hello "); + assert_set_string(" # hello"); } #[test] diff --git a/git-config/tests/value/normalize.rs b/git-config/tests/value/normalize.rs index 187cae0b794..9c91fd2b032 100644 --- a/git-config/tests/value/normalize.rs +++ b/git-config/tests/value/normalize.rs @@ -68,7 +68,7 @@ fn inner_quotes_are_removed() { } #[test] -fn newline_tab_backspace_are_escapeable() { +fn newline_tab_backspace_are_escapable() { assert_eq!(normalize_bstr(r#"\n\ta\b"#), cow_str("\n\t")); } From 7e03b835bd6f0f5b3f00dbc63e7960ce6364eaef Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 13 Jul 2022 13:07:47 +0800 Subject: [PATCH 183/366] default space is just a single tab, not two ones (#331) --- git-config/src/file/access/mutate.rs | 4 ++-- git-config/src/file/mutable/section.rs | 2 +- git-config/tests/file/mutable/section.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/git-config/src/file/access/mutate.rs b/git-config/src/file/access/mutate.rs index 821d0fabdde..d54550afc28 100644 --- a/git-config/src/file/access/mutate.rs +++ b/git-config/src/file/access/mutate.rs @@ -54,9 +54,9 @@ impl<'event> File<'event> { /// let mut git_config = git_config::File::default(); /// let mut section = git_config.new_section("hello", Some("world".into()))?; /// section.push(section::Key::try_from("a")?, b"b".as_bstr().into()); - /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n\t\ta=b\n"); + /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n\ta=b\n"); /// let _section = git_config.new_section("core", None); - /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n\t\ta=b\n[core]\n"); + /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n\ta=b\n[core]\n"); /// # Ok::<(), Box>(()) /// ``` pub fn new_section( diff --git a/git-config/src/file/mutable/section.rs b/git-config/src/file/mutable/section.rs index f5384845c17..1c032e5e111 100644 --- a/git-config/src/file/mutable/section.rs +++ b/git-config/src/file/mutable/section.rs @@ -237,7 +237,7 @@ fn compute_whitespace<'event>(s: &mut SectionBody<'event>) -> Option Some(s.clone()), _ => None, }), - None => Some("\t\t".as_bytes().as_bstr().into()), + None => Some("\t".as_bytes().as_bstr().into()), } } diff --git a/git-config/tests/file/mutable/section.rs b/git-config/tests/file/mutable/section.rs index 6e5626f4bda..bc2319638e6 100644 --- a/git-config/tests/file/mutable/section.rs +++ b/git-config/tests/file/mutable/section.rs @@ -8,7 +8,7 @@ mod push { for (input, expected) in [ ("[a]\n\t\tb = c", Some("\t\t".into())), ("[a]\nb = c", None), - ("[a]", Some("\t\t".into())), + ("[a]", Some("\t".into())), ("[a]\t\tb = c", Some("\t\t".into())), ("[a]\n\t\t \n \t b = c", Some(" \t ".into())), ] { @@ -29,6 +29,6 @@ mod push { let mut config = git_config::File::default(); let mut section = config.new_section("core", None).unwrap(); section.push(Key::try_from("value").unwrap(), Cow::Borrowed("none".into())); - assert_eq!(config.to_bstring(), "[core]\n\t\tvalue=none\n"); + assert_eq!(config.to_bstring(), "[core]\n\tvalue=none\n"); } } From 511985a8084f2a00e0550e5f2a85c93779385a1b Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 13 Jul 2022 13:34:25 +0800 Subject: [PATCH 184/366] more empty-value tests (#331) --- git-config/src/parse/nom/mod.rs | 11 +----- git-config/src/parse/nom/tests.rs | 41 ++++++++++++++++++++ git-config/tests/file/mutable/multi_value.rs | 15 ++++--- git-config/tests/file/mutable/value.rs | 22 +++++++---- git-config/tests/parse/error.rs | 5 --- 5 files changed, 66 insertions(+), 28 deletions(-) diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index f2cc3a56a81..9a1c8958f92 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -246,7 +246,6 @@ fn key_value_pair<'a>( receive_event(Event::SectionKey(section::Key(Cow::Borrowed(name)))); let (i, whitespace) = opt(take_spaces)(i)?; - if let Some(whitespace) = whitespace { receive_event(Event::Whitespace(Cow::Borrowed(whitespace))); } @@ -352,15 +351,7 @@ fn value_impl<'a>(i: &'a [u8], receive_event: &mut impl FnMut(Event<'a>)) -> IRe } else { // Didn't parse anything at all, newline straight away. receive_event(Event::Value(Cow::Borrowed("".into()))); - receive_event(Event::Newline(Cow::Borrowed("\n".into()))); - newlines += 1; - return Ok(( - i.get(1..).ok_or(nom::Err::Error(NomError { - input: i, - code: ErrorKind::Eof, - }))?, - newlines, - )); + return Ok((&i[0..], newlines)); } } diff --git a/git-config/src/parse/nom/tests.rs b/git-config/src/parse/nom/tests.rs index a3b1fb9800f..a0de4a05ee0 100644 --- a/git-config/src/parse/nom/tests.rs +++ b/git-config/src/parse/nom/tests.rs @@ -253,6 +253,47 @@ mod section { ); } + #[test] + fn section_with_empty_value_simplified() { + let mut node = ParseNode::SectionHeader; + let section_data = b"[a] k="; + assert_eq!( + section(section_data, &mut node).unwrap(), + fully_consumed(( + Section { + section_header: parsed_section_header("a", None), + events: vec![ + whitespace_event(" "), + name_event("k"), + Event::KeyValueSeparator, + value_event(""), + ] + .into() + }, + 0 + )) + ); + + let section_data = b"[a] k=\n"; + assert_eq!( + section(section_data, &mut node).unwrap(), + fully_consumed(( + Section { + section_header: parsed_section_header("a", None), + events: vec![ + whitespace_event(" "), + name_event("k"), + Event::KeyValueSeparator, + value_event(""), + newline_event(), + ] + .into() + }, + 1 + )) + ); + } + #[test] fn section_with_empty_value() { let mut node = ParseNode::SectionHeader; diff --git a/git-config/tests/file/mutable/multi_value.rs b/git-config/tests/file/mutable/multi_value.rs index 3e4365ed94c..3d30be5e50d 100644 --- a/git-config/tests/file/mutable/multi_value.rs +++ b/git-config/tests/file/mutable/multi_value.rs @@ -83,6 +83,15 @@ mod set { ); Ok(()) } + + #[test] + fn all_empty() -> crate::Result { + let mut config = init_config(); + let mut values = config.raw_values_mut("core", None, "a")?; + values.set_owned_values_all(""); + assert_eq!(config.to_string(), "[core]\n a=\n [core]\n a=\n a="); + Ok(()) + } } mod delete { @@ -114,12 +123,6 @@ mod delete { } } -#[test] -#[ignore] -fn empty_value() { - todo!() -} - fn init_config() -> git_config::File<'static> { r#"[core] a=b"100" diff --git a/git-config/tests/file/mutable/value.rs b/git-config/tests/file/mutable/value.rs index de67a52170f..0af9a088cc8 100644 --- a/git-config/tests/file/mutable/value.rs +++ b/git-config/tests/file/mutable/value.rs @@ -52,10 +52,23 @@ mod set_string { assert_eq!(v.get().unwrap().as_ref(), expected); - let file: git_config::File = file.to_string().parse().unwrap(); + let file: git_config::File = match file.to_string().parse() { + Ok(f) => f, + Err(err) => panic!("{:?} failed with: {}", file.to_string(), err), + }; assert_eq!(file.raw_value("a", None, "k").expect("present").as_ref(), expected); } + #[test] + fn empty() { + assert_set_string(""); + } + + #[test] + fn just_whitespace() { + assert_set_string("\t "); + } + #[test] fn leading_whitespace_causes_double_quotes() { assert_set_string(" v"); @@ -92,6 +105,7 @@ mod set_string { fn comment_included() { assert_set_string(";hello "); assert_set_string(" # hello"); + assert_set_string("value then seemingly # comment"); } #[test] @@ -210,12 +224,6 @@ b } } -#[test] -#[ignore] -fn empty_values() { - todo!() -} - fn init_config() -> git_config::File<'static> { use std::convert::TryFrom; git_config::File::try_from( diff --git a/git-config/tests/parse/error.rs b/git-config/tests/parse/error.rs index 47eec9f9eb0..4b6eeaabce5 100644 --- a/git-config/tests/parse/error.rs +++ b/git-config/tests/parse/error.rs @@ -18,11 +18,6 @@ fn to_string_truncates_extra_values() { ); } -#[test] -fn detected_by_fuzz() { - assert!(Events::from_str("[]I=").is_err()); -} - #[test] fn to_string() { let input = "[a_b]\n c=d"; From badd00c402b59994614e653b28bb3e6c5b70d9d1 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 13 Jul 2022 13:37:36 +0800 Subject: [PATCH 185/366] thanks clippy --- git-config/src/file/access/raw.rs | 2 +- git-config/tests/value/normalize.rs | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/git-config/src/file/access/raw.rs b/git-config/src/file/access/raw.rs index 3f7126ffc90..07fb04ed6b7 100644 --- a/git-config/src/file/access/raw.rs +++ b/git-config/src/file/access/raw.rs @@ -306,7 +306,7 @@ impl<'event> File<'event> { new_value: BString, ) -> Result<(), lookup::existing::Error> { self.raw_value_mut(section_name, subsection_name, key) - .map(|mut entry| entry.set_bytes(new_value.into())) + .map(|mut entry| entry.set_bytes(new_value)) } /// Sets a multivar in a given section, optional subsection, and key value. diff --git a/git-config/tests/value/normalize.rs b/git-config/tests/value/normalize.rs index 9c91fd2b032..cc28428aec7 100644 --- a/git-config/tests/value/normalize.rs +++ b/git-config/tests/value/normalize.rs @@ -72,6 +72,11 @@ fn newline_tab_backspace_are_escapable() { assert_eq!(normalize_bstr(r#"\n\ta\b"#), cow_str("\n\t")); } +#[test] +fn tabs_are_not_resolved_to_spaces_unlike_what_git_does() { + assert_eq!(normalize_bstr("\t"), cow_str("\t")); +} + #[test] fn other_escapes_are_ignored_entirely() { assert_eq!( From 15cd1d2ba447ff27819f6cf398d31e96ff11b213 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 13 Jul 2022 15:11:18 +0800 Subject: [PATCH 186/366] refactor (#331) --- git-config/src/file/mutable/section.rs | 84 +++++++++++++++--------- git-config/tests/file/mutable/section.rs | 24 +++++++ 2 files changed, 76 insertions(+), 32 deletions(-) diff --git a/git-config/src/file/mutable/section.rs b/git-config/src/file/mutable/section.rs index 1c032e5e111..1566ce34dc8 100644 --- a/git-config/src/file/mutable/section.rs +++ b/git-config/src/file/mutable/section.rs @@ -18,7 +18,7 @@ use crate::{ pub struct MutableSection<'a, 'event> { section: &'a mut SectionBody<'event>, implicit_newline: bool, - whitespace: Option>, + whitespace: Whitespace<'event>, } /// Mutating methods. @@ -26,7 +26,7 @@ impl<'a, 'event> MutableSection<'a, 'event> { /// Adds an entry to the end of this section. // TODO: multi-line handling - maybe just escape it for now. pub fn push(&mut self, key: Key<'event>, value: Cow<'event, BStr>) { - if let Some(ws) = &self.whitespace { + if let Some(ws) = &self.whitespace.pre_key { self.section.0.push(Event::Whitespace(ws.clone())); } @@ -110,8 +110,9 @@ impl<'a, 'event> MutableSection<'a, 'event> { self.implicit_newline = on; } - /// Sets the exact whitespace to use before each key-value pair, with only whitespace characters - /// being permissible. + /// Sets the exact whitespace to use before each newly created key-value pair, + /// with only whitespace characters being permissible. + /// /// The default is 2 tabs. /// Set to `None` to disable adding whitespace before a key value. /// @@ -126,14 +127,14 @@ impl<'a, 'event> MutableSection<'a, 'event> { .map_or(true, |ws| ws.iter().all(|b| b.is_ascii_whitespace())), "input whitespace must only contain whitespace characters." ); - self.whitespace = whitespace; + self.whitespace.pre_key = whitespace; } /// Returns the whitespace this section will insert before the /// beginning of a key, if any. #[must_use] pub fn leading_whitespace(&self) -> Option<&BStr> { - self.whitespace.as_deref() + self.whitespace.pre_key.as_deref() } } @@ -181,30 +182,7 @@ impl<'a, 'event> MutableSection<'a, 'event> { // TODO: borrow value only to avoid extra copy pub(crate) fn set_internal(&mut self, index: Index, key: Key<'event>, value: BString) -> Size { - let quote = value.get(0).map_or(false, |b| b.is_ascii_whitespace()) - || value - .get(value.len().saturating_sub(1)) - .map_or(false, |b| b.is_ascii_whitespace()) - || value.find_byteset(b";#").is_some(); - - let mut buf: BString = Vec::with_capacity(value.len()).into(); - if quote { - buf.push(b'"'); - } - - for b in value.iter().copied() { - match b { - b'\n' => buf.push_str("\\n"), - b'\t' => buf.push_str("\\t"), - b'"' => buf.push_str("\\\""), - b'\\' => buf.push_str("\\\\"), - _ => buf.push(b), - } - } - - if quote { - buf.push(b'"'); - } + let buf = escape_value(value); self.section.0.insert(index.0, Event::Value(buf.into())); self.section.0.insert(index.0, Event::KeyValueSeparator); @@ -227,18 +205,60 @@ impl<'a, 'event> MutableSection<'a, 'event> { } } -fn compute_whitespace<'event>(s: &mut SectionBody<'event>) -> Option> { +#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] +struct Whitespace<'a> { + pre_key: Option>, + pre_sep: Option>, + post_sep: Option>, +} + +fn compute_whitespace<'a>(s: &mut SectionBody<'a>) -> Whitespace<'a> { let first_value_pos = s.0.iter().enumerate().find_map(|(idx, e)| match e { Event::SectionKey(_) => Some(idx), _ => None, }); - match first_value_pos { + let pre_key = match first_value_pos { Some(pos) => s.0[..pos].iter().rev().next().and_then(|e| match e { Event::Whitespace(s) => Some(s.clone()), _ => None, }), None => Some("\t".as_bytes().as_bstr().into()), + }; + Whitespace { + pre_key, + pre_sep: None, + post_sep: None, + } +} + +fn escape_value(value: impl AsRef) -> BString { + let value = value.as_ref(); + let starts_with_whitespace = value.get(0).map_or(false, |b| b.is_ascii_whitespace()); + let ends_with_whitespace = value + .get(value.len().saturating_sub(1)) + .map_or(false, |b| b.is_ascii_whitespace()); + let contains_comment_indicators = value.find_byteset(b";#").is_some(); + let quote = starts_with_whitespace || ends_with_whitespace || contains_comment_indicators; + + let mut buf: BString = Vec::with_capacity(value.len()).into(); + if quote { + buf.push(b'"'); + } + + for b in value.iter().copied() { + match b { + b'\n' => buf.push_str("\\n"), + b'\t' => buf.push_str("\\t"), + b'"' => buf.push_str("\\\""), + b'\\' => buf.push_str("\\\\"), + _ => buf.push(b), + } + } + + if quote { + buf.push(b'"'); } + buf } impl<'event> Deref for MutableSection<'_, 'event> { diff --git a/git-config/tests/file/mutable/section.rs b/git-config/tests/file/mutable/section.rs index bc2319638e6..e54e4b31801 100644 --- a/git-config/tests/file/mutable/section.rs +++ b/git-config/tests/file/mutable/section.rs @@ -32,3 +32,27 @@ mod push { assert_eq!(config.to_bstring(), "[core]\n\tvalue=none\n"); } } + +mod set_leading_whitespace { + use crate::file::cow_str; + use git_config::parse::section::Key; + use std::convert::TryFrom; + + #[test] + fn any_whitespace_is_ok() -> crate::Result { + let mut config = git_config::File::default(); + let mut section = config.new_section("core", None)?; + section.set_leading_whitespace(cow_str("\n\t").into()); + section.push(Key::try_from("a")?, cow_str("v")); + assert_eq!(config.to_string(), "[core]\n\n\ta=v\n"); + Ok(()) + } + + #[test] + #[should_panic] + fn panics_if_non_whitespace_is_used() { + let mut config = git_config::File::default(); + let mut section = config.new_section("core", None).unwrap(); + section.set_leading_whitespace(cow_str("foo").into()); + } +} From a7eff0166f200a403d4dba320280f20a70e9afc7 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 13 Jul 2022 15:17:52 +0800 Subject: [PATCH 187/366] avoid extra copies when setting values and escaping them (#331) --- git-config/src/file/access/raw.rs | 4 ++-- git-config/src/file/mutable/section.rs | 12 ++++-------- git-config/src/file/mutable/value.rs | 6 +++--- git-config/tests/file/mutable/section.rs | 14 ++++++++------ 4 files changed, 17 insertions(+), 19 deletions(-) diff --git a/git-config/src/file/access/raw.rs b/git-config/src/file/access/raw.rs index 07fb04ed6b7..2c3ed0ab054 100644 --- a/git-config/src/file/access/raw.rs +++ b/git-config/src/file/access/raw.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, collections::HashMap}; -use bstr::{BStr, BString}; +use bstr::BStr; use crate::file::mutable::value::EntryData; use crate::{ @@ -303,7 +303,7 @@ impl<'event> File<'event> { section_name: &str, subsection_name: Option<&str>, key: &str, - new_value: BString, + new_value: &BStr, ) -> Result<(), lookup::existing::Error> { self.raw_value_mut(section_name, subsection_name, key) .map(|mut entry| entry.set_bytes(new_value)) diff --git a/git-config/src/file/mutable/section.rs b/git-config/src/file/mutable/section.rs index 1566ce34dc8..355dffdca49 100644 --- a/git-config/src/file/mutable/section.rs +++ b/git-config/src/file/mutable/section.rs @@ -32,7 +32,7 @@ impl<'a, 'event> MutableSection<'a, 'event> { self.section.0.push(Event::SectionKey(key)); self.section.0.push(Event::KeyValueSeparator); - self.section.0.push(Event::Value(value)); + self.section.0.push(Event::Value(escape_value(value.as_ref()).into())); if self.implicit_newline { self.section.0.push(Event::Newline(BString::from("\n").into())); } @@ -180,11 +180,8 @@ impl<'a, 'event> MutableSection<'a, 'event> { self.section.0.drain(start.0..end.0); } - // TODO: borrow value only to avoid extra copy - pub(crate) fn set_internal(&mut self, index: Index, key: Key<'event>, value: BString) -> Size { - let buf = escape_value(value); - - self.section.0.insert(index.0, Event::Value(buf.into())); + pub(crate) fn set_internal(&mut self, index: Index, key: Key<'event>, value: &BStr) -> Size { + self.section.0.insert(index.0, Event::Value(escape_value(value).into())); self.section.0.insert(index.0, Event::KeyValueSeparator); self.section.0.insert(index.0, Event::SectionKey(key)); @@ -231,8 +228,7 @@ fn compute_whitespace<'a>(s: &mut SectionBody<'a>) -> Whitespace<'a> { } } -fn escape_value(value: impl AsRef) -> BString { - let value = value.as_ref(); +fn escape_value(value: &BStr) -> BString { let starts_with_whitespace = value.get(0).map_or(false, |b| b.is_ascii_whitespace()); let ends_with_whitespace = value .get(value.len().saturating_sub(1)) diff --git a/git-config/src/file/mutable/value.rs b/git-config/src/file/mutable/value.rs index 49c74c3a520..0ebff32f795 100644 --- a/git-config/src/file/mutable/value.rs +++ b/git-config/src/file/mutable/value.rs @@ -29,14 +29,14 @@ impl<'borrow, 'lookup, 'event> MutableValue<'borrow, 'lookup, 'event> { /// Update the value to the provided one. This modifies the value such that /// the Value event(s) are replaced with a single new event containing the /// new value. - pub fn set_string(&mut self, input: impl Into) { - self.set_bytes(input.into().into()); + pub fn set_string(&mut self, input: impl AsRef) { + self.set_bytes(input.as_ref().into()); } /// Update the value to the provided one. This modifies the value such that /// the Value event(s) are replaced with a single new event containing the /// new value. - pub fn set_bytes(&mut self, input: BString) { + pub fn set_bytes(&mut self, input: &BStr) { if self.size.0 > 0 { self.section.delete(self.index, self.index + self.size); } diff --git a/git-config/tests/file/mutable/section.rs b/git-config/tests/file/mutable/section.rs index e54e4b31801..1cb2ba34678 100644 --- a/git-config/tests/file/mutable/section.rs +++ b/git-config/tests/file/mutable/section.rs @@ -1,6 +1,6 @@ mod push { + use crate::file::cow_str; use git_config::parse::section::Key; - use std::borrow::Cow; use std::convert::TryFrom; #[test] @@ -25,11 +25,13 @@ mod push { } #[test] - fn push_splits_values_into_events() { - let mut config = git_config::File::default(); - let mut section = config.new_section("core", None).unwrap(); - section.push(Key::try_from("value").unwrap(), Cow::Borrowed("none".into())); - assert_eq!(config.to_bstring(), "[core]\n\tvalue=none\n"); + fn values_are_escaped() { + for (value, expected) in [("a b", "[a]\n\tk=a b\n")] { + let mut config = git_config::File::default(); + let mut section = config.new_section("a", None).unwrap(); + section.push(Key::try_from("k").unwrap(), cow_str(value)); + assert_eq!(config.to_bstring(), expected); + } } } From 5418bc70e67476f8778656f2d577f1f9aa65ffbe Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 13 Jul 2022 15:56:26 +0800 Subject: [PATCH 188/366] feat: place spaces around `key = value` pairs, or whatever is used in the source configuration. (#331) --- git-config/src/file/access/mutate.rs | 4 +- git-config/src/file/mutable/section.rs | 98 +++++++++++++++++++----- git-config/tests/file/mutable/section.rs | 48 ++++++++---- 3 files changed, 115 insertions(+), 35 deletions(-) diff --git a/git-config/src/file/access/mutate.rs b/git-config/src/file/access/mutate.rs index d54550afc28..35ec182bfde 100644 --- a/git-config/src/file/access/mutate.rs +++ b/git-config/src/file/access/mutate.rs @@ -54,9 +54,9 @@ impl<'event> File<'event> { /// let mut git_config = git_config::File::default(); /// let mut section = git_config.new_section("hello", Some("world".into()))?; /// section.push(section::Key::try_from("a")?, b"b".as_bstr().into()); - /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n\ta=b\n"); + /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n\ta = b\n"); /// let _section = git_config.new_section("core", None); - /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n\ta=b\n[core]\n"); + /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n\ta = b\n[core]\n"); /// # Ok::<(), Box>(()) /// ``` pub fn new_section( diff --git a/git-config/src/file/mutable/section.rs b/git-config/src/file/mutable/section.rs index 355dffdca49..673d081442f 100644 --- a/git-config/src/file/mutable/section.rs +++ b/git-config/src/file/mutable/section.rs @@ -24,14 +24,13 @@ pub struct MutableSection<'a, 'event> { /// Mutating methods. impl<'a, 'event> MutableSection<'a, 'event> { /// Adds an entry to the end of this section. - // TODO: multi-line handling - maybe just escape it for now. pub fn push(&mut self, key: Key<'event>, value: Cow<'event, BStr>) { if let Some(ws) = &self.whitespace.pre_key { self.section.0.push(Event::Whitespace(ws.clone())); } self.section.0.push(Event::SectionKey(key)); - self.section.0.push(Event::KeyValueSeparator); + self.section.0.extend(self.key_value_separators()); self.section.0.push(Event::Value(escape_value(value.as_ref()).into())); if self.implicit_newline { self.section.0.push(Event::Newline(BString::from("\n").into())); @@ -136,6 +135,16 @@ impl<'a, 'event> MutableSection<'a, 'event> { pub fn leading_whitespace(&self) -> Option<&BStr> { self.whitespace.pre_key.as_deref() } + + /// Returns the whitespace to be used before and after the `=` between the key + /// and the value. + /// + /// For example, `k = v` will have `(Some(" "), Some(" "))`, whereas `k=\tv` will + /// have `(None, Some("\t"))`. + #[must_use] + pub fn separator_whitespace(&self) -> (Option<&BStr>, Option<&BStr>) { + (self.whitespace.pre_sep.as_deref(), self.whitespace.post_sep.as_deref()) + } } // Internal methods that may require exact indices for faster operations. @@ -181,11 +190,19 @@ impl<'a, 'event> MutableSection<'a, 'event> { } pub(crate) fn set_internal(&mut self, index: Index, key: Key<'event>, value: &BStr) -> Size { + let mut size = 0; + self.section.0.insert(index.0, Event::Value(escape_value(value).into())); - self.section.0.insert(index.0, Event::KeyValueSeparator); + size += 1; + + let sep_events = self.key_value_separators(); + size += sep_events.len(); + self.section.0.insert_many(index.0, sep_events.into_iter().rev()); + self.section.0.insert(index.0, Event::SectionKey(key)); + size += 1; - Size(3) + Size(size) } /// Performs the removal, assuming the range is valid. @@ -200,6 +217,18 @@ impl<'a, 'event> MutableSection<'a, 'event> { acc }) } + + fn key_value_separators(&self) -> Vec> { + let mut out = Vec::with_capacity(3); + if let Some(ws) = &self.whitespace.pre_sep { + out.push(Event::Whitespace(ws.clone())); + } + out.push(Event::KeyValueSeparator); + if let Some(ws) = &self.whitespace.post_sep { + out.push(Event::Whitespace(ws.clone())); + } + out + } } #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] @@ -209,25 +238,54 @@ struct Whitespace<'a> { post_sep: Option>, } -fn compute_whitespace<'a>(s: &mut SectionBody<'a>) -> Whitespace<'a> { - let first_value_pos = s.0.iter().enumerate().find_map(|(idx, e)| match e { - Event::SectionKey(_) => Some(idx), - _ => None, - }); - let pre_key = match first_value_pos { - Some(pos) => s.0[..pos].iter().rev().next().and_then(|e| match e { - Event::Whitespace(s) => Some(s.clone()), - _ => None, - }), - None => Some("\t".as_bytes().as_bstr().into()), - }; - Whitespace { - pre_key, - pre_sep: None, - post_sep: None, +impl Default for Whitespace<'_> { + fn default() -> Self { + Whitespace { + pre_key: Some(b"\t".as_bstr().into()), + pre_sep: Some(b" ".as_bstr().into()), + post_sep: Some(b" ".as_bstr().into()), + } } } +fn compute_whitespace<'a>(s: &mut SectionBody<'a>) -> Whitespace<'a> { + let key_pos = + s.0.iter() + .enumerate() + .find_map(|(idx, e)| matches!(e, Event::SectionKey(_)).then(|| idx)); + key_pos + .map(|key_pos| { + let pre_key = s.0[..key_pos].iter().rev().next().and_then(|e| match e { + Event::Whitespace(s) => Some(s.clone()), + _ => None, + }); + let from_key = &s.0[key_pos..]; + let (pre_sep, post_sep) = from_key + .iter() + .enumerate() + .find_map(|(idx, e)| matches!(e, Event::KeyValueSeparator).then(|| idx)) + .map(|sep_pos| { + ( + from_key.get(sep_pos - 1).and_then(|e| match e { + Event::Whitespace(ws) => Some(ws.clone()), + _ => None, + }), + from_key.get(sep_pos + 1).and_then(|e| match e { + Event::Whitespace(ws) => Some(ws.clone()), + _ => None, + }), + ) + }) + .unwrap_or_default(); + Whitespace { + pre_key, + pre_sep, + post_sep, + } + }) + .unwrap_or_default() +} + fn escape_value(value: &BStr) -> BString { let starts_with_whitespace = value.get(0).map_or(false, |b| b.is_ascii_whitespace()); let ends_with_whitespace = value diff --git a/git-config/tests/file/mutable/section.rs b/git-config/tests/file/mutable/section.rs index 1cb2ba34678..88c535223ae 100644 --- a/git-config/tests/file/mutable/section.rs +++ b/git-config/tests/file/mutable/section.rs @@ -5,30 +5,52 @@ mod push { #[test] fn whitespace_is_derived_from_whitespace_before_first_value() -> crate::Result { - for (input, expected) in [ - ("[a]\n\t\tb = c", Some("\t\t".into())), - ("[a]\nb = c", None), - ("[a]", Some("\t".into())), - ("[a]\t\tb = c", Some("\t\t".into())), - ("[a]\n\t\t \n \t b = c", Some(" \t ".into())), + for (input, expected_pre_key, expected_sep) in [ + ("[a]\n\t\tb=c", Some("\t\t".into()), (None, None)), + ("[a]\nb= c", None, (None, Some(" "))), + ("[a]", Some("\t".into()), (Some(" "), Some(" "))), + ("[a]\t\tb =c", Some("\t\t".into()), (Some(" "), None)), + ( + "[a]\n\t\t \n \t b = c", + Some(" \t ".into()), + (Some(" "), Some(" ")), + ), ] { let mut config: git_config::File = input.parse()?; + let section = config.section_mut("a", None)?; assert_eq!( - config.section_mut("a", None)?.leading_whitespace(), - expected, - "{:?} should find {:?} as whitespace", + section.leading_whitespace(), + expected_pre_key, + "{:?} should find {:?} as leading whitespace", input, - expected - ) + expected_pre_key + ); + + let (pre_sep, post_sep) = expected_sep; + assert_eq!( + section.separator_whitespace(), + (pre_sep.map(|s| s.into()), post_sep.map(|s| s.into())), + "{:?} should find {:?} as sep whitespace", + input, + expected_sep + ); } Ok(()) } #[test] fn values_are_escaped() { - for (value, expected) in [("a b", "[a]\n\tk=a b\n")] { + for (value, expected) in [ + ("a b", "[a]\n\tk = a b"), + (" a b", "[a]\n\tk = \" a b\""), + ("a b\t", "[a]\n\tk = \"a b\\t\""), + (";c", "[a]\n\tk = \";c\""), + ("#c", "[a]\n\tk = \"#c\""), + ("a\nb\n\tc", "[a]\n\tk = a\\nb\\n\\tc"), + ] { let mut config = git_config::File::default(); let mut section = config.new_section("a", None).unwrap(); + section.set_implicit_newline(false); section.push(Key::try_from("k").unwrap(), cow_str(value)); assert_eq!(config.to_bstring(), expected); } @@ -46,7 +68,7 @@ mod set_leading_whitespace { let mut section = config.new_section("core", None)?; section.set_leading_whitespace(cow_str("\n\t").into()); section.push(Key::try_from("a")?, cow_str("v")); - assert_eq!(config.to_string(), "[core]\n\n\ta=v\n"); + assert_eq!(config.to_string(), "[core]\n\n\ta = v\n"); Ok(()) } From c9a239095511ae95fb5efbbc9207293641b623f7 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 13 Jul 2022 16:19:03 +0800 Subject: [PATCH 189/366] thanks clippy --- git-config/src/file/mutable/value.rs | 4 ++-- git-config/tests/file/mutable/value.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/git-config/src/file/mutable/value.rs b/git-config/src/file/mutable/value.rs index 0ebff32f795..45e9eb087b0 100644 --- a/git-config/src/file/mutable/value.rs +++ b/git-config/src/file/mutable/value.rs @@ -246,14 +246,14 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { section: &mut SectionBody<'event>, section_id: SectionBodyId, offset_index: usize, - input: Cow<'a, BStr>, + value: Cow<'a, BStr>, ) { let (offset, size) = MutableMultiValue::index_and_size(offsets, section_id, offset_index); let section = section.as_mut(); section.drain(offset..offset + size); MutableMultiValue::set_offset(offsets, section_id, offset_index, 3); - section.insert(offset, Event::Value(input)); + section.insert(offset, Event::Value(value)); section.insert(offset, Event::KeyValueSeparator); section.insert(offset, Event::SectionKey(key.to_owned())); } diff --git a/git-config/tests/file/mutable/value.rs b/git-config/tests/file/mutable/value.rs index 0af9a088cc8..bc3f6a5b22c 100644 --- a/git-config/tests/file/mutable/value.rs +++ b/git-config/tests/file/mutable/value.rs @@ -113,7 +113,7 @@ mod set_string { let mut config = init_config(); let mut value = config.raw_value_mut("core", None, "a")?; - value.set_string("hello world".to_string()); + value.set_string("hello world"); assert_eq!( config.to_string(), r#"[core] @@ -176,7 +176,7 @@ mod delete { let mut value = config.raw_value_mut("core", None, "a")?; value.delete(); - value.set_string("hello world".to_string()); + value.set_string("hello world"); assert_eq!( config.to_string(), r#"[core] From 048b92531eb877a5a128e702504891bf1e31becf Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 13 Jul 2022 16:53:34 +0800 Subject: [PATCH 190/366] fix: `file::MutableMultiValue` escapes input values and maintains key separator specific whitespace. (#331) --- git-config/src/file/mutable/mod.rs | 106 +++++++++++++++++++ git-config/src/file/mutable/section.rs | 105 +----------------- git-config/src/file/mutable/value.rs | 9 +- git-config/tests/file/mutable/multi_value.rs | 45 ++++++-- 4 files changed, 152 insertions(+), 113 deletions(-) diff --git a/git-config/src/file/mutable/mod.rs b/git-config/src/file/mutable/mod.rs index 0da513dea9f..31c2d18736e 100644 --- a/git-config/src/file/mutable/mod.rs +++ b/git-config/src/file/mutable/mod.rs @@ -1,2 +1,108 @@ +use crate::file::SectionBody; +use crate::parse::Event; +use bstr::{BStr, BString}; +use bstr::{ByteSlice, ByteVec}; +use std::borrow::Cow; + +fn escape_value(value: &BStr) -> BString { + let starts_with_whitespace = value.get(0).map_or(false, |b| b.is_ascii_whitespace()); + let ends_with_whitespace = value + .get(value.len().saturating_sub(1)) + .map_or(false, |b| b.is_ascii_whitespace()); + let contains_comment_indicators = value.find_byteset(b";#").is_some(); + let quote = starts_with_whitespace || ends_with_whitespace || contains_comment_indicators; + + let mut buf: BString = Vec::with_capacity(value.len()).into(); + if quote { + buf.push(b'"'); + } + + for b in value.iter().copied() { + match b { + b'\n' => buf.push_str("\\n"), + b'\t' => buf.push_str("\\t"), + b'"' => buf.push_str("\\\""), + b'\\' => buf.push_str("\\\\"), + _ => buf.push(b), + } + } + + if quote { + buf.push(b'"'); + } + buf +} + +#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] +struct Whitespace<'a> { + pre_key: Option>, + pre_sep: Option>, + post_sep: Option>, +} + +impl Default for Whitespace<'_> { + fn default() -> Self { + Whitespace { + pre_key: Some(b"\t".as_bstr().into()), + pre_sep: Some(b" ".as_bstr().into()), + post_sep: Some(b" ".as_bstr().into()), + } + } +} + +impl<'a> Whitespace<'a> { + fn key_value_separators(&self) -> Vec> { + let mut out = Vec::with_capacity(3); + if let Some(ws) = &self.pre_sep { + out.push(Event::Whitespace(ws.clone())); + } + out.push(Event::KeyValueSeparator); + if let Some(ws) = &self.post_sep { + out.push(Event::Whitespace(ws.clone())); + } + out + } +} + +impl<'a> From<&SectionBody<'a>> for Whitespace<'a> { + fn from(s: &SectionBody<'a>) -> Self { + let key_pos = + s.0.iter() + .enumerate() + .find_map(|(idx, e)| matches!(e, Event::SectionKey(_)).then(|| idx)); + key_pos + .map(|key_pos| { + let pre_key = s.0[..key_pos].iter().rev().next().and_then(|e| match e { + Event::Whitespace(s) => Some(s.clone()), + _ => None, + }); + let from_key = &s.0[key_pos..]; + let (pre_sep, post_sep) = from_key + .iter() + .enumerate() + .find_map(|(idx, e)| matches!(e, Event::KeyValueSeparator).then(|| idx)) + .map(|sep_pos| { + ( + from_key.get(sep_pos - 1).and_then(|e| match e { + Event::Whitespace(ws) => Some(ws.clone()), + _ => None, + }), + from_key.get(sep_pos + 1).and_then(|e| match e { + Event::Whitespace(ws) => Some(ws.clone()), + _ => None, + }), + ) + }) + .unwrap_or_default(); + Whitespace { + pre_key, + pre_sep, + post_sep, + } + }) + .unwrap_or_default() + } +} + pub(crate) mod section; pub(crate) mod value; diff --git a/git-config/src/file/mutable/section.rs b/git-config/src/file/mutable/section.rs index 673d081442f..fe2eac93198 100644 --- a/git-config/src/file/mutable/section.rs +++ b/git-config/src/file/mutable/section.rs @@ -4,8 +4,9 @@ use std::{ ops::{Deref, Range}, }; -use bstr::{BStr, BString, ByteSlice, ByteVec}; +use bstr::{BStr, BString, ByteVec}; +use crate::file::mutable::{escape_value, Whitespace}; use crate::{ file::{Index, Size}, lookup, parse, @@ -30,7 +31,7 @@ impl<'a, 'event> MutableSection<'a, 'event> { } self.section.0.push(Event::SectionKey(key)); - self.section.0.extend(self.key_value_separators()); + self.section.0.extend(self.whitespace.key_value_separators()); self.section.0.push(Event::Value(escape_value(value.as_ref()).into())); if self.implicit_newline { self.section.0.push(Event::Newline(BString::from("\n").into())); @@ -150,7 +151,7 @@ impl<'a, 'event> MutableSection<'a, 'event> { // Internal methods that may require exact indices for faster operations. impl<'a, 'event> MutableSection<'a, 'event> { pub(crate) fn new(section: &'a mut SectionBody<'event>) -> Self { - let whitespace = compute_whitespace(section); + let whitespace = (&*section).into(); Self { section, implicit_newline: true, @@ -195,7 +196,7 @@ impl<'a, 'event> MutableSection<'a, 'event> { self.section.0.insert(index.0, Event::Value(escape_value(value).into())); size += 1; - let sep_events = self.key_value_separators(); + let sep_events = self.whitespace.key_value_separators(); size += sep_events.len(); self.section.0.insert_many(index.0, sep_events.into_iter().rev()); @@ -217,102 +218,6 @@ impl<'a, 'event> MutableSection<'a, 'event> { acc }) } - - fn key_value_separators(&self) -> Vec> { - let mut out = Vec::with_capacity(3); - if let Some(ws) = &self.whitespace.pre_sep { - out.push(Event::Whitespace(ws.clone())); - } - out.push(Event::KeyValueSeparator); - if let Some(ws) = &self.whitespace.post_sep { - out.push(Event::Whitespace(ws.clone())); - } - out - } -} - -#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] -struct Whitespace<'a> { - pre_key: Option>, - pre_sep: Option>, - post_sep: Option>, -} - -impl Default for Whitespace<'_> { - fn default() -> Self { - Whitespace { - pre_key: Some(b"\t".as_bstr().into()), - pre_sep: Some(b" ".as_bstr().into()), - post_sep: Some(b" ".as_bstr().into()), - } - } -} - -fn compute_whitespace<'a>(s: &mut SectionBody<'a>) -> Whitespace<'a> { - let key_pos = - s.0.iter() - .enumerate() - .find_map(|(idx, e)| matches!(e, Event::SectionKey(_)).then(|| idx)); - key_pos - .map(|key_pos| { - let pre_key = s.0[..key_pos].iter().rev().next().and_then(|e| match e { - Event::Whitespace(s) => Some(s.clone()), - _ => None, - }); - let from_key = &s.0[key_pos..]; - let (pre_sep, post_sep) = from_key - .iter() - .enumerate() - .find_map(|(idx, e)| matches!(e, Event::KeyValueSeparator).then(|| idx)) - .map(|sep_pos| { - ( - from_key.get(sep_pos - 1).and_then(|e| match e { - Event::Whitespace(ws) => Some(ws.clone()), - _ => None, - }), - from_key.get(sep_pos + 1).and_then(|e| match e { - Event::Whitespace(ws) => Some(ws.clone()), - _ => None, - }), - ) - }) - .unwrap_or_default(); - Whitespace { - pre_key, - pre_sep, - post_sep, - } - }) - .unwrap_or_default() -} - -fn escape_value(value: &BStr) -> BString { - let starts_with_whitespace = value.get(0).map_or(false, |b| b.is_ascii_whitespace()); - let ends_with_whitespace = value - .get(value.len().saturating_sub(1)) - .map_or(false, |b| b.is_ascii_whitespace()); - let contains_comment_indicators = value.find_byteset(b";#").is_some(); - let quote = starts_with_whitespace || ends_with_whitespace || contains_comment_indicators; - - let mut buf: BString = Vec::with_capacity(value.len()).into(); - if quote { - buf.push(b'"'); - } - - for b in value.iter().copied() { - match b { - b'\n' => buf.push_str("\\n"), - b'\t' => buf.push_str("\\t"), - b'"' => buf.push_str("\\\""), - b'\\' => buf.push_str("\\\\"), - _ => buf.push(b), - } - } - - if quote { - buf.push(b'"'); - } - buf } impl<'event> Deref for MutableSection<'_, 'event> { diff --git a/git-config/src/file/mutable/value.rs b/git-config/src/file/mutable/value.rs index 45e9eb087b0..2c5d799a28c 100644 --- a/git-config/src/file/mutable/value.rs +++ b/git-config/src/file/mutable/value.rs @@ -3,6 +3,7 @@ use std::{borrow::Cow, collections::HashMap, ops::DerefMut}; use bstr::{BStr, BString, ByteVec}; use crate::file::mutable::section::{MutableSection, SectionBody}; +use crate::file::mutable::{escape_value, Whitespace}; use crate::{ file::{Index, SectionBodyId, Size}, lookup, @@ -249,12 +250,14 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { value: Cow<'a, BStr>, ) { let (offset, size) = MutableMultiValue::index_and_size(offsets, section_id, offset_index); + let whitespace: Whitespace<'_> = (&*section).into(); let section = section.as_mut(); section.drain(offset..offset + size); - MutableMultiValue::set_offset(offsets, section_id, offset_index, 3); - section.insert(offset, Event::Value(value)); - section.insert(offset, Event::KeyValueSeparator); + let key_sep_events = whitespace.key_value_separators(); + MutableMultiValue::set_offset(offsets, section_id, offset_index, 2 + key_sep_events.len()); + section.insert(offset, Event::Value(escape_value(value.as_ref()).into())); + section.insert_many(offset, key_sep_events.into_iter().rev()); section.insert(offset, Event::SectionKey(key.to_owned())); } diff --git a/git-config/tests/file/mutable/multi_value.rs b/git-config/tests/file/mutable/multi_value.rs index 3d30be5e50d..6e406246cc1 100644 --- a/git-config/tests/file/mutable/multi_value.rs +++ b/git-config/tests/file/mutable/multi_value.rs @@ -46,8 +46,27 @@ mod access { } mod set { + use crate::file::cow_str; use crate::file::mutable::multi_value::init_config; + #[test] + fn values_are_escaped() -> crate::Result { + for value in ["a b", " a b", "a b\t", ";c", "#c", "a\nb\n\tc"] { + let mut config = init_config(); + let mut values = config.raw_values_mut("core", None, "a")?; + values.set_values_all(value.into()); + + let config_str = config.to_string(); + let config: git_config::File = config_str.parse()?; + assert_eq!( + config.raw_values("core", None, "a")?, + vec![cow_str(value), cow_str(value), cow_str(value)], + "{config_str:?}" + ); + } + Ok(()) + } + #[test] fn single_at_start() -> crate::Result { let mut config = init_config(); @@ -55,7 +74,7 @@ mod set { values.set_string(0, "Hello".into()); assert_eq!( config.to_string(), - "[core]\n a=Hello\n [core]\n a=d\n a=f" + "[core]\n a = Hello\n [core]\n a =d\n a= f" ); Ok(()) } @@ -67,7 +86,7 @@ mod set { values.set_string(2, "Hello".into()); assert_eq!( config.to_string(), - "[core]\n a=b\"100\"\n [core]\n a=d\n a=Hello" + "[core]\n a = b\"100\"\n [core]\n a =d\n a= Hello" ); Ok(()) } @@ -79,7 +98,7 @@ mod set { values.set_owned_values_all("Hello"); assert_eq!( config.to_string(), - "[core]\n a=Hello\n [core]\n a=Hello\n a=Hello" + "[core]\n a = Hello\n [core]\n a= Hello\n a =Hello" ); Ok(()) } @@ -89,7 +108,10 @@ mod set { let mut config = init_config(); let mut values = config.raw_values_mut("core", None, "a")?; values.set_owned_values_all(""); - assert_eq!(config.to_string(), "[core]\n a=\n [core]\n a=\n a="); + assert_eq!( + config.to_string(), + "[core]\n a = \n [core]\n a= \n a =" + ); Ok(()) } } @@ -103,12 +125,15 @@ mod delete { { let mut values = config.raw_values_mut("core", None, "a")?; values.delete(0); - assert_eq!(config.to_string(), "[core]\n \n [core]\n a=d\n a=f",); + assert_eq!( + config.to_string(), + "[core]\n \n [core]\n a =d\n a= f", + ); } let mut values = config.raw_values_mut("core", None, "a")?; values.delete(1); - assert_eq!(config.to_string(), "[core]\n \n [core]\n a=d\n ",); + assert_eq!(config.to_string(), "[core]\n \n [core]\n a =d\n "); Ok(()) } @@ -118,17 +143,17 @@ mod delete { let mut values = config.raw_values_mut("core", None, "a")?; values.delete_all(); assert!(values.get().is_err()); - assert_eq!(config.to_string(), "[core]\n \n [core]\n \n ",); + assert_eq!(config.to_string(), "[core]\n \n [core]\n \n "); Ok(()) } } fn init_config() -> git_config::File<'static> { r#"[core] - a=b"100" + a = b"100" [core] - a=d - a=f"# + a =d + a= f"# .parse() .unwrap() } From 0a7391a6575f4035c51a46d34fa20c69e9d078e9 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 13 Jul 2022 17:11:02 +0800 Subject: [PATCH 191/366] change!: conform APIs of `file::MutableValue` and `file::MutableMultiValue`. (#331) There are more renames and removals than worth mentioning here given the current adoption of the crate. --- git-config/src/file/access/raw.rs | 26 ++++----- git-config/src/file/mutable/value.rs | 56 ++++---------------- git-config/tests/file/mutable/multi_value.rs | 20 ++++--- 3 files changed, 36 insertions(+), 66 deletions(-) diff --git a/git-config/src/file/access/raw.rs b/git-config/src/file/access/raw.rs index 2c3ed0ab054..47c60389fb4 100644 --- a/git-config/src/file/access/raw.rs +++ b/git-config/src/file/access/raw.rs @@ -185,7 +185,7 @@ impl<'event> File<'event> { /// ] /// ); /// - /// git_config.raw_values_mut("core", None, "a")?.set_str_all("g"); + /// git_config.raw_values_mut("core", None, "a")?.set_all("g"); /// /// assert_eq!( /// git_config.raw_values("core", None, "a")?, @@ -306,7 +306,7 @@ impl<'event> File<'event> { new_value: &BStr, ) -> Result<(), lookup::existing::Error> { self.raw_value_mut(section_name, subsection_name, key) - .map(|mut entry| entry.set_bytes(new_value)) + .map(|mut entry| entry.set(new_value)) } /// Sets a multivar in a given section, optional subsection, and key value. @@ -344,9 +344,9 @@ impl<'event> File<'event> { /// # use bstr::BStr; /// # let mut git_config = git_config::File::try_from("[core]a=b\n[core]\na=c\na=d").unwrap(); /// let new_values = vec![ - /// Cow::::Borrowed("x".into()), - /// Cow::::Borrowed("y".into()), - /// Cow::::Borrowed("z".into()), + /// "x".into(), + /// "y".into(), + /// "z".into(), /// ]; /// git_config.set_raw_multi_value("core", None, "a", new_values.into_iter())?; /// let fetched_config = git_config.raw_values("core", None, "a")?; @@ -365,8 +365,8 @@ impl<'event> File<'event> { /// # use bstr::BStr; /// # let mut git_config = git_config::File::try_from("[core]a=b\n[core]\na=c\na=d").unwrap(); /// let new_values = vec![ - /// Cow::::Borrowed("x".into()), - /// Cow::::Borrowed("y".into()), + /// "x".into(), + /// "y".into(), /// ]; /// git_config.set_raw_multi_value("core", None, "a", new_values.into_iter())?; /// let fetched_config = git_config.raw_values("core", None, "a")?; @@ -384,21 +384,21 @@ impl<'event> File<'event> { /// # use bstr::BStr; /// # let mut git_config = git_config::File::try_from("[core]a=b\n[core]\na=c\na=d").unwrap(); /// let new_values = vec![ - /// Cow::::Borrowed("x".into()), - /// Cow::::Borrowed("y".into()), - /// Cow::::Borrowed("z".into()), - /// Cow::::Borrowed("discarded".into()), + /// "x".into(), + /// "y".into(), + /// "z".into(), + /// "discarded".into(), /// ]; /// git_config.set_raw_multi_value("core", None, "a", new_values)?; /// assert!(!git_config.raw_values("core", None, "a")?.contains(&Cow::::Borrowed("discarded".into()))); /// # Ok::<(), git_config::lookup::existing::Error>(()) /// ``` - pub fn set_raw_multi_value( + pub fn set_raw_multi_value<'a>( &mut self, section_name: &str, subsection_name: Option<&str>, key: &str, - new_values: impl IntoIterator>, + new_values: impl IntoIterator, ) -> Result<(), lookup::existing::Error> { self.raw_values_mut(section_name, subsection_name, key) .map(|mut v| v.set_values(new_values)) diff --git a/git-config/src/file/mutable/value.rs b/git-config/src/file/mutable/value.rs index 2c5d799a28c..98a2263abd1 100644 --- a/git-config/src/file/mutable/value.rs +++ b/git-config/src/file/mutable/value.rs @@ -31,13 +31,13 @@ impl<'borrow, 'lookup, 'event> MutableValue<'borrow, 'lookup, 'event> { /// the Value event(s) are replaced with a single new event containing the /// new value. pub fn set_string(&mut self, input: impl AsRef) { - self.set_bytes(input.as_ref().into()); + self.set(input.as_ref().into()); } /// Update the value to the provided one. This modifies the value such that /// the Value event(s) are replaced with a single new event containing the /// new value. - pub fn set_bytes(&mut self, input: &BStr) { + pub fn set(&mut self, input: &BStr) { if self.size.0 > 0 { self.section.delete(self.index, self.index + self.size); } @@ -132,8 +132,8 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { /// # Safety /// /// This will panic if the index is out of range. - pub fn set_string(&mut self, index: usize, input: String) { - self.set_bytes(index, input); + pub fn set_string_at(&mut self, index: usize, input: impl AsRef) { + self.set_at(index, input.as_ref().into()); } /// Sets the value at the given index. @@ -141,16 +141,7 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { /// # Safety /// /// This will panic if the index is out of range. - pub fn set_bytes(&mut self, index: usize, input: impl Into) { - self.set_value(index, Cow::Owned(input.into())); - } - - /// Sets the value at the given index. - /// - /// # Safety - /// - /// This will panic if the index is out of range. - pub fn set_value(&mut self, index: usize, input: Cow<'event, BStr>) { + pub fn set_at(&mut self, index: usize, input: &BStr) { let EntryData { section_id, offset_index, @@ -172,7 +163,7 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { /// remaining values are ignored. /// /// [`zip`]: std::iter::Iterator::zip - pub fn set_values(&mut self, input: impl IntoIterator>) { + pub fn set_values<'a>(&mut self, input: impl IntoIterator) { for ( EntryData { section_id, @@ -192,39 +183,14 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { } } - /// Sets all values in this multivar to the provided one by copying the - /// `input` string to all values. - pub fn set_str_all(&mut self, input: &str) { - self.set_owned_values_all(input); - } - - /// Sets all values in this multivar to the provided one by copying the - /// `input` bytes to all values. - pub fn set_owned_values_all<'a>(&mut self, input: impl Into<&'a BStr>) { - let input = input.into(); - for EntryData { - section_id, - offset_index, - } in &self.indices_and_sizes - { - Self::set_value_inner( - &self.key, - &mut self.offsets, - self.section.get_mut(section_id).expect("known section id"), - *section_id, - *offset_index, - Cow::Owned(input.to_owned()), - ); - } - } - /// Sets all values in this multivar to the provided one without owning the /// provided input. Consider using [`Self::set_owned_values_all`] or /// [`Self::set_str_all`] unless you have a strict performance or memory /// need for a more ergonomic interface. /// /// [`File`]: crate::File - pub fn set_values_all(&mut self, input: &'event BStr) { + pub fn set_all<'a>(&mut self, input: impl Into<&'a BStr>) { + let input = input.into(); for EntryData { section_id, offset_index, @@ -236,7 +202,7 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { self.section.get_mut(section_id).expect("known section id"), *section_id, *offset_index, - Cow::Borrowed(input), + input, ); } } @@ -247,7 +213,7 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { section: &mut SectionBody<'event>, section_id: SectionBodyId, offset_index: usize, - value: Cow<'a, BStr>, + value: &BStr, ) { let (offset, size) = MutableMultiValue::index_and_size(offsets, section_id, offset_index); let whitespace: Whitespace<'_> = (&*section).into(); @@ -256,7 +222,7 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { let key_sep_events = whitespace.key_value_separators(); MutableMultiValue::set_offset(offsets, section_id, offset_index, 2 + key_sep_events.len()); - section.insert(offset, Event::Value(escape_value(value.as_ref()).into())); + section.insert(offset, Event::Value(escape_value(value).into())); section.insert_many(offset, key_sep_events.into_iter().rev()); section.insert(offset, Event::SectionKey(key.to_owned())); } diff --git a/git-config/tests/file/mutable/multi_value.rs b/git-config/tests/file/mutable/multi_value.rs index 6e406246cc1..5b0305bf95b 100644 --- a/git-config/tests/file/mutable/multi_value.rs +++ b/git-config/tests/file/mutable/multi_value.rs @@ -18,13 +18,17 @@ mod get { "100" [core] a=d\ -b +"b "\ +c a=f\ -a"# + a"# .parse()?; let mut values = config.raw_values_mut("core", None, "a")?; - assert_eq!(&*values.get()?, vec![cow_str("b100"), cow_str("db"), cow_str("fa"),]); + assert_eq!( + &*values.get()?, + vec![cow_str("b100"), cow_str("db c"), cow_str("f a"),] + ); values.delete_all(); assert!(values.get().is_err()); @@ -54,7 +58,7 @@ mod set { for value in ["a b", " a b", "a b\t", ";c", "#c", "a\nb\n\tc"] { let mut config = init_config(); let mut values = config.raw_values_mut("core", None, "a")?; - values.set_values_all(value.into()); + values.set_all(value); let config_str = config.to_string(); let config: git_config::File = config_str.parse()?; @@ -71,7 +75,7 @@ mod set { fn single_at_start() -> crate::Result { let mut config = init_config(); let mut values = config.raw_values_mut("core", None, "a")?; - values.set_string(0, "Hello".into()); + values.set_string_at(0, "Hello"); assert_eq!( config.to_string(), "[core]\n a = Hello\n [core]\n a =d\n a= f" @@ -83,7 +87,7 @@ mod set { fn single_at_end() -> crate::Result { let mut config = init_config(); let mut values = config.raw_values_mut("core", None, "a")?; - values.set_string(2, "Hello".into()); + values.set_string_at(2, "Hello"); assert_eq!( config.to_string(), "[core]\n a = b\"100\"\n [core]\n a =d\n a= Hello" @@ -95,7 +99,7 @@ mod set { fn all() -> crate::Result { let mut config = init_config(); let mut values = config.raw_values_mut("core", None, "a")?; - values.set_owned_values_all("Hello"); + values.set_all("Hello"); assert_eq!( config.to_string(), "[core]\n a = Hello\n [core]\n a= Hello\n a =Hello" @@ -107,7 +111,7 @@ mod set { fn all_empty() -> crate::Result { let mut config = init_config(); let mut values = config.raw_values_mut("core", None, "a")?; - values.set_owned_values_all(""); + values.set_all(""); assert_eq!( config.to_string(), "[core]\n a = \n [core]\n a= \n a =" From aa9fdb0febfb29f906eb81e4378f07ef01b03e05 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 13 Jul 2022 18:29:23 +0800 Subject: [PATCH 192/366] make fmt --- git-config/src/file/access/mutate.rs | 3 +-- git-config/src/file/access/raw.rs | 3 +-- git-config/src/file/access/write.rs | 3 ++- git-config/src/file/impls.rs | 3 +-- git-config/src/file/init/from_env.rs | 8 ++++++-- git-config/src/file/init/resolve_includes.rs | 10 +++++++--- git-config/src/file/mutable/mod.rs | 8 ++++---- git-config/src/file/mutable/section.rs | 6 ++++-- git-config/src/file/mutable/value.rs | 11 ++++++++--- git-config/src/fs.rs | 6 ++++-- git-config/src/parse/section/header.rs | 11 +++++++---- git-config/src/parse/tests.rs | 6 +++--- git-config/tests/file/access/mutate.rs | 7 +++---- git-config/tests/file/init/from_env.rs | 7 ++++--- .../includes/conditional/gitdir/util.rs | 6 ++++-- .../init/from_paths/includes/conditional/mod.rs | 6 ++---- .../init/from_paths/includes/unconditional.rs | 6 ++---- git-config/tests/file/mutable/multi_value.rs | 6 ++---- git-config/tests/file/mutable/section.rs | 12 ++++++++---- git-config/tests/file/mutable/value.rs | 3 ++- git-config/tests/parse/from_bytes.rs | 3 ++- git-config/tests/parse/mod.rs | 3 +-- git-config/tests/parse/section.rs | 17 ++++++++++------- 23 files changed, 88 insertions(+), 66 deletions(-) diff --git a/git-config/src/file/access/mutate.rs b/git-config/src/file/access/mutate.rs index 35ec182bfde..1356f92bf32 100644 --- a/git-config/src/file/access/mutate.rs +++ b/git-config/src/file/access/mutate.rs @@ -1,8 +1,7 @@ use std::borrow::Cow; -use crate::file::rename_section; use crate::{ - file::{MutableSection, SectionBody}, + file::{rename_section, MutableSection, SectionBody}, lookup, parse::section, File, diff --git a/git-config/src/file/access/raw.rs b/git-config/src/file/access/raw.rs index 47c60389fb4..5c48c7b3f75 100644 --- a/git-config/src/file/access/raw.rs +++ b/git-config/src/file/access/raw.rs @@ -2,9 +2,8 @@ use std::{borrow::Cow, collections::HashMap}; use bstr::BStr; -use crate::file::mutable::value::EntryData; use crate::{ - file::{Index, MutableMultiValue, MutableSection, MutableValue, Size}, + file::{mutable::value::EntryData, Index, MutableMultiValue, MutableSection, MutableValue, Size}, lookup, parse::{section, Event}, File, diff --git a/git-config/src/file/access/write.rs b/git-config/src/file/access/write.rs index 59ed856b9cc..c986f220bd5 100644 --- a/git-config/src/file/access/write.rs +++ b/git-config/src/file/access/write.rs @@ -1,6 +1,7 @@ -use crate::File; use bstr::BString; +use crate::File; + impl File<'_> { /// Serialize this type into a `BString` for convenience. /// diff --git a/git-config/src/file/impls.rs b/git-config/src/file/impls.rs index ee67e33797b..bf756d948f6 100644 --- a/git-config/src/file/impls.rs +++ b/git-config/src/file/impls.rs @@ -1,5 +1,4 @@ -use std::str::FromStr; -use std::{convert::TryFrom, fmt::Display}; +use std::{convert::TryFrom, fmt::Display, str::FromStr}; use bstr::{BStr, BString}; diff --git a/git-config/src/file/init/from_env.rs b/git-config/src/file/init/from_env.rs index 317ebf98b63..ce10e877d43 100644 --- a/git-config/src/file/init/from_env.rs +++ b/git-config/src/file/init/from_env.rs @@ -2,8 +2,12 @@ use std::{borrow::Cow, path::PathBuf}; use bstr::BString; -use crate::file::from_paths; -use crate::{file::init::resolve_includes, parse::section, path::interpolate, File}; +use crate::{ + file::{from_paths, init::resolve_includes}, + parse::section, + path::interpolate, + File, +}; /// Represents the errors that may occur when calling [`File::from_env`][crate::File::from_env()]. #[derive(Debug, thiserror::Error)] diff --git a/git-config/src/file/init/resolve_includes.rs b/git-config/src/file/init/resolve_includes.rs index a6dc59e0124..6d73690c320 100644 --- a/git-config/src/file/init/resolve_includes.rs +++ b/git-config/src/file/init/resolve_includes.rs @@ -6,9 +6,13 @@ use std::{ use bstr::{BStr, BString, ByteSlice, ByteVec}; use git_ref::Category; -use crate::file::init::from_paths; -use crate::file::init::from_paths::Options; -use crate::{file::SectionBodyId, File}; +use crate::{ + file::{ + init::{from_paths, from_paths::Options}, + SectionBodyId, + }, + File, +}; pub(crate) fn resolve_includes( conf: &mut File<'static>, diff --git a/git-config/src/file/mutable/mod.rs b/git-config/src/file/mutable/mod.rs index 31c2d18736e..deed10148af 100644 --- a/git-config/src/file/mutable/mod.rs +++ b/git-config/src/file/mutable/mod.rs @@ -1,9 +1,9 @@ -use crate::file::SectionBody; -use crate::parse::Event; -use bstr::{BStr, BString}; -use bstr::{ByteSlice, ByteVec}; use std::borrow::Cow; +use bstr::{BStr, BString, ByteSlice, ByteVec}; + +use crate::{file::SectionBody, parse::Event}; + fn escape_value(value: &BStr) -> BString { let starts_with_whitespace = value.get(0).map_or(false, |b| b.is_ascii_whitespace()); let ends_with_whitespace = value diff --git a/git-config/src/file/mutable/section.rs b/git-config/src/file/mutable/section.rs index fe2eac93198..0dd3126425b 100644 --- a/git-config/src/file/mutable/section.rs +++ b/git-config/src/file/mutable/section.rs @@ -6,9 +6,11 @@ use std::{ use bstr::{BStr, BString, ByteVec}; -use crate::file::mutable::{escape_value, Whitespace}; use crate::{ - file::{Index, Size}, + file::{ + mutable::{escape_value, Whitespace}, + Index, Size, + }, lookup, parse, parse::{section::Key, Event}, value::{normalize, normalize_bstr, normalize_bstring}, diff --git a/git-config/src/file/mutable/value.rs b/git-config/src/file/mutable/value.rs index 98a2263abd1..88044cfe91c 100644 --- a/git-config/src/file/mutable/value.rs +++ b/git-config/src/file/mutable/value.rs @@ -2,10 +2,15 @@ use std::{borrow::Cow, collections::HashMap, ops::DerefMut}; use bstr::{BStr, BString, ByteVec}; -use crate::file::mutable::section::{MutableSection, SectionBody}; -use crate::file::mutable::{escape_value, Whitespace}; use crate::{ - file::{Index, SectionBodyId, Size}, + file::{ + mutable::{ + escape_value, + section::{MutableSection, SectionBody}, + Whitespace, + }, + Index, SectionBodyId, Size, + }, lookup, parse::{section, Event}, value::{normalize_bstr, normalize_bstring}, diff --git a/git-config/src/fs.rs b/git-config/src/fs.rs index 1a11e1a21cf..cb5f7ce5d4c 100644 --- a/git-config/src/fs.rs +++ b/git-config/src/fs.rs @@ -10,8 +10,10 @@ use std::{ use bstr::BStr; -use crate::file::{from_env, from_paths}; -use crate::{lookup, File}; +use crate::{ + file::{from_env, from_paths}, + lookup, File, +}; // TODO: how does this relate to `File::from_env_paths()`? #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] diff --git a/git-config/src/parse/section/header.rs b/git-config/src/parse/section/header.rs index 31e318386de..e7cd1e5243a 100644 --- a/git-config/src/parse/section/header.rs +++ b/git-config/src/parse/section/header.rs @@ -1,8 +1,11 @@ -use crate::parse::section::{into_cow_bstr, Header, Name}; -use crate::parse::Event; +use std::{borrow::Cow, fmt::Display}; + use bstr::{BStr, BString, ByteSlice, ByteVec}; -use std::borrow::Cow; -use std::fmt::Display; + +use crate::parse::{ + section::{into_cow_bstr, Header, Name}, + Event, +}; /// The error returned by [`Header::new(…)`][super::Header::new()]. #[derive(Debug, PartialOrd, PartialEq, thiserror::Error)] diff --git a/git-config/src/parse/tests.rs b/git-config/src/parse/tests.rs index 02494075341..fdd42594111 100644 --- a/git-config/src/parse/tests.rs +++ b/git-config/src/parse/tests.rs @@ -2,9 +2,10 @@ mod section { mod header { mod write_to { - use crate::parse::section; use std::borrow::Cow; + use crate::parse::section; + fn header(name: &str, subsection: impl Into>) -> section::Header<'_> { let name = section::Name(Cow::Borrowed(name.into())); if let Some((separator, subsection_name)) = subsection.into() { @@ -51,8 +52,7 @@ pub(crate) mod util { //! This module is only included for tests, and contains common unit test helper //! functions. - use std::borrow::Cow; - use std::convert::TryFrom; + use std::{borrow::Cow, convert::TryFrom}; use crate::parse::{section, Comment, Event}; diff --git a/git-config/tests/file/access/mutate.rs b/git-config/tests/file/access/mutate.rs index 0c74bd74d80..de28bf9c641 100644 --- a/git-config/tests/file/access/mutate.rs +++ b/git-config/tests/file/access/mutate.rs @@ -1,8 +1,7 @@ mod rename_section { - use git_config::file::rename_section; - use git_config::parse::section; - use std::borrow::Cow; - use std::convert::TryFrom; + use std::{borrow::Cow, convert::TryFrom}; + + use git_config::{file::rename_section, parse::section}; #[test] fn section_renaming_validates_new_name() { diff --git a/git-config/tests/file/init/from_env.rs b/git-config/tests/file/init/from_env.rs index c4f3be1f5e2..3a945f6fd0a 100644 --- a/git-config/tests/file/init/from_env.rs +++ b/git-config/tests/file/init/from_env.rs @@ -1,8 +1,9 @@ use std::{borrow::Cow, env, fs}; -use git_config::file::from_paths::Options; -use git_config::file::{from_env, from_paths}; -use git_config::File; +use git_config::{ + file::{from_env, from_paths, from_paths::Options}, + File, +}; use serial_test::serial; use tempfile::tempdir; diff --git a/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs b/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs index 4a467f2934d..763d1625639 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs @@ -8,8 +8,10 @@ use std::{ use bstr::{BString, ByteSlice}; -use crate::file::cow_str; -use crate::file::init::from_paths::{escape_backslashes, includes::conditional::options_with_git_dir}; +use crate::file::{ + cow_str, + init::from_paths::{escape_backslashes, includes::conditional::options_with_git_dir}, +}; #[derive(Debug)] pub struct GitEnv { diff --git a/git-config/tests/file/init/from_paths/includes/conditional/mod.rs b/git-config/tests/file/init/from_paths/includes/conditional/mod.rs index 2643e31169f..2538c232f52 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/mod.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/mod.rs @@ -1,11 +1,9 @@ use std::{fs, path::Path}; -use git_config::file::from_paths; -use git_config::{path, File}; +use git_config::{file::from_paths, path, File}; use tempfile::tempdir; -use crate::file::cow_str; -use crate::file::init::from_paths::escape_backslashes; +use crate::file::{cow_str, init::from_paths::escape_backslashes}; mod gitdir; mod onbranch; diff --git a/git-config/tests/file/init/from_paths/includes/unconditional.rs b/git-config/tests/file/init/from_paths/includes/unconditional.rs index b9440a6db03..f0c3433471d 100644 --- a/git-config/tests/file/init/from_paths/includes/unconditional.rs +++ b/git-config/tests/file/init/from_paths/includes/unconditional.rs @@ -1,11 +1,9 @@ use std::fs; -use git_config::file::from_paths; -use git_config::File; +use git_config::{file::from_paths, File}; use tempfile::tempdir; -use crate::file::cow_str; -use crate::file::init::from_paths::escape_backslashes; +use crate::file::{cow_str, init::from_paths::escape_backslashes}; #[test] fn multiple() -> crate::Result { diff --git a/git-config/tests/file/mutable/multi_value.rs b/git-config/tests/file/mutable/multi_value.rs index 5b0305bf95b..973cffd107d 100644 --- a/git-config/tests/file/mutable/multi_value.rs +++ b/git-config/tests/file/mutable/multi_value.rs @@ -1,6 +1,5 @@ mod get { - use crate::file::cow_str; - use crate::file::mutable::multi_value::init_config; + use crate::file::{cow_str, mutable::multi_value::init_config}; #[test] fn single_lines() -> crate::Result { @@ -50,8 +49,7 @@ mod access { } mod set { - use crate::file::cow_str; - use crate::file::mutable::multi_value::init_config; + use crate::file::{cow_str, mutable::multi_value::init_config}; #[test] fn values_are_escaped() -> crate::Result { diff --git a/git-config/tests/file/mutable/section.rs b/git-config/tests/file/mutable/section.rs index 88c535223ae..4225c9e09e2 100644 --- a/git-config/tests/file/mutable/section.rs +++ b/git-config/tests/file/mutable/section.rs @@ -1,8 +1,10 @@ mod push { - use crate::file::cow_str; - use git_config::parse::section::Key; use std::convert::TryFrom; + use git_config::parse::section::Key; + + use crate::file::cow_str; + #[test] fn whitespace_is_derived_from_whitespace_before_first_value() -> crate::Result { for (input, expected_pre_key, expected_sep) in [ @@ -58,10 +60,12 @@ mod push { } mod set_leading_whitespace { - use crate::file::cow_str; - use git_config::parse::section::Key; use std::convert::TryFrom; + use git_config::parse::section::Key; + + use crate::file::cow_str; + #[test] fn any_whitespace_is_ok() -> crate::Result { let mut config = git_config::File::default(); diff --git a/git-config/tests/file/mutable/value.rs b/git-config/tests/file/mutable/value.rs index bc3f6a5b22c..fed4d60ae5d 100644 --- a/git-config/tests/file/mutable/value.rs +++ b/git-config/tests/file/mutable/value.rs @@ -1,7 +1,8 @@ mod get { - use crate::file::mutable::value::init_config; use bstr::BString; + use crate::file::mutable::value::init_config; + fn config_get(input: &str) -> BString { let mut file: git_config::File = input.parse().unwrap(); file.raw_value_mut("a", None, "k").unwrap().get().unwrap().into_owned() diff --git a/git-config/tests/parse/from_bytes.rs b/git-config/tests/parse/from_bytes.rs index 5ddca2fce4c..91acb2c5d04 100644 --- a/git-config/tests/parse/from_bytes.rs +++ b/git-config/tests/parse/from_bytes.rs @@ -1,6 +1,7 @@ -use super::*; use git_config::parse::Events; +use super::*; + #[test] #[rustfmt::skip] fn complex() { diff --git a/git-config/tests/parse/mod.rs b/git-config/tests/parse/mod.rs index 34ac2b5b58b..36d4df3ba37 100644 --- a/git-config/tests/parse/mod.rs +++ b/git-config/tests/parse/mod.rs @@ -1,5 +1,4 @@ -use std::borrow::Cow; -use std::convert::TryFrom; +use std::{borrow::Cow, convert::TryFrom}; use git_config::parse::{Event, Events, Section}; diff --git a/git-config/tests/parse/section.rs b/git-config/tests/parse/section.rs index 4a5eb3bfccc..35333a7e601 100644 --- a/git-config/tests/parse/section.rs +++ b/git-config/tests/parse/section.rs @@ -1,16 +1,17 @@ -use git_config::parse::section; -use git_config::parse::Event; use std::borrow::Cow; +use git_config::parse::{section, Event}; + pub fn header_event(name: &'static str, subsection: impl Into>) -> Event<'static> { Event::SectionHeader(section::Header::new(name, subsection.into().map(Cow::Borrowed)).unwrap()) } mod header { mod write_to { - use git_config::parse::section; use std::borrow::Cow; + use git_config::parse::section; + #[test] fn subsection_backslashes_and_quotes_are_escaped() -> crate::Result { assert_eq!( @@ -34,9 +35,10 @@ mod header { } } mod new { - use git_config::parse::section; use std::borrow::Cow; + use git_config::parse::section; + #[test] fn names_must_be_mostly_ascii() { for name in ["🤗", "x.y", "x y", "x\ny"] { @@ -61,9 +63,10 @@ mod header { } } mod name { - use git_config::parse::section::Name; use std::convert::TryFrom; + use git_config::parse::section::Name; + #[test] fn alphanum_and_dash_are_valid() { assert!(Name::try_from("1a").is_ok()); @@ -80,9 +83,9 @@ mod name { } mod key { + use std::{cmp::Ordering, convert::TryFrom}; + use git_config::parse::section::Key; - use std::cmp::Ordering; - use std::convert::TryFrom; fn key(k: &str) -> Key<'_> { Key::try_from(k).unwrap() From 701266e6e52456c0c1938732c260be19ec8029c9 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 13 Jul 2022 19:10:42 +0800 Subject: [PATCH 193/366] refactor (#331) --- git-config/src/file/access/raw.rs | 2 +- git-config/src/file/mod.rs | 3 +- git-config/src/file/mutable/mod.rs | 1 + git-config/src/file/mutable/multi_value.rs | 259 +++++++++++++++++++ git-config/src/file/mutable/value.rs | 266 +------------------- git-config/tests/file/access/mod.rs | 1 - git-config/tests/file/mod.rs | 1 + git-config/tests/file/{access => }/write.rs | 0 8 files changed, 268 insertions(+), 265 deletions(-) create mode 100644 git-config/src/file/mutable/multi_value.rs rename git-config/tests/file/{access => }/write.rs (100%) diff --git a/git-config/src/file/access/raw.rs b/git-config/src/file/access/raw.rs index 5c48c7b3f75..6087020c362 100644 --- a/git-config/src/file/access/raw.rs +++ b/git-config/src/file/access/raw.rs @@ -3,7 +3,7 @@ use std::{borrow::Cow, collections::HashMap}; use bstr::BStr; use crate::{ - file::{mutable::value::EntryData, Index, MutableMultiValue, MutableSection, MutableValue, Size}, + file::{mutable::multi_value::EntryData, Index, MutableMultiValue, MutableSection, MutableValue, Size}, lookup, parse::{section, Event}, File, diff --git a/git-config/src/file/mod.rs b/git-config/src/file/mod.rs index 1bfc2bbb832..3f80e8d6093 100644 --- a/git-config/src/file/mod.rs +++ b/git-config/src/file/mod.rs @@ -10,8 +10,9 @@ use bstr::BStr; mod mutable; pub use mutable::{ + multi_value::MutableMultiValue, section::{MutableSection, SectionBody, SectionBodyIter}, - value::{MutableMultiValue, MutableValue}, + value::MutableValue, }; mod init; diff --git a/git-config/src/file/mutable/mod.rs b/git-config/src/file/mutable/mod.rs index deed10148af..aae564d96c5 100644 --- a/git-config/src/file/mutable/mod.rs +++ b/git-config/src/file/mutable/mod.rs @@ -104,5 +104,6 @@ impl<'a> From<&SectionBody<'a>> for Whitespace<'a> { } } +pub(crate) mod multi_value; pub(crate) mod section; pub(crate) mod value; diff --git a/git-config/src/file/mutable/multi_value.rs b/git-config/src/file/mutable/multi_value.rs new file mode 100644 index 00000000000..505cf977b4e --- /dev/null +++ b/git-config/src/file/mutable/multi_value.rs @@ -0,0 +1,259 @@ +use crate::file::mutable::{escape_value, Whitespace}; +use crate::file::{SectionBody, SectionBodyId}; +use crate::lookup; +use crate::parse::{section, Event}; +use crate::value::{normalize_bstr, normalize_bstring}; +use bstr::{BStr, BString, ByteVec}; +use std::borrow::Cow; +use std::collections::HashMap; +use std::ops::DerefMut; + +/// Internal data structure for [`MutableMultiValue`] +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub(crate) struct EntryData { + pub(crate) section_id: SectionBodyId, + pub(crate) offset_index: usize, +} + +/// An intermediate representation of a mutable multivar obtained from a [`File`][crate::File]. +#[derive(PartialEq, Eq, Debug)] +pub struct MutableMultiValue<'borrow, 'lookup, 'event> { + pub(crate) section: &'borrow mut HashMap>, + pub(crate) key: section::Key<'lookup>, + /// Each entry data struct provides sufficient information to index into + /// [`Self::offsets`]. This layer of indirection is used for users to index + /// into the offsets rather than leaking the internal data structures. + pub(crate) indices_and_sizes: Vec, + /// Each offset represents the size of a event slice and whether or not the + /// event slice is significant or not. This is used to index into the + /// actual section. + pub(crate) offsets: HashMap>, +} + +impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { + /// Returns the actual values. + pub fn get(&self) -> Result>, lookup::existing::Error> { + let mut expect_value = false; + let mut values = Vec::new(); + let mut concatenated_value = BString::default(); + + for EntryData { + section_id, + offset_index, + } in &self.indices_and_sizes + { + let (offset, size) = MutableMultiValue::index_and_size(&self.offsets, *section_id, *offset_index); + for event in &self.section.get(section_id).expect("known section id").as_ref()[offset..offset + size] { + match event { + Event::SectionKey(section_key) if *section_key == self.key => expect_value = true, + Event::Value(v) if expect_value => { + expect_value = false; + values.push(normalize_bstr(v.as_ref())); + } + Event::ValueNotDone(v) if expect_value => concatenated_value.push_str(v.as_ref()), + Event::ValueDone(v) if expect_value => { + expect_value = false; + concatenated_value.push_str(v.as_ref()); + values.push(normalize_bstring(std::mem::take(&mut concatenated_value))); + } + _ => (), + } + } + } + + if values.is_empty() { + return Err(lookup::existing::Error::KeyMissing); + } + + Ok(values) + } + + /// Returns the amount of values within this multivar. + #[must_use] + pub fn len(&self) -> usize { + self.indices_and_sizes.len() + } + + /// Returns true if the multivar does not have any values. + /// This might occur if the value was deleted but wasn't yet set with a new value. + #[must_use] + pub fn is_empty(&self) -> bool { + self.indices_and_sizes.is_empty() + } + + /// Sets the value at the given index. + /// + /// # Safety + /// + /// This will panic if the index is out of range. + pub fn set_string_at(&mut self, index: usize, input: impl AsRef) { + self.set_at(index, input.as_ref().into()); + } + + /// Sets the value at the given index. + /// + /// # Safety + /// + /// This will panic if the index is out of range. + pub fn set_at(&mut self, index: usize, input: &BStr) { + let EntryData { + section_id, + offset_index, + } = self.indices_and_sizes[index]; + MutableMultiValue::set_value_inner( + &self.key, + &mut self.offsets, + self.section.get_mut(§ion_id).expect("known section id"), + section_id, + offset_index, + input, + ); + } + + /// Sets all values to the provided ones. Note that this follows [`zip`] + /// logic: if the number of values in the input is less than the number of + /// values currently existing, then only the first `n` values are modified. + /// If more values are provided than there currently are, then the + /// remaining values are ignored. + /// + /// [`zip`]: std::iter::Iterator::zip + pub fn set_values<'a>(&mut self, input: impl IntoIterator) { + for ( + EntryData { + section_id, + offset_index, + }, + value, + ) in self.indices_and_sizes.iter().zip(input) + { + Self::set_value_inner( + &self.key, + &mut self.offsets, + self.section.get_mut(section_id).expect("known section id"), + *section_id, + *offset_index, + value, + ); + } + } + + /// Sets all values in this multivar to the provided one without owning the + /// provided input. Consider using [`Self::set_owned_values_all`] or + /// [`Self::set_str_all`] unless you have a strict performance or memory + /// need for a more ergonomic interface. + /// + /// [`File`]: crate::File + pub fn set_all<'a>(&mut self, input: impl Into<&'a BStr>) { + let input = input.into(); + for EntryData { + section_id, + offset_index, + } in &self.indices_and_sizes + { + Self::set_value_inner( + &self.key, + &mut self.offsets, + self.section.get_mut(section_id).expect("known section id"), + *section_id, + *offset_index, + input, + ); + } + } + + fn set_value_inner<'a: 'event>( + key: §ion::Key<'lookup>, + offsets: &mut HashMap>, + section: &mut SectionBody<'event>, + section_id: SectionBodyId, + offset_index: usize, + value: &BStr, + ) { + let (offset, size) = MutableMultiValue::index_and_size(offsets, section_id, offset_index); + let whitespace: Whitespace<'_> = (&*section).into(); + let section = section.as_mut(); + section.drain(offset..offset + size); + + let key_sep_events = whitespace.key_value_separators(); + MutableMultiValue::set_offset(offsets, section_id, offset_index, 2 + key_sep_events.len()); + section.insert(offset, Event::Value(escape_value(value).into())); + section.insert_many(offset, key_sep_events.into_iter().rev()); + section.insert(offset, Event::SectionKey(key.to_owned())); + } + + /// Removes the value at the given index. Does nothing when called multiple + /// times in succession. + /// + /// # Safety + /// + /// This will panic if the index is out of range. + pub fn delete(&mut self, index: usize) { + let EntryData { + section_id, + offset_index, + } = &self.indices_and_sizes[index]; + let (offset, size) = MutableMultiValue::index_and_size(&self.offsets, *section_id, *offset_index); + if size == 0 { + return; + } + self.section + .get_mut(section_id) + .expect("known section id") + .as_mut() + .drain(offset..offset + size); + + Self::set_offset(&mut self.offsets, *section_id, *offset_index, 0); + self.indices_and_sizes.remove(index); + } + + /// Removes all values. Does nothing when called multiple times in + /// succession. + pub fn delete_all(&mut self) { + for EntryData { + section_id, + offset_index, + } in &self.indices_and_sizes + { + let (offset, size) = MutableMultiValue::index_and_size(&self.offsets, *section_id, *offset_index); + if size == 0 { + continue; + } + self.section + .get_mut(section_id) + .expect("known section id") + .as_mut() + .drain(offset..offset + size); + Self::set_offset(&mut self.offsets, *section_id, *offset_index, 0); + } + self.indices_and_sizes.clear(); + } + + fn index_and_size( + offsets: &'lookup HashMap>, + section_id: SectionBodyId, + offset_index: usize, + ) -> (usize, usize) { + offsets + .get(§ion_id) + .expect("known section id") + .iter() + .take(offset_index + 1) + .fold((0, 0), |(total_ofs, ofs), size| (total_ofs + ofs, *size)) + } + + // This must be an associated function rather than a method to allow Rust + // to split mutable borrows. + fn set_offset( + offsets: &mut HashMap>, + section_id: SectionBodyId, + offset_index: usize, + value: usize, + ) { + *offsets + .get_mut(§ion_id) + .expect("known section id") + .get_mut(offset_index) + .unwrap() + .deref_mut() = value; + } +} diff --git a/git-config/src/file/mutable/value.rs b/git-config/src/file/mutable/value.rs index 88044cfe91c..1c85212e7ff 100644 --- a/git-config/src/file/mutable/value.rs +++ b/git-config/src/file/mutable/value.rs @@ -1,19 +1,11 @@ -use std::{borrow::Cow, collections::HashMap, ops::DerefMut}; +use std::borrow::Cow; -use bstr::{BStr, BString, ByteVec}; +use bstr::BStr; use crate::{ - file::{ - mutable::{ - escape_value, - section::{MutableSection, SectionBody}, - Whitespace, - }, - Index, SectionBodyId, Size, - }, + file::{mutable::section::MutableSection, Index, Size}, lookup, - parse::{section, Event}, - value::{normalize_bstr, normalize_bstring}, + parse::section, }; /// An intermediate representation of a mutable value obtained from a [`File`][crate::File]. @@ -58,253 +50,3 @@ impl<'borrow, 'lookup, 'event> MutableValue<'borrow, 'lookup, 'event> { } } } - -/// Internal data structure for [`MutableMultiValue`] -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] -pub(crate) struct EntryData { - pub(crate) section_id: SectionBodyId, - pub(crate) offset_index: usize, -} - -/// An intermediate representation of a mutable multivar obtained from a [`File`][crate::File]. -#[derive(PartialEq, Eq, Debug)] -pub struct MutableMultiValue<'borrow, 'lookup, 'event> { - pub(crate) section: &'borrow mut HashMap>, - pub(crate) key: section::Key<'lookup>, - /// Each entry data struct provides sufficient information to index into - /// [`Self::offsets`]. This layer of indirection is used for users to index - /// into the offsets rather than leaking the internal data structures. - pub(crate) indices_and_sizes: Vec, - /// Each offset represents the size of a event slice and whether or not the - /// event slice is significant or not. This is used to index into the - /// actual section. - pub(crate) offsets: HashMap>, -} - -impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { - /// Returns the actual values. - pub fn get(&self) -> Result>, lookup::existing::Error> { - let mut expect_value = false; - let mut values = Vec::new(); - let mut concatenated_value = BString::default(); - - for EntryData { - section_id, - offset_index, - } in &self.indices_and_sizes - { - let (offset, size) = MutableMultiValue::index_and_size(&self.offsets, *section_id, *offset_index); - for event in &self.section.get(section_id).expect("known section id").as_ref()[offset..offset + size] { - match event { - Event::SectionKey(section_key) if *section_key == self.key => expect_value = true, - Event::Value(v) if expect_value => { - expect_value = false; - values.push(normalize_bstr(v.as_ref())); - } - Event::ValueNotDone(v) if expect_value => concatenated_value.push_str(v.as_ref()), - Event::ValueDone(v) if expect_value => { - expect_value = false; - concatenated_value.push_str(v.as_ref()); - values.push(normalize_bstring(std::mem::take(&mut concatenated_value))); - } - _ => (), - } - } - } - - if values.is_empty() { - return Err(lookup::existing::Error::KeyMissing); - } - - Ok(values) - } - - /// Returns the amount of values within this multivar. - #[must_use] - pub fn len(&self) -> usize { - self.indices_and_sizes.len() - } - - /// Returns true if the multivar does not have any values. - /// This might occur if the value was deleted but wasn't yet set with a new value. - #[must_use] - pub fn is_empty(&self) -> bool { - self.indices_and_sizes.is_empty() - } - - /// Sets the value at the given index. - /// - /// # Safety - /// - /// This will panic if the index is out of range. - pub fn set_string_at(&mut self, index: usize, input: impl AsRef) { - self.set_at(index, input.as_ref().into()); - } - - /// Sets the value at the given index. - /// - /// # Safety - /// - /// This will panic if the index is out of range. - pub fn set_at(&mut self, index: usize, input: &BStr) { - let EntryData { - section_id, - offset_index, - } = self.indices_and_sizes[index]; - MutableMultiValue::set_value_inner( - &self.key, - &mut self.offsets, - self.section.get_mut(§ion_id).expect("known section id"), - section_id, - offset_index, - input, - ); - } - - /// Sets all values to the provided ones. Note that this follows [`zip`] - /// logic: if the number of values in the input is less than the number of - /// values currently existing, then only the first `n` values are modified. - /// If more values are provided than there currently are, then the - /// remaining values are ignored. - /// - /// [`zip`]: std::iter::Iterator::zip - pub fn set_values<'a>(&mut self, input: impl IntoIterator) { - for ( - EntryData { - section_id, - offset_index, - }, - value, - ) in self.indices_and_sizes.iter().zip(input) - { - Self::set_value_inner( - &self.key, - &mut self.offsets, - self.section.get_mut(section_id).expect("known section id"), - *section_id, - *offset_index, - value, - ); - } - } - - /// Sets all values in this multivar to the provided one without owning the - /// provided input. Consider using [`Self::set_owned_values_all`] or - /// [`Self::set_str_all`] unless you have a strict performance or memory - /// need for a more ergonomic interface. - /// - /// [`File`]: crate::File - pub fn set_all<'a>(&mut self, input: impl Into<&'a BStr>) { - let input = input.into(); - for EntryData { - section_id, - offset_index, - } in &self.indices_and_sizes - { - Self::set_value_inner( - &self.key, - &mut self.offsets, - self.section.get_mut(section_id).expect("known section id"), - *section_id, - *offset_index, - input, - ); - } - } - - fn set_value_inner<'a: 'event>( - key: §ion::Key<'lookup>, - offsets: &mut HashMap>, - section: &mut SectionBody<'event>, - section_id: SectionBodyId, - offset_index: usize, - value: &BStr, - ) { - let (offset, size) = MutableMultiValue::index_and_size(offsets, section_id, offset_index); - let whitespace: Whitespace<'_> = (&*section).into(); - let section = section.as_mut(); - section.drain(offset..offset + size); - - let key_sep_events = whitespace.key_value_separators(); - MutableMultiValue::set_offset(offsets, section_id, offset_index, 2 + key_sep_events.len()); - section.insert(offset, Event::Value(escape_value(value).into())); - section.insert_many(offset, key_sep_events.into_iter().rev()); - section.insert(offset, Event::SectionKey(key.to_owned())); - } - - /// Removes the value at the given index. Does nothing when called multiple - /// times in succession. - /// - /// # Safety - /// - /// This will panic if the index is out of range. - pub fn delete(&mut self, index: usize) { - let EntryData { - section_id, - offset_index, - } = &self.indices_and_sizes[index]; - let (offset, size) = MutableMultiValue::index_and_size(&self.offsets, *section_id, *offset_index); - if size == 0 { - return; - } - self.section - .get_mut(section_id) - .expect("known section id") - .as_mut() - .drain(offset..offset + size); - - Self::set_offset(&mut self.offsets, *section_id, *offset_index, 0); - self.indices_and_sizes.remove(index); - } - - /// Removes all values. Does nothing when called multiple times in - /// succession. - pub fn delete_all(&mut self) { - for EntryData { - section_id, - offset_index, - } in &self.indices_and_sizes - { - let (offset, size) = MutableMultiValue::index_and_size(&self.offsets, *section_id, *offset_index); - if size == 0 { - continue; - } - self.section - .get_mut(section_id) - .expect("known section id") - .as_mut() - .drain(offset..offset + size); - Self::set_offset(&mut self.offsets, *section_id, *offset_index, 0); - } - self.indices_and_sizes.clear(); - } - - fn index_and_size( - offsets: &'lookup HashMap>, - section_id: SectionBodyId, - offset_index: usize, - ) -> (usize, usize) { - offsets - .get(§ion_id) - .expect("known section id") - .iter() - .take(offset_index + 1) - .fold((0, 0), |(total_ofs, ofs), size| (total_ofs + ofs, *size)) - } - - // This must be an associated function rather than a method to allow Rust - // to split mutable borrows. - fn set_offset( - offsets: &mut HashMap>, - section_id: SectionBodyId, - offset_index: usize, - value: usize, - ) { - *offsets - .get_mut(§ion_id) - .expect("known section id") - .get_mut(offset_index) - .unwrap() - .deref_mut() = value; - } -} diff --git a/git-config/tests/file/access/mod.rs b/git-config/tests/file/access/mod.rs index cebb3dfa59b..3753747e5b2 100644 --- a/git-config/tests/file/access/mod.rs +++ b/git-config/tests/file/access/mod.rs @@ -1,4 +1,3 @@ mod mutate; mod raw; mod read_only; -mod write; diff --git a/git-config/tests/file/mod.rs b/git-config/tests/file/mod.rs index c016f0be7e5..3d0ed7bf1a4 100644 --- a/git-config/tests/file/mod.rs +++ b/git-config/tests/file/mod.rs @@ -30,3 +30,4 @@ mod access; mod impls; mod init; mod mutable; +mod write; diff --git a/git-config/tests/file/access/write.rs b/git-config/tests/file/write.rs similarity index 100% rename from git-config/tests/file/access/write.rs rename to git-config/tests/file/write.rs From ac843cbef4a6322be706b978e6691bc36c5e458f Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 13 Jul 2022 20:25:21 +0800 Subject: [PATCH 194/366] many more tests for MutableSection (#331) And a surprise when checking 'remove()'. --- git-config/src/file/init/from_env.rs | 2 +- git-config/src/file/mutable/section.rs | 25 ++++-- git-config/src/parse/nom/tests.rs | 2 +- git-config/src/parse/section/mod.rs | 8 ++ git-config/tests/file/mutable/section.rs | 107 ++++++++++++++++++++++- git-config/tests/file/mutable/value.rs | 37 ++++---- git-config/tests/file/write.rs | 10 ++- 7 files changed, 158 insertions(+), 33 deletions(-) diff --git a/git-config/src/file/init/from_env.rs b/git-config/src/file/init/from_env.rs index ce10e877d43..ab82b6389c3 100644 --- a/git-config/src/file/init/from_env.rs +++ b/git-config/src/file/init/from_env.rs @@ -113,7 +113,7 @@ impl File<'static> { section.push( section::Key(BString::from(key).into()), - git_path::into_bstr(PathBuf::from(value)).into_owned().into(), + git_path::into_bstr(PathBuf::from(value)).as_ref(), ); } None => { diff --git a/git-config/src/file/mutable/section.rs b/git-config/src/file/mutable/section.rs index 0dd3126425b..7624ddb8b68 100644 --- a/git-config/src/file/mutable/section.rs +++ b/git-config/src/file/mutable/section.rs @@ -27,14 +27,14 @@ pub struct MutableSection<'a, 'event> { /// Mutating methods. impl<'a, 'event> MutableSection<'a, 'event> { /// Adds an entry to the end of this section. - pub fn push(&mut self, key: Key<'event>, value: Cow<'event, BStr>) { + pub fn push(&mut self, key: Key<'event>, value: &BStr) { if let Some(ws) = &self.whitespace.pre_key { self.section.0.push(Event::Whitespace(ws.clone())); } self.section.0.push(Event::SectionKey(key)); self.section.0.extend(self.whitespace.key_value_separators()); - self.section.0.push(Event::Value(escape_value(value.as_ref()).into())); + self.section.0.push(Event::Value(escape_value(value).into())); if self.implicit_newline { self.section.0.push(Event::Newline(BString::from("\n").into())); } @@ -79,7 +79,7 @@ impl<'a, 'event> MutableSection<'a, 'event> { /// Sets the last key value pair if it exists, or adds the new value. /// Returns the previous value if it replaced a value, or None if it adds /// the value. - pub fn set(&mut self, key: Key<'event>, value: Cow<'event, BStr>) -> Option> { + pub fn set(&mut self, key: Key<'event>, value: &BStr) -> Option> { let range = self.value_range_by_key(&key); if range.is_empty() { self.push(key, value); @@ -87,7 +87,9 @@ impl<'a, 'event> MutableSection<'a, 'event> { } let range_start = range.start; let ret = self.remove_internal(range); - self.section.0.insert(range_start, Event::Value(value)); + self.section + .0 + .insert(range_start, Event::Value(escape_value(value).into())); Some(ret) } @@ -247,10 +249,12 @@ impl<'event> SectionBody<'event> { /// If the value is not found, then this returns an empty range. fn value_range_by_key(&self, key: &Key<'_>) -> Range { let mut range = Range::default(); + let mut key_seen = false; for (i, e) in self.0.iter().enumerate().rev() { match e { Event::SectionKey(k) => { if k == key { + key_seen = true; break; } range = Range::default(); @@ -268,10 +272,13 @@ impl<'event> SectionBody<'event> { _ => (), } } - - // value end needs to be offset by one so that the last value's index - // is included in the range - range.start..range.end + 1 + if !key_seen { + Range::default() + } else { + // value end needs to be offset by one so that the last value's index + // is included in the range + range.start..range.end + 1 + } } } @@ -361,6 +368,8 @@ impl<'event> SectionBody<'event> { } /// Returns if the section is empty. + /// Note that this may count whitespace, see [`num_values()`][Self::num_values()] for + /// another way to determine semantic emptiness. #[must_use] pub fn is_empty(&self) -> bool { self.0.is_empty() diff --git a/git-config/src/parse/nom/tests.rs b/git-config/src/parse/nom/tests.rs index a0de4a05ee0..19c58aabada 100644 --- a/git-config/src/parse/nom/tests.rs +++ b/git-config/src/parse/nom/tests.rs @@ -335,7 +335,7 @@ mod section { } #[test] - fn section_single_line() { + fn section_implicit_value() { let mut node = ParseNode::SectionHeader; assert_eq!( section(b"[hello] c", &mut node).unwrap(), diff --git a/git-config/src/parse/section/mod.rs b/git-config/src/parse/section/mod.rs index cced60dca1c..c4618c951bb 100644 --- a/git-config/src/parse/section/mod.rs +++ b/git-config/src/parse/section/mod.rs @@ -114,6 +114,14 @@ mod types { } } + impl<'a> std::convert::TryFrom for $name<'a> { + type Error = $module::Error; + + fn try_from(s: String) -> Result { + Self::try_from(std::borrow::Cow::Owned(bstr::BString::from(s))) + } + } + impl<'a> std::convert::TryFrom> for $name<'a> { type Error = $module::Error; diff --git a/git-config/tests/file/mutable/section.rs b/git-config/tests/file/mutable/section.rs index 4225c9e09e2..9c43bd4a46a 100644 --- a/git-config/tests/file/mutable/section.rs +++ b/git-config/tests/file/mutable/section.rs @@ -1,16 +1,101 @@ +mod remove { + use super::multi_value_section; + use std::convert::TryInto; + + #[test] + fn all() -> crate::Result { + let mut config = multi_value_section(); + let mut section = config.section_mut("a", None)?; + + assert_eq!(section.num_values(), 5); + assert_eq!(section.keys().count(), 5); + + let prev_values = vec!["v", "", "", "", "a b c"]; + let num_values = section.num_values(); + for (key, expected_prev_value) in ('a'..='e').zip(prev_values) { + let prev_value = section.remove(&key.to_string().try_into()?); + assert_eq!(prev_value.expect("present").as_ref(), expected_prev_value); + assert_eq!(section.num_values(), num_values, "we don't actually remove keys"); + } + + assert!(!section.is_empty(), "everything is still there"); + assert_eq!( + config.to_string(), + "\n [a]\n a = \n b = \n c=\n d\n e =" + ); + Ok(()) + } +} + +mod pop { + use super::multi_value_section; + + #[test] + fn all() -> crate::Result { + let mut config = multi_value_section(); + let mut section = config.section_mut("a", None)?; + + assert_eq!(section.num_values(), 5); + assert_eq!(section.keys().count(), 5); + + for key in b'a'..=b'e' { + assert!(section.contains_key(std::str::from_utf8(&[key])?)); + } + let mut num_values = section.num_values(); + for _ in 0..num_values { + section.pop(); + num_values -= 1; + assert_eq!(section.num_values(), num_values); + } + assert!(!section.is_empty(), "there still is some whitespace"); + assert_eq!(config.to_string(), "\n [a]\n"); + Ok(()) + } +} + +mod set { + use super::multi_value_section; + use std::convert::TryInto; + + #[test] + fn various_escapes_onto_various_kinds_of_values() -> crate::Result { + let mut config = multi_value_section(); + let mut section = config.section_mut("a", None)?; + let values = vec!["", " a", "b\t", "; comment", "a\n\tc d\\ \"x\""]; + let prev_values = vec!["v", "", "", "", "a b c"]; + assert_eq!(section.num_values(), values.len()); + + for (key, (new_value, expected_prev_value)) in (b'a'..=b'e').zip(values.into_iter().zip(prev_values)) { + let key = std::str::from_utf8(std::slice::from_ref(&key))?.to_owned(); + let prev_value = section.set(key.try_into()?, new_value.into()); + assert_eq!(prev_value.as_deref().expect("prev value set"), expected_prev_value); + } + + assert_eq!(config.to_string(), "\n [a]\n a = \n b = \" a\"\n c=\"b\\t\"\n d\"; comment\"\n e =a\\n\\tc d\\\\ \\\"x\\\""); + assert_eq!( + config + .section_mut("a", None)? + .set("new-one".to_owned().try_into()?, "value".into()), + None, + "new values don't replace an existing one" + ); + Ok(()) + } +} + mod push { use std::convert::TryFrom; use git_config::parse::section::Key; - use crate::file::cow_str; - #[test] fn whitespace_is_derived_from_whitespace_before_first_value() -> crate::Result { for (input, expected_pre_key, expected_sep) in [ ("[a]\n\t\tb=c", Some("\t\t".into()), (None, None)), ("[a]\nb= c", None, (None, Some(" "))), ("[a]", Some("\t".into()), (Some(" "), Some(" "))), + ("[a] b", Some(" ".into()), (None, None)), + ("[a]\tb = ", Some("\t".into()), (Some(" "), Some(" "))), ("[a]\t\tb =c", Some("\t\t".into()), (Some(" "), None)), ( "[a]\n\t\t \n \t b = c", @@ -53,7 +138,7 @@ mod push { let mut config = git_config::File::default(); let mut section = config.new_section("a", None).unwrap(); section.set_implicit_newline(false); - section.push(Key::try_from("k").unwrap(), cow_str(value)); + section.push(Key::try_from("k").unwrap(), value.into()); assert_eq!(config.to_bstring(), expected); } } @@ -71,7 +156,7 @@ mod set_leading_whitespace { let mut config = git_config::File::default(); let mut section = config.new_section("core", None)?; section.set_leading_whitespace(cow_str("\n\t").into()); - section.push(Key::try_from("a")?, cow_str("v")); + section.push(Key::try_from("a")?, "v".into()); assert_eq!(config.to_string(), "[core]\n\n\ta = v\n"); Ok(()) } @@ -84,3 +169,17 @@ mod set_leading_whitespace { section.set_leading_whitespace(cow_str("foo").into()); } } + +fn multi_value_section() -> git_config::File<'static> { + r#" + [a] + a = v + b = + c= + d + e =a \ + b \ + c"# + .parse() + .unwrap() +} diff --git a/git-config/tests/file/mutable/value.rs b/git-config/tests/file/mutable/value.rs index fed4d60ae5d..24bf8b3f253 100644 --- a/git-config/tests/file/mutable/value.rs +++ b/git-config/tests/file/mutable/value.rs @@ -41,23 +41,28 @@ mod get { mod set_string { use crate::file::mutable::value::init_config; - fn file() -> git_config::File<'static> { - "[a] k = v".parse().unwrap() - } - fn assert_set_string(expected: &str) { - let mut file = file(); - let mut v = file.raw_value_mut("a", None, "k").unwrap(); - assert_eq!(v.get().unwrap().as_ref(), "v"); - v.set_string(expected); - - assert_eq!(v.get().unwrap().as_ref(), expected); - - let file: git_config::File = match file.to_string().parse() { - Ok(f) => f, - Err(err) => panic!("{:?} failed with: {}", file.to_string(), err), - }; - assert_eq!(file.raw_value("a", None, "k").expect("present").as_ref(), expected); + for input in [ + "[a] k = v", + "[a] k = ", + "[a] k =", + "[a] k =\n", + "[a] k ", + "[a] k\n", + "[a] k", + ] { + let mut file: git_config::File = input.parse().unwrap(); + let mut v = file.raw_value_mut("a", None, "k").unwrap(); + v.set_string(expected); + + assert_eq!(v.get().unwrap().as_ref(), expected); + + let file: git_config::File = match file.to_string().parse() { + Ok(f) => f, + Err(err) => panic!("{:?} failed with: {}", file.to_string(), err), + }; + assert_eq!(file.raw_value("a", None, "k").expect("present").as_ref(), expected); + } } #[test] diff --git a/git-config/tests/file/write.rs b/git-config/tests/file/write.rs index e06d8263e1d..cd6d3418e58 100644 --- a/git-config/tests/file/write.rs +++ b/git-config/tests/file/write.rs @@ -13,10 +13,14 @@ fn complex_lossless_roundtrip() { url = git@github.com:Byron/gitoxide.git fetch = +refs/heads/*:refs/remotes/origin/* - [test] - other-quoted = "hello" + [test] # other comment + other-quoted = "hello" ; comment implicit implicit-equal = + implicit-equal-trailing-ws= + + ; more comments + # another one [test "sub-section \"special\" C:\\root"] bool-explicit = false @@ -41,7 +45,7 @@ fn complex_lossless_roundtrip() { git log -1; \ }; \ f; \ - unset f" + unset f" ; here we go "#; let config = git_config::File::try_from(input).unwrap(); assert_eq!(config.to_bstring(), input); From 3ca8027e07a835e84a704688778cfb82c956643b Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 13 Jul 2022 20:26:42 +0800 Subject: [PATCH 195/366] thanks clippy --- git-config/tests/file/mutable/section.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-config/tests/file/mutable/section.rs b/git-config/tests/file/mutable/section.rs index 9c43bd4a46a..ca5f178d8ef 100644 --- a/git-config/tests/file/mutable/section.rs +++ b/git-config/tests/file/mutable/section.rs @@ -42,7 +42,7 @@ mod pop { assert!(section.contains_key(std::str::from_utf8(&[key])?)); } let mut num_values = section.num_values(); - for _ in 0..num_values { + for _ in 0..section.num_values() { section.pop(); num_values -= 1; assert_eq!(section.num_values(), num_values); From 94dde44e8dd1a0b8d4e11f2627a3f6b345a15989 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 13 Jul 2022 21:23:44 +0800 Subject: [PATCH 196/366] fix: `file::MutableSection::remove()` now actually removes keys _and_ values. (#331) --- git-config/src/file/mutable/multi_value.rs | 6 +-- git-config/src/file/mutable/section.rs | 62 ++++++++++------------ git-config/tests/file/mutable/section.rs | 7 +-- 3 files changed, 34 insertions(+), 41 deletions(-) diff --git a/git-config/src/file/mutable/multi_value.rs b/git-config/src/file/mutable/multi_value.rs index 505cf977b4e..5569c696ce4 100644 --- a/git-config/src/file/mutable/multi_value.rs +++ b/git-config/src/file/mutable/multi_value.rs @@ -138,11 +138,7 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { } /// Sets all values in this multivar to the provided one without owning the - /// provided input. Consider using [`Self::set_owned_values_all`] or - /// [`Self::set_str_all`] unless you have a strict performance or memory - /// need for a more ergonomic interface. - /// - /// [`File`]: crate::File + /// provided input. pub fn set_all<'a>(&mut self, input: impl Into<&'a BStr>) { let input = input.into(); for EntryData { diff --git a/git-config/src/file/mutable/section.rs b/git-config/src/file/mutable/section.rs index 7624ddb8b68..e1ffce7018d 100644 --- a/git-config/src/file/mutable/section.rs +++ b/git-config/src/file/mutable/section.rs @@ -80,26 +80,26 @@ impl<'a, 'event> MutableSection<'a, 'event> { /// Returns the previous value if it replaced a value, or None if it adds /// the value. pub fn set(&mut self, key: Key<'event>, value: &BStr) -> Option> { - let range = self.value_range_by_key(&key); - if range.is_empty() { - self.push(key, value); - return None; + match self.key_and_value_range_by(&key) { + None => { + self.push(key, value); + None + } + Some((_, value_range)) => { + let range_start = value_range.start; + let ret = self.remove_internal(value_range); + self.section + .0 + .insert(range_start, Event::Value(escape_value(value).into())); + Some(ret) + } } - let range_start = range.start; - let ret = self.remove_internal(range); - self.section - .0 - .insert(range_start, Event::Value(escape_value(value).into())); - Some(ret) } /// Removes the latest value by key and returns it, if it exists. pub fn remove(&mut self, key: &Key<'event>) -> Option> { - let range = self.value_range_by_key(key); - if range.is_empty() { - return None; - } - Some(self.remove_internal(range)) + let (key_range, _value_range) = self.key_and_value_range_by(key)?; + Some(self.remove_internal(key_range)) } /// Adds a new line event. Note that you don't need to call this unless @@ -247,38 +247,37 @@ impl<'event> SectionBody<'event> { /// Returns the the range containing the value events for the `key`. /// If the value is not found, then this returns an empty range. - fn value_range_by_key(&self, key: &Key<'_>) -> Range { - let mut range = Range::default(); - let mut key_seen = false; + fn key_and_value_range_by(&self, key: &Key<'_>) -> Option<(Range, Range)> { + let mut value_range = Range::default(); + let mut key_start = None; for (i, e) in self.0.iter().enumerate().rev() { match e { Event::SectionKey(k) => { if k == key { - key_seen = true; + key_start = Some(i); break; } - range = Range::default(); + value_range = Range::default(); } Event::Value(_) => { - (range.start, range.end) = (i, i); + (value_range.start, value_range.end) = (i, i); } Event::ValueNotDone(_) | Event::ValueDone(_) => { - if range.end == 0 { - range.end = i + if value_range.end == 0 { + value_range.end = i } else { - range.start = i + value_range.start = i }; } _ => (), } } - if !key_seen { - Range::default() - } else { + key_start.map(|key_start| { // value end needs to be offset by one so that the last value's index // is included in the range - range.start..range.end + 1 - } + let value_range = value_range.start..value_range.end + 1; + (key_start..value_range.end, value_range) + }) } } @@ -288,10 +287,7 @@ impl<'event> SectionBody<'event> { #[must_use] pub fn value(&self, key: &str) -> Option> { let key = Key::from_str_unchecked(key); - let range = self.value_range_by_key(&key); - if range.is_empty() { - return None; - } + let (_, range) = self.key_and_value_range_by(&key)?; let mut concatenated = BString::default(); for event in &self.0[range] { diff --git a/git-config/tests/file/mutable/section.rs b/git-config/tests/file/mutable/section.rs index ca5f178d8ef..4abf3eae7f8 100644 --- a/git-config/tests/file/mutable/section.rs +++ b/git-config/tests/file/mutable/section.rs @@ -11,17 +11,18 @@ mod remove { assert_eq!(section.keys().count(), 5); let prev_values = vec!["v", "", "", "", "a b c"]; - let num_values = section.num_values(); + let mut num_values = section.num_values(); for (key, expected_prev_value) in ('a'..='e').zip(prev_values) { let prev_value = section.remove(&key.to_string().try_into()?); + num_values -= 1; assert_eq!(prev_value.expect("present").as_ref(), expected_prev_value); - assert_eq!(section.num_values(), num_values, "we don't actually remove keys"); + assert_eq!(section.num_values(), num_values); } assert!(!section.is_empty(), "everything is still there"); assert_eq!( config.to_string(), - "\n [a]\n a = \n b = \n c=\n d\n e =" + "\n [a]\n \n \n \n \n " ); Ok(()) } From 393b392d515661e5c3e60629319fdab771c3d3f0 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 13 Jul 2022 21:32:09 +0800 Subject: [PATCH 197/366] change!: Rename `Mutable*` into `$1Mut` for consistency. (#331) --- git-config/src/file/access/mutate.rs | 10 +++++----- git-config/src/file/access/raw.rs | 12 ++++++------ git-config/src/file/mod.rs | 6 +++--- git-config/src/file/mutable/multi_value.rs | 16 ++++++++-------- git-config/src/file/mutable/section.rs | 8 ++++---- git-config/src/file/mutable/value.rs | 8 ++++---- git-config/src/file/utils.rs | 6 +++--- git-config/tests/file/mutable/value.rs | 2 +- 8 files changed, 34 insertions(+), 34 deletions(-) diff --git a/git-config/src/file/access/mutate.rs b/git-config/src/file/access/mutate.rs index 1356f92bf32..b9022b98c12 100644 --- a/git-config/src/file/access/mutate.rs +++ b/git-config/src/file/access/mutate.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use crate::{ - file::{rename_section, MutableSection, SectionBody}, + file::{rename_section, SectionBody, SectionMut}, lookup, parse::section, File, @@ -14,13 +14,13 @@ impl<'event> File<'event> { &'a mut self, section_name: &str, subsection_name: Option<&str>, - ) -> Result, lookup::existing::Error> { + ) -> Result, lookup::existing::Error> { let id = self .section_ids_by_name_and_subname(section_name, subsection_name)? .rev() .next() .expect("BUG: Section lookup vec was empty"); - Ok(MutableSection::new( + Ok(SectionMut::new( self.sections .get_mut(&id) .expect("BUG: Section did not have id from lookup"), @@ -62,7 +62,7 @@ impl<'event> File<'event> { &mut self, section_name: impl Into>, subsection_name: impl Into>>, - ) -> Result, section::header::Error> { + ) -> Result, section::header::Error> { let mut section = self.push_section(section_name, subsection_name, SectionBody::default())?; section.push_newline(); Ok(section) @@ -131,7 +131,7 @@ impl<'event> File<'event> { section_name: impl Into>, subsection_name: impl Into>>, section: SectionBody<'event>, - ) -> Result, section::header::Error> { + ) -> Result, section::header::Error> { Ok(self.push_section_internal(section::Header::new(section_name, subsection_name)?, section)) } diff --git a/git-config/src/file/access/raw.rs b/git-config/src/file/access/raw.rs index 6087020c362..191a3d0d361 100644 --- a/git-config/src/file/access/raw.rs +++ b/git-config/src/file/access/raw.rs @@ -3,7 +3,7 @@ use std::{borrow::Cow, collections::HashMap}; use bstr::BStr; use crate::{ - file::{mutable::multi_value::EntryData, Index, MutableMultiValue, MutableSection, MutableValue, Size}, + file::{mutable::multi_value::EntryData, Index, MultiValueMut, SectionMut, Size, ValueMut}, lookup, parse::{section, Event}, File, @@ -44,7 +44,7 @@ impl<'event> File<'event> { section_name: &'lookup str, subsection_name: Option<&'lookup str>, key: &'lookup str, - ) -> Result, lookup::existing::Error> { + ) -> Result, lookup::existing::Error> { let mut section_ids = self .section_ids_by_name_and_subname(section_name, subsection_name)? .rev(); @@ -87,8 +87,8 @@ impl<'event> File<'event> { } drop(section_ids); - return Ok(MutableValue { - section: MutableSection::new(self.sections.get_mut(§ion_id).expect("known section-id")), + return Ok(ValueMut { + section: SectionMut::new(self.sections.get_mut(§ion_id).expect("known section-id")), key, index: Index(index), size: Size(size), @@ -207,7 +207,7 @@ impl<'event> File<'event> { section_name: &'lookup str, subsection_name: Option<&'lookup str>, key: &'lookup str, - ) -> Result, lookup::existing::Error> { + ) -> Result, lookup::existing::Error> { let section_ids = self.section_ids_by_name_and_subname(section_name, subsection_name)?; let key = section::Key(Cow::::Borrowed(key.into())); @@ -254,7 +254,7 @@ impl<'event> File<'event> { if entries.is_empty() { Err(lookup::existing::Error::KeyMissing) } else { - Ok(MutableMultiValue { + Ok(MultiValueMut { section: &mut self.sections, key, indices_and_sizes: entries, diff --git a/git-config/src/file/mod.rs b/git-config/src/file/mod.rs index 3f80e8d6093..1c78d090fd6 100644 --- a/git-config/src/file/mod.rs +++ b/git-config/src/file/mod.rs @@ -10,9 +10,9 @@ use bstr::BStr; mod mutable; pub use mutable::{ - multi_value::MutableMultiValue, - section::{MutableSection, SectionBody, SectionBodyIter}, - value::MutableValue, + multi_value::MultiValueMut, + section::{SectionBody, SectionBodyIter, SectionMut}, + value::ValueMut, }; mod init; diff --git a/git-config/src/file/mutable/multi_value.rs b/git-config/src/file/mutable/multi_value.rs index 5569c696ce4..26cf2d77f15 100644 --- a/git-config/src/file/mutable/multi_value.rs +++ b/git-config/src/file/mutable/multi_value.rs @@ -17,7 +17,7 @@ pub(crate) struct EntryData { /// An intermediate representation of a mutable multivar obtained from a [`File`][crate::File]. #[derive(PartialEq, Eq, Debug)] -pub struct MutableMultiValue<'borrow, 'lookup, 'event> { +pub struct MultiValueMut<'borrow, 'lookup, 'event> { pub(crate) section: &'borrow mut HashMap>, pub(crate) key: section::Key<'lookup>, /// Each entry data struct provides sufficient information to index into @@ -30,7 +30,7 @@ pub struct MutableMultiValue<'borrow, 'lookup, 'event> { pub(crate) offsets: HashMap>, } -impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { +impl<'borrow, 'lookup, 'event> MultiValueMut<'borrow, 'lookup, 'event> { /// Returns the actual values. pub fn get(&self) -> Result>, lookup::existing::Error> { let mut expect_value = false; @@ -42,7 +42,7 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { offset_index, } in &self.indices_and_sizes { - let (offset, size) = MutableMultiValue::index_and_size(&self.offsets, *section_id, *offset_index); + let (offset, size) = MultiValueMut::index_and_size(&self.offsets, *section_id, *offset_index); for event in &self.section.get(section_id).expect("known section id").as_ref()[offset..offset + size] { match event { Event::SectionKey(section_key) if *section_key == self.key => expect_value = true, @@ -100,7 +100,7 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { section_id, offset_index, } = self.indices_and_sizes[index]; - MutableMultiValue::set_value_inner( + MultiValueMut::set_value_inner( &self.key, &mut self.offsets, self.section.get_mut(§ion_id).expect("known section id"), @@ -165,13 +165,13 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { offset_index: usize, value: &BStr, ) { - let (offset, size) = MutableMultiValue::index_and_size(offsets, section_id, offset_index); + let (offset, size) = MultiValueMut::index_and_size(offsets, section_id, offset_index); let whitespace: Whitespace<'_> = (&*section).into(); let section = section.as_mut(); section.drain(offset..offset + size); let key_sep_events = whitespace.key_value_separators(); - MutableMultiValue::set_offset(offsets, section_id, offset_index, 2 + key_sep_events.len()); + MultiValueMut::set_offset(offsets, section_id, offset_index, 2 + key_sep_events.len()); section.insert(offset, Event::Value(escape_value(value).into())); section.insert_many(offset, key_sep_events.into_iter().rev()); section.insert(offset, Event::SectionKey(key.to_owned())); @@ -188,7 +188,7 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { section_id, offset_index, } = &self.indices_and_sizes[index]; - let (offset, size) = MutableMultiValue::index_and_size(&self.offsets, *section_id, *offset_index); + let (offset, size) = MultiValueMut::index_and_size(&self.offsets, *section_id, *offset_index); if size == 0 { return; } @@ -210,7 +210,7 @@ impl<'borrow, 'lookup, 'event> MutableMultiValue<'borrow, 'lookup, 'event> { offset_index, } in &self.indices_and_sizes { - let (offset, size) = MutableMultiValue::index_and_size(&self.offsets, *section_id, *offset_index); + let (offset, size) = MultiValueMut::index_and_size(&self.offsets, *section_id, *offset_index); if size == 0 { continue; } diff --git a/git-config/src/file/mutable/section.rs b/git-config/src/file/mutable/section.rs index e1ffce7018d..c8b0775f439 100644 --- a/git-config/src/file/mutable/section.rs +++ b/git-config/src/file/mutable/section.rs @@ -18,14 +18,14 @@ use crate::{ /// A opaque type that represents a mutable reference to a section. #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] -pub struct MutableSection<'a, 'event> { +pub struct SectionMut<'a, 'event> { section: &'a mut SectionBody<'event>, implicit_newline: bool, whitespace: Whitespace<'event>, } /// Mutating methods. -impl<'a, 'event> MutableSection<'a, 'event> { +impl<'a, 'event> SectionMut<'a, 'event> { /// Adds an entry to the end of this section. pub fn push(&mut self, key: Key<'event>, value: &BStr) { if let Some(ws) = &self.whitespace.pre_key { @@ -153,7 +153,7 @@ impl<'a, 'event> MutableSection<'a, 'event> { } // Internal methods that may require exact indices for faster operations. -impl<'a, 'event> MutableSection<'a, 'event> { +impl<'a, 'event> SectionMut<'a, 'event> { pub(crate) fn new(section: &'a mut SectionBody<'event>) -> Self { let whitespace = (&*section).into(); Self { @@ -224,7 +224,7 @@ impl<'a, 'event> MutableSection<'a, 'event> { } } -impl<'event> Deref for MutableSection<'_, 'event> { +impl<'event> Deref for SectionMut<'_, 'event> { type Target = SectionBody<'event>; fn deref(&self) -> &Self::Target { diff --git a/git-config/src/file/mutable/value.rs b/git-config/src/file/mutable/value.rs index 1c85212e7ff..563961482bc 100644 --- a/git-config/src/file/mutable/value.rs +++ b/git-config/src/file/mutable/value.rs @@ -3,21 +3,21 @@ use std::borrow::Cow; use bstr::BStr; use crate::{ - file::{mutable::section::MutableSection, Index, Size}, + file::{mutable::section::SectionMut, Index, Size}, lookup, parse::section, }; /// An intermediate representation of a mutable value obtained from a [`File`][crate::File]. #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] -pub struct MutableValue<'borrow, 'lookup, 'event> { - pub(crate) section: MutableSection<'borrow, 'event>, +pub struct ValueMut<'borrow, 'lookup, 'event> { + pub(crate) section: SectionMut<'borrow, 'event>, pub(crate) key: section::Key<'lookup>, pub(crate) index: Index, pub(crate) size: Size, } -impl<'borrow, 'lookup, 'event> MutableValue<'borrow, 'lookup, 'event> { +impl<'borrow, 'lookup, 'event> ValueMut<'borrow, 'lookup, 'event> { /// Returns the actual value. This is computed each time this is called /// requiring an allocation for multi-line values. pub fn get(&self) -> Result, lookup::existing::Error> { diff --git a/git-config/src/file/utils.rs b/git-config/src/file/utils.rs index c3407f75f4c..7642f05ba14 100644 --- a/git-config/src/file/utils.rs +++ b/git-config/src/file/utils.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use bstr::BStr; use crate::{ - file::{MutableSection, SectionBody, SectionBodyId, SectionBodyIds}, + file::{SectionBody, SectionBodyId, SectionBodyIds, SectionMut}, lookup, parse::section, File, @@ -16,7 +16,7 @@ impl<'event> File<'event> { &mut self, header: section::Header<'event>, section: SectionBody<'event>, - ) -> MutableSection<'_, 'event> { + ) -> SectionMut<'_, 'event> { let new_section_id = SectionBodyId(self.section_id_counter); self.section_headers.insert(new_section_id, header.clone()); self.sections.insert(new_section_id, section); @@ -55,7 +55,7 @@ impl<'event> File<'event> { self.section_id_counter += 1; self.sections .get_mut(&new_section_id) - .map(MutableSection::new) + .map(SectionMut::new) .expect("previously inserted section") } diff --git a/git-config/tests/file/mutable/value.rs b/git-config/tests/file/mutable/value.rs index 24bf8b3f253..fbce8329e16 100644 --- a/git-config/tests/file/mutable/value.rs +++ b/git-config/tests/file/mutable/value.rs @@ -144,7 +144,7 @@ mod set_string { } mod delete { - use crate::file::mutable::value::init_config; + use super::init_config; #[test] fn single_line_value() -> crate::Result { From 3d25fe6c7a52529488fab19c927d64a1bc75838f Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 13 Jul 2022 21:51:03 +0800 Subject: [PATCH 198/366] change!: Much more comfortable API `file::*Mut` types thanks to `impl Into/AsRef`. (#331) --- git-config/src/file/access/mutate.rs | 2 +- git-config/src/file/access/raw.rs | 28 +++++++++++--------- git-config/src/file/mutable/multi_value.rs | 18 ++++++++----- git-config/src/file/mutable/section.rs | 15 ++++++----- git-config/src/file/mutable/value.rs | 6 ++--- git-config/tests/file/mutable/multi_value.rs | 1 + git-config/tests/file/mutable/section.rs | 11 ++++---- 7 files changed, 45 insertions(+), 36 deletions(-) diff --git a/git-config/src/file/access/mutate.rs b/git-config/src/file/access/mutate.rs index b9022b98c12..2575cd26ff4 100644 --- a/git-config/src/file/access/mutate.rs +++ b/git-config/src/file/access/mutate.rs @@ -52,7 +52,7 @@ impl<'event> File<'event> { /// # use git_config::parse::section; /// let mut git_config = git_config::File::default(); /// let mut section = git_config.new_section("hello", Some("world".into()))?; - /// section.push(section::Key::try_from("a")?, b"b".as_bstr().into()); + /// section.push(section::Key::try_from("a")?, "b"); /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n\ta = b\n"); /// let _section = git_config.new_section("core", None); /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n\ta = b\n[core]\n"); diff --git a/git-config/src/file/access/raw.rs b/git-config/src/file/access/raw.rs index 191a3d0d361..406a575ce06 100644 --- a/git-config/src/file/access/raw.rs +++ b/git-config/src/file/access/raw.rs @@ -343,9 +343,9 @@ impl<'event> File<'event> { /// # use bstr::BStr; /// # let mut git_config = git_config::File::try_from("[core]a=b\n[core]\na=c\na=d").unwrap(); /// let new_values = vec![ - /// "x".into(), - /// "y".into(), - /// "z".into(), + /// "x", + /// "y", + /// "z", /// ]; /// git_config.set_raw_multi_value("core", None, "a", new_values.into_iter())?; /// let fetched_config = git_config.raw_values("core", None, "a")?; @@ -364,8 +364,8 @@ impl<'event> File<'event> { /// # use bstr::BStr; /// # let mut git_config = git_config::File::try_from("[core]a=b\n[core]\na=c\na=d").unwrap(); /// let new_values = vec![ - /// "x".into(), - /// "y".into(), + /// "x", + /// "y", /// ]; /// git_config.set_raw_multi_value("core", None, "a", new_values.into_iter())?; /// let fetched_config = git_config.raw_values("core", None, "a")?; @@ -383,22 +383,26 @@ impl<'event> File<'event> { /// # use bstr::BStr; /// # let mut git_config = git_config::File::try_from("[core]a=b\n[core]\na=c\na=d").unwrap(); /// let new_values = vec![ - /// "x".into(), - /// "y".into(), - /// "z".into(), - /// "discarded".into(), + /// "x", + /// "y", + /// "z", + /// "discarded", /// ]; /// git_config.set_raw_multi_value("core", None, "a", new_values)?; /// assert!(!git_config.raw_values("core", None, "a")?.contains(&Cow::::Borrowed("discarded".into()))); /// # Ok::<(), git_config::lookup::existing::Error>(()) /// ``` - pub fn set_raw_multi_value<'a>( + pub fn set_raw_multi_value<'a, Iter, Item>( &mut self, section_name: &str, subsection_name: Option<&str>, key: &str, - new_values: impl IntoIterator, - ) -> Result<(), lookup::existing::Error> { + new_values: Iter, + ) -> Result<(), lookup::existing::Error> + where + Iter: IntoIterator, + Item: Into<&'a BStr>, + { self.raw_values_mut(section_name, subsection_name, key) .map(|mut v| v.set_values(new_values)) } diff --git a/git-config/src/file/mutable/multi_value.rs b/git-config/src/file/mutable/multi_value.rs index 26cf2d77f15..096c55ee439 100644 --- a/git-config/src/file/mutable/multi_value.rs +++ b/git-config/src/file/mutable/multi_value.rs @@ -86,8 +86,8 @@ impl<'borrow, 'lookup, 'event> MultiValueMut<'borrow, 'lookup, 'event> { /// # Safety /// /// This will panic if the index is out of range. - pub fn set_string_at(&mut self, index: usize, input: impl AsRef) { - self.set_at(index, input.as_ref().into()); + pub fn set_string_at(&mut self, index: usize, value: impl AsRef) { + self.set_at(index, value.as_ref()); } /// Sets the value at the given index. @@ -95,7 +95,7 @@ impl<'borrow, 'lookup, 'event> MultiValueMut<'borrow, 'lookup, 'event> { /// # Safety /// /// This will panic if the index is out of range. - pub fn set_at(&mut self, index: usize, input: &BStr) { + pub fn set_at<'a>(&mut self, index: usize, value: impl Into<&'a BStr>) { let EntryData { section_id, offset_index, @@ -106,7 +106,7 @@ impl<'borrow, 'lookup, 'event> MultiValueMut<'borrow, 'lookup, 'event> { self.section.get_mut(§ion_id).expect("known section id"), section_id, offset_index, - input, + value.into(), ); } @@ -117,14 +117,18 @@ impl<'borrow, 'lookup, 'event> MultiValueMut<'borrow, 'lookup, 'event> { /// remaining values are ignored. /// /// [`zip`]: std::iter::Iterator::zip - pub fn set_values<'a>(&mut self, input: impl IntoIterator) { + pub fn set_values<'a, Iter, Item>(&mut self, values: Iter) + where + Iter: IntoIterator, + Item: Into<&'a BStr>, + { for ( EntryData { section_id, offset_index, }, value, - ) in self.indices_and_sizes.iter().zip(input) + ) in self.indices_and_sizes.iter().zip(values) { Self::set_value_inner( &self.key, @@ -132,7 +136,7 @@ impl<'borrow, 'lookup, 'event> MultiValueMut<'borrow, 'lookup, 'event> { self.section.get_mut(section_id).expect("known section id"), *section_id, *offset_index, - value, + value.into(), ); } } diff --git a/git-config/src/file/mutable/section.rs b/git-config/src/file/mutable/section.rs index c8b0775f439..aa5127de85e 100644 --- a/git-config/src/file/mutable/section.rs +++ b/git-config/src/file/mutable/section.rs @@ -26,15 +26,15 @@ pub struct SectionMut<'a, 'event> { /// Mutating methods. impl<'a, 'event> SectionMut<'a, 'event> { - /// Adds an entry to the end of this section. - pub fn push(&mut self, key: Key<'event>, value: &BStr) { + /// Adds an entry to the end of this section name `key` and `value`. + pub fn push<'b>(&mut self, key: Key<'event>, value: impl Into<&'b BStr>) { if let Some(ws) = &self.whitespace.pre_key { self.section.0.push(Event::Whitespace(ws.clone())); } self.section.0.push(Event::SectionKey(key)); self.section.0.extend(self.whitespace.key_value_separators()); - self.section.0.push(Event::Value(escape_value(value).into())); + self.section.0.push(Event::Value(escape_value(value.into()).into())); if self.implicit_newline { self.section.0.push(Event::Newline(BString::from("\n").into())); } @@ -79,7 +79,7 @@ impl<'a, 'event> SectionMut<'a, 'event> { /// Sets the last key value pair if it exists, or adds the new value. /// Returns the previous value if it replaced a value, or None if it adds /// the value. - pub fn set(&mut self, key: Key<'event>, value: &BStr) -> Option> { + pub fn set<'b>(&mut self, key: Key<'event>, value: impl Into<&'b BStr>) -> Option> { match self.key_and_value_range_by(&key) { None => { self.push(key, value); @@ -90,15 +90,16 @@ impl<'a, 'event> SectionMut<'a, 'event> { let ret = self.remove_internal(value_range); self.section .0 - .insert(range_start, Event::Value(escape_value(value).into())); + .insert(range_start, Event::Value(escape_value(value.into()).into())); Some(ret) } } } /// Removes the latest value by key and returns it, if it exists. - pub fn remove(&mut self, key: &Key<'event>) -> Option> { - let (key_range, _value_range) = self.key_and_value_range_by(key)?; + pub fn remove(&mut self, key: impl AsRef) -> Option> { + let key = Key::from_str_unchecked(key.as_ref()); + let (key_range, _value_range) = self.key_and_value_range_by(&key)?; Some(self.remove_internal(key_range)) } diff --git a/git-config/src/file/mutable/value.rs b/git-config/src/file/mutable/value.rs index 563961482bc..63d39ed1ec0 100644 --- a/git-config/src/file/mutable/value.rs +++ b/git-config/src/file/mutable/value.rs @@ -28,17 +28,17 @@ impl<'borrow, 'lookup, 'event> ValueMut<'borrow, 'lookup, 'event> { /// the Value event(s) are replaced with a single new event containing the /// new value. pub fn set_string(&mut self, input: impl AsRef) { - self.set(input.as_ref().into()); + self.set(input.as_ref()); } /// Update the value to the provided one. This modifies the value such that /// the Value event(s) are replaced with a single new event containing the /// new value. - pub fn set(&mut self, input: &BStr) { + pub fn set<'a>(&mut self, input: impl Into<&'a BStr>) { if self.size.0 > 0 { self.section.delete(self.index, self.index + self.size); } - self.size = self.section.set_internal(self.index, self.key.to_owned(), input); + self.size = self.section.set_internal(self.index, self.key.to_owned(), input.into()); } /// Removes the value. Does nothing when called multiple times in diff --git a/git-config/tests/file/mutable/multi_value.rs b/git-config/tests/file/mutable/multi_value.rs index 973cffd107d..5e4a5edea04 100644 --- a/git-config/tests/file/mutable/multi_value.rs +++ b/git-config/tests/file/mutable/multi_value.rs @@ -144,6 +144,7 @@ mod delete { let mut config = init_config(); let mut values = config.raw_values_mut("core", None, "a")?; values.delete_all(); + values.delete_all(); assert!(values.get().is_err()); assert_eq!(config.to_string(), "[core]\n \n [core]\n \n "); Ok(()) diff --git a/git-config/tests/file/mutable/section.rs b/git-config/tests/file/mutable/section.rs index 4abf3eae7f8..3b35401d456 100644 --- a/git-config/tests/file/mutable/section.rs +++ b/git-config/tests/file/mutable/section.rs @@ -1,6 +1,5 @@ mod remove { use super::multi_value_section; - use std::convert::TryInto; #[test] fn all() -> crate::Result { @@ -13,7 +12,7 @@ mod remove { let prev_values = vec!["v", "", "", "", "a b c"]; let mut num_values = section.num_values(); for (key, expected_prev_value) in ('a'..='e').zip(prev_values) { - let prev_value = section.remove(&key.to_string().try_into()?); + let prev_value = section.remove(key.to_string()); num_values -= 1; assert_eq!(prev_value.expect("present").as_ref(), expected_prev_value); assert_eq!(section.num_values(), num_values); @@ -68,7 +67,7 @@ mod set { for (key, (new_value, expected_prev_value)) in (b'a'..=b'e').zip(values.into_iter().zip(prev_values)) { let key = std::str::from_utf8(std::slice::from_ref(&key))?.to_owned(); - let prev_value = section.set(key.try_into()?, new_value.into()); + let prev_value = section.set(key.try_into()?, new_value); assert_eq!(prev_value.as_deref().expect("prev value set"), expected_prev_value); } @@ -76,7 +75,7 @@ mod set { assert_eq!( config .section_mut("a", None)? - .set("new-one".to_owned().try_into()?, "value".into()), + .set("new-one".to_owned().try_into()?, "value"), None, "new values don't replace an existing one" ); @@ -139,7 +138,7 @@ mod push { let mut config = git_config::File::default(); let mut section = config.new_section("a", None).unwrap(); section.set_implicit_newline(false); - section.push(Key::try_from("k").unwrap(), value.into()); + section.push(Key::try_from("k").unwrap(), value); assert_eq!(config.to_bstring(), expected); } } @@ -157,7 +156,7 @@ mod set_leading_whitespace { let mut config = git_config::File::default(); let mut section = config.new_section("core", None)?; section.set_leading_whitespace(cow_str("\n\t").into()); - section.push(Key::try_from("a")?, "v".into()); + section.push(Key::try_from("a")?, "v"); assert_eq!(config.to_string(), "[core]\n\n\ta = v\n"); Ok(()) } From 3de0cfd81523e4ba7cc362d8625f85ebf8fd9172 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 13 Jul 2022 22:08:16 +0800 Subject: [PATCH 199/366] change!: All accessors in `File` are now using `impl AsRef` where possible for added comfort. (#331) --- git-config/src/file/access/comfort.rs | 33 +++++++++++++++++------- git-config/src/file/access/mutate.rs | 8 +++--- git-config/src/file/access/raw.rs | 34 +++++++++++++------------ git-config/src/file/access/read_only.rs | 4 +-- git-config/src/file/mutable/section.rs | 12 ++++----- 5 files changed, 54 insertions(+), 37 deletions(-) diff --git a/git-config/src/file/access/comfort.rs b/git-config/src/file/access/comfort.rs index f4dc5ba34a8..af734c7649f 100644 --- a/git-config/src/file/access/comfort.rs +++ b/git-config/src/file/access/comfort.rs @@ -9,7 +9,12 @@ impl<'event> File<'event> { /// Like [`value()`][File::value()], but returning an `None` if the string wasn't found. /// /// As strings perform no conversions, this will never fail. - pub fn string(&self, section_name: &str, subsection_name: Option<&str>, key: &str) -> Option> { + pub fn string( + &self, + section_name: impl AsRef, + subsection_name: Option<&str>, + key: impl AsRef, + ) -> Option> { self.raw_value(section_name, subsection_name, key).ok() } @@ -22,7 +27,12 @@ impl<'event> File<'event> { // TODO: add `secure_path()` or similar to make use of our knowledge of the trust associated with each configuration // file, maybe even remove the insecure version to force every caller to ask themselves if the resource can // be used securely or not. - pub fn path(&self, section_name: &str, subsection_name: Option<&str>, key: &str) -> Option> { + pub fn path( + &self, + section_name: impl AsRef, + subsection_name: Option<&str>, + key: impl AsRef, + ) -> Option> { self.raw_value(section_name, subsection_name, key) .ok() .map(crate::Path::from) @@ -31,9 +41,9 @@ impl<'event> File<'event> { /// Like [`value()`][File::value()], but returning `None` if the boolean value wasn't found. pub fn boolean( &self, - section_name: &str, + section_name: impl AsRef, subsection_name: Option<&str>, - key: &str, + key: impl AsRef, ) -> Option> { self.raw_value(section_name, subsection_name, key) .ok() @@ -43,9 +53,9 @@ impl<'event> File<'event> { /// Like [`value()`][File::value()], but returning an `Option` if the integer wasn't found. pub fn integer( &self, - section_name: &str, + section_name: impl AsRef, subsection_name: Option<&str>, - key: &str, + key: impl AsRef, ) -> Option> { let int = self.raw_value(section_name, subsection_name, key).ok()?; Some(crate::Integer::try_from(int.as_ref()).and_then(|b| { @@ -55,7 +65,12 @@ impl<'event> File<'event> { } /// Similar to [`values(…)`][File::values()] but returning strings if at least one of them was found. - pub fn strings(&self, section_name: &str, subsection_name: Option<&str>, key: &str) -> Option>> { + pub fn strings( + &self, + section_name: impl AsRef, + subsection_name: Option<&str>, + key: impl AsRef, + ) -> Option>> { self.raw_values(section_name, subsection_name, key).ok() } @@ -63,9 +78,9 @@ impl<'event> File<'event> { /// and if none of them overflows. pub fn integers( &self, - section_name: &str, + section_name: impl AsRef, subsection_name: Option<&str>, - key: &str, + key: impl AsRef, ) -> Option, value::Error>> { self.raw_values(section_name, subsection_name, key).ok().map(|values| { values diff --git a/git-config/src/file/access/mutate.rs b/git-config/src/file/access/mutate.rs index 2575cd26ff4..509e6ca75f3 100644 --- a/git-config/src/file/access/mutate.rs +++ b/git-config/src/file/access/mutate.rs @@ -12,11 +12,11 @@ impl<'event> File<'event> { /// Returns an mutable section with a given name and optional subsection. pub fn section_mut<'a>( &'a mut self, - section_name: &str, + section_name: impl AsRef, subsection_name: Option<&str>, ) -> Result, lookup::existing::Error> { let id = self - .section_ids_by_name_and_subname(section_name, subsection_name)? + .section_ids_by_name_and_subname(section_name.as_ref(), subsection_name)? .rev() .next() .expect("BUG: Section lookup vec was empty"); @@ -138,13 +138,13 @@ impl<'event> File<'event> { /// Renames a section, modifying the last matching section. pub fn rename_section<'a>( &mut self, - section_name: &str, + section_name: impl AsRef, subsection_name: impl Into>, new_section_name: impl Into>, new_subsection_name: impl Into>>, ) -> Result<(), rename_section::Error> { let id = self - .section_ids_by_name_and_subname(section_name, subsection_name.into())? + .section_ids_by_name_and_subname(section_name.as_ref(), subsection_name.into())? .rev() .next() .expect("list of sections were empty, which violates invariant"); diff --git a/git-config/src/file/access/raw.rs b/git-config/src/file/access/raw.rs index 406a575ce06..983a90fcb4d 100644 --- a/git-config/src/file/access/raw.rs +++ b/git-config/src/file/access/raw.rs @@ -20,11 +20,12 @@ impl<'event> File<'event> { /// a multivar instead. pub fn raw_value( &self, - section_name: &str, + section_name: impl AsRef, subsection_name: Option<&str>, - key: &str, + key: impl AsRef, ) -> Result, lookup::existing::Error> { - let section_ids = self.section_ids_by_name_and_subname(section_name, subsection_name)?; + let section_ids = self.section_ids_by_name_and_subname(section_name.as_ref(), subsection_name)?; + let key = key.as_ref(); for section_id in section_ids.rev() { if let Some(v) = self.sections.get(§ion_id).expect("known section id").value(key) { return Ok(v); @@ -41,12 +42,12 @@ impl<'event> File<'event> { /// references to all values of a multivar instead. pub fn raw_value_mut<'lookup>( &mut self, - section_name: &'lookup str, + section_name: impl AsRef, subsection_name: Option<&'lookup str>, key: &'lookup str, ) -> Result, lookup::existing::Error> { let mut section_ids = self - .section_ids_by_name_and_subname(section_name, subsection_name)? + .section_ids_by_name_and_subname(section_name.as_ref(), subsection_name)? .rev(); let key = section::Key(Cow::::Borrowed(key.into())); @@ -135,12 +136,13 @@ impl<'event> File<'event> { /// value for a given key, if your key does not support multi-valued values. pub fn raw_values( &self, - section_name: &str, + section_name: impl AsRef, subsection_name: Option<&str>, - key: &str, + key: impl AsRef, ) -> Result>, lookup::existing::Error> { let mut values = Vec::new(); - let section_ids = self.section_ids_by_name_and_subname(section_name, subsection_name)?; + let section_ids = self.section_ids_by_name_and_subname(section_name.as_ref(), subsection_name)?; + let key = key.as_ref(); for section_id in section_ids { values.extend(self.sections.get(§ion_id).expect("known section id").values(key)); } @@ -204,11 +206,11 @@ impl<'event> File<'event> { /// traversal of the config. pub fn raw_values_mut<'lookup>( &mut self, - section_name: &'lookup str, + section_name: impl AsRef, subsection_name: Option<&'lookup str>, key: &'lookup str, ) -> Result, lookup::existing::Error> { - let section_ids = self.section_ids_by_name_and_subname(section_name, subsection_name)?; + let section_ids = self.section_ids_by_name_and_subname(section_name.as_ref(), subsection_name)?; let key = section::Key(Cow::::Borrowed(key.into())); let mut offsets = HashMap::new(); @@ -299,12 +301,12 @@ impl<'event> File<'event> { /// ``` pub fn set_raw_value( &mut self, - section_name: &str, + section_name: impl AsRef, subsection_name: Option<&str>, - key: &str, + key: impl AsRef, new_value: &BStr, ) -> Result<(), lookup::existing::Error> { - self.raw_value_mut(section_name, subsection_name, key) + self.raw_value_mut(section_name, subsection_name, key.as_ref()) .map(|mut entry| entry.set(new_value)) } @@ -394,16 +396,16 @@ impl<'event> File<'event> { /// ``` pub fn set_raw_multi_value<'a, Iter, Item>( &mut self, - section_name: &str, + section_name: impl AsRef, subsection_name: Option<&str>, - key: &str, + key: impl AsRef, new_values: Iter, ) -> Result<(), lookup::existing::Error> where Iter: IntoIterator, Item: Into<&'a BStr>, { - self.raw_values_mut(section_name, subsection_name, key) + self.raw_values_mut(section_name, subsection_name, key.as_ref()) .map(|mut v| v.set_values(new_values)) } } diff --git a/git-config/src/file/access/read_only.rs b/git-config/src/file/access/read_only.rs index 57da8abfa69..49b1fc059c4 100644 --- a/git-config/src/file/access/read_only.rs +++ b/git-config/src/file/access/read_only.rs @@ -120,11 +120,11 @@ impl<'event> File<'event> { /// Returns the last found immutable section with a given name and optional subsection name. pub fn section( &mut self, - section_name: &str, + section_name: impl AsRef, subsection_name: Option<&str>, ) -> Result<&SectionBody<'event>, lookup::existing::Error> { let id = self - .section_ids_by_name_and_subname(section_name, subsection_name)? + .section_ids_by_name_and_subname(section_name.as_ref(), subsection_name)? .rev() .next() .expect("BUG: Section lookup vec was empty"); diff --git a/git-config/src/file/mutable/section.rs b/git-config/src/file/mutable/section.rs index aa5127de85e..eadd2f9637b 100644 --- a/git-config/src/file/mutable/section.rs +++ b/git-config/src/file/mutable/section.rs @@ -286,8 +286,8 @@ impl<'event> SectionBody<'event> { impl<'event> SectionBody<'event> { /// Retrieves the last matching value in a section with the given key, if present. #[must_use] - pub fn value(&self, key: &str) -> Option> { - let key = Key::from_str_unchecked(key); + pub fn value(&self, key: impl AsRef) -> Option> { + let key = Key::from_str_unchecked(key.as_ref()); let (_, range) = self.key_and_value_range_by(&key)?; let mut concatenated = BString::default(); @@ -312,8 +312,8 @@ impl<'event> SectionBody<'event> { /// Retrieves all values that have the provided key name. This may return /// an empty vec, which implies there were no values with the provided key. #[must_use] - pub fn values(&self, key: &str) -> Vec> { - let key = &Key::from_str_unchecked(key); + pub fn values(&self, key: impl AsRef) -> Vec> { + let key = &Key::from_str_unchecked(key.as_ref()); let mut values = Vec::new(); let mut expect_value = false; let mut concatenated_value = BString::default(); @@ -349,8 +349,8 @@ impl<'event> SectionBody<'event> { /// Returns true if the section containss the provided key. #[must_use] - pub fn contains_key(&self, key: &str) -> bool { - let key = &Key::from_str_unchecked(key); + pub fn contains_key(&self, key: impl AsRef) -> bool { + let key = &Key::from_str_unchecked(key.as_ref()); self.0.iter().any(|e| { matches!(e, Event::SectionKey(k) if k == key From 4607a18e24b8270c182663a434b79dff8761db0e Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 14 Jul 2022 09:17:46 +0800 Subject: [PATCH 200/366] feat: Add `store::WriteRefLog::Always` to unconditionally write reflogs. (#331) --- git-ref/src/lib.rs | 2 ++ git-ref/src/store/file/loose/reflog.rs | 7 +++++-- .../src/store/file/loose/reflog/create_or_update/tests.rs | 6 +++--- .../transaction/prepare_and_commit/create_or_update.rs | 4 ++-- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/git-ref/src/lib.rs b/git-ref/src/lib.rs index cde0dad6c36..1a7bf1e85cd 100644 --- a/git-ref/src/lib.rs +++ b/git-ref/src/lib.rs @@ -58,6 +58,8 @@ pub mod store { /// The way a file store handles the reflog #[derive(Debug, PartialOrd, PartialEq, Ord, Eq, Hash, Clone, Copy)] pub enum WriteReflog { + /// Always write the reflog for all references for ref edits, unconditionally. + Always, /// Write a ref log for ref edits according to the standard rules. Normal, /// Never write a ref log. diff --git a/git-ref/src/store/file/loose/reflog.rs b/git-ref/src/store/file/loose/reflog.rs index ed2f7a86919..9d0d6223169 100644 --- a/git-ref/src/store/file/loose/reflog.rs +++ b/git-ref/src/store/file/loose/reflog.rs @@ -106,11 +106,14 @@ pub mod create_or_update { new: &oid, committer: git_actor::SignatureRef<'_>, message: &BStr, - force_create_reflog: bool, + mut force_create_reflog: bool, ) -> Result<(), Error> { let (reflog_base, full_name) = self.reflog_base_and_relative_path(name); match self.write_reflog { - WriteReflog::Normal => { + WriteReflog::Normal | WriteReflog::Always => { + if self.write_reflog == WriteReflog::Always { + force_create_reflog = true; + } let mut options = std::fs::OpenOptions::new(); options.append(true).read(false); let log_path = reflog_base.join(&full_name); diff --git a/git-ref/src/store/file/loose/reflog/create_or_update/tests.rs b/git-ref/src/store/file/loose/reflog/create_or_update/tests.rs index 72478f2ad6c..e5ff078607f 100644 --- a/git-ref/src/store/file/loose/reflog/create_or_update/tests.rs +++ b/git-ref/src/store/file/loose/reflog/create_or_update/tests.rs @@ -36,7 +36,7 @@ fn reflog_lines(store: &file::Store, name: &str, buf: &mut Vec) -> Result Result { @@ -79,7 +79,7 @@ fn missing_reflog_creates_it_even_if_similarly_named_empty_dir_exists_and_append let mut buf = Vec::new(); match mode { - WriteReflog::Normal => { + WriteReflog::Normal | WriteReflog::Always => { assert_eq!( reflog_lines(&store, full_name_str, &mut buf)?, vec![crate::log::Line { @@ -139,7 +139,7 @@ fn missing_reflog_creates_it_even_if_similarly_named_empty_dir_exists_and_append )?; match mode { - WriteReflog::Normal => { + WriteReflog::Normal | WriteReflog::Always => { assert_eq!( reflog_lines(&store, full_name_str, &mut buf)?.len(), 1, diff --git a/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs b/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs index 0f2872b6172..546d8fb90d9 100644 --- a/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs +++ b/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs @@ -397,7 +397,7 @@ fn cancellation_after_preparation_leaves_no_change() -> crate::Result { #[test] fn symbolic_head_missing_referent_then_update_referent() -> crate::Result { - for reflog_writemode in &[WriteReflog::Normal, WriteReflog::Disable] { + for reflog_writemode in &[WriteReflog::Normal, WriteReflog::Disable, WriteReflog::Always] { let (_keep, mut store) = empty_store()?; store.write_reflog = *reflog_writemode; let referent = "refs/heads/alt-main"; @@ -522,7 +522,7 @@ fn symbolic_head_missing_referent_then_update_referent() -> crate::Result { let mut buf = Vec::new(); for ref_name in &["HEAD", referent] { match reflog_writemode { - WriteReflog::Normal => { + WriteReflog::Normal | WriteReflog::Always => { let expected_line = log_line(git_hash::Kind::Sha1.null(), new_oid, "an actual change"); assert_eq!(reflog_lines(&store, *ref_name)?, vec![expected_line]); } From 6b901843cb18b3d31f8b0b84bb9ebbae279aff19 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 14 Jul 2022 09:49:43 +0800 Subject: [PATCH 201/366] fix: `Boolean` can use numbers to indicate true or false, drops support for `one` and `zero`.(#331) --- git-config/src/values/boolean.rs | 27 ++++++++++++++++++--------- git-config/tests/values/boolean.rs | 13 ++++++++----- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/git-config/src/values/boolean.rs b/git-config/src/values/boolean.rs index 83880085b98..365e00f3366 100644 --- a/git-config/src/values/boolean.rs +++ b/git-config/src/values/boolean.rs @@ -1,12 +1,12 @@ use std::{borrow::Cow, convert::TryFrom, fmt::Display}; -use bstr::{BStr, BString}; +use bstr::{BStr, BString, ByteSlice}; use crate::{value, Boolean}; fn bool_err(input: impl Into) -> value::Error { value::Error::new( - "Booleans need to be 'no', 'off', 'false', 'zero' or 'yes', 'on', 'true', 'one'", + "Booleans need to be 'no', 'off', 'false', '' or 'yes', 'on', 'true' or any number", input, ) } @@ -20,11 +20,25 @@ impl TryFrom<&BStr> for Boolean { } else if parse_false(value) { Ok(Boolean(false)) } else { - Err(bool_err(value)) + use std::str::FromStr; + if let Some(integer) = value.to_str().ok().and_then(|s| i64::from_str(s).ok()) { + Ok(Boolean(integer != 0)) + } else { + Err(bool_err(value)) + } } } } +impl Boolean { + /// Return true if the boolean is a true value. + /// + /// Note that the inner value is accessible directly as well. + pub fn is_true(self) -> bool { + self.0 + } +} + impl TryFrom> for Boolean { type Error = value::Error; fn try_from(c: Cow<'_, BStr>) -> Result { @@ -58,14 +72,9 @@ fn parse_true(value: &BStr) -> bool { value.eq_ignore_ascii_case(b"yes") || value.eq_ignore_ascii_case(b"on") || value.eq_ignore_ascii_case(b"true") - || value.eq_ignore_ascii_case(b"one") || value.is_empty() } fn parse_false(value: &BStr) -> bool { - value.eq_ignore_ascii_case(b"no") - || value.eq_ignore_ascii_case(b"off") - || value.eq_ignore_ascii_case(b"false") - || value.eq_ignore_ascii_case(b"zero") - || value == "\"\"" + value.eq_ignore_ascii_case(b"no") || value.eq_ignore_ascii_case(b"off") || value.eq_ignore_ascii_case(b"false") } diff --git a/git-config/tests/values/boolean.rs b/git-config/tests/values/boolean.rs index 25637d3cd3b..61488748b1d 100644 --- a/git-config/tests/values/boolean.rs +++ b/git-config/tests/values/boolean.rs @@ -9,23 +9,26 @@ fn from_str_false() -> crate::Result { assert!(!Boolean::try_from(b("no"))?.0); assert!(!Boolean::try_from(b("off"))?.0); assert!(!Boolean::try_from(b("false"))?.0); - assert!(!Boolean::try_from(b("zero"))?.0); - assert!(!Boolean::try_from(b("\"\""))?.0); + assert!(!Boolean::try_from(b("0"))?.0); Ok(()) } #[test] -fn from_str_true() { +fn from_str_true() -> crate::Result { assert_eq!(Boolean::try_from(b("yes")).map(Into::into), Ok(true)); assert_eq!(Boolean::try_from(b("on")), Ok(Boolean(true))); assert_eq!(Boolean::try_from(b("true")), Ok(Boolean(true))); - assert_eq!(Boolean::try_from(b("one")), Ok(Boolean(true))); + assert_eq!(Boolean::try_from(b("")).map(|b| b.is_true()), Ok(true)); + assert!(Boolean::try_from(b("1"))?.0); + assert!(Boolean::try_from(b("+10"))?.0); + assert!(Boolean::try_from(b("-1"))?.0); + Ok(()) } #[test] fn ignores_case() { // Random subset - for word in &["no", "yes", "off", "true", "zero"] { + for word in &["no", "yes", "on", "off", "true", "false"] { let first: bool = Boolean::try_from(b(word)).unwrap().into(); let second: bool = Boolean::try_from(b(&*word.to_uppercase())).unwrap().into(); assert_eq!(first, second); From e263e13d312e41aa1481d104fa79ede509fbe1c5 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 14 Jul 2022 09:50:53 +0800 Subject: [PATCH 202/366] feat: respect `core.logallrefupdates` configuration setting. (#331) --- git-repository/src/config.rs | 21 +++++++++++++++++---- git-repository/src/open.rs | 12 +++++++----- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/git-repository/src/config.rs b/git-repository/src/config.rs index 749daf7fea2..9776e3629dc 100644 --- a/git-repository/src/config.rs +++ b/git-repository/src/config.rs @@ -28,6 +28,8 @@ pub(crate) struct Cache { pub object_hash: git_hash::Kind, /// If true, multi-pack indices, whether present or not, may be used by the object database. pub use_multi_pack_index: bool, + /// The representation of `core.logallrefupdates`, or `None` if the variable wasn't set. + pub reflog: Option, /// If true, we are on a case-insensitive file system. #[cfg_attr(not(feature = "git-index"), allow(dead_code))] pub ignore_case: bool, @@ -70,7 +72,7 @@ mod cache { let is_bare = config_bool(&config, "core.bare", false)?; let use_multi_pack_index = config_bool(&config, "core.multiPackIndex", true)?; - let ignore_case = config_bool(&config, "core.ignorecase", false)?; + let ignore_case = config_bool(&config, "core.ignoreCase", false)?; let excludes_file = config .path("core", None, "excludesFile") .map(|p| { @@ -84,12 +86,12 @@ mod cache { .transpose()?; let repo_format_version = config .value::("core", None, "repositoryFormatVersion") - .map_or(0, |v| v.value); + .map_or(0, |v| v.to_decimal().unwrap_or_default()); let object_hash = (repo_format_version != 1) .then(|| Ok(git_hash::Kind::Sha1)) .or_else(|| { config.string("extensions", None, "objectFormat").map(|format| { - if format.as_ref() == "sha1" { + if format.as_ref().eq_ignore_ascii_case(b"sha1") { Ok(git_hash::Kind::Sha1) } else { Err(Error::UnsupportedObjectFormat { @@ -100,13 +102,23 @@ mod cache { }) .transpose()? .unwrap_or(git_hash::Kind::Sha1); + let reflog = config.string("core", None, "logallrefupdates").map(|val| { + (val.eq_ignore_ascii_case(b"always")) + .then(|| git_ref::store::WriteReflog::Always) + .or_else(|| { + git_config::Boolean::try_from(val) + .ok() + .and_then(|b| b.is_true().then(|| git_ref::store::WriteReflog::Normal)) + }) + .unwrap_or(git_ref::store::WriteReflog::Disable) + }); let mut hex_len = None; if let Some(hex_len_str) = config.string("core", None, "abbrev") { if hex_len_str.trim().is_empty() { return Err(Error::EmptyValue { key: "core.abbrev" }); } - if hex_len_str.as_ref() != "auto" { + if !hex_len_str.eq_ignore_ascii_case(b"auto") { let value_bytes = hex_len_str.as_ref(); if let Ok(false) = Boolean::try_from(value_bytes).map(Into::into) { hex_len = object_hash.len_in_hex().into(); @@ -136,6 +148,7 @@ mod cache { resolved: config.into(), use_multi_pack_index, object_hash, + reflog, is_bare, ignore_case, hex_len, diff --git a/git-repository/src/open.rs b/git-repository/src/open.rs index ce8e7d40b41..fd1dc002913 100644 --- a/git-repository/src/open.rs +++ b/git-repository/src/open.rs @@ -255,11 +255,13 @@ impl ThreadSafeRepository { } let refs = { - let reflog = if worktree_dir.is_none() { - git_ref::store::WriteReflog::Disable - } else { - git_ref::store::WriteReflog::Normal - }; + let reflog = config.reflog.unwrap_or_else(|| { + if worktree_dir.is_none() { + git_ref::store::WriteReflog::Disable + } else { + git_ref::store::WriteReflog::Normal + } + }); match &common_dir { Some(common_dir) => { crate::RefStore::for_linked_worktree(&git_dir, common_dir, reflog, config.object_hash) From 010350180459aec41132c960ddafc7b81dd9c04d Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 14 Jul 2022 10:21:58 +0800 Subject: [PATCH 203/366] feat: add `DOT_GIT_DIR` constant, containing the name ".git". (#331) --- git-discover/src/is.rs | 30 +++++++++++++++++------------- git-discover/src/lib.rs | 3 +++ git-discover/src/path.rs | 3 ++- git-discover/src/repository.rs | 3 ++- git-discover/src/upwards.rs | 8 ++++---- 5 files changed, 28 insertions(+), 19 deletions(-) diff --git a/git-discover/src/is.rs b/git-discover/src/is.rs index 9a5201d6f23..1dd221674c7 100644 --- a/git-discover/src/is.rs +++ b/git-discover/src/is.rs @@ -1,3 +1,4 @@ +use crate::DOT_GIT_DIR; use std::{borrow::Cow, ffi::OsStr, path::Path}; /// Returns true if the given `git_dir` seems to be a bare repository. @@ -5,19 +6,20 @@ use std::{borrow::Cow, ffi::OsStr, path::Path}; /// Please note that repositories without an index generally _look_ bare, even though they might also be uninitialized. pub fn bare(git_dir_candidate: impl AsRef) -> bool { let git_dir = git_dir_candidate.as_ref(); - !(git_dir.join("index").exists() || (git_dir.file_name() == Some(OsStr::new(".git")) && git_dir.is_file())) + !(git_dir.join("index").exists() || (git_dir.file_name() == Some(OsStr::new(DOT_GIT_DIR)) && git_dir.is_file())) } /// What constitutes a valid git repository, returning the guessed repository kind /// purely based on the presence of files. Note that the git-config ultimately decides what's bare. /// -/// Returns the Kind of git directory that was passed, possibly alongside the supporting private worktree git dir +/// Returns the `Kind` of git directory that was passed, possibly alongside the supporting private worktree git dir. /// -/// Note that `.git` files are followed to a valid git directory, which then requires +/// Note that `.git` files are followed to a valid git directory, which then requires… +/// +/// * …a valid head +/// * …an objects directory +/// * …a refs directory /// -/// * a valid head -/// * an objects directory -/// * a refs directory pub fn git(git_dir: impl AsRef) -> Result { #[derive(Eq, PartialEq)] enum Kind { @@ -52,13 +54,15 @@ pub fn git(git_dir: impl AsRef) -> Result { let common_dir = git_dir.join("commondir"); - match crate::path::from_plain_file(common_dir) - .and_then(Result::ok) - .and_then(|cd| { - crate::path::from_plain_file(git_dir.join("gitdir")) - .and_then(Result::ok) - .map(|worktree_gitfile| (crate::path::without_dot_git_dir(worktree_gitfile), cd)) - }) { + let worktree_and_common_dir = + crate::path::from_plain_file(common_dir) + .and_then(Result::ok) + .and_then(|cd| { + crate::path::from_plain_file(git_dir.join("gitdir")) + .and_then(Result::ok) + .map(|worktree_gitfile| (crate::path::without_dot_git_dir(worktree_gitfile), cd)) + }); + match worktree_and_common_dir { Some((work_dir, common_dir)) => { let common_dir = git_dir.join(common_dir); ( diff --git a/git-discover/src/lib.rs b/git-discover/src/lib.rs index 28d48163178..30cad933f0d 100644 --- a/git-discover/src/lib.rs +++ b/git-discover/src/lib.rs @@ -4,6 +4,9 @@ #![forbid(unsafe_code, rust_2018_idioms)] #![deny(missing_docs)] +/// The name of the `.git` directory. +pub const DOT_GIT_DIR: &str = ".git"; + /// pub mod repository; diff --git a/git-discover/src/path.rs b/git-discover/src/path.rs index 6cf04ee632f..8c882185b3a 100644 --- a/git-discover/src/path.rs +++ b/git-discover/src/path.rs @@ -1,3 +1,4 @@ +use crate::DOT_GIT_DIR; use std::{io::Read, path::PathBuf}; /// @@ -60,7 +61,7 @@ pub fn from_gitdir_file(path: impl AsRef) -> Result PathBuf { - if path.file_name().and_then(|n| n.to_str()) == Some(".git") { + if path.file_name().and_then(|n| n.to_str()) == Some(DOT_GIT_DIR) { path.pop(); } path diff --git a/git-discover/src/repository.rs b/git-discover/src/repository.rs index 002275febe6..b80b930a39a 100644 --- a/git-discover/src/repository.rs +++ b/git-discover/src/repository.rs @@ -24,6 +24,7 @@ mod path { use crate::{ path::without_dot_git_dir, repository::{Kind, Path}, + DOT_GIT_DIR, }; impl AsRef for Path { @@ -84,7 +85,7 @@ mod path { pub fn into_repository_and_work_tree_directories(self) -> (PathBuf, Option) { match self { Path::LinkedWorkTree { work_dir, git_dir } => (git_dir, Some(work_dir)), - Path::WorkTree(working_tree) => (working_tree.join(".git"), Some(working_tree)), + Path::WorkTree(working_tree) => (working_tree.join(DOT_GIT_DIR), Some(working_tree)), Path::Repository(repository) => (repository, None), } } diff --git a/git-discover/src/upwards.rs b/git-discover/src/upwards.rs index 9360746768b..b62a96ddc1f 100644 --- a/git-discover/src/upwards.rs +++ b/git-discover/src/upwards.rs @@ -155,7 +155,7 @@ pub(crate) mod function { use git_sec::Trust; use super::{Error, Options}; - use crate::is_git; + use crate::{is_git, DOT_GIT_DIR}; /// Find the location of the git repository directly in `directory` or in any of its parent directories and provide /// an associated Trust level by looking at the git directory's ownership, and control discovery using `options`. @@ -241,7 +241,7 @@ pub(crate) mod function { for append_dot_git in &[false, true] { if *append_dot_git { - cursor.push(".git"); + cursor.push(DOT_GIT_DIR); } if let Ok(kind) = is_git(&cursor) { match filter_by_trust(&cursor)? { @@ -302,7 +302,7 @@ pub(crate) mod function { } if let Some(cwd) = cwd { - debug_assert_eq!(cursor.file_name().and_then(|f| f.to_str()), Some(".git")); + debug_assert_eq!(cursor.file_name().and_then(|f| f.to_str()), Some(DOT_GIT_DIR)); let parent = cursor.parent().expect(".git appended"); cwd.strip_prefix(parent) .ok() @@ -312,7 +312,7 @@ pub(crate) mod function { (relative_path_components * "..".len() < current_component_len).then(|| { std::iter::repeat("..") .take(relative_path_components) - .chain(Some(".git")) + .chain(Some(DOT_GIT_DIR)) .collect() }) }) From 7f67b23b9462b805591b1fe5a8406f8d7404f372 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 14 Jul 2022 10:55:56 +0800 Subject: [PATCH 204/366] feat: Use `git-config` to write config file on initialization, including `logallrefupdates` and `precomposeunicode`. (#331) --- .../src/assets/baseline-init/config | 3 - git-repository/src/create.rs | 65 +++++++++++++------ git-repository/src/lib.rs | 21 +++++- git-repository/src/open.rs | 2 +- git-repository/tests/init/mod.rs | 18 ++--- gitoxide-core/src/repository/mod.rs | 10 ++- 6 files changed, 83 insertions(+), 36 deletions(-) delete mode 100644 git-repository/src/assets/baseline-init/config diff --git a/git-repository/src/assets/baseline-init/config b/git-repository/src/assets/baseline-init/config deleted file mode 100644 index 739a1b0de26..00000000000 --- a/git-repository/src/assets/baseline-init/config +++ /dev/null @@ -1,3 +0,0 @@ -[core] - repositoryformatversion = 0 - bare = {bare-value} diff --git a/git-repository/src/create.rs b/git-repository/src/create.rs index 4766ea8954b..fa39b66adba 100644 --- a/git-repository/src/create.rs +++ b/git-repository/src/create.rs @@ -1,11 +1,12 @@ +use git_config::parse::section; +use git_discover::DOT_GIT_DIR; +use std::convert::TryFrom; use std::{ fs::{self, OpenOptions}, io::Write, path::{Path, PathBuf}, }; -use crate::bstr::ByteSlice; - /// The error used in [`into()`]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] @@ -22,8 +23,6 @@ pub enum Error { CreateDirectory { source: std::io::Error, path: PathBuf }, } -const GIT_DIR_NAME: &str = ".git"; - const TPL_INFO_EXCLUDE: &[u8] = include_bytes!("assets/baseline-init/info/exclude"); const TPL_HOOKS_APPLYPATCH_MSG: &[u8] = include_bytes!("assets/baseline-init/hooks/applypatch-msg.sample"); const TPL_HOOKS_COMMIT_MSG: &[u8] = include_bytes!("assets/baseline-init/hooks/commit-msg.sample"); @@ -37,7 +36,6 @@ const TPL_HOOKS_PRE_REBASE: &[u8] = include_bytes!("assets/baseline-init/hooks/p const TPL_HOOKS_PRE_RECEIVE: &[u8] = include_bytes!("assets/baseline-init/hooks/pre-receive.sample"); const TPL_HOOKS_PREPARE_COMMIT_MSG: &[u8] = include_bytes!("assets/baseline-init/hooks/prepare-commit-msg.sample"); const TPL_HOOKS_UPDATE: &[u8] = include_bytes!("assets/baseline-init/hooks/update.sample"); -const TPL_CONFIG: &[u8] = include_bytes!("assets/baseline-init/config"); const TPL_DESCRIPTION: &[u8] = include_bytes!("assets/baseline-init/description"); const TPL_HEAD: &[u8] = include_bytes!("assets/baseline-init/HEAD"); @@ -99,14 +97,22 @@ fn create_dir(p: &Path) -> Result<(), Error> { } /// Options for use in [`into()`]; +#[derive(Copy, Clone)] pub struct Options { /// If true, the repository will be a bare repository without a worktree. pub bare: bool, + + /// If set, use these filesytem capabilities to populate the respective git-config fields. + /// If `None`, the directory will be probed. + pub fs_capabilities: Option, } /// Create a new `.git` repository of `kind` within the possibly non-existing `directory` /// and return its path. -pub fn into(directory: impl Into, Options { bare }: Options) -> Result { +pub fn into( + directory: impl Into, + Options { bare, fs_capabilities }: Options, +) -> Result { let mut dot_git = directory.into(); if bare { @@ -121,7 +127,7 @@ pub fn into(directory: impl Into, Options { bare }: Options) -> Result< return Err(Error::DirectoryNotEmpty { path: dot_git }); } } else { - dot_git.push(GIT_DIR_NAME); + dot_git.push(DOT_GIT_DIR); if dot_git.is_dir() { return Err(Error::DirectoryExists { path: dot_git }); @@ -166,19 +172,29 @@ pub fn into(directory: impl Into, Options { bare }: Options) -> Result< create_dir(PathCursor(cursor.as_mut()).at("tags"))?; } - for (tpl, filename) in &[ - (TPL_HEAD, "HEAD"), - (TPL_DESCRIPTION, "description"), - (TPL_CONFIG, "config"), - ] { - if *filename == "config" { - write_file( - &tpl.replace("{bare-value}", if bare { "true" } else { "false" }), - PathCursor(&mut dot_git).at(filename), - )?; - } else { - write_file(tpl, PathCursor(&mut dot_git).at(filename))?; + for (tpl, filename) in &[(TPL_HEAD, "HEAD"), (TPL_DESCRIPTION, "description")] { + write_file(tpl, PathCursor(&mut dot_git).at(filename))?; + } + + { + let mut config = git_config::File::default(); + { + let caps = fs_capabilities.unwrap_or_else(|| git_worktree::fs::Capabilities::probe(&dot_git)); + let mut core = config.new_section("core", None).expect("valid section name"); + + core.push(key("repositoryformatversion"), "0"); + core.push(key("filemode"), bool(caps.executable_bit)); + core.push(key("bare"), bool(bare)); + core.push(key("logallrefupdates"), bool(!bare)); + core.push(key("symlinks"), bool(caps.symlink)); + core.push(key("ignorecase"), bool(caps.ignore_case)); + core.push(key("precomposeunicode"), bool(caps.precompose_unicode)); } + let config_path = dot_git.join("config"); + std::fs::write(&config_path, &config.to_bstring()).map_err(|err| Error::IoWrite { + source: err, + path: config_path, + })?; } Ok(git_discover::repository::Path::from_dot_git_dir( @@ -187,3 +203,14 @@ pub fn into(directory: impl Into, Options { bare }: Options) -> Result< .unwrap_or(git_discover::repository::Kind::WorkTree { linked_git_dir: None }), )) } + +fn key(name: &'static str) -> section::Key<'static> { + section::Key::try_from(name).expect("valid key name") +} + +fn bool(v: bool) -> &'static str { + match v { + true => "true", + false => "false", + } +} diff --git a/git-repository/src/lib.rs b/git-repository/src/lib.rs index 837bf0c1f40..ea100128695 100644 --- a/git-repository/src/lib.rs +++ b/git-repository/src/lib.rs @@ -234,12 +234,26 @@ pub fn discover(directory: impl AsRef) -> Result) -> Result { - ThreadSafeRepository::init(directory, crate::create::Options { bare: false }).map(Into::into) + ThreadSafeRepository::init( + directory, + create::Options { + bare: false, + fs_capabilities: None, + }, + ) + .map(Into::into) } /// See [ThreadSafeRepository::init()], but returns a [`Repository`] instead. pub fn init_bare(directory: impl AsRef) -> Result { - ThreadSafeRepository::init(directory, crate::create::Options { bare: true }).map(Into::into) + ThreadSafeRepository::init( + directory, + create::Options { + bare: true, + fs_capabilities: None, + }, + ) + .map(Into::into) } /// See [ThreadSafeRepository::open()], but returns a [`Repository`] instead. @@ -395,7 +409,8 @@ pub mod discover { } impl ThreadSafeRepository { - /// Try to open a git repository in `directory` and search upwards through its parents until one is found. + /// Try to open a git repository in `directory` and search upwards through its parents until one is found, + /// using default trust options which matters in case the found repository isn't owned by the current user. pub fn discover(directory: impl AsRef) -> Result { Self::discover_opts(directory, Default::default(), Default::default()) } diff --git a/git-repository/src/open.rs b/git-repository/src/open.rs index fd1dc002913..e2f34292e37 100644 --- a/git-repository/src/open.rs +++ b/git-repository/src/open.rs @@ -172,7 +172,7 @@ impl ThreadSafeRepository { match git_discover::is_git(&path) { Ok(kind) => (path, kind), Err(_err) => { - let git_dir = path.join(".git"); + let git_dir = path.join(git_discover::DOT_GIT_DIR); git_discover::is_git(&git_dir).map(|kind| (git_dir, kind))? } } diff --git a/git-repository/tests/init/mod.rs b/git-repository/tests/init/mod.rs index 6bd37497cf8..aff094a9f96 100644 --- a/git-repository/tests/init/mod.rs +++ b/git-repository/tests/init/mod.rs @@ -1,8 +1,8 @@ mod bare { #[test] - fn init_into_empty_directory_creates_a_dot_git_dir() { - let tmp = tempfile::tempdir().unwrap(); - let repo = git_repository::init_bare(tmp.path()).unwrap(); + fn init_into_empty_directory_creates_a_dot_git_dir() -> crate::Result { + let tmp = tempfile::tempdir()?; + let repo = git_repository::init_bare(tmp.path())?; assert_eq!(repo.kind(), git_repository::Kind::Bare); assert!( repo.work_dir().is_none(), @@ -13,18 +13,20 @@ mod bare { tmp.path(), "the repository is placed into the directory itself" ); - assert_eq!(git_repository::open(repo.git_dir()).unwrap(), repo); + assert_eq!(git_repository::open(repo.git_dir())?, repo); + Ok(()) } #[test] - fn init_into_non_empty_directory_is_not_allowed() { - let tmp = tempfile::tempdir().unwrap(); - std::fs::write(tmp.path().join("existing.txt"), b"I was here before you").unwrap(); + fn init_into_non_empty_directory_is_not_allowed() -> crate::Result { + let tmp = tempfile::tempdir()?; + std::fs::write(tmp.path().join("existing.txt"), b"I was here before you")?; assert!(git_repository::init_bare(tmp.path()) .unwrap_err() .to_string() - .starts_with("Refusing to initialize the non-empty directory as"),); + .starts_with("Refusing to initialize the non-empty directory as")); + Ok(()) } } diff --git a/gitoxide-core/src/repository/mod.rs b/gitoxide-core/src/repository/mod.rs index fbd0b8f6ac0..d7d620fb28c 100644 --- a/gitoxide-core/src/repository/mod.rs +++ b/gitoxide-core/src/repository/mod.rs @@ -4,8 +4,14 @@ use anyhow::{Context as AnyhowContext, Result}; use git_repository as git; pub fn init(directory: Option) -> Result { - git_repository::create::into(directory.unwrap_or_default(), git::create::Options { bare: false }) - .with_context(|| "Repository initialization failed") + git_repository::create::into( + directory.unwrap_or_default(), + git::create::Options { + bare: false, + fs_capabilities: None, + }, + ) + .with_context(|| "Repository initialization failed") } pub mod tree; From 5771721ff5f86dd808d9961126c9c4a61867507c Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 14 Jul 2022 12:04:00 +0800 Subject: [PATCH 205/366] avoid extra copies of paths using `PathCursor` tool during repo init --- git-repository/src/create.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/git-repository/src/create.rs b/git-repository/src/create.rs index fa39b66adba..8d53336d54d 100644 --- a/git-repository/src/create.rs +++ b/git-repository/src/create.rs @@ -190,10 +190,11 @@ pub fn into( core.push(key("ignorecase"), bool(caps.ignore_case)); core.push(key("precomposeunicode"), bool(caps.precompose_unicode)); } - let config_path = dot_git.join("config"); + let mut cursor = PathCursor(&mut dot_git); + let config_path = cursor.at("config"); std::fs::write(&config_path, &config.to_bstring()).map_err(|err| Error::IoWrite { source: err, - path: config_path, + path: config_path.to_owned(), })?; } From c92d5c6a223e377c10c2ca6b822e7eeb9070e12c Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 14 Jul 2022 17:16:37 +0800 Subject: [PATCH 206/366] add `Source` type to allow knowing where a particular value is from. (#331) --- git-config/src/fs.rs | 258 ---------------------------------- git-config/src/lib.rs | 5 +- git-config/src/permissions.rs | 6 +- git-config/src/types.rs | 26 ++++ 4 files changed, 31 insertions(+), 264 deletions(-) delete mode 100644 git-config/src/fs.rs diff --git a/git-config/src/fs.rs b/git-config/src/fs.rs deleted file mode 100644 index cb5f7ce5d4c..00000000000 --- a/git-config/src/fs.rs +++ /dev/null @@ -1,258 +0,0 @@ -#![allow(missing_docs)] -#![allow(unused)] -#![allow(clippy::result_unit_err)] - -use std::{ - borrow::Cow, - convert::TryFrom, - path::{Path, PathBuf}, -}; - -use bstr::BStr; - -use crate::{ - file::{from_env, from_paths}, - lookup, File, -}; - -// TODO: how does this relate to `File::from_env_paths()`? -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] -pub enum ConfigSource { - /// System-wide configuration path. This is defined as - /// `$(prefix)/etc/gitconfig`. - System, - /// Also known as the user configuration path. This is usually `~/.gitconfig`. - Global, - /// Second user-specific configuration path; if `$XDG_CONFIG_HOME` is not - /// set or empty, `$HOME/.config/git/config` will be used. Any single-valued - /// variable set in this file will be overridden by whatever is in the - /// Global configuration file. - User, - - Repository, - // Worktree(&'a Path), - /// Config<'_> values parsed from the environment. - Env, - Cli, -} - -#[derive(Debug, PartialEq, Clone, Eq, Hash, Default)] -pub struct ConfigBuilder { - no_system: bool, - load_env_conf: bool, - override_system_config: Option, - override_global_config: Option, - override_repo_config: Option, -} - -impl ConfigBuilder { - /// Constructs a new builder that finds the default location - #[must_use] - pub fn new() -> Self { - Self { - load_env_conf: true, - ..Self::default() - } - } - - /// Whether or not to skip reading settings from the system-wide - /// `$(prefix)/etc/gitconfig` file. This corresponds to setting the - /// `GIT_CONFIG_NOSYSTEM` environment variable. - #[must_use] - pub fn no_system(&mut self, no_system: bool) -> &mut Self { - self.no_system = no_system; - self - } - - /// Whether or not to respect `GIT_CONFIG_COUNT`, `GIT_CONFIG_KEY_`, and - /// `GIT_CONFIG_VALUE_` environment variables. By default, this is true. - #[must_use] - pub fn load_environment_entries(&mut self, load_conf: bool) -> &mut Self { - self.load_env_conf = load_conf; - self - } - - /// Override the system-wide configuration file location. Providing [`None`] - /// or not calling this method will use the default location. - #[must_use] - pub fn system_config_path(&mut self, path: Option) -> &mut Self { - self.override_system_config = path; - self - } - - /// Override the global (user) configuration file location. Providing - /// [`None`] or not calling this method will use the default location. - #[must_use] - pub fn global_config_path(&mut self, path: Option) -> &mut Self { - self.override_global_config = path; - self - } - - /// Sets where to read the repository-specific configuration file. This - /// is equivalent to setting `GIT_CONFIG`. If none is provided, then the - /// builder will look in the default location, `.git/config`. - #[must_use] - pub fn repository_config_path(&mut self, path: Option) -> &mut Self { - self.override_repo_config = path; - self - } - - /// Builds a config, ignoring any failed configuration files. - #[must_use] - pub fn build(&self) -> Config<'static> { - let system_conf = if self.no_system { None } else { todo!() }; - - let global_conf = { - let path = self - .override_global_config - .as_ref() - .map_or_else(|| Path::new(".git/config"), PathBuf::as_path); - - File::from_paths(Some(path), Default::default()).ok() - }; - - let env_conf = if self.load_env_conf { - // TODO: when bringing up the system, make sure options can be passed. Have to review this entire module first though. - File::from_env(from_paths::Options::default()).ok().flatten() - } else { - None - }; - - // let user_conf = if self. - - Config { - system_conf, - global_conf, - user_conf: todo!(), - repository_conf: todo!(), - worktree_conf: todo!(), - env_conf, - cli_conf: todo!(), - } - } - - /// Attempts to build a config, returning error if the environment variable - /// is invalid, if a config file is invalid, or if an overridden config file - /// does not exist. This is only recommended when you have a very controlled - /// system state. Otherwise, this will likely fail more often than you'd - /// like. - pub fn try_build(&self) -> Result, ()> { - todo!() - } -} - -pub struct Config<'a> { - system_conf: Option>, - global_conf: Option>, - user_conf: Option>, - repository_conf: Option>, - worktree_conf: Option>, - env_conf: Option>, - cli_conf: Option>, -} - -impl<'a> Config<'a> { - #[must_use] - pub fn value>>( - &'a self, - section_name: &str, - subsection_name: Option<&str>, - key: &str, - ) -> Option { - self.value_with_source(section_name, subsection_name, key) - .map(|(value, _)| value) - } - - fn value_with_source>>( - &'a self, - section_name: &str, - subsection_name: Option<&str>, - key: &str, - ) -> Option<(T, ConfigSource)> { - let mapping = self.mapping(); - - for (conf, source) in mapping.iter() { - if let Some(conf) = conf { - if let Ok(v) = conf.value(section_name, subsection_name, key) { - return Some((v, *source)); - } - } - } - - None - } - - pub fn try_value>>( - &'a self, - section_name: &str, - subsection_name: Option<&str>, - key: &str, - ) -> Result, lookup::Error> { - self.try_value_with_source(section_name, subsection_name, key) - .map(|res| res.map(|(value, _)| value)) - } - - /// Tries to retrieve the value, returning an error if the parsing fails or - /// if the key was not found. On a successful parse, the value will be - /// returned as well as the source location. This respects the priority of - /// the various configuration files. - pub fn try_value_with_source>>( - &'a self, - section_name: &str, - subsection_name: Option<&str>, - key: &str, - ) -> Result, lookup::Error> { - let mapping = self.mapping(); - - for (conf, source) in mapping.iter() { - if let Some(conf) = conf { - return Ok(Some((conf.value(section_name, subsection_name, key)?, *source))); - } - } - - Ok(None) - } - - /// Returns a mapping from [`File`] to [`ConfigSource`] - const fn mapping(&self) -> [(&Option>, ConfigSource); 6] { - [ - (&self.cli_conf, ConfigSource::Cli), - (&self.env_conf, ConfigSource::Env), - (&self.repository_conf, ConfigSource::Repository), - (&self.user_conf, ConfigSource::User), - (&self.global_conf, ConfigSource::Global), - (&self.system_conf, ConfigSource::System), - ] - } -} - -/// Lower-level interface for directly accessing a -impl<'a> Config<'a> { - /// Retrieves the underlying [`File`] object, if one was found during - /// initialization. - #[must_use] - pub fn config(&self, source: ConfigSource) -> Option<&File<'a>> { - match source { - ConfigSource::System => self.system_conf.as_ref(), - ConfigSource::Global => self.global_conf.as_ref(), - ConfigSource::User => self.user_conf.as_ref(), - ConfigSource::Repository => self.repository_conf.as_ref(), - ConfigSource::Env => self.env_conf.as_ref(), - ConfigSource::Cli => self.cli_conf.as_ref(), - } - } - - /// Retrieves the underlying [`File`] object as a mutable reference, - /// if one was found during initialization. - #[must_use] - pub fn config_mut(&mut self, source: ConfigSource) -> Option<&mut File<'a>> { - match source { - ConfigSource::System => self.system_conf.as_mut(), - ConfigSource::Global => self.global_conf.as_mut(), - ConfigSource::User => self.user_conf.as_mut(), - ConfigSource::Repository => self.repository_conf.as_mut(), - ConfigSource::Env => self.env_conf.as_mut(), - ConfigSource::Cli => self.cli_conf.as_mut(), - } - } -} diff --git a/git-config/src/lib.rs b/git-config/src/lib.rs index cd6712544c0..bc47db97a32 100644 --- a/git-config/src/lib.rs +++ b/git-config/src/lib.rs @@ -38,8 +38,7 @@ )] pub mod file; -/// -pub mod fs; + /// pub mod lookup; pub mod parse; @@ -49,7 +48,7 @@ mod values; pub use values::{boolean, color, integer, path}; mod types; -pub use types::{Boolean, Color, File, Integer, Path}; +pub use types::{Boolean, Color, File, Integer, Path, Source}; mod permissions; pub use permissions::Permissions; diff --git a/git-config/src/permissions.rs b/git-config/src/permissions.rs index 327792e5f0d..4934fc3057a 100644 --- a/git-config/src/permissions.rs +++ b/git-config/src/permissions.rs @@ -13,7 +13,7 @@ pub struct Permissions { /// set or empty, `$HOME/.config/git/config` will be used. pub user: git_sec::Permission, /// How to use the repository configuration. - pub repository: git_sec::Permission, + pub local: git_sec::Permission, /// How to use worktree configuration from `config.worktree`. // TODO: figure out how this really applies and provide more information here. pub worktree: git_sec::Permission, @@ -31,7 +31,7 @@ impl Permissions { system: Allow, global: Allow, user: Allow, - repository: Allow, + local: Allow, worktree: Allow, env: Allow, includes: Allow, @@ -46,7 +46,7 @@ impl Permissions { system: Allow, global: Allow, user: Allow, - repository: Deny, + local: Deny, worktree: Deny, env: Allow, includes: Deny, diff --git a/git-config/src/types.rs b/git-config/src/types.rs index 69dfa86068e..a49bd0ebb52 100644 --- a/git-config/src/types.rs +++ b/git-config/src/types.rs @@ -7,6 +7,32 @@ use crate::{ parse::section, }; +/// A list of known sources for configuration files, with the first one being overridden +/// by the second one, and so forth. +/// Note that included files via `include.path` and `includeIf..path` inherit +/// their source. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +pub enum Source { + /// System-wide configuration path. This is defined as + /// `$(prefix)/etc/gitconfig` (where prefix is the git-installation directory). + System, + /// Also known as the user configuration path. This is usually `~/.gitconfig`. + Global, + /// Second user-specific configuration path; if `$XDG_CONFIG_HOME` is not + /// set or empty, `$HOME/.config/git/config` will be used. + User, + /// The configuration of the repository itself, located in `.git/config`. + Local, + /// Configuration specific to a worktree as created with `git worktree` and + /// typically located in `$GIT_DIR/config.worktree` if `extensions.worktreeConfig` + /// is enabled. + Worktree, + /// values parsed from the environment. + Env, + /// Values set from the command-line. + Cli, +} + /// High level `git-config` reader and writer. /// /// This is the full-featured implementation that can deserialize, serialize, From 9cb9acb7b7ebada4d6bb3eef199337912ceeaa36 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 14 Jul 2022 21:50:47 +0800 Subject: [PATCH 207/366] sketch new section and metadata (#331) --- git-config/src/file/mod.rs | 10 ++++++++++ git-config/src/file/section.rs | 32 ++++++++++++++++++++++++++++++++ git-config/src/types.rs | 2 +- 3 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 git-config/src/file/section.rs diff --git a/git-config/src/file/mod.rs b/git-config/src/file/mod.rs index 1c78d090fd6..23408397b7f 100644 --- a/git-config/src/file/mod.rs +++ b/git-config/src/file/mod.rs @@ -6,6 +6,7 @@ use std::{ }; use bstr::BStr; +use git_features::threading::OwnShared; mod mutable; @@ -35,6 +36,15 @@ mod access; mod impls; mod utils; +/// +pub mod section; + +/// A section in a git-config file, like `[core]` or `[remote "origin"]`. +pub struct Section<'a> { + inner: crate::parse::Section<'a>, + meta: OwnShared, +} + /// A strongly typed index into some range. #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Clone, Copy)] pub(crate) struct Index(pub(crate) usize); diff --git a/git-config/src/file/section.rs b/git-config/src/file/section.rs new file mode 100644 index 00000000000..33ecf01228b --- /dev/null +++ b/git-config/src/file/section.rs @@ -0,0 +1,32 @@ +use crate::file::Section; +use crate::Source; +use std::ops::Deref; +use std::path::PathBuf; + +/// Additional information about a section. +#[derive(Clone, Debug, PartialOrd, PartialEq, Ord, Eq, Hash)] +pub struct Metadata { + /// The file path of the source, if known. + pub path: Option, + /// Where the section is coming from. + pub source: Source, + /// The levels of indirection of the file, with 0 being a section + /// that was directly loaded, and 1 being an `include.path` of a + /// level 0 file. + pub level: u8, +} + +impl<'a> Deref for Section<'a> { + type Target = crate::parse::Section<'a>; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl Section<'_> { + /// Return our meta data, additional information about this section. + pub fn meta(&self) -> &Metadata { + self.meta.as_ref() + } +} diff --git a/git-config/src/types.rs b/git-config/src/types.rs index a49bd0ebb52..251d748a4c8 100644 --- a/git-config/src/types.rs +++ b/git-config/src/types.rs @@ -11,7 +11,7 @@ use crate::{ /// by the second one, and so forth. /// Note that included files via `include.path` and `includeIf..path` inherit /// their source. -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum Source { /// System-wide configuration path. This is defined as /// `$(prefix)/etc/gitconfig` (where prefix is the git-installation directory). From 9750b7a1f01d6f0690221c6091b16c51784df0a3 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 15 Jul 2022 14:09:43 +0800 Subject: [PATCH 208/366] try to fix filter settings, but it doesn't seem to work (#331) --- git-config/tests/.gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/git-config/tests/.gitattributes b/git-config/tests/.gitattributes index 5a9d49f190e..293a183bae6 100644 --- a/git-config/tests/.gitattributes +++ b/git-config/tests/.gitattributes @@ -1,3 +1,4 @@ # assure line feeds don't interfere with our working copy hash +/fixtures/repo-config.crlf text eol=crlf /fixtures/**/* text crlf=input eol=lf From 0d07ef1aa4a9e238c20249d4ae2ed19e6740308a Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 15 Jul 2022 15:17:37 +0800 Subject: [PATCH 209/366] fix: validate incoming conifguration keys when interpreting envirnoment variables. (#331) --- git-config/src/file/init/from_env.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/git-config/src/file/init/from_env.rs b/git-config/src/file/init/from_env.rs index ab82b6389c3..db683bef8cc 100644 --- a/git-config/src/file/init/from_env.rs +++ b/git-config/src/file/init/from_env.rs @@ -1,7 +1,6 @@ +use std::convert::TryFrom; use std::{borrow::Cow, path::PathBuf}; -use bstr::BString; - use crate::{ file::{from_paths, init::resolve_includes}, parse::section, @@ -27,6 +26,8 @@ pub enum Error { FromPathsError(#[from] from_paths::Error), #[error(transparent)] Section(#[from] section::header::Error), + #[error(transparent)] + Key(#[from] section::key::Error), } /// Instantiation from environment variables @@ -112,8 +113,8 @@ impl File<'static> { }; section.push( - section::Key(BString::from(key).into()), - git_path::into_bstr(PathBuf::from(value)).as_ref(), + section::Key::try_from(key.to_owned())?, + git_path::os_str_into_bstr(&value).expect("no illformed UTF-8").as_ref(), ); } None => { From 207e483620b29efb029c6ee742c0bb48d54be020 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 15 Jul 2022 15:19:47 +0800 Subject: [PATCH 210/366] try to fix attributes, once more (#331) --- git-config/tests/.gitattributes | 2 +- git-config/tests/fixtures/repo-config.crlf | 28 +++++++++++----------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/git-config/tests/.gitattributes b/git-config/tests/.gitattributes index 293a183bae6..272372614d0 100644 --- a/git-config/tests/.gitattributes +++ b/git-config/tests/.gitattributes @@ -1,4 +1,4 @@ # assure line feeds don't interfere with our working copy hash +/fixtures/**/* text eol=lf /fixtures/repo-config.crlf text eol=crlf -/fixtures/**/* text crlf=input eol=lf diff --git a/git-config/tests/fixtures/repo-config.crlf b/git-config/tests/fixtures/repo-config.crlf index 96672b93484..a7cd0042827 100644 --- a/git-config/tests/fixtures/repo-config.crlf +++ b/git-config/tests/fixtures/repo-config.crlf @@ -1,14 +1,14 @@ -; hello -# world - -[core] - repositoryformatversion = 0 - bare = true -[other] - repositoryformatversion = 0 - - bare = true - -# before section with newline -[foo] - repositoryformatversion = 0 +; hello +# world + +[core] + repositoryformatversion = 0 + bare = true +[other] + repositoryformatversion = 0 + + bare = true + +# before section with newline +[foo] + repositoryformatversion = 0 From cabc8ef0e31c954642525e7693009a7fe4b4c465 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 15 Jul 2022 15:58:54 +0800 Subject: [PATCH 211/366] change!: rename `path::Options` into `path::Context`. (#331) It's not an option if it's required context to perform a certain operation. --- DEVELOPMENT.md | 7 ++++ git-config/src/file/init/from_paths.rs | 2 +- git-config/src/file/init/resolve_includes.rs | 4 +- git-config/src/file/mod.rs | 38 +++++++++++++++++++ git-config/src/values/path.rs | 16 +++----- git-config/tests/file/access/read_only.rs | 2 +- .../from_paths/includes/conditional/mod.rs | 2 +- git-config/tests/values/path.rs | 8 ++-- 8 files changed, 60 insertions(+), 19 deletions(-) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index e3798d63f34..e2000c8002a 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -9,6 +9,8 @@ for the mundane things, like unhappy code paths. * *use git itself* as reference implementation, and use their test-cases and fixtures where appropriate. At the very least, try to learn from them. + * Run the same test against git whenever feasible to assure git agrees with our implementation. + See `git-glob` for examples. * *use libgit2* test fixtures and cases where appropriate, or learn from them. * **safety first** * handle all errors, never `unwrap()`. If needed, `expect("why")`. @@ -106,6 +108,11 @@ A bunch of notes collected to keep track of what's needed to eventually support * Use `expect(…)` as assertion on Options, providing context on *why* the expectations should hold. Or in other words, answer "This should work _because_…" +## `Options` vs `Context` + +- Use `Options` whenever there is something to configure in terms of branching behaviour. +- Use `Context` when potential optional data is required to perform an operation at all. See `git_config::path::Context` as reference. + ## Examples, Experiments, Porcelain CLI and Plumbing CLI - which does what? ### Plumbing vs Porcelain diff --git a/git-config/src/file/init/from_paths.rs b/git-config/src/file/init/from_paths.rs index 2a3786351c6..91de42f9229 100644 --- a/git-config/src/file/init/from_paths.rs +++ b/git-config/src/file/init/from_paths.rs @@ -24,7 +24,7 @@ pub enum Error { #[derive(Clone, Copy)] pub struct Options<'a> { /// Used during path interpolation. - pub interpolate: interpolate::Options<'a>, + pub interpolate: interpolate::Context<'a>, /// The maximum allowed length of the file include chain built by following nested resolve_includes where base level is depth = 0. pub max_depth: u8, /// When max depth is exceeded while following nested included, return an error if true or silently stop following diff --git a/git-config/src/file/init/resolve_includes.rs b/git-config/src/file/init/resolve_includes.rs index 6d73690c320..5a985434fcc 100644 --- a/git-config/src/file/init/resolve_includes.rs +++ b/git-config/src/file/init/resolve_includes.rs @@ -133,8 +133,8 @@ fn include_condition_match( } } -fn onbranch_matches(condition: &BStr, options: Options<'_>) -> Option<()> { - let branch_name = options.branch_name?; +fn onbranch_matches(condition: &BStr, Options { branch_name, .. }: Options<'_>) -> Option<()> { + let branch_name = branch_name?; let (_, branch_name) = branch_name .category_and_short_name() .filter(|(cat, _)| *cat == Category::LocalBranch)?; diff --git a/git-config/src/file/mod.rs b/git-config/src/file/mod.rs index 23408397b7f..ffb7ddd57f6 100644 --- a/git-config/src/file/mod.rs +++ b/git-config/src/file/mod.rs @@ -19,6 +19,44 @@ pub use mutable::{ mod init; pub use init::{from_env, from_paths}; +/// +pub mod resolve_includes { + /// Options to handle includes, like `include.path` or `includeIf..path`, + #[derive(Clone, Copy)] + pub struct Options<'a> { + /// The maximum allowed length of the file include chain built by following nested resolve_includes where base level is depth = 0. + pub max_depth: u8, + /// When max depth is exceeded while following nested includes, + /// return an error if true or silently stop following resolve_includes. + /// + /// Setting this value to false allows to read configuration with cycles, + /// which otherwise always results in an error. + pub error_on_max_depth_exceeded: bool, + + /// Used during path interpolation, as each include path is interpolated before use. + pub interpolate: crate::path::interpolate::Context<'a>, + + /// Additional context for conditional includes to work. + pub conditional: conditional::Context<'a>, + } + + /// + pub mod conditional { + /// Options to handle conditional includes like `includeIf..path`. + #[derive(Clone, Copy)] + pub struct Context<'a> { + /// The location of the .git directory. If `None`, `gitdir` conditions cause an error. + /// + /// Used for conditional includes, e.g. `includeIf.gitdir:…` or `includeIf:gitdir/i…`. + pub git_dir: Option<&'a std::path::Path>, + /// The name of the branch that is currently checked out. If `None`, `onbranch` conditions cause an error. + /// + /// Used for conditional includes, e.g. `includeIf.onbranch:main.…` + pub branch_name: Option<&'a git_ref::FullNameRef>, + } + } +} + /// pub mod rename_section { /// The error returned by [`File::rename_section(…)`][crate::File::rename_section()]. diff --git a/git-config/src/values/path.rs b/git-config/src/values/path.rs index 00ae0410ad4..95bfdc67bfe 100644 --- a/git-config/src/values/path.rs +++ b/git-config/src/values/path.rs @@ -10,16 +10,12 @@ pub mod interpolate { /// Options for interpolating paths with [`Path::interpolate()`][crate::Path::interpolate()]. #[derive(Clone, Copy, Default)] - pub struct Options<'a> { - /// The location where gitoxide or git is installed + pub struct Context<'a> { + /// The location where gitoxide or git is installed. If `None`, `%(prefix)` in paths will cause an error. pub git_install_dir: Option<&'a std::path::Path>, - /// The home directory of the current user. - /// - /// Used during path interpolation. + /// The home directory of the current user. If `None`, `~/` in paths will cause an error. pub home_dir: Option<&'a std::path::Path>, - /// A function returning the home directory of a given user. - /// - /// Used during path interpolation. + /// A function returning the home directory of a given user. If `None`, `~name/` in paths will cause an error. pub home_for_user: Option Option>, } @@ -114,11 +110,11 @@ impl<'a> Path<'a> { /// wasn't provided. pub fn interpolate( self, - interpolate::Options { + interpolate::Context { git_install_dir, home_dir, home_for_user, - }: interpolate::Options<'_>, + }: interpolate::Context<'_>, ) -> Result, interpolate::Error> { if self.is_empty() { return Err(interpolate::Error::Missing { what: "path" }); diff --git a/git-config/tests/file/access/read_only.rs b/git-config/tests/file/access/read_only.rs index 8a7fc9531a0..86b519a42ba 100644 --- a/git-config/tests/file/access/read_only.rs +++ b/git-config/tests/file/access/read_only.rs @@ -113,7 +113,7 @@ fn get_value_for_all_provided_values() -> crate::Result { assert!(matches!(actual.value, Cow::Borrowed(_))); assert_eq!( actual - .interpolate(path::interpolate::Options { + .interpolate(path::interpolate::Context { home_dir: home.as_path().into(), ..Default::default() }) diff --git a/git-config/tests/file/init/from_paths/includes/conditional/mod.rs b/git-config/tests/file/init/from_paths/includes/conditional/mod.rs index 2538c232f52..72f664a46fc 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/mod.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/mod.rs @@ -79,7 +79,7 @@ fn include_and_includeif_correct_inclusion_order() { fn options_with_git_dir(git_dir: &Path) -> from_paths::Options<'_> { from_paths::Options { git_dir: Some(git_dir), - interpolate: path::interpolate::Options { + interpolate: path::interpolate::Context { home_dir: Some(git_dir.parent().unwrap()), ..Default::default() }, diff --git a/git-config/tests/values/path.rs b/git-config/tests/values/path.rs index c41dd464d3a..6ae146e5a39 100644 --- a/git-config/tests/values/path.rs +++ b/git-config/tests/values/path.rs @@ -37,7 +37,7 @@ mod interpolate { std::path::PathBuf::from(format!("{}{}{}", git_install_dir, std::path::MAIN_SEPARATOR, expected)); assert_eq!( git_config::Path::from(cow_str(val)) - .interpolate(path::interpolate::Options { + .interpolate(path::interpolate::Context { git_install_dir: Path::new(git_install_dir).into(), ..Default::default() }) @@ -55,7 +55,7 @@ mod interpolate { let git_install_dir = "/tmp/git"; assert_eq!( git_config::Path::from(Cow::Borrowed(b(path))) - .interpolate(path::interpolate::Options { + .interpolate(path::interpolate::Context { git_install_dir: Path::new(git_install_dir).into(), ..Default::default() }) @@ -77,7 +77,7 @@ mod interpolate { let expected = home.join("user").join("bar"); assert_eq!( git_config::Path::from(cow_str(path)) - .interpolate(path::interpolate::Options { + .interpolate(path::interpolate::Context { home_dir: Some(&home), home_for_user: Some(home_for_user), ..Default::default() @@ -115,7 +115,7 @@ mod interpolate { fn interpolate_without_context( path: impl AsRef, ) -> Result, git_config::path::interpolate::Error> { - git_config::Path::from(Cow::Owned(path.as_ref().to_owned().into())).interpolate(path::interpolate::Options { + git_config::Path::from(Cow::Owned(path.as_ref().to_owned().into())).interpolate(path::interpolate::Context { home_for_user: Some(home_for_user), ..Default::default() }) From 41b3e622ee71943c285eadc518150fc7b6c92361 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 15 Jul 2022 16:56:23 +0800 Subject: [PATCH 212/366] change!: create `resolve_includes` options to make space for more options when loading paths. (#331) --- git-config/src/file/init/from_env.rs | 2 +- git-config/src/file/init/from_paths.rs | 37 ++-------- git-config/src/file/init/resolve_includes.rs | 38 +++++----- git-config/src/file/mod.rs | 38 +++++++++- git-config/tests/file/init/from_env.rs | 25 ++++--- .../includes/conditional/gitdir/util.rs | 11 ++- .../from_paths/includes/conditional/mod.rs | 17 +++-- .../includes/conditional/onbranch.rs | 12 +++- .../init/from_paths/includes/unconditional.rs | 71 ++++++++++--------- 9 files changed, 145 insertions(+), 106 deletions(-) diff --git a/git-config/src/file/init/from_env.rs b/git-config/src/file/init/from_env.rs index db683bef8cc..09ddc09af21 100644 --- a/git-config/src/file/init/from_env.rs +++ b/git-config/src/file/init/from_env.rs @@ -82,7 +82,7 @@ impl File<'static> { /// environment variable for more information. /// /// [`git-config`'s documentation]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-GITCONFIGCOUNT - pub fn from_env(options: from_paths::Options<'_>) -> Result>, Error> { + pub fn from_env(options: crate::file::resolve_includes::Options<'_>) -> Result>, Error> { use std::env; let count: usize = match env::var("GIT_CONFIG_COUNT") { Ok(v) => v.parse().map_err(|_| Error::InvalidConfigCount { input: v })?, diff --git a/git-config/src/file/init/from_paths.rs b/git-config/src/file/init/from_paths.rs index 91de42f9229..64d173a89e5 100644 --- a/git-config/src/file/init/from_paths.rs +++ b/git-config/src/file/init/from_paths.rs @@ -1,4 +1,4 @@ -use crate::{file::init::resolve_includes, parse, path::interpolate, File}; +use crate::{file, file::init::resolve_includes, parse, path::interpolate, File}; /// The error returned by [`File::from_paths()`][crate::File::from_paths()] and [`File::from_env_paths()`][crate::File::from_env_paths()]. #[derive(Debug, thiserror::Error)] @@ -21,37 +21,10 @@ pub enum Error { } /// Options when loading git config using [`File::from_paths()`][crate::File::from_paths()]. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Default)] pub struct Options<'a> { - /// Used during path interpolation. - pub interpolate: interpolate::Context<'a>, - /// The maximum allowed length of the file include chain built by following nested resolve_includes where base level is depth = 0. - pub max_depth: u8, - /// When max depth is exceeded while following nested included, return an error if true or silently stop following - /// resolve_includes. - /// - /// Setting this value to false allows to read configuration with cycles, which otherwise always results in an error. - pub error_on_max_depth_exceeded: bool, - /// The location of the .git directory - /// - /// Used for conditional includes, e.g. `gitdir:` or `gitdir/i`. - pub git_dir: Option<&'a std::path::Path>, - /// The name of the branch that is currently checked out - /// - /// Used for conditional includes, e.g. `onbranch:` - pub branch_name: Option<&'a git_ref::FullNameRef>, -} - -impl Default for Options<'_> { - fn default() -> Self { - Options { - interpolate: Default::default(), - max_depth: 10, - error_on_max_depth_exceeded: true, - git_dir: None, - branch_name: None, - } - } + /// Configure how to follow includes while handling paths. + pub resolve_includes: file::resolve_includes::Options<'a>, } /// Instantiation from one or more paths @@ -74,7 +47,7 @@ impl File<'static> { for path in paths { let path = path.as_ref(); let mut config = Self::from_path_with_buf(path, &mut buf)?; - resolve_includes(&mut config, Some(path), &mut buf, options)?; + resolve_includes(&mut config, Some(path), &mut buf, options.resolve_includes)?; target.append(config); } Ok(target) diff --git a/git-config/src/file/init/resolve_includes.rs b/git-config/src/file/init/resolve_includes.rs index 5a985434fcc..40eb2d4563e 100644 --- a/git-config/src/file/init/resolve_includes.rs +++ b/git-config/src/file/init/resolve_includes.rs @@ -6,11 +6,9 @@ use std::{ use bstr::{BStr, BString, ByteSlice, ByteVec}; use git_ref::Category; +use crate::file::resolve_includes::{conditional, Options}; use crate::{ - file::{ - init::{from_paths, from_paths::Options}, - SectionBodyId, - }, + file::{init::from_paths, SectionBodyId}, File, }; @@ -18,7 +16,7 @@ pub(crate) fn resolve_includes( conf: &mut File<'static>, config_path: Option<&std::path::Path>, buf: &mut Vec, - options: from_paths::Options<'_>, + options: Options<'_>, ) -> Result<(), from_paths::Error> { resolve_includes_recursive(conf, config_path, 0, buf, options) } @@ -28,7 +26,7 @@ fn resolve_includes_recursive( target_config_path: Option<&Path>, depth: u8, buf: &mut Vec, - options: from_paths::Options<'_>, + options: Options<'_>, ) -> Result<(), from_paths::Error> { if depth == options.max_depth { return if options.error_on_max_depth_exceeded { @@ -107,7 +105,7 @@ fn extract_include_path( fn include_condition_match( condition: &BStr, target_config_path: Option<&Path>, - options: from_paths::Options<'_>, + options: Options<'_>, ) -> Result { let mut tokens = condition.splitn(2, |b| *b == b':'); let (prefix, condition) = match (tokens.next(), tokens.next()) { @@ -128,12 +126,15 @@ fn include_condition_match( options, git_glob::wildmatch::Mode::IGNORE_CASE, ), - b"onbranch" => Ok(onbranch_matches(condition, options).is_some()), + b"onbranch" => Ok(onbranch_matches(condition, options.conditional).is_some()), _ => Ok(false), } } -fn onbranch_matches(condition: &BStr, Options { branch_name, .. }: Options<'_>) -> Option<()> { +fn onbranch_matches( + condition: &BStr, + conditional::Context { branch_name, .. }: conditional::Context<'_>, +) -> Option<()> { let branch_name = branch_name?; let (_, branch_name) = branch_name .category_and_short_name() @@ -158,18 +159,18 @@ fn onbranch_matches(condition: &BStr, Options { branch_name, .. }: Options<'_>) fn gitdir_matches( condition_path: &BStr, target_config_path: Option<&Path>, - from_paths::Options { - git_dir, - interpolate: interpolate_options, + Options { + conditional: conditional::Context { git_dir, .. }, + interpolate: context, .. - }: from_paths::Options<'_>, + }: Options<'_>, wildmatch_mode: git_glob::wildmatch::Mode, ) -> Result { let git_dir = git_path::to_unix_separators_on_windows(git_path::into_bstr(git_dir.ok_or(from_paths::Error::MissingGitDir)?)); let mut pattern_path: Cow<'_, _> = { - let path = crate::Path::from(Cow::Borrowed(condition_path)).interpolate(interpolate_options)?; + let path = crate::Path::from(Cow::Borrowed(condition_path)).interpolate(context)?; git_path::into_bstr(path).into_owned().into() }; // NOTE: yes, only if we do path interpolation will the slashes be forced to unix separators on windows @@ -219,12 +220,11 @@ fn gitdir_matches( fn resolve( path: crate::Path<'_>, target_config_path: Option<&Path>, - from_paths::Options { - interpolate: interpolate_options, - .. - }: from_paths::Options<'_>, + Options { + interpolate: context, .. + }: Options<'_>, ) -> Result { - let path = path.interpolate(interpolate_options)?; + let path = path.interpolate(context)?; let path: PathBuf = if path.is_relative() { target_config_path .ok_or(from_paths::Error::MissingConfigPath)? diff --git a/git-config/src/file/mod.rs b/git-config/src/file/mod.rs index ffb7ddd57f6..069789cc347 100644 --- a/git-config/src/file/mod.rs +++ b/git-config/src/file/mod.rs @@ -33,17 +33,51 @@ pub mod resolve_includes { /// which otherwise always results in an error. pub error_on_max_depth_exceeded: bool, - /// Used during path interpolation, as each include path is interpolated before use. + /// Used during path interpolation, both for include paths before trying to read the file, and for + /// paths used in conditional `gitdir` includes. pub interpolate: crate::path::interpolate::Context<'a>, /// Additional context for conditional includes to work. pub conditional: conditional::Context<'a>, } + impl Options<'_> { + /// Provide options to never follow include directives at all. + pub fn no_follow() -> Self { + Options { + max_depth: 0, + error_on_max_depth_exceeded: false, + interpolate: Default::default(), + conditional: Default::default(), + } + } + } + + impl<'a> Options<'a> { + /// Provide options to follow includes like git does, provided the required `conditional` and `interpolate` contexts. + pub fn follow( + interpolate: crate::path::interpolate::Context<'a>, + conditional: conditional::Context<'a>, + ) -> Self { + Options { + max_depth: 10, + error_on_max_depth_exceeded: true, + interpolate, + conditional, + } + } + } + + impl Default for Options<'_> { + fn default() -> Self { + Self::no_follow() + } + } + /// pub mod conditional { /// Options to handle conditional includes like `includeIf..path`. - #[derive(Clone, Copy)] + #[derive(Clone, Copy, Default)] pub struct Context<'a> { /// The location of the .git directory. If `None`, `gitdir` conditions cause an error. /// diff --git a/git-config/tests/file/init/from_env.rs b/git-config/tests/file/init/from_env.rs index 3a945f6fd0a..ec837e828a2 100644 --- a/git-config/tests/file/init/from_env.rs +++ b/git-config/tests/file/init/from_env.rs @@ -1,7 +1,8 @@ use std::{borrow::Cow, env, fs}; +use git_config::file::resolve_includes; use git_config::{ - file::{from_env, from_paths, from_paths::Options}, + file::{from_env, from_paths}, File, }; use serial_test::serial; @@ -38,7 +39,7 @@ impl<'a> Drop for Env<'a> { #[test] #[serial] fn empty_without_relevant_environment() { - let config = File::from_env(Options::default()).unwrap(); + let config = File::from_env(Default::default()).unwrap(); assert!(config.is_none()); } @@ -46,7 +47,7 @@ fn empty_without_relevant_environment() { #[serial] fn empty_with_zero_count() { let _env = Env::new().set("GIT_CONFIG_COUNT", "0"); - let config = File::from_env(Options::default()).unwrap(); + let config = File::from_env(Default::default()).unwrap(); assert!(config.is_none()); } @@ -54,7 +55,7 @@ fn empty_with_zero_count() { #[serial] fn parse_error_with_invalid_count() { let _env = Env::new().set("GIT_CONFIG_COUNT", "invalid"); - let err = File::from_env(Options::default()).unwrap_err(); + let err = File::from_env(Default::default()).unwrap_err(); assert!(matches!(err, from_env::Error::InvalidConfigCount { .. })); } @@ -66,7 +67,7 @@ fn single_key_value_pair() { .set("GIT_CONFIG_KEY_0", "core.key") .set("GIT_CONFIG_VALUE_0", "value"); - let config = File::from_env(Options::default()).unwrap().unwrap(); + let config = File::from_env(Default::default()).unwrap().unwrap(); assert_eq!( config.raw_value("core", None, "key").unwrap(), Cow::<[u8]>::Borrowed(b"value") @@ -87,7 +88,7 @@ fn multiple_key_value_pairs() { .set("GIT_CONFIG_KEY_2", "core.c") .set("GIT_CONFIG_VALUE_2", "c"); - let config = File::from_env(Options::default()).unwrap().unwrap(); + let config = File::from_env(Default::default()).unwrap().unwrap(); assert_eq!( config.raw_value("core", None, "a").unwrap(), @@ -112,7 +113,10 @@ fn error_on_relative_paths_in_include_paths() { .set("GIT_CONFIG_KEY_0", "include.path") .set("GIT_CONFIG_VALUE_0", "some_git_config"); - let res = File::from_env(Options::default()); + let res = File::from_env(resolve_includes::Options { + max_depth: 1, + ..Default::default() + }); assert!(matches!( res, Err(from_env::Error::FromPathsError(from_paths::Error::MissingConfigPath)) @@ -139,7 +143,12 @@ fn follow_include_paths() { .set("GIT_CONFIG_KEY_3", "include.origin.path") .set("GIT_CONFIG_VALUE_3", escape_backslashes(b_path)); - let config = File::from_env(Options::default()).unwrap().unwrap(); + let config = File::from_env(resolve_includes::Options { + max_depth: 1, + ..Default::default() + }) + .unwrap() + .unwrap(); assert_eq!( config.raw_value("core", None, "key").unwrap(), diff --git a/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs b/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs index 763d1625639..a80a1a44d36 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs @@ -7,6 +7,7 @@ use std::{ }; use bstr::{BString, ByteSlice}; +use git_config::file::{from_paths, resolve_includes}; use crate::file::{ cow_str, @@ -83,9 +84,13 @@ impl GitEnv { } impl GitEnv { - pub fn include_options(&self) -> git_config::file::from_paths::Options { + pub fn include_options(&self) -> resolve_includes::Options<'_> { + self.from_paths_options().resolve_includes + } + + pub fn from_paths_options(&self) -> from_paths::Options<'_> { let mut opts = options_with_git_dir(self.git_dir()); - opts.interpolate.home_dir = Some(self.home_dir()); + opts.resolve_includes.interpolate.home_dir = Some(self.home_dir()); opts } @@ -123,7 +128,7 @@ pub fn assert_section_value( paths.push(env.home_dir().join(".gitconfig")); } - let config = git_config::File::from_paths(paths, env.include_options())?; + let config = git_config::File::from_paths(paths, env.from_paths_options())?; assert_eq!( config.string("section", None, "value"), diff --git a/git-config/tests/file/init/from_paths/includes/conditional/mod.rs b/git-config/tests/file/init/from_paths/includes/conditional/mod.rs index 72f664a46fc..9fe43cde8f2 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/mod.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/mod.rs @@ -1,5 +1,6 @@ use std::{fs, path::Path}; +use git_config::file::resolve_includes; use git_config::{file::from_paths, path, File}; use tempfile::tempdir; @@ -78,12 +79,16 @@ fn include_and_includeif_correct_inclusion_order() { fn options_with_git_dir(git_dir: &Path) -> from_paths::Options<'_> { from_paths::Options { - git_dir: Some(git_dir), - interpolate: path::interpolate::Context { - home_dir: Some(git_dir.parent().unwrap()), - ..Default::default() - }, - ..Default::default() + resolve_includes: resolve_includes::Options::follow( + path::interpolate::Context { + home_dir: Some(git_dir.parent().unwrap()), + ..Default::default() + }, + resolve_includes::conditional::Context { + git_dir: Some(git_dir), + ..Default::default() + }, + ), } } diff --git a/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs b/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs index 819cb33066a..63bad430cfc 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs @@ -4,7 +4,8 @@ use std::{ }; use bstr::{BString, ByteSlice}; -use git_config::file::from_paths; +use git_config::file::resolve_includes::conditional; +use git_config::file::{from_paths, resolve_includes}; use git_ref::{ transaction::{Change, PreviousValue, RefEdit}, FullName, Target, @@ -234,8 +235,13 @@ value = branch-override-by-include let branch_name = FullName::try_from(BString::from(branch_name))?; let options = from_paths::Options { - branch_name: Some(branch_name.as_ref()), - ..Default::default() + resolve_includes: resolve_includes::Options::follow( + Default::default(), + conditional::Context { + branch_name: Some(branch_name.as_ref()), + ..Default::default() + }, + ), }; let config = git_config::File::from_paths(Some(&root_config), options)?; diff --git a/git-config/tests/file/init/from_paths/includes/unconditional.rs b/git-config/tests/file/init/from_paths/includes/unconditional.rs index f0c3433471d..5ac8d1ca405 100644 --- a/git-config/tests/file/init/from_paths/includes/unconditional.rs +++ b/git-config/tests/file/init/from_paths/includes/unconditional.rs @@ -1,10 +1,17 @@ use std::fs; +use git_config::file::resolve_includes; use git_config::{file::from_paths, File}; use tempfile::tempdir; use crate::file::{cow_str, init::from_paths::escape_backslashes}; +fn follow_options() -> from_paths::Options<'static> { + from_paths::Options { + resolve_includes: resolve_includes::Options::follow(Default::default(), Default::default()), + } +} + #[test] fn multiple() -> crate::Result { let dir = tempdir()?; @@ -61,7 +68,7 @@ fn multiple() -> crate::Result { ), )?; - let config = File::from_paths(vec![c_path], Default::default())?; + let config = File::from_paths(vec![c_path], follow_options())?; assert_eq!(config.string("core", None, "c"), Some(cow_str("12"))); assert_eq!(config.integer("core", None, "d"), Some(Ok(41))); @@ -106,37 +113,39 @@ fn respect_max_depth() -> crate::Result { .replace("{}", &max_depth.to_string()), )?; - let config = File::from_paths(vec![dir.path().join("0")], Default::default())?; + let config = File::from_paths(vec![dir.path().join("0")], follow_options())?; assert_eq!(config.integers("core", None, "i"), Some(Ok(vec![0, 1, 2, 3, 4]))); + fn make_options(max_depth: u8, error_on_max_depth_exceeded: bool) -> from_paths::Options<'static> { + from_paths::Options { + resolve_includes: resolve_includes::Options { + max_depth, + error_on_max_depth_exceeded, + ..Default::default() + }, + } + } + // with max_allowed_depth of 1 and 4 levels of includes and error_on_max_depth_exceeded: false, max_allowed_depth is exceeded and the value of level 1 is returned // this is equivalent to running git with --no-includes option - let options = from_paths::Options { - max_depth: 1, - error_on_max_depth_exceeded: false, - ..Default::default() - }; + let options = make_options(1, false); let config = File::from_paths(vec![dir.path().join("0")], options)?; assert_eq!(config.integer("core", None, "i"), Some(Ok(1))); // with default max_allowed_depth of 10 and 4 levels of includes, last level is read - let options = from_paths::Options::default(); + let options = from_paths::Options { + resolve_includes: resolve_includes::Options::follow(Default::default(), Default::default()), + }; let config = File::from_paths(vec![dir.path().join("0")], options)?; assert_eq!(config.integer("core", None, "i"), Some(Ok(4))); // with max_allowed_depth of 5, the base and 4 levels of includes, last level is read - let options = from_paths::Options { - max_depth: 5, - ..Default::default() - }; + let options = make_options(5, false); let config = File::from_paths(vec![dir.path().join("0")], options)?; assert_eq!(config.integer("core", None, "i"), Some(Ok(4))); // with max_allowed_depth of 2 and 4 levels of includes, max_allowed_depth is exceeded and error is returned - let options = from_paths::Options { - max_depth: 2, - ..Default::default() - }; + let options = make_options(2, true); let config = File::from_paths(vec![dir.path().join("0")], options); assert!(matches!( config.unwrap_err(), @@ -144,19 +153,12 @@ fn respect_max_depth() -> crate::Result { )); // with max_allowed_depth of 2 and 4 levels of includes and error_on_max_depth_exceeded: false , max_allowed_depth is exceeded and the value of level 2 is returned - let options = from_paths::Options { - max_depth: 2, - error_on_max_depth_exceeded: false, - ..Default::default() - }; + let options = make_options(2, false); let config = File::from_paths(vec![dir.path().join("0")], options)?; assert_eq!(config.integer("core", None, "i"), Some(Ok(2))); // with max_allowed_depth of 0 and 4 levels of includes, max_allowed_depth is exceeded and error is returned - let options = from_paths::Options { - max_depth: 0, - ..Default::default() - }; + let options = make_options(0, true); let config = File::from_paths(vec![dir.path().join("0")], options); assert!(matches!( config.unwrap_err(), @@ -198,7 +200,7 @@ fn simple() { ) .unwrap(); - let config = File::from_paths(vec![a_path], Default::default()).unwrap(); + let config = File::from_paths(vec![a_path], follow_options()).unwrap(); assert_eq!(config.boolean("core", None, "b"), Some(Ok(false))); } @@ -234,8 +236,11 @@ fn cycle_detection() -> crate::Result { )?; let options = from_paths::Options { - max_depth: 4, - ..Default::default() + resolve_includes: resolve_includes::Options { + max_depth: 4, + error_on_max_depth_exceeded: true, + ..Default::default() + }, }; let config = File::from_paths(vec![a_path.clone()], options); assert!(matches!( @@ -244,9 +249,11 @@ fn cycle_detection() -> crate::Result { )); let options = from_paths::Options { - max_depth: 4, - error_on_max_depth_exceeded: false, - ..Default::default() + resolve_includes: resolve_includes::Options { + max_depth: 4, + error_on_max_depth_exceeded: false, + ..Default::default() + }, }; let config = File::from_paths(vec![a_path], options)?; assert_eq!(config.integers("core", None, "b"), Some(Ok(vec![0, 1, 0, 1, 0]))); @@ -292,7 +299,7 @@ fn nested() -> crate::Result { ), )?; - let config = File::from_paths(vec![c_path], Default::default())?; + let config = File::from_paths(vec![c_path], follow_options())?; assert_eq!(config.integer("core", None, "c"), Some(Ok(1))); assert_eq!(config.boolean("core", None, "b"), Some(Ok(true))); From 81e63cc3590301ca32c1172b358ffb45a13b6a8f Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 15 Jul 2022 16:57:57 +0800 Subject: [PATCH 213/366] adjust to changes in `git-config` (#331) --- git-repository/src/config.rs | 2 +- git-repository/src/repository/snapshots.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/git-repository/src/config.rs b/git-repository/src/config.rs index 9776e3629dc..c87e55356ef 100644 --- a/git-repository/src/config.rs +++ b/git-repository/src/config.rs @@ -76,7 +76,7 @@ mod cache { let excludes_file = config .path("core", None, "excludesFile") .map(|p| { - p.interpolate(path::interpolate::Options { + p.interpolate(path::interpolate::Context { git_install_dir, home_dir: home.as_deref(), home_for_user: Some(git_config::path::interpolate::home_for_user), diff --git a/git-repository/src/repository/snapshots.rs b/git-repository/src/repository/snapshots.rs index ecd4e219c2a..a675b3e3556 100644 --- a/git-repository/src/repository/snapshots.rs +++ b/git-repository/src/repository/snapshots.rs @@ -98,7 +98,7 @@ impl crate::Repository { .and_then(|path| { let install_dir = self.install_dir().ok()?; let home = self.config.home_dir(); - match path.interpolate(git_config::path::interpolate::Options { + match path.interpolate(git_config::path::interpolate::Context { git_install_dir: Some(install_dir.as_path()), home_dir: home.as_deref(), home_for_user: if self.linked_worktree_options.permissions.git_dir.is_all() { From e84263362fe0631935379a0b4e8d8b1fcf6ac81b Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 15 Jul 2022 16:58:04 +0800 Subject: [PATCH 214/366] thanks clippy --- .../init/from_paths/includes/conditional/gitdir/util.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs b/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs index a80a1a44d36..6bcdeac033c 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs @@ -85,10 +85,10 @@ impl GitEnv { impl GitEnv { pub fn include_options(&self) -> resolve_includes::Options<'_> { - self.from_paths_options().resolve_includes + self.to_from_paths_options().resolve_includes } - pub fn from_paths_options(&self) -> from_paths::Options<'_> { + pub fn to_from_paths_options(&self) -> from_paths::Options<'_> { let mut opts = options_with_git_dir(self.git_dir()); opts.resolve_includes.interpolate.home_dir = Some(self.home_dir()); opts @@ -128,7 +128,7 @@ pub fn assert_section_value( paths.push(env.home_dir().join(".gitconfig")); } - let config = git_config::File::from_paths(paths, env.from_paths_options())?; + let config = git_config::File::from_paths(paths, env.to_from_paths_options())?; assert_eq!( config.string("section", None, "value"), From 9aa5acdec12a0721543c6bcc39ffe6bd734f9a69 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 15 Jul 2022 20:17:42 +0800 Subject: [PATCH 215/366] A way to more easily set interpolation even without following includes. (#331) --- git-config/src/file/mod.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/git-config/src/file/mod.rs b/git-config/src/file/mod.rs index 069789cc347..89794f3fc92 100644 --- a/git-config/src/file/mod.rs +++ b/git-config/src/file/mod.rs @@ -54,7 +54,10 @@ pub mod resolve_includes { } impl<'a> Options<'a> { - /// Provide options to follow includes like git does, provided the required `conditional` and `interpolate` contexts. + /// Provide options to follow includes like git does, provided the required `conditional` and `interpolate` contexts + /// to support `gitdir` and `onbranch` based `includeIf` directives as well as standard `include.path` resolution. + /// Note that the follow-mode is `git`-style, following at most 10 indirections while + /// producing an error if the depth is exceeded. pub fn follow( interpolate: crate::path::interpolate::Context<'a>, conditional: conditional::Context<'a>, @@ -66,6 +69,13 @@ pub mod resolve_includes { conditional, } } + + /// Set the context used for interpolation when interpolating paths to include as well as the paths + /// in `gitdir` conditional includes. + pub fn interpolate_with(mut self, context: crate::path::interpolate::Context<'a>) -> Self { + self.interpolate = context.into(); + self + } } impl Default for Options<'_> { From 3bea26d7d2a9b5751c6c15e1fa9a924b67e0159e Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 15 Jul 2022 21:49:25 +0800 Subject: [PATCH 216/366] change!: Remove `File::sections_by_name_with_header()` as `::sections_by_name()` now returns entire sections. (#331) --- git-config/src/file/access/mutate.rs | 6 +- git-config/src/file/access/read_only.rs | 75 +------ git-config/src/file/access/write.rs | 6 +- git-config/src/file/init/resolve_includes.rs | 11 +- git-config/src/file/mod.rs | 35 ++- git-config/src/file/mutable/multi_value.rs | 28 +-- git-config/src/file/mutable/section.rs | 219 ++----------------- git-config/src/file/section.rs | 32 --- git-config/src/file/section/body.rs | 189 ++++++++++++++++ git-config/src/file/section/mod.rs | 61 ++++++ git-config/src/file/tests.rs | 80 +++---- git-config/src/file/utils.rs | 29 +-- git-config/src/types.rs | 10 +- git-config/tests/file/mod.rs | 2 +- git-config/tests/file/mutable/section.rs | 4 +- 15 files changed, 383 insertions(+), 404 deletions(-) delete mode 100644 git-config/src/file/section.rs create mode 100644 git-config/src/file/section/body.rs create mode 100644 git-config/src/file/section/mod.rs diff --git a/git-config/src/file/access/mutate.rs b/git-config/src/file/access/mutate.rs index 509e6ca75f3..2a38b42dec3 100644 --- a/git-config/src/file/access/mutate.rs +++ b/git-config/src/file/access/mutate.rs @@ -121,7 +121,7 @@ impl<'event> File<'event> { .position(|v| *v == id) .expect("known section id"), ); - self.sections.remove(&id) + self.sections.remove(&id).map(|s| s.body) } /// Adds the provided section to the config, returning a mutable reference @@ -148,8 +148,8 @@ impl<'event> File<'event> { .rev() .next() .expect("list of sections were empty, which violates invariant"); - let header = self.section_headers.get_mut(&id).expect("known section-id"); - *header = section::Header::new(new_section_name, new_subsection_name)?; + let section = self.sections.get_mut(&id).expect("known section-id"); + section.header = section::Header::new(new_section_name, new_subsection_name)?; Ok(()) } } diff --git a/git-config/src/file/access/read_only.rs b/git-config/src/file/access/read_only.rs index 49b1fc059c4..38dd7f7d2b5 100644 --- a/git-config/src/file/access/read_only.rs +++ b/git-config/src/file/access/read_only.rs @@ -2,7 +2,7 @@ use std::{borrow::Cow, convert::TryFrom}; use bstr::BStr; -use crate::{file::SectionBody, lookup, parse::section, File}; +use crate::{file, file::SectionBody, lookup, File}; /// Read-only low-level access methods, as it requires generics for converting into /// custom values defined in this crate like [`Integer`][crate::Integer] and @@ -172,7 +172,7 @@ impl<'event> File<'event> { pub fn sections_by_name<'a>( &'a self, section_name: &'a str, - ) -> Option> + '_> { + ) -> Option> + '_> { self.section_ids_by_name(section_name).ok().map(move |ids| { ids.map(move |id| { self.sections @@ -182,70 +182,6 @@ impl<'event> File<'event> { }) } - /// Get all sections that match the `section_name`, returning all matching section header along with their body. - /// - /// `None` is returned if there is no section with `section_name`. - /// - /// # Example - /// - /// Provided the following config: - /// ```plain - /// [url "ssh://git@github.com/"] - /// insteadOf = https://github.com/ - /// [url "ssh://git@bitbucket.org"] - /// insteadOf = https://bitbucket.org/ - /// ``` - /// Calling this method will yield all section bodies and their header: - /// - /// ```rust - /// use git_config::File; - /// use git_config::parse::section; - /// use std::borrow::Cow; - /// use std::convert::TryFrom; - /// use nom::AsBytes; - /// - /// let input = r#" - /// [url "ssh://git@github.com/"] - /// insteadOf = https://github.com/ - /// [url "ssh://git@bitbucket.org"] - /// insteadOf = https://bitbucket.org/ - /// "#; - /// let config = git_config::File::try_from(input)?; - /// let url = config.sections_by_name_with_header("url"); - /// assert_eq!(url.map_or(0, |s| s.count()), 2); - /// - /// for (i, (header, body)) in config.sections_by_name_with_header("url").unwrap().enumerate() { - /// let url = header.subsection_name().unwrap(); - /// let instead_of = body.value("insteadOf").unwrap(); - /// - /// if i == 0 { - /// assert_eq!(instead_of.as_ref(), "https://github.com/"); - /// assert_eq!(url, "ssh://git@github.com/"); - /// } else { - /// assert_eq!(instead_of.as_ref(), "https://bitbucket.org/"); - /// assert_eq!(url, "ssh://git@bitbucket.org"); - /// } - /// } - /// # Ok::<(), Box>(()) - /// ``` - pub fn sections_by_name_with_header<'a>( - &'a self, - section_name: &'a str, - ) -> Option, &SectionBody<'event>)> + '_> { - self.section_ids_by_name(section_name).ok().map(move |ids| { - ids.map(move |id| { - ( - self.section_headers - .get(&id) - .expect("section doesn't have a section header??"), - self.sections - .get(&id) - .expect("section doesn't have id from from lookup"), - ) - }) - }) - } - /// Returns the number of values in the config, no matter in which section. /// /// For example, a config with multiple empty sections will return 0. @@ -256,9 +192,10 @@ impl<'event> File<'event> { } /// Returns if there are no entries in the config. This will return true - /// if there are only empty sections or comments. + /// if there are only empty sections, with whitespace and comments not being considered + /// 'empty'. #[must_use] - pub fn is_empty(&self) -> bool { - self.sections.values().all(SectionBody::is_empty) + pub fn is_void(&self) -> bool { + self.sections.values().all(|s| s.body.is_void()) } } diff --git a/git-config/src/file/access/write.rs b/git-config/src/file/access/write.rs index c986f220bd5..61131d9d4bf 100644 --- a/git-config/src/file/access/write.rs +++ b/git-config/src/file/access/write.rs @@ -21,14 +21,10 @@ impl File<'_> { } for section_id in &self.section_order { - self.section_headers + self.sections .get(section_id) .expect("known section-id") .write_to(&mut out)?; - - for event in self.sections.get(section_id).expect("known section-id").as_ref() { - event.write_to(&mut out)?; - } } Ok(()) diff --git a/git-config/src/file/init/resolve_includes.rs b/git-config/src/file/init/resolve_includes.rs index 40eb2d4563e..b0a4c063d00 100644 --- a/git-config/src/file/init/resolve_includes.rs +++ b/git-config/src/file/init/resolve_includes.rs @@ -8,7 +8,7 @@ use git_ref::Category; use crate::file::resolve_includes::{conditional, Options}; use crate::{ - file::{init::from_paths, SectionBodyId}, + file::{init::from_paths, SectionId}, File, }; @@ -58,8 +58,9 @@ fn resolve_includes_recursive( incl_section_ids.sort_by(|a, b| a.1.cmp(&b.1)); let mut include_paths = Vec::new(); + // TODO: could this just use the section order and compare the name itself? for (id, _) in incl_section_ids { - if let Some(header) = target_config.section_headers.get(&id) { + if let Some(header) = target_config.sections.get(&id).map(|s| &s.header) { if header.name.0.as_ref() == "include" && header.subsection_name.is_none() { extract_include_path(target_config, &mut include_paths, id) } else if header.name.0.as_ref() == "includeIf" { @@ -88,11 +89,7 @@ fn resolve_includes_recursive( Ok(()) } -fn extract_include_path( - target_config: &mut File<'_>, - include_paths: &mut Vec>, - id: SectionBodyId, -) { +fn extract_include_path(target_config: &mut File<'_>, include_paths: &mut Vec>, id: SectionId) { if let Some(body) = target_config.sections.get(&id) { let paths = body.values("path"); let paths = paths diff --git a/git-config/src/file/mod.rs b/git-config/src/file/mod.rs index 89794f3fc92..f3ea03dfa49 100644 --- a/git-config/src/file/mod.rs +++ b/git-config/src/file/mod.rs @@ -6,19 +6,22 @@ use std::{ }; use bstr::BStr; -use git_features::threading::OwnShared; mod mutable; -pub use mutable::{ - multi_value::MultiValueMut, - section::{SectionBody, SectionBodyIter, SectionMut}, - value::ValueMut, -}; +pub use mutable::{multi_value::MultiValueMut, section::SectionMut, value::ValueMut}; mod init; pub use init::{from_env, from_paths}; +mod access; +mod impls; +mod utils; + +/// +pub mod section; +pub use section::body::{SectionBody, SectionBodyIter}; + /// pub mod resolve_includes { /// Options to handle includes, like `include.path` or `includeIf..path`, @@ -114,17 +117,11 @@ pub mod rename_section { } } -mod access; -mod impls; -mod utils; - -/// -pub mod section; - -/// A section in a git-config file, like `[core]` or `[remote "origin"]`. +/// A section in a git-config file, like `[core]` or `[remote "origin"]`, along with all of its keys. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] pub struct Section<'a> { - inner: crate::parse::Section<'a>, - meta: OwnShared, + header: crate::parse::section::Header<'a>, + body: SectionBody<'a>, } /// A strongly typed index into some range. @@ -160,7 +157,7 @@ impl AddAssign for Size { /// words, it's possible that a section may have an ID of 3 but the next section /// has an ID of 5 as 4 was deleted. #[derive(PartialEq, Eq, Hash, Copy, Clone, PartialOrd, Ord, Debug)] -pub(crate) struct SectionBodyId(pub(crate) usize); +pub(crate) struct SectionId(pub(crate) usize); /// All section body ids referred to by a section name. /// @@ -170,9 +167,9 @@ pub(crate) struct SectionBodyId(pub(crate) usize); #[derive(PartialEq, Eq, Clone, Debug)] pub(crate) enum SectionBodyIds<'a> { /// The list of section ids to use for obtaining the section body. - Terminal(Vec), + Terminal(Vec), /// A hashmap from sub-section names to section ids. - NonTerminal(HashMap, Vec>), + NonTerminal(HashMap, Vec>), } #[cfg(test)] mod tests; diff --git a/git-config/src/file/mutable/multi_value.rs b/git-config/src/file/mutable/multi_value.rs index 096c55ee439..420a46f2568 100644 --- a/git-config/src/file/mutable/multi_value.rs +++ b/git-config/src/file/mutable/multi_value.rs @@ -1,5 +1,5 @@ use crate::file::mutable::{escape_value, Whitespace}; -use crate::file::{SectionBody, SectionBodyId}; +use crate::file::{Section, SectionBody, SectionId}; use crate::lookup; use crate::parse::{section, Event}; use crate::value::{normalize_bstr, normalize_bstring}; @@ -11,14 +11,14 @@ use std::ops::DerefMut; /// Internal data structure for [`MutableMultiValue`] #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] pub(crate) struct EntryData { - pub(crate) section_id: SectionBodyId, + pub(crate) section_id: SectionId, pub(crate) offset_index: usize, } /// An intermediate representation of a mutable multivar obtained from a [`File`][crate::File]. #[derive(PartialEq, Eq, Debug)] pub struct MultiValueMut<'borrow, 'lookup, 'event> { - pub(crate) section: &'borrow mut HashMap>, + pub(crate) section: &'borrow mut HashMap>, pub(crate) key: section::Key<'lookup>, /// Each entry data struct provides sufficient information to index into /// [`Self::offsets`]. This layer of indirection is used for users to index @@ -27,7 +27,7 @@ pub struct MultiValueMut<'borrow, 'lookup, 'event> { /// Each offset represents the size of a event slice and whether or not the /// event slice is significant or not. This is used to index into the /// actual section. - pub(crate) offsets: HashMap>, + pub(crate) offsets: HashMap>, } impl<'borrow, 'lookup, 'event> MultiValueMut<'borrow, 'lookup, 'event> { @@ -103,7 +103,7 @@ impl<'borrow, 'lookup, 'event> MultiValueMut<'borrow, 'lookup, 'event> { MultiValueMut::set_value_inner( &self.key, &mut self.offsets, - self.section.get_mut(§ion_id).expect("known section id"), + &mut self.section.get_mut(§ion_id).expect("known section id").body, section_id, offset_index, value.into(), @@ -133,7 +133,7 @@ impl<'borrow, 'lookup, 'event> MultiValueMut<'borrow, 'lookup, 'event> { Self::set_value_inner( &self.key, &mut self.offsets, - self.section.get_mut(section_id).expect("known section id"), + &mut self.section.get_mut(section_id).expect("known section id").body, *section_id, *offset_index, value.into(), @@ -153,7 +153,7 @@ impl<'borrow, 'lookup, 'event> MultiValueMut<'borrow, 'lookup, 'event> { Self::set_value_inner( &self.key, &mut self.offsets, - self.section.get_mut(section_id).expect("known section id"), + &mut self.section.get_mut(section_id).expect("known section id").body, *section_id, *offset_index, input, @@ -163,9 +163,9 @@ impl<'borrow, 'lookup, 'event> MultiValueMut<'borrow, 'lookup, 'event> { fn set_value_inner<'a: 'event>( key: §ion::Key<'lookup>, - offsets: &mut HashMap>, + offsets: &mut HashMap>, section: &mut SectionBody<'event>, - section_id: SectionBodyId, + section_id: SectionId, offset_index: usize, value: &BStr, ) { @@ -199,6 +199,7 @@ impl<'borrow, 'lookup, 'event> MultiValueMut<'borrow, 'lookup, 'event> { self.section .get_mut(section_id) .expect("known section id") + .body .as_mut() .drain(offset..offset + size); @@ -221,6 +222,7 @@ impl<'borrow, 'lookup, 'event> MultiValueMut<'borrow, 'lookup, 'event> { self.section .get_mut(section_id) .expect("known section id") + .body .as_mut() .drain(offset..offset + size); Self::set_offset(&mut self.offsets, *section_id, *offset_index, 0); @@ -229,8 +231,8 @@ impl<'borrow, 'lookup, 'event> MultiValueMut<'borrow, 'lookup, 'event> { } fn index_and_size( - offsets: &'lookup HashMap>, - section_id: SectionBodyId, + offsets: &'lookup HashMap>, + section_id: SectionId, offset_index: usize, ) -> (usize, usize) { offsets @@ -244,8 +246,8 @@ impl<'borrow, 'lookup, 'event> MultiValueMut<'borrow, 'lookup, 'event> { // This must be an associated function rather than a method to allow Rust // to split mutable borrows. fn set_offset( - offsets: &mut HashMap>, - section_id: SectionBodyId, + offsets: &mut HashMap>, + section_id: SectionId, offset_index: usize, value: usize, ) { diff --git a/git-config/src/file/mutable/section.rs b/git-config/src/file/mutable/section.rs index eadd2f9637b..88df20cd8f1 100644 --- a/git-config/src/file/mutable/section.rs +++ b/git-config/src/file/mutable/section.rs @@ -1,11 +1,11 @@ use std::{ borrow::Cow, - iter::FusedIterator, ops::{Deref, Range}, }; use bstr::{BStr, BString, ByteVec}; +use crate::file::{Section, SectionBody}; use crate::{ file::{ mutable::{escape_value, Whitespace}, @@ -19,7 +19,7 @@ use crate::{ /// A opaque type that represents a mutable reference to a section. #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] pub struct SectionMut<'a, 'event> { - section: &'a mut SectionBody<'event>, + section: &'a mut Section<'event>, implicit_newline: bool, whitespace: Whitespace<'event>, } @@ -28,15 +28,16 @@ pub struct SectionMut<'a, 'event> { impl<'a, 'event> SectionMut<'a, 'event> { /// Adds an entry to the end of this section name `key` and `value`. pub fn push<'b>(&mut self, key: Key<'event>, value: impl Into<&'b BStr>) { + let body = &mut self.section.body.0; if let Some(ws) = &self.whitespace.pre_key { - self.section.0.push(Event::Whitespace(ws.clone())); + body.push(Event::Whitespace(ws.clone())); } - self.section.0.push(Event::SectionKey(key)); - self.section.0.extend(self.whitespace.key_value_separators()); - self.section.0.push(Event::Value(escape_value(value.into()).into())); + body.push(Event::SectionKey(key)); + body.extend(self.whitespace.key_value_separators()); + body.push(Event::Value(escape_value(value.into()).into())); if self.implicit_newline { - self.section.0.push(Event::Newline(BString::from("\n").into())); + body.push(Event::Newline(BString::from("\n").into())); } } @@ -45,12 +46,13 @@ impl<'a, 'event> SectionMut<'a, 'event> { pub fn pop(&mut self) -> Option<(Key<'_>, Cow<'event, BStr>)> { let mut values = Vec::new(); // events are popped in reverse order - while let Some(e) = self.section.0.pop() { + let body = &mut self.section.body.0; + while let Some(e) = body.pop() { match e { Event::SectionKey(k) => { // pop leading whitespace - if let Some(Event::Whitespace(_)) = self.section.0.last() { - self.section.0.pop(); + if let Some(Event::Whitespace(_)) = body.last() { + body.pop(); } if values.len() == 1 { @@ -89,6 +91,7 @@ impl<'a, 'event> SectionMut<'a, 'event> { let range_start = value_range.start; let ret = self.remove_internal(value_range); self.section + .body .0 .insert(range_start, Event::Value(escape_value(value.into()).into())); Some(ret) @@ -106,7 +109,7 @@ impl<'a, 'event> SectionMut<'a, 'event> { /// Adds a new line event. Note that you don't need to call this unless /// you've disabled implicit newlines. pub fn push_newline(&mut self) { - self.section.0.push(Event::Newline(Cow::Borrowed("\n".into()))); + self.section.body.0.push(Event::Newline(Cow::Borrowed("\n".into()))); } /// Enables or disables automatically adding newline events after adding @@ -155,8 +158,8 @@ impl<'a, 'event> SectionMut<'a, 'event> { // Internal methods that may require exact indices for faster operations. impl<'a, 'event> SectionMut<'a, 'event> { - pub(crate) fn new(section: &'a mut SectionBody<'event>) -> Self { - let whitespace = (&*section).into(); + pub(crate) fn new(section: &'a mut Section<'event>) -> Self { + let whitespace = (§ion.body).into(); Self { section, implicit_newline: true, @@ -192,20 +195,21 @@ impl<'a, 'event> SectionMut<'a, 'event> { } pub(crate) fn delete(&mut self, start: Index, end: Index) { - self.section.0.drain(start.0..end.0); + self.section.body.0.drain(start.0..end.0); } pub(crate) fn set_internal(&mut self, index: Index, key: Key<'event>, value: &BStr) -> Size { let mut size = 0; - self.section.0.insert(index.0, Event::Value(escape_value(value).into())); + let body = &mut self.section.body.0; + body.insert(index.0, Event::Value(escape_value(value).into())); size += 1; let sep_events = self.whitespace.key_value_separators(); size += sep_events.len(); - self.section.0.insert_many(index.0, sep_events.into_iter().rev()); + body.insert_many(index.0, sep_events.into_iter().rev()); - self.section.0.insert(index.0, Event::SectionKey(key)); + body.insert(index.0, Event::SectionKey(key)); size += 1; Size(size) @@ -214,6 +218,7 @@ impl<'a, 'event> SectionMut<'a, 'event> { /// Performs the removal, assuming the range is valid. fn remove_internal(&mut self, range: Range) -> Cow<'event, BStr> { self.section + .body .0 .drain(range) .fold(Cow::Owned(BString::default()), |mut acc, e| { @@ -233,188 +238,8 @@ impl<'event> Deref for SectionMut<'_, 'event> { } } -/// A opaque type that represents a section body. -#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Clone, Debug, Default)] -pub struct SectionBody<'event>(pub(crate) parse::section::Events<'event>); - impl<'event> SectionBody<'event> { - pub(crate) fn as_ref(&self) -> &[Event<'_>] { - &self.0 - } - pub(crate) fn as_mut(&mut self) -> &mut parse::section::Events<'event> { &mut self.0 } - - /// Returns the the range containing the value events for the `key`. - /// If the value is not found, then this returns an empty range. - fn key_and_value_range_by(&self, key: &Key<'_>) -> Option<(Range, Range)> { - let mut value_range = Range::default(); - let mut key_start = None; - for (i, e) in self.0.iter().enumerate().rev() { - match e { - Event::SectionKey(k) => { - if k == key { - key_start = Some(i); - break; - } - value_range = Range::default(); - } - Event::Value(_) => { - (value_range.start, value_range.end) = (i, i); - } - Event::ValueNotDone(_) | Event::ValueDone(_) => { - if value_range.end == 0 { - value_range.end = i - } else { - value_range.start = i - }; - } - _ => (), - } - } - key_start.map(|key_start| { - // value end needs to be offset by one so that the last value's index - // is included in the range - let value_range = value_range.start..value_range.end + 1; - (key_start..value_range.end, value_range) - }) - } } - -/// Access -impl<'event> SectionBody<'event> { - /// Retrieves the last matching value in a section with the given key, if present. - #[must_use] - pub fn value(&self, key: impl AsRef) -> Option> { - let key = Key::from_str_unchecked(key.as_ref()); - let (_, range) = self.key_and_value_range_by(&key)?; - let mut concatenated = BString::default(); - - for event in &self.0[range] { - match event { - Event::Value(v) => { - return Some(normalize_bstr(v.as_ref())); - } - Event::ValueNotDone(v) => { - concatenated.push_str(v.as_ref()); - } - Event::ValueDone(v) => { - concatenated.push_str(v.as_ref()); - return Some(normalize_bstring(concatenated)); - } - _ => (), - } - } - None - } - - /// Retrieves all values that have the provided key name. This may return - /// an empty vec, which implies there were no values with the provided key. - #[must_use] - pub fn values(&self, key: impl AsRef) -> Vec> { - let key = &Key::from_str_unchecked(key.as_ref()); - let mut values = Vec::new(); - let mut expect_value = false; - let mut concatenated_value = BString::default(); - - for event in &self.0 { - match event { - Event::SectionKey(event_key) if event_key == key => expect_value = true, - Event::Value(v) if expect_value => { - expect_value = false; - values.push(normalize_bstr(v.as_ref())); - } - Event::ValueNotDone(v) if expect_value => { - concatenated_value.push_str(v.as_ref()); - } - Event::ValueDone(v) if expect_value => { - expect_value = false; - concatenated_value.push_str(v.as_ref()); - values.push(normalize_bstring(std::mem::take(&mut concatenated_value))); - } - _ => (), - } - } - - values - } - - /// Returns an iterator visiting all keys in order. - pub fn keys(&self) -> impl Iterator> { - self.0 - .iter() - .filter_map(|e| if let Event::SectionKey(k) = e { Some(k) } else { None }) - } - - /// Returns true if the section containss the provided key. - #[must_use] - pub fn contains_key(&self, key: impl AsRef) -> bool { - let key = &Key::from_str_unchecked(key.as_ref()); - self.0.iter().any(|e| { - matches!(e, - Event::SectionKey(k) if k == key - ) - }) - } - - /// Returns the number of values in the section. - #[must_use] - pub fn num_values(&self) -> usize { - self.0.iter().filter(|e| matches!(e, Event::SectionKey(_))).count() - } - - /// Returns if the section is empty. - /// Note that this may count whitespace, see [`num_values()`][Self::num_values()] for - /// another way to determine semantic emptiness. - #[must_use] - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } -} - -/// An owning iterator of a section body. Created by [`SectionBody::into_iter`], yielding -/// un-normalized (`key`, `value`) pairs. -// TODO: tests -pub struct SectionBodyIter<'event>(smallvec::IntoIter<[Event<'event>; 64]>); - -impl<'event> IntoIterator for SectionBody<'event> { - type Item = (Key<'event>, Cow<'event, BStr>); - - type IntoIter = SectionBodyIter<'event>; - - fn into_iter(self) -> Self::IntoIter { - SectionBodyIter(self.0.into_iter()) - } -} - -impl<'event> Iterator for SectionBodyIter<'event> { - type Item = (Key<'event>, Cow<'event, BStr>); - - fn next(&mut self) -> Option { - let mut key = None; - let mut partial_value = BString::default(); - let mut value = None; - - for event in self.0.by_ref() { - match event { - Event::SectionKey(k) => key = Some(k), - Event::Value(v) => { - value = Some(v); - break; - } - Event::ValueNotDone(v) => partial_value.push_str(v.as_ref()), - Event::ValueDone(v) => { - partial_value.push_str(v.as_ref()); - value = Some(partial_value.into()); - break; - } - _ => (), - } - } - - key.zip(value.map(normalize)) - } -} - -impl FusedIterator for SectionBodyIter<'_> {} diff --git a/git-config/src/file/section.rs b/git-config/src/file/section.rs deleted file mode 100644 index 33ecf01228b..00000000000 --- a/git-config/src/file/section.rs +++ /dev/null @@ -1,32 +0,0 @@ -use crate::file::Section; -use crate::Source; -use std::ops::Deref; -use std::path::PathBuf; - -/// Additional information about a section. -#[derive(Clone, Debug, PartialOrd, PartialEq, Ord, Eq, Hash)] -pub struct Metadata { - /// The file path of the source, if known. - pub path: Option, - /// Where the section is coming from. - pub source: Source, - /// The levels of indirection of the file, with 0 being a section - /// that was directly loaded, and 1 being an `include.path` of a - /// level 0 file. - pub level: u8, -} - -impl<'a> Deref for Section<'a> { - type Target = crate::parse::Section<'a>; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl Section<'_> { - /// Return our meta data, additional information about this section. - pub fn meta(&self) -> &Metadata { - self.meta.as_ref() - } -} diff --git a/git-config/src/file/section/body.rs b/git-config/src/file/section/body.rs new file mode 100644 index 00000000000..ed044a14bbb --- /dev/null +++ b/git-config/src/file/section/body.rs @@ -0,0 +1,189 @@ +use crate::parse::section::Key; +use crate::parse::Event; +use crate::value::{normalize, normalize_bstr, normalize_bstring}; +use bstr::{BStr, BString, ByteVec}; +use std::borrow::Cow; +use std::iter::FusedIterator; +use std::ops::Range; + +/// A opaque type that represents a section body. +#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Clone, Debug, Default)] +pub struct SectionBody<'event>(pub(crate) crate::parse::section::Events<'event>); + +/// Access +impl<'event> SectionBody<'event> { + /// Retrieves the last matching value in a section with the given key, if present. + #[must_use] + pub fn value(&self, key: impl AsRef) -> Option> { + let key = Key::from_str_unchecked(key.as_ref()); + let (_, range) = self.key_and_value_range_by(&key)?; + let mut concatenated = BString::default(); + + for event in &self.0[range] { + match event { + Event::Value(v) => { + return Some(normalize_bstr(v.as_ref())); + } + Event::ValueNotDone(v) => { + concatenated.push_str(v.as_ref()); + } + Event::ValueDone(v) => { + concatenated.push_str(v.as_ref()); + return Some(normalize_bstring(concatenated)); + } + _ => (), + } + } + None + } + + /// Retrieves all values that have the provided key name. This may return + /// an empty vec, which implies there were no values with the provided key. + #[must_use] + pub fn values(&self, key: impl AsRef) -> Vec> { + let key = &Key::from_str_unchecked(key.as_ref()); + let mut values = Vec::new(); + let mut expect_value = false; + let mut concatenated_value = BString::default(); + + for event in &self.0 { + match event { + Event::SectionKey(event_key) if event_key == key => expect_value = true, + Event::Value(v) if expect_value => { + expect_value = false; + values.push(normalize_bstr(v.as_ref())); + } + Event::ValueNotDone(v) if expect_value => { + concatenated_value.push_str(v.as_ref()); + } + Event::ValueDone(v) if expect_value => { + expect_value = false; + concatenated_value.push_str(v.as_ref()); + values.push(normalize_bstring(std::mem::take(&mut concatenated_value))); + } + _ => (), + } + } + + values + } + + /// Returns an iterator visiting all keys in order. + pub fn keys(&self) -> impl Iterator> { + self.0 + .iter() + .filter_map(|e| if let Event::SectionKey(k) = e { Some(k) } else { None }) + } + + /// Returns true if the section containss the provided key. + #[must_use] + pub fn contains_key(&self, key: impl AsRef) -> bool { + let key = &Key::from_str_unchecked(key.as_ref()); + self.0.iter().any(|e| { + matches!(e, + Event::SectionKey(k) if k == key + ) + }) + } + + /// Returns the number of values in the section. + #[must_use] + pub fn num_values(&self) -> usize { + self.0.iter().filter(|e| matches!(e, Event::SectionKey(_))).count() + } + + /// Returns if the section is empty. + /// Note that this may count whitespace, see [`num_values()`][Self::num_values()] for + /// another way to determine semantic emptiness. + #[must_use] + pub fn is_void(&self) -> bool { + self.0.is_empty() + } +} + +impl<'event> SectionBody<'event> { + pub(crate) fn as_ref(&self) -> &[Event<'_>] { + &self.0 + } + + /// Returns the the range containing the value events for the `key`. + /// If the value is not found, then this returns an empty range. + pub(crate) fn key_and_value_range_by(&self, key: &Key<'_>) -> Option<(Range, Range)> { + let mut value_range = Range::default(); + let mut key_start = None; + for (i, e) in self.0.iter().enumerate().rev() { + match e { + Event::SectionKey(k) => { + if k == key { + key_start = Some(i); + break; + } + value_range = Range::default(); + } + Event::Value(_) => { + (value_range.start, value_range.end) = (i, i); + } + Event::ValueNotDone(_) | Event::ValueDone(_) => { + if value_range.end == 0 { + value_range.end = i + } else { + value_range.start = i + }; + } + _ => (), + } + } + key_start.map(|key_start| { + // value end needs to be offset by one so that the last value's index + // is included in the range + let value_range = value_range.start..value_range.end + 1; + (key_start..value_range.end, value_range) + }) + } +} + +/// An owning iterator of a section body. Created by [`SectionBody::into_iter`], yielding +/// un-normalized (`key`, `value`) pairs. +// TODO: tests +pub struct SectionBodyIter<'event>(smallvec::IntoIter<[Event<'event>; 64]>); + +impl<'event> IntoIterator for SectionBody<'event> { + type Item = (Key<'event>, Cow<'event, BStr>); + + type IntoIter = SectionBodyIter<'event>; + + fn into_iter(self) -> Self::IntoIter { + SectionBodyIter(self.0.into_iter()) + } +} + +impl<'event> Iterator for SectionBodyIter<'event> { + type Item = (Key<'event>, Cow<'event, BStr>); + + fn next(&mut self) -> Option { + let mut key = None; + let mut partial_value = BString::default(); + let mut value = None; + + for event in self.0.by_ref() { + match event { + Event::SectionKey(k) => key = Some(k), + Event::Value(v) => { + value = Some(v); + break; + } + Event::ValueNotDone(v) => partial_value.push_str(v.as_ref()), + Event::ValueDone(v) => { + partial_value.push_str(v.as_ref()); + value = Some(partial_value.into()); + break; + } + _ => (), + } + } + + key.zip(value.map(normalize)) + } +} + +impl FusedIterator for SectionBodyIter<'_> {} diff --git a/git-config/src/file/section/mod.rs b/git-config/src/file/section/mod.rs new file mode 100644 index 00000000000..c01bdb3cc9a --- /dev/null +++ b/git-config/src/file/section/mod.rs @@ -0,0 +1,61 @@ +use crate::file::{Section, SectionBody}; +use crate::parse::section; +use crate::Source; +use bstr::BString; +use std::ops::Deref; +use std::path::PathBuf; + +/// Additional information about a section. +#[derive(Clone, Debug, PartialOrd, PartialEq, Ord, Eq, Hash)] +pub struct Metadata { + /// The file path of the source, if known. + pub path: Option, + /// Where the section is coming from. + pub source: Source, + /// The levels of indirection of the file, with 0 being a section + /// that was directly loaded, and 1 being an `include.path` of a + /// level 0 file. + pub level: u8, +} + +impl<'a> Deref for Section<'a> { + type Target = SectionBody<'a>; + + fn deref(&self) -> &Self::Target { + &self.body + } +} + +impl<'a> Section<'a> { + /// Return our header. + pub fn header(&self) -> §ion::Header<'a> { + &self.header + } + + /// Return our body, containing all keys and values. + pub fn body(&self) -> &SectionBody<'a> { + &self.body + } + + /// Serialize this type into a `BString` for convenience. + /// + /// Note that `to_string()` can also be used, but might not be lossless. + #[must_use] + pub fn to_bstring(&self) -> BString { + let mut buf = Vec::new(); + self.write_to(&mut buf).expect("io error impossible"); + buf.into() + } + + /// Stream ourselves to the given `out`, in order to reproduce this section mostly losslessly + /// as it was parsed. + pub fn write_to(&self, mut out: impl std::io::Write) -> std::io::Result<()> { + self.header.write_to(&mut out)?; + for event in self.body.as_ref() { + event.write_to(&mut out)?; + } + Ok(()) + } +} + +pub(crate) mod body; diff --git a/git-config/src/file/tests.rs b/git-config/src/file/tests.rs index fffc4d2a92e..093ae505c9e 100644 --- a/git-config/src/file/tests.rs +++ b/git-config/src/file/tests.rs @@ -1,8 +1,13 @@ +use crate::file::{Section, SectionBody, SectionId}; +use crate::parse::section; +use std::collections::HashMap; + mod try_from { + use super::{bodies, headers}; use std::{borrow::Cow, collections::HashMap, convert::TryFrom}; use crate::{ - file::{SectionBody, SectionBodyId, SectionBodyIds}, + file::{SectionBody, SectionBodyIds, SectionId}, parse::{ section, tests::util::{name_event, newline_event, section_header, value_event}, @@ -14,7 +19,6 @@ mod try_from { #[test] fn empty() { let config = File::try_from("").unwrap(); - assert!(config.section_headers.is_empty()); assert_eq!(config.section_id_counter, 0); assert!(config.section_lookup_tree.is_empty()); assert!(config.sections.is_empty()); @@ -26,16 +30,16 @@ mod try_from { let mut config = File::try_from("[core]\na=b\nc=d").unwrap(); let expected_separators = { let mut map = HashMap::new(); - map.insert(SectionBodyId(0), section_header("core", None)); + map.insert(SectionId(0), section_header("core", None)); map }; - assert_eq!(config.section_headers, expected_separators); + assert_eq!(headers(&config.sections), expected_separators); assert_eq!(config.section_id_counter, 1); let expected_lookup_tree = { let mut tree = HashMap::new(); tree.insert( section::Name(Cow::Borrowed("core".into())), - vec![SectionBodyIds::Terminal(vec![SectionBodyId(0)])], + vec![SectionBodyIds::Terminal(vec![SectionId(0)])], ); tree }; @@ -43,7 +47,7 @@ mod try_from { let expected_sections = { let mut sections = HashMap::new(); sections.insert( - SectionBodyId(0), + SectionId(0), SectionBody( vec![ newline_event(), @@ -60,8 +64,8 @@ mod try_from { ); sections }; - assert_eq!(config.sections, expected_sections); - assert_eq!(config.section_order.make_contiguous(), &[SectionBodyId(0)]); + assert_eq!(bodies(&config.sections), expected_sections); + assert_eq!(config.section_order.make_contiguous(), &[SectionId(0)]); } #[test] @@ -69,15 +73,15 @@ mod try_from { let mut config = File::try_from("[core.sub]\na=b\nc=d").unwrap(); let expected_separators = { let mut map = HashMap::new(); - map.insert(SectionBodyId(0), section_header("core", (".", "sub"))); + map.insert(SectionId(0), section_header("core", (".", "sub"))); map }; - assert_eq!(config.section_headers, expected_separators); + assert_eq!(headers(&config.sections), expected_separators); assert_eq!(config.section_id_counter, 1); let expected_lookup_tree = { let mut tree = HashMap::new(); let mut inner_tree = HashMap::new(); - inner_tree.insert(Cow::Borrowed("sub".into()), vec![SectionBodyId(0)]); + inner_tree.insert(Cow::Borrowed("sub".into()), vec![SectionId(0)]); tree.insert( section::Name(Cow::Borrowed("core".into())), vec![SectionBodyIds::NonTerminal(inner_tree)], @@ -88,7 +92,7 @@ mod try_from { let expected_sections = { let mut sections = HashMap::new(); sections.insert( - SectionBodyId(0), + SectionId(0), SectionBody( vec![ newline_event(), @@ -105,8 +109,8 @@ mod try_from { ); sections }; - assert_eq!(config.sections, expected_sections); - assert_eq!(config.section_order.make_contiguous(), &[SectionBodyId(0)]); + assert_eq!(bodies(&config.sections), expected_sections); + assert_eq!(config.section_order.make_contiguous(), &[SectionId(0)]); } #[test] @@ -114,21 +118,21 @@ mod try_from { let mut config = File::try_from("[core]\na=b\nc=d\n[other]e=f").unwrap(); let expected_separators = { let mut map = HashMap::new(); - map.insert(SectionBodyId(0), section_header("core", None)); - map.insert(SectionBodyId(1), section_header("other", None)); + map.insert(SectionId(0), section_header("core", None)); + map.insert(SectionId(1), section_header("other", None)); map }; - assert_eq!(config.section_headers, expected_separators); + assert_eq!(headers(&config.sections), expected_separators); assert_eq!(config.section_id_counter, 2); let expected_lookup_tree = { let mut tree = HashMap::new(); tree.insert( section::Name(Cow::Borrowed("core".into())), - vec![SectionBodyIds::Terminal(vec![SectionBodyId(0)])], + vec![SectionBodyIds::Terminal(vec![SectionId(0)])], ); tree.insert( section::Name(Cow::Borrowed("other".into())), - vec![SectionBodyIds::Terminal(vec![SectionBodyId(1)])], + vec![SectionBodyIds::Terminal(vec![SectionId(1)])], ); tree }; @@ -136,7 +140,7 @@ mod try_from { let expected_sections = { let mut sections = HashMap::new(); sections.insert( - SectionBodyId(0), + SectionId(0), SectionBody( vec![ newline_event(), @@ -153,16 +157,13 @@ mod try_from { ), ); sections.insert( - SectionBodyId(1), + SectionId(1), SectionBody(vec![name_event("e"), Event::KeyValueSeparator, value_event("f")].into()), ); sections }; - assert_eq!(config.sections, expected_sections); - assert_eq!( - config.section_order.make_contiguous(), - &[SectionBodyId(0), SectionBodyId(1)] - ); + assert_eq!(bodies(&config.sections), expected_sections); + assert_eq!(config.section_order.make_contiguous(), &[SectionId(0), SectionId(1)]); } #[test] @@ -170,17 +171,17 @@ mod try_from { let mut config = File::try_from("[core]\na=b\nc=d\n[core]e=f").unwrap(); let expected_separators = { let mut map = HashMap::new(); - map.insert(SectionBodyId(0), section_header("core", None)); - map.insert(SectionBodyId(1), section_header("core", None)); + map.insert(SectionId(0), section_header("core", None)); + map.insert(SectionId(1), section_header("core", None)); map }; - assert_eq!(config.section_headers, expected_separators); + assert_eq!(headers(&config.sections), expected_separators); assert_eq!(config.section_id_counter, 2); let expected_lookup_tree = { let mut tree = HashMap::new(); tree.insert( section::Name(Cow::Borrowed("core".into())), - vec![SectionBodyIds::Terminal(vec![SectionBodyId(0), SectionBodyId(1)])], + vec![SectionBodyIds::Terminal(vec![SectionId(0), SectionId(1)])], ); tree }; @@ -188,7 +189,7 @@ mod try_from { let expected_sections = { let mut sections = HashMap::new(); sections.insert( - SectionBodyId(0), + SectionId(0), SectionBody( vec![ newline_event(), @@ -205,15 +206,20 @@ mod try_from { ), ); sections.insert( - SectionBodyId(1), + SectionId(1), SectionBody(vec![name_event("e"), Event::KeyValueSeparator, value_event("f")].into()), ); sections }; - assert_eq!(config.sections, expected_sections); - assert_eq!( - config.section_order.make_contiguous(), - &[SectionBodyId(0), SectionBodyId(1)] - ); + assert_eq!(bodies(&config.sections), expected_sections); + assert_eq!(config.section_order.make_contiguous(), &[SectionId(0), SectionId(1)]); } } + +fn headers<'a>(sections: &HashMap>) -> HashMap> { + sections.iter().map(|(k, v)| (*k, v.header.clone())).collect() +} + +fn bodies<'a>(sections: &HashMap>) -> HashMap> { + sections.iter().map(|(k, v)| (*k, v.body.clone())).collect() +} diff --git a/git-config/src/file/utils.rs b/git-config/src/file/utils.rs index 7642f05ba14..38b43057772 100644 --- a/git-config/src/file/utils.rs +++ b/git-config/src/file/utils.rs @@ -2,8 +2,9 @@ use std::collections::HashMap; use bstr::BStr; +use crate::file::Section; use crate::{ - file::{SectionBody, SectionBodyId, SectionBodyIds, SectionMut}, + file::{SectionBody, SectionBodyIds, SectionId, SectionMut}, lookup, parse::section, File, @@ -15,11 +16,16 @@ impl<'event> File<'event> { pub(crate) fn push_section_internal( &mut self, header: section::Header<'event>, - section: SectionBody<'event>, + body: SectionBody<'event>, ) -> SectionMut<'_, 'event> { - let new_section_id = SectionBodyId(self.section_id_counter); - self.section_headers.insert(new_section_id, header.clone()); - self.sections.insert(new_section_id, section); + let new_section_id = SectionId(self.section_id_counter); + self.sections.insert( + new_section_id, + Section { + body, + header: header.clone(), + }, + ); let lookup = self.section_lookup_tree.entry(header.name).or_default(); let mut found_node = false; @@ -64,10 +70,8 @@ impl<'event> File<'event> { &'a self, section_name: &'a str, subsection_name: Option<&str>, - ) -> Result< - impl Iterator + ExactSizeIterator + DoubleEndedIterator + '_, - lookup::existing::Error, - > { + ) -> Result + ExactSizeIterator + DoubleEndedIterator + '_, lookup::existing::Error> + { let section_name = section::Name::from_str_unchecked(section_name); let section_ids = self .section_lookup_tree @@ -99,7 +103,7 @@ impl<'event> File<'event> { pub(crate) fn section_ids_by_name<'a>( &'a self, section_name: &'a str, - ) -> Result + '_, lookup::existing::Error> { + ) -> Result + '_, lookup::existing::Error> { let section_name = section::Name::from_str_unchecked(section_name); match self.section_lookup_tree.get(§ion_name) { Some(lookup) => Ok(lookup.iter().flat_map({ @@ -120,9 +124,8 @@ impl<'event> File<'event> { // so can't be written back. This will probably change a lot during refactor, so it's not too important now. pub(crate) fn append(&mut self, mut other: Self) { for id in std::mem::take(&mut other.section_order) { - let header = other.section_headers.remove(&id).expect("present"); - let body = other.sections.remove(&id).expect("present"); - self.push_section_internal(header, body); + let section = other.sections.remove(&id).expect("present"); + self.push_section_internal(section.header, section.body); } } } diff --git a/git-config/src/types.rs b/git-config/src/types.rs index 251d748a4c8..cfb8fbd65d9 100644 --- a/git-config/src/types.rs +++ b/git-config/src/types.rs @@ -1,8 +1,8 @@ use std::collections::{HashMap, VecDeque}; use crate::{ - color, - file::{SectionBody, SectionBodyId, SectionBodyIds}, + color, file, + file::{SectionBodyIds, SectionId}, integer, parse::section, }; @@ -84,13 +84,11 @@ pub struct File<'event> { pub(crate) section_lookup_tree: HashMap, Vec>>, /// This indirection with the SectionId as the key is critical to flexibly /// supporting `git-config` sections, as duplicated keys are permitted. - pub(crate) sections: HashMap>, - /// A way to reconstruct the complete section being a header and a body. - pub(crate) section_headers: HashMap>, + pub(crate) sections: HashMap>, /// Internal monotonically increasing counter for section ids. pub(crate) section_id_counter: usize, /// Section order for output ordering. - pub(crate) section_order: VecDeque, + pub(crate) section_order: VecDeque, } /// Any value that may contain a foreground color, background color, a diff --git a/git-config/tests/file/mod.rs b/git-config/tests/file/mod.rs index 3d0ed7bf1a4..6c9b93db19f 100644 --- a/git-config/tests/file/mod.rs +++ b/git-config/tests/file/mod.rs @@ -10,7 +10,7 @@ pub fn cow_str(s: &str) -> Cow<'_, BStr> { fn size_in_memory() { assert_eq!( std::mem::size_of::>(), - 1032, + 984, "This shouldn't change without us noticing" ); } diff --git a/git-config/tests/file/mutable/section.rs b/git-config/tests/file/mutable/section.rs index 3b35401d456..1ae8f8c1290 100644 --- a/git-config/tests/file/mutable/section.rs +++ b/git-config/tests/file/mutable/section.rs @@ -18,7 +18,7 @@ mod remove { assert_eq!(section.num_values(), num_values); } - assert!(!section.is_empty(), "everything is still there"); + assert!(!section.is_void(), "everything is still there"); assert_eq!( config.to_string(), "\n [a]\n \n \n \n \n " @@ -47,7 +47,7 @@ mod pop { num_values -= 1; assert_eq!(section.num_values(), num_values); } - assert!(!section.is_empty(), "there still is some whitespace"); + assert!(!section.is_void(), "there still is some whitespace"); assert_eq!(config.to_string(), "\n [a]\n"); Ok(()) } From 09e23743035b9d4463f438378aed54677c03311f Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 15 Jul 2022 21:53:20 +0800 Subject: [PATCH 217/366] thanks clippy --- git-config/src/file/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-config/src/file/mod.rs b/git-config/src/file/mod.rs index f3ea03dfa49..19a190776de 100644 --- a/git-config/src/file/mod.rs +++ b/git-config/src/file/mod.rs @@ -76,7 +76,7 @@ pub mod resolve_includes { /// Set the context used for interpolation when interpolating paths to include as well as the paths /// in `gitdir` conditional includes. pub fn interpolate_with(mut self, context: crate::path::interpolate::Context<'a>) -> Self { - self.interpolate = context.into(); + self.interpolate = context; self } } From b672ed7667a334be3d45c59f4727f12797b340da Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 15 Jul 2022 22:06:29 +0800 Subject: [PATCH 218/366] change!: rename `file::SectionBody` to `file::section::Body`. (#331) --- git-config/src/file/access/mutate.rs | 8 ++++---- git-config/src/file/access/read_only.rs | 4 ++-- git-config/src/file/impls.rs | 4 ++-- git-config/src/file/mod.rs | 3 +-- git-config/src/file/mutable/mod.rs | 14 +++++++------- git-config/src/file/mutable/multi_value.rs | 4 ++-- git-config/src/file/mutable/section.rs | 6 +++--- git-config/src/file/section/body.rs | 18 +++++++++--------- git-config/src/file/section/mod.rs | 7 ++++--- git-config/src/file/tests.rs | 18 +++++++++--------- git-config/src/file/utils.rs | 4 ++-- 11 files changed, 45 insertions(+), 45 deletions(-) diff --git a/git-config/src/file/access/mutate.rs b/git-config/src/file/access/mutate.rs index 2a38b42dec3..4eb02927cfd 100644 --- a/git-config/src/file/access/mutate.rs +++ b/git-config/src/file/access/mutate.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use crate::{ - file::{rename_section, SectionBody, SectionMut}, + file::{self, rename_section, SectionMut}, lookup, parse::section, File, @@ -63,7 +63,7 @@ impl<'event> File<'event> { section_name: impl Into>, subsection_name: impl Into>>, ) -> Result, section::header::Error> { - let mut section = self.push_section(section_name, subsection_name, SectionBody::default())?; + let mut section = self.push_section(section_name, subsection_name, file::section::Body::default())?; section.push_newline(); Ok(section) } @@ -109,7 +109,7 @@ impl<'event> File<'event> { &mut self, section_name: &str, subsection_name: impl Into>, - ) -> Option> { + ) -> Option> { let id = self .section_ids_by_name_and_subname(section_name, subsection_name.into()) .ok()? @@ -130,7 +130,7 @@ impl<'event> File<'event> { &mut self, section_name: impl Into>, subsection_name: impl Into>>, - section: SectionBody<'event>, + section: file::section::Body<'event>, ) -> Result, section::header::Error> { Ok(self.push_section_internal(section::Header::new(section_name, subsection_name)?, section)) } diff --git a/git-config/src/file/access/read_only.rs b/git-config/src/file/access/read_only.rs index 38dd7f7d2b5..a2e65e5ded7 100644 --- a/git-config/src/file/access/read_only.rs +++ b/git-config/src/file/access/read_only.rs @@ -2,7 +2,7 @@ use std::{borrow::Cow, convert::TryFrom}; use bstr::BStr; -use crate::{file, file::SectionBody, lookup, File}; +use crate::{file, lookup, File}; /// Read-only low-level access methods, as it requires generics for converting into /// custom values defined in this crate like [`Integer`][crate::Integer] and @@ -122,7 +122,7 @@ impl<'event> File<'event> { &mut self, section_name: impl AsRef, subsection_name: Option<&str>, - ) -> Result<&SectionBody<'event>, lookup::existing::Error> { + ) -> Result<&file::section::Body<'event>, lookup::existing::Error> { let id = self .section_ids_by_name_and_subname(section_name.as_ref(), subsection_name)? .rev() diff --git a/git-config/src/file/impls.rs b/git-config/src/file/impls.rs index bf756d948f6..69547162189 100644 --- a/git-config/src/file/impls.rs +++ b/git-config/src/file/impls.rs @@ -2,7 +2,7 @@ use std::{convert::TryFrom, fmt::Display, str::FromStr}; use bstr::{BStr, BString}; -use crate::{file::SectionBody, parse, File}; +use crate::{file::section, parse, File}; impl FromStr for File<'static> { type Err = parse::Error; @@ -40,7 +40,7 @@ impl<'a> From> for File<'a> { }; for section in events.sections { - this.push_section_internal(section.section_header, SectionBody(section.events)); + this.push_section_internal(section.section_header, section::Body(section.events)); } this diff --git a/git-config/src/file/mod.rs b/git-config/src/file/mod.rs index 19a190776de..41471cec74f 100644 --- a/git-config/src/file/mod.rs +++ b/git-config/src/file/mod.rs @@ -20,7 +20,6 @@ mod utils; /// pub mod section; -pub use section::body::{SectionBody, SectionBodyIter}; /// pub mod resolve_includes { @@ -121,7 +120,7 @@ pub mod rename_section { #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] pub struct Section<'a> { header: crate::parse::section::Header<'a>, - body: SectionBody<'a>, + body: section::Body<'a>, } /// A strongly typed index into some range. diff --git a/git-config/src/file/mutable/mod.rs b/git-config/src/file/mutable/mod.rs index aae564d96c5..39b8a505452 100644 --- a/git-config/src/file/mutable/mod.rs +++ b/git-config/src/file/mutable/mod.rs @@ -2,7 +2,11 @@ use std::borrow::Cow; use bstr::{BStr, BString, ByteSlice, ByteVec}; -use crate::{file::SectionBody, parse::Event}; +use crate::{file, parse::Event}; + +pub(crate) mod multi_value; +pub(crate) mod section; +pub(crate) mod value; fn escape_value(value: &BStr) -> BString { let starts_with_whitespace = value.get(0).map_or(false, |b| b.is_ascii_whitespace()); @@ -64,8 +68,8 @@ impl<'a> Whitespace<'a> { } } -impl<'a> From<&SectionBody<'a>> for Whitespace<'a> { - fn from(s: &SectionBody<'a>) -> Self { +impl<'a> From<&file::section::Body<'a>> for Whitespace<'a> { + fn from(s: &file::section::Body<'a>) -> Self { let key_pos = s.0.iter() .enumerate() @@ -103,7 +107,3 @@ impl<'a> From<&SectionBody<'a>> for Whitespace<'a> { .unwrap_or_default() } } - -pub(crate) mod multi_value; -pub(crate) mod section; -pub(crate) mod value; diff --git a/git-config/src/file/mutable/multi_value.rs b/git-config/src/file/mutable/multi_value.rs index 420a46f2568..db4b34df947 100644 --- a/git-config/src/file/mutable/multi_value.rs +++ b/git-config/src/file/mutable/multi_value.rs @@ -1,5 +1,5 @@ use crate::file::mutable::{escape_value, Whitespace}; -use crate::file::{Section, SectionBody, SectionId}; +use crate::file::{self, Section, SectionId}; use crate::lookup; use crate::parse::{section, Event}; use crate::value::{normalize_bstr, normalize_bstring}; @@ -164,7 +164,7 @@ impl<'borrow, 'lookup, 'event> MultiValueMut<'borrow, 'lookup, 'event> { fn set_value_inner<'a: 'event>( key: §ion::Key<'lookup>, offsets: &mut HashMap>, - section: &mut SectionBody<'event>, + section: &mut file::section::Body<'event>, section_id: SectionId, offset_index: usize, value: &BStr, diff --git a/git-config/src/file/mutable/section.rs b/git-config/src/file/mutable/section.rs index 88df20cd8f1..7a7ec8183ed 100644 --- a/git-config/src/file/mutable/section.rs +++ b/git-config/src/file/mutable/section.rs @@ -5,7 +5,7 @@ use std::{ use bstr::{BStr, BString, ByteVec}; -use crate::file::{Section, SectionBody}; +use crate::file::{self, Section}; use crate::{ file::{ mutable::{escape_value, Whitespace}, @@ -231,14 +231,14 @@ impl<'a, 'event> SectionMut<'a, 'event> { } impl<'event> Deref for SectionMut<'_, 'event> { - type Target = SectionBody<'event>; + type Target = file::section::Body<'event>; fn deref(&self) -> &Self::Target { self.section } } -impl<'event> SectionBody<'event> { +impl<'event> file::section::Body<'event> { pub(crate) fn as_mut(&mut self) -> &mut parse::section::Events<'event> { &mut self.0 } diff --git a/git-config/src/file/section/body.rs b/git-config/src/file/section/body.rs index ed044a14bbb..73d6e1f59bd 100644 --- a/git-config/src/file/section/body.rs +++ b/git-config/src/file/section/body.rs @@ -8,10 +8,10 @@ use std::ops::Range; /// A opaque type that represents a section body. #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Clone, Debug, Default)] -pub struct SectionBody<'event>(pub(crate) crate::parse::section::Events<'event>); +pub struct Body<'event>(pub(crate) crate::parse::section::Events<'event>); /// Access -impl<'event> SectionBody<'event> { +impl<'event> Body<'event> { /// Retrieves the last matching value in a section with the given key, if present. #[must_use] pub fn value(&self, key: impl AsRef) -> Option> { @@ -101,7 +101,7 @@ impl<'event> SectionBody<'event> { } } -impl<'event> SectionBody<'event> { +impl<'event> Body<'event> { pub(crate) fn as_ref(&self) -> &[Event<'_>] { &self.0 } @@ -145,19 +145,19 @@ impl<'event> SectionBody<'event> { /// An owning iterator of a section body. Created by [`SectionBody::into_iter`], yielding /// un-normalized (`key`, `value`) pairs. // TODO: tests -pub struct SectionBodyIter<'event>(smallvec::IntoIter<[Event<'event>; 64]>); +pub struct BodyIter<'event>(smallvec::IntoIter<[Event<'event>; 64]>); -impl<'event> IntoIterator for SectionBody<'event> { +impl<'event> IntoIterator for Body<'event> { type Item = (Key<'event>, Cow<'event, BStr>); - type IntoIter = SectionBodyIter<'event>; + type IntoIter = BodyIter<'event>; fn into_iter(self) -> Self::IntoIter { - SectionBodyIter(self.0.into_iter()) + BodyIter(self.0.into_iter()) } } -impl<'event> Iterator for SectionBodyIter<'event> { +impl<'event> Iterator for BodyIter<'event> { type Item = (Key<'event>, Cow<'event, BStr>); fn next(&mut self) -> Option { @@ -186,4 +186,4 @@ impl<'event> Iterator for SectionBodyIter<'event> { } } -impl FusedIterator for SectionBodyIter<'_> {} +impl FusedIterator for BodyIter<'_> {} diff --git a/git-config/src/file/section/mod.rs b/git-config/src/file/section/mod.rs index c01bdb3cc9a..69adf5a137e 100644 --- a/git-config/src/file/section/mod.rs +++ b/git-config/src/file/section/mod.rs @@ -1,4 +1,4 @@ -use crate::file::{Section, SectionBody}; +use crate::file::Section; use crate::parse::section; use crate::Source; use bstr::BString; @@ -19,7 +19,7 @@ pub struct Metadata { } impl<'a> Deref for Section<'a> { - type Target = SectionBody<'a>; + type Target = Body<'a>; fn deref(&self) -> &Self::Target { &self.body @@ -33,7 +33,7 @@ impl<'a> Section<'a> { } /// Return our body, containing all keys and values. - pub fn body(&self) -> &SectionBody<'a> { + pub fn body(&self) -> &Body<'a> { &self.body } @@ -59,3 +59,4 @@ impl<'a> Section<'a> { } pub(crate) mod body; +pub use body::{Body, BodyIter}; diff --git a/git-config/src/file/tests.rs b/git-config/src/file/tests.rs index 093ae505c9e..455b67dd705 100644 --- a/git-config/src/file/tests.rs +++ b/git-config/src/file/tests.rs @@ -1,4 +1,4 @@ -use crate::file::{Section, SectionBody, SectionId}; +use crate::file::{self, Section, SectionId}; use crate::parse::section; use std::collections::HashMap; @@ -7,7 +7,7 @@ mod try_from { use std::{borrow::Cow, collections::HashMap, convert::TryFrom}; use crate::{ - file::{SectionBody, SectionBodyIds, SectionId}, + file::{self, SectionBodyIds, SectionId}, parse::{ section, tests::util::{name_event, newline_event, section_header, value_event}, @@ -48,7 +48,7 @@ mod try_from { let mut sections = HashMap::new(); sections.insert( SectionId(0), - SectionBody( + file::section::Body( vec![ newline_event(), name_event("a"), @@ -93,7 +93,7 @@ mod try_from { let mut sections = HashMap::new(); sections.insert( SectionId(0), - SectionBody( + file::section::Body( vec![ newline_event(), name_event("a"), @@ -141,7 +141,7 @@ mod try_from { let mut sections = HashMap::new(); sections.insert( SectionId(0), - SectionBody( + file::section::Body( vec![ newline_event(), name_event("a"), @@ -158,7 +158,7 @@ mod try_from { ); sections.insert( SectionId(1), - SectionBody(vec![name_event("e"), Event::KeyValueSeparator, value_event("f")].into()), + file::section::Body(vec![name_event("e"), Event::KeyValueSeparator, value_event("f")].into()), ); sections }; @@ -190,7 +190,7 @@ mod try_from { let mut sections = HashMap::new(); sections.insert( SectionId(0), - SectionBody( + file::section::Body( vec![ newline_event(), name_event("a"), @@ -207,7 +207,7 @@ mod try_from { ); sections.insert( SectionId(1), - SectionBody(vec![name_event("e"), Event::KeyValueSeparator, value_event("f")].into()), + file::section::Body(vec![name_event("e"), Event::KeyValueSeparator, value_event("f")].into()), ); sections }; @@ -220,6 +220,6 @@ fn headers<'a>(sections: &HashMap>) -> HashMap(sections: &HashMap>) -> HashMap> { +fn bodies<'a>(sections: &HashMap>) -> HashMap> { sections.iter().map(|(k, v)| (*k, v.body.clone())).collect() } diff --git a/git-config/src/file/utils.rs b/git-config/src/file/utils.rs index 38b43057772..0df7e884bef 100644 --- a/git-config/src/file/utils.rs +++ b/git-config/src/file/utils.rs @@ -4,7 +4,7 @@ use bstr::BStr; use crate::file::Section; use crate::{ - file::{SectionBody, SectionBodyIds, SectionId, SectionMut}, + file::{self, SectionBodyIds, SectionId, SectionMut}, lookup, parse::section, File, @@ -16,7 +16,7 @@ impl<'event> File<'event> { pub(crate) fn push_section_internal( &mut self, header: section::Header<'event>, - body: SectionBody<'event>, + body: file::section::Body<'event>, ) -> SectionMut<'_, 'event> { let new_section_id = SectionId(self.section_id_counter); self.sections.insert( From 6f4eea936d64fb9827277c160f989168e7b1dba2 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 16 Jul 2022 18:10:31 +0800 Subject: [PATCH 219/366] change!: Associate `file::Metadata` with each `File`. (#331) This is the first step towards knowing more about the source of each value to filter them based on some properties. This breaks various methods handling the instantiation of configuration files as `file::Metadata` typically has to be provided by the caller now or be associated with each path to read configuration from. --- git-config/src/file/access/mutate.rs | 15 +-- git-config/src/file/access/read_only.rs | 12 +++ git-config/src/file/impls.rs | 25 ++--- git-config/src/file/init/from_env.rs | 71 +++++++++--- git-config/src/file/init/from_paths.rs | 68 ++++++++---- git-config/src/file/init/mod.rs | 41 +++++++ git-config/src/file/init/resolve_includes.rs | 21 +++- git-config/src/file/meta.rs | 54 ++++++++++ git-config/src/file/mod.rs | 102 ++++-------------- git-config/src/file/resolve_includes.rs | 75 +++++++++++++ git-config/src/file/section/body.rs | 2 +- git-config/src/file/section/mod.rs | 48 +++++---- git-config/src/file/utils.rs | 23 ++-- git-config/src/parse/mod.rs | 2 +- git-config/src/parse/section/mod.rs | 2 +- git-config/src/types.rs | 6 ++ git-config/tests/file/access/read_only.rs | 3 +- .../includes/conditional/gitdir/util.rs | 7 +- .../from_paths/includes/conditional/mod.rs | 25 +++-- .../includes/conditional/onbranch.rs | 8 +- .../init/from_paths/includes/unconditional.rs | 25 ++--- git-config/tests/file/init/from_paths/mod.rs | 22 +++- git-config/tests/file/mod.rs | 10 +- git-repository/src/config.rs | 19 +++- gitoxide-core/src/organize.rs | 5 +- 25 files changed, 469 insertions(+), 222 deletions(-) create mode 100644 git-config/src/file/meta.rs create mode 100644 git-config/src/file/resolve_includes.rs diff --git a/git-config/src/file/access/mutate.rs b/git-config/src/file/access/mutate.rs index 4eb02927cfd..fff19faf96c 100644 --- a/git-config/src/file/access/mutate.rs +++ b/git-config/src/file/access/mutate.rs @@ -1,3 +1,4 @@ +use git_features::threading::OwnShared; use std::borrow::Cow; use crate::{ @@ -60,10 +61,11 @@ impl<'event> File<'event> { /// ``` pub fn new_section( &mut self, - section_name: impl Into>, - subsection_name: impl Into>>, + name: impl Into>, + subsection: impl Into>>, ) -> Result, section::header::Error> { - let mut section = self.push_section(section_name, subsection_name, file::section::Body::default())?; + let mut section = + self.push_section_internal(file::Section::new(name, subsection, OwnShared::clone(&self.meta))?); section.push_newline(); Ok(section) } @@ -126,13 +128,12 @@ impl<'event> File<'event> { /// Adds the provided section to the config, returning a mutable reference /// to it for immediate editing. + /// Note that its meta-data will remain as is. pub fn push_section( &mut self, - section_name: impl Into>, - subsection_name: impl Into>>, - section: file::section::Body<'event>, + section: file::Section<'event>, ) -> Result, section::header::Error> { - Ok(self.push_section_internal(section::Header::new(section_name, subsection_name)?, section)) + Ok(self.push_section_internal(section)) } /// Renames a section, modifying the last matching section. diff --git a/git-config/src/file/access/read_only.rs b/git-config/src/file/access/read_only.rs index a2e65e5ded7..31177c18205 100644 --- a/git-config/src/file/access/read_only.rs +++ b/git-config/src/file/access/read_only.rs @@ -1,7 +1,9 @@ use std::{borrow::Cow, convert::TryFrom}; use bstr::BStr; +use git_features::threading::OwnShared; +use crate::file::Metadata; use crate::{file, lookup, File}; /// Read-only low-level access methods, as it requires generics for converting into @@ -198,4 +200,14 @@ impl<'event> File<'event> { pub fn is_void(&self) -> bool { self.sections.values().all(|s| s.body.is_void()) } + + /// Return the file's metadata to guide filtering of all values upon retrieval. + pub fn meta(&self) -> &Metadata { + &*self.meta + } + + /// Return the file's metadata to guide filtering of all values upon retrieval, wrapped for shared ownership. + pub fn meta_owned(&self) -> OwnShared { + OwnShared::clone(&self.meta) + } } diff --git a/git-config/src/file/impls.rs b/git-config/src/file/impls.rs index 69547162189..6909ce05e7a 100644 --- a/git-config/src/file/impls.rs +++ b/git-config/src/file/impls.rs @@ -2,13 +2,15 @@ use std::{convert::TryFrom, fmt::Display, str::FromStr}; use bstr::{BStr, BString}; -use crate::{file::section, parse, File}; +use crate::file::Metadata; +use crate::{parse, File}; impl FromStr for File<'static> { type Err = parse::Error; fn from_str(s: &str) -> Result { - parse::Events::from_bytes_owned(s.as_bytes(), None).map(File::from) + parse::Events::from_bytes_owned(s.as_bytes(), None) + .map(|events| File::from_parse_events(events, Metadata::api())) } } @@ -18,7 +20,7 @@ impl<'a> TryFrom<&'a str> for File<'a> { /// Convenience constructor. Attempts to parse the provided string into a /// [`File`]. See [`Events::from_str()`][crate::parse::Events::from_str()] for more information. fn try_from(s: &'a str) -> Result, Self::Error> { - parse::Events::from_str(s).map(Self::from) + parse::Events::from_str(s).map(|events| Self::from_parse_events(events, Metadata::api())) } } @@ -28,22 +30,7 @@ impl<'a> TryFrom<&'a BStr> for File<'a> { /// Convenience constructor. Attempts to parse the provided byte string into /// a [`File`]. See [`Events::from_bytes()`][parse::Events::from_bytes()] for more information. fn try_from(value: &'a BStr) -> Result, Self::Error> { - parse::Events::from_bytes(value).map(File::from) - } -} - -impl<'a> From> for File<'a> { - fn from(events: parse::Events<'a>) -> Self { - let mut this = File { - frontmatter_events: events.frontmatter, - ..Default::default() - }; - - for section in events.sections { - this.push_section_internal(section.section_header, section::Body(section.events)); - } - - this + parse::Events::from_bytes(value).map(|events| Self::from_parse_events(events, Metadata::api())) } } diff --git a/git-config/src/file/init/from_env.rs b/git-config/src/file/init/from_env.rs index 09ddc09af21..a0c07d3a896 100644 --- a/git-config/src/file/init/from_env.rs +++ b/git-config/src/file/init/from_env.rs @@ -1,11 +1,14 @@ +use git_features::threading::OwnShared; use std::convert::TryFrom; use std::{borrow::Cow, path::PathBuf}; +use crate::file::Metadata; use crate::{ + file, file::{from_paths, init::resolve_includes}, parse::section, path::interpolate, - File, + File, Source, }; /// Represents the errors that may occur when calling [`File::from_env`][crate::File::from_env()]. @@ -40,41 +43,69 @@ impl File<'static> { pub fn from_env_paths(options: from_paths::Options<'_>) -> Result, from_paths::Error> { use std::env; - let mut paths = vec![]; + let mut metas = vec![]; + let mut push_path = |path: PathBuf, source: Source, trust: Option| { + if let Some(meta) = trust + .or_else(|| git_sec::Trust::from_path_ownership(&path).ok()) + .map(|trust| Metadata { + path: Some(path), + trust, + level: 0, + source, + }) + { + metas.push(meta) + } + }; if env::var("GIT_CONFIG_NO_SYSTEM").is_err() { let git_config_system_path = env::var_os("GIT_CONFIG_SYSTEM").unwrap_or_else(|| "/etc/gitconfig".into()); - paths.push(PathBuf::from(git_config_system_path)); + push_path( + PathBuf::from(git_config_system_path), + Source::System, + git_sec::Trust::Full.into(), + ); } if let Some(git_config_global) = env::var_os("GIT_CONFIG_GLOBAL") { - paths.push(PathBuf::from(git_config_global)); + push_path( + PathBuf::from(git_config_global), + Source::Global, + git_sec::Trust::Full.into(), + ); } else { // Divergence from git-config(1) // These two are supposed to share the same scope and override // rather than append according to git-config(1) documentation. if let Some(xdg_config_home) = env::var_os("XDG_CONFIG_HOME") { - paths.push(PathBuf::from(xdg_config_home).join("git/config")); + push_path( + PathBuf::from(xdg_config_home).join("git/config"), + Source::User, + git_sec::Trust::Full.into(), + ); } else if let Some(home) = env::var_os("HOME") { - paths.push(PathBuf::from(home).join(".config/git/config")); + push_path( + PathBuf::from(home).join(".config/git/config"), + Source::User, + git_sec::Trust::Full.into(), + ); } if let Some(home) = env::var_os("HOME") { - paths.push(PathBuf::from(home).join(".gitconfig")); + push_path( + PathBuf::from(home).join(".gitconfig"), + Source::User, + git_sec::Trust::Full.into(), + ); } } + // TODO: remove this in favor of a clear non-local approach to integrate better with git-repository if let Some(git_dir) = env::var_os("GIT_DIR") { - paths.push(PathBuf::from(git_dir).join("config")); + push_path(PathBuf::from(git_dir).join("config"), Source::Local, None); } - // To support more platforms/configurations: - // Drop any possible config locations which aren't present to avoid - // `parser::parse_from_path` failing too early with "not found" before - // it reaches a path which _does_ exist. - let paths = paths.into_iter().filter(|p| p.exists()); - - File::from_paths(paths, options) + File::from_paths_metadata(metas, options) } /// Generates a config from the environment variables. This is neither @@ -93,7 +124,13 @@ impl File<'static> { return Ok(None); } - let mut config = File::default(); + let meta = OwnShared::new(file::Metadata { + path: None, + source: crate::Source::Env, + level: 0, + trust: git_sec::Trust::Full, + }); + let mut config = File::new(OwnShared::clone(&meta)); for i in 0..count { let key = env::var(format!("GIT_CONFIG_KEY_{}", i)).map_err(|_| Error::InvalidKeyId { key_id: i })?; let value = env::var_os(format!("GIT_CONFIG_VALUE_{}", i)).ok_or(Error::InvalidValueId { value_id: i })?; @@ -127,7 +164,7 @@ impl File<'static> { } let mut buf = Vec::new(); - resolve_includes(&mut config, None, &mut buf, options)?; + resolve_includes(&mut config, meta, &mut buf, options)?; Ok(Some(config)) } } diff --git a/git-config/src/file/init/from_paths.rs b/git-config/src/file/init/from_paths.rs index 64d173a89e5..fc5e413d405 100644 --- a/git-config/src/file/init/from_paths.rs +++ b/git-config/src/file/init/from_paths.rs @@ -1,6 +1,8 @@ +use crate::file::Metadata; use crate::{file, file::init::resolve_includes, parse, path::interpolate, File}; +use git_features::threading::OwnShared; -/// The error returned by [`File::from_paths()`][crate::File::from_paths()] and [`File::from_env_paths()`][crate::File::from_env_paths()]. +/// The error returned by [`File::from_paths_metadata()`] and [`File::from_env_paths()`]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { @@ -18,9 +20,11 @@ pub enum Error { MissingGitDir, #[error(transparent)] Realpath(#[from] git_path::realpath::Error), + #[error("Not a single path was provided to load the configuration from")] + NoInput, } -/// Options when loading git config using [`File::from_paths()`][crate::File::from_paths()]. +/// Options when loading git config using [`File::from_paths_metadata()`]. #[derive(Clone, Copy, Default)] pub struct Options<'a> { /// Configure how to follow includes while handling paths. @@ -29,31 +33,55 @@ pub struct Options<'a> { /// Instantiation from one or more paths impl File<'static> { + /// Load the file at `path` from `source` without following include directives. + pub fn from_path_no_includes(path: impl Into, source: crate::Source) -> Result { + let path = path.into(); + let trust = git_sec::Trust::from_path_ownership(&path)?; + let mut buf = Vec::new(); + File::from_path_with_buf(path, &mut buf, Metadata::from(source).with(trust), Default::default()) + } + /// Open a single configuration file by reading all data at `path` into `buf` and - /// copying all contents from there, without resolving includes. - pub fn from_path_with_buf(path: &std::path::Path, buf: &mut Vec) -> Result { + /// copying all contents from there, without resolving includes. Note that the `path` in `meta` + /// will be set to the one provided here. + pub fn from_path_with_buf( + path: impl Into, + buf: &mut Vec, + mut meta: file::Metadata, + options: Options<'_>, + ) -> Result { + let path = path.into(); buf.clear(); - std::io::copy(&mut std::fs::File::open(path)?, buf)?; - Self::from_bytes(buf) + std::io::copy(&mut std::fs::File::open(&path)?, buf)?; + + meta.path = path.clone().into(); + let meta = OwnShared::new(meta); + let mut config = Self::from_parse_events(parse::Events::from_bytes_owned(buf, None)?, OwnShared::clone(&meta)); + let mut buf = Vec::new(); + resolve_includes(&mut config, meta, &mut buf, options.resolve_includes)?; + + Ok(config) } - /// Constructs a `git-config` file from the provided paths in the order provided. - pub fn from_paths( - paths: impl IntoIterator>, + /// Constructs a `git-config` file from the provided metadata, which must include a path to read from or be ignored. + pub fn from_paths_metadata( + path_meta: impl IntoIterator>, options: Options<'_>, ) -> Result { - let mut target = Self::default(); + let mut target = None; let mut buf = Vec::with_capacity(512); - for path in paths { - let path = path.as_ref(); - let mut config = Self::from_path_with_buf(path, &mut buf)?; - resolve_includes(&mut config, Some(path), &mut buf, options.resolve_includes)?; - target.append(config); + for (path, meta) in path_meta.into_iter().filter_map(|meta| { + let mut meta = meta.into(); + meta.path.take().map(|p| (p, meta)) + }) { + let config = Self::from_path_with_buf(path, &mut buf, meta, options)?; + match &mut target { + None => { + target = Some(config); + } + Some(target) => target.append(config), + } } - Ok(target) - } - - pub(crate) fn from_bytes(input: &[u8]) -> Result { - Ok(parse::Events::from_bytes_owned(input, None)?.into()) + Ok(target.ok_or(Error::NoInput)?) } } diff --git a/git-config/src/file/init/mod.rs b/git-config/src/file/init/mod.rs index 76024ca5e73..e5c2aedd466 100644 --- a/git-config/src/file/init/mod.rs +++ b/git-config/src/file/init/mod.rs @@ -1,3 +1,7 @@ +use crate::file::{section, Metadata}; +use crate::{parse, File}; +use git_features::threading::OwnShared; + /// pub mod from_env; /// @@ -5,3 +9,40 @@ pub mod from_paths; mod resolve_includes; pub(crate) use resolve_includes::resolve_includes; + +impl<'a> File<'a> { + /// Return an empty `File` with the given `meta`-data to be attached to all new sections. + pub fn new(meta: impl Into>) -> Self { + Self { + frontmatter_events: Default::default(), + section_lookup_tree: Default::default(), + sections: Default::default(), + section_id_counter: 0, + section_order: Default::default(), + meta: meta.into(), + } + } + /// Instantiate a new `File` from given `events`, associating each section and their values with + /// `meta`-data. + /// + /// That way, one can search for values fulfilling a particular requirements. + pub fn from_parse_events( + parse::Events { frontmatter, sections }: parse::Events<'a>, + meta: impl Into>, + ) -> Self { + let meta = meta.into(); + let mut this = File::new(OwnShared::clone(&meta)); + + this.frontmatter_events = frontmatter; + + for section in sections { + this.push_section_internal(crate::file::Section { + header: section.section_header, + body: section::Body(section.events), + meta: OwnShared::clone(&meta), + }); + } + + this + } +} diff --git a/git-config/src/file/init/resolve_includes.rs b/git-config/src/file/init/resolve_includes.rs index b0a4c063d00..ecaae146649 100644 --- a/git-config/src/file/init/resolve_includes.rs +++ b/git-config/src/file/init/resolve_includes.rs @@ -4,9 +4,11 @@ use std::{ }; use bstr::{BStr, BString, ByteSlice, ByteVec}; +use git_features::threading::OwnShared; use git_ref::Category; use crate::file::resolve_includes::{conditional, Options}; +use crate::file::Metadata; use crate::{ file::{init::from_paths, SectionId}, File, @@ -14,16 +16,16 @@ use crate::{ pub(crate) fn resolve_includes( conf: &mut File<'static>, - config_path: Option<&std::path::Path>, + meta: OwnShared, buf: &mut Vec, options: Options<'_>, ) -> Result<(), from_paths::Error> { - resolve_includes_recursive(conf, config_path, 0, buf, options) + resolve_includes_recursive(conf, meta, 0, buf, options) } fn resolve_includes_recursive( target_config: &mut File<'static>, - target_config_path: Option<&Path>, + meta: OwnShared, depth: u8, buf: &mut Vec, options: Options<'_>, @@ -39,6 +41,7 @@ fn resolve_includes_recursive( } let mut paths_to_include = Vec::new(); + let target_config_path = meta.path.as_deref(); let mut incl_section_ids = Vec::new(); for name in ["include", "includeIf"] { @@ -82,8 +85,16 @@ fn resolve_includes_recursive( } for config_path in paths_to_include { - let mut include_config = File::from_path_with_buf(&config_path, buf)?; - resolve_includes_recursive(&mut include_config, Some(&config_path), depth + 1, buf, options)?; + let config_meta = Metadata { + path: None, + trust: meta.trust, + level: meta.level + 1, + source: meta.source, + }; + let no_follow_options = from_paths::Options::default(); + let mut include_config = File::from_path_with_buf(config_path, buf, config_meta, no_follow_options)?; + let config_meta = include_config.meta_owned(); + resolve_includes_recursive(&mut include_config, config_meta, depth + 1, buf, options)?; target_config.append(include_config); } Ok(()) diff --git a/git-config/src/file/meta.rs b/git-config/src/file/meta.rs new file mode 100644 index 00000000000..8b698ced6ad --- /dev/null +++ b/git-config/src/file/meta.rs @@ -0,0 +1,54 @@ +use std::path::PathBuf; + +use crate::file::Metadata; +use crate::{file, Source}; + +/// Instantiation +impl Metadata { + /// Return metadata indicating the source of a [`File`][crate::File] is from an API user. + pub fn api() -> Self { + file::Metadata { + path: None, + source: Source::Api, + level: 0, + trust: git_sec::Trust::Full, + } + } + + /// Return metadata as derived from the given `path` at `source`, which will also be used to derive the trust level + /// by checking its ownership. + pub fn try_from_path(path: impl Into, source: Source) -> std::io::Result { + let path = path.into(); + git_sec::Trust::from_path_ownership(&path).map(|trust| Metadata { + path: path.into(), + source, + level: 0, + trust, + }) + } + + /// Set the trust level of this instance to the given `trust` and return it. + /// + /// Useful in conjunction with `Metadata::from(source)`. + pub fn with(mut self, trust: git_sec::Trust) -> Self { + self.trust = trust; + self + } +} + +impl Default for Metadata { + fn default() -> Self { + Metadata::api() + } +} + +impl From for Metadata { + fn from(source: Source) -> Self { + file::Metadata { + path: None, + source, + level: 0, + trust: git_sec::Trust::Full, + } + } +} diff --git a/git-config/src/file/mod.rs b/git-config/src/file/mod.rs index 41471cec74f..6839e712173 100644 --- a/git-config/src/file/mod.rs +++ b/git-config/src/file/mod.rs @@ -1,4 +1,5 @@ //! A high level wrapper around a single or multiple `git-config` file, for reading and mutation. +use std::path::PathBuf; use std::{ borrow::Cow, collections::HashMap, @@ -6,6 +7,7 @@ use std::{ }; use bstr::BStr; +use git_features::threading::OwnShared; mod mutable; @@ -16,92 +18,14 @@ pub use init::{from_env, from_paths}; mod access; mod impls; +mod meta; mod utils; /// pub mod section; /// -pub mod resolve_includes { - /// Options to handle includes, like `include.path` or `includeIf..path`, - #[derive(Clone, Copy)] - pub struct Options<'a> { - /// The maximum allowed length of the file include chain built by following nested resolve_includes where base level is depth = 0. - pub max_depth: u8, - /// When max depth is exceeded while following nested includes, - /// return an error if true or silently stop following resolve_includes. - /// - /// Setting this value to false allows to read configuration with cycles, - /// which otherwise always results in an error. - pub error_on_max_depth_exceeded: bool, - - /// Used during path interpolation, both for include paths before trying to read the file, and for - /// paths used in conditional `gitdir` includes. - pub interpolate: crate::path::interpolate::Context<'a>, - - /// Additional context for conditional includes to work. - pub conditional: conditional::Context<'a>, - } - - impl Options<'_> { - /// Provide options to never follow include directives at all. - pub fn no_follow() -> Self { - Options { - max_depth: 0, - error_on_max_depth_exceeded: false, - interpolate: Default::default(), - conditional: Default::default(), - } - } - } - - impl<'a> Options<'a> { - /// Provide options to follow includes like git does, provided the required `conditional` and `interpolate` contexts - /// to support `gitdir` and `onbranch` based `includeIf` directives as well as standard `include.path` resolution. - /// Note that the follow-mode is `git`-style, following at most 10 indirections while - /// producing an error if the depth is exceeded. - pub fn follow( - interpolate: crate::path::interpolate::Context<'a>, - conditional: conditional::Context<'a>, - ) -> Self { - Options { - max_depth: 10, - error_on_max_depth_exceeded: true, - interpolate, - conditional, - } - } - - /// Set the context used for interpolation when interpolating paths to include as well as the paths - /// in `gitdir` conditional includes. - pub fn interpolate_with(mut self, context: crate::path::interpolate::Context<'a>) -> Self { - self.interpolate = context; - self - } - } - - impl Default for Options<'_> { - fn default() -> Self { - Self::no_follow() - } - } - - /// - pub mod conditional { - /// Options to handle conditional includes like `includeIf..path`. - #[derive(Clone, Copy, Default)] - pub struct Context<'a> { - /// The location of the .git directory. If `None`, `gitdir` conditions cause an error. - /// - /// Used for conditional includes, e.g. `includeIf.gitdir:…` or `includeIf:gitdir/i…`. - pub git_dir: Option<&'a std::path::Path>, - /// The name of the branch that is currently checked out. If `None`, `onbranch` conditions cause an error. - /// - /// Used for conditional includes, e.g. `includeIf.onbranch:main.…` - pub branch_name: Option<&'a git_ref::FullNameRef>, - } - } -} +pub mod resolve_includes; /// pub mod rename_section { @@ -116,11 +40,27 @@ pub mod rename_section { } } +/// Additional information about a section. +#[derive(Clone, Debug, PartialOrd, PartialEq, Ord, Eq, Hash)] +pub struct Metadata { + /// The file path of the source, if known. + pub path: Option, + /// Where the section is coming from. + pub source: crate::Source, + /// The levels of indirection of the file, with 0 being a section + /// that was directly loaded, and 1 being an `include.path` of a + /// level 0 file. + pub level: u8, + /// The trust-level for the section this meta-data is associated with. + pub trust: git_sec::Trust, +} + /// A section in a git-config file, like `[core]` or `[remote "origin"]`, along with all of its keys. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] pub struct Section<'a> { header: crate::parse::section::Header<'a>, body: section::Body<'a>, + meta: OwnShared, } /// A strongly typed index into some range. diff --git a/git-config/src/file/resolve_includes.rs b/git-config/src/file/resolve_includes.rs new file mode 100644 index 00000000000..25bc68534ca --- /dev/null +++ b/git-config/src/file/resolve_includes.rs @@ -0,0 +1,75 @@ +/// Options to handle includes, like `include.path` or `includeIf..path`, +#[derive(Clone, Copy)] +pub struct Options<'a> { + /// The maximum allowed length of the file include chain built by following nested resolve_includes where base level is depth = 0. + pub max_depth: u8, + /// When max depth is exceeded while following nested includes, + /// return an error if true or silently stop following resolve_includes. + /// + /// Setting this value to false allows to read configuration with cycles, + /// which otherwise always results in an error. + pub error_on_max_depth_exceeded: bool, + + /// Used during path interpolation, both for include paths before trying to read the file, and for + /// paths used in conditional `gitdir` includes. + pub interpolate: crate::path::interpolate::Context<'a>, + + /// Additional context for conditional includes to work. + pub conditional: conditional::Context<'a>, +} + +impl Options<'_> { + /// Provide options to never follow include directives at all. + pub fn no_follow() -> Self { + Options { + max_depth: 0, + error_on_max_depth_exceeded: false, + interpolate: Default::default(), + conditional: Default::default(), + } + } +} + +impl<'a> Options<'a> { + /// Provide options to follow includes like git does, provided the required `conditional` and `interpolate` contexts + /// to support `gitdir` and `onbranch` based `includeIf` directives as well as standard `include.path` resolution. + /// Note that the follow-mode is `git`-style, following at most 10 indirections while + /// producing an error if the depth is exceeded. + pub fn follow(interpolate: crate::path::interpolate::Context<'a>, conditional: conditional::Context<'a>) -> Self { + Options { + max_depth: 10, + error_on_max_depth_exceeded: true, + interpolate, + conditional, + } + } + + /// Set the context used for interpolation when interpolating paths to include as well as the paths + /// in `gitdir` conditional includes. + pub fn interpolate_with(mut self, context: crate::path::interpolate::Context<'a>) -> Self { + self.interpolate = context; + self + } +} + +impl Default for Options<'_> { + fn default() -> Self { + Self::no_follow() + } +} + +/// +pub mod conditional { + /// Options to handle conditional includes like `includeIf..path`. + #[derive(Clone, Copy, Default)] + pub struct Context<'a> { + /// The location of the .git directory. If `None`, `gitdir` conditions cause an error. + /// + /// Used for conditional includes, e.g. `includeIf.gitdir:…` or `includeIf:gitdir/i…`. + pub git_dir: Option<&'a std::path::Path>, + /// The name of the branch that is currently checked out. If `None`, `onbranch` conditions cause an error. + /// + /// Used for conditional includes, e.g. `includeIf.onbranch:main.…` + pub branch_name: Option<&'a git_ref::FullNameRef>, + } +} diff --git a/git-config/src/file/section/body.rs b/git-config/src/file/section/body.rs index 73d6e1f59bd..fa8460685d0 100644 --- a/git-config/src/file/section/body.rs +++ b/git-config/src/file/section/body.rs @@ -142,7 +142,7 @@ impl<'event> Body<'event> { } } -/// An owning iterator of a section body. Created by [`SectionBody::into_iter`], yielding +/// An owning iterator of a section body. Created by [`Body::into_iter`], yielding /// un-normalized (`key`, `value`) pairs. // TODO: tests pub struct BodyIter<'event>(smallvec::IntoIter<[Event<'event>; 64]>); diff --git a/git-config/src/file/section/mod.rs b/git-config/src/file/section/mod.rs index 69adf5a137e..2e434630166 100644 --- a/git-config/src/file/section/mod.rs +++ b/git-config/src/file/section/mod.rs @@ -1,22 +1,13 @@ -use crate::file::Section; +use crate::file::{Section, SectionMut}; use crate::parse::section; -use crate::Source; +use crate::{file, parse}; use bstr::BString; +use std::borrow::Cow; use std::ops::Deref; -use std::path::PathBuf; - -/// Additional information about a section. -#[derive(Clone, Debug, PartialOrd, PartialEq, Ord, Eq, Hash)] -pub struct Metadata { - /// The file path of the source, if known. - pub path: Option, - /// Where the section is coming from. - pub source: Source, - /// The levels of indirection of the file, with 0 being a section - /// that was directly loaded, and 1 being an `include.path` of a - /// level 0 file. - pub level: u8, -} + +pub(crate) mod body; +pub use body::{Body, BodyIter}; +use git_features::threading::OwnShared; impl<'a> Deref for Section<'a> { type Target = Body<'a>; @@ -26,6 +17,23 @@ impl<'a> Deref for Section<'a> { } } +/// Instantiation and conversion +impl<'a> Section<'a> { + /// Create a new section with the given `name` and optional, `subsection`, `meta`-data and an empty body. + pub fn new( + name: impl Into>, + subsection: impl Into>>, + meta: impl Into>, + ) -> Result { + Ok(Section { + header: parse::section::Header::new(name, subsection)?, + body: Default::default(), + meta: meta.into(), + }) + } +} + +/// Access impl<'a> Section<'a> { /// Return our header. pub fn header(&self) -> §ion::Header<'a> { @@ -56,7 +64,9 @@ impl<'a> Section<'a> { } Ok(()) } -} -pub(crate) mod body; -pub use body::{Body, BodyIter}; + /// Returns a mutable version of this section for adjustment of values. + pub fn to_mut(&mut self) -> SectionMut<'_, 'a> { + SectionMut::new(self) + } +} diff --git a/git-config/src/file/utils.rs b/git-config/src/file/utils.rs index 0df7e884bef..d4a092a39cf 100644 --- a/git-config/src/file/utils.rs +++ b/git-config/src/file/utils.rs @@ -2,7 +2,6 @@ use std::collections::HashMap; use bstr::BStr; -use crate::file::Section; use crate::{ file::{self, SectionBodyIds, SectionId, SectionMut}, lookup, @@ -13,23 +12,14 @@ use crate::{ /// Private helper functions impl<'event> File<'event> { /// Adds a new section to the config file. - pub(crate) fn push_section_internal( - &mut self, - header: section::Header<'event>, - body: file::section::Body<'event>, - ) -> SectionMut<'_, 'event> { + pub(crate) fn push_section_internal(&mut self, section: file::Section<'event>) -> SectionMut<'_, 'event> { let new_section_id = SectionId(self.section_id_counter); - self.sections.insert( - new_section_id, - Section { - body, - header: header.clone(), - }, - ); - let lookup = self.section_lookup_tree.entry(header.name).or_default(); + self.sections.insert(new_section_id, section); + let header = &self.sections[&new_section_id].header; + let lookup = self.section_lookup_tree.entry(header.name.clone()).or_default(); let mut found_node = false; - if let Some(subsection_name) = header.subsection_name { + if let Some(subsection_name) = header.subsection_name.clone() { for node in lookup.iter_mut() { if let SectionBodyIds::NonTerminal(subsections) = node { found_node = true; @@ -123,9 +113,10 @@ impl<'event> File<'event> { // TODO: add note indicating that probably a lot if not all information about the original files is currently lost, // so can't be written back. This will probably change a lot during refactor, so it's not too important now. pub(crate) fn append(&mut self, mut other: Self) { + // TODO: don't loose the front-matter here. Not doing so means we know after which section it needs to be inserted, complicating things. for id in std::mem::take(&mut other.section_order) { let section = other.sections.remove(&id).expect("present"); - self.push_section_internal(section.header, section.body); + self.push_section_internal(section); } } } diff --git a/git-config/src/parse/mod.rs b/git-config/src/parse/mod.rs index a48f8eb9da6..380c878c395 100644 --- a/git-config/src/parse/mod.rs +++ b/git-config/src/parse/mod.rs @@ -85,7 +85,7 @@ pub enum Event<'a> { /// A parsed section containing the header and the section events, typically /// comprising the keys and their values. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] pub struct Section<'a> { /// The section name and subsection name, if any. pub section_header: section::Header<'a>, diff --git a/git-config/src/parse/section/mod.rs b/git-config/src/parse/section/mod.rs index c4618c951bb..aa4b5fedeeb 100644 --- a/git-config/src/parse/section/mod.rs +++ b/git-config/src/parse/section/mod.rs @@ -12,7 +12,7 @@ pub mod header; pub type Events<'a> = SmallVec<[Event<'a>; 64]>; /// A parsed section header, containing a name and optionally a subsection name. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] pub struct Header<'a> { /// The name of the header. pub(crate) name: Name<'a>, diff --git a/git-config/src/types.rs b/git-config/src/types.rs index cfb8fbd65d9..1f637b68069 100644 --- a/git-config/src/types.rs +++ b/git-config/src/types.rs @@ -1,5 +1,7 @@ +use git_features::threading::OwnShared; use std::collections::{HashMap, VecDeque}; +use crate::file::Metadata; use crate::{ color, file, file::{SectionBodyIds, SectionId}, @@ -31,6 +33,8 @@ pub enum Source { Env, /// Values set from the command-line. Cli, + /// Entirely internal from a programmatic source + Api, } /// High level `git-config` reader and writer. @@ -89,6 +93,8 @@ pub struct File<'event> { pub(crate) section_id_counter: usize, /// Section order for output ordering. pub(crate) section_order: VecDeque, + /// The source of the File itself, which is attached to new sections automatically. + pub(crate) meta: OwnShared, } /// Any value that may contain a foreground color, background color, a diff --git a/git-config/tests/file/access/read_only.rs b/git-config/tests/file/access/read_only.rs index 86b519a42ba..cd50c858f5f 100644 --- a/git-config/tests/file/access/read_only.rs +++ b/git-config/tests/file/access/read_only.rs @@ -1,3 +1,4 @@ +use std::str::FromStr; use std::{borrow::Cow, convert::TryFrom, error::Error}; use bstr::BStr; @@ -24,7 +25,7 @@ fn get_value_for_all_provided_values() -> crate::Result { location-quoted = "~/quoted" "#; - let config = git_config::parse::Events::from_bytes_owned(config.as_bytes(), None).map(File::from)?; + let config = File::from_str(config)?; assert!(!config.value::("core", None, "bool-explicit")?.0); assert!(!config.boolean("core", None, "bool-explicit").expect("exists")?); diff --git a/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs b/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs index 6bcdeac033c..29faca7dc17 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs @@ -128,7 +128,12 @@ pub fn assert_section_value( paths.push(env.home_dir().join(".gitconfig")); } - let config = git_config::File::from_paths(paths, env.to_from_paths_options())?; + let config = git_config::File::from_paths_metadata( + paths + .into_iter() + .map(|path| git_config::file::Metadata::try_from_path(path, git_config::Source::Local).unwrap()), + env.to_from_paths_options(), + )?; assert_eq!( config.string("section", None, "value"), diff --git a/git-config/tests/file/init/from_paths/includes/conditional/mod.rs b/git-config/tests/file/init/from_paths/includes/conditional/mod.rs index 9fe43cde8f2..cb50999cb97 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/mod.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/mod.rs @@ -10,8 +10,8 @@ mod gitdir; mod onbranch; #[test] -fn include_and_includeif_correct_inclusion_order() { - let dir = tempdir().unwrap(); +fn include_and_includeif_correct_inclusion_order() -> crate::Result { + let dir = tempdir()?; let config_path = dir.path().join("p"); let first_include_path = dir.path().join("first-incl"); let second_include_path = dir.path().join("second-incl"); @@ -21,24 +21,21 @@ fn include_and_includeif_correct_inclusion_order() { " [core] b = first-incl-path", - ) - .unwrap(); + )?; fs::write( second_include_path.as_path(), " [core] b = second-incl-path", - ) - .unwrap(); + )?; fs::write( include_if_path.as_path(), " [core] b = incl-if-path", - ) - .unwrap(); + )?; fs::write( config_path.as_path(), @@ -55,11 +52,16 @@ fn include_and_includeif_correct_inclusion_order() { escape_backslashes(&include_if_path), escape_backslashes(&second_include_path), ), - ) - .unwrap(); + )?; let dir = config_path.join(".git"); - let config = File::from_paths(Some(&config_path), options_with_git_dir(&dir)).unwrap(); + let config = File::from_paths_metadata( + Some(git_config::file::Metadata::try_from_path( + &config_path, + git_config::Source::Api, + )?), + options_with_git_dir(&dir), + )?; assert_eq!( config.strings("core", None, "b"), @@ -75,6 +77,7 @@ fn include_and_includeif_correct_inclusion_order() { Some(cow_str("second-incl-path")), "second include is matched after incl-if", ); + Ok(()) } fn options_with_git_dir(git_dir: &Path) -> from_paths::Options<'_> { diff --git a/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs b/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs index 63bad430cfc..da77440beb1 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs @@ -244,7 +244,13 @@ value = branch-override-by-include ), }; - let config = git_config::File::from_paths(Some(&root_config), options)?; + let config = git_config::File::from_paths_metadata( + Some(git_config::file::Metadata::try_from_path( + &root_config, + git_config::Source::Local, + )?), + options, + )?; assert_eq!( config.string("section", None, "value"), Some(cow_str(match expect { diff --git a/git-config/tests/file/init/from_paths/includes/unconditional.rs b/git-config/tests/file/init/from_paths/includes/unconditional.rs index 5ac8d1ca405..41c9a348422 100644 --- a/git-config/tests/file/init/from_paths/includes/unconditional.rs +++ b/git-config/tests/file/init/from_paths/includes/unconditional.rs @@ -4,6 +4,7 @@ use git_config::file::resolve_includes; use git_config::{file::from_paths, File}; use tempfile::tempdir; +use crate::file::init::from_paths::into_meta; use crate::file::{cow_str, init::from_paths::escape_backslashes}; fn follow_options() -> from_paths::Options<'static> { @@ -68,7 +69,7 @@ fn multiple() -> crate::Result { ), )?; - let config = File::from_paths(vec![c_path], follow_options())?; + let config = File::from_paths_metadata(into_meta(vec![c_path]), follow_options())?; assert_eq!(config.string("core", None, "c"), Some(cow_str("12"))); assert_eq!(config.integer("core", None, "d"), Some(Ok(41))); @@ -113,7 +114,7 @@ fn respect_max_depth() -> crate::Result { .replace("{}", &max_depth.to_string()), )?; - let config = File::from_paths(vec![dir.path().join("0")], follow_options())?; + let config = File::from_paths_metadata(into_meta(vec![dir.path().join("0")]), follow_options())?; assert_eq!(config.integers("core", None, "i"), Some(Ok(vec![0, 1, 2, 3, 4]))); fn make_options(max_depth: u8, error_on_max_depth_exceeded: bool) -> from_paths::Options<'static> { @@ -129,24 +130,24 @@ fn respect_max_depth() -> crate::Result { // with max_allowed_depth of 1 and 4 levels of includes and error_on_max_depth_exceeded: false, max_allowed_depth is exceeded and the value of level 1 is returned // this is equivalent to running git with --no-includes option let options = make_options(1, false); - let config = File::from_paths(vec![dir.path().join("0")], options)?; + let config = File::from_paths_metadata(into_meta(vec![dir.path().join("0")]), options)?; assert_eq!(config.integer("core", None, "i"), Some(Ok(1))); // with default max_allowed_depth of 10 and 4 levels of includes, last level is read let options = from_paths::Options { resolve_includes: resolve_includes::Options::follow(Default::default(), Default::default()), }; - let config = File::from_paths(vec![dir.path().join("0")], options)?; + let config = File::from_paths_metadata(into_meta(vec![dir.path().join("0")]), options)?; assert_eq!(config.integer("core", None, "i"), Some(Ok(4))); // with max_allowed_depth of 5, the base and 4 levels of includes, last level is read let options = make_options(5, false); - let config = File::from_paths(vec![dir.path().join("0")], options)?; + let config = File::from_paths_metadata(into_meta(vec![dir.path().join("0")]), options)?; assert_eq!(config.integer("core", None, "i"), Some(Ok(4))); // with max_allowed_depth of 2 and 4 levels of includes, max_allowed_depth is exceeded and error is returned let options = make_options(2, true); - let config = File::from_paths(vec![dir.path().join("0")], options); + let config = File::from_paths_metadata(into_meta(vec![dir.path().join("0")]), options); assert!(matches!( config.unwrap_err(), from_paths::Error::IncludeDepthExceeded { max_depth: 2 } @@ -154,12 +155,12 @@ fn respect_max_depth() -> crate::Result { // with max_allowed_depth of 2 and 4 levels of includes and error_on_max_depth_exceeded: false , max_allowed_depth is exceeded and the value of level 2 is returned let options = make_options(2, false); - let config = File::from_paths(vec![dir.path().join("0")], options)?; + let config = File::from_paths_metadata(into_meta(vec![dir.path().join("0")]), options)?; assert_eq!(config.integer("core", None, "i"), Some(Ok(2))); // with max_allowed_depth of 0 and 4 levels of includes, max_allowed_depth is exceeded and error is returned let options = make_options(0, true); - let config = File::from_paths(vec![dir.path().join("0")], options); + let config = File::from_paths_metadata(into_meta(vec![dir.path().join("0")]), options); assert!(matches!( config.unwrap_err(), from_paths::Error::IncludeDepthExceeded { max_depth: 0 } @@ -200,7 +201,7 @@ fn simple() { ) .unwrap(); - let config = File::from_paths(vec![a_path], follow_options()).unwrap(); + let config = File::from_paths_metadata(into_meta(vec![a_path]), follow_options()).unwrap(); assert_eq!(config.boolean("core", None, "b"), Some(Ok(false))); } @@ -242,7 +243,7 @@ fn cycle_detection() -> crate::Result { ..Default::default() }, }; - let config = File::from_paths(vec![a_path.clone()], options); + let config = File::from_paths_metadata(into_meta(vec![a_path.clone()]), options); assert!(matches!( config.unwrap_err(), from_paths::Error::IncludeDepthExceeded { max_depth: 4 } @@ -255,7 +256,7 @@ fn cycle_detection() -> crate::Result { ..Default::default() }, }; - let config = File::from_paths(vec![a_path], options)?; + let config = File::from_paths_metadata(into_meta(vec![a_path]), options)?; assert_eq!(config.integers("core", None, "b"), Some(Ok(vec![0, 1, 0, 1, 0]))); Ok(()) } @@ -299,7 +300,7 @@ fn nested() -> crate::Result { ), )?; - let config = File::from_paths(vec![c_path], follow_options())?; + let config = File::from_paths_metadata(into_meta(vec![c_path]), follow_options())?; assert_eq!(config.integer("core", None, "c"), Some(Ok(1))); assert_eq!(config.boolean("core", None, "b"), Some(Ok(true))); diff --git a/git-config/tests/file/init/from_paths/mod.rs b/git-config/tests/file/init/from_paths/mod.rs index b2b8d24fb78..70f481a7feb 100644 --- a/git-config/tests/file/init/from_paths/mod.rs +++ b/git-config/tests/file/init/from_paths/mod.rs @@ -1,3 +1,4 @@ +use std::path::PathBuf; use std::{borrow::Cow, fs, io}; use git_config::File; @@ -16,7 +17,14 @@ fn file_not_found() { let config_path = dir.path().join("config"); let paths = vec![config_path]; - let err = File::from_paths(paths, Default::default()).unwrap_err(); + let err = File::from_paths_metadata( + paths.into_iter().map(|p| git_config::file::Metadata { + path: Some(p), + ..Default::default() + }), + Default::default(), + ) + .unwrap_err(); assert!( matches!(err, git_config::file::from_paths::Error::Io(io_error) if io_error.kind() == io::ErrorKind::NotFound) ); @@ -29,7 +37,7 @@ fn single_path() { fs::write(config_path.as_path(), b"[core]\nboolean = true").unwrap(); let paths = vec![config_path]; - let config = File::from_paths(paths, Default::default()).unwrap(); + let config = File::from_paths_metadata(into_meta(paths), Default::default()).unwrap(); assert_eq!( config.raw_value("core", None, "boolean").unwrap(), @@ -56,7 +64,7 @@ fn multiple_paths_single_value() -> crate::Result { fs::write(d_path.as_path(), b"[core]\na = false")?; let paths = vec![a_path, b_path, c_path, d_path]; - let config = File::from_paths(paths, Default::default())?; + let config = File::from_paths_metadata(into_meta(paths), Default::default())?; assert_eq!(config.boolean("core", None, "a"), Some(Ok(false))); assert_eq!(config.boolean("core", None, "b"), Some(Ok(true))); @@ -86,7 +94,7 @@ fn multiple_paths_multi_value() -> crate::Result { fs::write(e_path.as_path(), b"[include]\npath = e_path")?; let paths = vec![a_path, b_path, c_path, d_path, e_path]; - let config = File::from_paths(paths, Default::default())?; + let config = File::from_paths_metadata(into_meta(paths), Default::default())?; assert_eq!( config.strings("core", None, "key"), @@ -102,6 +110,12 @@ fn multiple_paths_multi_value() -> crate::Result { Ok(()) } +fn into_meta(paths: impl IntoIterator) -> impl IntoIterator { + paths + .into_iter() + .map(|p| git_config::file::Metadata::try_from_path(p, git_config::Source::Local).unwrap()) +} + mod includes { mod conditional; mod unconditional; diff --git a/git-config/tests/file/mod.rs b/git-config/tests/file/mod.rs index 6c9b93db19f..888bbf2fe8f 100644 --- a/git-config/tests/file/mod.rs +++ b/git-config/tests/file/mod.rs @@ -10,7 +10,7 @@ pub fn cow_str(s: &str) -> Cow<'_, BStr> { fn size_in_memory() { assert_eq!( std::mem::size_of::>(), - 984, + 992, "This shouldn't change without us noticing" ); } @@ -22,7 +22,13 @@ mod open { #[test] fn parse_config_with_windows_line_endings_successfully() { let mut buf = Vec::new(); - File::from_path_with_buf(&fixture_path("repo-config.crlf"), &mut buf).unwrap(); + File::from_path_with_buf( + &fixture_path("repo-config.crlf"), + &mut buf, + Default::default(), + Default::default(), + ) + .unwrap(); } } diff --git a/git-repository/src/config.rs b/git-repository/src/config.rs index c87e55356ef..6fb9274c35c 100644 --- a/git-repository/src/config.rs +++ b/git-repository/src/config.rs @@ -67,7 +67,24 @@ mod cache { // like git here: https://github.com/git/git/blob/master/config.c#L208:L208 let config = { let mut buf = Vec::with_capacity(512); - File::from_path_with_buf(&git_dir.join("config"), &mut buf)? + File::from_path_with_buf( + &git_dir.join("config"), + &mut buf, + git_config::file::Metadata::from(git_config::Source::Local), + git_config::file::from_paths::Options { + resolve_includes: git_config::file::resolve_includes::Options::follow( + git_config::path::interpolate::Context { + git_install_dir, + home_dir: None, + home_for_user: None, // TODO: figure out how to configure this + }, + git_config::file::resolve_includes::conditional::Context { + git_dir: git_dir.into(), + branch_name: None, + }, + ), + }, + )? }; let is_bare = config_bool(&config, "core.bare", false)?; diff --git a/gitoxide-core/src/organize.rs b/gitoxide-core/src/organize.rs index d60f6cbe883..330c69880bc 100644 --- a/gitoxide-core/src/organize.rs +++ b/gitoxide-core/src/organize.rs @@ -101,8 +101,9 @@ where fn find_origin_remote(repo: &Path) -> anyhow::Result> { let non_bare = repo.join(".git").join("config"); - let config = File::from_path_with_buf(non_bare.as_path(), &mut Vec::new()) - .or_else(|_| File::from_path_with_buf(repo.join("config").as_path(), &mut Vec::new()))?; + let local = git_config::Source::Local; + let config = File::from_path_no_includes(non_bare.as_path(), local) + .or_else(|_| File::from_path_no_includes(repo.join("config").as_path(), local))?; Ok(config .string("remote", Some("origin"), "url") .map(|url| git_url::Url::from_bytes(url.as_ref())) From 00bfbca21e2361008c2e81b54424a9c6f09e76e9 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 16 Jul 2022 18:29:32 +0800 Subject: [PATCH 220/366] thanks clippy --- git-config/src/file/init/from_paths.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git-config/src/file/init/from_paths.rs b/git-config/src/file/init/from_paths.rs index fc5e413d405..e20c4bead37 100644 --- a/git-config/src/file/init/from_paths.rs +++ b/git-config/src/file/init/from_paths.rs @@ -54,7 +54,7 @@ impl File<'static> { buf.clear(); std::io::copy(&mut std::fs::File::open(&path)?, buf)?; - meta.path = path.clone().into(); + meta.path = path.into(); let meta = OwnShared::new(meta); let mut config = Self::from_parse_events(parse::Events::from_bytes_owned(buf, None)?, OwnShared::clone(&meta)); let mut buf = Vec::new(); @@ -82,6 +82,6 @@ impl File<'static> { Some(target) => target.append(config), } } - Ok(target.ok_or(Error::NoInput)?) + target.ok_or(Error::NoInput) } } From 6f97bf0c3e7164855cf5aa53462dbc39c430e03f Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 16 Jul 2022 20:33:23 +0800 Subject: [PATCH 221/366] feat: `File::sections()` to obtain an iterator over all sections, in order. (#331) --- git-config/src/file/access/read_only.rs | 5 ++ git-config/src/file/init/resolve_includes.rs | 71 +++++++------------- git-config/tests/file/init/from_paths/mod.rs | 60 +++++++---------- 3 files changed, 54 insertions(+), 82 deletions(-) diff --git a/git-config/src/file/access/read_only.rs b/git-config/src/file/access/read_only.rs index 31177c18205..19aa0e4d7ef 100644 --- a/git-config/src/file/access/read_only.rs +++ b/git-config/src/file/access/read_only.rs @@ -210,4 +210,9 @@ impl<'event> File<'event> { pub fn meta_owned(&self) -> OwnShared { OwnShared::clone(&self.meta) } + + /// Return an iterator over all sections, in order of occurrence in the file itself. + pub fn sections(&self) -> impl Iterator> + '_ { + self.section_order.iter().map(move |id| &self.sections[id]) + } } diff --git a/git-config/src/file/init/resolve_includes.rs b/git-config/src/file/init/resolve_includes.rs index ecaae146649..39735471194 100644 --- a/git-config/src/file/init/resolve_includes.rs +++ b/git-config/src/file/init/resolve_includes.rs @@ -9,10 +9,7 @@ use git_ref::Category; use crate::file::resolve_includes::{conditional, Options}; use crate::file::Metadata; -use crate::{ - file::{init::from_paths, SectionId}, - File, -}; +use crate::{file, file::init::from_paths, File}; pub(crate) fn resolve_includes( conf: &mut File<'static>, @@ -40,51 +37,29 @@ fn resolve_includes_recursive( }; } - let mut paths_to_include = Vec::new(); let target_config_path = meta.path.as_deref(); - let mut incl_section_ids = Vec::new(); - for name in ["include", "includeIf"] { - if let Ok(ids) = target_config.section_ids_by_name(name) { - for id in ids { - incl_section_ids.push(( - id, - target_config - .section_order - .iter() - .position(|&e| e == id) - .expect("section id is from config"), - )); - } - } - } - incl_section_ids.sort_by(|a, b| a.1.cmp(&b.1)); - let mut include_paths = Vec::new(); - // TODO: could this just use the section order and compare the name itself? - for (id, _) in incl_section_ids { - if let Some(header) = target_config.sections.get(&id).map(|s| &s.header) { - if header.name.0.as_ref() == "include" && header.subsection_name.is_none() { - extract_include_path(target_config, &mut include_paths, id) - } else if header.name.0.as_ref() == "includeIf" { - if let Some(condition) = &header.subsection_name { - if include_condition_match(condition.as_ref(), target_config_path, options)? { - extract_include_path(target_config, &mut include_paths, id) - } + for section in target_config.sections() { + let header = §ion.header; + let header_name = header.name.as_ref(); + if header_name == "include" && header.subsection_name.is_none() { + extract_include_path(&mut include_paths, section) + } else if header_name == "includeIf" { + if let Some(condition) = &header.subsection_name { + if include_condition_match(condition.as_ref(), target_config_path, options)? { + extract_include_path(&mut include_paths, section) } } } } - for path in include_paths { - let path = resolve(path, target_config_path, options)?; - - if path.is_file() { - paths_to_include.push(path); + for config_path in include_paths { + let config_path = resolve(config_path, target_config_path, options)?; + if !config_path.is_file() { + continue; } - } - for config_path in paths_to_include { let config_meta = Metadata { path: None, trust: meta.trust, @@ -94,20 +69,22 @@ fn resolve_includes_recursive( let no_follow_options = from_paths::Options::default(); let mut include_config = File::from_path_with_buf(config_path, buf, config_meta, no_follow_options)?; let config_meta = include_config.meta_owned(); + resolve_includes_recursive(&mut include_config, config_meta, depth + 1, buf, options)?; + target_config.append(include_config); } Ok(()) } -fn extract_include_path(target_config: &mut File<'_>, include_paths: &mut Vec>, id: SectionId) { - if let Some(body) = target_config.sections.get(&id) { - let paths = body.values("path"); - let paths = paths - .iter() - .map(|path| crate::Path::from(Cow::Owned(path.as_ref().to_owned()))); - include_paths.extend(paths); - } +fn extract_include_path(include_paths: &mut Vec>, section: &file::Section<'_>) { + include_paths.extend( + section + .body + .values("path") + .into_iter() + .map(|path| crate::Path::from(Cow::Owned(path.into_owned()))), + ) } fn include_condition_match( diff --git a/git-config/tests/file/init/from_paths/mod.rs b/git-config/tests/file/init/from_paths/mod.rs index 70f481a7feb..64f5b5355a4 100644 --- a/git-config/tests/file/init/from_paths/mod.rs +++ b/git-config/tests/file/init/from_paths/mod.rs @@ -1,5 +1,5 @@ +use std::fs; use std::path::PathBuf; -use std::{borrow::Cow, fs, io}; use git_config::File; use tempfile::tempdir; @@ -11,40 +11,29 @@ pub(crate) fn escape_backslashes(path: impl AsRef) -> String { path.as_ref().to_str().unwrap().replace('\\', "\\\\") } -#[test] -fn file_not_found() { - let dir = tempdir().unwrap(); - let config_path = dir.path().join("config"); - - let paths = vec![config_path]; - let err = File::from_paths_metadata( - paths.into_iter().map(|p| git_config::file::Metadata { - path: Some(p), - ..Default::default() - }), - Default::default(), - ) - .unwrap_err(); - assert!( - matches!(err, git_config::file::from_paths::Error::Io(io_error) if io_error.kind() == io::ErrorKind::NotFound) - ); -} - -#[test] -fn single_path() { - let dir = tempdir().unwrap(); - let config_path = dir.path().join("config"); - fs::write(config_path.as_path(), b"[core]\nboolean = true").unwrap(); - - let paths = vec![config_path]; - let config = File::from_paths_metadata(into_meta(paths), Default::default()).unwrap(); - - assert_eq!( - config.raw_value("core", None, "boolean").unwrap(), - Cow::<[u8]>::Borrowed(b"true") - ); - - assert_eq!(config.num_values(), 1); +mod from_path_no_includes { + #[test] + fn file_not_found() { + let dir = tempfile::tempdir().unwrap(); + let config_path = dir.path().join("config"); + + let err = git_config::File::from_path_no_includes(config_path, git_config::Source::Local).unwrap_err(); + assert!( + matches!(err, git_config::file::from_paths::Error::Io(io_error) if io_error.kind() == std::io::ErrorKind::NotFound) + ); + } + + #[test] + fn single_path() { + let dir = tempfile::tempdir().unwrap(); + let config_path = dir.path().join("config"); + std::fs::write(config_path.as_path(), b"[core]\nboolean = true").unwrap(); + + let config = git_config::File::from_path_no_includes(config_path, git_config::Source::Local).unwrap(); + + assert_eq!(config.raw_value("core", None, "boolean").unwrap().as_ref(), "true"); + assert_eq!(config.num_values(), 1); + } } #[test] @@ -70,6 +59,7 @@ fn multiple_paths_single_value() -> crate::Result { assert_eq!(config.boolean("core", None, "b"), Some(Ok(true))); assert_eq!(config.boolean("core", None, "c"), Some(Ok(true))); assert_eq!(config.num_values(), 4); + assert_eq!(config.sections().count(), 4, "each value is in a dedicated section"); Ok(()) } From d60025e317d2b5f34f3569f321845bbb557ba2e7 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 16 Jul 2022 20:42:30 +0800 Subject: [PATCH 222/366] refactor (#331) --- git-config/src/file/init/resolve_includes.rs | 26 +++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/git-config/src/file/init/resolve_includes.rs b/git-config/src/file/init/resolve_includes.rs index 39735471194..5c1a0c1f9ae 100644 --- a/git-config/src/file/init/resolve_includes.rs +++ b/git-config/src/file/init/resolve_includes.rs @@ -44,16 +44,36 @@ fn resolve_includes_recursive( let header = §ion.header; let header_name = header.name.as_ref(); if header_name == "include" && header.subsection_name.is_none() { - extract_include_path(&mut include_paths, section) + detach_include_paths(&mut include_paths, section) } else if header_name == "includeIf" { if let Some(condition) = &header.subsection_name { if include_condition_match(condition.as_ref(), target_config_path, options)? { - extract_include_path(&mut include_paths, section) + detach_include_paths(&mut include_paths, section) } } } } + append_followed_includes_recursively( + include_paths, + target_config, + target_config_path, + depth, + meta.clone(), + options, + buf, + ) +} + +fn append_followed_includes_recursively( + include_paths: Vec>, + target_config: &mut File<'static>, + target_config_path: Option<&Path>, + depth: u8, + meta: OwnShared, + options: Options<'_>, + buf: &mut Vec, +) -> Result<(), from_paths::Error> { for config_path in include_paths { let config_path = resolve(config_path, target_config_path, options)?; if !config_path.is_file() { @@ -77,7 +97,7 @@ fn resolve_includes_recursive( Ok(()) } -fn extract_include_path(include_paths: &mut Vec>, section: &file::Section<'_>) { +fn detach_include_paths(include_paths: &mut Vec>, section: &file::Section<'_>) { include_paths.extend( section .body From 56ae5744e8957e617f3a0ebc4d725846b18d93f8 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 16 Jul 2022 21:07:42 +0800 Subject: [PATCH 223/366] feat: `file::Section::meta()` to access a section's metadata. (#331) --- git-config/src/file/section/mod.rs | 7 ++++- git-config/tests/file/init/from_paths/mod.rs | 30 ++++++++++++++++++-- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/git-config/src/file/section/mod.rs b/git-config/src/file/section/mod.rs index 2e434630166..f6c8a24790e 100644 --- a/git-config/src/file/section/mod.rs +++ b/git-config/src/file/section/mod.rs @@ -1,4 +1,4 @@ -use crate::file::{Section, SectionMut}; +use crate::file::{Metadata, Section, SectionMut}; use crate::parse::section; use crate::{file, parse}; use bstr::BString; @@ -65,6 +65,11 @@ impl<'a> Section<'a> { Ok(()) } + /// Return additional information about this sections origin. + pub fn meta(&self) -> &Metadata { + &self.meta + } + /// Returns a mutable version of this section for adjustment of values. pub fn to_mut(&mut self) -> SectionMut<'_, 'a> { SectionMut::new(self) diff --git a/git-config/tests/file/init/from_paths/mod.rs b/git-config/tests/file/init/from_paths/mod.rs index 64f5b5355a4..624281f33ac 100644 --- a/git-config/tests/file/init/from_paths/mod.rs +++ b/git-config/tests/file/init/from_paths/mod.rs @@ -1,7 +1,7 @@ use std::fs; use std::path::PathBuf; -use git_config::File; +use git_config::{File, Source}; use tempfile::tempdir; use crate::file::cow_str; @@ -83,8 +83,20 @@ fn multiple_paths_multi_value() -> crate::Result { let e_path = dir.path().join("e"); fs::write(e_path.as_path(), b"[include]\npath = e_path")?; - let paths = vec![a_path, b_path, c_path, d_path, e_path]; - let config = File::from_paths_metadata(into_meta(paths), Default::default())?; + let paths_and_source = vec![ + (a_path, Source::System), + (b_path, Source::Global), + (c_path, Source::User), + (d_path, Source::Worktree), + (e_path, Source::Local), + ]; + + let config = File::from_paths_metadata( + paths_and_source + .iter() + .map(|(p, s)| git_config::file::Metadata::try_from_path(p, *s).unwrap()), + Default::default(), + )?; assert_eq!( config.strings("core", None, "key"), @@ -97,6 +109,18 @@ fn multiple_paths_multi_value() -> crate::Result { ); assert_eq!(config.num_values(), 5); + assert_eq!( + config + .sections() + .map(|s| ( + s.meta().path.as_ref().expect("each section has file source").to_owned(), + s.meta().source, + s.meta().level + )) + .collect::>(), + paths_and_source.into_iter().map(|(p, s)| (p, s, 0)).collect::>(), + "sections are added in order and their path and sources are set as given, levels are 0 for the non-included ones" + ); Ok(()) } From 4665e876df4ac6ab9135c10ee69b5408b89b5313 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 16 Jul 2022 21:14:41 +0800 Subject: [PATCH 224/366] A test to validate frontmatter isn't currently handled correctly when appending (#331) --- git-config/tests/file/init/from_paths/mod.rs | 24 ++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/git-config/tests/file/init/from_paths/mod.rs b/git-config/tests/file/init/from_paths/mod.rs index 624281f33ac..d11213a9580 100644 --- a/git-config/tests/file/init/from_paths/mod.rs +++ b/git-config/tests/file/init/from_paths/mod.rs @@ -64,6 +64,30 @@ fn multiple_paths_single_value() -> crate::Result { Ok(()) } +#[test] +#[ignore] +fn frontmatter_is_maintained_in_multiple_files() -> crate::Result { + let dir = tempdir()?; + + let a_path = dir.path().join("a"); + fs::write(a_path.as_path(), b";before a\n[core]\na = true")?; + + let b_path = dir.path().join("b"); + fs::write(b_path.as_path(), b";before b\n [core]\nb = true")?; + + let c_path = dir.path().join("c"); + fs::write(c_path.as_path(), b"# nothing in c")?; + + let d_path = dir.path().join("d"); + fs::write(d_path.as_path(), b"; nothing in d")?; + + let paths = vec![a_path, b_path, c_path, d_path]; + let config = File::from_paths_metadata(into_meta(paths), Default::default())?; + + assert_eq!(config.to_string(), ";before a\n[core]\na = true[core]\nb = true"); + Ok(()) +} + #[test] fn multiple_paths_multi_value() -> crate::Result { let dir = tempdir()?; From 09966a8ea4eaa3e0805e04188de86dd1bac9f388 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 16 Jul 2022 22:23:06 +0800 Subject: [PATCH 225/366] feat: `File::append()` can append one file to another rather losslessly. (#331) The loss happens as we, maybe for the wrong reasons, automatically insert newlines where needed which can only be done while we still know the file boundaries. --- git-config/src/file/access/mutate.rs | 60 ++++++++++++++++++++ git-config/src/file/access/write.rs | 5 ++ git-config/src/file/init/mod.rs | 1 + git-config/src/file/utils.rs | 10 ---- git-config/src/types.rs | 4 +- git-config/tests/file/init/from_paths/mod.rs | 15 ++++- git-config/tests/file/mod.rs | 2 +- 7 files changed, 82 insertions(+), 15 deletions(-) diff --git a/git-config/src/file/access/mutate.rs b/git-config/src/file/access/mutate.rs index fff19faf96c..08c82a25f0c 100644 --- a/git-config/src/file/access/mutate.rs +++ b/git-config/src/file/access/mutate.rs @@ -1,6 +1,8 @@ use git_features::threading::OwnShared; use std::borrow::Cow; +use crate::file::SectionId; +use crate::parse::{Event, FrontMatterEvents}; use crate::{ file::{self, rename_section, SectionMut}, lookup, @@ -153,4 +155,62 @@ impl<'event> File<'event> { section.header = section::Header::new(new_section_name, new_subsection_name)?; Ok(()) } + + /// Append another File to the end of ourselves, without loosing any information. + pub fn append(&mut self, mut other: Self) { + fn ends_with_newline<'a>(it: impl DoubleEndedIterator>) -> bool { + it.last().map_or(true, |e| e.to_bstring().last() == Some(&b'\n')) + } + fn newline_event() -> Event<'static> { + Event::Newline(Cow::Borrowed( + // NOTE: this is usually the right thing to assume, let's see + if cfg!(windows) { "\r\n" } else { "\n" }.into(), + )) + } + + fn assure_ends_with_newline<'a, 'b>(events: &'b mut FrontMatterEvents<'a>) -> &'b mut FrontMatterEvents<'a> { + if !ends_with_newline(events.iter()) { + events.push(newline_event()); + } + events + } + + let last_section_id = (self.section_id_counter != 0).then(|| self.section_id_counter - 1); + let mut last_added_section_id = None; + for id in std::mem::take(&mut other.section_order) { + let section = other.sections.remove(&id).expect("present"); + self.push_section_internal(section); + + let new_id = self.section_id_counter - 1; + last_added_section_id = Some(new_id); + if let Some(post_matter) = other.frontmatter_post_section.remove(&id) { + self.frontmatter_post_section.insert(SectionId(new_id), post_matter); + } + } + + if !other.frontmatter_events.is_empty() { + match last_added_section_id { + Some(id) => { + if !ends_with_newline(self.sections[&SectionId(id)].body.0.iter()) { + other.frontmatter_events.insert(0, newline_event()); + } + } + None => { + if !last_section_id.map_or(true, |id| { + ends_with_newline(self.sections[&SectionId(id)].body.0.iter()) + }) { + other.frontmatter_events.insert(0, newline_event()); + } + } + } + + match last_section_id { + Some(last_id) => { + assure_ends_with_newline(self.frontmatter_post_section.entry(SectionId(last_id)).or_default()) + .extend(other.frontmatter_events) + } + None => assure_ends_with_newline(&mut self.frontmatter_events).extend(other.frontmatter_events), + } + } + } } diff --git a/git-config/src/file/access/write.rs b/git-config/src/file/access/write.rs index 61131d9d4bf..c023ed97dd7 100644 --- a/git-config/src/file/access/write.rs +++ b/git-config/src/file/access/write.rs @@ -25,6 +25,11 @@ impl File<'_> { .get(section_id) .expect("known section-id") .write_to(&mut out)?; + if let Some(post_matter) = self.frontmatter_post_section.get(section_id) { + for event in post_matter { + event.write_to(&mut out)?; + } + } } Ok(()) diff --git a/git-config/src/file/init/mod.rs b/git-config/src/file/init/mod.rs index e5c2aedd466..1b1d0585e37 100644 --- a/git-config/src/file/init/mod.rs +++ b/git-config/src/file/init/mod.rs @@ -15,6 +15,7 @@ impl<'a> File<'a> { pub fn new(meta: impl Into>) -> Self { Self { frontmatter_events: Default::default(), + frontmatter_post_section: Default::default(), section_lookup_tree: Default::default(), sections: Default::default(), section_id_counter: 0, diff --git a/git-config/src/file/utils.rs b/git-config/src/file/utils.rs index d4a092a39cf..e5e816ce083 100644 --- a/git-config/src/file/utils.rs +++ b/git-config/src/file/utils.rs @@ -109,14 +109,4 @@ impl<'event> File<'event> { None => Err(lookup::existing::Error::SectionMissing), } } - - // TODO: add note indicating that probably a lot if not all information about the original files is currently lost, - // so can't be written back. This will probably change a lot during refactor, so it's not too important now. - pub(crate) fn append(&mut self, mut other: Self) { - // TODO: don't loose the front-matter here. Not doing so means we know after which section it needs to be inserted, complicating things. - for id in std::mem::take(&mut other.section_order) { - let section = other.sections.remove(&id).expect("present"); - self.push_section_internal(section); - } - } } diff --git a/git-config/src/types.rs b/git-config/src/types.rs index 1f637b68069..08b96cbee2f 100644 --- a/git-config/src/types.rs +++ b/git-config/src/types.rs @@ -79,10 +79,12 @@ pub enum Source { /// [`raw_value()`]: Self::raw_value #[derive(PartialEq, Eq, Clone, Debug, Default)] pub struct File<'event> { - /// The list of events that occur before an actual section. Since a + /// The list of events that occur before any section. Since a /// `git-config` file prohibits global values, this vec is limited to only /// comment, newline, and whitespace events. pub(crate) frontmatter_events: crate::parse::FrontMatterEvents<'event>, + /// Frontmatter events to be placed after the given section. + pub(crate) frontmatter_post_section: HashMap>, /// Section name to section id lookup tree, with section bodies for subsections being in a non-terminal /// variant of `SectionBodyIds`. pub(crate) section_lookup_tree: HashMap, Vec>>, diff --git a/git-config/tests/file/init/from_paths/mod.rs b/git-config/tests/file/init/from_paths/mod.rs index d11213a9580..73ac2dc89ad 100644 --- a/git-config/tests/file/init/from_paths/mod.rs +++ b/git-config/tests/file/init/from_paths/mod.rs @@ -65,7 +65,6 @@ fn multiple_paths_single_value() -> crate::Result { } #[test] -#[ignore] fn frontmatter_is_maintained_in_multiple_files() -> crate::Result { let dir = tempdir()?; @@ -82,9 +81,19 @@ fn frontmatter_is_maintained_in_multiple_files() -> crate::Result { fs::write(d_path.as_path(), b"; nothing in d")?; let paths = vec![a_path, b_path, c_path, d_path]; - let config = File::from_paths_metadata(into_meta(paths), Default::default())?; + let mut config = File::from_paths_metadata(into_meta(paths), Default::default())?; + + assert_eq!( + config.to_string(), + ";before a\n[core]\na = true\n;before b\n [core]\nb = true\n# nothing in c\n\n; nothing in d" + ); - assert_eq!(config.to_string(), ";before a\n[core]\na = true[core]\nb = true"); + config.append(config.clone()); + assert_eq!( + config.to_string(), + ";before a\n[core]\na = true\n;before b\n [core]\nb = true\n# nothing in c\n\n; nothing in d\n\n;before a\n[core]\na = true\n;before b\n [core]\nb = true\n# nothing in c\n\n; nothing in d", + "other files post-section matter works as well, adding newlines as needed" + ); Ok(()) } diff --git a/git-config/tests/file/mod.rs b/git-config/tests/file/mod.rs index 888bbf2fe8f..cda3557a90a 100644 --- a/git-config/tests/file/mod.rs +++ b/git-config/tests/file/mod.rs @@ -10,7 +10,7 @@ pub fn cow_str(s: &str) -> Cow<'_, BStr> { fn size_in_memory() { assert_eq!( std::mem::size_of::>(), - 992, + 1040, "This shouldn't change without us noticing" ); } From 9a1e9828e813ec1de68ac2e83a986c49c71c5dbe Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 17 Jul 2022 09:28:48 +0800 Subject: [PATCH 226/366] fix: on windows, emit a `NotFound` io error, similar to what happens on unix. (#331) That way code relying on this behaviour will work the same on both platforms. On windows, this costs at an additional metadata check. --- git-sec/src/identity.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/git-sec/src/identity.rs b/git-sec/src/identity.rs index d6523da4284..d38a29e8b04 100644 --- a/git-sec/src/identity.rs +++ b/git-sec/src/identity.rs @@ -80,6 +80,13 @@ mod impl_ { let mut is_owned = false; let path = path.as_ref(); + if !path.exists() { + return Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + format!("{:?} does not exist.", path), + )); + } + // Home is not actually owned by the corresponding user // but it can be considered de-facto owned by the user // Ignore errors here and just do the regular checks below From 25ed92e66bf4345f852e7e84741079c61ae896c8 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 17 Jul 2022 09:41:02 +0800 Subject: [PATCH 227/366] Be smarter about which newline style to use by guessing it based onprior events (#331) --- git-config/src/file/access/mutate.rs | 46 +++++++++++++++++++------- git-config/src/file/init/from_paths.rs | 3 +- 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/git-config/src/file/access/mutate.rs b/git-config/src/file/access/mutate.rs index 08c82a25f0c..d6d57825d64 100644 --- a/git-config/src/file/access/mutate.rs +++ b/git-config/src/file/access/mutate.rs @@ -1,3 +1,4 @@ +use bstr::{BStr, BString}; use git_features::threading::OwnShared; use std::borrow::Cow; @@ -158,19 +159,19 @@ impl<'event> File<'event> { /// Append another File to the end of ourselves, without loosing any information. pub fn append(&mut self, mut other: Self) { + let nl = self.detect_newline_style(); + fn ends_with_newline<'a>(it: impl DoubleEndedIterator>) -> bool { it.last().map_or(true, |e| e.to_bstring().last() == Some(&b'\n')) } - fn newline_event() -> Event<'static> { - Event::Newline(Cow::Borrowed( - // NOTE: this is usually the right thing to assume, let's see - if cfg!(windows) { "\r\n" } else { "\n" }.into(), - )) - } + let newline_event = || Event::Newline(Cow::Owned(nl.clone())); - fn assure_ends_with_newline<'a, 'b>(events: &'b mut FrontMatterEvents<'a>) -> &'b mut FrontMatterEvents<'a> { + fn assure_ends_with_newline<'a, 'b>( + events: &'b mut FrontMatterEvents<'a>, + nl: &BStr, + ) -> &'b mut FrontMatterEvents<'a> { if !ends_with_newline(events.iter()) { - events.push(newline_event()); + events.push(Event::Newline(nl.to_owned().into())); } events } @@ -205,12 +206,33 @@ impl<'event> File<'event> { } match last_section_id { - Some(last_id) => { - assure_ends_with_newline(self.frontmatter_post_section.entry(SectionId(last_id)).or_default()) - .extend(other.frontmatter_events) + Some(last_id) => assure_ends_with_newline( + self.frontmatter_post_section.entry(SectionId(last_id)).or_default(), + nl.as_ref(), + ) + .extend(other.frontmatter_events), + None => { + assure_ends_with_newline(&mut self.frontmatter_events, nl.as_ref()).extend(other.frontmatter_events) } - None => assure_ends_with_newline(&mut self.frontmatter_events).extend(other.frontmatter_events), } } } + + fn detect_newline_style(&self) -> BString { + fn extract_newline(e: &Event<'_>) -> Option { + match e { + Event::Newline(b) => b.as_ref().to_owned().into(), + _ => None, + } + } + + self.frontmatter_events + .iter() + .find_map(extract_newline) + .or_else(|| { + self.sections() + .find_map(|s| s.body.as_ref().iter().find_map(extract_newline)) + }) + .unwrap_or_else(|| if cfg!(windows) { "\r\n" } else { "\n" }.into()) + } } diff --git a/git-config/src/file/init/from_paths.rs b/git-config/src/file/init/from_paths.rs index e20c4bead37..32f4dbd0bcd 100644 --- a/git-config/src/file/init/from_paths.rs +++ b/git-config/src/file/init/from_paths.rs @@ -33,7 +33,8 @@ pub struct Options<'a> { /// Instantiation from one or more paths impl File<'static> { - /// Load the file at `path` from `source` without following include directives. + /// Load the file at `path` from `source` without following include directives. Note that the path will be checked for + /// ownership to derive trust. pub fn from_path_no_includes(path: impl Into, source: crate::Source) -> Result { let path = path.into(); let trust = git_sec::Trust::from_path_ownership(&path)?; From c70e135ecbbce8c696a6ab542ae20f5b5981dfdf Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 17 Jul 2022 10:05:48 +0800 Subject: [PATCH 228/366] finally fix newline behaviour (#331) --- git-config/src/file/access/mutate.rs | 31 ++++++++++++-------- git-config/tests/file/init/from_paths/mod.rs | 4 +-- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/git-config/src/file/access/mutate.rs b/git-config/src/file/access/mutate.rs index d6d57825d64..7c838ab01b4 100644 --- a/git-config/src/file/access/mutate.rs +++ b/git-config/src/file/access/mutate.rs @@ -161,22 +161,28 @@ impl<'event> File<'event> { pub fn append(&mut self, mut other: Self) { let nl = self.detect_newline_style(); + // TODO: don't allocate here fn ends_with_newline<'a>(it: impl DoubleEndedIterator>) -> bool { it.last().map_or(true, |e| e.to_bstring().last() == Some(&b'\n')) } + fn starts_with_newline<'a>(mut it: impl Iterator>) -> bool { + it.next().map_or(true, |e| e.to_bstring().first() == Some(&b'\n')) + } let newline_event = || Event::Newline(Cow::Owned(nl.clone())); - fn assure_ends_with_newline<'a, 'b>( + fn assure_ends_with_newline_if<'a, 'b>( + needs_nl: bool, events: &'b mut FrontMatterEvents<'a>, nl: &BStr, ) -> &'b mut FrontMatterEvents<'a> { - if !ends_with_newline(events.iter()) { + if needs_nl && !ends_with_newline(events.iter()) { events.push(Event::Newline(nl.to_owned().into())); } events } - let last_section_id = (self.section_id_counter != 0).then(|| self.section_id_counter - 1); + let our_last_section_before_append = + (self.section_id_counter != 0).then(|| SectionId(self.section_id_counter - 1)); let mut last_added_section_id = None; for id in std::mem::take(&mut other.section_order) { let section = other.sections.remove(&id).expect("present"); @@ -197,23 +203,24 @@ impl<'event> File<'event> { } } None => { - if !last_section_id.map_or(true, |id| { - ends_with_newline(self.sections[&SectionId(id)].body.0.iter()) - }) { + if !our_last_section_before_append + .map_or(true, |id| ends_with_newline(self.sections[&id].body.0.iter())) + { other.frontmatter_events.insert(0, newline_event()); } } } - match last_section_id { - Some(last_id) => assure_ends_with_newline( - self.frontmatter_post_section.entry(SectionId(last_id)).or_default(), + let needs_nl = !starts_with_newline(other.frontmatter_events.iter()); + match our_last_section_before_append { + Some(last_id) => assure_ends_with_newline_if( + needs_nl, + self.frontmatter_post_section.entry(last_id).or_default(), nl.as_ref(), ) .extend(other.frontmatter_events), - None => { - assure_ends_with_newline(&mut self.frontmatter_events, nl.as_ref()).extend(other.frontmatter_events) - } + None => assure_ends_with_newline_if(needs_nl, &mut self.frontmatter_events, nl.as_ref()) + .extend(other.frontmatter_events), } } } diff --git a/git-config/tests/file/init/from_paths/mod.rs b/git-config/tests/file/init/from_paths/mod.rs index 73ac2dc89ad..8c5e0638edf 100644 --- a/git-config/tests/file/init/from_paths/mod.rs +++ b/git-config/tests/file/init/from_paths/mod.rs @@ -85,13 +85,13 @@ fn frontmatter_is_maintained_in_multiple_files() -> crate::Result { assert_eq!( config.to_string(), - ";before a\n[core]\na = true\n;before b\n [core]\nb = true\n# nothing in c\n\n; nothing in d" + ";before a\n[core]\na = true\n;before b\n [core]\nb = true\n# nothing in c\n; nothing in d" ); config.append(config.clone()); assert_eq!( config.to_string(), - ";before a\n[core]\na = true\n;before b\n [core]\nb = true\n# nothing in c\n\n; nothing in d\n\n;before a\n[core]\na = true\n;before b\n [core]\nb = true\n# nothing in c\n\n; nothing in d", + ";before a\n[core]\na = true\n;before b\n [core]\nb = true\n# nothing in c\n; nothing in d\n;before a\n[core]\na = true\n;before b\n [core]\nb = true\n# nothing in c\n; nothing in d", "other files post-section matter works as well, adding newlines as needed" ); Ok(()) From fc7e311b423c5fffb8240d9d0f917ae7139a6133 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 17 Jul 2022 10:13:34 +0800 Subject: [PATCH 229/366] feat: `parse::Event::to_bstr_lossy()` to get a glimpse at event content. (#331) --- git-config/src/file/access/mutate.rs | 5 ++--- git-config/src/parse/event.rs | 18 +++++++++++++++++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/git-config/src/file/access/mutate.rs b/git-config/src/file/access/mutate.rs index 7c838ab01b4..959652839ae 100644 --- a/git-config/src/file/access/mutate.rs +++ b/git-config/src/file/access/mutate.rs @@ -161,12 +161,11 @@ impl<'event> File<'event> { pub fn append(&mut self, mut other: Self) { let nl = self.detect_newline_style(); - // TODO: don't allocate here fn ends_with_newline<'a>(it: impl DoubleEndedIterator>) -> bool { - it.last().map_or(true, |e| e.to_bstring().last() == Some(&b'\n')) + it.last().map_or(true, |e| e.to_bstr_lossy().last() == Some(&b'\n')) } fn starts_with_newline<'a>(mut it: impl Iterator>) -> bool { - it.next().map_or(true, |e| e.to_bstring().first() == Some(&b'\n')) + it.next().map_or(true, |e| e.to_bstr_lossy().first() == Some(&b'\n')) } let newline_event = || Event::Newline(Cow::Owned(nl.clone())); diff --git a/git-config/src/parse/event.rs b/git-config/src/parse/event.rs index d2a57f4ab86..cc314498ae4 100644 --- a/git-config/src/parse/event.rs +++ b/git-config/src/parse/event.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, fmt::Display}; -use bstr::BString; +use bstr::{BStr, BString}; use crate::parse::Event; @@ -15,6 +15,22 @@ impl Event<'_> { buf.into() } + /// Turn ourselves into the text we represent, lossy. + /// + /// Note that this will be partial in case of `ValueNotDone` which doesn't include the backslash, and `SectionHeader` will only + /// provide their name, lacking the sub-section name. + pub fn to_bstr_lossy(&self) -> &BStr { + match self { + Self::ValueNotDone(e) | Self::Whitespace(e) | Self::Newline(e) | Self::Value(e) | Self::ValueDone(e) => { + e.as_ref() + } + Self::KeyValueSeparator => "=".into(), + Self::SectionKey(k) => k.0.as_ref(), + Self::SectionHeader(h) => h.name.0.as_ref(), + Self::Comment(c) => c.comment.as_ref(), + } + } + /// Stream ourselves to the given `out`, in order to reproduce this event mostly losslessly /// as it was parsed. pub fn write_to(&self, mut out: impl std::io::Write) -> std::io::Result<()> { From 3f3ff11a6ebe9775ee5ae7fc0ec18a94b5b46d61 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 17 Jul 2022 10:15:41 +0800 Subject: [PATCH 230/366] change!: rename `parse::Comment::(comment_tag|comment)` to `::tag|text` and `parse::Section::section_header` to `::header`. (#331) --- git-config/src/file/init/mod.rs | 2 +- git-config/src/parse/comment.rs | 8 ++++---- git-config/src/parse/event.rs | 2 +- git-config/src/parse/events.rs | 14 +++++++------- git-config/src/parse/mod.rs | 6 +++--- git-config/src/parse/nom/mod.rs | 4 ++-- git-config/src/parse/nom/tests.rs | 22 +++++++++++----------- git-config/src/parse/section/mod.rs | 4 ++-- git-config/src/parse/tests.rs | 4 ++-- 9 files changed, 33 insertions(+), 33 deletions(-) diff --git a/git-config/src/file/init/mod.rs b/git-config/src/file/init/mod.rs index 1b1d0585e37..c80ba07f647 100644 --- a/git-config/src/file/init/mod.rs +++ b/git-config/src/file/init/mod.rs @@ -38,7 +38,7 @@ impl<'a> File<'a> { for section in sections { this.push_section_internal(crate::file::Section { - header: section.section_header, + header: section.header, body: section::Body(section.events), meta: OwnShared::clone(&meta), }); diff --git a/git-config/src/parse/comment.rs b/git-config/src/parse/comment.rs index aba6d197085..6d4bb15ffb5 100644 --- a/git-config/src/parse/comment.rs +++ b/git-config/src/parse/comment.rs @@ -9,8 +9,8 @@ impl Comment<'_> { #[must_use] pub fn to_owned(&self) -> Comment<'static> { Comment { - comment_tag: self.comment_tag, - comment: Cow::Owned(self.comment.as_ref().into()), + tag: self.tag, + text: Cow::Owned(self.text.as_ref().into()), } } @@ -26,8 +26,8 @@ impl Comment<'_> { /// Stream ourselves to the given `out`, in order to reproduce this comment losslessly. pub fn write_to(&self, mut out: impl std::io::Write) -> std::io::Result<()> { - out.write_all(&[self.comment_tag])?; - out.write_all(self.comment.as_ref()) + out.write_all(&[self.tag])?; + out.write_all(self.text.as_ref()) } } diff --git a/git-config/src/parse/event.rs b/git-config/src/parse/event.rs index cc314498ae4..b7b96934d04 100644 --- a/git-config/src/parse/event.rs +++ b/git-config/src/parse/event.rs @@ -27,7 +27,7 @@ impl Event<'_> { Self::KeyValueSeparator => "=".into(), Self::SectionKey(k) => k.0.as_ref(), Self::SectionHeader(h) => h.name.0.as_ref(), - Self::Comment(c) => c.comment.as_ref(), + Self::Comment(c) => c.text.as_ref(), } } diff --git a/git-config/src/parse/events.rs b/git-config/src/parse/events.rs index 768c06769d7..3ea8e6ce415 100644 --- a/git-config/src/parse/events.rs +++ b/git-config/src/parse/events.rs @@ -255,11 +255,11 @@ impl<'a> Events<'a> { #[must_use = "iterators are lazy and do nothing unless consumed"] #[allow(clippy::should_implement_trait)] pub fn into_iter(self) -> impl Iterator> + std::iter::FusedIterator { - self.frontmatter - .into_iter() - .chain(self.sections.into_iter().flat_map(|section| { - std::iter::once(parse::Event::SectionHeader(section.section_header)).chain(section.events) - })) + self.frontmatter.into_iter().chain( + self.sections + .into_iter() + .flat_map(|section| std::iter::once(parse::Event::SectionHeader(section.header)).chain(section.events)), + ) } /// Place all contained events into a single `Vec`. @@ -301,7 +301,7 @@ fn from_bytes<'a, 'b>( } Some(prev_header) => { sections.push(parse::Section { - section_header: prev_header, + header: prev_header, events: std::mem::take(&mut events), }); } @@ -325,7 +325,7 @@ fn from_bytes<'a, 'b>( } Some(prev_header) => { sections.push(parse::Section { - section_header: prev_header, + header: prev_header, events: std::mem::take(&mut events), }); } diff --git a/git-config/src/parse/mod.rs b/git-config/src/parse/mod.rs index 380c878c395..fd1f779b77e 100644 --- a/git-config/src/parse/mod.rs +++ b/git-config/src/parse/mod.rs @@ -88,7 +88,7 @@ pub enum Event<'a> { #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] pub struct Section<'a> { /// The section name and subsection name, if any. - pub section_header: section::Header<'a>, + pub header: section::Header<'a>, /// The syntactic events found in this section. pub events: section::Events<'a>, } @@ -97,9 +97,9 @@ pub struct Section<'a> { #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] pub struct Comment<'a> { /// The comment marker used. This is either a semicolon or octothorpe/hash. - pub comment_tag: u8, + pub tag: u8, /// The parsed comment. - pub comment: Cow<'a, BStr>, + pub text: Cow<'a, BStr>, } /// A parser error reports the one-indexed line number where the parsing error diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index 9a1c8958f92..ae22b5f1b36 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -78,8 +78,8 @@ fn comment(i: &[u8]) -> IResult<&[u8], Comment<'_>> { Ok(( i, Comment { - comment_tag: comment_tag as u8, - comment: Cow::Borrowed(comment.as_bstr()), + tag: comment_tag as u8, + text: Cow::Borrowed(comment.as_bstr()), }, )) } diff --git a/git-config/src/parse/nom/tests.rs b/git-config/src/parse/nom/tests.rs index 19c58aabada..9a7e275f961 100644 --- a/git-config/src/parse/nom/tests.rs +++ b/git-config/src/parse/nom/tests.rs @@ -187,7 +187,7 @@ mod section { i, ( Section { - section_header: match header.expect("header set") { + header: match header.expect("header set") { Event::SectionHeader(header) => header, _ => unreachable!("unexpected"), }, @@ -206,7 +206,7 @@ mod section { section(b"[test]", &mut node).unwrap(), fully_consumed(( Section { - section_header: parsed_section_header("test", None), + header: parsed_section_header("test", None), events: Default::default() }, 0 @@ -225,7 +225,7 @@ mod section { section(section_data, &mut node).unwrap(), fully_consumed(( Section { - section_header: parsed_section_header("hello", None), + header: parsed_section_header("hello", None), events: vec![ newline_event(), whitespace_event(" "), @@ -261,7 +261,7 @@ mod section { section(section_data, &mut node).unwrap(), fully_consumed(( Section { - section_header: parsed_section_header("a", None), + header: parsed_section_header("a", None), events: vec![ whitespace_event(" "), name_event("k"), @@ -279,7 +279,7 @@ mod section { section(section_data, &mut node).unwrap(), fully_consumed(( Section { - section_header: parsed_section_header("a", None), + header: parsed_section_header("a", None), events: vec![ whitespace_event(" "), name_event("k"), @@ -305,7 +305,7 @@ mod section { section(section_data, &mut node).unwrap(), fully_consumed(( Section { - section_header: parsed_section_header("hello", None), + header: parsed_section_header("hello", None), events: vec![ newline_event(), whitespace_event(" "), @@ -341,7 +341,7 @@ mod section { section(b"[hello] c", &mut node).unwrap(), fully_consumed(( Section { - section_header: parsed_section_header("hello", None), + header: parsed_section_header("hello", None), events: vec![whitespace_event(" "), name_event("c"), value_event("")].into() }, 0 @@ -361,7 +361,7 @@ mod section { section(section_data, &mut node).unwrap(), fully_consumed(( Section { - section_header: parsed_section_header("hello", None), + header: parsed_section_header("hello", None), events: vec![ whitespace_event(" "), comment_event(';', " commentA"), @@ -403,7 +403,7 @@ mod section { section(b"[section] a = 1 \"\\\"\\\na ; e \"\\\"\\\nd # \"b\t ; c", &mut node).unwrap(), fully_consumed(( Section { - section_header: parsed_section_header("section", None), + header: parsed_section_header("section", None), events: vec![ whitespace_event(" "), name_event("a"), @@ -432,7 +432,7 @@ mod section { section(b"[section \"a\"] b =\"\\\n;\";a", &mut node).unwrap(), fully_consumed(( Section { - section_header: parsed_section_header("section", (" ", "a")), + header: parsed_section_header("section", (" ", "a")), events: vec![ whitespace_event(" "), name_event("b"), @@ -457,7 +457,7 @@ mod section { section(b"[s]hello #world", &mut node).unwrap(), fully_consumed(( Section { - section_header: parsed_section_header("s", None), + header: parsed_section_header("s", None), events: vec![ name_event("hello"), whitespace_event(" "), diff --git a/git-config/src/parse/section/mod.rs b/git-config/src/parse/section/mod.rs index aa4b5fedeeb..459ad5906ef 100644 --- a/git-config/src/parse/section/mod.rs +++ b/git-config/src/parse/section/mod.rs @@ -30,7 +30,7 @@ impl Section<'_> { #[must_use] pub fn to_owned(&self) -> Section<'static> { Section { - section_header: self.section_header.to_owned(), + header: self.header.to_owned(), events: self.events.iter().map(Event::to_owned).collect(), } } @@ -38,7 +38,7 @@ impl Section<'_> { impl Display for Section<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.section_header)?; + write!(f, "{}", self.header)?; for event in &self.events { event.fmt(f)?; } diff --git a/git-config/src/parse/tests.rs b/git-config/src/parse/tests.rs index fdd42594111..790c1fee5af 100644 --- a/git-config/src/parse/tests.rs +++ b/git-config/src/parse/tests.rs @@ -114,8 +114,8 @@ pub(crate) mod util { pub(crate) fn comment(comment_tag: char, comment: &'static str) -> Comment<'static> { Comment { - comment_tag: comment_tag as u8, - comment: Cow::Borrowed(comment.into()), + tag: comment_tag as u8, + text: Cow::Borrowed(comment.into()), } } From df94c6737ba642fff40623f406df0764d5bd3c43 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 17 Jul 2022 10:20:48 +0800 Subject: [PATCH 231/366] refactor (#331) Clone only where needed, to not water down our actual requirements. --- git-config/src/file/access/mutate.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/git-config/src/file/access/mutate.rs b/git-config/src/file/access/mutate.rs index 959652839ae..600466826e3 100644 --- a/git-config/src/file/access/mutate.rs +++ b/git-config/src/file/access/mutate.rs @@ -1,4 +1,4 @@ -use bstr::{BStr, BString}; +use bstr::BStr; use git_features::threading::OwnShared; use std::borrow::Cow; @@ -159,7 +159,7 @@ impl<'event> File<'event> { /// Append another File to the end of ourselves, without loosing any information. pub fn append(&mut self, mut other: Self) { - let nl = self.detect_newline_style(); + let nl = self.detect_newline_style().to_owned(); fn ends_with_newline<'a>(it: impl DoubleEndedIterator>) -> bool { it.last().map_or(true, |e| e.to_bstr_lossy().last() == Some(&b'\n')) @@ -224,10 +224,10 @@ impl<'event> File<'event> { } } - fn detect_newline_style(&self) -> BString { - fn extract_newline(e: &Event<'_>) -> Option { + fn detect_newline_style(&self) -> &BStr { + fn extract_newline<'a, 'b>(e: &'a Event<'b>) -> Option<&'a BStr> { match e { - Event::Newline(b) => b.as_ref().to_owned().into(), + Event::Newline(b) => b.as_ref().into(), _ => None, } } From 50c1753c6389f29279d278fbab1afbd9ded34a76 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 17 Jul 2022 10:32:16 +0800 Subject: [PATCH 232/366] even better handling of newlines (#331) --- git-config/src/file/access/mutate.rs | 23 ++++++++------------ git-config/tests/file/init/from_paths/mod.rs | 2 +- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/git-config/src/file/access/mutate.rs b/git-config/src/file/access/mutate.rs index 600466826e3..73e9c2cd7cb 100644 --- a/git-config/src/file/access/mutate.rs +++ b/git-config/src/file/access/mutate.rs @@ -188,29 +188,24 @@ impl<'event> File<'event> { self.push_section_internal(section); let new_id = self.section_id_counter - 1; - last_added_section_id = Some(new_id); + last_added_section_id = Some(SectionId(new_id)); if let Some(post_matter) = other.frontmatter_post_section.remove(&id) { self.frontmatter_post_section.insert(SectionId(new_id), post_matter); } } if !other.frontmatter_events.is_empty() { - match last_added_section_id { - Some(id) => { - if !ends_with_newline(self.sections[&SectionId(id)].body.0.iter()) { - other.frontmatter_events.insert(0, newline_event()); - } - } - None => { - if !our_last_section_before_append - .map_or(true, |id| ends_with_newline(self.sections[&id].body.0.iter())) - { - other.frontmatter_events.insert(0, newline_event()); - } + let mut needs_nl = !starts_with_newline(other.frontmatter_events.iter()); + if let Some(id) = last_added_section_id + .or(our_last_section_before_append) + .filter(|_| needs_nl) + { + if !ends_with_newline(self.sections[&id].body.0.iter()) { + other.frontmatter_events.insert(0, newline_event()); + needs_nl = false; } } - let needs_nl = !starts_with_newline(other.frontmatter_events.iter()); match our_last_section_before_append { Some(last_id) => assure_ends_with_newline_if( needs_nl, diff --git a/git-config/tests/file/init/from_paths/mod.rs b/git-config/tests/file/init/from_paths/mod.rs index 8c5e0638edf..6246f96d032 100644 --- a/git-config/tests/file/init/from_paths/mod.rs +++ b/git-config/tests/file/init/from_paths/mod.rs @@ -78,7 +78,7 @@ fn frontmatter_is_maintained_in_multiple_files() -> crate::Result { fs::write(c_path.as_path(), b"# nothing in c")?; let d_path = dir.path().join("d"); - fs::write(d_path.as_path(), b"; nothing in d")?; + fs::write(d_path.as_path(), b"\n; nothing in d")?; let paths = vec![a_path, b_path, c_path, d_path]; let mut config = File::from_paths_metadata(into_meta(paths), Default::default())?; From 1ea26d80f392114349d25ebf88a7b260ee822aa1 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 17 Jul 2022 13:31:28 +0800 Subject: [PATCH 233/366] feat!: add `_filter()` versions to most access methods. (#331) That way it's possible to filter values by their origin. Note that the `remove_section()` methods now return the entire removed section, not just the body, which yields more information than before including section metadata. --- git-config/src/file/access/comfort.rs | 110 +++++++++++--- git-config/src/file/access/mutate.rs | 146 ++++++++++++++----- git-config/src/file/access/raw.rs | 101 ++++++++++--- git-config/src/file/access/read_only.rs | 67 ++++++--- git-config/src/file/init/from_paths.rs | 4 +- git-config/src/file/mod.rs | 4 +- git-config/tests/file/init/from_paths/mod.rs | 14 +- 7 files changed, 350 insertions(+), 96 deletions(-) diff --git a/git-config/src/file/access/comfort.rs b/git-config/src/file/access/comfort.rs index af734c7649f..57fedf56125 100644 --- a/git-config/src/file/access/comfort.rs +++ b/git-config/src/file/access/comfort.rs @@ -2,11 +2,12 @@ use std::{borrow::Cow, convert::TryFrom}; use bstr::BStr; +use crate::file::MetadataFilter; use crate::{value, File}; /// Comfortable API for accessing values impl<'event> File<'event> { - /// Like [`value()`][File::value()], but returning an `None` if the string wasn't found. + /// Like [`value()`][File::value()], but returning `None` if the string wasn't found. /// /// As strings perform no conversions, this will never fail. pub fn string( @@ -15,25 +16,49 @@ impl<'event> File<'event> { subsection_name: Option<&str>, key: impl AsRef, ) -> Option> { - self.raw_value(section_name, subsection_name, key).ok() + self.string_filter(section_name, subsection_name, key, &mut |_| true) + } + + /// Like [`string()`][File::string()], but the section containing the returned value must pass `filter` as well. + pub fn string_filter( + &self, + section_name: impl AsRef, + subsection_name: Option<&str>, + key: impl AsRef, + filter: &mut MetadataFilter, + ) -> Option> { + self.raw_value_filter(section_name, subsection_name, key, filter).ok() } /// Like [`value()`][File::value()], but returning `None` if the path wasn't found. /// /// Note that this path is not vetted and should only point to resources which can't be used - /// to pose a security risk. + /// to pose a security risk. Prefer using [`path_filter()`][File::path_filter()] instead. /// /// As paths perform no conversions, this will never fail. - // TODO: add `secure_path()` or similar to make use of our knowledge of the trust associated with each configuration - // file, maybe even remove the insecure version to force every caller to ask themselves if the resource can - // be used securely or not. pub fn path( &self, section_name: impl AsRef, subsection_name: Option<&str>, key: impl AsRef, ) -> Option> { - self.raw_value(section_name, subsection_name, key) + self.path_filter(section_name, subsection_name, key, &mut |_| true) + } + + /// Like [`path()`][File::path()], but the section containing the returned value must pass `filter` as well. + /// + /// This should be the preferred way of accessing paths as those from untrusted + /// locations can be + /// + /// As paths perform no conversions, this will never fail. + pub fn path_filter( + &self, + section_name: impl AsRef, + subsection_name: Option<&str>, + key: impl AsRef, + filter: &mut MetadataFilter, + ) -> Option> { + self.raw_value_filter(section_name, subsection_name, key, filter) .ok() .map(crate::Path::from) } @@ -45,7 +70,18 @@ impl<'event> File<'event> { subsection_name: Option<&str>, key: impl AsRef, ) -> Option> { - self.raw_value(section_name, subsection_name, key) + self.boolean_filter(section_name, subsection_name, key, &mut |_| true) + } + + /// Like [`boolean()`][File::boolean()], but the section containing the returned value must pass `filter` as well. + pub fn boolean_filter( + &self, + section_name: impl AsRef, + subsection_name: Option<&str>, + key: impl AsRef, + filter: &mut MetadataFilter, + ) -> Option> { + self.raw_value_filter(section_name, subsection_name, key, filter) .ok() .map(|v| crate::Boolean::try_from(v).map(|b| b.into())) } @@ -57,7 +93,18 @@ impl<'event> File<'event> { subsection_name: Option<&str>, key: impl AsRef, ) -> Option> { - let int = self.raw_value(section_name, subsection_name, key).ok()?; + self.integer_filter(section_name, subsection_name, key, &mut |_| true) + } + + /// Like [`integer()`][File::integer()], but the section containing the returned value must pass `filter` as well. + pub fn integer_filter( + &self, + section_name: impl AsRef, + subsection_name: Option<&str>, + key: impl AsRef, + filter: &mut MetadataFilter, + ) -> Option> { + let int = self.raw_value_filter(section_name, subsection_name, key, filter).ok()?; Some(crate::Integer::try_from(int.as_ref()).and_then(|b| { b.to_decimal() .ok_or_else(|| value::Error::new("Integer overflow", int.into_owned())) @@ -74,6 +121,17 @@ impl<'event> File<'event> { self.raw_values(section_name, subsection_name, key).ok() } + /// Similar to [`strings(…)`][File::strings()], but all values are in sections that passed `filter`. + pub fn strings_filter( + &self, + section_name: impl AsRef, + subsection_name: Option<&str>, + key: impl AsRef, + filter: &mut MetadataFilter, + ) -> Option>> { + self.raw_values_filter(section_name, subsection_name, key, filter).ok() + } + /// Similar to [`values(…)`][File::values()] but returning integers if at least one of them was found /// and if none of them overflows. pub fn integers( @@ -82,16 +140,30 @@ impl<'event> File<'event> { subsection_name: Option<&str>, key: impl AsRef, ) -> Option, value::Error>> { - self.raw_values(section_name, subsection_name, key).ok().map(|values| { - values - .into_iter() - .map(|v| { - crate::Integer::try_from(v.as_ref()).and_then(|int| { - int.to_decimal() - .ok_or_else(|| value::Error::new("Integer overflow", v.into_owned())) + self.integers_filter(section_name, subsection_name, key, &mut |_| true) + } + + /// Similar to [`integers(…)`][File::integers()] but all integers are in sections that passed `filter` + /// and that are not overflowing. + pub fn integers_filter( + &self, + section_name: impl AsRef, + subsection_name: Option<&str>, + key: impl AsRef, + filter: &mut MetadataFilter, + ) -> Option, value::Error>> { + self.raw_values_filter(section_name, subsection_name, key, filter) + .ok() + .map(|values| { + values + .into_iter() + .map(|v| { + crate::Integer::try_from(v.as_ref()).and_then(|int| { + int.to_decimal() + .ok_or_else(|| value::Error::new("Integer overflow", v.into_owned())) + }) }) - }) - .collect() - }) + .collect() + }) } } diff --git a/git-config/src/file/access/mutate.rs b/git-config/src/file/access/mutate.rs index 73e9c2cd7cb..ca41d9a712c 100644 --- a/git-config/src/file/access/mutate.rs +++ b/git-config/src/file/access/mutate.rs @@ -2,7 +2,7 @@ use bstr::BStr; use git_features::threading::OwnShared; use std::borrow::Cow; -use crate::file::SectionId; +use crate::file::{MetadataFilter, SectionId}; use crate::parse::{Event, FrontMatterEvents}; use crate::{ file::{self, rename_section, SectionMut}, @@ -13,14 +13,14 @@ use crate::{ /// Mutating low-level access methods. impl<'event> File<'event> { - /// Returns an mutable section with a given name and optional subsection. + /// Returns an mutable section with a given `name` and optional `subsection_name`. pub fn section_mut<'a>( &'a mut self, - section_name: impl AsRef, + name: impl AsRef, subsection_name: Option<&str>, ) -> Result, lookup::existing::Error> { let id = self - .section_ids_by_name_and_subname(section_name.as_ref(), subsection_name)? + .section_ids_by_name_and_subname(name.as_ref(), subsection_name)? .rev() .next() .expect("BUG: Section lookup vec was empty"); @@ -31,6 +31,26 @@ impl<'event> File<'event> { )) } + /// Returns the last found mutable section with a given `name` and optional `subsection_name`, that matches `filter`. + /// + /// If there are sections matching `section_name` and `subsection_name` but the `filter` rejects all of them, `Ok(None)` + /// is returned. + pub fn section_mut_filter<'a>( + &'a mut self, + name: impl AsRef, + subsection_name: Option<&str>, + filter: &mut MetadataFilter, + ) -> Result>, lookup::existing::Error> { + let id = self + .section_ids_by_name_and_subname(name.as_ref(), subsection_name)? + .rev() + .find(|id| { + let s = &self.sections[&id]; + filter(s.meta()) + }); + Ok(id.and_then(move |id| self.sections.get_mut(&id).map(|s| s.to_mut()))) + } + /// Adds a new section. If a subsection name was provided, then /// the generated header will use the modern subsection syntax. /// Returns a reference to the new section for immediate editing. @@ -73,8 +93,8 @@ impl<'event> File<'event> { Ok(section) } - /// Removes the section, returning the events it had, if any. If multiple - /// sections have the same name, then the last one is returned. Note that + /// Removes the section with `name` and `subsection_name` , returning it if there was a matching section. + /// If multiple sections have the same name, then the last one is returned. Note that /// later sections with the same name have precedent over earlier ones. /// /// # Examples @@ -89,7 +109,7 @@ impl<'event> File<'event> { /// some-value = 4 /// "#)?; /// - /// let events = git_config.remove_section("hello", Some("world".into())); + /// let section = git_config.remove_section("hello", Some("world".into())); /// assert_eq!(git_config.to_string(), ""); /// # Ok::<(), Box>(()) /// ``` @@ -106,17 +126,17 @@ impl<'event> File<'event> { /// some-value = 5 /// "#)?; /// - /// let events = git_config.remove_section("hello", Some("world".into())); + /// let section = git_config.remove_section("hello", Some("world".into())); /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n some-value = 4\n"); /// # Ok::<(), Box>(()) /// ``` pub fn remove_section<'a>( &mut self, - section_name: &str, + name: &str, subsection_name: impl Into>, - ) -> Option> { + ) -> Option> { let id = self - .section_ids_by_name_and_subname(section_name, subsection_name.into()) + .section_ids_by_name_and_subname(name, subsection_name.into()) .ok()? .rev() .next()?; @@ -126,7 +146,31 @@ impl<'event> File<'event> { .position(|v| *v == id) .expect("known section id"), ); - self.sections.remove(&id).map(|s| s.body) + self.sections.remove(&id) + } + + /// Removes the section with `name` and `subsection_name` that passed `filter`, returning the removed section + /// if at least one section matched the `filter`. + /// If multiple sections have the same name, then the last one is returned. Note that + /// later sections with the same name have precedent over earlier ones. + pub fn remove_section_filter<'a>( + &mut self, + name: &str, + subsection_name: impl Into>, + filter: &mut MetadataFilter, + ) -> Option> { + let id = self + .section_ids_by_name_and_subname(name, subsection_name.into()) + .ok()? + .rev() + .find(|id| filter(self.sections.get(&id).expect("each id has a section").meta()))?; + self.section_order.remove( + self.section_order + .iter() + .position(|v| *v == id) + .expect("known section id"), + ); + self.sections.remove(&id) } /// Adds the provided section to the config, returning a mutable reference @@ -139,26 +183,50 @@ impl<'event> File<'event> { Ok(self.push_section_internal(section)) } - /// Renames a section, modifying the last matching section. + /// Renames the section with `name` and `subsection_name`, modifying the last matching section + /// to use `new_name` and `new_subsection_name`. pub fn rename_section<'a>( &mut self, - section_name: impl AsRef, + name: impl AsRef, subsection_name: impl Into>, - new_section_name: impl Into>, + new_name: impl Into>, new_subsection_name: impl Into>>, ) -> Result<(), rename_section::Error> { let id = self - .section_ids_by_name_and_subname(section_name.as_ref(), subsection_name.into())? + .section_ids_by_name_and_subname(name.as_ref(), subsection_name.into())? .rev() .next() .expect("list of sections were empty, which violates invariant"); let section = self.sections.get_mut(&id).expect("known section-id"); - section.header = section::Header::new(new_section_name, new_subsection_name)?; + section.header = section::Header::new(new_name, new_subsection_name)?; + Ok(()) + } + + /// Renames the section with `name` and `subsection_name`, modifying the last matching section + /// that also passes `filter` to use `new_name` and `new_subsection_name`. + /// + /// Note that the otherwise unused [`lookup::existing::Error::KeyMissing`] variant is used to indicate + /// that the `filter` rejected all candidates, leading to no section being renamed after all. + pub fn rename_section_filter<'a>( + &mut self, + name: impl AsRef, + subsection_name: impl Into>, + new_name: impl Into>, + new_subsection_name: impl Into>>, + filter: &mut MetadataFilter, + ) -> Result<(), rename_section::Error> { + let id = self + .section_ids_by_name_and_subname(name.as_ref(), subsection_name.into())? + .rev() + .find(|id| filter(self.sections.get(&id).expect("each id has a section").meta())) + .ok_or(rename_section::Error::Lookup(lookup::existing::Error::KeyMissing))?; + let section = self.sections.get_mut(&id).expect("known section-id"); + section.header = section::Header::new(new_name, new_subsection_name)?; Ok(()) } /// Append another File to the end of ourselves, without loosing any information. - pub fn append(&mut self, mut other: Self) { + pub fn append(&mut self, mut other: Self) -> &mut Self { let nl = self.detect_newline_style().to_owned(); fn ends_with_newline<'a>(it: impl DoubleEndedIterator>) -> bool { @@ -183,6 +251,7 @@ impl<'event> File<'event> { let our_last_section_before_append = (self.section_id_counter != 0).then(|| SectionId(self.section_id_counter - 1)); let mut last_added_section_id = None; + for id in std::mem::take(&mut other.section_order) { let section = other.sections.remove(&id).expect("present"); self.push_section_internal(section); @@ -194,29 +263,32 @@ impl<'event> File<'event> { } } - if !other.frontmatter_events.is_empty() { - let mut needs_nl = !starts_with_newline(other.frontmatter_events.iter()); - if let Some(id) = last_added_section_id - .or(our_last_section_before_append) - .filter(|_| needs_nl) - { - if !ends_with_newline(self.sections[&id].body.0.iter()) { - other.frontmatter_events.insert(0, newline_event()); - needs_nl = false; - } + if other.frontmatter_events.is_empty() { + return self; + } + + let mut needs_nl = !starts_with_newline(other.frontmatter_events.iter()); + if let Some(id) = last_added_section_id + .or(our_last_section_before_append) + .filter(|_| needs_nl) + { + if !ends_with_newline(self.sections[&id].body.0.iter()) { + other.frontmatter_events.insert(0, newline_event()); + needs_nl = false; } + } - match our_last_section_before_append { - Some(last_id) => assure_ends_with_newline_if( - needs_nl, - self.frontmatter_post_section.entry(last_id).or_default(), - nl.as_ref(), - ) + match our_last_section_before_append { + Some(last_id) => assure_ends_with_newline_if( + needs_nl, + self.frontmatter_post_section.entry(last_id).or_default(), + nl.as_ref(), + ) + .extend(other.frontmatter_events), + None => assure_ends_with_newline_if(needs_nl, &mut self.frontmatter_events, nl.as_ref()) .extend(other.frontmatter_events), - None => assure_ends_with_newline_if(needs_nl, &mut self.frontmatter_events, nl.as_ref()) - .extend(other.frontmatter_events), - } } + self } fn detect_newline_style(&self) -> &BStr { diff --git a/git-config/src/file/access/raw.rs b/git-config/src/file/access/raw.rs index 983a90fcb4d..cf992d111bd 100644 --- a/git-config/src/file/access/raw.rs +++ b/git-config/src/file/access/raw.rs @@ -2,6 +2,7 @@ use std::{borrow::Cow, collections::HashMap}; use bstr::BStr; +use crate::file::MetadataFilter; use crate::{ file::{mutable::multi_value::EntryData, Index, MultiValueMut, SectionMut, Size, ValueMut}, lookup, @@ -23,11 +24,30 @@ impl<'event> File<'event> { section_name: impl AsRef, subsection_name: Option<&str>, key: impl AsRef, + ) -> Result, lookup::existing::Error> { + self.raw_value_filter(section_name, subsection_name, key, &mut |_| true) + } + + /// Returns an uninterpreted value given a section, an optional subsection + /// and key, if it passes the `filter`. + /// + /// Consider [`Self::raw_values()`] if you want to get all values of + /// a multivar instead. + pub fn raw_value_filter( + &self, + section_name: impl AsRef, + subsection_name: Option<&str>, + key: impl AsRef, + filter: &mut MetadataFilter, ) -> Result, lookup::existing::Error> { let section_ids = self.section_ids_by_name_and_subname(section_name.as_ref(), subsection_name)?; let key = key.as_ref(); for section_id in section_ids.rev() { - if let Some(v) = self.sections.get(§ion_id).expect("known section id").value(key) { + let section = self.sections.get(§ion_id).expect("known section id"); + if !filter(section.meta()) { + continue; + } + if let Some(v) = section.value(key) { return Ok(v); } } @@ -45,6 +65,21 @@ impl<'event> File<'event> { section_name: impl AsRef, subsection_name: Option<&'lookup str>, key: &'lookup str, + ) -> Result, lookup::existing::Error> { + self.raw_value_mut_filter(section_name, subsection_name, key, &mut |_| true) + } + + /// Returns a mutable reference to an uninterpreted value given a section, + /// an optional subsection and key, and if it passes `filter`. + /// + /// Consider [`Self::raw_values_mut`] if you want to get mutable + /// references to all values of a multivar instead. + pub fn raw_value_mut_filter<'lookup>( + &mut self, + section_name: impl AsRef, + subsection_name: Option<&'lookup str>, + key: &'lookup str, + filter: &mut MetadataFilter, ) -> Result, lookup::existing::Error> { let mut section_ids = self .section_ids_by_name_and_subname(section_name.as_ref(), subsection_name)? @@ -55,14 +90,11 @@ impl<'event> File<'event> { let mut index = 0; let mut size = 0; let mut found_key = false; - for (i, event) in self - .sections - .get(§ion_id) - .expect("known section id") - .as_ref() - .iter() - .enumerate() - { + let section = self.sections.get(§ion_id).expect("known section id"); + if !filter(section.meta()) { + continue; + } + for (i, event) in section.as_ref().iter().enumerate() { match event { Event::SectionKey(event_key) if *event_key == key => { found_key = true; @@ -100,7 +132,10 @@ impl<'event> File<'event> { } /// Returns all uninterpreted values given a section, an optional subsection - /// and key. + /// ain order of occurrence. + /// + /// The ordering means that the last of the returned values is the one that would be the + /// value used in the single-value case.nd key. /// /// # Examples /// @@ -139,12 +174,31 @@ impl<'event> File<'event> { section_name: impl AsRef, subsection_name: Option<&str>, key: impl AsRef, + ) -> Result>, lookup::existing::Error> { + self.raw_values_filter(section_name, subsection_name, key, &mut |_| true) + } + + /// Returns all uninterpreted values given a section, an optional subsection + /// and key, if the value passes `filter`, in order of occurrence. + /// + /// The ordering means that the last of the returned values is the one that would be the + /// value used in the single-value case. + pub fn raw_values_filter( + &self, + section_name: impl AsRef, + subsection_name: Option<&str>, + key: impl AsRef, + filter: &mut MetadataFilter, ) -> Result>, lookup::existing::Error> { let mut values = Vec::new(); let section_ids = self.section_ids_by_name_and_subname(section_name.as_ref(), subsection_name)?; let key = key.as_ref(); for section_id in section_ids { - values.extend(self.sections.get(§ion_id).expect("known section id").values(key)); + let section = self.sections.get(§ion_id).expect("known section id"); + if !filter(section.meta()) { + continue; + } + values.extend(section.values(key)); } if values.is_empty() { @@ -209,6 +263,18 @@ impl<'event> File<'event> { section_name: impl AsRef, subsection_name: Option<&'lookup str>, key: &'lookup str, + ) -> Result, lookup::existing::Error> { + self.raw_values_mut_filter(section_name, subsection_name, key, &mut |_| true) + } + + /// Returns mutable references to all uninterpreted values given a section, + /// an optional subsection and key, if their sections pass `filter`. + pub fn raw_values_mut_filter<'lookup>( + &mut self, + section_name: impl AsRef, + subsection_name: Option<&'lookup str>, + key: &'lookup str, + filter: &mut MetadataFilter, ) -> Result, lookup::existing::Error> { let section_ids = self.section_ids_by_name_and_subname(section_name.as_ref(), subsection_name)?; let key = section::Key(Cow::::Borrowed(key.into())); @@ -220,14 +286,11 @@ impl<'event> File<'event> { let mut expect_value = false; let mut offset_list = Vec::new(); let mut offset_index = 0; - for (i, event) in self - .sections - .get(§ion_id) - .expect("known section-id") - .as_ref() - .iter() - .enumerate() - { + let section = self.sections.get(§ion_id).expect("known section-id"); + if !filter(section.meta()) { + continue; + } + for (i, event) in section.as_ref().iter().enumerate() { match event { Event::SectionKey(event_key) if *event_key == key => { expect_value = true; diff --git a/git-config/src/file/access/read_only.rs b/git-config/src/file/access/read_only.rs index 19aa0e4d7ef..07961aa3c53 100644 --- a/git-config/src/file/access/read_only.rs +++ b/git-config/src/file/access/read_only.rs @@ -3,7 +3,7 @@ use std::{borrow::Cow, convert::TryFrom}; use bstr::BStr; use git_features::threading::OwnShared; -use crate::file::Metadata; +use crate::file::{Metadata, MetadataFilter}; use crate::{file, lookup, File}; /// Read-only low-level access methods, as it requires generics for converting into @@ -119,24 +119,40 @@ impl<'event> File<'event> { .map_err(lookup::Error::FailedConversion) } - /// Returns the last found immutable section with a given name and optional subsection name. + /// Returns the last found immutable section with a given `name` and optional `subsection_name`. pub fn section( &mut self, - section_name: impl AsRef, + name: impl AsRef, subsection_name: Option<&str>, - ) -> Result<&file::section::Body<'event>, lookup::existing::Error> { - let id = self - .section_ids_by_name_and_subname(section_name.as_ref(), subsection_name)? - .rev() - .next() - .expect("BUG: Section lookup vec was empty"); + ) -> Result<&file::Section<'event>, lookup::existing::Error> { Ok(self - .sections - .get(&id) - .expect("BUG: Section did not have id from lookup")) + .section_filter(name, subsection_name, &mut |_| true)? + .expect("section present as we take all")) } - /// Gets all sections that match the provided name, ignoring any subsections. + /// Returns the last found immutable section with a given `name` and optional `subsection_name`, that matches `filter`. + /// + /// If there are sections matching `section_name` and `subsection_name` but the `filter` rejects all of them, `Ok(None)` + /// is returned. + pub fn section_filter<'a>( + &'a mut self, + name: impl AsRef, + subsection_name: Option<&str>, + filter: &mut MetadataFilter, + ) -> Result>, lookup::existing::Error> { + Ok(self + .section_ids_by_name_and_subname(name.as_ref(), subsection_name)? + .rev() + .find_map({ + let sections = &self.sections; + move |id| { + let s = §ions[&id]; + filter(s.meta()).then(|| s) + } + })) + } + + /// Gets all sections that match the provided `name`, ignoring any subsections. /// /// # Examples /// @@ -171,11 +187,8 @@ impl<'event> File<'event> { /// # Ok::<(), Box>(()) /// ``` #[must_use] - pub fn sections_by_name<'a>( - &'a self, - section_name: &'a str, - ) -> Option> + '_> { - self.section_ids_by_name(section_name).ok().map(move |ids| { + pub fn sections_by_name<'a>(&'a self, name: &'a str) -> Option> + '_> { + self.section_ids_by_name(name).ok().map(move |ids| { ids.map(move |id| { self.sections .get(&id) @@ -184,6 +197,24 @@ impl<'event> File<'event> { }) } + /// Gets all sections that match the provided `name`, ignoring any subsections, and pass the `filter`. + #[must_use] + pub fn sections_by_name_and_filter<'a>( + &'a self, + name: &'a str, + filter: &'a mut MetadataFilter, + ) -> Option> + '_> { + self.section_ids_by_name(name).ok().map(move |ids| { + ids.filter_map(move |id| { + let s = self + .sections + .get(&id) + .expect("section doesn't have id from from lookup"); + filter(s.meta()).then(|| s) + }) + }) + } + /// Returns the number of values in the config, no matter in which section. /// /// For example, a config with multiple empty sections will return 0. diff --git a/git-config/src/file/init/from_paths.rs b/git-config/src/file/init/from_paths.rs index 32f4dbd0bcd..1b45d6f2735 100644 --- a/git-config/src/file/init/from_paths.rs +++ b/git-config/src/file/init/from_paths.rs @@ -80,7 +80,9 @@ impl File<'static> { None => { target = Some(config); } - Some(target) => target.append(config), + Some(target) => { + target.append(config); + } } } target.ok_or(Error::NoInput) diff --git a/git-config/src/file/mod.rs b/git-config/src/file/mod.rs index 6839e712173..a92a237c1b0 100644 --- a/git-config/src/file/mod.rs +++ b/git-config/src/file/mod.rs @@ -10,7 +10,6 @@ use bstr::BStr; use git_features::threading::OwnShared; mod mutable; - pub use mutable::{multi_value::MultiValueMut, section::SectionMut, value::ValueMut}; mod init; @@ -63,6 +62,9 @@ pub struct Section<'a> { meta: OwnShared, } +/// A function to filter metadata, returning `true` if the corresponding but ommitted value can be used. +pub type MetadataFilter = dyn FnMut(&'_ Metadata) -> bool; + /// A strongly typed index into some range. #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Clone, Copy)] pub(crate) struct Index(pub(crate) usize); diff --git a/git-config/tests/file/init/from_paths/mod.rs b/git-config/tests/file/init/from_paths/mod.rs index 6246f96d032..d9c33e36b4f 100644 --- a/git-config/tests/file/init/from_paths/mod.rs +++ b/git-config/tests/file/init/from_paths/mod.rs @@ -98,7 +98,7 @@ fn frontmatter_is_maintained_in_multiple_files() -> crate::Result { } #[test] -fn multiple_paths_multi_value() -> crate::Result { +fn multiple_paths_multi_value_and_filter() -> crate::Result { let dir = tempdir()?; let a_path = dir.path().join("a"); @@ -136,6 +136,18 @@ fn multiple_paths_multi_value() -> crate::Result { Some(vec![cow_str("a"), cow_str("b"), cow_str("c"),]) ); + assert_eq!( + config.string_filter("core", None, "key", &mut |m| m.source == Source::System), + Some(cow_str("a")), + "the filter discards all values with higher priority" + ); + + assert_eq!( + config.strings_filter("core", None, "key", &mut |m| m.source == Source::Global + || m.source == Source::User), + Some(vec![cow_str("b"), cow_str("c")]) + ); + assert_eq!( config.strings("include", None, "path"), Some(vec![cow_str("d_path"), cow_str("e_path")]) From e5ba0f532bf9bfee46d2dab24e6a6503df4d239d Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 17 Jul 2022 15:44:00 +0800 Subject: [PATCH 234/366] thanks clippy --- git-config/src/file/access/mutate.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/git-config/src/file/access/mutate.rs b/git-config/src/file/access/mutate.rs index ca41d9a712c..f0c233d2013 100644 --- a/git-config/src/file/access/mutate.rs +++ b/git-config/src/file/access/mutate.rs @@ -45,7 +45,7 @@ impl<'event> File<'event> { .section_ids_by_name_and_subname(name.as_ref(), subsection_name)? .rev() .find(|id| { - let s = &self.sections[&id]; + let s = &self.sections[id]; filter(s.meta()) }); Ok(id.and_then(move |id| self.sections.get_mut(&id).map(|s| s.to_mut()))) @@ -163,7 +163,7 @@ impl<'event> File<'event> { .section_ids_by_name_and_subname(name, subsection_name.into()) .ok()? .rev() - .find(|id| filter(self.sections.get(&id).expect("each id has a section").meta()))?; + .find(|id| filter(self.sections.get(id).expect("each id has a section").meta()))?; self.section_order.remove( self.section_order .iter() @@ -218,7 +218,7 @@ impl<'event> File<'event> { let id = self .section_ids_by_name_and_subname(name.as_ref(), subsection_name.into())? .rev() - .find(|id| filter(self.sections.get(&id).expect("each id has a section").meta())) + .find(|id| filter(self.sections.get(id).expect("each id has a section").meta())) .ok_or(rename_section::Error::Lookup(lookup::existing::Error::KeyMissing))?; let section = self.sections.get_mut(&id).expect("known section-id"); section.header = section::Header::new(new_name, new_subsection_name)?; From 0ad1c9a5280cc172432b5258e0f79898721bac68 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 17 Jul 2022 16:24:45 +0800 Subject: [PATCH 235/366] feat: `File::frontmatter()` and `File::sections_and_postmatter()`. (#331) --- etc/check-package-size.sh | 2 +- git-config/src/file/access/read_only.rs | 30 ++++++++++++++++++-- git-config/src/types.rs | 9 ++++++ git-config/tests/file/init/from_paths/mod.rs | 16 +++++++++++ 4 files changed, 54 insertions(+), 3 deletions(-) diff --git a/etc/check-package-size.sh b/etc/check-package-size.sh index 8a73946e988..77eab01e7a3 100755 --- a/etc/check-package-size.sh +++ b/etc/check-package-size.sh @@ -28,7 +28,7 @@ echo "in root: gitoxide CLI" (enter git-bitmap && indent cargo diet -n --package-size-limit 5KB) (enter git-tempfile && indent cargo diet -n --package-size-limit 25KB) (enter git-lock && indent cargo diet -n --package-size-limit 15KB) -(enter git-config && indent cargo diet -n --package-size-limit 80KB) +(enter git-config && indent cargo diet -n --package-size-limit 85KB) (enter git-hash && indent cargo diet -n --package-size-limit 20KB) (enter git-chunk && indent cargo diet -n --package-size-limit 10KB) (enter git-rebase && indent cargo diet -n --package-size-limit 5KB) diff --git a/git-config/src/file/access/read_only.rs b/git-config/src/file/access/read_only.rs index 07961aa3c53..51c6801c7a6 100644 --- a/git-config/src/file/access/read_only.rs +++ b/git-config/src/file/access/read_only.rs @@ -226,18 +226,20 @@ impl<'event> File<'event> { /// Returns if there are no entries in the config. This will return true /// if there are only empty sections, with whitespace and comments not being considered - /// 'empty'. + /// void. #[must_use] pub fn is_void(&self) -> bool { self.sections.values().all(|s| s.body.is_void()) } /// Return the file's metadata to guide filtering of all values upon retrieval. + /// + /// This is the metadata the file was instantiated with for use in all newly created sections. pub fn meta(&self) -> &Metadata { &*self.meta } - /// Return the file's metadata to guide filtering of all values upon retrieval, wrapped for shared ownership. + /// Similar to [`meta()`][File::meta()], but with shared ownership. pub fn meta_owned(&self) -> OwnShared { OwnShared::clone(&self.meta) } @@ -246,4 +248,28 @@ impl<'event> File<'event> { pub fn sections(&self) -> impl Iterator> + '_ { self.section_order.iter().map(move |id| &self.sections[id]) } + + /// Return an iterator over all sections along with non-section events that are placed right after them, + /// in order of occurrence in the file itself. + /// + /// This allows to reproduce the look of sections perfectly when serializing them with + /// [`write_to()`][file::Section::write_to()]. + pub fn sections_and_postmatter( + &self, + ) -> impl Iterator, Vec<&crate::parse::Event<'event>>)> { + self.section_order.iter().map(move |id| { + let s = &self.sections[id]; + let pm: Vec<_> = self + .frontmatter_post_section + .get(id) + .map(|events| events.iter().collect()) + .unwrap_or_default(); + (s, pm) + }) + } + + /// Return all events which are in front of the first of our sections, or `None` if there are none. + pub fn frontmatter(&self) -> Option>> { + (!self.frontmatter_events.is_empty()).then(|| self.frontmatter_events.iter()) + } } diff --git a/git-config/src/types.rs b/git-config/src/types.rs index 08b96cbee2f..8fa77cf4d51 100644 --- a/git-config/src/types.rs +++ b/git-config/src/types.rs @@ -66,6 +66,15 @@ pub enum Source { /// key `a` with the above config will fetch `d` or replace `d`, since the last /// valid config key/value pair is `a = d`: /// +/// # Filtering +/// +/// All methods exist in a `*_filter(…, filter)` version to allow skipping sections by +/// their metadata. That way it's possible to select values based on their `git_sec::Trust` +/// for example, or by their location. +/// +/// Note that the filter may be executed even on sections that don't contain the key in question, +/// even though the section will have matched the `name` and `subsection_name` respectively. +/// /// ``` /// # use std::borrow::Cow; /// # use std::convert::TryFrom; diff --git a/git-config/tests/file/init/from_paths/mod.rs b/git-config/tests/file/init/from_paths/mod.rs index d9c33e36b4f..ca4d8702e6d 100644 --- a/git-config/tests/file/init/from_paths/mod.rs +++ b/git-config/tests/file/init/from_paths/mod.rs @@ -94,6 +94,22 @@ fn frontmatter_is_maintained_in_multiple_files() -> crate::Result { ";before a\n[core]\na = true\n;before b\n [core]\nb = true\n# nothing in c\n; nothing in d\n;before a\n[core]\na = true\n;before b\n [core]\nb = true\n# nothing in c\n; nothing in d", "other files post-section matter works as well, adding newlines as needed" ); + + assert_eq!( + config + .frontmatter() + .expect("present") + .map(|e| e.to_string()) + .collect::>() + .join(""), + ";before a\n" + ); + + assert_eq!( + config.sections_and_postmatter().count(), + 4, + "we trust rust here and don't validate it's actually what we think it is" + ); Ok(()) } From a50a3964dbf01982b5a2c9a8ccd469332b6f9ca1 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 15 Jul 2022 15:19:47 +0800 Subject: [PATCH 236/366] try to fix attributes, once more (#331) --- git-config/tests/.gitattributes | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/git-config/tests/.gitattributes b/git-config/tests/.gitattributes index 5a9d49f190e..d0e2adc08ab 100644 --- a/git-config/tests/.gitattributes +++ b/git-config/tests/.gitattributes @@ -1,3 +1,8 @@ # assure line feeds don't interfere with our working copy hash +<<<<<<< HEAD /fixtures/**/* text crlf=input eol=lf +======= +/fixtures/**/* text eol=lf +/fixtures/repo-config.crlf text eol=crlf +>>>>>>> 207e48362 (try to fix attributes, once more (#331)) From 5d0a5c0712fbd8fcc00aff54563c83281afc9476 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 17 Jul 2022 16:41:03 +0800 Subject: [PATCH 237/366] forced checkin to fix strange crlf issue --- git-config/tests/fixtures/repo-config.crlf | 28 +++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/git-config/tests/fixtures/repo-config.crlf b/git-config/tests/fixtures/repo-config.crlf index 96672b93484..a7cd0042827 100644 --- a/git-config/tests/fixtures/repo-config.crlf +++ b/git-config/tests/fixtures/repo-config.crlf @@ -1,14 +1,14 @@ -; hello -# world - -[core] - repositoryformatversion = 0 - bare = true -[other] - repositoryformatversion = 0 - - bare = true - -# before section with newline -[foo] - repositoryformatversion = 0 +; hello +# world + +[core] + repositoryformatversion = 0 + bare = true +[other] + repositoryformatversion = 0 + + bare = true + +# before section with newline +[foo] + repositoryformatversion = 0 From a7417664ca1e41936f9de8cf066e13aeaf9b0d75 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 17 Jul 2022 18:46:41 +0800 Subject: [PATCH 238/366] fix git-config/tests/.gitattributes --- git-config/tests/.gitattributes | 9 --------- 1 file changed, 9 deletions(-) diff --git a/git-config/tests/.gitattributes b/git-config/tests/.gitattributes index 4f310552ac0..272372614d0 100644 --- a/git-config/tests/.gitattributes +++ b/git-config/tests/.gitattributes @@ -1,13 +1,4 @@ # assure line feeds don't interfere with our working copy hash -<<<<<<< HEAD -<<<<<<< HEAD -/fixtures/**/* text crlf=input eol=lf -======= /fixtures/**/* text eol=lf /fixtures/repo-config.crlf text eol=crlf ->>>>>>> 207e48362 (try to fix attributes, once more (#331)) -======= -/fixtures/**/* text eol=lf -/fixtures/repo-config.crlf text eol=crlf ->>>>>>> config-metadata From 32d5b3c695d868ba93755123a25b276bfbe55e0a Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 17 Jul 2022 18:45:15 +0800 Subject: [PATCH 239/366] `parse::Events::from_bytes()` with `filter` support. (#331) --- git-config/src/file/impls.rs | 2 +- git-config/src/file/init/from_paths.rs | 2 ++ git-config/src/parse/events.rs | 16 +++++++++------- git-config/tests/parse/from_bytes.rs | 4 ++-- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/git-config/src/file/impls.rs b/git-config/src/file/impls.rs index 6909ce05e7a..60c71b0d1d1 100644 --- a/git-config/src/file/impls.rs +++ b/git-config/src/file/impls.rs @@ -30,7 +30,7 @@ impl<'a> TryFrom<&'a BStr> for File<'a> { /// Convenience constructor. Attempts to parse the provided byte string into /// a [`File`]. See [`Events::from_bytes()`][parse::Events::from_bytes()] for more information. fn try_from(value: &'a BStr) -> Result, Self::Error> { - parse::Events::from_bytes(value).map(|events| Self::from_parse_events(events, Metadata::api())) + parse::Events::from_bytes(value, None).map(|events| Self::from_parse_events(events, Metadata::api())) } } diff --git a/git-config/src/file/init/from_paths.rs b/git-config/src/file/init/from_paths.rs index 1b45d6f2735..029156e9aa3 100644 --- a/git-config/src/file/init/from_paths.rs +++ b/git-config/src/file/init/from_paths.rs @@ -29,6 +29,8 @@ pub enum Error { pub struct Options<'a> { /// Configure how to follow includes while handling paths. pub resolve_includes: file::resolve_includes::Options<'a>, + // /// The way parse events should be processed. + // pub events: crate::parse::Mode, } /// Instantiation from one or more paths diff --git a/git-config/src/parse/events.rs b/git-config/src/parse/events.rs index 3ea8e6ce415..2295a583a48 100644 --- a/git-config/src/parse/events.rs +++ b/git-config/src/parse/events.rs @@ -222,9 +222,9 @@ pub struct Events<'a> { impl Events<'static> { /// Parses the provided bytes, returning an [`Events`] that contains allocated /// and owned events. This is similar to [`Events::from_bytes()`], but performance - /// is degraded as it requires allocation for every event. However, this permits - /// the `input` bytes to be dropped and he parser to be passed around - /// without lifetime worries. + /// is degraded as it requires allocation for every event. + /// + /// Use `filter` to only include those events for which it returns true. pub fn from_bytes_owned<'a>( input: &'a [u8], filter: Option) -> bool>, @@ -238,8 +238,10 @@ impl<'a> Events<'a> { /// [`Events`] that provides methods to accessing leading comments and sections /// of a `git-config` file and can be converted into an iterator of [`Event`] /// for higher level processing. - pub fn from_bytes(input: &'a [u8]) -> Result, parse::Error> { - from_bytes(input, std::convert::identity, None) + /// + /// Use `filter` to only include those events for which it returns true. + pub fn from_bytes(input: &'a [u8], filter: Option) -> bool>) -> Result, parse::Error> { + from_bytes(input, std::convert::identity, filter) } /// Attempt to zero-copy parse the provided `input` string. @@ -248,7 +250,7 @@ impl<'a> Events<'a> { /// isn't guaranteed. #[allow(clippy::should_implement_trait)] pub fn from_str(input: &'a str) -> Result, parse::Error> { - Self::from_bytes(input.as_bytes()) + Self::from_bytes(input.as_bytes(), None) } /// Consumes the parser to produce an iterator of all contained events. @@ -280,7 +282,7 @@ impl<'a> TryFrom<&'a [u8]> for Events<'a> { type Error = parse::Error; fn try_from(value: &'a [u8]) -> Result { - Events::from_bytes(value) + Events::from_bytes(value, None) } } diff --git a/git-config/tests/parse/from_bytes.rs b/git-config/tests/parse/from_bytes.rs index 91acb2c5d04..7b24f2b58f3 100644 --- a/git-config/tests/parse/from_bytes.rs +++ b/git-config/tests/parse/from_bytes.rs @@ -152,8 +152,8 @@ fn skips_bom() { "; assert_eq!( - Events::from_bytes(bytes), - Events::from_bytes(bytes_with_gb18030_bom.as_bytes()) + Events::from_bytes(bytes, None), + Events::from_bytes(bytes_with_gb18030_bom.as_bytes(), None) ); assert_eq!( Events::from_bytes_owned(bytes, None), From d003c0f139d61e3bd998a0283a9c7af25a60db02 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 17 Jul 2022 19:25:44 +0800 Subject: [PATCH 240/366] feat!: Support for `lossy` load mode. (#331) There is a lot of breaking changes as `file::from_paths::Options` now became `file::init::Options`, and the same goes for the error type. --- .../file/{resolve_includes.rs => includes.rs} | 0 git-config/src/file/init/from_env.rs | 6 ++-- git-config/src/file/init/from_paths.rs | 36 +++++++++++++++---- .../init/{resolve_includes.rs => includes.rs} | 8 ++--- git-config/src/file/init/mod.rs | 3 +- git-config/src/file/mod.rs | 2 +- git-config/tests/file/init/from_env.rs | 6 ++-- .../includes/conditional/gitdir/util.rs | 8 ++--- .../from_paths/includes/conditional/mod.rs | 7 ++-- .../includes/conditional/onbranch.rs | 7 ++-- .../init/from_paths/includes/unconditional.rs | 17 +++++---- git-repository/src/config.rs | 5 +-- 12 files changed, 67 insertions(+), 38 deletions(-) rename git-config/src/file/{resolve_includes.rs => includes.rs} (100%) rename git-config/src/file/init/{resolve_includes.rs => includes.rs} (97%) diff --git a/git-config/src/file/resolve_includes.rs b/git-config/src/file/includes.rs similarity index 100% rename from git-config/src/file/resolve_includes.rs rename to git-config/src/file/includes.rs diff --git a/git-config/src/file/init/from_env.rs b/git-config/src/file/init/from_env.rs index a0c07d3a896..98f1a3075ce 100644 --- a/git-config/src/file/init/from_env.rs +++ b/git-config/src/file/init/from_env.rs @@ -5,7 +5,7 @@ use std::{borrow::Cow, path::PathBuf}; use crate::file::Metadata; use crate::{ file, - file::{from_paths, init::resolve_includes}, + file::{from_paths, init::includes}, parse::section, path::interpolate, File, Source, @@ -113,7 +113,7 @@ impl File<'static> { /// environment variable for more information. /// /// [`git-config`'s documentation]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-GITCONFIGCOUNT - pub fn from_env(options: crate::file::resolve_includes::Options<'_>) -> Result>, Error> { + pub fn from_env(options: crate::file::includes::Options<'_>) -> Result>, Error> { use std::env; let count: usize = match env::var("GIT_CONFIG_COUNT") { Ok(v) => v.parse().map_err(|_| Error::InvalidConfigCount { input: v })?, @@ -164,7 +164,7 @@ impl File<'static> { } let mut buf = Vec::new(); - resolve_includes(&mut config, meta, &mut buf, options)?; + includes::resolve(&mut config, meta, &mut buf, options)?; Ok(Some(config)) } } diff --git a/git-config/src/file/init/from_paths.rs b/git-config/src/file/init/from_paths.rs index 029156e9aa3..a0b6daac4de 100644 --- a/git-config/src/file/init/from_paths.rs +++ b/git-config/src/file/init/from_paths.rs @@ -1,5 +1,6 @@ use crate::file::Metadata; -use crate::{file, file::init::resolve_includes, parse, path::interpolate, File}; +use crate::parse::Event; +use crate::{file, file::init::includes, parse, path::interpolate, File}; use git_features::threading::OwnShared; /// The error returned by [`File::from_paths_metadata()`] and [`File::from_env_paths()`]. @@ -28,9 +29,12 @@ pub enum Error { #[derive(Clone, Copy, Default)] pub struct Options<'a> { /// Configure how to follow includes while handling paths. - pub resolve_includes: file::resolve_includes::Options<'a>, - // /// The way parse events should be processed. - // pub events: crate::parse::Mode, + pub includes: file::includes::Options<'a>, + /// If true, only value-bearing parse events will be kept to reduce memory usage and increase performance. + /// + /// Note that doing so will prevent [`write_to()`][File::write_to()] to serialize itself meaningfully and correctly, + /// as newlines will be missing. Use this only if it's clear that serialization will not be attempted. + pub lossy: bool, } /// Instantiation from one or more paths @@ -51,7 +55,10 @@ impl File<'static> { path: impl Into, buf: &mut Vec, mut meta: file::Metadata, - options: Options<'_>, + Options { + includes: include_options, + lossy, + }: Options<'_>, ) -> Result { let path = path.into(); buf.clear(); @@ -59,9 +66,12 @@ impl File<'static> { meta.path = path.into(); let meta = OwnShared::new(meta); - let mut config = Self::from_parse_events(parse::Events::from_bytes_owned(buf, None)?, OwnShared::clone(&meta)); + let mut config = Self::from_parse_events( + parse::Events::from_bytes_owned(buf, if lossy { Some(discard_nonessential_events) } else { None })?, + OwnShared::clone(&meta), + ); let mut buf = Vec::new(); - resolve_includes(&mut config, meta, &mut buf, options.resolve_includes)?; + includes::resolve(&mut config, meta, &mut buf, include_options)?; Ok(config) } @@ -90,3 +100,15 @@ impl File<'static> { target.ok_or(Error::NoInput) } } + +fn discard_nonessential_events(e: &Event<'_>) -> bool { + match e { + Event::Whitespace(_) | Event::Comment(_) | Event::Newline(_) => false, + Event::SectionHeader(_) + | Event::SectionKey(_) + | Event::KeyValueSeparator + | Event::Value(_) + | Event::ValueNotDone(_) + | Event::ValueDone(_) => true, + } +} diff --git a/git-config/src/file/init/resolve_includes.rs b/git-config/src/file/init/includes.rs similarity index 97% rename from git-config/src/file/init/resolve_includes.rs rename to git-config/src/file/init/includes.rs index 5c1a0c1f9ae..c7676f169ed 100644 --- a/git-config/src/file/init/resolve_includes.rs +++ b/git-config/src/file/init/includes.rs @@ -7,11 +7,11 @@ use bstr::{BStr, BString, ByteSlice, ByteVec}; use git_features::threading::OwnShared; use git_ref::Category; -use crate::file::resolve_includes::{conditional, Options}; +use crate::file::includes::{conditional, Options}; use crate::file::Metadata; use crate::{file, file::init::from_paths, File}; -pub(crate) fn resolve_includes( +pub(crate) fn resolve( conf: &mut File<'static>, meta: OwnShared, buf: &mut Vec, @@ -75,7 +75,7 @@ fn append_followed_includes_recursively( buf: &mut Vec, ) -> Result<(), from_paths::Error> { for config_path in include_paths { - let config_path = resolve(config_path, target_config_path, options)?; + let config_path = resolve_path(config_path, target_config_path, options)?; if !config_path.is_file() { continue; } @@ -222,7 +222,7 @@ fn gitdir_matches( )) } -fn resolve( +fn resolve_path( path: crate::Path<'_>, target_config_path: Option<&Path>, Options { diff --git a/git-config/src/file/init/mod.rs b/git-config/src/file/init/mod.rs index c80ba07f647..ec954b853b4 100644 --- a/git-config/src/file/init/mod.rs +++ b/git-config/src/file/init/mod.rs @@ -7,8 +7,7 @@ pub mod from_env; /// pub mod from_paths; -mod resolve_includes; -pub(crate) use resolve_includes::resolve_includes; +pub(crate) mod includes; impl<'a> File<'a> { /// Return an empty `File` with the given `meta`-data to be attached to all new sections. diff --git a/git-config/src/file/mod.rs b/git-config/src/file/mod.rs index a92a237c1b0..2100f99dc2a 100644 --- a/git-config/src/file/mod.rs +++ b/git-config/src/file/mod.rs @@ -24,7 +24,7 @@ mod utils; pub mod section; /// -pub mod resolve_includes; +pub mod includes; /// pub mod rename_section { diff --git a/git-config/tests/file/init/from_env.rs b/git-config/tests/file/init/from_env.rs index ec837e828a2..0e66f6b09f4 100644 --- a/git-config/tests/file/init/from_env.rs +++ b/git-config/tests/file/init/from_env.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, env, fs}; -use git_config::file::resolve_includes; +use git_config::file::includes; use git_config::{ file::{from_env, from_paths}, File, @@ -113,7 +113,7 @@ fn error_on_relative_paths_in_include_paths() { .set("GIT_CONFIG_KEY_0", "include.path") .set("GIT_CONFIG_VALUE_0", "some_git_config"); - let res = File::from_env(resolve_includes::Options { + let res = File::from_env(includes::Options { max_depth: 1, ..Default::default() }); @@ -143,7 +143,7 @@ fn follow_include_paths() { .set("GIT_CONFIG_KEY_3", "include.origin.path") .set("GIT_CONFIG_VALUE_3", escape_backslashes(b_path)); - let config = File::from_env(resolve_includes::Options { + let config = File::from_env(includes::Options { max_depth: 1, ..Default::default() }) diff --git a/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs b/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs index 29faca7dc17..2aa95b2db6d 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs @@ -7,7 +7,7 @@ use std::{ }; use bstr::{BString, ByteSlice}; -use git_config::file::{from_paths, resolve_includes}; +use git_config::file::{from_paths, includes}; use crate::file::{ cow_str, @@ -84,13 +84,13 @@ impl GitEnv { } impl GitEnv { - pub fn include_options(&self) -> resolve_includes::Options<'_> { - self.to_from_paths_options().resolve_includes + pub fn include_options(&self) -> includes::Options<'_> { + self.to_from_paths_options().includes } pub fn to_from_paths_options(&self) -> from_paths::Options<'_> { let mut opts = options_with_git_dir(self.git_dir()); - opts.resolve_includes.interpolate.home_dir = Some(self.home_dir()); + opts.includes.interpolate.home_dir = Some(self.home_dir()); opts } diff --git a/git-config/tests/file/init/from_paths/includes/conditional/mod.rs b/git-config/tests/file/init/from_paths/includes/conditional/mod.rs index cb50999cb97..63a913da8d7 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/mod.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/mod.rs @@ -1,6 +1,6 @@ use std::{fs, path::Path}; -use git_config::file::resolve_includes; +use git_config::file::includes; use git_config::{file::from_paths, path, File}; use tempfile::tempdir; @@ -82,16 +82,17 @@ fn include_and_includeif_correct_inclusion_order() -> crate::Result { fn options_with_git_dir(git_dir: &Path) -> from_paths::Options<'_> { from_paths::Options { - resolve_includes: resolve_includes::Options::follow( + includes: includes::Options::follow( path::interpolate::Context { home_dir: Some(git_dir.parent().unwrap()), ..Default::default() }, - resolve_includes::conditional::Context { + includes::conditional::Context { git_dir: Some(git_dir), ..Default::default() }, ), + ..Default::default() } } diff --git a/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs b/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs index da77440beb1..3cd5b427135 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs @@ -4,8 +4,8 @@ use std::{ }; use bstr::{BString, ByteSlice}; -use git_config::file::resolve_includes::conditional; -use git_config::file::{from_paths, resolve_includes}; +use git_config::file::includes::conditional; +use git_config::file::{from_paths, includes}; use git_ref::{ transaction::{Change, PreviousValue, RefEdit}, FullName, Target, @@ -235,13 +235,14 @@ value = branch-override-by-include let branch_name = FullName::try_from(BString::from(branch_name))?; let options = from_paths::Options { - resolve_includes: resolve_includes::Options::follow( + includes: includes::Options::follow( Default::default(), conditional::Context { branch_name: Some(branch_name.as_ref()), ..Default::default() }, ), + ..Default::default() }; let config = git_config::File::from_paths_metadata( diff --git a/git-config/tests/file/init/from_paths/includes/unconditional.rs b/git-config/tests/file/init/from_paths/includes/unconditional.rs index 41c9a348422..2b332a2524a 100644 --- a/git-config/tests/file/init/from_paths/includes/unconditional.rs +++ b/git-config/tests/file/init/from_paths/includes/unconditional.rs @@ -1,6 +1,6 @@ use std::fs; -use git_config::file::resolve_includes; +use git_config::file::includes; use git_config::{file::from_paths, File}; use tempfile::tempdir; @@ -9,7 +9,8 @@ use crate::file::{cow_str, init::from_paths::escape_backslashes}; fn follow_options() -> from_paths::Options<'static> { from_paths::Options { - resolve_includes: resolve_includes::Options::follow(Default::default(), Default::default()), + includes: includes::Options::follow(Default::default(), Default::default()), + ..Default::default() } } @@ -119,11 +120,12 @@ fn respect_max_depth() -> crate::Result { fn make_options(max_depth: u8, error_on_max_depth_exceeded: bool) -> from_paths::Options<'static> { from_paths::Options { - resolve_includes: resolve_includes::Options { + includes: includes::Options { max_depth, error_on_max_depth_exceeded, ..Default::default() }, + ..Default::default() } } @@ -135,7 +137,8 @@ fn respect_max_depth() -> crate::Result { // with default max_allowed_depth of 10 and 4 levels of includes, last level is read let options = from_paths::Options { - resolve_includes: resolve_includes::Options::follow(Default::default(), Default::default()), + includes: includes::Options::follow(Default::default(), Default::default()), + ..Default::default() }; let config = File::from_paths_metadata(into_meta(vec![dir.path().join("0")]), options)?; assert_eq!(config.integer("core", None, "i"), Some(Ok(4))); @@ -237,11 +240,12 @@ fn cycle_detection() -> crate::Result { )?; let options = from_paths::Options { - resolve_includes: resolve_includes::Options { + includes: includes::Options { max_depth: 4, error_on_max_depth_exceeded: true, ..Default::default() }, + ..Default::default() }; let config = File::from_paths_metadata(into_meta(vec![a_path.clone()]), options); assert!(matches!( @@ -250,11 +254,12 @@ fn cycle_detection() -> crate::Result { )); let options = from_paths::Options { - resolve_includes: resolve_includes::Options { + includes: includes::Options { max_depth: 4, error_on_max_depth_exceeded: false, ..Default::default() }, + ..Default::default() }; let config = File::from_paths_metadata(into_meta(vec![a_path]), options)?; assert_eq!(config.integers("core", None, "b"), Some(Ok(vec![0, 1, 0, 1, 0]))); diff --git a/git-repository/src/config.rs b/git-repository/src/config.rs index 6fb9274c35c..c83da1747a2 100644 --- a/git-repository/src/config.rs +++ b/git-repository/src/config.rs @@ -72,13 +72,14 @@ mod cache { &mut buf, git_config::file::Metadata::from(git_config::Source::Local), git_config::file::from_paths::Options { - resolve_includes: git_config::file::resolve_includes::Options::follow( + lossy: true, + includes: git_config::file::includes::Options::follow( git_config::path::interpolate::Context { git_install_dir, home_dir: None, home_for_user: None, // TODO: figure out how to configure this }, - git_config::file::resolve_includes::conditional::Context { + git_config::file::includes::conditional::Context { git_dir: git_dir.into(), branch_name: None, }, From 230a523593afcfb8720db965ff56265aaceea772 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 17 Jul 2022 20:30:02 +0800 Subject: [PATCH 241/366] =?UTF-8?q?change!:=20untangle=20`file::init::?= =?UTF-8?q?=E2=80=A6`=20`Option`=20and=20`Error`=20types.=20(#331)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This moves types to where they belong which is more specific instead of having a catch-all `Error` and `Options` type. --- git-config/src/file/includes.rs | 75 --------- git-config/src/file/init/from_env.rs | 19 +-- git-config/src/file/init/from_paths.rs | 34 +--- git-config/src/file/init/includes.rs | 145 ++++++++++++++++-- git-config/src/file/init/mod.rs | 7 +- git-config/src/file/init/types.rs | 28 ++++ git-config/src/file/mod.rs | 7 +- git-config/tests/file/init/from_env.rs | 9 +- .../includes/conditional/gitdir/mod.rs | 9 +- .../includes/conditional/gitdir/util.rs | 4 +- .../from_paths/includes/conditional/mod.rs | 9 +- .../includes/conditional/onbranch.rs | 6 +- .../init/from_paths/includes/unconditional.rs | 31 ++-- git-config/tests/file/init/from_paths/mod.rs | 2 +- 14 files changed, 214 insertions(+), 171 deletions(-) delete mode 100644 git-config/src/file/includes.rs create mode 100644 git-config/src/file/init/types.rs diff --git a/git-config/src/file/includes.rs b/git-config/src/file/includes.rs deleted file mode 100644 index 25bc68534ca..00000000000 --- a/git-config/src/file/includes.rs +++ /dev/null @@ -1,75 +0,0 @@ -/// Options to handle includes, like `include.path` or `includeIf..path`, -#[derive(Clone, Copy)] -pub struct Options<'a> { - /// The maximum allowed length of the file include chain built by following nested resolve_includes where base level is depth = 0. - pub max_depth: u8, - /// When max depth is exceeded while following nested includes, - /// return an error if true or silently stop following resolve_includes. - /// - /// Setting this value to false allows to read configuration with cycles, - /// which otherwise always results in an error. - pub error_on_max_depth_exceeded: bool, - - /// Used during path interpolation, both for include paths before trying to read the file, and for - /// paths used in conditional `gitdir` includes. - pub interpolate: crate::path::interpolate::Context<'a>, - - /// Additional context for conditional includes to work. - pub conditional: conditional::Context<'a>, -} - -impl Options<'_> { - /// Provide options to never follow include directives at all. - pub fn no_follow() -> Self { - Options { - max_depth: 0, - error_on_max_depth_exceeded: false, - interpolate: Default::default(), - conditional: Default::default(), - } - } -} - -impl<'a> Options<'a> { - /// Provide options to follow includes like git does, provided the required `conditional` and `interpolate` contexts - /// to support `gitdir` and `onbranch` based `includeIf` directives as well as standard `include.path` resolution. - /// Note that the follow-mode is `git`-style, following at most 10 indirections while - /// producing an error if the depth is exceeded. - pub fn follow(interpolate: crate::path::interpolate::Context<'a>, conditional: conditional::Context<'a>) -> Self { - Options { - max_depth: 10, - error_on_max_depth_exceeded: true, - interpolate, - conditional, - } - } - - /// Set the context used for interpolation when interpolating paths to include as well as the paths - /// in `gitdir` conditional includes. - pub fn interpolate_with(mut self, context: crate::path::interpolate::Context<'a>) -> Self { - self.interpolate = context; - self - } -} - -impl Default for Options<'_> { - fn default() -> Self { - Self::no_follow() - } -} - -/// -pub mod conditional { - /// Options to handle conditional includes like `includeIf..path`. - #[derive(Clone, Copy, Default)] - pub struct Context<'a> { - /// The location of the .git directory. If `None`, `gitdir` conditions cause an error. - /// - /// Used for conditional includes, e.g. `includeIf.gitdir:…` or `includeIf:gitdir/i…`. - pub git_dir: Option<&'a std::path::Path>, - /// The name of the branch that is currently checked out. If `None`, `onbranch` conditions cause an error. - /// - /// Used for conditional includes, e.g. `includeIf.onbranch:main.…` - pub branch_name: Option<&'a git_ref::FullNameRef>, - } -} diff --git a/git-config/src/file/init/from_env.rs b/git-config/src/file/init/from_env.rs index 98f1a3075ce..b39552f8947 100644 --- a/git-config/src/file/init/from_env.rs +++ b/git-config/src/file/init/from_env.rs @@ -2,14 +2,8 @@ use git_features::threading::OwnShared; use std::convert::TryFrom; use std::{borrow::Cow, path::PathBuf}; -use crate::file::Metadata; -use crate::{ - file, - file::{from_paths, init::includes}, - parse::section, - path::interpolate, - File, Source, -}; +use crate::file::{init, Metadata}; +use crate::{file, parse::section, path::interpolate, File, Source}; /// Represents the errors that may occur when calling [`File::from_env`][crate::File::from_env()]. #[derive(Debug, thiserror::Error)] @@ -26,7 +20,7 @@ pub enum Error { #[error(transparent)] PathInterpolationError(#[from] interpolate::Error), #[error(transparent)] - FromPathsError(#[from] from_paths::Error), + Includes(#[from] init::includes::Error), #[error(transparent)] Section(#[from] section::header::Error), #[error(transparent)] @@ -40,7 +34,7 @@ impl File<'static> { /// /// See for details. // TODO: how does this relate to the `fs` module? Have a feeling options should contain instructions on which files to use. - pub fn from_env_paths(options: from_paths::Options<'_>) -> Result, from_paths::Error> { + pub fn from_env_paths(options: init::Options<'_>) -> Result, init::from_paths::Error> { use std::env; let mut metas = vec![]; @@ -113,7 +107,8 @@ impl File<'static> { /// environment variable for more information. /// /// [`git-config`'s documentation]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-GITCONFIGCOUNT - pub fn from_env(options: crate::file::includes::Options<'_>) -> Result>, Error> { + // TODO: use `init::Options` instead for lossy support. + pub fn from_env(options: init::includes::Options<'_>) -> Result>, Error> { use std::env; let count: usize = match env::var("GIT_CONFIG_COUNT") { Ok(v) => v.parse().map_err(|_| Error::InvalidConfigCount { input: v })?, @@ -164,7 +159,7 @@ impl File<'static> { } let mut buf = Vec::new(); - includes::resolve(&mut config, meta, &mut buf, options)?; + init::includes::resolve(&mut config, meta, &mut buf, options)?; Ok(Some(config)) } } diff --git a/git-config/src/file/init/from_paths.rs b/git-config/src/file/init/from_paths.rs index a0b6daac4de..5f229c937a2 100644 --- a/git-config/src/file/init/from_paths.rs +++ b/git-config/src/file/init/from_paths.rs @@ -1,6 +1,7 @@ -use crate::file::Metadata; +use crate::file::init::Options; +use crate::file::{init, Metadata}; use crate::parse::Event; -use crate::{file, file::init::includes, parse, path::interpolate, File}; +use crate::{file, file::init::includes, parse, File}; use git_features::threading::OwnShared; /// The error returned by [`File::from_paths_metadata()`] and [`File::from_env_paths()`]. @@ -10,33 +11,11 @@ pub enum Error { #[error(transparent)] Io(#[from] std::io::Error), #[error(transparent)] - Parse(#[from] parse::Error), - #[error(transparent)] - Interpolate(#[from] interpolate::Error), - #[error("The maximum allowed length {} of the file include chain built by following nested resolve_includes is exceeded", .max_depth)] - IncludeDepthExceeded { max_depth: u8 }, - #[error("Include paths from environment variables must not be relative as no config file paths exists as root")] - MissingConfigPath, - #[error("The git directory must be provided to support `gitdir:` conditional includes")] - MissingGitDir, - #[error(transparent)] - Realpath(#[from] git_path::realpath::Error), + Init(#[from] init::Error), #[error("Not a single path was provided to load the configuration from")] NoInput, } -/// Options when loading git config using [`File::from_paths_metadata()`]. -#[derive(Clone, Copy, Default)] -pub struct Options<'a> { - /// Configure how to follow includes while handling paths. - pub includes: file::includes::Options<'a>, - /// If true, only value-bearing parse events will be kept to reduce memory usage and increase performance. - /// - /// Note that doing so will prevent [`write_to()`][File::write_to()] to serialize itself meaningfully and correctly, - /// as newlines will be missing. Use this only if it's clear that serialization will not be attempted. - pub lossy: bool, -} - /// Instantiation from one or more paths impl File<'static> { /// Load the file at `path` from `source` without following include directives. Note that the path will be checked for @@ -67,11 +46,12 @@ impl File<'static> { meta.path = path.into(); let meta = OwnShared::new(meta); let mut config = Self::from_parse_events( - parse::Events::from_bytes_owned(buf, if lossy { Some(discard_nonessential_events) } else { None })?, + parse::Events::from_bytes_owned(buf, if lossy { Some(discard_nonessential_events) } else { None }) + .map_err(init::Error::from)?, OwnShared::clone(&meta), ); let mut buf = Vec::new(); - includes::resolve(&mut config, meta, &mut buf, include_options)?; + includes::resolve(&mut config, meta, &mut buf, include_options).map_err(init::Error::from)?; Ok(config) } diff --git a/git-config/src/file/init/includes.rs b/git-config/src/file/init/includes.rs index c7676f169ed..cd3b2dea397 100644 --- a/git-config/src/file/init/includes.rs +++ b/git-config/src/file/init/includes.rs @@ -7,17 +7,16 @@ use bstr::{BStr, BString, ByteSlice, ByteVec}; use git_features::threading::OwnShared; use git_ref::Category; -use crate::file::includes::{conditional, Options}; -use crate::file::Metadata; +use crate::file::{init, Metadata}; use crate::{file, file::init::from_paths, File}; pub(crate) fn resolve( - conf: &mut File<'static>, + config: &mut File<'static>, meta: OwnShared, buf: &mut Vec, options: Options<'_>, -) -> Result<(), from_paths::Error> { - resolve_includes_recursive(conf, meta, 0, buf, options) +) -> Result<(), Error> { + resolve_includes_recursive(config, meta, 0, buf, options) } fn resolve_includes_recursive( @@ -26,10 +25,10 @@ fn resolve_includes_recursive( depth: u8, buf: &mut Vec, options: Options<'_>, -) -> Result<(), from_paths::Error> { +) -> Result<(), Error> { if depth == options.max_depth { return if options.error_on_max_depth_exceeded { - Err(from_paths::Error::IncludeDepthExceeded { + Err(Error::IncludeDepthExceeded { max_depth: options.max_depth, }) } else { @@ -73,7 +72,7 @@ fn append_followed_includes_recursively( meta: OwnShared, options: Options<'_>, buf: &mut Vec, -) -> Result<(), from_paths::Error> { +) -> Result<(), Error> { for config_path in include_paths { let config_path = resolve_path(config_path, target_config_path, options)?; if !config_path.is_file() { @@ -86,8 +85,14 @@ fn append_followed_includes_recursively( level: meta.level + 1, source: meta.source, }; - let no_follow_options = from_paths::Options::default(); - let mut include_config = File::from_path_with_buf(config_path, buf, config_meta, no_follow_options)?; + + let no_follow_options = init::Options::default(); + let mut include_config = + File::from_path_with_buf(config_path, buf, config_meta, no_follow_options).map_err(|err| match err { + from_paths::Error::Io(err) => Error::Io(err), + from_paths::Error::Init(init::Error::Parse(err)) => Error::Parse(err), + err => unreachable!("BUG: {:?} shouldn't be possible here", err), + })?; let config_meta = include_config.meta_owned(); resolve_includes_recursive(&mut include_config, config_meta, depth + 1, buf, options)?; @@ -111,7 +116,7 @@ fn include_condition_match( condition: &BStr, target_config_path: Option<&Path>, options: Options<'_>, -) -> Result { +) -> Result { let mut tokens = condition.splitn(2, |b| *b == b':'); let (prefix, condition) = match (tokens.next(), tokens.next()) { (Some(a), Some(b)) => (a, b), @@ -170,9 +175,8 @@ fn gitdir_matches( .. }: Options<'_>, wildmatch_mode: git_glob::wildmatch::Mode, -) -> Result { - let git_dir = - git_path::to_unix_separators_on_windows(git_path::into_bstr(git_dir.ok_or(from_paths::Error::MissingGitDir)?)); +) -> Result { + let git_dir = git_path::to_unix_separators_on_windows(git_path::into_bstr(git_dir.ok_or(Error::MissingGitDir)?)); let mut pattern_path: Cow<'_, _> = { let path = crate::Path::from(Cow::Borrowed(condition_path)).interpolate(context)?; @@ -185,7 +189,7 @@ fn gitdir_matches( if let Some(relative_pattern_path) = pattern_path.strip_prefix(b"./") { let parent_dir = target_config_path - .ok_or(from_paths::Error::MissingConfigPath)? + .ok_or(Error::MissingConfigPath)? .parent() .expect("config path can never be /"); let mut joined_path = git_path::to_unix_separators_on_windows(git_path::into_bstr(parent_dir)).into_owned(); @@ -228,11 +232,11 @@ fn resolve_path( Options { interpolate: context, .. }: Options<'_>, -) -> Result { +) -> Result { let path = path.interpolate(context)?; let path: PathBuf = if path.is_relative() { target_config_path - .ok_or(from_paths::Error::MissingConfigPath)? + .ok_or(Error::MissingConfigPath)? .parent() .expect("path is a config file which naturally lives in a directory") .join(path) @@ -241,3 +245,110 @@ fn resolve_path( }; Ok(path) } + +mod types { + use crate::parse; + use crate::path::interpolate; + + /// The error returned by [`File::from_paths_metadata()`] and [`File::from_env_paths()`]. + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error(transparent)] + Io(#[from] std::io::Error), + #[error(transparent)] + Parse(#[from] parse::Error), + #[error(transparent)] + Interpolate(#[from] interpolate::Error), + #[error("The maximum allowed length {} of the file include chain built by following nested resolve_includes is exceeded", .max_depth)] + IncludeDepthExceeded { max_depth: u8 }, + #[error( + "Include paths from environment variables must not be relative as no config file paths exists as root" + )] + MissingConfigPath, + #[error("The git directory must be provided to support `gitdir:` conditional includes")] + MissingGitDir, + #[error(transparent)] + Realpath(#[from] git_path::realpath::Error), + } + + /// Options to handle includes, like `include.path` or `includeIf..path`, + #[derive(Clone, Copy)] + pub struct Options<'a> { + /// The maximum allowed length of the file include chain built by following nested resolve_includes where base level is depth = 0. + pub max_depth: u8, + /// When max depth is exceeded while following nested includes, + /// return an error if true or silently stop following resolve_includes. + /// + /// Setting this value to false allows to read configuration with cycles, + /// which otherwise always results in an error. + pub error_on_max_depth_exceeded: bool, + + /// Used during path interpolation, both for include paths before trying to read the file, and for + /// paths used in conditional `gitdir` includes. + pub interpolate: crate::path::interpolate::Context<'a>, + + /// Additional context for conditional includes to work. + pub conditional: conditional::Context<'a>, + } + + impl Options<'_> { + /// Provide options to never follow include directives at all. + pub fn no_follow() -> Self { + Options { + max_depth: 0, + error_on_max_depth_exceeded: false, + interpolate: Default::default(), + conditional: Default::default(), + } + } + } + + impl<'a> Options<'a> { + /// Provide options to follow includes like git does, provided the required `conditional` and `interpolate` contexts + /// to support `gitdir` and `onbranch` based `includeIf` directives as well as standard `include.path` resolution. + /// Note that the follow-mode is `git`-style, following at most 10 indirections while + /// producing an error if the depth is exceeded. + pub fn follow( + interpolate: crate::path::interpolate::Context<'a>, + conditional: conditional::Context<'a>, + ) -> Self { + Options { + max_depth: 10, + error_on_max_depth_exceeded: true, + interpolate, + conditional, + } + } + + /// Set the context used for interpolation when interpolating paths to include as well as the paths + /// in `gitdir` conditional includes. + pub fn interpolate_with(mut self, context: crate::path::interpolate::Context<'a>) -> Self { + self.interpolate = context; + self + } + } + + impl Default for Options<'_> { + fn default() -> Self { + Self::no_follow() + } + } + + /// + pub mod conditional { + /// Options to handle conditional includes like `includeIf..path`. + #[derive(Clone, Copy, Default)] + pub struct Context<'a> { + /// The location of the .git directory. If `None`, `gitdir` conditions cause an error. + /// + /// Used for conditional includes, e.g. `includeIf.gitdir:…` or `includeIf:gitdir/i…`. + pub git_dir: Option<&'a std::path::Path>, + /// The name of the branch that is currently checked out. If `None`, `onbranch` conditions cause an error. + /// + /// Used for conditional includes, e.g. `includeIf.onbranch:main.…` + pub branch_name: Option<&'a git_ref::FullNameRef>, + } + } +} +pub use types::{conditional, Error, Options}; diff --git a/git-config/src/file/init/mod.rs b/git-config/src/file/init/mod.rs index ec954b853b4..e99d54b8b38 100644 --- a/git-config/src/file/init/mod.rs +++ b/git-config/src/file/init/mod.rs @@ -2,12 +2,15 @@ use crate::file::{section, Metadata}; use crate::{parse, File}; use git_features::threading::OwnShared; +mod types; +pub use types::{Error, Options}; + /// pub mod from_env; /// pub mod from_paths; - -pub(crate) mod includes; +/// +pub mod includes; impl<'a> File<'a> { /// Return an empty `File` with the given `meta`-data to be attached to all new sections. diff --git a/git-config/src/file/init/types.rs b/git-config/src/file/init/types.rs new file mode 100644 index 00000000000..60f5ae1d0b3 --- /dev/null +++ b/git-config/src/file/init/types.rs @@ -0,0 +1,28 @@ +use crate::file::init; +use crate::parse; +use crate::path::interpolate; + +/// The error returned by [`File::from_paths_metadata()`][crate::File::from_paths_metadata()] and +/// [`File::from_env_paths()`][crate::File::from_env_paths()]. +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error { + #[error(transparent)] + Parse(#[from] parse::Error), + #[error(transparent)] + Interpolate(#[from] interpolate::Error), + #[error(transparent)] + Includes(#[from] init::includes::Error), +} + +/// Options when loading git config using [`File::from_paths_metadata()`]. +#[derive(Clone, Copy, Default)] +pub struct Options<'a> { + /// Configure how to follow includes while handling paths. + pub includes: init::includes::Options<'a>, + /// If true, only value-bearing parse events will be kept to reduce memory usage and increase performance. + /// + /// Note that doing so will prevent [`write_to()`][File::write_to()] to serialize itself meaningfully and correctly, + /// as newlines will be missing. Use this only if it's clear that serialization will not be attempted. + pub lossy: bool, +} diff --git a/git-config/src/file/mod.rs b/git-config/src/file/mod.rs index 2100f99dc2a..4645276b653 100644 --- a/git-config/src/file/mod.rs +++ b/git-config/src/file/mod.rs @@ -12,8 +12,8 @@ use git_features::threading::OwnShared; mod mutable; pub use mutable::{multi_value::MultiValueMut, section::SectionMut, value::ValueMut}; -mod init; -pub use init::{from_env, from_paths}; +/// +pub mod init; mod access; mod impls; @@ -23,9 +23,6 @@ mod utils; /// pub mod section; -/// -pub mod includes; - /// pub mod rename_section { /// The error returned by [`File::rename_section(…)`][crate::File::rename_section()]. diff --git a/git-config/tests/file/init/from_env.rs b/git-config/tests/file/init/from_env.rs index 0e66f6b09f4..991b98e975b 100644 --- a/git-config/tests/file/init/from_env.rs +++ b/git-config/tests/file/init/from_env.rs @@ -1,10 +1,7 @@ use std::{borrow::Cow, env, fs}; -use git_config::file::includes; -use git_config::{ - file::{from_env, from_paths}, - File, -}; +use git_config::file::init::includes; +use git_config::{file::init::from_env, File}; use serial_test::serial; use tempfile::tempdir; @@ -119,7 +116,7 @@ fn error_on_relative_paths_in_include_paths() { }); assert!(matches!( res, - Err(from_env::Error::FromPathsError(from_paths::Error::MissingConfigPath)) + Err(from_env::Error::Includes(includes::Error::MissingConfigPath)) )); } diff --git a/git-config/tests/file/init/from_paths/includes/conditional/gitdir/mod.rs b/git-config/tests/file/init/from_paths/includes/conditional/gitdir/mod.rs index 07bc12f8e8f..846389a2cd9 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/gitdir/mod.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/gitdir/mod.rs @@ -87,7 +87,6 @@ fn dot_slash_path_is_replaced_with_directory_containing_the_including_config_fil #[test] #[serial] fn dot_slash_from_environment_causes_error() -> crate::Result { - use git_config::file::from_paths; let env = GitEnv::repo_name("worktree")?; { @@ -103,8 +102,8 @@ fn dot_slash_from_environment_causes_error() -> crate::Result { assert!( matches!( res, - Err(git_config::file::from_env::Error::FromPathsError( - from_paths::Error::MissingConfigPath + Err(git_config::file::init::from_env::Error::Includes( + git_config::file::init::includes::Error::MissingConfigPath )) ), "this is a failure of resolving the include path, after trying to include it" @@ -122,8 +121,8 @@ fn dot_slash_from_environment_causes_error() -> crate::Result { assert!( matches!( res, - Err(git_config::file::from_env::Error::FromPathsError( - from_paths::Error::MissingConfigPath + Err(git_config::file::init::from_env::Error::Includes( + git_config::file::init::includes::Error::MissingConfigPath )) ), "here the pattern path tries to be resolved and fails as target config isn't set" diff --git a/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs b/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs index 2aa95b2db6d..f3c295000cd 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs @@ -7,7 +7,7 @@ use std::{ }; use bstr::{BString, ByteSlice}; -use git_config::file::{from_paths, includes}; +use git_config::file::init::{self, includes}; use crate::file::{ cow_str, @@ -88,7 +88,7 @@ impl GitEnv { self.to_from_paths_options().includes } - pub fn to_from_paths_options(&self) -> from_paths::Options<'_> { + pub fn to_from_paths_options(&self) -> init::Options<'_> { let mut opts = options_with_git_dir(self.git_dir()); opts.includes.interpolate.home_dir = Some(self.home_dir()); opts diff --git a/git-config/tests/file/init/from_paths/includes/conditional/mod.rs b/git-config/tests/file/init/from_paths/includes/conditional/mod.rs index 63a913da8d7..993bc82127d 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/mod.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/mod.rs @@ -1,7 +1,8 @@ use std::{fs, path::Path}; -use git_config::file::includes; -use git_config::{file::from_paths, path, File}; +use git_config::file::init; +use git_config::file::init::includes; +use git_config::{path, File}; use tempfile::tempdir; use crate::file::{cow_str, init::from_paths::escape_backslashes}; @@ -80,8 +81,8 @@ fn include_and_includeif_correct_inclusion_order() -> crate::Result { Ok(()) } -fn options_with_git_dir(git_dir: &Path) -> from_paths::Options<'_> { - from_paths::Options { +fn options_with_git_dir(git_dir: &Path) -> init::Options<'_> { + init::Options { includes: includes::Options::follow( path::interpolate::Context { home_dir: Some(git_dir.parent().unwrap()), diff --git a/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs b/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs index 3cd5b427135..d538ef89fb8 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs @@ -4,8 +4,8 @@ use std::{ }; use bstr::{BString, ByteSlice}; -use git_config::file::includes::conditional; -use git_config::file::{from_paths, includes}; +use git_config::file::init::includes::conditional; +use git_config::file::init::{self, includes}; use git_ref::{ transaction::{Change, PreviousValue, RefEdit}, FullName, Target, @@ -234,7 +234,7 @@ value = branch-override-by-include )?; let branch_name = FullName::try_from(BString::from(branch_name))?; - let options = from_paths::Options { + let options = init::Options { includes: includes::Options::follow( Default::default(), conditional::Context { diff --git a/git-config/tests/file/init/from_paths/includes/unconditional.rs b/git-config/tests/file/init/from_paths/includes/unconditional.rs index 2b332a2524a..d52aee9f6b2 100644 --- a/git-config/tests/file/init/from_paths/includes/unconditional.rs +++ b/git-config/tests/file/init/from_paths/includes/unconditional.rs @@ -1,14 +1,15 @@ use std::fs; -use git_config::file::includes; -use git_config::{file::from_paths, File}; +use git_config::file::init; +use git_config::file::init::includes; +use git_config::{file::init::from_paths, File}; use tempfile::tempdir; use crate::file::init::from_paths::into_meta; use crate::file::{cow_str, init::from_paths::escape_backslashes}; -fn follow_options() -> from_paths::Options<'static> { - from_paths::Options { +fn follow_options() -> init::Options<'static> { + init::Options { includes: includes::Options::follow(Default::default(), Default::default()), ..Default::default() } @@ -118,8 +119,8 @@ fn respect_max_depth() -> crate::Result { let config = File::from_paths_metadata(into_meta(vec![dir.path().join("0")]), follow_options())?; assert_eq!(config.integers("core", None, "i"), Some(Ok(vec![0, 1, 2, 3, 4]))); - fn make_options(max_depth: u8, error_on_max_depth_exceeded: bool) -> from_paths::Options<'static> { - from_paths::Options { + fn make_options(max_depth: u8, error_on_max_depth_exceeded: bool) -> init::Options<'static> { + init::Options { includes: includes::Options { max_depth, error_on_max_depth_exceeded, @@ -136,7 +137,7 @@ fn respect_max_depth() -> crate::Result { assert_eq!(config.integer("core", None, "i"), Some(Ok(1))); // with default max_allowed_depth of 10 and 4 levels of includes, last level is read - let options = from_paths::Options { + let options = init::Options { includes: includes::Options::follow(Default::default(), Default::default()), ..Default::default() }; @@ -153,7 +154,9 @@ fn respect_max_depth() -> crate::Result { let config = File::from_paths_metadata(into_meta(vec![dir.path().join("0")]), options); assert!(matches!( config.unwrap_err(), - from_paths::Error::IncludeDepthExceeded { max_depth: 2 } + from_paths::Error::Init(init::Error::Includes(includes::Error::IncludeDepthExceeded { + max_depth: 2 + })) )); // with max_allowed_depth of 2 and 4 levels of includes and error_on_max_depth_exceeded: false , max_allowed_depth is exceeded and the value of level 2 is returned @@ -166,7 +169,9 @@ fn respect_max_depth() -> crate::Result { let config = File::from_paths_metadata(into_meta(vec![dir.path().join("0")]), options); assert!(matches!( config.unwrap_err(), - from_paths::Error::IncludeDepthExceeded { max_depth: 0 } + from_paths::Error::Init(init::Error::Includes(includes::Error::IncludeDepthExceeded { + max_depth: 0 + })) )); Ok(()) } @@ -239,7 +244,7 @@ fn cycle_detection() -> crate::Result { ), )?; - let options = from_paths::Options { + let options = init::Options { includes: includes::Options { max_depth: 4, error_on_max_depth_exceeded: true, @@ -250,10 +255,12 @@ fn cycle_detection() -> crate::Result { let config = File::from_paths_metadata(into_meta(vec![a_path.clone()]), options); assert!(matches!( config.unwrap_err(), - from_paths::Error::IncludeDepthExceeded { max_depth: 4 } + from_paths::Error::Init(init::Error::Includes(includes::Error::IncludeDepthExceeded { + max_depth: 4 + })) )); - let options = from_paths::Options { + let options = init::Options { includes: includes::Options { max_depth: 4, error_on_max_depth_exceeded: false, diff --git a/git-config/tests/file/init/from_paths/mod.rs b/git-config/tests/file/init/from_paths/mod.rs index ca4d8702e6d..c6be00cf3a8 100644 --- a/git-config/tests/file/init/from_paths/mod.rs +++ b/git-config/tests/file/init/from_paths/mod.rs @@ -19,7 +19,7 @@ mod from_path_no_includes { let err = git_config::File::from_path_no_includes(config_path, git_config::Source::Local).unwrap_err(); assert!( - matches!(err, git_config::file::from_paths::Error::Io(io_error) if io_error.kind() == std::io::ErrorKind::NotFound) + matches!(err, git_config::file::init::from_paths::Error::Io(io_error) if io_error.kind() == std::io::ErrorKind::NotFound) ); } From c9423db5381064296d22f48b532f29d3e8162ce9 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 17 Jul 2022 20:36:29 +0800 Subject: [PATCH 242/366] adapt to changes in `git-config` (#331) --- git-repository/src/config.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/git-repository/src/config.rs b/git-repository/src/config.rs index c83da1747a2..a622f05f994 100644 --- a/git-repository/src/config.rs +++ b/git-repository/src/config.rs @@ -3,7 +3,7 @@ use crate::{bstr::BString, permission}; #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Could not open repository conifguration file")] - Open(#[from] git_config::file::from_paths::Error), + Open(#[from] git_config::file::init::from_paths::Error), #[error("Cannot handle objects formatted as {:?}", .name)] UnsupportedObjectFormat { name: BString }, #[error("The value for '{}' cannot be empty", .key)] @@ -71,15 +71,15 @@ mod cache { &git_dir.join("config"), &mut buf, git_config::file::Metadata::from(git_config::Source::Local), - git_config::file::from_paths::Options { + git_config::file::init::Options { lossy: true, - includes: git_config::file::includes::Options::follow( + includes: git_config::file::init::includes::Options::follow( git_config::path::interpolate::Context { git_install_dir, home_dir: None, home_for_user: None, // TODO: figure out how to configure this }, - git_config::file::includes::conditional::Context { + git_config::file::init::includes::conditional::Context { git_dir: git_dir.into(), branch_name: None, }, From 88c6b185b2e51858b140e4378a5b5730b5cb4075 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 17 Jul 2022 20:45:15 +0800 Subject: [PATCH 243/366] 'lossy' is now inherited by includes processing (#331) --- git-config/src/file/init/from_env.rs | 2 +- git-config/src/file/init/from_paths.rs | 18 +++++++------ git-config/src/file/init/includes.rs | 25 ++++++++++--------- git-config/tests/file/init/from_env.rs | 15 ++++++++--- .../includes/conditional/gitdir/mod.rs | 6 ++--- .../includes/conditional/gitdir/util.rs | 10 +++----- 6 files changed, 42 insertions(+), 34 deletions(-) diff --git a/git-config/src/file/init/from_env.rs b/git-config/src/file/init/from_env.rs index b39552f8947..dc136b27a3a 100644 --- a/git-config/src/file/init/from_env.rs +++ b/git-config/src/file/init/from_env.rs @@ -108,7 +108,7 @@ impl File<'static> { /// /// [`git-config`'s documentation]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-GITCONFIGCOUNT // TODO: use `init::Options` instead for lossy support. - pub fn from_env(options: init::includes::Options<'_>) -> Result>, Error> { + pub fn from_env(options: init::Options<'_>) -> Result>, Error> { use std::env; let count: usize = match env::var("GIT_CONFIG_COUNT") { Ok(v) => v.parse().map_err(|_| Error::InvalidConfigCount { input: v })?, diff --git a/git-config/src/file/init/from_paths.rs b/git-config/src/file/init/from_paths.rs index 5f229c937a2..2087bd749a3 100644 --- a/git-config/src/file/init/from_paths.rs +++ b/git-config/src/file/init/from_paths.rs @@ -34,10 +34,7 @@ impl File<'static> { path: impl Into, buf: &mut Vec, mut meta: file::Metadata, - Options { - includes: include_options, - lossy, - }: Options<'_>, + options: Options<'_>, ) -> Result { let path = path.into(); buf.clear(); @@ -46,12 +43,19 @@ impl File<'static> { meta.path = path.into(); let meta = OwnShared::new(meta); let mut config = Self::from_parse_events( - parse::Events::from_bytes_owned(buf, if lossy { Some(discard_nonessential_events) } else { None }) - .map_err(init::Error::from)?, + parse::Events::from_bytes_owned( + buf, + if options.lossy { + Some(discard_nonessential_events) + } else { + None + }, + ) + .map_err(init::Error::from)?, OwnShared::clone(&meta), ); let mut buf = Vec::new(); - includes::resolve(&mut config, meta, &mut buf, include_options).map_err(init::Error::from)?; + includes::resolve(&mut config, meta, &mut buf, options).map_err(init::Error::from)?; Ok(config) } diff --git a/git-config/src/file/init/includes.rs b/git-config/src/file/init/includes.rs index cd3b2dea397..ef820f154de 100644 --- a/git-config/src/file/init/includes.rs +++ b/git-config/src/file/init/includes.rs @@ -14,7 +14,7 @@ pub(crate) fn resolve( config: &mut File<'static>, meta: OwnShared, buf: &mut Vec, - options: Options<'_>, + options: init::Options<'_>, ) -> Result<(), Error> { resolve_includes_recursive(config, meta, 0, buf, options) } @@ -24,12 +24,12 @@ fn resolve_includes_recursive( meta: OwnShared, depth: u8, buf: &mut Vec, - options: Options<'_>, + options: init::Options<'_>, ) -> Result<(), Error> { - if depth == options.max_depth { - return if options.error_on_max_depth_exceeded { + if depth == options.includes.max_depth { + return if options.includes.error_on_max_depth_exceeded { Err(Error::IncludeDepthExceeded { - max_depth: options.max_depth, + max_depth: options.includes.max_depth, }) } else { Ok(()) @@ -46,7 +46,7 @@ fn resolve_includes_recursive( detach_include_paths(&mut include_paths, section) } else if header_name == "includeIf" { if let Some(condition) = &header.subsection_name { - if include_condition_match(condition.as_ref(), target_config_path, options)? { + if include_condition_match(condition.as_ref(), target_config_path, options.includes)? { detach_include_paths(&mut include_paths, section) } } @@ -70,11 +70,11 @@ fn append_followed_includes_recursively( target_config_path: Option<&Path>, depth: u8, meta: OwnShared, - options: Options<'_>, + options: init::Options<'_>, buf: &mut Vec, ) -> Result<(), Error> { for config_path in include_paths { - let config_path = resolve_path(config_path, target_config_path, options)?; + let config_path = resolve_path(config_path, target_config_path, options.includes.interpolate)?; if !config_path.is_file() { continue; } @@ -86,7 +86,10 @@ fn append_followed_includes_recursively( source: meta.source, }; - let no_follow_options = init::Options::default(); + let no_follow_options = init::Options { + lossy: options.lossy, + ..Default::default() + }; let mut include_config = File::from_path_with_buf(config_path, buf, config_meta, no_follow_options).map_err(|err| match err { from_paths::Error::Io(err) => Error::Io(err), @@ -229,9 +232,7 @@ fn gitdir_matches( fn resolve_path( path: crate::Path<'_>, target_config_path: Option<&Path>, - Options { - interpolate: context, .. - }: Options<'_>, + context: crate::path::interpolate::Context<'_>, ) -> Result { let path = path.interpolate(context)?; let path: PathBuf = if path.is_relative() { diff --git a/git-config/tests/file/init/from_env.rs b/git-config/tests/file/init/from_env.rs index 991b98e975b..4d600be0c15 100644 --- a/git-config/tests/file/init/from_env.rs +++ b/git-config/tests/file/init/from_env.rs @@ -1,5 +1,6 @@ use std::{borrow::Cow, env, fs}; +use git_config::file::init; use git_config::file::init::includes; use git_config::{file::init::from_env, File}; use serial_test::serial; @@ -110,8 +111,11 @@ fn error_on_relative_paths_in_include_paths() { .set("GIT_CONFIG_KEY_0", "include.path") .set("GIT_CONFIG_VALUE_0", "some_git_config"); - let res = File::from_env(includes::Options { - max_depth: 1, + let res = File::from_env(init::Options { + includes: includes::Options { + max_depth: 1, + ..Default::default() + }, ..Default::default() }); assert!(matches!( @@ -140,8 +144,11 @@ fn follow_include_paths() { .set("GIT_CONFIG_KEY_3", "include.origin.path") .set("GIT_CONFIG_VALUE_3", escape_backslashes(b_path)); - let config = File::from_env(includes::Options { - max_depth: 1, + let config = File::from_env(init::Options { + includes: includes::Options { + max_depth: 1, + ..Default::default() + }, ..Default::default() }) .unwrap() diff --git a/git-config/tests/file/init/from_paths/includes/conditional/gitdir/mod.rs b/git-config/tests/file/init/from_paths/includes/conditional/gitdir/mod.rs index 846389a2cd9..797674b4007 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/gitdir/mod.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/gitdir/mod.rs @@ -98,7 +98,7 @@ fn dot_slash_from_environment_causes_error() -> crate::Result { ) .set("GIT_CONFIG_VALUE_0", "./include.path"); - let res = git_config::File::from_env(env.include_options()); + let res = git_config::File::from_env(env.to_init_options()); assert!( matches!( res, @@ -117,7 +117,7 @@ fn dot_slash_from_environment_causes_error() -> crate::Result { .set("GIT_CONFIG_KEY_0", "includeIf.gitdir:./worktree/.path") .set("GIT_CONFIG_VALUE_0", &absolute_path); - let res = git_config::File::from_env(env.include_options()); + let res = git_config::File::from_env(env.to_init_options()); assert!( matches!( res, @@ -138,7 +138,7 @@ fn dot_slash_from_environment_causes_error() -> crate::Result { ) .set("GIT_CONFIG_VALUE_0", absolute_path); - let res = git_config::File::from_env(env.include_options()); + let res = git_config::File::from_env(env.to_init_options()); assert!(res.is_ok(), "missing paths are ignored as before"); } diff --git a/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs b/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs index f3c295000cd..d07472c43e1 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs @@ -7,7 +7,7 @@ use std::{ }; use bstr::{BString, ByteSlice}; -use git_config::file::init::{self, includes}; +use git_config::file::init::{self}; use crate::file::{ cow_str, @@ -84,11 +84,7 @@ impl GitEnv { } impl GitEnv { - pub fn include_options(&self) -> includes::Options<'_> { - self.to_from_paths_options().includes - } - - pub fn to_from_paths_options(&self) -> init::Options<'_> { + pub fn to_init_options(&self) -> init::Options<'_> { let mut opts = options_with_git_dir(self.git_dir()); opts.includes.interpolate.home_dir = Some(self.home_dir()); opts @@ -132,7 +128,7 @@ pub fn assert_section_value( paths .into_iter() .map(|path| git_config::file::Metadata::try_from_path(path, git_config::Source::Local).unwrap()), - env.to_from_paths_options(), + env.to_init_options(), )?; assert_eq!( From 5e8127b395bd564129b20a1db2d59d39307a2857 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 17 Jul 2022 21:12:14 +0800 Subject: [PATCH 244/366] a test for lossy File parsing (#331) It works, and could be broken if the event fliter was dropping essential events. --- git-config/src/file/impls.rs | 7 +- git-config/src/file/init/from_paths.rs | 25 +-- git-config/src/file/init/mod.rs | 21 ++- git-config/src/file/init/types.rs | 23 +++ git-config/tests/file/access/read_only.rs | 190 +++++++++++----------- git-config/tests/file/mod.rs | 6 +- 6 files changed, 150 insertions(+), 122 deletions(-) diff --git a/git-config/src/file/impls.rs b/git-config/src/file/impls.rs index 60c71b0d1d1..2f898609f27 100644 --- a/git-config/src/file/impls.rs +++ b/git-config/src/file/impls.rs @@ -10,7 +10,7 @@ impl FromStr for File<'static> { fn from_str(s: &str) -> Result { parse::Events::from_bytes_owned(s.as_bytes(), None) - .map(|events| File::from_parse_events(events, Metadata::api())) + .map(|events| File::from_parse_events_no_includes(events, Metadata::api())) } } @@ -20,7 +20,7 @@ impl<'a> TryFrom<&'a str> for File<'a> { /// Convenience constructor. Attempts to parse the provided string into a /// [`File`]. See [`Events::from_str()`][crate::parse::Events::from_str()] for more information. fn try_from(s: &'a str) -> Result, Self::Error> { - parse::Events::from_str(s).map(|events| Self::from_parse_events(events, Metadata::api())) + parse::Events::from_str(s).map(|events| Self::from_parse_events_no_includes(events, Metadata::api())) } } @@ -30,7 +30,8 @@ impl<'a> TryFrom<&'a BStr> for File<'a> { /// Convenience constructor. Attempts to parse the provided byte string into /// a [`File`]. See [`Events::from_bytes()`][parse::Events::from_bytes()] for more information. fn try_from(value: &'a BStr) -> Result, Self::Error> { - parse::Events::from_bytes(value, None).map(|events| Self::from_parse_events(events, Metadata::api())) + parse::Events::from_bytes(value, None) + .map(|events| Self::from_parse_events_no_includes(events, Metadata::api())) } } diff --git a/git-config/src/file/init/from_paths.rs b/git-config/src/file/init/from_paths.rs index 2087bd749a3..008e7d09cc3 100644 --- a/git-config/src/file/init/from_paths.rs +++ b/git-config/src/file/init/from_paths.rs @@ -1,6 +1,5 @@ use crate::file::init::Options; use crate::file::{init, Metadata}; -use crate::parse::Event; use crate::{file, file::init::includes, parse, File}; use git_features::threading::OwnShared; @@ -42,16 +41,8 @@ impl File<'static> { meta.path = path.into(); let meta = OwnShared::new(meta); - let mut config = Self::from_parse_events( - parse::Events::from_bytes_owned( - buf, - if options.lossy { - Some(discard_nonessential_events) - } else { - None - }, - ) - .map_err(init::Error::from)?, + let mut config = Self::from_parse_events_no_includes( + parse::Events::from_bytes_owned(buf, options.to_event_filter()).map_err(init::Error::from)?, OwnShared::clone(&meta), ); let mut buf = Vec::new(); @@ -84,15 +75,3 @@ impl File<'static> { target.ok_or(Error::NoInput) } } - -fn discard_nonessential_events(e: &Event<'_>) -> bool { - match e { - Event::Whitespace(_) | Event::Comment(_) | Event::Newline(_) => false, - Event::SectionHeader(_) - | Event::SectionKey(_) - | Event::KeyValueSeparator - | Event::Value(_) - | Event::ValueNotDone(_) - | Event::ValueDone(_) => true, - } -} diff --git a/git-config/src/file/init/mod.rs b/git-config/src/file/init/mod.rs index e99d54b8b38..f55953ac844 100644 --- a/git-config/src/file/init/mod.rs +++ b/git-config/src/file/init/mod.rs @@ -1,4 +1,4 @@ -use crate::file::{section, Metadata}; +use crate::file::{init, section, Metadata}; use crate::{parse, File}; use git_features::threading::OwnShared; @@ -25,11 +25,24 @@ impl<'a> File<'a> { meta: meta.into(), } } + + /// Instantiate a new `File` from given `input`, associating each section and their values with + /// `meta`-data, while respecting `options`. + pub fn from_bytes_no_includes( + input: &'a [u8], + meta: impl Into>, + options: init::Options<'_>, + ) -> Result { + let meta = meta.into(); + Ok(Self::from_parse_events_no_includes( + parse::Events::from_bytes(input, options.to_event_filter())?, + meta.clone(), + )) + } + /// Instantiate a new `File` from given `events`, associating each section and their values with /// `meta`-data. - /// - /// That way, one can search for values fulfilling a particular requirements. - pub fn from_parse_events( + pub fn from_parse_events_no_includes( parse::Events { frontmatter, sections }: parse::Events<'a>, meta: impl Into>, ) -> Self { diff --git a/git-config/src/file/init/types.rs b/git-config/src/file/init/types.rs index 60f5ae1d0b3..1794582091b 100644 --- a/git-config/src/file/init/types.rs +++ b/git-config/src/file/init/types.rs @@ -1,5 +1,6 @@ use crate::file::init; use crate::parse; +use crate::parse::Event; use crate::path::interpolate; /// The error returned by [`File::from_paths_metadata()`][crate::File::from_paths_metadata()] and @@ -26,3 +27,25 @@ pub struct Options<'a> { /// as newlines will be missing. Use this only if it's clear that serialization will not be attempted. pub lossy: bool, } + +impl Options<'_> { + pub(crate) fn to_event_filter(&self) -> Option) -> bool> { + if self.lossy { + Some(discard_nonessential_events) + } else { + None + } + } +} + +fn discard_nonessential_events(e: &Event<'_>) -> bool { + match e { + Event::Whitespace(_) | Event::Comment(_) | Event::Newline(_) => false, + Event::SectionHeader(_) + | Event::SectionKey(_) + | Event::KeyValueSeparator + | Event::Value(_) + | Event::ValueNotDone(_) + | Event::ValueDone(_) => true, + } +} diff --git a/git-config/tests/file/access/read_only.rs b/git-config/tests/file/access/read_only.rs index cd50c858f5f..4e583b9dc55 100644 --- a/git-config/tests/file/access/read_only.rs +++ b/git-config/tests/file/access/read_only.rs @@ -1,7 +1,7 @@ -use std::str::FromStr; use std::{borrow::Cow, convert::TryFrom, error::Error}; use bstr::BStr; +use git_config::file::{init, Metadata}; use git_config::{color, integer, path, Boolean, Color, File, Integer}; use crate::file::cow_str; @@ -24,114 +24,122 @@ fn get_value_for_all_provided_values() -> crate::Result { location = ~/tmp location-quoted = "~/quoted" "#; + for lossy in [false, true] { + let config = File::from_bytes_no_includes( + config.as_bytes(), + Metadata::api(), + init::Options { + lossy, + ..Default::default() + }, + )?; + + assert!(!config.value::("core", None, "bool-explicit")?.0); + assert!(!config.boolean("core", None, "bool-explicit").expect("exists")?); + + assert!(config.value::("core", None, "bool-implicit")?.0); + assert!( + config + .try_value::("core", None, "bool-implicit") + .expect("exists")? + .0 + ); - let config = File::from_str(config)?; + assert!(config.boolean("core", None, "bool-implicit").expect("present")?); + assert_eq!(config.string("doesnt", None, "exist"), None); - assert!(!config.value::("core", None, "bool-explicit")?.0); - assert!(!config.boolean("core", None, "bool-explicit").expect("exists")?); + assert_eq!( + config.value::("core", None, "integer-no-prefix")?, + Integer { + value: 10, + suffix: None + } + ); - assert!(config.value::("core", None, "bool-implicit")?.0); - assert!( - config - .try_value::("core", None, "bool-implicit") - .expect("exists")? - .0 - ); + assert_eq!( + config.value::("core", None, "integer-no-prefix")?, + Integer { + value: 10, + suffix: None + } + ); - assert!(config.boolean("core", None, "bool-implicit").expect("present")?); - assert_eq!(config.string("doesnt", None, "exist"), None); + assert_eq!( + config.value::("core", None, "integer-prefix")?, + Integer { + value: 10, + suffix: Some(integer::Suffix::Gibi), + } + ); - assert_eq!( - config.value::("core", None, "integer-no-prefix")?, - Integer { - value: 10, - suffix: None - } - ); + assert_eq!( + config.value::("core", None, "color")?, + Color { + foreground: Some(color::Name::BrightGreen), + background: Some(color::Name::Red), + attributes: color::Attribute::BOLD + } + ); - assert_eq!( - config.value::("core", None, "integer-no-prefix")?, - Integer { - value: 10, - suffix: None + { + let string = config.value::>("core", None, "other")?; + assert_eq!(string, cow_str("hello world")); + assert!( + matches!(string, Cow::Borrowed(_)), + "no copy is made, we reference the `file` itself" + ); } - ); - assert_eq!( - config.value::("core", None, "integer-prefix")?, - Integer { - value: 10, - suffix: Some(integer::Suffix::Gibi), - } - ); + assert_eq!( + config.string("core", None, "other-quoted").unwrap(), + cow_str("hello world") + ); - assert_eq!( - config.value::("core", None, "color")?, - Color { - foreground: Some(color::Name::BrightGreen), - background: Some(color::Name::Red), - attributes: color::Attribute::BOLD + { + let strings = config.strings("core", None, "other-quoted").unwrap(); + assert_eq!(strings, vec![cow_str("hello"), cow_str("hello world")]); + assert!(matches!(strings[0], Cow::Borrowed(_))); + assert!(matches!(strings[1], Cow::Borrowed(_))); } - ); - { - let string = config.value::>("core", None, "other")?; - assert_eq!(string, cow_str("hello world")); - assert!( - matches!(string, Cow::Borrowed(_)), - "no copy is made, we reference the `file` itself" + { + let cow = config.string("core", None, "other").expect("present"); + assert_eq!(cow.as_ref(), "hello world"); + assert!(matches!(cow, Cow::Borrowed(_))); + } + assert_eq!( + config.string("core", None, "other-quoted").expect("present").as_ref(), + "hello world" ); - } - - assert_eq!( - config.string("core", None, "other-quoted").unwrap(), - cow_str("hello world") - ); - { - let strings = config.strings("core", None, "other-quoted").unwrap(); - assert_eq!(strings, vec![cow_str("hello"), cow_str("hello world")]); - assert!(matches!(strings[0], Cow::Borrowed(_))); - assert!(matches!(strings[1], Cow::Borrowed(_))); - } + { + let actual = config.value::("core", None, "location")?; + assert_eq!(&*actual, "~/tmp", "no interpolation occurs when querying a path"); + + let home = std::env::current_dir()?; + let expected = home.join("tmp"); + assert!(matches!(actual.value, Cow::Borrowed(_))); + assert_eq!( + actual + .interpolate(path::interpolate::Context { + home_dir: home.as_path().into(), + ..Default::default() + }) + .unwrap(), + expected + ); + } - { - let cow = config.string("core", None, "other").expect("present"); - assert_eq!(cow.as_ref(), "hello world"); - assert!(matches!(cow, Cow::Borrowed(_))); - } - assert_eq!( - config.string("core", None, "other-quoted").expect("present").as_ref(), - "hello world" - ); + let actual = config.path("core", None, "location").expect("present"); + assert_eq!(&*actual, "~/tmp"); - { - let actual = config.value::("core", None, "location")?; - assert_eq!(&*actual, "~/tmp", "no interpolation occurs when querying a path"); + let actual = config.path("core", None, "location-quoted").expect("present"); + assert_eq!(&*actual, "~/quoted"); - let home = std::env::current_dir()?; - let expected = home.join("tmp"); - assert!(matches!(actual.value, Cow::Borrowed(_))); - assert_eq!( - actual - .interpolate(path::interpolate::Context { - home_dir: home.as_path().into(), - ..Default::default() - }) - .unwrap(), - expected - ); + let actual = config.value::("core", None, "location-quoted")?; + assert_eq!(&*actual, "~/quoted", "but the path is unquoted"); } - let actual = config.path("core", None, "location").expect("present"); - assert_eq!(&*actual, "~/tmp"); - - let actual = config.path("core", None, "location-quoted").expect("present"); - assert_eq!(&*actual, "~/quoted"); - - let actual = config.value::("core", None, "location-quoted")?; - assert_eq!(&*actual, "~/quoted", "but the path is unquoted"); - Ok(()) } diff --git a/git-config/tests/file/mod.rs b/git-config/tests/file/mod.rs index cda3557a90a..684b6d9743e 100644 --- a/git-config/tests/file/mod.rs +++ b/git-config/tests/file/mod.rs @@ -16,6 +16,7 @@ fn size_in_memory() { } mod open { + use git_config::file::init; use git_config::File; use git_testtools::fixture_path; @@ -26,7 +27,10 @@ mod open { &fixture_path("repo-config.crlf"), &mut buf, Default::default(), - Default::default(), + init::Options { + lossy: true, + ..Default::default() + }, ) .unwrap(); } From 693e304a2c38130ed936d5e4544faaa858665872 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 17 Jul 2022 21:13:05 +0800 Subject: [PATCH 245/366] thanks clippy --- git-config/src/file/init/mod.rs | 2 +- git-config/src/file/init/types.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/git-config/src/file/init/mod.rs b/git-config/src/file/init/mod.rs index f55953ac844..e6671ce89d8 100644 --- a/git-config/src/file/init/mod.rs +++ b/git-config/src/file/init/mod.rs @@ -36,7 +36,7 @@ impl<'a> File<'a> { let meta = meta.into(); Ok(Self::from_parse_events_no_includes( parse::Events::from_bytes(input, options.to_event_filter())?, - meta.clone(), + meta, )) } diff --git a/git-config/src/file/init/types.rs b/git-config/src/file/init/types.rs index 1794582091b..0981cf4d72f 100644 --- a/git-config/src/file/init/types.rs +++ b/git-config/src/file/init/types.rs @@ -29,7 +29,7 @@ pub struct Options<'a> { } impl Options<'_> { - pub(crate) fn to_event_filter(&self) -> Option) -> bool> { + pub(crate) fn to_event_filter(self) -> Option) -> bool> { if self.lossy { Some(discard_nonessential_events) } else { From 78e85d9786a541aa43ad7266e85dc1da5e71a412 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 17 Jul 2022 21:14:23 +0800 Subject: [PATCH 246/366] fix docs (#331) --- git-config/src/file/init/includes.rs | 3 ++- git-config/src/file/init/types.rs | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/git-config/src/file/init/includes.rs b/git-config/src/file/init/includes.rs index ef820f154de..553d2fc32c6 100644 --- a/git-config/src/file/init/includes.rs +++ b/git-config/src/file/init/includes.rs @@ -251,7 +251,8 @@ mod types { use crate::parse; use crate::path::interpolate; - /// The error returned by [`File::from_paths_metadata()`] and [`File::from_env_paths()`]. + /// The error returned by [`File::from_paths_metadata()`][crate::File::from_paths_metadata()] + /// and [`File::from_env_paths()`][crate::File::from_env_paths()]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { diff --git a/git-config/src/file/init/types.rs b/git-config/src/file/init/types.rs index 0981cf4d72f..4b586169823 100644 --- a/git-config/src/file/init/types.rs +++ b/git-config/src/file/init/types.rs @@ -16,14 +16,14 @@ pub enum Error { Includes(#[from] init::includes::Error), } -/// Options when loading git config using [`File::from_paths_metadata()`]. +/// Options when loading git config using [`File::from_paths_metadata()`][crate::File::from_paths_metadata()]. #[derive(Clone, Copy, Default)] pub struct Options<'a> { /// Configure how to follow includes while handling paths. pub includes: init::includes::Options<'a>, /// If true, only value-bearing parse events will be kept to reduce memory usage and increase performance. /// - /// Note that doing so will prevent [`write_to()`][File::write_to()] to serialize itself meaningfully and correctly, + /// Note that doing so will prevent [`write_to()`][crate::File::write_to()] to serialize itself meaningfully and correctly, /// as newlines will be missing. Use this only if it's clear that serialization will not be attempted. pub lossy: bool, } From 26147a7a61a695eda680808ee4aab44a890b2964 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 17 Jul 2022 22:29:59 +0800 Subject: [PATCH 247/366] feat: Add `File::detect_newline_style()`, which does at it says. (#331) --- git-config/src/file/access/mutate.rs | 18 ------------------ git-config/src/file/access/read_only.rs | 23 +++++++++++++++++++++++ 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/git-config/src/file/access/mutate.rs b/git-config/src/file/access/mutate.rs index f0c233d2013..deb742b4d47 100644 --- a/git-config/src/file/access/mutate.rs +++ b/git-config/src/file/access/mutate.rs @@ -290,22 +290,4 @@ impl<'event> File<'event> { } self } - - fn detect_newline_style(&self) -> &BStr { - fn extract_newline<'a, 'b>(e: &'a Event<'b>) -> Option<&'a BStr> { - match e { - Event::Newline(b) => b.as_ref().into(), - _ => None, - } - } - - self.frontmatter_events - .iter() - .find_map(extract_newline) - .or_else(|| { - self.sections() - .find_map(|s| s.body.as_ref().iter().find_map(extract_newline)) - }) - .unwrap_or_else(|| if cfg!(windows) { "\r\n" } else { "\n" }.into()) - } } diff --git a/git-config/src/file/access/read_only.rs b/git-config/src/file/access/read_only.rs index 51c6801c7a6..c2fe75ba754 100644 --- a/git-config/src/file/access/read_only.rs +++ b/git-config/src/file/access/read_only.rs @@ -4,6 +4,7 @@ use bstr::BStr; use git_features::threading::OwnShared; use crate::file::{Metadata, MetadataFilter}; +use crate::parse::Event; use crate::{file, lookup, File}; /// Read-only low-level access methods, as it requires generics for converting into @@ -272,4 +273,26 @@ impl<'event> File<'event> { pub fn frontmatter(&self) -> Option>> { (!self.frontmatter_events.is_empty()).then(|| self.frontmatter_events.iter()) } + + /// Return the newline characters that have been detected in this config file or the default ones + /// for the current platform. + /// + /// Note that the first found newline is the one we use in the assumption of consistency. + pub fn detect_newline_style(&self) -> &BStr { + fn extract_newline<'a, 'b>(e: &'a Event<'b>) -> Option<&'a BStr> { + match e { + Event::Newline(b) => b.as_ref().into(), + _ => None, + } + } + + self.frontmatter_events + .iter() + .find_map(extract_newline) + .or_else(|| { + self.sections() + .find_map(|s| s.body.as_ref().iter().find_map(extract_newline)) + }) + .unwrap_or_else(|| if cfg!(windows) { "\r\n" } else { "\n" }.into()) + } } From 3c06f8889854860b731735a8ce2bf532366003ef Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 17 Jul 2022 22:42:32 +0800 Subject: [PATCH 248/366] prepare for passing through newline (#331) --- git-config/src/file/access/mutate.rs | 10 +++++----- git-config/src/file/access/raw.rs | 4 ++-- git-config/src/file/access/read_only.rs | 6 ++---- git-config/src/file/utils.rs | 2 +- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/git-config/src/file/access/mutate.rs b/git-config/src/file/access/mutate.rs index deb742b4d47..df376a8041c 100644 --- a/git-config/src/file/access/mutate.rs +++ b/git-config/src/file/access/mutate.rs @@ -24,11 +24,11 @@ impl<'event> File<'event> { .rev() .next() .expect("BUG: Section lookup vec was empty"); - Ok(SectionMut::new( - self.sections - .get_mut(&id) - .expect("BUG: Section did not have id from lookup"), - )) + Ok(self + .sections + .get_mut(&id) + .expect("BUG: Section did not have id from lookup") + .to_mut()) } /// Returns the last found mutable section with a given `name` and optional `subsection_name`, that matches `filter`. diff --git a/git-config/src/file/access/raw.rs b/git-config/src/file/access/raw.rs index cf992d111bd..1218c862685 100644 --- a/git-config/src/file/access/raw.rs +++ b/git-config/src/file/access/raw.rs @@ -4,7 +4,7 @@ use bstr::BStr; use crate::file::MetadataFilter; use crate::{ - file::{mutable::multi_value::EntryData, Index, MultiValueMut, SectionMut, Size, ValueMut}, + file::{mutable::multi_value::EntryData, Index, MultiValueMut, Size, ValueMut}, lookup, parse::{section, Event}, File, @@ -121,7 +121,7 @@ impl<'event> File<'event> { drop(section_ids); return Ok(ValueMut { - section: SectionMut::new(self.sections.get_mut(§ion_id).expect("known section-id")), + section: self.sections.get_mut(§ion_id).expect("known section-id").to_mut(), key, index: Index(index), size: Size(size), diff --git a/git-config/src/file/access/read_only.rs b/git-config/src/file/access/read_only.rs index c2fe75ba754..946b02934a0 100644 --- a/git-config/src/file/access/read_only.rs +++ b/git-config/src/file/access/read_only.rs @@ -255,9 +255,7 @@ impl<'event> File<'event> { /// /// This allows to reproduce the look of sections perfectly when serializing them with /// [`write_to()`][file::Section::write_to()]. - pub fn sections_and_postmatter( - &self, - ) -> impl Iterator, Vec<&crate::parse::Event<'event>>)> { + pub fn sections_and_postmatter(&self) -> impl Iterator, Vec<&Event<'event>>)> { self.section_order.iter().map(move |id| { let s = &self.sections[id]; let pm: Vec<_> = self @@ -270,7 +268,7 @@ impl<'event> File<'event> { } /// Return all events which are in front of the first of our sections, or `None` if there are none. - pub fn frontmatter(&self) -> Option>> { + pub fn frontmatter(&self) -> Option>> { (!self.frontmatter_events.is_empty()).then(|| self.frontmatter_events.iter()) } diff --git a/git-config/src/file/utils.rs b/git-config/src/file/utils.rs index e5e816ce083..f1447ca4b04 100644 --- a/git-config/src/file/utils.rs +++ b/git-config/src/file/utils.rs @@ -51,8 +51,8 @@ impl<'event> File<'event> { self.section_id_counter += 1; self.sections .get_mut(&new_section_id) - .map(SectionMut::new) .expect("previously inserted section") + .to_mut() } /// Returns the mapping between section and subsection name to section ids. From f7bd2caceb87a179288030e0771da2e4ed6bd1e4 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 18 Jul 2022 09:08:36 +0800 Subject: [PATCH 249/366] fix: maintain newline format depending on what's present or use platform default. (#331) Previously implicit newlines when adding new sections or keys to sections was always `\n` which isn't correct on windows. Now the newline style is detected and used according to what's present, or in the lack of content, defaults to what's correct for the platform. --- git-config/src/file/access/mutate.rs | 27 ++++++++++++++-------- git-config/src/file/access/raw.rs | 4 +++- git-config/src/file/access/read_only.rs | 6 +++++ git-config/src/file/mutable/mod.rs | 4 +--- git-config/src/file/mutable/multi_value.rs | 2 +- git-config/src/file/mutable/section.rs | 21 +++++++++++++---- git-config/src/file/section/mod.rs | 5 ++-- git-config/src/file/utils.rs | 11 ++++----- git-config/tests/file/mutable/section.rs | 22 +++++++++++------- 9 files changed, 66 insertions(+), 36 deletions(-) diff --git a/git-config/src/file/access/mutate.rs b/git-config/src/file/access/mutate.rs index df376a8041c..189a62bf6b3 100644 --- a/git-config/src/file/access/mutate.rs +++ b/git-config/src/file/access/mutate.rs @@ -24,11 +24,12 @@ impl<'event> File<'event> { .rev() .next() .expect("BUG: Section lookup vec was empty"); + let nl = self.detect_newline_style_smallvec(); Ok(self .sections .get_mut(&id) .expect("BUG: Section did not have id from lookup") - .to_mut()) + .to_mut(nl)) } /// Returns the last found mutable section with a given `name` and optional `subsection_name`, that matches `filter`. @@ -48,7 +49,8 @@ impl<'event> File<'event> { let s = &self.sections[id]; filter(s.meta()) }); - Ok(id.and_then(move |id| self.sections.get_mut(&id).map(|s| s.to_mut()))) + let nl = self.detect_newline_style_smallvec(); + Ok(id.and_then(move |id| self.sections.get_mut(&id).map(move |s| s.to_mut(nl)))) } /// Adds a new section. If a subsection name was provided, then @@ -63,8 +65,10 @@ impl<'event> File<'event> { /// # use git_config::File; /// # use std::convert::TryFrom; /// let mut git_config = git_config::File::default(); - /// let _section = git_config.new_section("hello", Some("world".into())); - /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n"); + /// let section = git_config.new_section("hello", Some("world".into()))?; + /// let nl = section.newline().to_owned(); + /// assert_eq!(git_config.to_string(), format!("[hello \"world\"]{nl}")); + /// # Ok::<(), Box>(()) /// ``` /// /// Creating a new empty section and adding values to it: @@ -77,9 +81,10 @@ impl<'event> File<'event> { /// let mut git_config = git_config::File::default(); /// let mut section = git_config.new_section("hello", Some("world".into()))?; /// section.push(section::Key::try_from("a")?, "b"); - /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n\ta = b\n"); + /// let nl = section.newline().to_owned(); + /// assert_eq!(git_config.to_string(), format!("[hello \"world\"]{nl}\ta = b{nl}")); /// let _section = git_config.new_section("core", None); - /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n\ta = b\n[core]\n"); + /// assert_eq!(git_config.to_string(), format!("[hello \"world\"]{nl}\ta = b{nl}[core]{nl}")); /// # Ok::<(), Box>(()) /// ``` pub fn new_section( @@ -87,8 +92,9 @@ impl<'event> File<'event> { name: impl Into>, subsection: impl Into>>, ) -> Result, section::header::Error> { - let mut section = - self.push_section_internal(file::Section::new(name, subsection, OwnShared::clone(&self.meta))?); + let id = self.push_section_internal(file::Section::new(name, subsection, OwnShared::clone(&self.meta))?); + let nl = self.detect_newline_style_smallvec(); + let mut section = self.sections.get_mut(&id).expect("each id yields a section").to_mut(nl); section.push_newline(); Ok(section) } @@ -180,7 +186,10 @@ impl<'event> File<'event> { &mut self, section: file::Section<'event>, ) -> Result, section::header::Error> { - Ok(self.push_section_internal(section)) + let id = self.push_section_internal(section); + let nl = self.detect_newline_style_smallvec(); + let section = self.sections.get_mut(&id).expect("each id yields a section").to_mut(nl); + Ok(section) } /// Renames the section with `name` and `subsection_name`, modifying the last matching section diff --git a/git-config/src/file/access/raw.rs b/git-config/src/file/access/raw.rs index 1218c862685..60c4aae2ade 100644 --- a/git-config/src/file/access/raw.rs +++ b/git-config/src/file/access/raw.rs @@ -1,6 +1,7 @@ use std::{borrow::Cow, collections::HashMap}; use bstr::BStr; +use smallvec::ToSmallVec; use crate::file::MetadataFilter; use crate::{ @@ -120,8 +121,9 @@ impl<'event> File<'event> { } drop(section_ids); + let nl = self.detect_newline_style().to_smallvec(); return Ok(ValueMut { - section: self.sections.get_mut(§ion_id).expect("known section-id").to_mut(), + section: self.sections.get_mut(§ion_id).expect("known section-id").to_mut(nl), key, index: Index(index), size: Size(size), diff --git a/git-config/src/file/access/read_only.rs b/git-config/src/file/access/read_only.rs index 946b02934a0..37c6e6fd1ef 100644 --- a/git-config/src/file/access/read_only.rs +++ b/git-config/src/file/access/read_only.rs @@ -1,7 +1,9 @@ +use std::iter::FromIterator; use std::{borrow::Cow, convert::TryFrom}; use bstr::BStr; use git_features::threading::OwnShared; +use smallvec::SmallVec; use crate::file::{Metadata, MetadataFilter}; use crate::parse::Event; @@ -293,4 +295,8 @@ impl<'event> File<'event> { }) .unwrap_or_else(|| if cfg!(windows) { "\r\n" } else { "\n" }.into()) } + + pub(crate) fn detect_newline_style_smallvec(&self) -> SmallVec<[u8; 2]> { + SmallVec::from_iter(self.detect_newline_style().iter().copied()) + } } diff --git a/git-config/src/file/mutable/mod.rs b/git-config/src/file/mutable/mod.rs index 39b8a505452..efe896f0210 100644 --- a/git-config/src/file/mutable/mod.rs +++ b/git-config/src/file/mutable/mod.rs @@ -66,10 +66,8 @@ impl<'a> Whitespace<'a> { } out } -} -impl<'a> From<&file::section::Body<'a>> for Whitespace<'a> { - fn from(s: &file::section::Body<'a>) -> Self { + fn from_body(s: &file::section::Body<'a>) -> Self { let key_pos = s.0.iter() .enumerate() diff --git a/git-config/src/file/mutable/multi_value.rs b/git-config/src/file/mutable/multi_value.rs index db4b34df947..2ddbff34b7b 100644 --- a/git-config/src/file/mutable/multi_value.rs +++ b/git-config/src/file/mutable/multi_value.rs @@ -170,7 +170,7 @@ impl<'borrow, 'lookup, 'event> MultiValueMut<'borrow, 'lookup, 'event> { value: &BStr, ) { let (offset, size) = MultiValueMut::index_and_size(offsets, section_id, offset_index); - let whitespace: Whitespace<'_> = (&*section).into(); + let whitespace = Whitespace::from_body(section); let section = section.as_mut(); section.drain(offset..offset + size); diff --git a/git-config/src/file/mutable/section.rs b/git-config/src/file/mutable/section.rs index 7a7ec8183ed..d02438940f0 100644 --- a/git-config/src/file/mutable/section.rs +++ b/git-config/src/file/mutable/section.rs @@ -3,7 +3,8 @@ use std::{ ops::{Deref, Range}, }; -use bstr::{BStr, BString, ByteVec}; +use bstr::{BStr, BString, ByteSlice, ByteVec}; +use smallvec::SmallVec; use crate::file::{self, Section}; use crate::{ @@ -22,6 +23,7 @@ pub struct SectionMut<'a, 'event> { section: &'a mut Section<'event>, implicit_newline: bool, whitespace: Whitespace<'event>, + newline: SmallVec<[u8; 2]>, } /// Mutating methods. @@ -37,7 +39,7 @@ impl<'a, 'event> SectionMut<'a, 'event> { body.extend(self.whitespace.key_value_separators()); body.push(Event::Value(escape_value(value.into()).into())); if self.implicit_newline { - body.push(Event::Newline(BString::from("\n").into())); + body.push(Event::Newline(BString::from(self.newline.to_vec()).into())); } } @@ -109,7 +111,15 @@ impl<'a, 'event> SectionMut<'a, 'event> { /// Adds a new line event. Note that you don't need to call this unless /// you've disabled implicit newlines. pub fn push_newline(&mut self) { - self.section.body.0.push(Event::Newline(Cow::Borrowed("\n".into()))); + self.section + .body + .0 + .push(Event::Newline(Cow::Owned(BString::from(self.newline.to_vec())))); + } + + /// Return the newline used when calling [`push_newline()`][Self::push_newline()]. + pub fn newline(&self) -> &BStr { + self.newline.as_slice().as_bstr() } /// Enables or disables automatically adding newline events after adding @@ -158,12 +168,13 @@ impl<'a, 'event> SectionMut<'a, 'event> { // Internal methods that may require exact indices for faster operations. impl<'a, 'event> SectionMut<'a, 'event> { - pub(crate) fn new(section: &'a mut Section<'event>) -> Self { - let whitespace = (§ion.body).into(); + pub(crate) fn new(section: &'a mut Section<'event>, newline: SmallVec<[u8; 2]>) -> Self { + let whitespace = Whitespace::from_body(§ion.body); Self { section, implicit_newline: true, whitespace, + newline, } } diff --git a/git-config/src/file/section/mod.rs b/git-config/src/file/section/mod.rs index f6c8a24790e..8631243a611 100644 --- a/git-config/src/file/section/mod.rs +++ b/git-config/src/file/section/mod.rs @@ -2,6 +2,7 @@ use crate::file::{Metadata, Section, SectionMut}; use crate::parse::section; use crate::{file, parse}; use bstr::BString; +use smallvec::SmallVec; use std::borrow::Cow; use std::ops::Deref; @@ -71,7 +72,7 @@ impl<'a> Section<'a> { } /// Returns a mutable version of this section for adjustment of values. - pub fn to_mut(&mut self) -> SectionMut<'_, 'a> { - SectionMut::new(self) + pub fn to_mut(&mut self, newline: SmallVec<[u8; 2]>) -> SectionMut<'_, 'a> { + SectionMut::new(self, newline) } } diff --git a/git-config/src/file/utils.rs b/git-config/src/file/utils.rs index f1447ca4b04..ea4cfc5267f 100644 --- a/git-config/src/file/utils.rs +++ b/git-config/src/file/utils.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use bstr::BStr; use crate::{ - file::{self, SectionBodyIds, SectionId, SectionMut}, + file::{self, SectionBodyIds, SectionId}, lookup, parse::section, File, @@ -11,8 +11,8 @@ use crate::{ /// Private helper functions impl<'event> File<'event> { - /// Adds a new section to the config file. - pub(crate) fn push_section_internal(&mut self, section: file::Section<'event>) -> SectionMut<'_, 'event> { + /// Adds a new section to the config file, returning the section id of the newly added section. + pub(crate) fn push_section_internal(&mut self, section: file::Section<'event>) -> SectionId { let new_section_id = SectionId(self.section_id_counter); self.sections.insert(new_section_id, section); let header = &self.sections[&new_section_id].header; @@ -49,10 +49,7 @@ impl<'event> File<'event> { } self.section_order.push_back(new_section_id); self.section_id_counter += 1; - self.sections - .get_mut(&new_section_id) - .expect("previously inserted section") - .to_mut() + new_section_id } /// Returns the mapping between section and subsection name to section ids. diff --git a/git-config/tests/file/mutable/section.rs b/git-config/tests/file/mutable/section.rs index 1ae8f8c1290..c2d35f96afe 100644 --- a/git-config/tests/file/mutable/section.rs +++ b/git-config/tests/file/mutable/section.rs @@ -128,23 +128,26 @@ mod push { #[test] fn values_are_escaped() { for (value, expected) in [ - ("a b", "[a]\n\tk = a b"), - (" a b", "[a]\n\tk = \" a b\""), - ("a b\t", "[a]\n\tk = \"a b\\t\""), - (";c", "[a]\n\tk = \";c\""), - ("#c", "[a]\n\tk = \"#c\""), - ("a\nb\n\tc", "[a]\n\tk = a\\nb\\n\\tc"), + ("a b", "$head\tk = a b"), + (" a b", "$head\tk = \" a b\""), + ("a b\t", "$head\tk = \"a b\\t\""), + (";c", "$head\tk = \";c\""), + ("#c", "$head\tk = \"#c\""), + ("a\nb\n\tc", "$head\tk = a\\nb\\n\\tc"), ] { let mut config = git_config::File::default(); let mut section = config.new_section("a", None).unwrap(); section.set_implicit_newline(false); section.push(Key::try_from("k").unwrap(), value); + let expected = expected.replace("$head", &format!("[a]{nl}", nl = section.newline())); assert_eq!(config.to_bstring(), expected); } } } mod set_leading_whitespace { + use bstr::BString; + use std::borrow::Cow; use std::convert::TryFrom; use git_config::parse::section::Key; @@ -155,9 +158,12 @@ mod set_leading_whitespace { fn any_whitespace_is_ok() -> crate::Result { let mut config = git_config::File::default(); let mut section = config.new_section("core", None)?; - section.set_leading_whitespace(cow_str("\n\t").into()); + + let nl = section.newline().to_owned(); + section.set_leading_whitespace(Some(Cow::Owned(BString::from(format!("{nl}\t"))))); section.push(Key::try_from("a")?, "v"); - assert_eq!(config.to_string(), "[core]\n\n\ta = v\n"); + + assert_eq!(config.to_string(), format!("[core]{nl}{nl}\ta = v{nl}")); Ok(()) } From 91e718f0e116052b64ca436d7c74cea79529e696 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 18 Jul 2022 13:12:21 +0800 Subject: [PATCH 250/366] feat: `parse::key` to parse a `remote.origin.url`-like key to identify a value (#331) --- git-config/src/file/init/from_env.rs | 42 +++++++++++----------------- git-config/src/parse/key.rs | 27 ++++++++++++++++++ git-config/src/parse/mod.rs | 4 +++ git-config/tests/parse/key.rs | 39 ++++++++++++++++++++++++++ git-config/tests/parse/mod.rs | 1 + 5 files changed, 87 insertions(+), 26 deletions(-) create mode 100644 git-config/src/parse/key.rs create mode 100644 git-config/tests/parse/key.rs diff --git a/git-config/src/file/init/from_env.rs b/git-config/src/file/init/from_env.rs index dc136b27a3a..9d759f1c122 100644 --- a/git-config/src/file/init/from_env.rs +++ b/git-config/src/file/init/from_env.rs @@ -3,7 +3,7 @@ use std::convert::TryFrom; use std::{borrow::Cow, path::PathBuf}; use crate::file::{init, Metadata}; -use crate::{file, parse::section, path::interpolate, File, Source}; +use crate::{file, parse, parse::section, path::interpolate, File, Source}; /// Represents the errors that may occur when calling [`File::from_env`][crate::File::from_env()]. #[derive(Debug, thiserror::Error)] @@ -129,33 +129,23 @@ impl File<'static> { for i in 0..count { let key = env::var(format!("GIT_CONFIG_KEY_{}", i)).map_err(|_| Error::InvalidKeyId { key_id: i })?; let value = env::var_os(format!("GIT_CONFIG_VALUE_{}", i)).ok_or(Error::InvalidValueId { value_id: i })?; - match key.split_once('.') { - Some((section_name, maybe_subsection)) => { - let (subsection, key) = match maybe_subsection.rsplit_once('.') { - Some((subsection, key)) => (Some(subsection), key), - None => (None, maybe_subsection), - }; + let key = parse::key(&key).ok_or_else(|| Error::InvalidKeyValue { + key_id: i, + key_val: key.to_string(), + })?; - let mut section = match config.section_mut(section_name, subsection) { - Ok(section) => section, - Err(_) => config.new_section( - section_name.to_string(), - subsection.map(|subsection| Cow::Owned(subsection.to_string())), - )?, - }; + let mut section = match config.section_mut(key.section_name, key.subsection_name) { + Ok(section) => section, + Err(_) => config.new_section( + key.section_name.to_owned(), + key.subsection_name.map(|subsection| Cow::Owned(subsection.to_owned())), + )?, + }; - section.push( - section::Key::try_from(key.to_owned())?, - git_path::os_str_into_bstr(&value).expect("no illformed UTF-8").as_ref(), - ); - } - None => { - return Err(Error::InvalidKeyValue { - key_id: i, - key_val: key.to_string(), - }) - } - } + section.push( + section::Key::try_from(key.value_name.to_owned())?, + git_path::os_str_into_bstr(&value).expect("no illformed UTF-8").as_ref(), + ); } let mut buf = Vec::new(); diff --git a/git-config/src/parse/key.rs b/git-config/src/parse/key.rs new file mode 100644 index 00000000000..d4b11bb4707 --- /dev/null +++ b/git-config/src/parse/key.rs @@ -0,0 +1,27 @@ +/// An unvalidated parse result of parsing input like `remote.origin.url` or `core.bare`. +#[derive(Debug, PartialEq, Ord, PartialOrd, Eq, Hash, Clone, Copy)] +pub struct Key<'a> { + /// The name of the section, like `core` in `core.bare`. + pub section_name: &'a str, + /// The name of the sub-section, like `origin` in `remote.origin.url`. + pub subsection_name: Option<&'a str>, + /// The name of the section key, like `url` in `remote.origin.url`. + pub value_name: &'a str, +} + +/// Parse `input` like `core.bare` or `remote.origin.url` as a `Key` to make its fields available, +/// or `None` if there were not at least 2 tokens separated by `.`. +/// Note that `input` isn't validated, and is `str` as ascii is a subset of UTF-8 which is required for any valid keys. +pub fn parse_unvalidated(input: &str) -> Option> { + let (section_name, subsection_or_key) = input.split_once('.')?; + let (subsection_name, value_name) = match subsection_or_key.rsplit_once('.') { + Some((subsection, key)) => (Some(subsection), key), + None => (None, subsection_or_key), + }; + + Some(Key { + section_name, + subsection_name, + value_name, + }) +} diff --git a/git-config/src/parse/mod.rs b/git-config/src/parse/mod.rs index fd1f779b77e..33c51b19d57 100644 --- a/git-config/src/parse/mod.rs +++ b/git-config/src/parse/mod.rs @@ -26,6 +26,10 @@ mod error; /// pub mod section; +/// +mod key; +pub use key::{parse_unvalidated as key, Key}; + #[cfg(test)] pub(crate) mod tests; diff --git a/git-config/tests/parse/key.rs b/git-config/tests/parse/key.rs new file mode 100644 index 00000000000..af46e647486 --- /dev/null +++ b/git-config/tests/parse/key.rs @@ -0,0 +1,39 @@ +use git_config::parse; + +#[test] +fn missing_dot_is_invalid() { + assert_eq!(parse::key("hello"), None); +} + +#[test] +fn section_name_and_key() { + assert_eq!( + parse::key("core.bare"), + Some(parse::Key { + section_name: "core", + subsection_name: None, + value_name: "bare" + }) + ); +} + +#[test] +fn section_name_subsection_and_key() { + assert_eq!( + parse::key("remote.origin.url"), + Some(parse::Key { + section_name: "remote", + subsection_name: Some("origin"), + value_name: "url" + }) + ); + + assert_eq!( + parse::key("includeIf.gitdir/i:C:\\bare.git.path"), + Some(parse::Key { + section_name: "includeIf", + subsection_name: Some("gitdir/i:C:\\bare.git"), + value_name: "path" + }) + ); +} diff --git a/git-config/tests/parse/mod.rs b/git-config/tests/parse/mod.rs index 36d4df3ba37..659ebc26db9 100644 --- a/git-config/tests/parse/mod.rs +++ b/git-config/tests/parse/mod.rs @@ -4,6 +4,7 @@ use git_config::parse::{Event, Events, Section}; mod error; mod from_bytes; +mod key; mod section; #[test] From 5f9bfa89ceb61f484be80575b0379bbf9d7a36b3 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 18 Jul 2022 13:33:48 +0800 Subject: [PATCH 251/366] feat: `Repository::config_snapshot()` to access configuration values. (#331) --- git-repository/src/config.rs | 217 ---------------------- git-repository/src/config/cache.rs | 168 +++++++++++++++++ git-repository/src/config/mod.rs | 88 +++++++++ git-repository/src/repository/config.rs | 8 + git-repository/src/repository/mod.rs | 2 + git-repository/tests/git.rs | 10 +- git-repository/tests/repository/config.rs | 10 + git-repository/tests/repository/mod.rs | 1 + 8 files changed, 282 insertions(+), 222 deletions(-) delete mode 100644 git-repository/src/config.rs create mode 100644 git-repository/src/config/cache.rs create mode 100644 git-repository/src/config/mod.rs create mode 100644 git-repository/src/repository/config.rs create mode 100644 git-repository/tests/repository/config.rs diff --git a/git-repository/src/config.rs b/git-repository/src/config.rs deleted file mode 100644 index a622f05f994..00000000000 --- a/git-repository/src/config.rs +++ /dev/null @@ -1,217 +0,0 @@ -use crate::{bstr::BString, permission}; - -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("Could not open repository conifguration file")] - Open(#[from] git_config::file::init::from_paths::Error), - #[error("Cannot handle objects formatted as {:?}", .name)] - UnsupportedObjectFormat { name: BString }, - #[error("The value for '{}' cannot be empty", .key)] - EmptyValue { key: &'static str }, - #[error("Invalid value for 'core.abbrev' = '{}'. It must be between 4 and {}", .value, .max)] - CoreAbbrev { value: BString, max: u8 }, - #[error("Value '{}' at key '{}' could not be decoded as boolean", .value, .key)] - DecodeBoolean { key: String, value: BString }, - #[error(transparent)] - PathInterpolation(#[from] git_config::path::interpolate::Error), -} - -/// Utility type to keep pre-obtained configuration values. -#[derive(Debug, Clone)] -pub(crate) struct Cache { - pub resolved: crate::Config, - /// The hex-length to assume when shortening object ids. If `None`, it should be computed based on the approximate object count. - pub hex_len: Option, - /// true if the repository is designated as 'bare', without work tree. - pub is_bare: bool, - /// The type of hash to use. - pub object_hash: git_hash::Kind, - /// If true, multi-pack indices, whether present or not, may be used by the object database. - pub use_multi_pack_index: bool, - /// The representation of `core.logallrefupdates`, or `None` if the variable wasn't set. - pub reflog: Option, - /// If true, we are on a case-insensitive file system. - #[cfg_attr(not(feature = "git-index"), allow(dead_code))] - pub ignore_case: bool, - /// The path to the user-level excludes file to ignore certain files in the worktree. - #[cfg_attr(not(feature = "git-index"), allow(dead_code))] - pub excludes_file: Option, - /// Define how we can use values obtained with `xdg_config(…)` and its `XDG_CONFIG_HOME` variable. - #[cfg_attr(not(feature = "git-index"), allow(dead_code))] - xdg_config_home_env: permission::env_var::Resource, - /// Define how we can use values obtained with `xdg_config(…)`. and its `HOME` variable. - #[cfg_attr(not(feature = "git-index"), allow(dead_code))] - home_env: permission::env_var::Resource, - // TODO: make core.precomposeUnicode available as well. -} - -mod cache { - use std::{convert::TryFrom, path::PathBuf}; - - use git_config::{path, Boolean, File, Integer}; - - use super::{Cache, Error}; - use crate::{bstr::ByteSlice, permission}; - - impl Cache { - pub fn new( - git_dir: &std::path::Path, - xdg_config_home_env: permission::env_var::Resource, - home_env: permission::env_var::Resource, - git_install_dir: Option<&std::path::Path>, - ) -> Result { - let home = std::env::var_os("HOME") - .map(PathBuf::from) - .and_then(|home| home_env.check(home).ok().flatten()); - // TODO: don't forget to use the canonicalized home for initializing the stacked config. - // like git here: https://github.com/git/git/blob/master/config.c#L208:L208 - let config = { - let mut buf = Vec::with_capacity(512); - File::from_path_with_buf( - &git_dir.join("config"), - &mut buf, - git_config::file::Metadata::from(git_config::Source::Local), - git_config::file::init::Options { - lossy: true, - includes: git_config::file::init::includes::Options::follow( - git_config::path::interpolate::Context { - git_install_dir, - home_dir: None, - home_for_user: None, // TODO: figure out how to configure this - }, - git_config::file::init::includes::conditional::Context { - git_dir: git_dir.into(), - branch_name: None, - }, - ), - }, - )? - }; - - let is_bare = config_bool(&config, "core.bare", false)?; - let use_multi_pack_index = config_bool(&config, "core.multiPackIndex", true)?; - let ignore_case = config_bool(&config, "core.ignoreCase", false)?; - let excludes_file = config - .path("core", None, "excludesFile") - .map(|p| { - p.interpolate(path::interpolate::Context { - git_install_dir, - home_dir: home.as_deref(), - home_for_user: Some(git_config::path::interpolate::home_for_user), - }) - .map(|p| p.into_owned()) - }) - .transpose()?; - let repo_format_version = config - .value::("core", None, "repositoryFormatVersion") - .map_or(0, |v| v.to_decimal().unwrap_or_default()); - let object_hash = (repo_format_version != 1) - .then(|| Ok(git_hash::Kind::Sha1)) - .or_else(|| { - config.string("extensions", None, "objectFormat").map(|format| { - if format.as_ref().eq_ignore_ascii_case(b"sha1") { - Ok(git_hash::Kind::Sha1) - } else { - Err(Error::UnsupportedObjectFormat { - name: format.to_vec().into(), - }) - } - }) - }) - .transpose()? - .unwrap_or(git_hash::Kind::Sha1); - let reflog = config.string("core", None, "logallrefupdates").map(|val| { - (val.eq_ignore_ascii_case(b"always")) - .then(|| git_ref::store::WriteReflog::Always) - .or_else(|| { - git_config::Boolean::try_from(val) - .ok() - .and_then(|b| b.is_true().then(|| git_ref::store::WriteReflog::Normal)) - }) - .unwrap_or(git_ref::store::WriteReflog::Disable) - }); - - let mut hex_len = None; - if let Some(hex_len_str) = config.string("core", None, "abbrev") { - if hex_len_str.trim().is_empty() { - return Err(Error::EmptyValue { key: "core.abbrev" }); - } - if !hex_len_str.eq_ignore_ascii_case(b"auto") { - let value_bytes = hex_len_str.as_ref(); - if let Ok(false) = Boolean::try_from(value_bytes).map(Into::into) { - hex_len = object_hash.len_in_hex().into(); - } else { - let value = Integer::try_from(value_bytes) - .map_err(|_| Error::CoreAbbrev { - value: hex_len_str.clone().into_owned(), - max: object_hash.len_in_hex() as u8, - })? - .to_decimal() - .ok_or_else(|| Error::CoreAbbrev { - value: hex_len_str.clone().into_owned(), - max: object_hash.len_in_hex() as u8, - })?; - if value < 4 || value as usize > object_hash.len_in_hex() { - return Err(Error::CoreAbbrev { - value: hex_len_str.clone().into_owned(), - max: object_hash.len_in_hex() as u8, - }); - } - hex_len = Some(value as usize); - } - } - } - - Ok(Cache { - resolved: config.into(), - use_multi_pack_index, - object_hash, - reflog, - is_bare, - ignore_case, - hex_len, - excludes_file, - xdg_config_home_env, - home_env, - }) - } - - /// Return a path by using the `$XDF_CONFIG_HOME` or `$HOME/.config/…` environment variables locations. - #[cfg_attr(not(feature = "git-index"), allow(dead_code))] - pub fn xdg_config_path( - &self, - resource_file_name: &str, - ) -> Result, git_sec::permission::Error> { - std::env::var_os("XDG_CONFIG_HOME") - .map(|path| (path, &self.xdg_config_home_env)) - .or_else(|| std::env::var_os("HOME").map(|path| (path, &self.home_env))) - .and_then(|(base, permission)| { - let resource = std::path::PathBuf::from(base).join("git").join(resource_file_name); - permission.check(resource).transpose() - }) - .transpose() - } - - /// Return the home directory if we are allowed to read it and if it is set in the environment. - /// - /// We never fail for here even if the permission is set to deny as we `git-config` will fail later - /// if it actually wants to use the home directory - we don't want to fail prematurely. - #[cfg(feature = "git-mailmap")] - pub fn home_dir(&self) -> Option { - std::env::var_os("HOME") - .map(PathBuf::from) - .and_then(|path| self.home_env.check(path).ok().flatten()) - } - } - - fn config_bool(config: &File<'_>, key: &str, default: bool) -> Result { - let (section, key) = key.split_once('.').expect("valid section.key format"); - config - .boolean(section, None, key) - .unwrap_or(Ok(default)) - .map_err(|err| Error::DecodeBoolean { - value: err.input, - key: key.into(), - }) - } -} diff --git a/git-repository/src/config/cache.rs b/git-repository/src/config/cache.rs new file mode 100644 index 00000000000..60406c2deaa --- /dev/null +++ b/git-repository/src/config/cache.rs @@ -0,0 +1,168 @@ +use std::{convert::TryFrom, path::PathBuf}; + +use git_config::{path, Boolean, File, Integer}; + +use super::{Cache, Error}; +use crate::{bstr::ByteSlice, permission}; + +impl Cache { + pub fn new( + git_dir: &std::path::Path, + xdg_config_home_env: permission::env_var::Resource, + home_env: permission::env_var::Resource, + git_install_dir: Option<&std::path::Path>, + ) -> Result { + let home = std::env::var_os("HOME") + .map(PathBuf::from) + .and_then(|home| home_env.check(home).ok().flatten()); + // TODO: don't forget to use the canonicalized home for initializing the stacked config. + // like git here: https://github.com/git/git/blob/master/config.c#L208:L208 + let config = { + let mut buf = Vec::with_capacity(512); + File::from_path_with_buf( + &git_dir.join("config"), + &mut buf, + git_config::file::Metadata::from(git_config::Source::Local), + git_config::file::init::Options { + lossy: true, + includes: git_config::file::init::includes::Options::follow( + git_config::path::interpolate::Context { + git_install_dir, + home_dir: None, + home_for_user: None, // TODO: figure out how to configure this + }, + git_config::file::init::includes::conditional::Context { + git_dir: git_dir.into(), + branch_name: None, + }, + ), + }, + )? + }; + + let is_bare = config_bool(&config, "core.bare", false)?; + let use_multi_pack_index = config_bool(&config, "core.multiPackIndex", true)?; + let ignore_case = config_bool(&config, "core.ignoreCase", false)?; + let excludes_file = config + .path("core", None, "excludesFile") + .map(|p| { + p.interpolate(path::interpolate::Context { + git_install_dir, + home_dir: home.as_deref(), + home_for_user: Some(git_config::path::interpolate::home_for_user), + }) + .map(|p| p.into_owned()) + }) + .transpose()?; + let repo_format_version = config + .value::("core", None, "repositoryFormatVersion") + .map_or(0, |v| v.to_decimal().unwrap_or_default()); + let object_hash = (repo_format_version != 1) + .then(|| Ok(git_hash::Kind::Sha1)) + .or_else(|| { + config.string("extensions", None, "objectFormat").map(|format| { + if format.as_ref().eq_ignore_ascii_case(b"sha1") { + Ok(git_hash::Kind::Sha1) + } else { + Err(Error::UnsupportedObjectFormat { + name: format.to_vec().into(), + }) + } + }) + }) + .transpose()? + .unwrap_or(git_hash::Kind::Sha1); + let reflog = config.string("core", None, "logallrefupdates").map(|val| { + (val.eq_ignore_ascii_case(b"always")) + .then(|| git_ref::store::WriteReflog::Always) + .or_else(|| { + git_config::Boolean::try_from(val) + .ok() + .and_then(|b| b.is_true().then(|| git_ref::store::WriteReflog::Normal)) + }) + .unwrap_or(git_ref::store::WriteReflog::Disable) + }); + + let mut hex_len = None; + if let Some(hex_len_str) = config.string("core", None, "abbrev") { + if hex_len_str.trim().is_empty() { + return Err(Error::EmptyValue { key: "core.abbrev" }); + } + if !hex_len_str.eq_ignore_ascii_case(b"auto") { + let value_bytes = hex_len_str.as_ref(); + if let Ok(false) = Boolean::try_from(value_bytes).map(Into::into) { + hex_len = object_hash.len_in_hex().into(); + } else { + let value = Integer::try_from(value_bytes) + .map_err(|_| Error::CoreAbbrev { + value: hex_len_str.clone().into_owned(), + max: object_hash.len_in_hex() as u8, + })? + .to_decimal() + .ok_or_else(|| Error::CoreAbbrev { + value: hex_len_str.clone().into_owned(), + max: object_hash.len_in_hex() as u8, + })?; + if value < 4 || value as usize > object_hash.len_in_hex() { + return Err(Error::CoreAbbrev { + value: hex_len_str.clone().into_owned(), + max: object_hash.len_in_hex() as u8, + }); + } + hex_len = Some(value as usize); + } + } + } + + Ok(Cache { + resolved: config.into(), + use_multi_pack_index, + object_hash, + reflog, + is_bare, + ignore_case, + hex_len, + excludes_file, + xdg_config_home_env, + home_env, + }) + } + + /// Return a path by using the `$XDF_CONFIG_HOME` or `$HOME/.config/…` environment variables locations. + #[cfg_attr(not(feature = "git-index"), allow(dead_code))] + pub fn xdg_config_path( + &self, + resource_file_name: &str, + ) -> Result, git_sec::permission::Error> { + std::env::var_os("XDG_CONFIG_HOME") + .map(|path| (path, &self.xdg_config_home_env)) + .or_else(|| std::env::var_os("HOME").map(|path| (path, &self.home_env))) + .and_then(|(base, permission)| { + let resource = std::path::PathBuf::from(base).join("git").join(resource_file_name); + permission.check(resource).transpose() + }) + .transpose() + } + + /// Return the home directory if we are allowed to read it and if it is set in the environment. + /// + /// We never fail for here even if the permission is set to deny as we `git-config` will fail later + /// if it actually wants to use the home directory - we don't want to fail prematurely. + #[cfg(feature = "git-mailmap")] + pub fn home_dir(&self) -> Option { + std::env::var_os("HOME") + .map(PathBuf::from) + .and_then(|path| self.home_env.check(path).ok().flatten()) + } +} + +fn config_bool(config: &File<'_>, key: &str, default: bool) -> Result { + let (section, key) = key.split_once('.').expect("valid section.key format"); + config + .boolean(section, None, key) + .unwrap_or(Ok(default)) + .map_err(|err| Error::DecodeBoolean { + value: err.input, + key: key.into(), + }) +} diff --git a/git-repository/src/config/mod.rs b/git-repository/src/config/mod.rs new file mode 100644 index 00000000000..5f97bf56e64 --- /dev/null +++ b/git-repository/src/config/mod.rs @@ -0,0 +1,88 @@ +use crate::{bstr::BString, permission, Repository}; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Could not open repository conifguration file")] + Open(#[from] git_config::file::init::from_paths::Error), + #[error("Cannot handle objects formatted as {:?}", .name)] + UnsupportedObjectFormat { name: BString }, + #[error("The value for '{}' cannot be empty", .key)] + EmptyValue { key: &'static str }, + #[error("Invalid value for 'core.abbrev' = '{}'. It must be between 4 and {}", .value, .max)] + CoreAbbrev { value: BString, max: u8 }, + #[error("Value '{}' at key '{}' could not be decoded as boolean", .value, .key)] + DecodeBoolean { key: String, value: BString }, + #[error(transparent)] + PathInterpolation(#[from] git_config::path::interpolate::Error), +} + +/// A platform to access configuration values as read from disk. +/// +/// Note that these values won't update even if the underlying file(s) change. +pub struct Snapshot<'repo> { + pub(crate) repo: &'repo Repository, +} + +mod snapshot { + use crate::config::Snapshot; + + /// Access configuration values, frozen in time, using a `key` which is a `.` separated string of up to + /// three tokens, namely `section_name.[subsection_name.]value_name`, like `core.bare` or `remote.origin.url`. + /// + /// Note that single-value methods always return the last value found, which is the one set most recently in the + /// hierarchy of configuration files, aka 'last one wins'. + impl<'repo> Snapshot<'repo> { + /// Return the boolean at `key`, or `None` if there is no such value or if the value can't be interpreted as + /// boolean. + /// + /// Note that this method takes the most recent value at `key` even if it is from a file with reduced trust. + /// For a non-degenerating version, use [`try_boolean(…)`][Self::try_boolean()] + pub fn boolean(&self, key: &str) -> Option { + self.try_boolean(key).map(Result::ok).flatten() + } + + /// Like [`boolean()`][Self::boolean()], but it will report an error if the value couldn't be interpreted as boolean. + pub fn try_boolean(&self, key: &str) -> Option> { + let git_config::parse::Key { + section_name, + subsection_name, + value_name, + } = git_config::parse::key(key)?; + self.repo + .config + .resolved + .boolean(section_name, subsection_name, value_name) + } + } +} + +/// Utility type to keep pre-obtained configuration values. +#[derive(Debug, Clone)] +pub(crate) struct Cache { + pub resolved: crate::Config, + /// The hex-length to assume when shortening object ids. If `None`, it should be computed based on the approximate object count. + pub hex_len: Option, + /// true if the repository is designated as 'bare', without work tree. + pub is_bare: bool, + /// The type of hash to use. + pub object_hash: git_hash::Kind, + /// If true, multi-pack indices, whether present or not, may be used by the object database. + pub use_multi_pack_index: bool, + /// The representation of `core.logallrefupdates`, or `None` if the variable wasn't set. + pub reflog: Option, + /// If true, we are on a case-insensitive file system. + #[cfg_attr(not(feature = "git-index"), allow(dead_code))] + pub ignore_case: bool, + /// The path to the user-level excludes file to ignore certain files in the worktree. + #[cfg_attr(not(feature = "git-index"), allow(dead_code))] + pub excludes_file: Option, + /// Define how we can use values obtained with `xdg_config(…)` and its `XDG_CONFIG_HOME` variable. + #[cfg_attr(not(feature = "git-index"), allow(dead_code))] + xdg_config_home_env: permission::env_var::Resource, + /// Define how we can use values obtained with `xdg_config(…)`. and its `HOME` variable. + #[cfg_attr(not(feature = "git-index"), allow(dead_code))] + home_env: permission::env_var::Resource, + // TODO: make core.precomposeUnicode available as well. +} + +mod cache; diff --git a/git-repository/src/repository/config.rs b/git-repository/src/repository/config.rs new file mode 100644 index 00000000000..9484524ae59 --- /dev/null +++ b/git-repository/src/repository/config.rs @@ -0,0 +1,8 @@ +use crate::config; + +impl crate::Repository { + /// Return a snapshot of the configuration as seen upon opening the repository. + pub fn config_snapshot(&self) -> config::Snapshot<'_> { + config::Snapshot { repo: self } + } +} diff --git a/git-repository/src/repository/mod.rs b/git-repository/src/repository/mod.rs index b0b6fb36b72..c2e0edaf69a 100644 --- a/git-repository/src/repository/mod.rs +++ b/git-repository/src/repository/mod.rs @@ -44,6 +44,8 @@ mod worktree; /// Various permissions for parts of git repositories. pub(crate) mod permissions; +mod config; + mod init; mod location; diff --git a/git-repository/tests/git.rs b/git-repository/tests/git.rs index 8c9280a29c4..75a31893a0d 100644 --- a/git-repository/tests/git.rs +++ b/git-repository/tests/git.rs @@ -2,17 +2,17 @@ use git_repository::{Repository, ThreadSafeRepository}; type Result = std::result::Result>; -fn repo(name: &str) -> crate::Result { +fn repo(name: &str) -> Result { let repo_path = git_testtools::scripted_fixture_repo_read_only(name)?; Ok(ThreadSafeRepository::open(repo_path)?) } -fn named_repo(name: &str) -> crate::Result { +fn named_repo(name: &str) -> Result { let repo_path = git_testtools::scripted_fixture_repo_read_only(name)?; Ok(ThreadSafeRepository::open(repo_path)?.to_thread_local()) } -fn repo_rw(name: &str) -> crate::Result<(Repository, tempfile::TempDir)> { +fn repo_rw(name: &str) -> Result<(Repository, tempfile::TempDir)> { let repo_path = git_testtools::scripted_fixture_repo_writable(name)?; Ok(( ThreadSafeRepository::discover(repo_path.path())?.to_thread_local(), @@ -20,11 +20,11 @@ fn repo_rw(name: &str) -> crate::Result<(Repository, tempfile::TempDir)> { )) } -fn basic_repo() -> crate::Result { +fn basic_repo() -> Result { repo("make_basic_repo.sh").map(|r| r.to_thread_local()) } -fn basic_rw_repo() -> crate::Result<(Repository, tempfile::TempDir)> { +fn basic_rw_repo() -> Result<(Repository, tempfile::TempDir)> { repo_rw("make_basic_repo.sh") } diff --git a/git-repository/tests/repository/config.rs b/git-repository/tests/repository/config.rs new file mode 100644 index 00000000000..a29bcf4db11 --- /dev/null +++ b/git-repository/tests/repository/config.rs @@ -0,0 +1,10 @@ +use crate::basic_rw_repo; + +#[test] +fn access_values() { + let (repo, _dir) = basic_rw_repo().unwrap(); + let config = repo.config_snapshot(); + + assert_eq!(config.boolean("core.bare"), Some(false)); + assert_eq!(config.boolean("core.missing"), None); +} diff --git a/git-repository/tests/repository/mod.rs b/git-repository/tests/repository/mod.rs index 5d1a173dcf3..dbc256ac1e3 100644 --- a/git-repository/tests/repository/mod.rs +++ b/git-repository/tests/repository/mod.rs @@ -1,3 +1,4 @@ +mod config; mod object; mod reference; mod remote; From 2c2195640818319795a93e73bed79174fa358f55 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 18 Jul 2022 13:44:38 +0800 Subject: [PATCH 252/366] `Debug` for `config::Snapshot`. (#331) It's a bit special as it works around the release-versions inability to reproduce itself losslessly. In debug mode, we do load files withtout loss, making nice debug printing possible. --- git-repository/src/config/cache.rs | 2 +- git-repository/src/config/mod.rs | 52 ++++++--------------------- git-repository/src/config/snapshot.rs | 41 +++++++++++++++++++++ 3 files changed, 52 insertions(+), 43 deletions(-) create mode 100644 git-repository/src/config/snapshot.rs diff --git a/git-repository/src/config/cache.rs b/git-repository/src/config/cache.rs index 60406c2deaa..3acf1eb741f 100644 --- a/git-repository/src/config/cache.rs +++ b/git-repository/src/config/cache.rs @@ -24,7 +24,7 @@ impl Cache { &mut buf, git_config::file::Metadata::from(git_config::Source::Local), git_config::file::init::Options { - lossy: true, + lossy: !cfg!(debug_assertions), includes: git_config::file::init::includes::Options::follow( git_config::path::interpolate::Context { git_install_dir, diff --git a/git-repository/src/config/mod.rs b/git-repository/src/config/mod.rs index 5f97bf56e64..97ff57b05d0 100644 --- a/git-repository/src/config/mod.rs +++ b/git-repository/src/config/mod.rs @@ -1,5 +1,15 @@ use crate::{bstr::BString, permission, Repository}; +mod cache; +mod snapshot; + +/// A platform to access configuration values as read from disk. +/// +/// Note that these values won't update even if the underlying file(s) change. +pub struct Snapshot<'repo> { + pub(crate) repo: &'repo Repository, +} + #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Could not open repository conifguration file")] @@ -16,46 +26,6 @@ pub enum Error { PathInterpolation(#[from] git_config::path::interpolate::Error), } -/// A platform to access configuration values as read from disk. -/// -/// Note that these values won't update even if the underlying file(s) change. -pub struct Snapshot<'repo> { - pub(crate) repo: &'repo Repository, -} - -mod snapshot { - use crate::config::Snapshot; - - /// Access configuration values, frozen in time, using a `key` which is a `.` separated string of up to - /// three tokens, namely `section_name.[subsection_name.]value_name`, like `core.bare` or `remote.origin.url`. - /// - /// Note that single-value methods always return the last value found, which is the one set most recently in the - /// hierarchy of configuration files, aka 'last one wins'. - impl<'repo> Snapshot<'repo> { - /// Return the boolean at `key`, or `None` if there is no such value or if the value can't be interpreted as - /// boolean. - /// - /// Note that this method takes the most recent value at `key` even if it is from a file with reduced trust. - /// For a non-degenerating version, use [`try_boolean(…)`][Self::try_boolean()] - pub fn boolean(&self, key: &str) -> Option { - self.try_boolean(key).map(Result::ok).flatten() - } - - /// Like [`boolean()`][Self::boolean()], but it will report an error if the value couldn't be interpreted as boolean. - pub fn try_boolean(&self, key: &str) -> Option> { - let git_config::parse::Key { - section_name, - subsection_name, - value_name, - } = git_config::parse::key(key)?; - self.repo - .config - .resolved - .boolean(section_name, subsection_name, value_name) - } - } -} - /// Utility type to keep pre-obtained configuration values. #[derive(Debug, Clone)] pub(crate) struct Cache { @@ -84,5 +54,3 @@ pub(crate) struct Cache { home_env: permission::env_var::Resource, // TODO: make core.precomposeUnicode available as well. } - -mod cache; diff --git a/git-repository/src/config/snapshot.rs b/git-repository/src/config/snapshot.rs new file mode 100644 index 00000000000..49a9f8e473b --- /dev/null +++ b/git-repository/src/config/snapshot.rs @@ -0,0 +1,41 @@ +use crate::config::Snapshot; +use std::fmt::{Debug, Formatter}; + +/// Access configuration values, frozen in time, using a `key` which is a `.` separated string of up to +/// three tokens, namely `section_name.[subsection_name.]value_name`, like `core.bare` or `remote.origin.url`. +/// +/// Note that single-value methods always return the last value found, which is the one set most recently in the +/// hierarchy of configuration files, aka 'last one wins'. +impl<'repo> Snapshot<'repo> { + /// Return the boolean at `key`, or `None` if there is no such value or if the value can't be interpreted as + /// boolean. + /// + /// Note that this method takes the most recent value at `key` even if it is from a file with reduced trust. + /// For a non-degenerating version, use [`try_boolean(…)`][Self::try_boolean()] + pub fn boolean(&self, key: &str) -> Option { + self.try_boolean(key).map(Result::ok).flatten() + } + + /// Like [`boolean()`][Self::boolean()], but it will report an error if the value couldn't be interpreted as boolean. + pub fn try_boolean(&self, key: &str) -> Option> { + let git_config::parse::Key { + section_name, + subsection_name, + value_name, + } = git_config::parse::key(key)?; + self.repo + .config + .resolved + .boolean(section_name, subsection_name, value_name) + } +} + +impl Debug for Snapshot<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + if cfg!(debug_assertions) { + f.write_str(&self.repo.config.resolved.to_string()) + } else { + Debug::fmt(&self.repo.config.resolved, f) + } + } +} From d5a48b82230b047434610550aacd2dc741b4b5f0 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 18 Jul 2022 15:57:52 +0800 Subject: [PATCH 253/366] feat: `config::Snapshot::trusted_path()` to obtain trustworthy paths. (#331) We also apply trust-based config query during initialization to assure we don't use paths which aren't owned by the current user. --- git-repository/src/config/cache.rs | 35 ++++++++++--------- git-repository/src/config/mod.rs | 7 +++- git-repository/src/config/snapshot.rs | 30 ++++++++++++---- .../make_config_repo.tar.xz | 3 ++ .../tests/fixtures/make_config_repo.sh | 13 +++++++ git-repository/tests/repository/config.rs | 26 ++++++++++++-- 6 files changed, 89 insertions(+), 25 deletions(-) create mode 100644 git-repository/tests/fixtures/generated-archives/make_config_repo.tar.xz create mode 100644 git-repository/tests/fixtures/make_config_repo.sh diff --git a/git-repository/src/config/cache.rs b/git-repository/src/config/cache.rs index 3acf1eb741f..e54759a8969 100644 --- a/git-repository/src/config/cache.rs +++ b/git-repository/src/config/cache.rs @@ -1,8 +1,9 @@ use std::{convert::TryFrom, path::PathBuf}; -use git_config::{path, Boolean, File, Integer}; +use git_config::{Boolean, Integer}; use super::{Cache, Error}; +use crate::config::section::is_trusted; use crate::{bstr::ByteSlice, permission}; impl Cache { @@ -19,18 +20,14 @@ impl Cache { // like git here: https://github.com/git/git/blob/master/config.c#L208:L208 let config = { let mut buf = Vec::with_capacity(512); - File::from_path_with_buf( + git_config::File::from_path_with_buf( &git_dir.join("config"), &mut buf, git_config::file::Metadata::from(git_config::Source::Local), git_config::file::init::Options { lossy: !cfg!(debug_assertions), includes: git_config::file::init::includes::Options::follow( - git_config::path::interpolate::Context { - git_install_dir, - home_dir: None, - home_for_user: None, // TODO: figure out how to configure this - }, + interpolate_context(git_install_dir, home.as_deref()), git_config::file::init::includes::conditional::Context { git_dir: git_dir.into(), branch_name: None, @@ -44,14 +41,10 @@ impl Cache { let use_multi_pack_index = config_bool(&config, "core.multiPackIndex", true)?; let ignore_case = config_bool(&config, "core.ignoreCase", false)?; let excludes_file = config - .path("core", None, "excludesFile") + .path_filter("core", None, "excludesFile", &mut is_trusted) .map(|p| { - p.interpolate(path::interpolate::Context { - git_install_dir, - home_dir: home.as_deref(), - home_for_user: Some(git_config::path::interpolate::home_for_user), - }) - .map(|p| p.into_owned()) + p.interpolate(interpolate_context(git_install_dir, home.as_deref())) + .map(|p| p.into_owned()) }) .transpose()?; let repo_format_version = config @@ -148,7 +141,6 @@ impl Cache { /// /// We never fail for here even if the permission is set to deny as we `git-config` will fail later /// if it actually wants to use the home directory - we don't want to fail prematurely. - #[cfg(feature = "git-mailmap")] pub fn home_dir(&self) -> Option { std::env::var_os("HOME") .map(PathBuf::from) @@ -156,7 +148,18 @@ impl Cache { } } -fn config_bool(config: &File<'_>, key: &str, default: bool) -> Result { +pub(crate) fn interpolate_context<'a>( + git_install_dir: Option<&'a std::path::Path>, + home_dir: Option<&'a std::path::Path>, +) -> git_config::path::interpolate::Context<'a> { + git_config::path::interpolate::Context { + git_install_dir, + home_dir, + home_for_user: Some(git_config::path::interpolate::home_for_user), // TODO: figure out how to configure this + } +} + +fn config_bool(config: &git_config::File<'_>, key: &str, default: bool) -> Result { let (section, key) = key.split_once('.').expect("valid section.key format"); config .boolean(section, None, key) diff --git a/git-repository/src/config/mod.rs b/git-repository/src/config/mod.rs index 97ff57b05d0..6cad58abba5 100644 --- a/git-repository/src/config/mod.rs +++ b/git-repository/src/config/mod.rs @@ -10,6 +10,12 @@ pub struct Snapshot<'repo> { pub(crate) repo: &'repo Repository, } +pub(crate) mod section { + pub fn is_trusted(meta: &git_config::file::Metadata) -> bool { + meta.trust == git_sec::Trust::Full || !meta.source.is_in_repository() + } +} + #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Could not open repository conifguration file")] @@ -50,7 +56,6 @@ pub(crate) struct Cache { #[cfg_attr(not(feature = "git-index"), allow(dead_code))] xdg_config_home_env: permission::env_var::Resource, /// Define how we can use values obtained with `xdg_config(…)`. and its `HOME` variable. - #[cfg_attr(not(feature = "git-index"), allow(dead_code))] home_env: permission::env_var::Resource, // TODO: make core.precomposeUnicode available as well. } diff --git a/git-repository/src/config/snapshot.rs b/git-repository/src/config/snapshot.rs index 49a9f8e473b..7770d95d468 100644 --- a/git-repository/src/config/snapshot.rs +++ b/git-repository/src/config/snapshot.rs @@ -1,4 +1,6 @@ +use crate::config::cache::interpolate_context; use crate::config::Snapshot; +use std::borrow::Cow; use std::fmt::{Debug, Formatter}; /// Access configuration values, frozen in time, using a `key` which is a `.` separated string of up to @@ -18,15 +20,31 @@ impl<'repo> Snapshot<'repo> { /// Like [`boolean()`][Self::boolean()], but it will report an error if the value couldn't be interpreted as boolean. pub fn try_boolean(&self, key: &str) -> Option> { - let git_config::parse::Key { - section_name, - subsection_name, - value_name, - } = git_config::parse::key(key)?; + let key = git_config::parse::key(key)?; self.repo .config .resolved - .boolean(section_name, subsection_name, value_name) + .boolean(key.section_name, key.subsection_name, key.value_name) + } + + /// Return the trusted and fully interpolated path at `key`, or `None` if there is no such value + /// or if no value was found in a trusted file. + /// An error occours if the path could not be interpolated to its final value. + pub fn trusted_path( + &self, + key: &str, + ) -> Option, git_config::path::interpolate::Error>> { + let key = git_config::parse::key(key)?; + let path = self.repo.config.resolved.path_filter( + key.section_name, + key.subsection_name, + key.value_name, + &mut crate::config::section::is_trusted, + )?; + + let install_dir = self.repo.install_dir().ok(); + let home = self.repo.config.home_dir(); + Some(path.interpolate(interpolate_context(install_dir.as_deref(), home.as_deref()))) } } diff --git a/git-repository/tests/fixtures/generated-archives/make_config_repo.tar.xz b/git-repository/tests/fixtures/generated-archives/make_config_repo.tar.xz new file mode 100644 index 00000000000..9bcd8d20535 --- /dev/null +++ b/git-repository/tests/fixtures/generated-archives/make_config_repo.tar.xz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6d1ea527c4a20e55ed4771c18310b9ecf69b52f24b91c7391b1e4d762c0449e5 +size 9112 diff --git a/git-repository/tests/fixtures/make_config_repo.sh b/git-repository/tests/fixtures/make_config_repo.sh new file mode 100644 index 00000000000..2eb0c835fe9 --- /dev/null +++ b/git-repository/tests/fixtures/make_config_repo.sh @@ -0,0 +1,13 @@ +#!/bin/bash +set -eu -o pipefail + +git init -q + +cat <>.git/config +[a] + bool = on + bad-bool = zero + relative-path = ./something + absolute-path = /etc/man.conf + bad-user-path = ~noname/repo +EOF diff --git a/git-repository/tests/repository/config.rs b/git-repository/tests/repository/config.rs index a29bcf4db11..4893af9196a 100644 --- a/git-repository/tests/repository/config.rs +++ b/git-repository/tests/repository/config.rs @@ -1,10 +1,32 @@ -use crate::basic_rw_repo; +use crate::named_repo; +use std::path::Path; #[test] fn access_values() { - let (repo, _dir) = basic_rw_repo().unwrap(); + let repo = named_repo("make_config_repo.sh").unwrap(); let config = repo.config_snapshot(); assert_eq!(config.boolean("core.bare"), Some(false)); + assert_eq!(config.boolean("a.bad-bool"), None); + assert_eq!(config.try_boolean("core.bare"), Some(Ok(false))); + assert!(matches!(config.try_boolean("a.bad-bool"), Some(Err(_)))); + assert_eq!(config.boolean("core.missing"), None); + assert_eq!(config.try_boolean("core.missing"), None); + + assert_eq!( + config + .trusted_path("a.relative-path") + .expect("exists") + .expect("no error"), + Path::new("./something") + ); + assert_eq!( + config + .trusted_path("a.absolute-path") + .expect("exists") + .expect("no error"), + Path::new("/etc/man.conf") + ); + assert!(config.trusted_path("a.bad-user-path").expect("exists").is_err()); } From f5f2d9b3fef98d9100d713f9291510fa4aa27867 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 18 Jul 2022 15:59:37 +0800 Subject: [PATCH 254/366] feat: `Source::is_in_repository()` to find out if a source is in the repository. (#331) --- git-config/src/types.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/git-config/src/types.rs b/git-config/src/types.rs index 8fa77cf4d51..f442e372de2 100644 --- a/git-config/src/types.rs +++ b/git-config/src/types.rs @@ -37,6 +37,13 @@ pub enum Source { Api, } +impl Source { + /// Return true if the source indicates a location within a file of a repository. + pub fn is_in_repository(self) -> bool { + matches!(self, Source::Local | Source::Worktree) + } +} + /// High level `git-config` reader and writer. /// /// This is the full-featured implementation that can deserialize, serialize, From d9eb34cad7a69b56f10eec5b88b86ebd6a9a74af Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 18 Jul 2022 16:01:19 +0800 Subject: [PATCH 255/366] thanks clippy --- git-repository/src/config/snapshot.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-repository/src/config/snapshot.rs b/git-repository/src/config/snapshot.rs index 7770d95d468..a20b6b7dbc2 100644 --- a/git-repository/src/config/snapshot.rs +++ b/git-repository/src/config/snapshot.rs @@ -15,7 +15,7 @@ impl<'repo> Snapshot<'repo> { /// Note that this method takes the most recent value at `key` even if it is from a file with reduced trust. /// For a non-degenerating version, use [`try_boolean(…)`][Self::try_boolean()] pub fn boolean(&self, key: &str) -> Option { - self.try_boolean(key).map(Result::ok).flatten() + self.try_boolean(key).and_then(Result::ok) } /// Like [`boolean()`][Self::boolean()], but it will report an error if the value couldn't be interpreted as boolean. From fff088485dd5067976cc93d525903b39aafea76a Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 18 Jul 2022 16:42:49 +0800 Subject: [PATCH 256/366] feat: `file::ValueMut::(section|into_section_mut)()` to go from value to the owning section. (#331) This can be useful if the value was obtained using `raw_value_mut()`. --- git-config/src/file/mutable/section.rs | 2 +- git-config/src/file/mutable/value.rs | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/git-config/src/file/mutable/section.rs b/git-config/src/file/mutable/section.rs index d02438940f0..276faff7148 100644 --- a/git-config/src/file/mutable/section.rs +++ b/git-config/src/file/mutable/section.rs @@ -242,7 +242,7 @@ impl<'a, 'event> SectionMut<'a, 'event> { } impl<'event> Deref for SectionMut<'_, 'event> { - type Target = file::section::Body<'event>; + type Target = file::Section<'event>; fn deref(&self) -> &Self::Target { self.section diff --git a/git-config/src/file/mutable/value.rs b/git-config/src/file/mutable/value.rs index 63d39ed1ec0..2bccfd32ab4 100644 --- a/git-config/src/file/mutable/value.rs +++ b/git-config/src/file/mutable/value.rs @@ -3,6 +3,7 @@ use std::borrow::Cow; use bstr::BStr; use crate::{ + file, file::{mutable::section::SectionMut, Index, Size}, lookup, parse::section, @@ -49,4 +50,14 @@ impl<'borrow, 'lookup, 'event> ValueMut<'borrow, 'lookup, 'event> { self.size = Size(0); } } + + /// Return the section containing the value. + pub fn section(&self) -> &file::Section<'event> { + &self.section + } + + /// Convert this value into its owning mutable section. + pub fn into_section_mut(self) -> file::SectionMut<'borrow, 'event> { + self.section + } } From 81715ffca33e40cb6e37fff25baa68fca70c4844 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 18 Jul 2022 16:56:57 +0800 Subject: [PATCH 257/366] Add remaining config access, and an escape hatch. (#331) --- git-repository/src/config/snapshot.rs | 44 ++++++++++++++++++- .../make_config_repo.tar.xz | 4 +- .../tests/fixtures/make_config_repo.sh | 3 ++ git-repository/tests/repository/config.rs | 10 +++++ 4 files changed, 58 insertions(+), 3 deletions(-) diff --git a/git-repository/src/config/snapshot.rs b/git-repository/src/config/snapshot.rs index a20b6b7dbc2..53b1c11a159 100644 --- a/git-repository/src/config/snapshot.rs +++ b/git-repository/src/config/snapshot.rs @@ -1,3 +1,4 @@ +use crate::bstr::BStr; use crate::config::cache::interpolate_context; use crate::config::Snapshot; use std::borrow::Cow; @@ -12,8 +13,9 @@ impl<'repo> Snapshot<'repo> { /// Return the boolean at `key`, or `None` if there is no such value or if the value can't be interpreted as /// boolean. /// + /// For a non-degenerating version, use [`try_boolean(…)`][Self::try_boolean()]. + /// /// Note that this method takes the most recent value at `key` even if it is from a file with reduced trust. - /// For a non-degenerating version, use [`try_boolean(…)`][Self::try_boolean()] pub fn boolean(&self, key: &str) -> Option { self.try_boolean(key).and_then(Result::ok) } @@ -27,6 +29,36 @@ impl<'repo> Snapshot<'repo> { .boolean(key.section_name, key.subsection_name, key.value_name) } + /// Return the resolved integer at `key`, or `None` if there is no such value or if the value can't be interpreted as + /// integer or exceeded the value range. + /// + /// For a non-degenerating version, use [`try_integer(…)`][Self::try_integer()]. + /// + /// Note that this method takes the most recent value at `key` even if it is from a file with reduced trust. + pub fn integer(&self, key: &str) -> Option { + self.try_integer(key).and_then(Result::ok) + } + + /// Like [`integer()`][Self::integer()], but it will report an error if the value couldn't be interpreted as boolean. + pub fn try_integer(&self, key: &str) -> Option> { + let key = git_config::parse::key(key)?; + self.repo + .config + .resolved + .integer(key.section_name, key.subsection_name, key.value_name) + } + + /// Return the string at `key`, or `None` if there is no such value. + /// + /// Note that this method takes the most recent value at `key` even if it is from a file with reduced trust. + pub fn string(&self, key: &str) -> Option> { + let key = git_config::parse::key(key)?; + self.repo + .config + .resolved + .string(key.section_name, key.subsection_name, key.value_name) + } + /// Return the trusted and fully interpolated path at `key`, or `None` if there is no such value /// or if no value was found in a trusted file. /// An error occours if the path could not be interpolated to its final value. @@ -48,6 +80,16 @@ impl<'repo> Snapshot<'repo> { } } +/// Utilities and additional access +impl<'repo> Snapshot<'repo> { + /// Returns the underlying configuration implementation for a complete API, despite being a little less convenient. + /// + /// It's expected that more functionality will move up depending on demand. + pub fn plumbing(&self) -> &git_config::File<'static> { + &self.repo.config.resolved + } +} + impl Debug for Snapshot<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { if cfg!(debug_assertions) { diff --git a/git-repository/tests/fixtures/generated-archives/make_config_repo.tar.xz b/git-repository/tests/fixtures/generated-archives/make_config_repo.tar.xz index 9bcd8d20535..940cd3bb580 100644 --- a/git-repository/tests/fixtures/generated-archives/make_config_repo.tar.xz +++ b/git-repository/tests/fixtures/generated-archives/make_config_repo.tar.xz @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d1ea527c4a20e55ed4771c18310b9ecf69b52f24b91c7391b1e4d762c0449e5 -size 9112 +oid sha256:68233a1e37e4d423532370d975297116f967c9b5c0044d66534e7074f92acf77 +size 9152 diff --git a/git-repository/tests/fixtures/make_config_repo.sh b/git-repository/tests/fixtures/make_config_repo.sh index 2eb0c835fe9..ff58143975f 100644 --- a/git-repository/tests/fixtures/make_config_repo.sh +++ b/git-repository/tests/fixtures/make_config_repo.sh @@ -7,7 +7,10 @@ cat <>.git/config [a] bool = on bad-bool = zero + int = 42 + int-overflowing = 9999999999999g relative-path = ./something absolute-path = /etc/man.conf bad-user-path = ~noname/repo + single-string = hello world EOF diff --git a/git-repository/tests/repository/config.rs b/git-repository/tests/repository/config.rs index 4893af9196a..a60ba776f08 100644 --- a/git-repository/tests/repository/config.rs +++ b/git-repository/tests/repository/config.rs @@ -11,6 +11,16 @@ fn access_values() { assert_eq!(config.try_boolean("core.bare"), Some(Ok(false))); assert!(matches!(config.try_boolean("a.bad-bool"), Some(Err(_)))); + assert_eq!(config.integer("a.int"), Some(42)); + assert_eq!(config.integer("a.int-overflowing"), None); + assert_eq!(config.integer("a.int-overflowing"), None); + assert!(config.try_integer("a.int-overflowing").expect("present").is_err()); + + assert_eq!( + config.string("a.single-string").expect("present").as_ref(), + "hello world" + ); + assert_eq!(config.boolean("core.missing"), None); assert_eq!(config.try_boolean("core.missing"), None); From d8e41e20de741c3d4701d862033cf50582a0d015 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 18 Jul 2022 19:21:36 +0800 Subject: [PATCH 258/366] load configuration with trust information, needs cleanup (#331) --- git-repository/src/config/cache.rs | 4 +++- git-repository/src/config/mod.rs | 2 ++ git-repository/src/lib.rs | 7 ++++--- git-repository/src/open.rs | 14 ++++++++------ git-repository/src/repository/location.rs | 5 +++++ git-repository/src/worktree/proxy.rs | 10 ++++++++-- 6 files changed, 30 insertions(+), 12 deletions(-) diff --git a/git-repository/src/config/cache.rs b/git-repository/src/config/cache.rs index e54759a8969..004b2ce2166 100644 --- a/git-repository/src/config/cache.rs +++ b/git-repository/src/config/cache.rs @@ -8,6 +8,7 @@ use crate::{bstr::ByteSlice, permission}; impl Cache { pub fn new( + git_dir_trust: git_sec::Trust, git_dir: &std::path::Path, xdg_config_home_env: permission::env_var::Resource, home_env: permission::env_var::Resource, @@ -23,7 +24,7 @@ impl Cache { git_config::File::from_path_with_buf( &git_dir.join("config"), &mut buf, - git_config::file::Metadata::from(git_config::Source::Local), + git_config::file::Metadata::from(git_config::Source::Local).with(git_dir_trust), git_config::file::init::Options { lossy: !cfg!(debug_assertions), includes: git_config::file::init::includes::Options::follow( @@ -108,6 +109,7 @@ impl Cache { } Ok(Cache { + git_dir_trust, resolved: config.into(), use_multi_pack_index, object_hash, diff --git a/git-repository/src/config/mod.rs b/git-repository/src/config/mod.rs index 6cad58abba5..d284f110aff 100644 --- a/git-repository/src/config/mod.rs +++ b/git-repository/src/config/mod.rs @@ -52,6 +52,8 @@ pub(crate) struct Cache { /// The path to the user-level excludes file to ignore certain files in the worktree. #[cfg_attr(not(feature = "git-index"), allow(dead_code))] pub excludes_file: Option, + /// The amount of trust we place into the git_dir of the repository. + pub git_dir_trust: git_sec::Trust, /// Define how we can use values obtained with `xdg_config(…)` and its `XDG_CONFIG_HOME` variable. #[cfg_attr(not(feature = "git-index"), allow(dead_code))] xdg_config_home_env: permission::env_var::Resource, diff --git a/git-repository/src/lib.rs b/git-repository/src/lib.rs index ea100128695..8e2d24747a3 100644 --- a/git-repository/src/lib.rs +++ b/git-repository/src/lib.rs @@ -355,8 +355,9 @@ pub mod init { use git_sec::trust::DefaultForLevel; let path = crate::create::into(directory.as_ref(), options)?; let (git_dir, worktree_dir) = path.into_repository_and_work_tree_directories(); - let options = crate::open::Options::default_for_level(git_sec::Trust::Full); - ThreadSafeRepository::open_from_paths(git_dir, worktree_dir, options).map_err(Into::into) + let trust = git_sec::Trust::Full; + let options = crate::open::Options::default_for_level(trust); + ThreadSafeRepository::open_from_paths(trust, git_dir, worktree_dir, options).map_err(Into::into) } } } @@ -426,7 +427,7 @@ pub mod discover { let (path, trust) = upwards_opts(directory, options)?; let (git_dir, worktree_dir) = path.into_repository_and_work_tree_directories(); let options = trust_map.into_value_by_level(trust); - Self::open_from_paths(git_dir, worktree_dir, options).map_err(Into::into) + Self::open_from_paths(trust, git_dir, worktree_dir, options).map_err(Into::into) } /// Try to open a git repository directly from the environment. diff --git a/git-repository/src/open.rs b/git-repository/src/open.rs index e2f34292e37..6168b7fa0a5 100644 --- a/git-repository/src/open.rs +++ b/git-repository/src/open.rs @@ -1,7 +1,6 @@ use std::path::PathBuf; use git_features::threading::OwnShared; -use git_sec::Trust; use crate::{Permissions, ThreadSafeRepository}; @@ -124,7 +123,7 @@ impl Options { } impl git_sec::trust::DefaultForLevel for Options { - fn default_for_level(level: Trust) -> Self { + fn default_for_level(level: git_sec::Trust) -> Self { match level { git_sec::Trust::Full => Options { object_store_slots: Default::default(), @@ -179,7 +178,8 @@ impl ThreadSafeRepository { }; let (git_dir, worktree_dir) = git_discover::repository::Path::from_dot_git_dir(path, kind).into_repository_and_work_tree_directories(); - ThreadSafeRepository::open_from_paths(git_dir, worktree_dir, options) + let git_dir_trust = git_sec::Trust::from_path_ownership(&git_dir)?; + ThreadSafeRepository::open_from_paths(git_dir_trust, git_dir, worktree_dir, options) } /// Try to open a git repository in `fallback_directory` (can be worktree or `.git` directory) only if there is no override @@ -208,12 +208,13 @@ impl ThreadSafeRepository { .into_repository_and_work_tree_directories(); let worktree_dir = worktree_dir.or(overrides.worktree_dir); - let trust = git_sec::Trust::from_path_ownership(&git_dir)?; - let options = trust_map.into_value_by_level(trust); - ThreadSafeRepository::open_from_paths(git_dir, worktree_dir, options) + let git_dir_trust = git_sec::Trust::from_path_ownership(&git_dir)?; + let options = trust_map.into_value_by_level(git_dir_trust); + ThreadSafeRepository::open_from_paths(git_dir_trust, git_dir, worktree_dir, options) } pub(crate) fn open_from_paths( + git_dir_trust: git_sec::Trust, git_dir: PathBuf, mut worktree_dir: Option, Options { @@ -238,6 +239,7 @@ impl ThreadSafeRepository { .map(|cd| git_dir.join(cd)); let common_dir_ref = common_dir.as_deref().unwrap_or(&git_dir); let config = crate::config::Cache::new( + git_dir_trust, common_dir_ref, env.xdg_config_home.clone(), env.home.clone(), diff --git a/git-repository/src/repository/location.rs b/git-repository/src/repository/location.rs index 0e23cbc0a1a..a9bf9a9c07c 100644 --- a/git-repository/src/repository/location.rs +++ b/git-repository/src/repository/location.rs @@ -65,4 +65,9 @@ impl crate::Repository { pub fn git_dir(&self) -> &std::path::Path { self.refs.git_dir() } + + /// The trust we place in the git-dir, with lower amounts of trust causing access to configuration to be limited. + pub fn git_dir_trust(&self) -> git_sec::Trust { + self.config.git_dir_trust + } } diff --git a/git-repository/src/worktree/proxy.rs b/git-repository/src/worktree/proxy.rs index a2bb7ebdc4e..85b77382504 100644 --- a/git-repository/src/worktree/proxy.rs +++ b/git-repository/src/worktree/proxy.rs @@ -81,8 +81,13 @@ impl<'repo> Proxy<'repo> { /// a lot of information if work tree access is avoided. pub fn into_repo_with_possibly_inaccessible_worktree(self) -> Result { let base = self.base().ok(); - let repo = - ThreadSafeRepository::open_from_paths(self.git_dir, base, self.parent.linked_worktree_options.clone())?; + let git_dir_trust = git_sec::Trust::from_path_ownership(&self.git_dir)?; + let repo = ThreadSafeRepository::open_from_paths( + git_dir_trust, + self.git_dir, + base, + self.parent.linked_worktree_options.clone(), + )?; Ok(repo.into()) } @@ -96,6 +101,7 @@ impl<'repo> Proxy<'repo> { return Err(into_repo::Error::MissingWorktree { base }); } let repo = ThreadSafeRepository::open_from_paths( + self.parent.config.git_dir_trust, self.git_dir, base.into(), self.parent.linked_worktree_options.clone(), From 57237303d9ae8a746c64d05ecedf3d43a0d041f6 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 18 Jul 2022 19:43:54 +0800 Subject: [PATCH 259/366] refactor (#331) --- git-repository/src/config/cache.rs | 1 - git-repository/src/config/mod.rs | 2 - git-repository/src/lib.rs | 12 ++-- git-repository/src/open.rs | 58 +++++++++++------- git-repository/src/repository/config.rs | 5 ++ git-repository/src/repository/location.rs | 4 +- git-repository/src/repository/worktree.rs | 2 +- git-repository/src/worktree/proxy.rs | 10 +-- git-repository/tests/repository/config.rs | 75 +++++++++++++---------- 9 files changed, 98 insertions(+), 71 deletions(-) diff --git a/git-repository/src/config/cache.rs b/git-repository/src/config/cache.rs index 004b2ce2166..7f4998c9ba1 100644 --- a/git-repository/src/config/cache.rs +++ b/git-repository/src/config/cache.rs @@ -109,7 +109,6 @@ impl Cache { } Ok(Cache { - git_dir_trust, resolved: config.into(), use_multi_pack_index, object_hash, diff --git a/git-repository/src/config/mod.rs b/git-repository/src/config/mod.rs index d284f110aff..6cad58abba5 100644 --- a/git-repository/src/config/mod.rs +++ b/git-repository/src/config/mod.rs @@ -52,8 +52,6 @@ pub(crate) struct Cache { /// The path to the user-level excludes file to ignore certain files in the worktree. #[cfg_attr(not(feature = "git-index"), allow(dead_code))] pub excludes_file: Option, - /// The amount of trust we place into the git_dir of the repository. - pub git_dir_trust: git_sec::Trust, /// Define how we can use values obtained with `xdg_config(…)` and its `XDG_CONFIG_HOME` variable. #[cfg_attr(not(feature = "git-index"), allow(dead_code))] xdg_config_home_env: permission::env_var::Resource, diff --git a/git-repository/src/lib.rs b/git-repository/src/lib.rs index 8e2d24747a3..587e27c0b58 100644 --- a/git-repository/src/lib.rs +++ b/git-repository/src/lib.rs @@ -261,6 +261,11 @@ pub fn open(directory: impl Into) -> Result, options: open::Options) -> Result { + ThreadSafeRepository::open_opts(directory, options).map(Into::into) +} + /// pub mod permission { /// @@ -355,9 +360,8 @@ pub mod init { use git_sec::trust::DefaultForLevel; let path = crate::create::into(directory.as_ref(), options)?; let (git_dir, worktree_dir) = path.into_repository_and_work_tree_directories(); - let trust = git_sec::Trust::Full; - let options = crate::open::Options::default_for_level(trust); - ThreadSafeRepository::open_from_paths(trust, git_dir, worktree_dir, options).map_err(Into::into) + let options = crate::open::Options::default_for_level(git_sec::Trust::Full); + ThreadSafeRepository::open_from_paths(git_dir, worktree_dir, options).map_err(Into::into) } } } @@ -427,7 +431,7 @@ pub mod discover { let (path, trust) = upwards_opts(directory, options)?; let (git_dir, worktree_dir) = path.into_repository_and_work_tree_directories(); let options = trust_map.into_value_by_level(trust); - Self::open_from_paths(trust, git_dir, worktree_dir, options).map_err(Into::into) + Self::open_from_paths(git_dir, worktree_dir, options).map_err(Into::into) } /// Try to open a git repository directly from the environment. diff --git a/git-repository/src/open.rs b/git-repository/src/open.rs index 6168b7fa0a5..eaee0cb6b13 100644 --- a/git-repository/src/open.rs +++ b/git-repository/src/open.rs @@ -65,6 +65,7 @@ pub struct Options { pub(crate) object_store_slots: git_odb::store::init::Slots, pub(crate) replacement_objects: ReplacementObjects, pub(crate) permissions: Permissions, + pub(crate) git_dir_trust: Option, } #[derive(Default, Clone)] @@ -116,6 +117,19 @@ impl Options { self } + /// Set the trust level of the `.git` directory we are about to open. + /// + /// This can be set manually to force trust even though otherwise it might + /// not be fully trusted, leading to limitations in how configuration files + /// are interpreted. + /// + /// If not called explicitly, it will be determined by looking at its + /// ownership via [`git_sec::Trust::from_path_ownership()`]. + pub fn with(mut self, trust: git_sec::Trust) -> Self { + self.git_dir_trust = trust.into(); + self + } + /// Open a repository at `path` with the options set so far. pub fn open(self, path: impl Into) -> Result { ThreadSafeRepository::open_opts(path, self) @@ -129,11 +143,13 @@ impl git_sec::trust::DefaultForLevel for Options { object_store_slots: Default::default(), replacement_objects: Default::default(), permissions: Permissions::all(), + git_dir_trust: git_sec::Trust::Full.into(), }, git_sec::Trust::Reduced => Options { object_store_slots: git_odb::store::init::Slots::Given(32), // limit resource usage replacement_objects: ReplacementObjects::Disable, // don't be tricked into seeing manufactured objects permissions: Default::default(), + git_dir_trust: git_sec::Trust::Reduced.into(), }, } } @@ -165,7 +181,7 @@ impl ThreadSafeRepository { /// `options` for fine-grained control. /// /// Note that you should use [`crate::discover()`] if security should be adjusted by ownership. - pub fn open_opts(path: impl Into, options: Options) -> Result { + pub fn open_opts(path: impl Into, mut options: Options) -> Result { let (path, kind) = { let path = path.into(); match git_discover::is_git(&path) { @@ -178,8 +194,10 @@ impl ThreadSafeRepository { }; let (git_dir, worktree_dir) = git_discover::repository::Path::from_dot_git_dir(path, kind).into_repository_and_work_tree_directories(); - let git_dir_trust = git_sec::Trust::from_path_ownership(&git_dir)?; - ThreadSafeRepository::open_from_paths(git_dir_trust, git_dir, worktree_dir, options) + if options.git_dir_trust.is_none() { + options.git_dir_trust = git_sec::Trust::from_path_ownership(&git_dir)?.into(); + } + ThreadSafeRepository::open_from_paths(git_dir, worktree_dir, options) } /// Try to open a git repository in `fallback_directory` (can be worktree or `.git` directory) only if there is no override @@ -210,23 +228,26 @@ impl ThreadSafeRepository { let git_dir_trust = git_sec::Trust::from_path_ownership(&git_dir)?; let options = trust_map.into_value_by_level(git_dir_trust); - ThreadSafeRepository::open_from_paths(git_dir_trust, git_dir, worktree_dir, options) + ThreadSafeRepository::open_from_paths(git_dir, worktree_dir, options) } pub(crate) fn open_from_paths( - git_dir_trust: git_sec::Trust, git_dir: PathBuf, mut worktree_dir: Option, - Options { + options: Options, + ) -> Result { + let Options { + git_dir_trust, object_store_slots, - replacement_objects, + ref replacement_objects, permissions: Permissions { - git_dir: git_dir_perm, - env, + git_dir: ref git_dir_perm, + ref env, }, - }: Options, - ) -> Result { - if *git_dir_perm != git_sec::ReadWrite::all() { + } = options; + let git_dir_trust = git_dir_trust.expect("trust must be been determined by now"); + + if **git_dir_perm != git_sec::ReadWrite::all() { // TODO: respect `save.directory`, which needs more support from git-config to do properly. return Err(Error::UnsafeGitDir { path: git_dir }); } @@ -292,16 +313,6 @@ impl ThreadSafeRepository { }) .unwrap_or_default(); - // used when spawning new repositories off this one when following worktrees - let linked_worktree_options = Options { - object_store_slots, - replacement_objects, - permissions: Permissions { - env, - git_dir: git_dir_perm, - }, - }; - Ok(ThreadSafeRepository { objects: OwnShared::new(git_odb::Store::at_opts( common_dir_ref.join("objects"), @@ -316,7 +327,8 @@ impl ThreadSafeRepository { refs, work_tree: worktree_dir, config, - linked_worktree_options, + // used when spawning new repositories off this one when following worktrees + linked_worktree_options: options, }) } } diff --git a/git-repository/src/repository/config.rs b/git-repository/src/repository/config.rs index 9484524ae59..728222f9312 100644 --- a/git-repository/src/repository/config.rs +++ b/git-repository/src/repository/config.rs @@ -5,4 +5,9 @@ impl crate::Repository { pub fn config_snapshot(&self) -> config::Snapshot<'_> { config::Snapshot { repo: self } } + + /// The options used to open the repository. + pub fn open_options(&self) -> &crate::open::Options { + &self.linked_worktree_options + } } diff --git a/git-repository/src/repository/location.rs b/git-repository/src/repository/location.rs index a9bf9a9c07c..3f2e990f981 100644 --- a/git-repository/src/repository/location.rs +++ b/git-repository/src/repository/location.rs @@ -68,6 +68,8 @@ impl crate::Repository { /// The trust we place in the git-dir, with lower amounts of trust causing access to configuration to be limited. pub fn git_dir_trust(&self) -> git_sec::Trust { - self.config.git_dir_trust + self.linked_worktree_options + .git_dir_trust + .expect("definitely set by now") } } diff --git a/git-repository/src/repository/worktree.rs b/git-repository/src/repository/worktree.rs index 5826e0fde2b..cef2f54a088 100644 --- a/git-repository/src/repository/worktree.rs +++ b/git-repository/src/repository/worktree.rs @@ -44,7 +44,7 @@ impl crate::Repository { /// Note that it might be the one that is currently open if this repository dosn't point to a linked worktree. /// Also note that the main repo might be bare. pub fn main_repo(&self) -> Result { - crate::open(self.common_dir()) + crate::ThreadSafeRepository::open_opts(self.common_dir(), self.linked_worktree_options.clone()).map(Into::into) } /// Return the currently set worktree if there is one, acting as platform providing a validated worktree base path. diff --git a/git-repository/src/worktree/proxy.rs b/git-repository/src/worktree/proxy.rs index 85b77382504..a2bb7ebdc4e 100644 --- a/git-repository/src/worktree/proxy.rs +++ b/git-repository/src/worktree/proxy.rs @@ -81,13 +81,8 @@ impl<'repo> Proxy<'repo> { /// a lot of information if work tree access is avoided. pub fn into_repo_with_possibly_inaccessible_worktree(self) -> Result { let base = self.base().ok(); - let git_dir_trust = git_sec::Trust::from_path_ownership(&self.git_dir)?; - let repo = ThreadSafeRepository::open_from_paths( - git_dir_trust, - self.git_dir, - base, - self.parent.linked_worktree_options.clone(), - )?; + let repo = + ThreadSafeRepository::open_from_paths(self.git_dir, base, self.parent.linked_worktree_options.clone())?; Ok(repo.into()) } @@ -101,7 +96,6 @@ impl<'repo> Proxy<'repo> { return Err(into_repo::Error::MissingWorktree { base }); } let repo = ThreadSafeRepository::open_from_paths( - self.parent.config.git_dir_trust, self.git_dir, base.into(), self.parent.linked_worktree_options.clone(), diff --git a/git-repository/tests/repository/config.rs b/git-repository/tests/repository/config.rs index a60ba776f08..9a49aedab01 100644 --- a/git-repository/tests/repository/config.rs +++ b/git-repository/tests/repository/config.rs @@ -1,42 +1,55 @@ use crate::named_repo; +use git_repository as git; use std::path::Path; #[test] fn access_values() { - let repo = named_repo("make_config_repo.sh").unwrap(); - let config = repo.config_snapshot(); + for trust in [git_sec::Trust::Full, git_sec::Trust::Reduced] { + let repo = named_repo("make_config_repo.sh").unwrap(); + let repo = git::open_opts(repo.git_dir(), repo.open_options().clone().with(trust)).unwrap(); - assert_eq!(config.boolean("core.bare"), Some(false)); - assert_eq!(config.boolean("a.bad-bool"), None); - assert_eq!(config.try_boolean("core.bare"), Some(Ok(false))); - assert!(matches!(config.try_boolean("a.bad-bool"), Some(Err(_)))); + let config = repo.config_snapshot(); - assert_eq!(config.integer("a.int"), Some(42)); - assert_eq!(config.integer("a.int-overflowing"), None); - assert_eq!(config.integer("a.int-overflowing"), None); - assert!(config.try_integer("a.int-overflowing").expect("present").is_err()); + assert_eq!(config.boolean("core.bare"), Some(false)); + assert_eq!(config.boolean("a.bad-bool"), None); + assert_eq!(config.try_boolean("core.bare"), Some(Ok(false))); + assert!(matches!(config.try_boolean("a.bad-bool"), Some(Err(_)))); - assert_eq!( - config.string("a.single-string").expect("present").as_ref(), - "hello world" - ); + assert_eq!(config.integer("a.int"), Some(42)); + assert_eq!(config.integer("a.int-overflowing"), None); + assert_eq!(config.integer("a.int-overflowing"), None); + assert!(config.try_integer("a.int-overflowing").expect("present").is_err()); - assert_eq!(config.boolean("core.missing"), None); - assert_eq!(config.try_boolean("core.missing"), None); + assert_eq!( + config.string("a.single-string").expect("present").as_ref(), + "hello world" + ); - assert_eq!( - config - .trusted_path("a.relative-path") - .expect("exists") - .expect("no error"), - Path::new("./something") - ); - assert_eq!( - config - .trusted_path("a.absolute-path") - .expect("exists") - .expect("no error"), - Path::new("/etc/man.conf") - ); - assert!(config.trusted_path("a.bad-user-path").expect("exists").is_err()); + assert_eq!(config.boolean("core.missing"), None); + assert_eq!(config.try_boolean("core.missing"), None); + + let relative_path_key = "a.relative-path"; + if trust == git_sec::Trust::Full { + assert_eq!( + config + .trusted_path(relative_path_key) + .expect("exists") + .expect("no error"), + Path::new("./something") + ); + assert_eq!( + config + .trusted_path("a.absolute-path") + .expect("exists") + .expect("no error"), + Path::new("/etc/man.conf") + ); + assert!(config.trusted_path("a.bad-user-path").expect("exists").is_err()); + } else { + assert!( + config.trusted_path(relative_path_key).is_none(), + "trusted paths need full trust" + ); + } + } } From 014d27c9f5272bc098fa38a258db03cf46768b3f Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 19 Jul 2022 09:29:10 +0800 Subject: [PATCH 260/366] update crate status to reflect current state (#331) --- crate-status.md | 60 ++++++++++++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/crate-status.md b/crate-status.md index 7b67713a973..2fc0df7b4c9 100644 --- a/crate-status.md +++ b/crate-status.md @@ -388,26 +388,32 @@ See its [README.md](https://github.com/Byron/gitoxide/blob/main/git-tempfile/REA See its [README.md](https://github.com/Byron/gitoxide/blob/main/git-lock/README.md). ### git-config -* [ ] read - * line-wise parsing with decent error messages +* [x] read + * zero-copy parsing with event emission * [x] decode value * [x] boolean * [x] integer * [x] color * [ ] ANSI code output for terminal colors * [x] path (incl. resolution) + * [ ] date * [x] include - * **includeIf** - * [x] `gitdir`, `gitdir/i`, `onbranch` - * [ ] `hasconfig` -* [x] write + * **includeIf** + * [x] `gitdir`, `gitdir/i`, and `onbranch` + * [ ] `hasconfig` +* [x] access values and sections by name and sub-section +* [x] edit configuration in memory, non-destructively + * cross-platform newline handling +* [x] write files back for lossless round-trips. * keep comments and whitespace, and only change lines that are affected by actual changes, to allow truly non-destructive editing -* [ ] `Config` type which integrates multiple files into one interface to support system, user and repository levels for config files +* [x] cascaded loading of various configuration files into one + * [x] load from environment variables + * [ ] load from well-known sources for global configuration + * [ ] load repository configuration with all known sources * [x] API documentation * [x] Some examples ### git-repository - * [x] utilities for applications to make long running operations interruptible gracefully and to support timeouts in servers. * [ ] handle `core.repositoryFormatVersion` and extensions * [x] support for unicode-precomposition of command-line arguments (needs explicit use in parent application) @@ -427,16 +433,15 @@ See its [README.md](https://github.com/Byron/gitoxide/blob/main/git-lock/README. * [ ] make [git-notes](https://git-scm.com/docs/git-notes) accessible * [x] tree entries * **diffs/changes** - * [x] tree with tree + * [x] tree with working tree * [ ] tree with index - * [ ] index with working tree * [x] initialize - * [ ] Proper configuration depending on platform (e.g. ignorecase, filemode, …) + * [x] Proper configuration depending on platform (e.g. ignorecase, filemode, …) * **Id** * [x] short hashes with detection of ambiguity. * **Commit** * [x] `describe()` like functionality - * [x] create new commit + * [x] create new commit from tree * **Objects** * [x] lookup * [x] peel to object kind @@ -446,23 +451,32 @@ See its [README.md](https://github.com/Byron/gitoxide/blob/main/git-lock/README. * **references** * [x] peel to end * [x] ref-log access - * [ ] clone + * [ ] clone from remote * [ ] shallow - * [ ] namespaces support - * [ ] sparse checkout support * [ ] execute hooks - * [ ] .gitignore handling - * [ ] checkout/stage conversions clean + smudge as in .gitattributes * **refs** * [ ] run transaction hooks and handle special repository states like quarantine * [ ] support for different backends like `files` and `reftable` - * **worktrees** - * [x] open a repository with worktrees - * [x] read locked state - * [ ] obtain 'prunable' information - * [x] proper handling of worktree related refs - * [ ] create, move, remove, and repair + * **main or linked worktree** + * [ ] add files with `.gitignore` handling + * [ ] checkout with conversions like clean + smudge as in `.gitattributes` + * [ ] _diff_ index with working tree + * [ ] sparse checkout support * [ ] read per-worktree config if `extensions.worktreeConfig` is enabled. + * **index** + * [ ] tree from index + * [ ] index from tree + * **worktrees** + * [x] open a repository with worktrees + * [x] read locked state + * [ ] obtain 'prunable' information + * [x] proper handling of worktree related refs + * [ ] create, move, remove, and repair + * **config** + * [x] read the primitive types `boolean`, `integer`, `string` + * [x] read and interpolate trusted paths + * [x] low-level API for more elaborate access to all details of `git-config` files + * [ ] a way to make changes to individual configuration files * [ ] remotes with push and pull * [x] mailmap * [x] object replacements (`git replace`) From e701e053fd05850973930be0cefe73e8f3604d40 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 19 Jul 2022 11:07:15 +0800 Subject: [PATCH 261/366] feat: `Source::storage_location()` to know where files should be located. (#331) --- git-config/src/file/init/from_env.rs | 7 +--- git-config/src/lib.rs | 1 + git-config/src/source.rs | 62 ++++++++++++++++++++++++++++ git-config/src/types.rs | 7 ---- 4 files changed, 65 insertions(+), 12 deletions(-) create mode 100644 git-config/src/source.rs diff --git a/git-config/src/file/init/from_env.rs b/git-config/src/file/init/from_env.rs index 9d759f1c122..0eb0c9d5b2f 100644 --- a/git-config/src/file/init/from_env.rs +++ b/git-config/src/file/init/from_env.rs @@ -68,19 +68,16 @@ impl File<'static> { git_sec::Trust::Full.into(), ); } else { - // Divergence from git-config(1) - // These two are supposed to share the same scope and override - // rather than append according to git-config(1) documentation. if let Some(xdg_config_home) = env::var_os("XDG_CONFIG_HOME") { push_path( PathBuf::from(xdg_config_home).join("git/config"), - Source::User, + Source::Global, git_sec::Trust::Full.into(), ); } else if let Some(home) = env::var_os("HOME") { push_path( PathBuf::from(home).join(".config/git/config"), - Source::User, + Source::Global, git_sec::Trust::Full.into(), ); } diff --git a/git-config/src/lib.rs b/git-config/src/lib.rs index bc47db97a32..e5ffff2a287 100644 --- a/git-config/src/lib.rs +++ b/git-config/src/lib.rs @@ -49,6 +49,7 @@ pub use values::{boolean, color, integer, path}; mod types; pub use types::{Boolean, Color, File, Integer, Path, Source}; +mod source; mod permissions; pub use permissions::Permissions; diff --git a/git-config/src/source.rs b/git-config/src/source.rs new file mode 100644 index 00000000000..f1a8e4002f8 --- /dev/null +++ b/git-config/src/source.rs @@ -0,0 +1,62 @@ +use crate::Source; +use std::borrow::Cow; +use std::ffi::OsString; +use std::path::{Path, PathBuf}; + +impl Source { + /// Return true if the source indicates a location within a file of a repository. + pub fn is_in_repository(self) -> bool { + matches!(self, Source::Local | Source::Worktree) + } + + /// Returns the location at which a file of this type would be stored, or `None` if + /// there is no notion of persistent storage for this source, with `env_var` to obtain environment variables. + /// Note that the location can be relative for repository-local sources like `Local` and `Worktree`, + /// and the caller has to known which base it it relative to, namely the `common_dir` in the `Local` case + /// and the `git_dir` in the `Worktree` case. + /// Be aware that depending on environment overrides, multiple scopes might return the same path, which should + /// only be loaded once nonetheless. + /// + /// With `env_var` it becomes possible to prevent accessing environment variables entirely to comply with `git-sec` + /// permissions for example. + pub fn storage_location(self, env_var: &mut dyn FnMut(&str) -> Option) -> Option> { + use Source::*; + match self { + System => env_var("GIT_CONFIG_NO_SYSTEM") + .is_none() + .then(|| PathBuf::from(env_var("GIT_CONFIG_SYSTEM").unwrap_or_else(|| "/etc/gitconfig".into())).into()), + Global => match env_var("GIT_CONFIG_GLOBAL") { + Some(global_override) => Some(PathBuf::from(global_override).into()), + None => env_var("XDG_CONFIG_HOME") + .map(|home| { + let mut p = PathBuf::from(home); + p.push("git"); + p.push("config"); + p + }) + .or_else(|| { + env_var("HOME").map(|home| { + let mut p = PathBuf::from(home); + p.push(".config"); + p.push("git"); + p.push("config"); + p + }) + }) + .map(Cow::Owned), + }, + User => env_var("GIT_CONFIG_GLOBAL") + .map(|global_override| PathBuf::from(global_override).into()) + .or_else(|| { + env_var("HOME").map(|home| { + let mut p = PathBuf::from(home); + p.push(".gitconfig"); + p.into() + }) + }), + Local => Some(Path::new("config").into()), + Worktree => Some(Path::new("config.worktree").into()), + Env | Cli | Api => None, + } + } +} diff --git a/git-config/src/types.rs b/git-config/src/types.rs index f442e372de2..8fa77cf4d51 100644 --- a/git-config/src/types.rs +++ b/git-config/src/types.rs @@ -37,13 +37,6 @@ pub enum Source { Api, } -impl Source { - /// Return true if the source indicates a location within a file of a repository. - pub fn is_in_repository(self) -> bool { - matches!(self, Source::Local | Source::Worktree) - } -} - /// High level `git-config` reader and writer. /// /// This is the full-featured implementation that can deserialize, serialize, From 97374e4d867e82d7be04da2eaa6ef553e0d9a7ff Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 19 Jul 2022 12:03:26 +0800 Subject: [PATCH 262/366] Classify `Source` in accordance for what git actually does. (#331) It's also opening the system up to eventually provide platform specific locations for different kinds of configuration files. See https://github.com/Byron/gitoxide/discussions/417#discussioncomment-3067962 for reference. --- git-config/src/file/init/from_env.rs | 6 ++--- git-config/src/lib.rs | 3 ++- git-config/src/source.rs | 25 +++++++++++++++++--- git-config/src/types.rs | 11 +++++---- git-config/tests/file/init/from_paths/mod.rs | 4 ++-- 5 files changed, 35 insertions(+), 14 deletions(-) diff --git a/git-config/src/file/init/from_env.rs b/git-config/src/file/init/from_env.rs index 0eb0c9d5b2f..e512679b9f7 100644 --- a/git-config/src/file/init/from_env.rs +++ b/git-config/src/file/init/from_env.rs @@ -64,20 +64,20 @@ impl File<'static> { if let Some(git_config_global) = env::var_os("GIT_CONFIG_GLOBAL") { push_path( PathBuf::from(git_config_global), - Source::Global, + Source::Application, git_sec::Trust::Full.into(), ); } else { if let Some(xdg_config_home) = env::var_os("XDG_CONFIG_HOME") { push_path( PathBuf::from(xdg_config_home).join("git/config"), - Source::Global, + Source::Application, git_sec::Trust::Full.into(), ); } else if let Some(home) = env::var_os("HOME") { push_path( PathBuf::from(home).join(".config/git/config"), - Source::Global, + Source::Application, git_sec::Trust::Full.into(), ); } diff --git a/git-config/src/lib.rs b/git-config/src/lib.rs index e5ffff2a287..2ecf0ded7f0 100644 --- a/git-config/src/lib.rs +++ b/git-config/src/lib.rs @@ -49,7 +49,8 @@ pub use values::{boolean, color, integer, path}; mod types; pub use types::{Boolean, Color, File, Integer, Path, Source}; -mod source; +/// +pub mod source; mod permissions; pub use permissions::Permissions; diff --git a/git-config/src/source.rs b/git-config/src/source.rs index f1a8e4002f8..a500fbdda08 100644 --- a/git-config/src/source.rs +++ b/git-config/src/source.rs @@ -3,10 +3,29 @@ use std::borrow::Cow; use std::ffi::OsString; use std::path::{Path, PathBuf}; +/// The category of a [`Source`], in order of ascending precedence. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum Kind { + /// A source shared for the entire system. + System, + /// Application specific configuration unique for each user of the `System`. + Global, + /// Configuration relevant only to the repository, possibly including the worktree. + Repository, + /// Configuration specified after all other configuration was loaded for the purpose of overrides. + Override, +} + impl Source { /// Return true if the source indicates a location within a file of a repository. - pub fn is_in_repository(self) -> bool { - matches!(self, Source::Local | Source::Worktree) + pub const fn kind(self) -> Kind { + use Source::*; + match self { + System => Kind::System, + Application | User => Kind::Global, + Local | Worktree => Kind::Repository, + Env | Cli | Api => Kind::Override, + } } /// Returns the location at which a file of this type would be stored, or `None` if @@ -25,7 +44,7 @@ impl Source { System => env_var("GIT_CONFIG_NO_SYSTEM") .is_none() .then(|| PathBuf::from(env_var("GIT_CONFIG_SYSTEM").unwrap_or_else(|| "/etc/gitconfig".into())).into()), - Global => match env_var("GIT_CONFIG_GLOBAL") { + Application => match env_var("GIT_CONFIG_GLOBAL") { Some(global_override) => Some(PathBuf::from(global_override).into()), None => env_var("XDG_CONFIG_HOME") .map(|home| { diff --git a/git-config/src/types.rs b/git-config/src/types.rs index 8fa77cf4d51..1a3117df08e 100644 --- a/git-config/src/types.rs +++ b/git-config/src/types.rs @@ -10,7 +10,7 @@ use crate::{ }; /// A list of known sources for configuration files, with the first one being overridden -/// by the second one, and so forth. +/// by the second one, and so forth, in order of ascending precedence. /// Note that included files via `include.path` and `includeIf..path` inherit /// their source. #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] @@ -18,10 +18,11 @@ pub enum Source { /// System-wide configuration path. This is defined as /// `$(prefix)/etc/gitconfig` (where prefix is the git-installation directory). System, - /// Also known as the user configuration path. This is usually `~/.gitconfig`. - Global, - /// Second user-specific configuration path; if `$XDG_CONFIG_HOME` is not - /// set or empty, `$HOME/.config/git/config` will be used. + /// A platform defined location for where a user's application data should be located. + /// If `$XDG_CONFIG_HOME` is not set or empty, `$HOME/.config/git/config` will be used + /// on unix. + Application, + /// This is usually `~/.gitconfig` on unix. User, /// The configuration of the repository itself, located in `.git/config`. Local, diff --git a/git-config/tests/file/init/from_paths/mod.rs b/git-config/tests/file/init/from_paths/mod.rs index c6be00cf3a8..36905627fc4 100644 --- a/git-config/tests/file/init/from_paths/mod.rs +++ b/git-config/tests/file/init/from_paths/mod.rs @@ -134,7 +134,7 @@ fn multiple_paths_multi_value_and_filter() -> crate::Result { let paths_and_source = vec![ (a_path, Source::System), - (b_path, Source::Global), + (b_path, Source::Application), (c_path, Source::User), (d_path, Source::Worktree), (e_path, Source::Local), @@ -159,7 +159,7 @@ fn multiple_paths_multi_value_and_filter() -> crate::Result { ); assert_eq!( - config.strings_filter("core", None, "key", &mut |m| m.source == Source::Global + config.strings_filter("core", None, "key", &mut |m| m.source == Source::Application || m.source == Source::User), Some(vec![cow_str("b"), cow_str("c")]) ); From ca89d0d4785ec4d66a0a4316fbc74be63dcc0f48 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 19 Jul 2022 12:04:51 +0800 Subject: [PATCH 263/366] adjust to changes in `git-config` (#331) --- git-repository/src/config/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-repository/src/config/mod.rs b/git-repository/src/config/mod.rs index 6cad58abba5..7e498a974c7 100644 --- a/git-repository/src/config/mod.rs +++ b/git-repository/src/config/mod.rs @@ -12,7 +12,7 @@ pub struct Snapshot<'repo> { pub(crate) mod section { pub fn is_trusted(meta: &git_config::file::Metadata) -> bool { - meta.trust == git_sec::Trust::Full || !meta.source.is_in_repository() + meta.trust == git_sec::Trust::Full || meta.source.kind() != git_config::source::Kind::Repository } } From cc29ad0c94e32379521daf92037a0e01669c1f3a Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 19 Jul 2022 15:46:39 +0800 Subject: [PATCH 264/366] try to fix rustup auto-update which now breaks CI regularly (#331) Don't konw why it sometimes works though, maybe some windows filesystem issue with latency after deletion? --- .github/workflows/ci.yml | 3 +++ .github/workflows/msrv.yml | 3 +++ .github/workflows/release.yml | 4 ++++ 3 files changed, 10 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b645d057175..b819a07b72e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,6 +52,9 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v1 + - run: rustup set auto-self-update disable + if: contains(runner.os, 'windows') + shell: bash - uses: actions-rs/toolchain@v1 with: profile: default diff --git a/.github/workflows/msrv.yml b/.github/workflows/msrv.yml index bf86d3b0719..ae0a0a9ed6d 100644 --- a/.github/workflows/msrv.yml +++ b/.github/workflows/msrv.yml @@ -22,6 +22,9 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 + - run: rustup set auto-self-update disable + if: contains(runner.os, 'windows') + shell: bash - uses: actions-rs/toolchain@v1 with: profile: minimal diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6257e4fdb2c..a44e8299fb8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -121,6 +121,10 @@ jobs: run: | ci/macos-install-packages + - run: rustup set auto-self-update disable + if: contains(runner.os, 'windows') + shell: bash + - name: Install Rust uses: actions-rs/toolchain@v1 with: From e512ab09477629957e469719f05e7de65955f3db Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 19 Jul 2022 16:11:03 +0800 Subject: [PATCH 265/366] Allow to configure a different filter for configuration section. (#331) That way we don't hard-code that but merely provide a reasonable default. --- git-repository/src/config/cache.rs | 4 ++-- git-repository/src/config/snapshot.rs | 6 +++++- git-repository/src/open.rs | 17 ++++++++++++++++- git-repository/src/repository/config.rs | 2 +- git-repository/src/repository/impls.rs | 4 ++-- git-repository/src/repository/init.rs | 2 +- git-repository/src/repository/location.rs | 4 +--- git-repository/src/repository/snapshots.rs | 2 +- git-repository/src/repository/worktree.rs | 2 +- git-repository/src/types.rs | 6 ++++-- git-repository/src/worktree/proxy.rs | 9 ++------- 11 files changed, 36 insertions(+), 22 deletions(-) diff --git a/git-repository/src/config/cache.rs b/git-repository/src/config/cache.rs index 7f4998c9ba1..d63d254f6b0 100644 --- a/git-repository/src/config/cache.rs +++ b/git-repository/src/config/cache.rs @@ -3,11 +3,11 @@ use std::{convert::TryFrom, path::PathBuf}; use git_config::{Boolean, Integer}; use super::{Cache, Error}; -use crate::config::section::is_trusted; use crate::{bstr::ByteSlice, permission}; impl Cache { pub fn new( + mut filter_config_section: fn(&git_config::file::Metadata) -> bool, git_dir_trust: git_sec::Trust, git_dir: &std::path::Path, xdg_config_home_env: permission::env_var::Resource, @@ -42,7 +42,7 @@ impl Cache { let use_multi_pack_index = config_bool(&config, "core.multiPackIndex", true)?; let ignore_case = config_bool(&config, "core.ignoreCase", false)?; let excludes_file = config - .path_filter("core", None, "excludesFile", &mut is_trusted) + .path_filter("core", None, "excludesFile", &mut filter_config_section) .map(|p| { p.interpolate(interpolate_context(git_install_dir, home.as_deref())) .map(|p| p.into_owned()) diff --git a/git-repository/src/config/snapshot.rs b/git-repository/src/config/snapshot.rs index 53b1c11a159..8de4e0b88f4 100644 --- a/git-repository/src/config/snapshot.rs +++ b/git-repository/src/config/snapshot.rs @@ -71,7 +71,11 @@ impl<'repo> Snapshot<'repo> { key.section_name, key.subsection_name, key.value_name, - &mut crate::config::section::is_trusted, + &mut self + .repo + .options + .filter_config_section + .unwrap_or(crate::config::section::is_trusted), )?; let install_dir = self.repo.install_dir().ok(); diff --git a/git-repository/src/open.rs b/git-repository/src/open.rs index eaee0cb6b13..e05b085f227 100644 --- a/git-repository/src/open.rs +++ b/git-repository/src/open.rs @@ -66,6 +66,7 @@ pub struct Options { pub(crate) replacement_objects: ReplacementObjects, pub(crate) permissions: Permissions, pub(crate) git_dir_trust: Option, + pub(crate) filter_config_section: Option bool>, } #[derive(Default, Clone)] @@ -130,6 +131,16 @@ impl Options { self } + /// Set the filter which determines if a configuration section can be used to read values from, + /// hence it returns true if it is eligible. + /// + /// The default filter selects sections whose trust level is [`full`][git_sec::Trust::Full] or + /// whose source is not [`repository-local`][git_config::source::Kind::Repository]. + pub fn filter_config_section(mut self, filter: fn(&git_config::file::Metadata) -> bool) -> Self { + self.filter_config_section = Some(filter); + self + } + /// Open a repository at `path` with the options set so far. pub fn open(self, path: impl Into) -> Result { ThreadSafeRepository::open_opts(path, self) @@ -144,12 +155,14 @@ impl git_sec::trust::DefaultForLevel for Options { replacement_objects: Default::default(), permissions: Permissions::all(), git_dir_trust: git_sec::Trust::Full.into(), + filter_config_section: Some(crate::config::section::is_trusted), }, git_sec::Trust::Reduced => Options { object_store_slots: git_odb::store::init::Slots::Given(32), // limit resource usage replacement_objects: ReplacementObjects::Disable, // don't be tricked into seeing manufactured objects permissions: Default::default(), git_dir_trust: git_sec::Trust::Reduced.into(), + filter_config_section: Some(crate::config::section::is_trusted), }, } } @@ -239,6 +252,7 @@ impl ThreadSafeRepository { let Options { git_dir_trust, object_store_slots, + filter_config_section, ref replacement_objects, permissions: Permissions { git_dir: ref git_dir_perm, @@ -260,6 +274,7 @@ impl ThreadSafeRepository { .map(|cd| git_dir.join(cd)); let common_dir_ref = common_dir.as_deref().unwrap_or(&git_dir); let config = crate::config::Cache::new( + filter_config_section.unwrap_or(crate::config::section::is_trusted), git_dir_trust, common_dir_ref, env.xdg_config_home.clone(), @@ -341,7 +356,7 @@ mod tests { fn size_of_options() { assert_eq!( std::mem::size_of::(), - 56, + 64, "size shouldn't change without us knowing" ); } diff --git a/git-repository/src/repository/config.rs b/git-repository/src/repository/config.rs index 728222f9312..0273d19d784 100644 --- a/git-repository/src/repository/config.rs +++ b/git-repository/src/repository/config.rs @@ -8,6 +8,6 @@ impl crate::Repository { /// The options used to open the repository. pub fn open_options(&self) -> &crate::open::Options { - &self.linked_worktree_options + &self.options } } diff --git a/git-repository/src/repository/impls.rs b/git-repository/src/repository/impls.rs index b8104465aca..aaafab1f824 100644 --- a/git-repository/src/repository/impls.rs +++ b/git-repository/src/repository/impls.rs @@ -6,7 +6,7 @@ impl Clone for crate::Repository { self.work_tree.clone(), self.common_dir.clone(), self.config.clone(), - self.linked_worktree_options.clone(), + self.options.clone(), ) } } @@ -63,7 +63,7 @@ impl From for crate::ThreadSafeRepository { work_tree: r.work_tree, common_dir: r.common_dir, config: r.config, - linked_worktree_options: r.linked_worktree_options, + linked_worktree_options: r.options, } } } diff --git a/git-repository/src/repository/init.rs b/git-repository/src/repository/init.rs index d757ab1e3a7..eaebcce4ae8 100644 --- a/git-repository/src/repository/init.rs +++ b/git-repository/src/repository/init.rs @@ -25,7 +25,7 @@ impl crate::Repository { }, refs, config, - linked_worktree_options, + options: linked_worktree_options, } } diff --git a/git-repository/src/repository/location.rs b/git-repository/src/repository/location.rs index 3f2e990f981..a324c6688dd 100644 --- a/git-repository/src/repository/location.rs +++ b/git-repository/src/repository/location.rs @@ -68,8 +68,6 @@ impl crate::Repository { /// The trust we place in the git-dir, with lower amounts of trust causing access to configuration to be limited. pub fn git_dir_trust(&self) -> git_sec::Trust { - self.linked_worktree_options - .git_dir_trust - .expect("definitely set by now") + self.options.git_dir_trust.expect("definitely set by now") } } diff --git a/git-repository/src/repository/snapshots.rs b/git-repository/src/repository/snapshots.rs index a675b3e3556..216cd9d5bfb 100644 --- a/git-repository/src/repository/snapshots.rs +++ b/git-repository/src/repository/snapshots.rs @@ -101,7 +101,7 @@ impl crate::Repository { match path.interpolate(git_config::path::interpolate::Context { git_install_dir: Some(install_dir.as_path()), home_dir: home.as_deref(), - home_for_user: if self.linked_worktree_options.permissions.git_dir.is_all() { + home_for_user: if self.options.permissions.git_dir.is_all() { Some(git_config::path::interpolate::home_for_user) } else { None diff --git a/git-repository/src/repository/worktree.rs b/git-repository/src/repository/worktree.rs index cef2f54a088..41d0f26aa3e 100644 --- a/git-repository/src/repository/worktree.rs +++ b/git-repository/src/repository/worktree.rs @@ -44,7 +44,7 @@ impl crate::Repository { /// Note that it might be the one that is currently open if this repository dosn't point to a linked worktree. /// Also note that the main repo might be bare. pub fn main_repo(&self) -> Result { - crate::ThreadSafeRepository::open_opts(self.common_dir(), self.linked_worktree_options.clone()).map(Into::into) + crate::ThreadSafeRepository::open_opts(self.common_dir(), self.options.clone()).map(Into::into) } /// Return the currently set worktree if there is one, acting as platform providing a validated worktree base path. diff --git a/git-repository/src/types.rs b/git-repository/src/types.rs index dee5c78aa39..e3920adec36 100644 --- a/git-repository/src/types.rs +++ b/git-repository/src/types.rs @@ -132,8 +132,10 @@ pub struct Repository { pub(crate) bufs: RefCell>>, /// A pre-assembled selection of often-accessed configuration values for quick access. pub(crate) config: crate::config::Cache, - /// options obtained when instantiating this repository for use when following linked worktrees. - pub(crate) linked_worktree_options: crate::open::Options, + /// the options obtained when instantiating this repository. + /// + /// Particularly useful when following linked worktrees and instantiating new equally configured worktree repositories. + pub(crate) options: crate::open::Options, } /// An instance with access to everything a git repository entails, best imagined as container implementing `Sync + Send` for _most_ diff --git a/git-repository/src/worktree/proxy.rs b/git-repository/src/worktree/proxy.rs index a2bb7ebdc4e..80240001634 100644 --- a/git-repository/src/worktree/proxy.rs +++ b/git-repository/src/worktree/proxy.rs @@ -81,8 +81,7 @@ impl<'repo> Proxy<'repo> { /// a lot of information if work tree access is avoided. pub fn into_repo_with_possibly_inaccessible_worktree(self) -> Result { let base = self.base().ok(); - let repo = - ThreadSafeRepository::open_from_paths(self.git_dir, base, self.parent.linked_worktree_options.clone())?; + let repo = ThreadSafeRepository::open_from_paths(self.git_dir, base, self.parent.options.clone())?; Ok(repo.into()) } @@ -95,11 +94,7 @@ impl<'repo> Proxy<'repo> { if !base.is_dir() { return Err(into_repo::Error::MissingWorktree { base }); } - let repo = ThreadSafeRepository::open_from_paths( - self.git_dir, - base.into(), - self.parent.linked_worktree_options.clone(), - )?; + let repo = ThreadSafeRepository::open_from_paths(self.git_dir, base.into(), self.parent.options.clone())?; Ok(repo.into()) } } From 146eeb064822839bc46fd37a247a1b9a84f64e40 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 18 Jul 2022 20:01:15 +0800 Subject: [PATCH 266/366] feat: `File::new_globals()` can instantiate non-local configuration with zero-configuration. (#331) --- git-config/src/file/init/comfort.rs | 54 ++++++++++++++++++++ git-config/src/file/init/from_env.rs | 6 +-- git-config/src/file/init/from_paths.rs | 5 ++ git-config/src/file/init/mod.rs | 1 + git-config/src/source.rs | 21 +++++++- git-config/src/types.rs | 4 +- git-config/tests/file/init/comfort.rs | 10 ++++ git-config/tests/file/init/from_paths/mod.rs | 4 +- git-config/tests/file/init/mod.rs | 1 + 9 files changed, 97 insertions(+), 9 deletions(-) create mode 100644 git-config/src/file/init/comfort.rs create mode 100644 git-config/tests/file/init/comfort.rs diff --git a/git-config/src/file/init/comfort.rs b/git-config/src/file/init/comfort.rs new file mode 100644 index 00000000000..2016c0b6164 --- /dev/null +++ b/git-config/src/file/init/comfort.rs @@ -0,0 +1,54 @@ +use crate::file::{init, Metadata}; +use crate::{path, source, File}; +use std::path::PathBuf; + +/// easy-instantiation of typical git configuration files with all configuration defaulting to typical values. +impl File<'static> { + /// Open all global configuration files which involves the following sources: + /// + /// * [system][Source::System] + /// * [git][Source::Git] + /// * [user][Source::User] + /// + /// which excludes repository local configuration. + /// + /// Note that `includeIf` conditions in global files will cause failure as the required information + /// to resolve them isn't present without a repository. + /// + /// Also note that relevant information to interpolate paths will be obtained from the environment or other + /// source on unix. + /// + pub fn new_globals() -> Result, init::from_paths::Error> { + let metas = [source::Kind::System, source::Kind::Global] + .iter() + .flat_map(|kind| kind.sources()) + .filter_map(|source| { + let path = source + .storage_location(&mut |name| std::env::var_os(name)) + .and_then(|p| p.is_file().then(|| p)) + .map(|p| p.into_owned()); + + Metadata { + path, + source: *source, + level: 0, + trust: git_sec::Trust::Full, + } + .into() + }); + + let home = std::env::var("HOME").ok().map(PathBuf::from); + let options = init::Options { + includes: init::includes::Options::follow( + path::interpolate::Context { + git_install_dir: None, + home_dir: home.as_deref(), + home_for_user: Some(path::interpolate::home_for_user), + }, + Default::default(), + ), + ..Default::default() + }; + File::from_paths_metadata(metas, options) + } +} diff --git a/git-config/src/file/init/from_env.rs b/git-config/src/file/init/from_env.rs index e512679b9f7..d40453ed7d7 100644 --- a/git-config/src/file/init/from_env.rs +++ b/git-config/src/file/init/from_env.rs @@ -64,20 +64,20 @@ impl File<'static> { if let Some(git_config_global) = env::var_os("GIT_CONFIG_GLOBAL") { push_path( PathBuf::from(git_config_global), - Source::Application, + Source::Git, git_sec::Trust::Full.into(), ); } else { if let Some(xdg_config_home) = env::var_os("XDG_CONFIG_HOME") { push_path( PathBuf::from(xdg_config_home).join("git/config"), - Source::Application, + Source::Git, git_sec::Trust::Full.into(), ); } else if let Some(home) = env::var_os("HOME") { push_path( PathBuf::from(home).join(".config/git/config"), - Source::Application, + Source::Git, git_sec::Trust::Full.into(), ); } diff --git a/git-config/src/file/init/from_paths.rs b/git-config/src/file/init/from_paths.rs index 008e7d09cc3..68da308595f 100644 --- a/git-config/src/file/init/from_paths.rs +++ b/git-config/src/file/init/from_paths.rs @@ -2,6 +2,7 @@ use crate::file::init::Options; use crate::file::{init, Metadata}; use crate::{file, file::init::includes, parse, File}; use git_features::threading::OwnShared; +use std::collections::BTreeSet; /// The error returned by [`File::from_paths_metadata()`] and [`File::from_env_paths()`]. #[derive(Debug, thiserror::Error)] @@ -58,10 +59,14 @@ impl File<'static> { ) -> Result { let mut target = None; let mut buf = Vec::with_capacity(512); + let mut seen = BTreeSet::default(); for (path, meta) in path_meta.into_iter().filter_map(|meta| { let mut meta = meta.into(); meta.path.take().map(|p| (p, meta)) }) { + if !seen.insert(path.clone()) { + continue; + } let config = Self::from_path_with_buf(path, &mut buf, meta, options)?; match &mut target { None => { diff --git a/git-config/src/file/init/mod.rs b/git-config/src/file/init/mod.rs index e6671ce89d8..a76203753bf 100644 --- a/git-config/src/file/init/mod.rs +++ b/git-config/src/file/init/mod.rs @@ -5,6 +5,7 @@ use git_features::threading::OwnShared; mod types; pub use types::{Error, Options}; +mod comfort; /// pub mod from_env; /// diff --git a/git-config/src/source.rs b/git-config/src/source.rs index a500fbdda08..651dc0c27d0 100644 --- a/git-config/src/source.rs +++ b/git-config/src/source.rs @@ -16,13 +16,30 @@ pub enum Kind { Override, } +impl Kind { + /// Return a list of sources associated with this `Kind` of source, in order of ascending precedence. + pub fn sources(self) -> &'static [Source] { + let src = match self { + Kind::System => &[Source::System] as &[_], + Kind::Global => &[Source::Git, Source::User], + Kind::Repository => &[Source::Local, Source::Worktree], + Kind::Override => &[Source::Env, Source::Cli, Source::Api], + }; + debug_assert!( + src.iter().all(|src| src.kind() == self), + "BUG: classification of source has to match the ordering here, see `Source::kind()`" + ); + src + } +} + impl Source { /// Return true if the source indicates a location within a file of a repository. pub const fn kind(self) -> Kind { use Source::*; match self { System => Kind::System, - Application | User => Kind::Global, + Git | User => Kind::Global, Local | Worktree => Kind::Repository, Env | Cli | Api => Kind::Override, } @@ -44,7 +61,7 @@ impl Source { System => env_var("GIT_CONFIG_NO_SYSTEM") .is_none() .then(|| PathBuf::from(env_var("GIT_CONFIG_SYSTEM").unwrap_or_else(|| "/etc/gitconfig".into())).into()), - Application => match env_var("GIT_CONFIG_GLOBAL") { + Git => match env_var("GIT_CONFIG_GLOBAL") { Some(global_override) => Some(PathBuf::from(global_override).into()), None => env_var("XDG_CONFIG_HOME") .map(|home| { diff --git a/git-config/src/types.rs b/git-config/src/types.rs index 1a3117df08e..479ffad16b6 100644 --- a/git-config/src/types.rs +++ b/git-config/src/types.rs @@ -18,10 +18,10 @@ pub enum Source { /// System-wide configuration path. This is defined as /// `$(prefix)/etc/gitconfig` (where prefix is the git-installation directory). System, - /// A platform defined location for where a user's application data should be located. + /// A platform defined location for where a user's git application configuration should be located. /// If `$XDG_CONFIG_HOME` is not set or empty, `$HOME/.config/git/config` will be used /// on unix. - Application, + Git, /// This is usually `~/.gitconfig` on unix. User, /// The configuration of the repository itself, located in `.git/config`. diff --git a/git-config/tests/file/init/comfort.rs b/git-config/tests/file/init/comfort.rs new file mode 100644 index 00000000000..2032ce8f10b --- /dev/null +++ b/git-config/tests/file/init/comfort.rs @@ -0,0 +1,10 @@ +use git_config::source; + +#[test] +fn new_globals() { + let config = git_config::File::new_globals().unwrap(); + assert!(config.sections().all(|section| { + let kind = section.meta().source.kind(); + kind != source::Kind::Repository && kind != source::Kind::Override + })); +} diff --git a/git-config/tests/file/init/from_paths/mod.rs b/git-config/tests/file/init/from_paths/mod.rs index 36905627fc4..e2583ae0c1e 100644 --- a/git-config/tests/file/init/from_paths/mod.rs +++ b/git-config/tests/file/init/from_paths/mod.rs @@ -134,7 +134,7 @@ fn multiple_paths_multi_value_and_filter() -> crate::Result { let paths_and_source = vec![ (a_path, Source::System), - (b_path, Source::Application), + (b_path, Source::Git), (c_path, Source::User), (d_path, Source::Worktree), (e_path, Source::Local), @@ -159,7 +159,7 @@ fn multiple_paths_multi_value_and_filter() -> crate::Result { ); assert_eq!( - config.strings_filter("core", None, "key", &mut |m| m.source == Source::Application + config.strings_filter("core", None, "key", &mut |m| m.source == Source::Git || m.source == Source::User), Some(vec![cow_str("b"), cow_str("c")]) ); diff --git a/git-config/tests/file/init/mod.rs b/git-config/tests/file/init/mod.rs index 47eb4a8d09c..ba8967d590b 100644 --- a/git-config/tests/file/init/mod.rs +++ b/git-config/tests/file/init/mod.rs @@ -1,3 +1,4 @@ +mod comfort; pub mod from_env; mod from_paths; mod from_str; From 98d45c2f59863fdee033b38e757cec09593f6892 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 19 Jul 2022 17:11:38 +0800 Subject: [PATCH 267/366] change!: remove `File::from_env_paths()`. (#331) It's replaced by its more comfortable `new_globals()`. --- git-config/src/file/init/from_env.rs | 84 ++----------------- git-config/tests/file/init/from_env.rs | 14 ++-- .../includes/conditional/gitdir/mod.rs | 6 +- 3 files changed, 17 insertions(+), 87 deletions(-) diff --git a/git-config/src/file/init/from_env.rs b/git-config/src/file/init/from_env.rs index d40453ed7d7..d6d82556897 100644 --- a/git-config/src/file/init/from_env.rs +++ b/git-config/src/file/init/from_env.rs @@ -1,9 +1,9 @@ use git_features::threading::OwnShared; +use std::borrow::Cow; use std::convert::TryFrom; -use std::{borrow::Cow, path::PathBuf}; -use crate::file::{init, Metadata}; -use crate::{file, parse, parse::section, path::interpolate, File, Source}; +use crate::file::init; +use crate::{file, parse, parse::section, path::interpolate, File}; /// Represents the errors that may occur when calling [`File::from_env`][crate::File::from_env()]. #[derive(Debug, thiserror::Error)] @@ -29,83 +29,13 @@ pub enum Error { /// Instantiation from environment variables impl File<'static> { - /// Constructs a `git-config` from the default cascading sequence of global configuration files, - /// excluding any repository-local configuration. + /// Generates a config from `GIT_CONFIG_*` environment variables or returns `Ok(None)` if no configuration was found. + /// See [`git-config`'s documentation] for more information on the environment variables in question. /// - /// See for details. - // TODO: how does this relate to the `fs` module? Have a feeling options should contain instructions on which files to use. - pub fn from_env_paths(options: init::Options<'_>) -> Result, init::from_paths::Error> { - use std::env; - - let mut metas = vec![]; - let mut push_path = |path: PathBuf, source: Source, trust: Option| { - if let Some(meta) = trust - .or_else(|| git_sec::Trust::from_path_ownership(&path).ok()) - .map(|trust| Metadata { - path: Some(path), - trust, - level: 0, - source, - }) - { - metas.push(meta) - } - }; - - if env::var("GIT_CONFIG_NO_SYSTEM").is_err() { - let git_config_system_path = env::var_os("GIT_CONFIG_SYSTEM").unwrap_or_else(|| "/etc/gitconfig".into()); - push_path( - PathBuf::from(git_config_system_path), - Source::System, - git_sec::Trust::Full.into(), - ); - } - - if let Some(git_config_global) = env::var_os("GIT_CONFIG_GLOBAL") { - push_path( - PathBuf::from(git_config_global), - Source::Git, - git_sec::Trust::Full.into(), - ); - } else { - if let Some(xdg_config_home) = env::var_os("XDG_CONFIG_HOME") { - push_path( - PathBuf::from(xdg_config_home).join("git/config"), - Source::Git, - git_sec::Trust::Full.into(), - ); - } else if let Some(home) = env::var_os("HOME") { - push_path( - PathBuf::from(home).join(".config/git/config"), - Source::Git, - git_sec::Trust::Full.into(), - ); - } - - if let Some(home) = env::var_os("HOME") { - push_path( - PathBuf::from(home).join(".gitconfig"), - Source::User, - git_sec::Trust::Full.into(), - ); - } - } - - // TODO: remove this in favor of a clear non-local approach to integrate better with git-repository - if let Some(git_dir) = env::var_os("GIT_DIR") { - push_path(PathBuf::from(git_dir).join("config"), Source::Local, None); - } - - File::from_paths_metadata(metas, options) - } - - /// Generates a config from the environment variables. This is neither - /// zero-copy nor zero-alloc. See [`git-config`'s documentation] on - /// environment variable for more information. + /// With `options` configured, it's possible `include.path` directives as well. /// /// [`git-config`'s documentation]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-GITCONFIGCOUNT - // TODO: use `init::Options` instead for lossy support. - pub fn from_env(options: init::Options<'_>) -> Result>, Error> { + pub fn from_environment(options: init::Options<'_>) -> Result>, Error> { use std::env; let count: usize = match env::var("GIT_CONFIG_COUNT") { Ok(v) => v.parse().map_err(|_| Error::InvalidConfigCount { input: v })?, diff --git a/git-config/tests/file/init/from_env.rs b/git-config/tests/file/init/from_env.rs index 4d600be0c15..961d6bddfd9 100644 --- a/git-config/tests/file/init/from_env.rs +++ b/git-config/tests/file/init/from_env.rs @@ -37,7 +37,7 @@ impl<'a> Drop for Env<'a> { #[test] #[serial] fn empty_without_relevant_environment() { - let config = File::from_env(Default::default()).unwrap(); + let config = File::from_environment(Default::default()).unwrap(); assert!(config.is_none()); } @@ -45,7 +45,7 @@ fn empty_without_relevant_environment() { #[serial] fn empty_with_zero_count() { let _env = Env::new().set("GIT_CONFIG_COUNT", "0"); - let config = File::from_env(Default::default()).unwrap(); + let config = File::from_environment(Default::default()).unwrap(); assert!(config.is_none()); } @@ -53,7 +53,7 @@ fn empty_with_zero_count() { #[serial] fn parse_error_with_invalid_count() { let _env = Env::new().set("GIT_CONFIG_COUNT", "invalid"); - let err = File::from_env(Default::default()).unwrap_err(); + let err = File::from_environment(Default::default()).unwrap_err(); assert!(matches!(err, from_env::Error::InvalidConfigCount { .. })); } @@ -65,7 +65,7 @@ fn single_key_value_pair() { .set("GIT_CONFIG_KEY_0", "core.key") .set("GIT_CONFIG_VALUE_0", "value"); - let config = File::from_env(Default::default()).unwrap().unwrap(); + let config = File::from_environment(Default::default()).unwrap().unwrap(); assert_eq!( config.raw_value("core", None, "key").unwrap(), Cow::<[u8]>::Borrowed(b"value") @@ -86,7 +86,7 @@ fn multiple_key_value_pairs() { .set("GIT_CONFIG_KEY_2", "core.c") .set("GIT_CONFIG_VALUE_2", "c"); - let config = File::from_env(Default::default()).unwrap().unwrap(); + let config = File::from_environment(Default::default()).unwrap().unwrap(); assert_eq!( config.raw_value("core", None, "a").unwrap(), @@ -111,7 +111,7 @@ fn error_on_relative_paths_in_include_paths() { .set("GIT_CONFIG_KEY_0", "include.path") .set("GIT_CONFIG_VALUE_0", "some_git_config"); - let res = File::from_env(init::Options { + let res = File::from_environment(init::Options { includes: includes::Options { max_depth: 1, ..Default::default() @@ -144,7 +144,7 @@ fn follow_include_paths() { .set("GIT_CONFIG_KEY_3", "include.origin.path") .set("GIT_CONFIG_VALUE_3", escape_backslashes(b_path)); - let config = File::from_env(init::Options { + let config = File::from_environment(init::Options { includes: includes::Options { max_depth: 1, ..Default::default() diff --git a/git-config/tests/file/init/from_paths/includes/conditional/gitdir/mod.rs b/git-config/tests/file/init/from_paths/includes/conditional/gitdir/mod.rs index 797674b4007..9c76422974d 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/gitdir/mod.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/gitdir/mod.rs @@ -98,7 +98,7 @@ fn dot_slash_from_environment_causes_error() -> crate::Result { ) .set("GIT_CONFIG_VALUE_0", "./include.path"); - let res = git_config::File::from_env(env.to_init_options()); + let res = git_config::File::from_environment(env.to_init_options()); assert!( matches!( res, @@ -117,7 +117,7 @@ fn dot_slash_from_environment_causes_error() -> crate::Result { .set("GIT_CONFIG_KEY_0", "includeIf.gitdir:./worktree/.path") .set("GIT_CONFIG_VALUE_0", &absolute_path); - let res = git_config::File::from_env(env.to_init_options()); + let res = git_config::File::from_environment(env.to_init_options()); assert!( matches!( res, @@ -138,7 +138,7 @@ fn dot_slash_from_environment_causes_error() -> crate::Result { ) .set("GIT_CONFIG_VALUE_0", absolute_path); - let res = git_config::File::from_env(env.to_init_options()); + let res = git_config::File::from_environment(env.to_init_options()); assert!(res.is_ok(), "missing paths are ignored as before"); } From 45c964a3f581dc7d3090bbbe26f188d553783fb3 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 19 Jul 2022 17:31:57 +0800 Subject: [PATCH 268/366] prepare for supporting comfortable version of environment overrides (#331) --- git-config/src/file/init/comfort.rs | 14 ++++---------- git-config/src/file/init/includes.rs | 26 ++++++++++++++++++++------ 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/git-config/src/file/init/comfort.rs b/git-config/src/file/init/comfort.rs index 2016c0b6164..5250f469c70 100644 --- a/git-config/src/file/init/comfort.rs +++ b/git-config/src/file/init/comfort.rs @@ -1,5 +1,5 @@ use crate::file::{init, Metadata}; -use crate::{path, source, File}; +use crate::{source, File}; use std::path::PathBuf; /// easy-instantiation of typical git configuration files with all configuration defaulting to typical values. @@ -10,7 +10,7 @@ impl File<'static> { /// * [git][Source::Git] /// * [user][Source::User] /// - /// which excludes repository local configuration. + /// which excludes repository local configuration, as well as override-configuration from environment variables. /// /// Note that `includeIf` conditions in global files will cause failure as the required information /// to resolve them isn't present without a repository. @@ -18,6 +18,7 @@ impl File<'static> { /// Also note that relevant information to interpolate paths will be obtained from the environment or other /// source on unix. /// + /// pub fn new_globals() -> Result, init::from_paths::Error> { let metas = [source::Kind::System, source::Kind::Global] .iter() @@ -39,14 +40,7 @@ impl File<'static> { let home = std::env::var("HOME").ok().map(PathBuf::from); let options = init::Options { - includes: init::includes::Options::follow( - path::interpolate::Context { - git_install_dir: None, - home_dir: home.as_deref(), - home_for_user: Some(path::interpolate::home_for_user), - }, - Default::default(), - ), + includes: init::includes::Options::follow_without_conditional(home.as_deref()), ..Default::default() }; File::from_paths_metadata(metas, options) diff --git a/git-config/src/file/init/includes.rs b/git-config/src/file/init/includes.rs index 553d2fc32c6..fc7c7340f62 100644 --- a/git-config/src/file/init/includes.rs +++ b/git-config/src/file/init/includes.rs @@ -288,7 +288,7 @@ mod types { /// Used during path interpolation, both for include paths before trying to read the file, and for /// paths used in conditional `gitdir` includes. - pub interpolate: crate::path::interpolate::Context<'a>, + pub interpolate: interpolate::Context<'a>, /// Additional context for conditional includes to work. pub conditional: conditional::Context<'a>, @@ -311,10 +311,7 @@ mod types { /// to support `gitdir` and `onbranch` based `includeIf` directives as well as standard `include.path` resolution. /// Note that the follow-mode is `git`-style, following at most 10 indirections while /// producing an error if the depth is exceeded. - pub fn follow( - interpolate: crate::path::interpolate::Context<'a>, - conditional: conditional::Context<'a>, - ) -> Self { + pub fn follow(interpolate: interpolate::Context<'a>, conditional: conditional::Context<'a>) -> Self { Options { max_depth: 10, error_on_max_depth_exceeded: true, @@ -323,9 +320,26 @@ mod types { } } + /// Like [`follow`][Options::follow()], but without information to resolve `includeIf` directories as well as default + /// configuration to allow resolving `~username/` path. `home_dir` is required to resolve `~/` paths if set. + /// Note that `%(prefix)` paths cannot be interpolated with this configuration, use [`follow()`][Options::follow()] + /// instead for complete control. + pub fn follow_without_conditional(home_dir: Option<&'a std::path::Path>) -> Self { + Options { + max_depth: 10, + error_on_max_depth_exceeded: true, + interpolate: interpolate::Context { + git_install_dir: None, + home_dir, + home_for_user: Some(interpolate::home_for_user), + }, + conditional: Default::default(), + } + } + /// Set the context used for interpolation when interpolating paths to include as well as the paths /// in `gitdir` conditional includes. - pub fn interpolate_with(mut self, context: crate::path::interpolate::Context<'a>) -> Self { + pub fn interpolate_with(mut self, context: interpolate::Context<'a>) -> Self { self.interpolate = context; self } From 7dadfd82494d47e36d3f570988eaf3c6b628977f Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 19 Jul 2022 17:38:38 +0800 Subject: [PATCH 269/366] feat: `File::new_environment_overrides()` to easily instantiate overrides from the environment. (#331) --- git-config/src/file/init/comfort.rs | 41 ++++++++++++++++++-------- git-config/src/file/init/from_env.rs | 2 +- git-config/src/file/init/from_paths.rs | 2 +- git-config/src/file/init/includes.rs | 3 +- git-config/src/file/init/types.rs | 3 +- git-config/tests/file/init/comfort.rs | 8 +++++ 6 files changed, 41 insertions(+), 18 deletions(-) diff --git a/git-config/src/file/init/comfort.rs b/git-config/src/file/init/comfort.rs index 5250f469c70..5ddc591ead7 100644 --- a/git-config/src/file/init/comfort.rs +++ b/git-config/src/file/init/comfort.rs @@ -2,23 +2,23 @@ use crate::file::{init, Metadata}; use crate::{source, File}; use std::path::PathBuf; -/// easy-instantiation of typical git configuration files with all configuration defaulting to typical values. +/// Easy-instantiation of typical non-repository git configuration files with all configuration defaulting to typical values. +/// +/// ### Limitations +/// +/// Note that `includeIf` conditions in global files will cause failure as the required information +/// to resolve them isn't present without a repository. +/// +/// Also note that relevant information to interpolate paths will be obtained from the environment or other +/// source on unix. impl File<'static> { /// Open all global configuration files which involves the following sources: /// - /// * [system][Source::System] - /// * [git][Source::Git] - /// * [user][Source::User] + /// * [system][crate::Source::System] + /// * [git][crate::Source::Git] + /// * [user][crate::Source::User] /// /// which excludes repository local configuration, as well as override-configuration from environment variables. - /// - /// Note that `includeIf` conditions in global files will cause failure as the required information - /// to resolve them isn't present without a repository. - /// - /// Also note that relevant information to interpolate paths will be obtained from the environment or other - /// source on unix. - /// - /// pub fn new_globals() -> Result, init::from_paths::Error> { let metas = [source::Kind::System, source::Kind::Global] .iter() @@ -45,4 +45,21 @@ impl File<'static> { }; File::from_paths_metadata(metas, options) } + + /// Generates a config from `GIT_CONFIG_*` environment variables and return a possibly empty `File`. + /// A typical use of this is to [`append`][File::append()] this configuration to another one with lower + /// precedence to obtain overrides. + /// + /// See [`git-config`'s documentation] for more information on the environment variables in question. + /// + /// [`git-config`'s documentation]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-GITCONFIGCOUNT + pub fn new_environment_overrides() -> Result, init::from_env::Error> { + let home = std::env::var("HOME").ok().map(PathBuf::from); + let options = init::Options { + includes: init::includes::Options::follow_without_conditional(home.as_deref()), + ..Default::default() + }; + + File::from_environment(options).map(Option::unwrap_or_default) + } } diff --git a/git-config/src/file/init/from_env.rs b/git-config/src/file/init/from_env.rs index d6d82556897..387dee8afcb 100644 --- a/git-config/src/file/init/from_env.rs +++ b/git-config/src/file/init/from_env.rs @@ -5,7 +5,7 @@ use std::convert::TryFrom; use crate::file::init; use crate::{file, parse, parse::section, path::interpolate, File}; -/// Represents the errors that may occur when calling [`File::from_env`][crate::File::from_env()]. +/// Represents the errors that may occur when calling [`File::from_environment()`]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { diff --git a/git-config/src/file/init/from_paths.rs b/git-config/src/file/init/from_paths.rs index 68da308595f..2fdb225561e 100644 --- a/git-config/src/file/init/from_paths.rs +++ b/git-config/src/file/init/from_paths.rs @@ -4,7 +4,7 @@ use crate::{file, file::init::includes, parse, File}; use git_features::threading::OwnShared; use std::collections::BTreeSet; -/// The error returned by [`File::from_paths_metadata()`] and [`File::from_env_paths()`]. +/// The error returned by [`File::from_paths_metadata()`] and [`File::from_path_with_buf()`]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { diff --git a/git-config/src/file/init/includes.rs b/git-config/src/file/init/includes.rs index fc7c7340f62..1675fbb6447 100644 --- a/git-config/src/file/init/includes.rs +++ b/git-config/src/file/init/includes.rs @@ -251,8 +251,7 @@ mod types { use crate::parse; use crate::path::interpolate; - /// The error returned by [`File::from_paths_metadata()`][crate::File::from_paths_metadata()] - /// and [`File::from_env_paths()`][crate::File::from_env_paths()]. + /// The error returned when following includes. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { diff --git a/git-config/src/file/init/types.rs b/git-config/src/file/init/types.rs index 4b586169823..76f5ce10e70 100644 --- a/git-config/src/file/init/types.rs +++ b/git-config/src/file/init/types.rs @@ -3,8 +3,7 @@ use crate::parse; use crate::parse::Event; use crate::path::interpolate; -/// The error returned by [`File::from_paths_metadata()`][crate::File::from_paths_metadata()] and -/// [`File::from_env_paths()`][crate::File::from_env_paths()]. +/// The error returned by [`File::from_bytes_no_includes()`][crate::File::from_bytes_no_includes()]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { diff --git a/git-config/tests/file/init/comfort.rs b/git-config/tests/file/init/comfort.rs index 2032ce8f10b..7d3cf8ea674 100644 --- a/git-config/tests/file/init/comfort.rs +++ b/git-config/tests/file/init/comfort.rs @@ -1,4 +1,5 @@ use git_config::source; +use serial_test::serial; #[test] fn new_globals() { @@ -8,3 +9,10 @@ fn new_globals() { kind != source::Kind::Repository && kind != source::Kind::Override })); } + +#[test] +#[serial] +fn new_environment_overrides() { + let config = git_config::File::new_environment_overrides().unwrap(); + assert!(config.is_void()); +} From 0f753e922e313f735ed267f913366771e9de1111 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 19 Jul 2022 18:22:18 +0800 Subject: [PATCH 270/366] change!: `Target(Ref)?::try_name()` now returns `Option<&FullNameRef>`. (#331) That way, the name is actually directly usable in most methods that require a validated name as input. --- git-ref/src/target.rs | 35 +++++++++---------- git-ref/tests/file/reference.rs | 5 ++- .../prepare_and_commit/create_or_update.rs | 7 ++-- 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/git-ref/src/target.rs b/git-ref/src/target.rs index d2bfabd6020..8e38d75a1fd 100644 --- a/git-ref/src/target.rs +++ b/git-ref/src/target.rs @@ -1,9 +1,8 @@ use std::{convert::TryFrom, fmt}; use git_hash::{oid, ObjectId}; -use git_object::bstr::BStr; -use crate::{FullName, Kind, Target, TargetRef}; +use crate::{FullName, FullNameRef, Kind, Target, TargetRef}; impl<'a> TargetRef<'a> { /// Returns the kind of the target the ref is pointing to. @@ -28,14 +27,14 @@ impl<'a> TargetRef<'a> { } } /// Interpret this target as name of the reference it points to which maybe `None` if it an object id. - pub fn try_name(&self) -> Option<&BStr> { + pub fn try_name(&self) -> Option<&FullNameRef> { match self { - TargetRef::Symbolic(path) => Some(path.as_bstr()), + TargetRef::Symbolic(name) => Some(name), TargetRef::Peeled(_) => None, } } /// Convert this instance into an owned version, without consuming it. - pub fn into_owned(self) -> crate::Target { + pub fn into_owned(self) -> Target { self.into() } } @@ -58,10 +57,10 @@ impl Target { } /// Interpret this owned Target as shared Target - pub fn to_ref(&self) -> crate::TargetRef<'_> { + pub fn to_ref(&self) -> TargetRef<'_> { match self { - Target::Peeled(oid) => crate::TargetRef::Peeled(oid), - Target::Symbolic(name) => crate::TargetRef::Symbolic(name.as_ref()), + Target::Peeled(oid) => TargetRef::Peeled(oid), + Target::Symbolic(name) => TargetRef::Symbolic(name.as_ref()), } } @@ -95,28 +94,28 @@ impl Target { } } /// Interpret this target as name of the reference it points to which maybe `None` if it an object id. - pub fn try_name(&self) -> Option<&BStr> { + pub fn try_name(&self) -> Option<&FullNameRef> { match self { - Target::Symbolic(name) => Some(name.as_bstr()), + Target::Symbolic(name) => Some(name.as_ref()), Target::Peeled(_) => None, } } } -impl<'a> From> for Target { - fn from(src: crate::TargetRef<'a>) -> Self { +impl<'a> From> for Target { + fn from(src: TargetRef<'a>) -> Self { match src { - crate::TargetRef::Peeled(oid) => Target::Peeled(oid.to_owned()), - crate::TargetRef::Symbolic(name) => Target::Symbolic(name.to_owned()), + TargetRef::Peeled(oid) => Target::Peeled(oid.to_owned()), + TargetRef::Symbolic(name) => Target::Symbolic(name.to_owned()), } } } -impl<'a> PartialEq> for Target { - fn eq(&self, other: &crate::TargetRef<'a>) -> bool { +impl<'a> PartialEq> for Target { + fn eq(&self, other: &TargetRef<'a>) -> bool { match (self, other) { - (Target::Peeled(lhs), crate::TargetRef::Peeled(rhs)) => lhs == rhs, - (Target::Symbolic(lhs), crate::TargetRef::Symbolic(rhs)) => lhs.as_bstr() == rhs.as_bstr(), + (Target::Peeled(lhs), TargetRef::Peeled(rhs)) => lhs == rhs, + (Target::Symbolic(lhs), TargetRef::Symbolic(rhs)) => lhs.as_bstr() == rhs.as_bstr(), _ => false, } } diff --git a/git-ref/tests/file/reference.rs b/git-ref/tests/file/reference.rs index 222d009907c..70a910dc48f 100644 --- a/git-ref/tests/file/reference.rs +++ b/git-ref/tests/file/reference.rs @@ -193,7 +193,10 @@ mod parse { Reference::try_from_path("HEAD".try_into().expect("valid static name"), $input).unwrap(); assert_eq!(reference.kind(), $kind); assert_eq!(reference.target.to_ref().try_id(), $id); - assert_eq!(reference.target.to_ref().try_name(), $ref); + assert_eq!( + reference.target.to_ref().try_name().map(|n| n.as_bstr()), + $ref + ); } }; } diff --git a/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs b/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs index 546d8fb90d9..9efaf76b7c5 100644 --- a/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs +++ b/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs @@ -443,7 +443,10 @@ fn symbolic_head_missing_referent_then_update_referent() -> crate::Result { let head = store.find_loose(&edits[0].name)?; assert_eq!(head.name.as_bstr(), "HEAD"); assert_eq!(head.kind(), git_ref::Kind::Symbolic); - assert_eq!(head.target.to_ref().try_name(), Some(referent.as_bytes().as_bstr())); + assert_eq!( + head.target.to_ref().try_name().map(|n| n.as_bstr()), + Some(referent.as_bytes().as_bstr()) + ); assert!(!head.log_exists(&store), "no reflog is written for symbolic ref"); assert!(store.try_find_loose(referent)?.is_none(), "referent wasn't created"); @@ -506,7 +509,7 @@ fn symbolic_head_missing_referent_then_update_referent() -> crate::Result { "head is still symbolic, not detached" ); assert_eq!( - head.target.to_ref().try_name(), + head.target.to_ref().try_name().map(|n| n.as_bstr()), Some(referent.as_bytes().as_bstr()), "it still points to the referent" ); From 9be1dd6f7cdb9aea7c85df896e370b3c40f5e4ec Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 19 Jul 2022 18:30:29 +0800 Subject: [PATCH 271/366] =?UTF-8?q?prepare=20for=20resolving=20a=20complet?= =?UTF-8?q?e=20config=E2=80=A6=20(#331)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …which turns out to be more complicated due to cycles between ref-store instantiation and the config instantiation itself. It can be broken but that requires some more refactoring. --- git-repository/src/config/cache.rs | 3 ++- git-repository/src/open.rs | 30 ++++++++++++++++-------------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/git-repository/src/config/cache.rs b/git-repository/src/config/cache.rs index d63d254f6b0..ee53c9a47be 100644 --- a/git-repository/src/config/cache.rs +++ b/git-repository/src/config/cache.rs @@ -7,6 +7,7 @@ use crate::{bstr::ByteSlice, permission}; impl Cache { pub fn new( + branch_name: Option<&git_ref::FullNameRef>, mut filter_config_section: fn(&git_config::file::Metadata) -> bool, git_dir_trust: git_sec::Trust, git_dir: &std::path::Path, @@ -31,7 +32,7 @@ impl Cache { interpolate_context(git_install_dir, home.as_deref()), git_config::file::init::includes::conditional::Context { git_dir: git_dir.into(), - branch_name: None, + branch_name, }, ), }, diff --git a/git-repository/src/open.rs b/git-repository/src/open.rs index e05b085f227..a02982e0513 100644 --- a/git-repository/src/open.rs +++ b/git-repository/src/open.rs @@ -273,7 +273,17 @@ impl ThreadSafeRepository { .transpose()? .map(|cd| git_dir.join(cd)); let common_dir_ref = common_dir.as_deref().unwrap_or(&git_dir); + let mut refs = { + let reflog = git_ref::store::WriteReflog::Disable; + let object_hash = git_hash::Kind::Sha1; // TODO: load repo-local config first, no includes resolution, then merge with global. + match &common_dir { + Some(common_dir) => crate::RefStore::for_linked_worktree(&git_dir, common_dir, reflog, object_hash), + None => crate::RefStore::at(&git_dir, reflog, object_hash), + } + }; + let head = refs.find("HEAD").ok(); let config = crate::config::Cache::new( + head.as_ref().and_then(|head| head.target.try_name()), filter_config_section.unwrap_or(crate::config::section::is_trusted), git_dir_trust, common_dir_ref, @@ -292,21 +302,13 @@ impl ThreadSafeRepository { None => {} } - let refs = { - let reflog = config.reflog.unwrap_or_else(|| { - if worktree_dir.is_none() { - git_ref::store::WriteReflog::Disable - } else { - git_ref::store::WriteReflog::Normal - } - }); - match &common_dir { - Some(common_dir) => { - crate::RefStore::for_linked_worktree(&git_dir, common_dir, reflog, config.object_hash) - } - None => crate::RefStore::at(&git_dir, reflog, config.object_hash), + refs.write_reflog = config.reflog.unwrap_or_else(|| { + if worktree_dir.is_none() { + git_ref::store::WriteReflog::Disable + } else { + git_ref::store::WriteReflog::Normal } - }; + }); let replacements = replacement_objects .clone() From 61ecaca43fb871eaff5cf94a8e7f9cc9413a5a77 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 19 Jul 2022 19:30:42 +0800 Subject: [PATCH 272/366] Don't fail on empty input on the comfort level (#331) --- git-config/src/file/init/comfort.rs | 15 ++++++++++++++- git-config/src/file/init/from_paths.rs | 5 +++-- git-config/src/file/init/includes.rs | 4 ++-- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/git-config/src/file/init/comfort.rs b/git-config/src/file/init/comfort.rs index 5ddc591ead7..b376b04bf31 100644 --- a/git-config/src/file/init/comfort.rs +++ b/git-config/src/file/init/comfort.rs @@ -19,6 +19,8 @@ impl File<'static> { /// * [user][crate::Source::User] /// /// which excludes repository local configuration, as well as override-configuration from environment variables. + /// + /// Note that the file might [be empty][File::is_void()] in case no configuration file was found. pub fn new_globals() -> Result, init::from_paths::Error> { let metas = [source::Kind::System, source::Kind::Global] .iter() @@ -43,7 +45,10 @@ impl File<'static> { includes: init::includes::Options::follow_without_conditional(home.as_deref()), ..Default::default() }; - File::from_paths_metadata(metas, options) + File::from_paths_metadata(metas, options).or_else(|err| match err { + init::from_paths::Error::NoInput => Ok(File::default()), + err => Err(err), + }) } /// Generates a config from `GIT_CONFIG_*` environment variables and return a possibly empty `File`. @@ -63,3 +68,11 @@ impl File<'static> { File::from_environment(options).map(Option::unwrap_or_default) } } + +/// An easy way to provide complete configuration for a repository. +impl File<'static> { + /// TODO + pub fn from_git_dir(_dir: impl AsRef) -> Result, init::from_paths::Error> { + todo!() + } +} diff --git a/git-config/src/file/init/from_paths.rs b/git-config/src/file/init/from_paths.rs index 2fdb225561e..ab1eb8f0775 100644 --- a/git-config/src/file/init/from_paths.rs +++ b/git-config/src/file/init/from_paths.rs @@ -18,8 +18,9 @@ pub enum Error { /// Instantiation from one or more paths impl File<'static> { - /// Load the file at `path` from `source` without following include directives. Note that the path will be checked for - /// ownership to derive trust. + /// Load the single file at `path` with `source` without following include directives. + /// + /// Note that the path will be checked for ownership to derive trust. pub fn from_path_no_includes(path: impl Into, source: crate::Source) -> Result { let path = path.into(); let trust = git_sec::Trust::from_path_ownership(&path)?; diff --git a/git-config/src/file/init/includes.rs b/git-config/src/file/init/includes.rs index 1675fbb6447..20cc81c828c 100644 --- a/git-config/src/file/init/includes.rs +++ b/git-config/src/file/init/includes.rs @@ -295,7 +295,7 @@ mod types { impl Options<'_> { /// Provide options to never follow include directives at all. - pub fn no_follow() -> Self { + pub fn no_includes() -> Self { Options { max_depth: 0, error_on_max_depth_exceeded: false, @@ -346,7 +346,7 @@ mod types { impl Default for Options<'_> { fn default() -> Self { - Self::no_follow() + Self::no_includes() } } From 612645f74ffc49229ccd783361b4d455e2284ac0 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 19 Jul 2022 20:00:31 +0800 Subject: [PATCH 273/366] make it necessary to deal with the possibility of no-input in `from_paths_metadata()` . (#331) --- git-config/src/file/init/comfort.rs | 5 +--- git-config/src/file/init/from_paths.rs | 10 +++---- .../includes/conditional/gitdir/util.rs | 3 +- .../from_paths/includes/conditional/mod.rs | 3 +- .../includes/conditional/onbranch.rs | 3 +- .../init/from_paths/includes/unconditional.rs | 30 +++++++++---------- git-config/tests/file/init/from_paths/mod.rs | 7 +++-- 7 files changed, 31 insertions(+), 30 deletions(-) diff --git a/git-config/src/file/init/comfort.rs b/git-config/src/file/init/comfort.rs index b376b04bf31..1200d9a0498 100644 --- a/git-config/src/file/init/comfort.rs +++ b/git-config/src/file/init/comfort.rs @@ -45,10 +45,7 @@ impl File<'static> { includes: init::includes::Options::follow_without_conditional(home.as_deref()), ..Default::default() }; - File::from_paths_metadata(metas, options).or_else(|err| match err { - init::from_paths::Error::NoInput => Ok(File::default()), - err => Err(err), - }) + File::from_paths_metadata(metas, options).map(Option::unwrap_or_default) } /// Generates a config from `GIT_CONFIG_*` environment variables and return a possibly empty `File`. diff --git a/git-config/src/file/init/from_paths.rs b/git-config/src/file/init/from_paths.rs index ab1eb8f0775..ac426b02fc6 100644 --- a/git-config/src/file/init/from_paths.rs +++ b/git-config/src/file/init/from_paths.rs @@ -12,8 +12,6 @@ pub enum Error { Io(#[from] std::io::Error), #[error(transparent)] Init(#[from] init::Error), - #[error("Not a single path was provided to load the configuration from")] - NoInput, } /// Instantiation from one or more paths @@ -54,10 +52,12 @@ impl File<'static> { } /// Constructs a `git-config` file from the provided metadata, which must include a path to read from or be ignored. + /// Returns `Ok(None)` if there was not a single input path provided, which is a possibility due to + /// [`Metadata::path`] being an `Option`. pub fn from_paths_metadata( - path_meta: impl IntoIterator>, + path_meta: impl IntoIterator>, options: Options<'_>, - ) -> Result { + ) -> Result, Error> { let mut target = None; let mut buf = Vec::with_capacity(512); let mut seen = BTreeSet::default(); @@ -78,6 +78,6 @@ impl File<'static> { } } } - target.ok_or(Error::NoInput) + Ok(target) } } diff --git a/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs b/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs index d07472c43e1..064ce420163 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs @@ -129,7 +129,8 @@ pub fn assert_section_value( .into_iter() .map(|path| git_config::file::Metadata::try_from_path(path, git_config::Source::Local).unwrap()), env.to_init_options(), - )?; + )? + .expect("non-empty"); assert_eq!( config.string("section", None, "value"), diff --git a/git-config/tests/file/init/from_paths/includes/conditional/mod.rs b/git-config/tests/file/init/from_paths/includes/conditional/mod.rs index 993bc82127d..d99b39ddc4b 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/mod.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/mod.rs @@ -62,7 +62,8 @@ fn include_and_includeif_correct_inclusion_order() -> crate::Result { git_config::Source::Api, )?), options_with_git_dir(&dir), - )?; + )? + .expect("non-empty"); assert_eq!( config.strings("core", None, "b"), diff --git a/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs b/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs index d538ef89fb8..588f477d9a7 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs @@ -251,7 +251,8 @@ value = branch-override-by-include git_config::Source::Local, )?), options, - )?; + )? + .expect("non-empty"); assert_eq!( config.string("section", None, "value"), Some(cow_str(match expect { diff --git a/git-config/tests/file/init/from_paths/includes/unconditional.rs b/git-config/tests/file/init/from_paths/includes/unconditional.rs index d52aee9f6b2..6ec70cd351f 100644 --- a/git-config/tests/file/init/from_paths/includes/unconditional.rs +++ b/git-config/tests/file/init/from_paths/includes/unconditional.rs @@ -71,7 +71,7 @@ fn multiple() -> crate::Result { ), )?; - let config = File::from_paths_metadata(into_meta(vec![c_path]), follow_options())?; + let config = File::from_paths_metadata(into_meta(vec![c_path]), follow_options())?.expect("non-empty"); assert_eq!(config.string("core", None, "c"), Some(cow_str("12"))); assert_eq!(config.integer("core", None, "d"), Some(Ok(41))); @@ -116,7 +116,8 @@ fn respect_max_depth() -> crate::Result { .replace("{}", &max_depth.to_string()), )?; - let config = File::from_paths_metadata(into_meta(vec![dir.path().join("0")]), follow_options())?; + let config = + File::from_paths_metadata(into_meta(vec![dir.path().join("0")]), follow_options())?.expect("non-empty"); assert_eq!(config.integers("core", None, "i"), Some(Ok(vec![0, 1, 2, 3, 4]))); fn make_options(max_depth: u8, error_on_max_depth_exceeded: bool) -> init::Options<'static> { @@ -133,7 +134,7 @@ fn respect_max_depth() -> crate::Result { // with max_allowed_depth of 1 and 4 levels of includes and error_on_max_depth_exceeded: false, max_allowed_depth is exceeded and the value of level 1 is returned // this is equivalent to running git with --no-includes option let options = make_options(1, false); - let config = File::from_paths_metadata(into_meta(vec![dir.path().join("0")]), options)?; + let config = File::from_paths_metadata(into_meta(vec![dir.path().join("0")]), options)?.expect("non-empty"); assert_eq!(config.integer("core", None, "i"), Some(Ok(1))); // with default max_allowed_depth of 10 and 4 levels of includes, last level is read @@ -141,12 +142,12 @@ fn respect_max_depth() -> crate::Result { includes: includes::Options::follow(Default::default(), Default::default()), ..Default::default() }; - let config = File::from_paths_metadata(into_meta(vec![dir.path().join("0")]), options)?; + let config = File::from_paths_metadata(into_meta(vec![dir.path().join("0")]), options)?.expect("non-empty"); assert_eq!(config.integer("core", None, "i"), Some(Ok(4))); // with max_allowed_depth of 5, the base and 4 levels of includes, last level is read let options = make_options(5, false); - let config = File::from_paths_metadata(into_meta(vec![dir.path().join("0")]), options)?; + let config = File::from_paths_metadata(into_meta(vec![dir.path().join("0")]), options)?.expect("non-empty"); assert_eq!(config.integer("core", None, "i"), Some(Ok(4))); // with max_allowed_depth of 2 and 4 levels of includes, max_allowed_depth is exceeded and error is returned @@ -161,7 +162,7 @@ fn respect_max_depth() -> crate::Result { // with max_allowed_depth of 2 and 4 levels of includes and error_on_max_depth_exceeded: false , max_allowed_depth is exceeded and the value of level 2 is returned let options = make_options(2, false); - let config = File::from_paths_metadata(into_meta(vec![dir.path().join("0")]), options)?; + let config = File::from_paths_metadata(into_meta(vec![dir.path().join("0")]), options)?.expect("non-empty"); assert_eq!(config.integer("core", None, "i"), Some(Ok(2))); // with max_allowed_depth of 0 and 4 levels of includes, max_allowed_depth is exceeded and error is returned @@ -177,8 +178,8 @@ fn respect_max_depth() -> crate::Result { } #[test] -fn simple() { - let dir = tempdir().unwrap(); +fn simple() -> crate::Result { + let dir = tempdir()?; let a_path = dir.path().join("a"); let b_path = dir.path().join("b"); @@ -198,19 +199,18 @@ fn simple() { escape_backslashes(&b_path), escape_backslashes(&b_path) ), - ) - .unwrap(); + )?; fs::write( b_path.as_path(), " [core] b = false", - ) - .unwrap(); + )?; - let config = File::from_paths_metadata(into_meta(vec![a_path]), follow_options()).unwrap(); + let config = File::from_paths_metadata(into_meta(vec![a_path]), follow_options())?.expect("non-empty"); assert_eq!(config.boolean("core", None, "b"), Some(Ok(false))); + Ok(()) } #[test] @@ -268,7 +268,7 @@ fn cycle_detection() -> crate::Result { }, ..Default::default() }; - let config = File::from_paths_metadata(into_meta(vec![a_path]), options)?; + let config = File::from_paths_metadata(into_meta(vec![a_path]), options)?.expect("non-empty"); assert_eq!(config.integers("core", None, "b"), Some(Ok(vec![0, 1, 0, 1, 0]))); Ok(()) } @@ -312,7 +312,7 @@ fn nested() -> crate::Result { ), )?; - let config = File::from_paths_metadata(into_meta(vec![c_path]), follow_options())?; + let config = File::from_paths_metadata(into_meta(vec![c_path]), follow_options())?.expect("non-empty"); assert_eq!(config.integer("core", None, "c"), Some(Ok(1))); assert_eq!(config.boolean("core", None, "b"), Some(Ok(true))); diff --git a/git-config/tests/file/init/from_paths/mod.rs b/git-config/tests/file/init/from_paths/mod.rs index e2583ae0c1e..7a44bdd6e07 100644 --- a/git-config/tests/file/init/from_paths/mod.rs +++ b/git-config/tests/file/init/from_paths/mod.rs @@ -53,7 +53,7 @@ fn multiple_paths_single_value() -> crate::Result { fs::write(d_path.as_path(), b"[core]\na = false")?; let paths = vec![a_path, b_path, c_path, d_path]; - let config = File::from_paths_metadata(into_meta(paths), Default::default())?; + let config = File::from_paths_metadata(into_meta(paths), Default::default())?.expect("non-empty"); assert_eq!(config.boolean("core", None, "a"), Some(Ok(false))); assert_eq!(config.boolean("core", None, "b"), Some(Ok(true))); @@ -81,7 +81,7 @@ fn frontmatter_is_maintained_in_multiple_files() -> crate::Result { fs::write(d_path.as_path(), b"\n; nothing in d")?; let paths = vec![a_path, b_path, c_path, d_path]; - let mut config = File::from_paths_metadata(into_meta(paths), Default::default())?; + let mut config = File::from_paths_metadata(into_meta(paths), Default::default())?.expect("non-empty"); assert_eq!( config.to_string(), @@ -145,7 +145,8 @@ fn multiple_paths_multi_value_and_filter() -> crate::Result { .iter() .map(|(p, s)| git_config::file::Metadata::try_from_path(p, *s).unwrap()), Default::default(), - )?; + )? + .expect("non-empty"); assert_eq!( config.strings("core", None, "key"), From 5221676e28f2b6cc1a7ef1bdd5654b880965f38c Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 19 Jul 2022 20:46:16 +0800 Subject: [PATCH 274/366] change!: add `File::from_bytes_owned()` and remove `File::from_path_with_buf()` (#331) --- git-config/src/file/init/comfort.rs | 2 +- git-config/src/file/init/from_env.rs | 4 +- git-config/src/file/init/from_paths.rs | 44 +++++++------------ git-config/src/file/init/includes.rs | 17 ++++--- git-config/src/file/init/mod.rs | 26 +++++++++-- git-config/src/file/meta.rs | 6 +++ git-config/tests/file/init/from_env.rs | 14 +++--- .../includes/conditional/gitdir/mod.rs | 6 +-- git-config/tests/file/mod.rs | 13 +----- 9 files changed, 68 insertions(+), 64 deletions(-) diff --git a/git-config/src/file/init/comfort.rs b/git-config/src/file/init/comfort.rs index 1200d9a0498..9454d74d6a0 100644 --- a/git-config/src/file/init/comfort.rs +++ b/git-config/src/file/init/comfort.rs @@ -62,7 +62,7 @@ impl File<'static> { ..Default::default() }; - File::from_environment(options).map(Option::unwrap_or_default) + File::from_env(options).map(Option::unwrap_or_default) } } diff --git a/git-config/src/file/init/from_env.rs b/git-config/src/file/init/from_env.rs index 387dee8afcb..0f4ffeed6eb 100644 --- a/git-config/src/file/init/from_env.rs +++ b/git-config/src/file/init/from_env.rs @@ -5,7 +5,7 @@ use std::convert::TryFrom; use crate::file::init; use crate::{file, parse, parse::section, path::interpolate, File}; -/// Represents the errors that may occur when calling [`File::from_environment()`]. +/// Represents the errors that may occur when calling [`File::from_env()`]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { @@ -35,7 +35,7 @@ impl File<'static> { /// With `options` configured, it's possible `include.path` directives as well. /// /// [`git-config`'s documentation]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-GITCONFIGCOUNT - pub fn from_environment(options: init::Options<'_>) -> Result>, Error> { + pub fn from_env(options: init::Options<'_>) -> Result>, Error> { use std::env; let count: usize = match env::var("GIT_CONFIG_COUNT") { Ok(v) => v.parse().map_err(|_| Error::InvalidConfigCount { input: v })?, diff --git a/git-config/src/file/init/from_paths.rs b/git-config/src/file/init/from_paths.rs index ac426b02fc6..ef2a7f691a4 100644 --- a/git-config/src/file/init/from_paths.rs +++ b/git-config/src/file/init/from_paths.rs @@ -1,10 +1,9 @@ use crate::file::init::Options; use crate::file::{init, Metadata}; -use crate::{file, file::init::includes, parse, File}; -use git_features::threading::OwnShared; +use crate::File; use std::collections::BTreeSet; -/// The error returned by [`File::from_paths_metadata()`] and [`File::from_path_with_buf()`]. +/// The error returned by [`File::from_paths_metadata()`] and [`File::from_path_no_includes()`]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { @@ -22,33 +21,15 @@ impl File<'static> { pub fn from_path_no_includes(path: impl Into, source: crate::Source) -> Result { let path = path.into(); let trust = git_sec::Trust::from_path_ownership(&path)?; - let mut buf = Vec::new(); - File::from_path_with_buf(path, &mut buf, Metadata::from(source).with(trust), Default::default()) - } - /// Open a single configuration file by reading all data at `path` into `buf` and - /// copying all contents from there, without resolving includes. Note that the `path` in `meta` - /// will be set to the one provided here. - pub fn from_path_with_buf( - path: impl Into, - buf: &mut Vec, - mut meta: file::Metadata, - options: Options<'_>, - ) -> Result { - let path = path.into(); - buf.clear(); - std::io::copy(&mut std::fs::File::open(&path)?, buf)?; - - meta.path = path.into(); - let meta = OwnShared::new(meta); - let mut config = Self::from_parse_events_no_includes( - parse::Events::from_bytes_owned(buf, options.to_event_filter()).map_err(init::Error::from)?, - OwnShared::clone(&meta), - ); let mut buf = Vec::new(); - includes::resolve(&mut config, meta, &mut buf, options).map_err(init::Error::from)?; + std::io::copy(&mut std::fs::File::open(&path)?, &mut buf)?; - Ok(config) + Ok(File::from_bytes_owned( + &mut buf, + Metadata::from(source).at(path).with(trust), + Default::default(), + )?) } /// Constructs a `git-config` file from the provided metadata, which must include a path to read from or be ignored. @@ -61,14 +42,19 @@ impl File<'static> { let mut target = None; let mut buf = Vec::with_capacity(512); let mut seen = BTreeSet::default(); - for (path, meta) in path_meta.into_iter().filter_map(|meta| { + for (path, mut meta) in path_meta.into_iter().filter_map(|meta| { let mut meta = meta.into(); meta.path.take().map(|p| (p, meta)) }) { if !seen.insert(path.clone()) { continue; } - let config = Self::from_path_with_buf(path, &mut buf, meta, options)?; + + buf.clear(); + std::io::copy(&mut std::fs::File::open(&path)?, &mut buf)?; + meta.path = Some(path); + + let config = Self::from_bytes_owned(&mut buf, meta, options)?; match &mut target { None => { target = Some(config); diff --git a/git-config/src/file/init/includes.rs b/git-config/src/file/init/includes.rs index 20cc81c828c..0cc74e0b1ba 100644 --- a/git-config/src/file/init/includes.rs +++ b/git-config/src/file/init/includes.rs @@ -8,7 +8,7 @@ use git_features::threading::OwnShared; use git_ref::Category; use crate::file::{init, Metadata}; -use crate::{file, file::init::from_paths, File}; +use crate::{file, File}; pub(crate) fn resolve( config: &mut File<'static>, @@ -79,22 +79,25 @@ fn append_followed_includes_recursively( continue; } + buf.clear(); + std::io::copy(&mut std::fs::File::open(&config_path)?, buf)?; + let config_meta = Metadata { - path: None, + path: Some(config_path), trust: meta.trust, level: meta.level + 1, source: meta.source, }; - let no_follow_options = init::Options { lossy: options.lossy, ..Default::default() }; + let mut include_config = - File::from_path_with_buf(config_path, buf, config_meta, no_follow_options).map_err(|err| match err { - from_paths::Error::Io(err) => Error::Io(err), - from_paths::Error::Init(init::Error::Parse(err)) => Error::Parse(err), - err => unreachable!("BUG: {:?} shouldn't be possible here", err), + File::from_bytes_owned(buf, config_meta, no_follow_options).map_err(|err| match err { + init::Error::Parse(err) => Error::Parse(err), + init::Error::Interpolate(err) => Error::Interpolate(err), + init::Error::Includes(_) => unreachable!("BUG: {:?} not possible due to no-follow options", err), })?; let config_meta = include_config.meta_owned(); diff --git a/git-config/src/file/init/mod.rs b/git-config/src/file/init/mod.rs index a76203753bf..802bd15732a 100644 --- a/git-config/src/file/init/mod.rs +++ b/git-config/src/file/init/mod.rs @@ -1,4 +1,4 @@ -use crate::file::{init, section, Metadata}; +use crate::file::{section, Metadata}; use crate::{parse, File}; use git_features::threading::OwnShared; @@ -32,8 +32,8 @@ impl<'a> File<'a> { pub fn from_bytes_no_includes( input: &'a [u8], meta: impl Into>, - options: init::Options<'_>, - ) -> Result { + options: Options<'_>, + ) -> Result { let meta = meta.into(); Ok(Self::from_parse_events_no_includes( parse::Events::from_bytes(input, options.to_event_filter())?, @@ -63,3 +63,23 @@ impl<'a> File<'a> { this } } + +impl File<'static> { + /// Instantiate a new fully-owned `File` from given `input` (later reused as buffer when resolving includes), + /// associating each section and their values with `meta`-data, while respecting `options`, and + /// following includes as configured there. + pub fn from_bytes_owned( + input_and_buf: &mut Vec, + meta: impl Into>, + options: Options<'_>, + ) -> Result { + let meta = meta.into(); + let mut config = Self::from_parse_events_no_includes( + parse::Events::from_bytes_owned(input_and_buf, options.to_event_filter()).map_err(Error::from)?, + OwnShared::clone(&meta), + ); + + includes::resolve(&mut config, meta, input_and_buf, options).map_err(Error::from)?; + Ok(config) + } +} diff --git a/git-config/src/file/meta.rs b/git-config/src/file/meta.rs index 8b698ced6ad..9f8c02ffbcb 100644 --- a/git-config/src/file/meta.rs +++ b/git-config/src/file/meta.rs @@ -34,6 +34,12 @@ impl Metadata { self.trust = trust; self } + + /// Set the metadata to be located at the given `path`. + pub fn at(mut self, path: impl Into) -> Self { + self.path = Some(path.into()); + self + } } impl Default for Metadata { diff --git a/git-config/tests/file/init/from_env.rs b/git-config/tests/file/init/from_env.rs index 961d6bddfd9..4d600be0c15 100644 --- a/git-config/tests/file/init/from_env.rs +++ b/git-config/tests/file/init/from_env.rs @@ -37,7 +37,7 @@ impl<'a> Drop for Env<'a> { #[test] #[serial] fn empty_without_relevant_environment() { - let config = File::from_environment(Default::default()).unwrap(); + let config = File::from_env(Default::default()).unwrap(); assert!(config.is_none()); } @@ -45,7 +45,7 @@ fn empty_without_relevant_environment() { #[serial] fn empty_with_zero_count() { let _env = Env::new().set("GIT_CONFIG_COUNT", "0"); - let config = File::from_environment(Default::default()).unwrap(); + let config = File::from_env(Default::default()).unwrap(); assert!(config.is_none()); } @@ -53,7 +53,7 @@ fn empty_with_zero_count() { #[serial] fn parse_error_with_invalid_count() { let _env = Env::new().set("GIT_CONFIG_COUNT", "invalid"); - let err = File::from_environment(Default::default()).unwrap_err(); + let err = File::from_env(Default::default()).unwrap_err(); assert!(matches!(err, from_env::Error::InvalidConfigCount { .. })); } @@ -65,7 +65,7 @@ fn single_key_value_pair() { .set("GIT_CONFIG_KEY_0", "core.key") .set("GIT_CONFIG_VALUE_0", "value"); - let config = File::from_environment(Default::default()).unwrap().unwrap(); + let config = File::from_env(Default::default()).unwrap().unwrap(); assert_eq!( config.raw_value("core", None, "key").unwrap(), Cow::<[u8]>::Borrowed(b"value") @@ -86,7 +86,7 @@ fn multiple_key_value_pairs() { .set("GIT_CONFIG_KEY_2", "core.c") .set("GIT_CONFIG_VALUE_2", "c"); - let config = File::from_environment(Default::default()).unwrap().unwrap(); + let config = File::from_env(Default::default()).unwrap().unwrap(); assert_eq!( config.raw_value("core", None, "a").unwrap(), @@ -111,7 +111,7 @@ fn error_on_relative_paths_in_include_paths() { .set("GIT_CONFIG_KEY_0", "include.path") .set("GIT_CONFIG_VALUE_0", "some_git_config"); - let res = File::from_environment(init::Options { + let res = File::from_env(init::Options { includes: includes::Options { max_depth: 1, ..Default::default() @@ -144,7 +144,7 @@ fn follow_include_paths() { .set("GIT_CONFIG_KEY_3", "include.origin.path") .set("GIT_CONFIG_VALUE_3", escape_backslashes(b_path)); - let config = File::from_environment(init::Options { + let config = File::from_env(init::Options { includes: includes::Options { max_depth: 1, ..Default::default() diff --git a/git-config/tests/file/init/from_paths/includes/conditional/gitdir/mod.rs b/git-config/tests/file/init/from_paths/includes/conditional/gitdir/mod.rs index 9c76422974d..797674b4007 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/gitdir/mod.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/gitdir/mod.rs @@ -98,7 +98,7 @@ fn dot_slash_from_environment_causes_error() -> crate::Result { ) .set("GIT_CONFIG_VALUE_0", "./include.path"); - let res = git_config::File::from_environment(env.to_init_options()); + let res = git_config::File::from_env(env.to_init_options()); assert!( matches!( res, @@ -117,7 +117,7 @@ fn dot_slash_from_environment_causes_error() -> crate::Result { .set("GIT_CONFIG_KEY_0", "includeIf.gitdir:./worktree/.path") .set("GIT_CONFIG_VALUE_0", &absolute_path); - let res = git_config::File::from_environment(env.to_init_options()); + let res = git_config::File::from_env(env.to_init_options()); assert!( matches!( res, @@ -138,7 +138,7 @@ fn dot_slash_from_environment_causes_error() -> crate::Result { ) .set("GIT_CONFIG_VALUE_0", absolute_path); - let res = git_config::File::from_environment(env.to_init_options()); + let res = git_config::File::from_env(env.to_init_options()); assert!(res.is_ok(), "missing paths are ignored as before"); } diff --git a/git-config/tests/file/mod.rs b/git-config/tests/file/mod.rs index 684b6d9743e..1a7df50c946 100644 --- a/git-config/tests/file/mod.rs +++ b/git-config/tests/file/mod.rs @@ -16,23 +16,12 @@ fn size_in_memory() { } mod open { - use git_config::file::init; use git_config::File; use git_testtools::fixture_path; #[test] fn parse_config_with_windows_line_endings_successfully() { - let mut buf = Vec::new(); - File::from_path_with_buf( - &fixture_path("repo-config.crlf"), - &mut buf, - Default::default(), - init::Options { - lossy: true, - ..Default::default() - }, - ) - .unwrap(); + File::from_path_no_includes(&fixture_path("repo-config.crlf"), git_config::Source::Local).unwrap(); } } From 7f41f1e267c9cbf87061821dd2f0edb6b0984226 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 19 Jul 2022 20:47:17 +0800 Subject: [PATCH 275/366] adapt to changes in `git-config` (#331) --- git-repository/src/config/cache.rs | 24 +++++++++++++++++++++--- git-repository/src/config/mod.rs | 8 +++++--- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/git-repository/src/config/cache.rs b/git-repository/src/config/cache.rs index ee53c9a47be..dcf927f33d0 100644 --- a/git-repository/src/config/cache.rs +++ b/git-repository/src/config/cache.rs @@ -5,6 +5,20 @@ use git_config::{Boolean, Integer}; use super::{Cache, Error}; use crate::{bstr::ByteSlice, permission}; +/// A utility to deal with the cyclic dependency between the ref store and the configuration. The ref-store needs the +/// object hash kind, and the configuration needs the current branch name to resolve conditional includes with `onbranch`. +#[allow(dead_code)] +pub struct StageOne { + git_dir_config: git_config::File<'static>, +} + +#[allow(dead_code)] +impl StageOne { + pub fn new(_git_dir_trust: git_sec::Trust, _git_dir: &std::path::Path) -> Result { + todo!() + } +} + impl Cache { pub fn new( branch_name: Option<&git_ref::FullNameRef>, @@ -22,10 +36,14 @@ impl Cache { // like git here: https://github.com/git/git/blob/master/config.c#L208:L208 let config = { let mut buf = Vec::with_capacity(512); - git_config::File::from_path_with_buf( - &git_dir.join("config"), + let config_path = git_dir.join("config"); + std::io::copy(&mut std::fs::File::open(&config_path)?, &mut buf)?; + + git_config::File::from_bytes_owned( &mut buf, - git_config::file::Metadata::from(git_config::Source::Local).with(git_dir_trust), + git_config::file::Metadata::from(git_config::Source::Local) + .at(config_path) + .with(git_dir_trust), git_config::file::init::Options { lossy: !cfg!(debug_assertions), includes: git_config::file::init::includes::Options::follow( diff --git a/git-repository/src/config/mod.rs b/git-repository/src/config/mod.rs index 7e498a974c7..a06a9b4c529 100644 --- a/git-repository/src/config/mod.rs +++ b/git-repository/src/config/mod.rs @@ -1,6 +1,6 @@ use crate::{bstr::BString, permission, Repository}; -mod cache; +pub(crate) mod cache; mod snapshot; /// A platform to access configuration values as read from disk. @@ -18,8 +18,10 @@ pub(crate) mod section { #[derive(Debug, thiserror::Error)] pub enum Error { - #[error("Could not open repository conifguration file")] - Open(#[from] git_config::file::init::from_paths::Error), + #[error("Could not read configuration file")] + Io(#[from] std::io::Error), + #[error(transparent)] + Config(#[from] git_config::file::init::Error), #[error("Cannot handle objects formatted as {:?}", .name)] UnsupportedObjectFormat { name: BString }, #[error("The value for '{}' cannot be empty", .key)] From 1679d5684cec852b39a0d51d5001fbcecafc6748 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 19 Jul 2022 22:13:21 +0800 Subject: [PATCH 276/366] solve cycle between config and ref-store (#331) --- crate-status.md | 1 + etc/check-package-size.sh | 2 +- git-repository/src/config/cache.rs | 99 ++++++++++++++++++------------ git-repository/src/open.rs | 22 ++++--- 4 files changed, 76 insertions(+), 48 deletions(-) diff --git a/crate-status.md b/crate-status.md index 2fc0df7b4c9..d6ce25058a1 100644 --- a/crate-status.md +++ b/crate-status.md @@ -397,6 +397,7 @@ See its [README.md](https://github.com/Byron/gitoxide/blob/main/git-lock/README. * [ ] ANSI code output for terminal colors * [x] path (incl. resolution) * [ ] date + * [ ] [permission][https://github.com/git/git/blob/71a8fab31b70c417e8f5b5f716581f89955a7082/setup.c#L1526:L1526] * [x] include * **includeIf** * [x] `gitdir`, `gitdir/i`, and `onbranch` diff --git a/etc/check-package-size.sh b/etc/check-package-size.sh index 77eab01e7a3..450ad7c2415 100755 --- a/etc/check-package-size.sh +++ b/etc/check-package-size.sh @@ -52,6 +52,6 @@ echo "in root: gitoxide CLI" (enter git-odb && indent cargo diet -n --package-size-limit 120KB) (enter git-protocol && indent cargo diet -n --package-size-limit 50KB) (enter git-packetline && indent cargo diet -n --package-size-limit 35KB) -(enter git-repository && indent cargo diet -n --package-size-limit 105KB) +(enter git-repository && indent cargo diet -n --package-size-limit 110KB) (enter git-transport && indent cargo diet -n --package-size-limit 50KB) (enter gitoxide-core && indent cargo diet -n --package-size-limit 70KB) diff --git a/git-repository/src/config/cache.rs b/git-repository/src/config/cache.rs index dcf927f33d0..f9191b321fa 100644 --- a/git-repository/src/config/cache.rs +++ b/git-repository/src/config/cache.rs @@ -8,34 +8,20 @@ use crate::{bstr::ByteSlice, permission}; /// A utility to deal with the cyclic dependency between the ref store and the configuration. The ref-store needs the /// object hash kind, and the configuration needs the current branch name to resolve conditional includes with `onbranch`. #[allow(dead_code)] -pub struct StageOne { +pub(crate) struct StageOne { git_dir_config: git_config::File<'static>, -} + buf: Vec, -#[allow(dead_code)] -impl StageOne { - pub fn new(_git_dir_trust: git_sec::Trust, _git_dir: &std::path::Path) -> Result { - todo!() - } + is_bare: bool, + pub object_hash: git_hash::Kind, + use_multi_pack_index: bool, + pub reflog: Option, } -impl Cache { - pub fn new( - branch_name: Option<&git_ref::FullNameRef>, - mut filter_config_section: fn(&git_config::file::Metadata) -> bool, - git_dir_trust: git_sec::Trust, - git_dir: &std::path::Path, - xdg_config_home_env: permission::env_var::Resource, - home_env: permission::env_var::Resource, - git_install_dir: Option<&std::path::Path>, - ) -> Result { - let home = std::env::var_os("HOME") - .map(PathBuf::from) - .and_then(|home| home_env.check(home).ok().flatten()); - // TODO: don't forget to use the canonicalized home for initializing the stacked config. - // like git here: https://github.com/git/git/blob/master/config.c#L208:L208 +impl StageOne { + pub fn new(git_dir: &std::path::Path, git_dir_trust: git_sec::Trust) -> Result { + let mut buf = Vec::with_capacity(512); let config = { - let mut buf = Vec::with_capacity(512); let config_path = git_dir.join("config"); std::io::copy(&mut std::fs::File::open(&config_path)?, &mut buf)?; @@ -46,27 +32,13 @@ impl Cache { .with(git_dir_trust), git_config::file::init::Options { lossy: !cfg!(debug_assertions), - includes: git_config::file::init::includes::Options::follow( - interpolate_context(git_install_dir, home.as_deref()), - git_config::file::init::includes::conditional::Context { - git_dir: git_dir.into(), - branch_name, - }, - ), + includes: git_config::file::init::includes::Options::no_includes(), }, )? }; let is_bare = config_bool(&config, "core.bare", false)?; let use_multi_pack_index = config_bool(&config, "core.multiPackIndex", true)?; - let ignore_case = config_bool(&config, "core.ignoreCase", false)?; - let excludes_file = config - .path_filter("core", None, "excludesFile", &mut filter_config_section) - .map(|p| { - p.interpolate(interpolate_context(git_install_dir, home.as_deref())) - .map(|p| p.into_owned()) - }) - .transpose()?; let repo_format_version = config .value::("core", None, "repositoryFormatVersion") .map_or(0, |v| v.to_decimal().unwrap_or_default()); @@ -96,6 +68,57 @@ impl Cache { .unwrap_or(git_ref::store::WriteReflog::Disable) }); + Ok(StageOne { + git_dir_config: config, + buf, + is_bare, + object_hash, + use_multi_pack_index, + reflog, + }) + } +} + +impl Cache { + pub fn from_stage_one( + StageOne { + git_dir_config: config, + buf: _, + is_bare, + object_hash, + use_multi_pack_index, + reflog, + }: StageOne, + git_dir: &std::path::Path, + branch_name: Option<&git_ref::FullNameRef>, + mut filter_config_section: fn(&git_config::file::Metadata) -> bool, + xdg_config_home_env: permission::env_var::Resource, + home_env: permission::env_var::Resource, + git_install_dir: Option<&std::path::Path>, + ) -> Result { + let home = std::env::var_os("HOME") + .map(PathBuf::from) + .and_then(|home| home_env.check(home).ok().flatten()); + // TODO: don't forget to use the canonicalized home for initializing the stacked config. + // like git here: https://github.com/git/git/blob/master/config.c#L208:L208 + // TODO: resolve includes and load other kinds of configuration + let options = git_config::file::init::Options { + lossy: !cfg!(debug_assertions), + includes: git_config::file::init::includes::Options::follow( + interpolate_context(git_install_dir, home.as_deref()), + git_config::file::init::includes::conditional::Context { + git_dir: git_dir.into(), + branch_name, + }, + ), + }; + + let excludes_file = config + .path_filter("core", None, "excludesFile", &mut filter_config_section) + .map(|p| p.interpolate(options.includes.interpolate).map(|p| p.into_owned())) + .transpose()?; + let ignore_case = config_bool(&config, "core.ignoreCase", false)?; + let mut hex_len = None; if let Some(hex_len_str) = config.string("core", None, "abbrev") { if hex_len_str.trim().is_empty() { diff --git a/git-repository/src/open.rs b/git-repository/src/open.rs index a02982e0513..00996c59fee 100644 --- a/git-repository/src/open.rs +++ b/git-repository/src/open.rs @@ -261,10 +261,6 @@ impl ThreadSafeRepository { } = options; let git_dir_trust = git_dir_trust.expect("trust must be been determined by now"); - if **git_dir_perm != git_sec::ReadWrite::all() { - // TODO: respect `save.directory`, which needs more support from git-config to do properly. - return Err(Error::UnsafeGitDir { path: git_dir }); - } // TODO: assure we handle the worktree-dir properly as we can have config per worktree with an extension. // This would be something read in later as have to first check for extensions. Also this means // that each worktree, even if accessible through this instance, has to come in its own Repository instance @@ -273,24 +269,32 @@ impl ThreadSafeRepository { .transpose()? .map(|cd| git_dir.join(cd)); let common_dir_ref = common_dir.as_deref().unwrap_or(&git_dir); + + let early_cache = crate::config::cache::StageOne::new(common_dir_ref, git_dir_trust)?; let mut refs = { - let reflog = git_ref::store::WriteReflog::Disable; - let object_hash = git_hash::Kind::Sha1; // TODO: load repo-local config first, no includes resolution, then merge with global. + let reflog = early_cache.reflog.unwrap_or(git_ref::store::WriteReflog::Disable); + let object_hash = early_cache.object_hash; match &common_dir { Some(common_dir) => crate::RefStore::for_linked_worktree(&git_dir, common_dir, reflog, object_hash), None => crate::RefStore::at(&git_dir, reflog, object_hash), } }; let head = refs.find("HEAD").ok(); - let config = crate::config::Cache::new( + let config = crate::config::Cache::from_stage_one( + early_cache, + common_dir_ref, head.as_ref().and_then(|head| head.target.try_name()), filter_config_section.unwrap_or(crate::config::section::is_trusted), - git_dir_trust, - common_dir_ref, env.xdg_config_home.clone(), env.home.clone(), crate::path::install_dir().ok().as_deref(), )?; + + if **git_dir_perm != git_sec::ReadWrite::all() { + // TODO: respect `save.directory`, which needs global configuration to later combine. Probably have to do the check later. + return Err(Error::UnsafeGitDir { path: git_dir }); + } + match worktree_dir { None if !config.is_bare => { worktree_dir = Some(git_dir.parent().expect("parent is always available").to_owned()); From 17c83d55f8942788aac5eb1bea22a48daa045bf4 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 20 Jul 2022 08:21:28 +0800 Subject: [PATCH 277/366] change!: add `File::resolve_includes()` and move its error type to `file::includes`. (#331) --- git-config/src/file/{init => }/includes.rs | 16 +++++ git-config/src/file/init/mod.rs | 4 +- git-config/src/file/mod.rs | 2 + git-config/tests/file/init/from_env.rs | 2 +- .../includes/conditional/gitdir/mod.rs | 4 +- .../from_paths/includes/conditional/mod.rs | 71 ++++++++++--------- .../includes/conditional/onbranch.rs | 5 +- .../init/from_paths/includes/unconditional.rs | 2 +- 8 files changed, 63 insertions(+), 43 deletions(-) rename git-config/src/file/{init => }/includes.rs (93%) diff --git a/git-config/src/file/init/includes.rs b/git-config/src/file/includes.rs similarity index 93% rename from git-config/src/file/init/includes.rs rename to git-config/src/file/includes.rs index 0cc74e0b1ba..bd48f3c358f 100644 --- a/git-config/src/file/init/includes.rs +++ b/git-config/src/file/includes.rs @@ -10,6 +10,22 @@ use git_ref::Category; use crate::file::{init, Metadata}; use crate::{file, File}; +impl File<'static> { + /// Traverse all `include` and `includeIf` directives found in this instance and follow them, loading the + /// referenced files from their location and adding their content right past the value that included them. + /// + /// # Limitations + /// + /// - Note that this method is _not idempotent_ and calling it multiple times will resolve includes multiple + /// times. It's recommended use is as part of a multi-step bootstrapping which needs fine-grained control, + /// and unless that's given one should prefer one of the other ways of initialization that resolve includes + /// at the right time. + pub fn resolve_includes(&mut self, options: init::Options<'_>) -> Result<(), Error> { + let mut buf = Vec::new(); + resolve(self, OwnShared::clone(&self.meta), &mut buf, options) + } +} + pub(crate) fn resolve( config: &mut File<'static>, meta: OwnShared, diff --git a/git-config/src/file/init/mod.rs b/git-config/src/file/init/mod.rs index 802bd15732a..979445b16af 100644 --- a/git-config/src/file/init/mod.rs +++ b/git-config/src/file/init/mod.rs @@ -1,4 +1,4 @@ -use crate::file::{section, Metadata}; +use crate::file::{includes, section, Metadata}; use crate::{parse, File}; use git_features::threading::OwnShared; @@ -10,8 +10,6 @@ mod comfort; pub mod from_env; /// pub mod from_paths; -/// -pub mod includes; impl<'a> File<'a> { /// Return an empty `File` with the given `meta`-data to be attached to all new sections. diff --git a/git-config/src/file/mod.rs b/git-config/src/file/mod.rs index 4645276b653..8c0d72a1765 100644 --- a/git-config/src/file/mod.rs +++ b/git-config/src/file/mod.rs @@ -17,6 +17,8 @@ pub mod init; mod access; mod impls; +/// +pub mod includes; mod meta; mod utils; diff --git a/git-config/tests/file/init/from_env.rs b/git-config/tests/file/init/from_env.rs index 4d600be0c15..a8476ad4796 100644 --- a/git-config/tests/file/init/from_env.rs +++ b/git-config/tests/file/init/from_env.rs @@ -1,7 +1,7 @@ use std::{borrow::Cow, env, fs}; +use git_config::file::includes; use git_config::file::init; -use git_config::file::init::includes; use git_config::{file::init::from_env, File}; use serial_test::serial; use tempfile::tempdir; diff --git a/git-config/tests/file/init/from_paths/includes/conditional/gitdir/mod.rs b/git-config/tests/file/init/from_paths/includes/conditional/gitdir/mod.rs index 797674b4007..43ed204ca51 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/gitdir/mod.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/gitdir/mod.rs @@ -103,7 +103,7 @@ fn dot_slash_from_environment_causes_error() -> crate::Result { matches!( res, Err(git_config::file::init::from_env::Error::Includes( - git_config::file::init::includes::Error::MissingConfigPath + git_config::file::includes::Error::MissingConfigPath )) ), "this is a failure of resolving the include path, after trying to include it" @@ -122,7 +122,7 @@ fn dot_slash_from_environment_causes_error() -> crate::Result { matches!( res, Err(git_config::file::init::from_env::Error::Includes( - git_config::file::init::includes::Error::MissingConfigPath + git_config::file::includes::Error::MissingConfigPath )) ), "here the pattern path tries to be resolved and fails as target config isn't set" diff --git a/git-config/tests/file/init/from_paths/includes/conditional/mod.rs b/git-config/tests/file/init/from_paths/includes/conditional/mod.rs index d99b39ddc4b..18b44bc09eb 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/mod.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/mod.rs @@ -1,7 +1,7 @@ use std::{fs, path::Path}; +use git_config::file::includes; use git_config::file::init; -use git_config::file::init::includes; use git_config::{path, File}; use tempfile::tempdir; @@ -13,7 +13,7 @@ mod onbranch; #[test] fn include_and_includeif_correct_inclusion_order() -> crate::Result { let dir = tempdir()?; - let config_path = dir.path().join("p"); + let config_path = dir.path().join("root"); let first_include_path = dir.path().join("first-incl"); let second_include_path = dir.path().join("second-incl"); let include_if_path = dir.path().join("incl-if"); @@ -38,47 +38,50 @@ fn include_and_includeif_correct_inclusion_order() -> crate::Result { b = incl-if-path", )?; - fs::write( - config_path.as_path(), - format!( - r#" + let root_config = format!( + r#" [core] [include] path = {} -[includeIf "gitdir:p/"] +[includeIf "gitdir:root/"] path = {} [include] path = {}"#, - escape_backslashes(&first_include_path), - escape_backslashes(&include_if_path), - escape_backslashes(&second_include_path), - ), - )?; + escape_backslashes(&first_include_path), + escape_backslashes(&include_if_path), + escape_backslashes(&second_include_path), + ); + fs::write(config_path.as_path(), &root_config)?; let dir = config_path.join(".git"); - let config = File::from_paths_metadata( - Some(git_config::file::Metadata::try_from_path( - &config_path, - git_config::Source::Api, - )?), - options_with_git_dir(&dir), - )? - .expect("non-empty"); + for delayed_resolve in [false, true] { + let meta = git_config::file::Metadata::try_from_path(&config_path, git_config::Source::Api)?; + let options = options_with_git_dir(&dir); + let config = if delayed_resolve { + let mut config = File::from_bytes_owned(&mut root_config.as_bytes().into(), meta, Default::default())?; + config.resolve_includes(options)?; + config + } else { + File::from_paths_metadata(Some(meta), options)?.expect("non-empty") + }; - assert_eq!( - config.strings("core", None, "b"), - Some(vec![ - cow_str("first-incl-path"), - cow_str("incl-if-path"), - cow_str("second-incl-path") - ]), - "first include is matched correctly", - ); - assert_eq!( - config.string("core", None, "b"), - Some(cow_str("second-incl-path")), - "second include is matched after incl-if", - ); + // TODO: test interaction with values from root as well - maybe against git as baseline. + assert_eq!( + config.strings("core", None, "b"), + Some(vec![ + cow_str("first-incl-path"), + cow_str("incl-if-path"), + cow_str("second-incl-path") + ]), + "first include is matched correctly, delayed_resolve = {}", + delayed_resolve, + ); + assert_eq!( + config.string("core", None, "b"), + Some(cow_str("second-incl-path")), + "second include is matched after incl-if", + ); + } Ok(()) } diff --git a/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs b/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs index 588f477d9a7..4d001f4d281 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs @@ -4,8 +4,9 @@ use std::{ }; use bstr::{BString, ByteSlice}; -use git_config::file::init::includes::conditional; -use git_config::file::init::{self, includes}; +use git_config::file::includes; +use git_config::file::includes::conditional; +use git_config::file::init::{self}; use git_ref::{ transaction::{Change, PreviousValue, RefEdit}, FullName, Target, diff --git a/git-config/tests/file/init/from_paths/includes/unconditional.rs b/git-config/tests/file/init/from_paths/includes/unconditional.rs index 6ec70cd351f..c5e525bb3c2 100644 --- a/git-config/tests/file/init/from_paths/includes/unconditional.rs +++ b/git-config/tests/file/init/from_paths/includes/unconditional.rs @@ -1,7 +1,7 @@ use std::fs; +use git_config::file::includes; use git_config::file::init; -use git_config::file::init::includes; use git_config::{file::init::from_paths, File}; use tempfile::tempdir; From 30cbe299860d84b5aeffced54839529dc068a8c7 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 20 Jul 2022 08:22:08 +0800 Subject: [PATCH 278/366] Adjust to changes in `git-config` (#331) --- git-repository/src/config/cache.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/git-repository/src/config/cache.rs b/git-repository/src/config/cache.rs index f9191b321fa..9ba8e846745 100644 --- a/git-repository/src/config/cache.rs +++ b/git-repository/src/config/cache.rs @@ -32,7 +32,7 @@ impl StageOne { .with(git_dir_trust), git_config::file::init::Options { lossy: !cfg!(debug_assertions), - includes: git_config::file::init::includes::Options::no_includes(), + includes: git_config::file::includes::Options::no_includes(), }, )? }; @@ -104,9 +104,9 @@ impl Cache { // TODO: resolve includes and load other kinds of configuration let options = git_config::file::init::Options { lossy: !cfg!(debug_assertions), - includes: git_config::file::init::includes::Options::follow( + includes: git_config::file::includes::Options::follow( interpolate_context(git_install_dir, home.as_deref()), - git_config::file::init::includes::conditional::Context { + git_config::file::includes::conditional::Context { git_dir: git_dir.into(), branch_name, }, From 4e47df5332810f6e46ab682a68e870220ba3a6fb Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 20 Jul 2022 08:28:39 +0800 Subject: [PATCH 279/366] a test showing that include ordering isn't correct compared to the including config. (#331) Git resolves includes inline, but we append configuration files. It's probably sufficient to insert them at a certain section instead. --- .../from_paths/includes/conditional/mod.rs | 42 +++++++++++-------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/git-config/tests/file/init/from_paths/includes/conditional/mod.rs b/git-config/tests/file/init/from_paths/includes/conditional/mod.rs index 18b44bc09eb..e72836fbbfa 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/mod.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/mod.rs @@ -11,6 +11,7 @@ mod gitdir; mod onbranch; #[test] +#[ignore] fn include_and_includeif_correct_inclusion_order() -> crate::Result { let dir = tempdir()?; let config_path = dir.path().join("root"); @@ -20,33 +21,40 @@ fn include_and_includeif_correct_inclusion_order() -> crate::Result { fs::write( first_include_path.as_path(), " -[core] - b = first-incl-path", +[section] + value = first-incl-path", )?; fs::write( second_include_path.as_path(), " -[core] - b = second-incl-path", +[section] + value = second-incl-path", )?; fs::write( include_if_path.as_path(), " -[core] - b = incl-if-path", +[section] + value = incl-if-path", )?; let root_config = format!( r#" -[core] +[section] + value = base [include] path = {} +[section] + value = base-past-first-include [includeIf "gitdir:root/"] path = {} +[section] + value = base-past-includeIf [include] - path = {}"#, + path = {} +[section] + value = base-past-second-include "#, escape_backslashes(&first_include_path), escape_backslashes(&include_if_path), escape_backslashes(&second_include_path), @@ -65,22 +73,22 @@ fn include_and_includeif_correct_inclusion_order() -> crate::Result { File::from_paths_metadata(Some(meta), options)?.expect("non-empty") }; - // TODO: test interaction with values from root as well - maybe against git as baseline. assert_eq!( - config.strings("core", None, "b"), + config.strings("section", None, "value"), Some(vec![ + cow_str("base"), cow_str("first-incl-path"), + cow_str("base-past-first-include"), cow_str("incl-if-path"), - cow_str("second-incl-path") + cow_str("base-past-includeIf"), + cow_str("second-incl-path"), + cow_str("base-past-second-include"), ]), - "first include is matched correctly, delayed_resolve = {}", + "include order isn't changed also in relation to the root configuratino, delayed_resolve = {}", delayed_resolve, ); - assert_eq!( - config.string("core", None, "b"), - Some(cow_str("second-incl-path")), - "second include is matched after incl-if", - ); + + // TODO: also validate serialization here, with front/post-matter. } Ok(()) } From f5580a3635289d96e662aab00e60d801c4e34e1c Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 20 Jul 2022 09:16:17 +0800 Subject: [PATCH 280/366] allow insertion of sections while preserving order (#331) --- git-config/src/file/access/mutate.rs | 22 ++++-- git-config/src/file/mod.rs | 2 +- git-config/src/file/tests.rs | 12 ++-- git-config/src/file/utils.rs | 101 ++++++++++++++++++++++++--- git-config/src/types.rs | 4 +- 5 files changed, 118 insertions(+), 23 deletions(-) diff --git a/git-config/src/file/access/mutate.rs b/git-config/src/file/access/mutate.rs index 189a62bf6b3..402a9db75e4 100644 --- a/git-config/src/file/access/mutate.rs +++ b/git-config/src/file/access/mutate.rs @@ -235,7 +235,12 @@ impl<'event> File<'event> { } /// Append another File to the end of ourselves, without loosing any information. - pub fn append(&mut self, mut other: Self) -> &mut Self { + pub fn append(&mut self, other: Self) -> &mut Self { + self.append_or_insert(other, None) + } + + /// Append another File to the end of ourselves, without loosing any information. + fn append_or_insert(&mut self, mut other: Self, mut insert_after: Option) -> &mut Self { let nl = self.detect_newline_style().to_owned(); fn ends_with_newline<'a>(it: impl DoubleEndedIterator>) -> bool { @@ -263,12 +268,19 @@ impl<'event> File<'event> { for id in std::mem::take(&mut other.section_order) { let section = other.sections.remove(&id).expect("present"); - self.push_section_internal(section); - let new_id = self.section_id_counter - 1; - last_added_section_id = Some(SectionId(new_id)); + let new_id = match insert_after { + Some(id) => { + let new_id = self.insert_section_after(section, id); + insert_after = Some(new_id); + new_id + } + None => self.push_section_internal(section), + }; + + last_added_section_id = Some(new_id); if let Some(post_matter) = other.frontmatter_post_section.remove(&id) { - self.frontmatter_post_section.insert(SectionId(new_id), post_matter); + self.frontmatter_post_section.insert(new_id, post_matter); } } diff --git a/git-config/src/file/mod.rs b/git-config/src/file/mod.rs index 8c0d72a1765..077afde5724 100644 --- a/git-config/src/file/mod.rs +++ b/git-config/src/file/mod.rs @@ -105,7 +105,7 @@ pub(crate) struct SectionId(pub(crate) usize); /// of section ids with the matched section and name, and is used for precedence /// management. #[derive(PartialEq, Eq, Clone, Debug)] -pub(crate) enum SectionBodyIds<'a> { +pub(crate) enum SectionBodyIdsLut<'a> { /// The list of section ids to use for obtaining the section body. Terminal(Vec), /// A hashmap from sub-section names to section ids. diff --git a/git-config/src/file/tests.rs b/git-config/src/file/tests.rs index 455b67dd705..0901786f69f 100644 --- a/git-config/src/file/tests.rs +++ b/git-config/src/file/tests.rs @@ -7,7 +7,7 @@ mod try_from { use std::{borrow::Cow, collections::HashMap, convert::TryFrom}; use crate::{ - file::{self, SectionBodyIds, SectionId}, + file::{self, SectionBodyIdsLut, SectionId}, parse::{ section, tests::util::{name_event, newline_event, section_header, value_event}, @@ -39,7 +39,7 @@ mod try_from { let mut tree = HashMap::new(); tree.insert( section::Name(Cow::Borrowed("core".into())), - vec![SectionBodyIds::Terminal(vec![SectionId(0)])], + vec![SectionBodyIdsLut::Terminal(vec![SectionId(0)])], ); tree }; @@ -84,7 +84,7 @@ mod try_from { inner_tree.insert(Cow::Borrowed("sub".into()), vec![SectionId(0)]); tree.insert( section::Name(Cow::Borrowed("core".into())), - vec![SectionBodyIds::NonTerminal(inner_tree)], + vec![SectionBodyIdsLut::NonTerminal(inner_tree)], ); tree }; @@ -128,11 +128,11 @@ mod try_from { let mut tree = HashMap::new(); tree.insert( section::Name(Cow::Borrowed("core".into())), - vec![SectionBodyIds::Terminal(vec![SectionId(0)])], + vec![SectionBodyIdsLut::Terminal(vec![SectionId(0)])], ); tree.insert( section::Name(Cow::Borrowed("other".into())), - vec![SectionBodyIds::Terminal(vec![SectionId(1)])], + vec![SectionBodyIdsLut::Terminal(vec![SectionId(1)])], ); tree }; @@ -181,7 +181,7 @@ mod try_from { let mut tree = HashMap::new(); tree.insert( section::Name(Cow::Borrowed("core".into())), - vec![SectionBodyIds::Terminal(vec![SectionId(0), SectionId(1)])], + vec![SectionBodyIdsLut::Terminal(vec![SectionId(0), SectionId(1)])], ); tree }; diff --git a/git-config/src/file/utils.rs b/git-config/src/file/utils.rs index ea4cfc5267f..2c05a918d89 100644 --- a/git-config/src/file/utils.rs +++ b/git-config/src/file/utils.rs @@ -1,9 +1,10 @@ +use std::cmp::Ordering; use std::collections::HashMap; use bstr::BStr; use crate::{ - file::{self, SectionBodyIds, SectionId}, + file::{self, SectionBodyIdsLut, SectionId}, lookup, parse::section, File, @@ -21,7 +22,7 @@ impl<'event> File<'event> { let mut found_node = false; if let Some(subsection_name) = header.subsection_name.clone() { for node in lookup.iter_mut() { - if let SectionBodyIds::NonTerminal(subsections) = node { + if let SectionBodyIdsLut::NonTerminal(subsections) = node { found_node = true; subsections .entry(subsection_name.clone()) @@ -33,18 +34,18 @@ impl<'event> File<'event> { if !found_node { let mut map = HashMap::new(); map.insert(subsection_name, vec![new_section_id]); - lookup.push(SectionBodyIds::NonTerminal(map)); + lookup.push(SectionBodyIdsLut::NonTerminal(map)); } } else { for node in lookup.iter_mut() { - if let SectionBodyIds::Terminal(vec) = node { + if let SectionBodyIdsLut::Terminal(vec) = node { found_node = true; vec.push(new_section_id); break; } } if !found_node { - lookup.push(SectionBodyIds::Terminal(vec![new_section_id])); + lookup.push(SectionBodyIdsLut::Terminal(vec![new_section_id])); } } self.section_order.push_back(new_section_id); @@ -52,6 +53,65 @@ impl<'event> File<'event> { new_section_id } + /// Inserts `section` after the section that comes `before` it, and maintains correct ordering in all of our lookup structures. + pub(crate) fn insert_section_after(&mut self, section: file::Section<'event>, before: SectionId) -> SectionId { + let lookup_section_order = { + let section_order = &self.section_order; + move |section_id| { + section_order + .iter() + .enumerate() + .find_map(|(idx, id)| (*id == section_id).then(|| idx)) + .expect("before-section exists") + } + }; + + let before_order = lookup_section_order(before); + let new_section_id = SectionId(self.section_id_counter); + self.sections.insert(new_section_id, section); + let header = &self.sections[&new_section_id].header; + let lookup = self.section_lookup_tree.entry(header.name.clone()).or_default(); + + let mut found_node = false; + if let Some(subsection_name) = header.subsection_name.clone() { + for node in lookup.iter_mut() { + if let SectionBodyIdsLut::NonTerminal(subsections) = node { + found_node = true; + let sections_with_name_and_subsection_name = + subsections.entry(subsection_name.clone()).or_default(); + let insert_pos = find_insert_pos_by_order( + sections_with_name_and_subsection_name, + before_order, + lookup_section_order, + ); + sections_with_name_and_subsection_name.insert(insert_pos, new_section_id); + break; + } + } + if !found_node { + let mut map = HashMap::new(); + map.insert(subsection_name, vec![new_section_id]); + lookup.push(SectionBodyIdsLut::NonTerminal(map)); + } + } else { + for node in lookup.iter_mut() { + if let SectionBodyIdsLut::Terminal(sections_with_name) = node { + found_node = true; + let insert_pos = find_insert_pos_by_order(sections_with_name, before_order, lookup_section_order); + sections_with_name.insert(insert_pos, new_section_id); + break; + } + } + if !found_node { + lookup.push(SectionBodyIdsLut::Terminal(vec![new_section_id])); + } + } + + self.section_order.insert(before_order + 1, new_section_id); + self.section_id_counter += 1; + new_section_id + } + /// Returns the mapping between section and subsection name to section ids. pub(crate) fn section_ids_by_name_and_subname<'a>( &'a self, @@ -71,14 +131,14 @@ impl<'event> File<'event> { if let Some(subsection_name) = subsection_name { let subsection_name: &BStr = subsection_name.into(); for node in section_ids { - if let SectionBodyIds::NonTerminal(subsection_lookup) = node { + if let SectionBodyIdsLut::NonTerminal(subsection_lookup) = node { maybe_ids = subsection_lookup.get(subsection_name).map(|v| v.iter().copied()); break; } } } else { for node in section_ids { - if let SectionBodyIds::Terminal(subsection_lookup) = node { + if let SectionBodyIdsLut::Terminal(subsection_lookup) = node { maybe_ids = Some(subsection_lookup.iter().copied()); break; } @@ -96,8 +156,8 @@ impl<'event> File<'event> { Some(lookup) => Ok(lookup.iter().flat_map({ let section_order = &self.section_order; move |node| match node { - SectionBodyIds::Terminal(v) => Box::new(v.iter().copied()) as Box>, - SectionBodyIds::NonTerminal(v) => Box::new({ + SectionBodyIdsLut::Terminal(v) => Box::new(v.iter().copied()) as Box>, + SectionBodyIdsLut::NonTerminal(v) => Box::new({ let v: Vec<_> = v.values().flatten().copied().collect(); section_order.iter().filter(move |a| v.contains(a)).copied() }), @@ -107,3 +167,26 @@ impl<'event> File<'event> { } } } + +fn find_insert_pos_by_order( + sections_with_name: &[SectionId], + before_order: usize, + lookup_section_order: impl Fn(SectionId) -> usize, +) -> usize { + let mut insert_pos = sections_with_name.len(); // push back by default + for (idx, candidate_id) in sections_with_name.iter().enumerate() { + let candidate_order = lookup_section_order(*candidate_id); + match candidate_order.cmp(&before_order) { + Ordering::Less => {} + Ordering::Equal => { + insert_pos = idx + 1; // insert right after this one + break; + } + Ordering::Greater => { + insert_pos = idx; // insert before this one + break; + } + } + } + insert_pos +} diff --git a/git-config/src/types.rs b/git-config/src/types.rs index 479ffad16b6..887f2c0f794 100644 --- a/git-config/src/types.rs +++ b/git-config/src/types.rs @@ -4,7 +4,7 @@ use std::collections::{HashMap, VecDeque}; use crate::file::Metadata; use crate::{ color, file, - file::{SectionBodyIds, SectionId}, + file::{SectionBodyIdsLut, SectionId}, integer, parse::section, }; @@ -97,7 +97,7 @@ pub struct File<'event> { pub(crate) frontmatter_post_section: HashMap>, /// Section name to section id lookup tree, with section bodies for subsections being in a non-terminal /// variant of `SectionBodyIds`. - pub(crate) section_lookup_tree: HashMap, Vec>>, + pub(crate) section_lookup_tree: HashMap, Vec>>, /// This indirection with the SectionId as the key is critical to flexibly /// supporting `git-config` sections, as duplicated keys are permitted. pub(crate) sections: HashMap>, From 6c1588fd1a2fa80fd866787cbf4bcc6e5b51abe6 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 20 Jul 2022 09:26:41 +0800 Subject: [PATCH 281/366] fix: maintain insertion order of includes on per-section basis at least. (#331) Note that git inserts values right after the include directive, 'splitting' the section, but we don't do that and insert new values after the section. Probably no issue in practice while keeping our implementation simple. --- git-config/src/file/access/mutate.rs | 2 +- git-config/src/file/includes.rs | 35 +++++++++++++------ .../from_paths/includes/conditional/mod.rs | 1 - 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/git-config/src/file/access/mutate.rs b/git-config/src/file/access/mutate.rs index 402a9db75e4..56def76846c 100644 --- a/git-config/src/file/access/mutate.rs +++ b/git-config/src/file/access/mutate.rs @@ -240,7 +240,7 @@ impl<'event> File<'event> { } /// Append another File to the end of ourselves, without loosing any information. - fn append_or_insert(&mut self, mut other: Self, mut insert_after: Option) -> &mut Self { + pub(crate) fn append_or_insert(&mut self, mut other: Self, mut insert_after: Option) -> &mut Self { let nl = self.detect_newline_style().to_owned(); fn ends_with_newline<'a>(it: impl DoubleEndedIterator>) -> bool { diff --git a/git-config/src/file/includes.rs b/git-config/src/file/includes.rs index bd48f3c358f..b2bd04a7275 100644 --- a/git-config/src/file/includes.rs +++ b/git-config/src/file/includes.rs @@ -7,7 +7,7 @@ use bstr::{BStr, BString, ByteSlice, ByteVec}; use git_features::threading::OwnShared; use git_ref::Category; -use crate::file::{init, Metadata}; +use crate::file::{init, Metadata, SectionId}; use crate::{file, File}; impl File<'static> { @@ -20,6 +20,11 @@ impl File<'static> { /// times. It's recommended use is as part of a multi-step bootstrapping which needs fine-grained control, /// and unless that's given one should prefer one of the other ways of initialization that resolve includes /// at the right time. + /// - included values are added after the _section_ that included them, not directly after the value. This is + /// a deviation from how git does it, as it technically adds new value right after the include path itself, + /// technically 'splitting' the section. This can only make a difference if the `include` section also has values + /// which later overwrite portions of the included file, which seems unusual as these would be related to `includes`. + /// We can fix this by 'splitting' the inlcude section if needed so the included sections are put into the right place. pub fn resolve_includes(&mut self, options: init::Options<'_>) -> Result<(), Error> { let mut buf = Vec::new(); resolve(self, OwnShared::clone(&self.meta), &mut buf, options) @@ -54,23 +59,27 @@ fn resolve_includes_recursive( let target_config_path = meta.path.as_deref(); - let mut include_paths = Vec::new(); - for section in target_config.sections() { + let mut section_ids_and_include_paths = Vec::new(); + for (id, section) in target_config + .section_order + .iter() + .map(|id| (*id, &target_config.sections[id])) + { let header = §ion.header; let header_name = header.name.as_ref(); if header_name == "include" && header.subsection_name.is_none() { - detach_include_paths(&mut include_paths, section) + detach_include_paths(&mut section_ids_and_include_paths, section, id) } else if header_name == "includeIf" { if let Some(condition) = &header.subsection_name { if include_condition_match(condition.as_ref(), target_config_path, options.includes)? { - detach_include_paths(&mut include_paths, section) + detach_include_paths(&mut section_ids_and_include_paths, section, id) } } } } append_followed_includes_recursively( - include_paths, + section_ids_and_include_paths, target_config, target_config_path, depth, @@ -81,7 +90,7 @@ fn resolve_includes_recursive( } fn append_followed_includes_recursively( - include_paths: Vec>, + section_ids_and_include_paths: Vec<(SectionId, crate::Path<'_>)>, target_config: &mut File<'static>, target_config_path: Option<&Path>, depth: u8, @@ -89,7 +98,7 @@ fn append_followed_includes_recursively( options: init::Options<'_>, buf: &mut Vec, ) -> Result<(), Error> { - for config_path in include_paths { + for (section_id, config_path) in section_ids_and_include_paths { let config_path = resolve_path(config_path, target_config_path, options.includes.interpolate)?; if !config_path.is_file() { continue; @@ -119,18 +128,22 @@ fn append_followed_includes_recursively( resolve_includes_recursive(&mut include_config, config_meta, depth + 1, buf, options)?; - target_config.append(include_config); + target_config.append_or_insert(include_config, Some(section_id)); } Ok(()) } -fn detach_include_paths(include_paths: &mut Vec>, section: &file::Section<'_>) { +fn detach_include_paths( + include_paths: &mut Vec<(SectionId, crate::Path<'static>)>, + section: &file::Section<'_>, + id: SectionId, +) { include_paths.extend( section .body .values("path") .into_iter() - .map(|path| crate::Path::from(Cow::Owned(path.into_owned()))), + .map(|path| (id, crate::Path::from(Cow::Owned(path.into_owned())))), ) } diff --git a/git-config/tests/file/init/from_paths/includes/conditional/mod.rs b/git-config/tests/file/init/from_paths/includes/conditional/mod.rs index e72836fbbfa..e6b87fc6e63 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/mod.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/mod.rs @@ -11,7 +11,6 @@ mod gitdir; mod onbranch; #[test] -#[ignore] fn include_and_includeif_correct_inclusion_order() -> crate::Result { let dir = tempdir()?; let config_path = dir.path().join("root"); From 14a68a6a78a09f8ae56e30e3b7501de66ef31fdc Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 20 Jul 2022 11:28:47 +0800 Subject: [PATCH 282/366] feat: `File` now compares actual content, ignoring whitespace and comments. (#331) --- git-config/src/file/access/mutate.rs | 4 +- git-config/src/file/impls.rs | 63 ++++++++++++++++++- git-config/src/types.rs | 7 ++- .../from_paths/includes/conditional/mod.rs | 45 ++++++++++--- 4 files changed, 108 insertions(+), 11 deletions(-) diff --git a/git-config/src/file/access/mutate.rs b/git-config/src/file/access/mutate.rs index 56def76846c..7f29ee14dc8 100644 --- a/git-config/src/file/access/mutate.rs +++ b/git-config/src/file/access/mutate.rs @@ -263,8 +263,8 @@ impl<'event> File<'event> { } let our_last_section_before_append = - (self.section_id_counter != 0).then(|| SectionId(self.section_id_counter - 1)); - let mut last_added_section_id = None; + insert_after.or_else(|| (self.section_id_counter != 0).then(|| SectionId(self.section_id_counter - 1))); + let mut last_added_section_id = None::; for id in std::mem::take(&mut other.section_order) { let section = other.sections.remove(&id).expect("present"); diff --git a/git-config/src/file/impls.rs b/git-config/src/file/impls.rs index 2f898609f27..21e4f788c9c 100644 --- a/git-config/src/file/impls.rs +++ b/git-config/src/file/impls.rs @@ -1,8 +1,11 @@ +use std::borrow::Cow; use std::{convert::TryFrom, fmt::Display, str::FromStr}; -use bstr::{BStr, BString}; +use bstr::{BStr, BString, ByteVec}; use crate::file::Metadata; +use crate::parse::{section, Event}; +use crate::value::normalize; use crate::{parse, File}; impl FromStr for File<'static> { @@ -46,3 +49,61 @@ impl Display for File<'_> { Display::fmt(&self.to_bstring(), f) } } + +impl PartialEq for File<'_> { + fn eq(&self, other: &Self) -> bool { + fn find_key<'a>(mut it: impl Iterator>) -> Option<&'a section::Key<'a>> { + it.find_map(|e| match e { + Event::SectionKey(k) => Some(k), + _ => None, + }) + } + fn collect_value<'a>(it: impl Iterator>) -> Cow<'a, BStr> { + let mut partial_value = BString::default(); + let mut value = None; + + for event in it { + match event { + Event::SectionKey(_) => break, + Event::Value(v) => { + value = v.clone().into(); + break; + } + Event::ValueNotDone(v) => partial_value.push_str(v.as_ref()), + Event::ValueDone(v) => { + partial_value.push_str(v.as_ref()); + value = Some(partial_value.into()); + break; + } + _ => (), + } + } + value.map(normalize).unwrap_or_default() + } + if self.section_order.len() != other.section_order.len() { + return false; + } + + for (lhs, rhs) in self + .section_order + .iter() + .zip(&other.section_order) + .map(|(lhs, rhs)| (&self.sections[lhs], &other.sections[rhs])) + { + if !(lhs.header.name == rhs.header.name && lhs.header.subsection_name == rhs.header.subsection_name) { + return false; + } + + let (mut lhs, mut rhs) = (lhs.body.0.iter(), rhs.body.0.iter()); + while let (Some(lhs_key), Some(rhs_key)) = (find_key(&mut lhs), find_key(&mut rhs)) { + if lhs_key != rhs_key { + return false; + } + if collect_value(&mut lhs) != collect_value(&mut rhs) { + return false; + } + } + } + true + } +} diff --git a/git-config/src/types.rs b/git-config/src/types.rs index 887f2c0f794..949d57fc384 100644 --- a/git-config/src/types.rs +++ b/git-config/src/types.rs @@ -86,8 +86,13 @@ pub enum Source { /// Consider the `multi` variants of the methods instead, if you want to work /// with all values. /// +/// # Equality +/// +/// In order to make it useful, equality will ignore all non-value bearing information, hence compare +/// only sections and their names, as well as all of their values. The ordering matters, of course. +/// /// [`raw_value()`]: Self::raw_value -#[derive(PartialEq, Eq, Clone, Debug, Default)] +#[derive(Eq, Clone, Debug, Default)] pub struct File<'event> { /// The list of events that occur before any section. Since a /// `git-config` file prohibits global values, this vec is limited to only diff --git a/git-config/tests/file/init/from_paths/includes/conditional/mod.rs b/git-config/tests/file/init/from_paths/includes/conditional/mod.rs index e6b87fc6e63..100affa65fe 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/mod.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/mod.rs @@ -1,3 +1,4 @@ +use std::str::FromStr; use std::{fs, path::Path}; use git_config::file::includes; @@ -11,6 +12,7 @@ mod gitdir; mod onbranch; #[test] +#[ignore] fn include_and_includeif_correct_inclusion_order() -> crate::Result { let dir = tempdir()?; let config_path = dir.path().join("root"); @@ -20,30 +22,39 @@ fn include_and_includeif_correct_inclusion_order() -> crate::Result { fs::write( first_include_path.as_path(), " +; first include beginning [section] - value = first-incl-path", + value = first-incl-path +# first include end no nl", )?; fs::write( second_include_path.as_path(), - " + "; second include beginning [section] - value = second-incl-path", + value = second-incl-path ; post value comment +# second include end +", )?; fs::write( include_if_path.as_path(), " +# includeIf beginning [section] - value = incl-if-path", + value = incl-if-path +; include if end no nl", )?; let root_config = format!( - r#" + r#" ; root beginning +# root pre base [section] - value = base + value = base # base comment +; root post base [include] path = {} +# root past first include [section] value = base-past-first-include [includeIf "gitdir:root/"] @@ -52,8 +63,10 @@ fn include_and_includeif_correct_inclusion_order() -> crate::Result { value = base-past-includeIf [include] path = {} +# root past last include [section] - value = base-past-second-include "#, + value = base-past-second-include +; root last include"#, escape_backslashes(&first_include_path), escape_backslashes(&include_if_path), escape_backslashes(&second_include_path), @@ -86,8 +99,26 @@ fn include_and_includeif_correct_inclusion_order() -> crate::Result { "include order isn't changed also in relation to the root configuratino, delayed_resolve = {}", delayed_resolve, ); + assert_eq!(config.sections().count(), 10); // TODO: also validate serialization here, with front/post-matter. + if delayed_resolve { + let config_string = config.to_string(); + let deserialized = File::from_str(&config_string)?; + assert_eq!(config, config, "equality comparisons work"); + eprintln!("{}", config_string); + assert_eq!( + deserialized.sections().count(), + config.sections().count(), + "sections must match to have a chance for equality" + ); + assert_eq!(config, deserialized, "we can round-trip the information at least"); + assert_eq!( + deserialized.to_string(), + config_string, + "even though complete roundtripping might not work due to newline issues" + ); + } } Ok(()) } From 9c248eeb015495f910f48ce5df3c8fcce905dba7 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 20 Jul 2022 11:33:18 +0800 Subject: [PATCH 283/366] refactor (#331) --- git-config/src/file/access/mod.rs | 1 - git-config/src/file/mod.rs | 1 + git-config/src/file/{access => }/write.rs | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename git-config/src/file/{access => }/write.rs (100%) diff --git a/git-config/src/file/access/mod.rs b/git-config/src/file/access/mod.rs index 1640081a16e..d602b5f8be2 100644 --- a/git-config/src/file/access/mod.rs +++ b/git-config/src/file/access/mod.rs @@ -2,4 +2,3 @@ mod comfort; mod mutate; mod raw; mod read_only; -mod write; diff --git a/git-config/src/file/mod.rs b/git-config/src/file/mod.rs index 077afde5724..cb8528edc12 100644 --- a/git-config/src/file/mod.rs +++ b/git-config/src/file/mod.rs @@ -113,3 +113,4 @@ pub(crate) enum SectionBodyIdsLut<'a> { } #[cfg(test)] mod tests; +mod write; diff --git a/git-config/src/file/access/write.rs b/git-config/src/file/write.rs similarity index 100% rename from git-config/src/file/access/write.rs rename to git-config/src/file/write.rs From ee10dd5a8ae0dabfee21c1ce146e92c3c9635e8a Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 20 Jul 2022 13:29:18 +0800 Subject: [PATCH 284/366] serializations maintains some invariants about whitespace where possible. (#331) However, this isn't enough and has to go together with insert/append to assure there is some visual separation between frontmatter and postmatter as well. --- git-config/src/file/access/mutate.rs | 48 ++------------------ git-config/src/file/write.rs | 45 +++++++++++++++--- git-config/tests/file/init/from_paths/mod.rs | 5 +- git-config/tests/file/mutable/multi_value.rs | 10 ++-- git-config/tests/file/mutable/section.rs | 14 +++--- git-config/tests/file/mutable/value.rs | 17 ++++--- 6 files changed, 68 insertions(+), 71 deletions(-) diff --git a/git-config/src/file/access/mutate.rs b/git-config/src/file/access/mutate.rs index 7f29ee14dc8..811ce847d47 100644 --- a/git-config/src/file/access/mutate.rs +++ b/git-config/src/file/access/mutate.rs @@ -1,9 +1,7 @@ -use bstr::BStr; use git_features::threading::OwnShared; use std::borrow::Cow; use crate::file::{MetadataFilter, SectionId}; -use crate::parse::{Event, FrontMatterEvents}; use crate::{ file::{self, rename_section, SectionMut}, lookup, @@ -241,30 +239,8 @@ impl<'event> File<'event> { /// Append another File to the end of ourselves, without loosing any information. pub(crate) fn append_or_insert(&mut self, mut other: Self, mut insert_after: Option) -> &mut Self { - let nl = self.detect_newline_style().to_owned(); - - fn ends_with_newline<'a>(it: impl DoubleEndedIterator>) -> bool { - it.last().map_or(true, |e| e.to_bstr_lossy().last() == Some(&b'\n')) - } - fn starts_with_newline<'a>(mut it: impl Iterator>) -> bool { - it.next().map_or(true, |e| e.to_bstr_lossy().first() == Some(&b'\n')) - } - let newline_event = || Event::Newline(Cow::Owned(nl.clone())); - - fn assure_ends_with_newline_if<'a, 'b>( - needs_nl: bool, - events: &'b mut FrontMatterEvents<'a>, - nl: &BStr, - ) -> &'b mut FrontMatterEvents<'a> { - if needs_nl && !ends_with_newline(events.iter()) { - events.push(Event::Newline(nl.to_owned().into())); - } - events - } - let our_last_section_before_append = insert_after.or_else(|| (self.section_id_counter != 0).then(|| SectionId(self.section_id_counter - 1))); - let mut last_added_section_id = None::; for id in std::mem::take(&mut other.section_order) { let section = other.sections.remove(&id).expect("present"); @@ -278,7 +254,6 @@ impl<'event> File<'event> { None => self.push_section_internal(section), }; - last_added_section_id = Some(new_id); if let Some(post_matter) = other.frontmatter_post_section.remove(&id) { self.frontmatter_post_section.insert(new_id, post_matter); } @@ -288,26 +263,13 @@ impl<'event> File<'event> { return self; } - let mut needs_nl = !starts_with_newline(other.frontmatter_events.iter()); - if let Some(id) = last_added_section_id - .or(our_last_section_before_append) - .filter(|_| needs_nl) - { - if !ends_with_newline(self.sections[&id].body.0.iter()) { - other.frontmatter_events.insert(0, newline_event()); - needs_nl = false; - } - } - match our_last_section_before_append { - Some(last_id) => assure_ends_with_newline_if( - needs_nl, - self.frontmatter_post_section.entry(last_id).or_default(), - nl.as_ref(), - ) - .extend(other.frontmatter_events), - None => assure_ends_with_newline_if(needs_nl, &mut self.frontmatter_events, nl.as_ref()) + Some(last_id) => self + .frontmatter_post_section + .entry(last_id) + .or_default() .extend(other.frontmatter_events), + None => self.frontmatter_events.extend(other.frontmatter_events), } self } diff --git a/git-config/src/file/write.rs b/git-config/src/file/write.rs index c023ed97dd7..3079a20c9af 100644 --- a/git-config/src/file/write.rs +++ b/git-config/src/file/write.rs @@ -1,4 +1,4 @@ -use bstr::BString; +use bstr::{BString, ByteSlice}; use crate::File; @@ -16,22 +16,53 @@ impl File<'_> { /// Stream ourselves to the given `out`, in order to reproduce this file mostly losslessly /// as it was parsed. pub fn write_to(&self, mut out: impl std::io::Write) -> std::io::Result<()> { - for event in self.frontmatter_events.as_ref() { - event.write_to(&mut out)?; + let nl = self.detect_newline_style(); + + let ends_with_newline = |e: &[crate::parse::Event<'_>]| -> bool { + if e.is_empty() { + return true; + } + e.iter() + .rev() + .take_while(|e| e.to_bstr_lossy().iter().all(|b| b.is_ascii_whitespace())) + .find_map(|e| e.to_bstr_lossy().contains_str(nl).then(|| true)) + .unwrap_or(false) + }; + + { + for event in self.frontmatter_events.as_ref() { + event.write_to(&mut out)?; + } + + if !ends_with_newline(self.frontmatter_events.as_ref()) && self.sections.iter().next().is_some() { + out.write_all(&nl)?; + } } + let mut prev_section_ended_with_newline = true; for section_id in &self.section_order { - self.sections - .get(section_id) - .expect("known section-id") - .write_to(&mut out)?; + if !prev_section_ended_with_newline { + out.write_all(&nl)?; + } + let section = self.sections.get(section_id).expect("known section-id"); + section.write_to(&mut out)?; + + prev_section_ended_with_newline = ends_with_newline(section.body.0.as_ref()); if let Some(post_matter) = self.frontmatter_post_section.get(section_id) { + if !prev_section_ended_with_newline { + out.write_all(&nl)?; + } for event in post_matter { event.write_to(&mut out)?; } + prev_section_ended_with_newline = ends_with_newline(post_matter); } } + if !prev_section_ended_with_newline { + out.write_all(&nl)?; + } + Ok(()) } } diff --git a/git-config/tests/file/init/from_paths/mod.rs b/git-config/tests/file/init/from_paths/mod.rs index 7a44bdd6e07..d8ce371e5ed 100644 --- a/git-config/tests/file/init/from_paths/mod.rs +++ b/git-config/tests/file/init/from_paths/mod.rs @@ -65,6 +65,7 @@ fn multiple_paths_single_value() -> crate::Result { } #[test] +#[ignore] fn frontmatter_is_maintained_in_multiple_files() -> crate::Result { let dir = tempdir()?; @@ -85,13 +86,13 @@ fn frontmatter_is_maintained_in_multiple_files() -> crate::Result { assert_eq!( config.to_string(), - ";before a\n[core]\na = true\n;before b\n [core]\nb = true\n# nothing in c\n; nothing in d" + ";before a\n[core]\na = true\n;before b\n [core]\nb = true\n# nothing in c\n; nothing in d\n" ); config.append(config.clone()); assert_eq!( config.to_string(), - ";before a\n[core]\na = true\n;before b\n [core]\nb = true\n# nothing in c\n; nothing in d\n;before a\n[core]\na = true\n;before b\n [core]\nb = true\n# nothing in c\n; nothing in d", + ";before a\n[core]\na = true\n;before b\n [core]\nb = true\n# nothing in c\n; nothing in d\n;before a\n[core]\na = true\n;before b\n [core]\nb = true\n# nothing in c\n; nothing in d\n", "other files post-section matter works as well, adding newlines as needed" ); diff --git a/git-config/tests/file/mutable/multi_value.rs b/git-config/tests/file/mutable/multi_value.rs index 5e4a5edea04..80219cc1778 100644 --- a/git-config/tests/file/mutable/multi_value.rs +++ b/git-config/tests/file/mutable/multi_value.rs @@ -76,7 +76,7 @@ mod set { values.set_string_at(0, "Hello"); assert_eq!( config.to_string(), - "[core]\n a = Hello\n [core]\n a =d\n a= f" + "[core]\n a = Hello\n [core]\n a =d\n a= f\n" ); Ok(()) } @@ -88,7 +88,7 @@ mod set { values.set_string_at(2, "Hello"); assert_eq!( config.to_string(), - "[core]\n a = b\"100\"\n [core]\n a =d\n a= Hello" + "[core]\n a = b\"100\"\n [core]\n a =d\n a= Hello\n" ); Ok(()) } @@ -100,7 +100,7 @@ mod set { values.set_all("Hello"); assert_eq!( config.to_string(), - "[core]\n a = Hello\n [core]\n a= Hello\n a =Hello" + "[core]\n a = Hello\n [core]\n a= Hello\n a =Hello\n" ); Ok(()) } @@ -112,7 +112,7 @@ mod set { values.set_all(""); assert_eq!( config.to_string(), - "[core]\n a = \n [core]\n a= \n a =" + "[core]\n a = \n [core]\n a= \n a =\n" ); Ok(()) } @@ -129,7 +129,7 @@ mod delete { values.delete(0); assert_eq!( config.to_string(), - "[core]\n \n [core]\n a =d\n a= f", + "[core]\n \n [core]\n a =d\n a= f\n", ); } diff --git a/git-config/tests/file/mutable/section.rs b/git-config/tests/file/mutable/section.rs index c2d35f96afe..f5bc8183e31 100644 --- a/git-config/tests/file/mutable/section.rs +++ b/git-config/tests/file/mutable/section.rs @@ -71,7 +71,7 @@ mod set { assert_eq!(prev_value.as_deref().expect("prev value set"), expected_prev_value); } - assert_eq!(config.to_string(), "\n [a]\n a = \n b = \" a\"\n c=\"b\\t\"\n d\"; comment\"\n e =a\\n\\tc d\\\\ \\\"x\\\""); + assert_eq!(config.to_string(), "\n [a]\n a = \n b = \" a\"\n c=\"b\\t\"\n d\"; comment\"\n e =a\\n\\tc d\\\\ \\\"x\\\"\n"); assert_eq!( config .section_mut("a", None)? @@ -128,12 +128,12 @@ mod push { #[test] fn values_are_escaped() { for (value, expected) in [ - ("a b", "$head\tk = a b"), - (" a b", "$head\tk = \" a b\""), - ("a b\t", "$head\tk = \"a b\\t\""), - (";c", "$head\tk = \";c\""), - ("#c", "$head\tk = \"#c\""), - ("a\nb\n\tc", "$head\tk = a\\nb\\n\\tc"), + ("a b", "$head\tk = a b\n"), + (" a b", "$head\tk = \" a b\"\n"), + ("a b\t", "$head\tk = \"a b\\t\"\n"), + (";c", "$head\tk = \";c\"\n"), + ("#c", "$head\tk = \"#c\"\n"), + ("a\nb\n\tc", "$head\tk = a\\nb\\n\\tc\n"), ] { let mut config = git_config::File::default(); let mut section = config.new_section("a", None).unwrap(); diff --git a/git-config/tests/file/mutable/value.rs b/git-config/tests/file/mutable/value.rs index fbce8329e16..5d75acf3c5a 100644 --- a/git-config/tests/file/mutable/value.rs +++ b/git-config/tests/file/mutable/value.rs @@ -126,7 +126,8 @@ mod set_string { a=hello world [core] c=d - e=f"#, + e=f +"#, ); let mut value = config.raw_value_mut("core", None, "e")?; @@ -137,7 +138,8 @@ mod set_string { a=hello world [core] c=d - e="#, + e= +"#, ); Ok(()) } @@ -154,14 +156,14 @@ mod delete { value.delete(); assert_eq!( config.to_string(), - "[core]\n \n [core]\n c=d\n e=f", + "[core]\n \n [core]\n c=d\n e=f\n", ); let mut value = config.raw_value_mut("core", None, "c")?; value.delete(); assert_eq!( config.to_string(), - "[core]\n \n [core]\n \n e=f", + "[core]\n \n [core]\n \n e=f\n", ); Ok(()) } @@ -189,7 +191,8 @@ mod delete { a=hello world [core] c=d - e=f"#, + e=f +"#, ); Ok(()) } @@ -204,7 +207,7 @@ mod delete { } assert_eq!( config.to_string(), - "[core]\n \n [core]\n c=d\n e=f" + "[core]\n \n [core]\n c=d\n e=f\n" ); Ok(()) } @@ -224,7 +227,7 @@ b value.delete(); assert_eq!( config.to_string(), - "[core]\n \n [core]\n c=d\n e=f" + "[core]\n \n [core]\n c=d\n e=f\n" ); Ok(()) } From 97e5ededb0390c1b4f296a35903433de9c519821 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 20 Jul 2022 14:11:18 +0800 Subject: [PATCH 285/366] finally proper whitespace handling in all the right places for perfect roundtripping to/from string (#331) --- git-config/src/file/access/mutate.rs | 27 ++++++++++--- git-config/src/file/write.rs | 28 ++++++------- .../from_paths/includes/conditional/mod.rs | 34 +++++++--------- git-config/tests/file/init/from_paths/mod.rs | 1 - git-config/tests/file/write.rs | 40 +++++++++++++++++++ 5 files changed, 90 insertions(+), 40 deletions(-) diff --git a/git-config/src/file/access/mutate.rs b/git-config/src/file/access/mutate.rs index 811ce847d47..f9abe20edf6 100644 --- a/git-config/src/file/access/mutate.rs +++ b/git-config/src/file/access/mutate.rs @@ -1,7 +1,9 @@ use git_features::threading::OwnShared; use std::borrow::Cow; +use crate::file::write::ends_with_newline; use crate::file::{MetadataFilter, SectionId}; +use crate::parse::{Event, FrontMatterEvents}; use crate::{ file::{self, rename_section, SectionMut}, lookup, @@ -239,6 +241,19 @@ impl<'event> File<'event> { /// Append another File to the end of ourselves, without loosing any information. pub(crate) fn append_or_insert(&mut self, mut other: Self, mut insert_after: Option) -> &mut Self { + let nl = self.detect_newline_style_smallvec(); + fn extend_and_assure_newline<'a>( + lhs: &mut FrontMatterEvents<'a>, + rhs: FrontMatterEvents<'a>, + nl: &impl AsRef<[u8]>, + ) { + if !ends_with_newline(lhs.as_ref(), nl) + && !rhs.first().map_or(true, |e| e.to_bstr_lossy().starts_with(nl.as_ref())) + { + lhs.push(Event::Newline(Cow::Owned(nl.as_ref().into()))) + } + lhs.extend(rhs); + } let our_last_section_before_append = insert_after.or_else(|| (self.section_id_counter != 0).then(|| SectionId(self.section_id_counter - 1))); @@ -264,12 +279,12 @@ impl<'event> File<'event> { } match our_last_section_before_append { - Some(last_id) => self - .frontmatter_post_section - .entry(last_id) - .or_default() - .extend(other.frontmatter_events), - None => self.frontmatter_events.extend(other.frontmatter_events), + Some(last_id) => extend_and_assure_newline( + self.frontmatter_post_section.entry(last_id).or_default(), + other.frontmatter_events, + &nl, + ), + None => extend_and_assure_newline(&mut self.frontmatter_events, other.frontmatter_events, &nl), } self } diff --git a/git-config/src/file/write.rs b/git-config/src/file/write.rs index 3079a20c9af..698fec6c923 100644 --- a/git-config/src/file/write.rs +++ b/git-config/src/file/write.rs @@ -18,23 +18,12 @@ impl File<'_> { pub fn write_to(&self, mut out: impl std::io::Write) -> std::io::Result<()> { let nl = self.detect_newline_style(); - let ends_with_newline = |e: &[crate::parse::Event<'_>]| -> bool { - if e.is_empty() { - return true; - } - e.iter() - .rev() - .take_while(|e| e.to_bstr_lossy().iter().all(|b| b.is_ascii_whitespace())) - .find_map(|e| e.to_bstr_lossy().contains_str(nl).then(|| true)) - .unwrap_or(false) - }; - { for event in self.frontmatter_events.as_ref() { event.write_to(&mut out)?; } - if !ends_with_newline(self.frontmatter_events.as_ref()) && self.sections.iter().next().is_some() { + if !ends_with_newline(self.frontmatter_events.as_ref(), nl) && self.sections.iter().next().is_some() { out.write_all(&nl)?; } } @@ -47,7 +36,7 @@ impl File<'_> { let section = self.sections.get(section_id).expect("known section-id"); section.write_to(&mut out)?; - prev_section_ended_with_newline = ends_with_newline(section.body.0.as_ref()); + prev_section_ended_with_newline = ends_with_newline(section.body.0.as_ref(), nl); if let Some(post_matter) = self.frontmatter_post_section.get(section_id) { if !prev_section_ended_with_newline { out.write_all(&nl)?; @@ -55,7 +44,7 @@ impl File<'_> { for event in post_matter { event.write_to(&mut out)?; } - prev_section_ended_with_newline = ends_with_newline(post_matter); + prev_section_ended_with_newline = ends_with_newline(post_matter, nl); } } @@ -66,3 +55,14 @@ impl File<'_> { Ok(()) } } + +pub(crate) fn ends_with_newline(e: &[crate::parse::Event<'_>], nl: impl AsRef<[u8]>) -> bool { + if e.is_empty() { + return true; + } + e.iter() + .rev() + .take_while(|e| e.to_bstr_lossy().iter().all(|b| b.is_ascii_whitespace())) + .find_map(|e| e.to_bstr_lossy().contains_str(nl.as_ref()).then(|| true)) + .unwrap_or(false) +} diff --git a/git-config/tests/file/init/from_paths/includes/conditional/mod.rs b/git-config/tests/file/init/from_paths/includes/conditional/mod.rs index 100affa65fe..a2069c1a07c 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/mod.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/mod.rs @@ -12,7 +12,6 @@ mod gitdir; mod onbranch; #[test] -#[ignore] fn include_and_includeif_correct_inclusion_order() -> crate::Result { let dir = tempdir()?; let config_path = dir.path().join("root"); @@ -57,6 +56,7 @@ fn include_and_includeif_correct_inclusion_order() -> crate::Result { # root past first include [section] value = base-past-first-include +# root before include-if no-nl [includeIf "gitdir:root/"] path = {} [section] @@ -101,24 +101,20 @@ fn include_and_includeif_correct_inclusion_order() -> crate::Result { ); assert_eq!(config.sections().count(), 10); - // TODO: also validate serialization here, with front/post-matter. - if delayed_resolve { - let config_string = config.to_string(); - let deserialized = File::from_str(&config_string)?; - assert_eq!(config, config, "equality comparisons work"); - eprintln!("{}", config_string); - assert_eq!( - deserialized.sections().count(), - config.sections().count(), - "sections must match to have a chance for equality" - ); - assert_eq!(config, deserialized, "we can round-trip the information at least"); - assert_eq!( - deserialized.to_string(), - config_string, - "even though complete roundtripping might not work due to newline issues" - ); - } + let config_string = config.to_string(); + let deserialized = File::from_str(&config_string)?; + assert_eq!(config, config, "equality comparisons work"); + assert_eq!( + deserialized.sections().count(), + config.sections().count(), + "sections must match to have a chance for equality" + ); + assert_eq!(config, deserialized, "we can round-trip the information at least"); + assert_eq!( + deserialized.to_string(), + config_string, + "serialization works exactly as before" + ); } Ok(()) } diff --git a/git-config/tests/file/init/from_paths/mod.rs b/git-config/tests/file/init/from_paths/mod.rs index d8ce371e5ed..a602745fe47 100644 --- a/git-config/tests/file/init/from_paths/mod.rs +++ b/git-config/tests/file/init/from_paths/mod.rs @@ -65,7 +65,6 @@ fn multiple_paths_single_value() -> crate::Result { } #[test] -#[ignore] fn frontmatter_is_maintained_in_multiple_files() -> crate::Result { let dir = tempdir()?; diff --git a/git-config/tests/file/write.rs b/git-config/tests/file/write.rs index cd6d3418e58..1d95774afb8 100644 --- a/git-config/tests/file/write.rs +++ b/git-config/tests/file/write.rs @@ -1,5 +1,45 @@ +use bstr::ByteVec; use std::convert::TryFrom; +#[test] +fn empty_sections_roundtrip() { + let input = r#" + [a] + [b] + [c] + + [d] +"#; + + let config = git_config::File::try_from(input).unwrap(); + assert_eq!(config.to_bstring(), input); +} + +#[test] +fn empty_sections_with_comments_roundtrip() { + let input = r#"; pre-a + [a] # side a + ; post a + [b] ; side b + [c] ; side c + ; post c + [d] # side d +"#; + + let mut config = git_config::File::try_from(input).unwrap(); + let mut single_string = config.to_bstring(); + assert_eq!(single_string, input); + assert_eq!( + config.append(config.clone()).to_string(), + { + let clone = single_string.clone(); + single_string.push_str(&clone); + single_string + }, + "string-duplication is the same as data structure duplication" + ); +} + #[test] fn complex_lossless_roundtrip() { let input = r#" From 0b05be850d629124f027af993e316b9018912337 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 20 Jul 2022 14:16:50 +0800 Subject: [PATCH 286/366] thanks clippy --- etc/check-package-size.sh | 2 +- git-config/src/file/write.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/etc/check-package-size.sh b/etc/check-package-size.sh index 450ad7c2415..6dbc8126e38 100755 --- a/etc/check-package-size.sh +++ b/etc/check-package-size.sh @@ -28,7 +28,7 @@ echo "in root: gitoxide CLI" (enter git-bitmap && indent cargo diet -n --package-size-limit 5KB) (enter git-tempfile && indent cargo diet -n --package-size-limit 25KB) (enter git-lock && indent cargo diet -n --package-size-limit 15KB) -(enter git-config && indent cargo diet -n --package-size-limit 85KB) +(enter git-config && indent cargo diet -n --package-size-limit 90KB) (enter git-hash && indent cargo diet -n --package-size-limit 20KB) (enter git-chunk && indent cargo diet -n --package-size-limit 10KB) (enter git-rebase && indent cargo diet -n --package-size-limit 5KB) diff --git a/git-config/src/file/write.rs b/git-config/src/file/write.rs index 698fec6c923..821192f0335 100644 --- a/git-config/src/file/write.rs +++ b/git-config/src/file/write.rs @@ -24,14 +24,14 @@ impl File<'_> { } if !ends_with_newline(self.frontmatter_events.as_ref(), nl) && self.sections.iter().next().is_some() { - out.write_all(&nl)?; + out.write_all(nl)?; } } let mut prev_section_ended_with_newline = true; for section_id in &self.section_order { if !prev_section_ended_with_newline { - out.write_all(&nl)?; + out.write_all(nl)?; } let section = self.sections.get(section_id).expect("known section-id"); section.write_to(&mut out)?; @@ -39,7 +39,7 @@ impl File<'_> { prev_section_ended_with_newline = ends_with_newline(section.body.0.as_ref(), nl); if let Some(post_matter) = self.frontmatter_post_section.get(section_id) { if !prev_section_ended_with_newline { - out.write_all(&nl)?; + out.write_all(nl)?; } for event in post_matter { event.write_to(&mut out)?; @@ -49,7 +49,7 @@ impl File<'_> { } if !prev_section_ended_with_newline { - out.write_all(&nl)?; + out.write_all(nl)?; } Ok(()) From fbcf40e16b8fc1ff97dbed2bc22b64bd44a8b99d Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 20 Jul 2022 14:50:07 +0800 Subject: [PATCH 287/366] fix windows tests (#331) --- git-config/src/parse/nom/mod.rs | 2 +- git-config/src/parse/nom/tests.rs | 49 ++++++++++++++++++++++++ git-config/tests/file/mutable/section.rs | 16 ++++---- git-config/tests/file/mutable/value.rs | 18 ++++++--- 4 files changed, 71 insertions(+), 14 deletions(-) diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index ae22b5f1b36..73d64f513df 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -372,7 +372,7 @@ fn value_impl<'a>(i: &'a [u8], receive_event: &mut impl FnMut(Event<'a>)) -> IRe } let (i, remainder_value) = { - let mut new_index = parsed_index; + let mut new_index = 0; for index in (offset..parsed_index).rev() { if !i[index].is_ascii_whitespace() { new_index = index + 1; diff --git a/git-config/src/parse/nom/tests.rs b/git-config/src/parse/nom/tests.rs index 9a7e275f961..4cc0396313c 100644 --- a/git-config/src/parse/nom/tests.rs +++ b/git-config/src/parse/nom/tests.rs @@ -163,6 +163,7 @@ mod config_name { } mod section { + use crate::parse::tests::util::newline_custom_event; use crate::parse::{ error::ParseNode, section, @@ -199,6 +200,54 @@ mod section { }) } + #[test] + fn empty_value_with_windows_newlines() { + let mut node = ParseNode::SectionHeader; + assert_eq!( + section(b"[a] k = \r\n", &mut node).unwrap(), + fully_consumed(( + Section { + header: parsed_section_header("a", None), + events: vec![ + whitespace_event(" "), + name_event("k"), + whitespace_event(" "), + Event::KeyValueSeparator, + whitespace_event(" "), + value_event(""), + newline_custom_event("\r\n") + ] + .into(), + }, + 1 + )), + ); + } + + #[test] + fn simple_value_with_windows_newlines() { + let mut node = ParseNode::SectionHeader; + assert_eq!( + section(b"[a] k = v\r\n", &mut node).unwrap(), + fully_consumed(( + Section { + header: parsed_section_header("a", None), + events: vec![ + whitespace_event(" "), + name_event("k"), + whitespace_event(" "), + Event::KeyValueSeparator, + whitespace_event(" "), + value_event("v"), + newline_custom_event("\r\n") + ] + .into(), + }, + 1 + )), + ); + } + #[test] fn empty_section() { let mut node = ParseNode::SectionHeader; diff --git a/git-config/tests/file/mutable/section.rs b/git-config/tests/file/mutable/section.rs index f5bc8183e31..5738aa989b7 100644 --- a/git-config/tests/file/mutable/section.rs +++ b/git-config/tests/file/mutable/section.rs @@ -128,18 +128,20 @@ mod push { #[test] fn values_are_escaped() { for (value, expected) in [ - ("a b", "$head\tk = a b\n"), - (" a b", "$head\tk = \" a b\"\n"), - ("a b\t", "$head\tk = \"a b\\t\"\n"), - (";c", "$head\tk = \";c\"\n"), - ("#c", "$head\tk = \"#c\"\n"), - ("a\nb\n\tc", "$head\tk = a\\nb\\n\\tc\n"), + ("a b", "$head\tk = a b$nl"), + (" a b", "$head\tk = \" a b\"$nl"), + ("a b\t", "$head\tk = \"a b\\t\"$nl"), + (";c", "$head\tk = \";c\"$nl"), + ("#c", "$head\tk = \"#c\"$nl"), + ("a\nb\n\tc", "$head\tk = a\\nb\\n\\tc$nl"), ] { let mut config = git_config::File::default(); let mut section = config.new_section("a", None).unwrap(); section.set_implicit_newline(false); section.push(Key::try_from("k").unwrap(), value); - let expected = expected.replace("$head", &format!("[a]{nl}", nl = section.newline())); + let expected = expected + .replace("$head", &format!("[a]{nl}", nl = section.newline())) + .replace("$nl", §ion.newline().to_string()); assert_eq!(config.to_bstring(), expected); } } diff --git a/git-config/tests/file/mutable/value.rs b/git-config/tests/file/mutable/value.rs index 5d75acf3c5a..c1354fee540 100644 --- a/git-config/tests/file/mutable/value.rs +++ b/git-config/tests/file/mutable/value.rs @@ -42,26 +42,32 @@ mod set_string { use crate::file::mutable::value::init_config; fn assert_set_string(expected: &str) { + let nl = git_config::File::default().detect_newline_style().to_string(); for input in [ "[a] k = v", "[a] k = ", "[a] k =", - "[a] k =\n", + "[a] k =$nl", "[a] k ", - "[a] k\n", + "[a] k$nl", "[a] k", ] { - let mut file: git_config::File = input.parse().unwrap(); + let mut file: git_config::File = input.replace("$nl", &nl).parse().unwrap(); let mut v = file.raw_value_mut("a", None, "k").unwrap(); v.set_string(expected); assert_eq!(v.get().unwrap().as_ref(), expected); - let file: git_config::File = match file.to_string().parse() { + let file_string = file.to_string(); + let file: git_config::File = match file_string.parse() { Ok(f) => f, - Err(err) => panic!("{:?} failed with: {}", file.to_string(), err), + Err(err) => panic!("{:?} failed with: {}", file_string, err), }; - assert_eq!(file.raw_value("a", None, "k").expect("present").as_ref(), expected); + assert_eq!( + file.raw_value("a", None, "k").expect("present").as_ref(), + expected, + "{file_string:?}" + ); } } From 8a7fb15f78ce16d5caedd7656e8aa98e72f248a6 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 20 Jul 2022 15:30:05 +0800 Subject: [PATCH 288/366] refactor (#331) --- git-config/src/parse/nom/mod.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index 73d64f513df..d597dc68cd5 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -372,13 +372,12 @@ fn value_impl<'a>(i: &'a [u8], receive_event: &mut impl FnMut(Event<'a>)) -> IRe } let (i, remainder_value) = { - let mut new_index = 0; - for index in (offset..parsed_index).rev() { - if !i[index].is_ascii_whitespace() { - new_index = index + 1; - break; - } - } + let new_index = i[offset..parsed_index] + .iter() + .enumerate() + .rev() + .find_map(|(idx, b)| (!b.is_ascii_whitespace()).then(|| offset + idx + 1)) + .unwrap_or(0); (&i[new_index..], &i[offset..new_index]) }; From 3d89a46bf88b1fb5b4aa5da9fd12c7e310be3f9d Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 20 Jul 2022 15:35:57 +0800 Subject: [PATCH 289/366] multi-path include test (#331) Just to be sure as I think it's not yet tested anywhere. --- .../tests/file/init/from_paths/includes/conditional/mod.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/git-config/tests/file/init/from_paths/includes/conditional/mod.rs b/git-config/tests/file/init/from_paths/includes/conditional/mod.rs index a2069c1a07c..9775d824d92 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/mod.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/mod.rs @@ -12,7 +12,7 @@ mod gitdir; mod onbranch; #[test] -fn include_and_includeif_correct_inclusion_order() -> crate::Result { +fn include_and_includeif_correct_inclusion_order_and_delayed_resolve_include() -> crate::Result { let dir = tempdir()?; let config_path = dir.path().join("root"); let first_include_path = dir.path().join("first-incl"); @@ -53,6 +53,7 @@ fn include_and_includeif_correct_inclusion_order() -> crate::Result { ; root post base [include] path = {} + path = {} ; paths are multi-values # root past first include [section] value = base-past-first-include @@ -67,6 +68,7 @@ fn include_and_includeif_correct_inclusion_order() -> crate::Result { [section] value = base-past-second-include ; root last include"#, + escape_backslashes(&first_include_path), escape_backslashes(&first_include_path), escape_backslashes(&include_if_path), escape_backslashes(&second_include_path), @@ -90,6 +92,7 @@ fn include_and_includeif_correct_inclusion_order() -> crate::Result { Some(vec![ cow_str("base"), cow_str("first-incl-path"), + cow_str("first-incl-path"), cow_str("base-past-first-include"), cow_str("incl-if-path"), cow_str("base-past-includeIf"), @@ -99,7 +102,7 @@ fn include_and_includeif_correct_inclusion_order() -> crate::Result { "include order isn't changed also in relation to the root configuratino, delayed_resolve = {}", delayed_resolve, ); - assert_eq!(config.sections().count(), 10); + assert_eq!(config.sections().count(), 11); let config_string = config.to_string(); let deserialized = File::from_str(&config_string)?; From de8572ff2ced9422832e1ba433955c33f0994675 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 20 Jul 2022 15:49:59 +0800 Subject: [PATCH 290/366] feat: resolve includes in local repository configuration (#331) --- git-repository/src/config/cache.rs | 4 ++-- git-repository/src/config/mod.rs | 4 +++- git-repository/src/open.rs | 8 ++++---- .../generated-archives/make_config_repo.tar.xz | 4 ++-- git-repository/tests/fixtures/make_config_repo.sh | 10 ++++++++++ git-repository/tests/repository/config.rs | 2 ++ 6 files changed, 23 insertions(+), 9 deletions(-) diff --git a/git-repository/src/config/cache.rs b/git-repository/src/config/cache.rs index 9ba8e846745..efd4ae91175 100644 --- a/git-repository/src/config/cache.rs +++ b/git-repository/src/config/cache.rs @@ -82,7 +82,7 @@ impl StageOne { impl Cache { pub fn from_stage_one( StageOne { - git_dir_config: config, + git_dir_config: mut config, buf: _, is_bare, object_hash, @@ -101,7 +101,6 @@ impl Cache { .and_then(|home| home_env.check(home).ok().flatten()); // TODO: don't forget to use the canonicalized home for initializing the stacked config. // like git here: https://github.com/git/git/blob/master/config.c#L208:L208 - // TODO: resolve includes and load other kinds of configuration let options = git_config::file::init::Options { lossy: !cfg!(debug_assertions), includes: git_config::file::includes::Options::follow( @@ -112,6 +111,7 @@ impl Cache { }, ), }; + config.resolve_includes(options)?; let excludes_file = config .path_filter("core", None, "excludesFile", &mut filter_config_section) diff --git a/git-repository/src/config/mod.rs b/git-repository/src/config/mod.rs index a06a9b4c529..dd9e4b2b28b 100644 --- a/git-repository/src/config/mod.rs +++ b/git-repository/src/config/mod.rs @@ -21,7 +21,9 @@ pub enum Error { #[error("Could not read configuration file")] Io(#[from] std::io::Error), #[error(transparent)] - Config(#[from] git_config::file::init::Error), + Init(#[from] git_config::file::init::Error), + #[error(transparent)] + ResolveIncludes(#[from] git_config::file::includes::Error), #[error("Cannot handle objects formatted as {:?}", .name)] UnsupportedObjectFormat { name: BString }, #[error("The value for '{}' cannot be empty", .key)] diff --git a/git-repository/src/open.rs b/git-repository/src/open.rs index 00996c59fee..f492feac75f 100644 --- a/git-repository/src/open.rs +++ b/git-repository/src/open.rs @@ -270,10 +270,10 @@ impl ThreadSafeRepository { .map(|cd| git_dir.join(cd)); let common_dir_ref = common_dir.as_deref().unwrap_or(&git_dir); - let early_cache = crate::config::cache::StageOne::new(common_dir_ref, git_dir_trust)?; + let cache = crate::config::cache::StageOne::new(common_dir_ref, git_dir_trust)?; let mut refs = { - let reflog = early_cache.reflog.unwrap_or(git_ref::store::WriteReflog::Disable); - let object_hash = early_cache.object_hash; + let reflog = cache.reflog.unwrap_or(git_ref::store::WriteReflog::Disable); + let object_hash = cache.object_hash; match &common_dir { Some(common_dir) => crate::RefStore::for_linked_worktree(&git_dir, common_dir, reflog, object_hash), None => crate::RefStore::at(&git_dir, reflog, object_hash), @@ -281,7 +281,7 @@ impl ThreadSafeRepository { }; let head = refs.find("HEAD").ok(); let config = crate::config::Cache::from_stage_one( - early_cache, + cache, common_dir_ref, head.as_ref().and_then(|head| head.target.try_name()), filter_config_section.unwrap_or(crate::config::section::is_trusted), diff --git a/git-repository/tests/fixtures/generated-archives/make_config_repo.tar.xz b/git-repository/tests/fixtures/generated-archives/make_config_repo.tar.xz index 940cd3bb580..3e468db3aec 100644 --- a/git-repository/tests/fixtures/generated-archives/make_config_repo.tar.xz +++ b/git-repository/tests/fixtures/generated-archives/make_config_repo.tar.xz @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:68233a1e37e4d423532370d975297116f967c9b5c0044d66534e7074f92acf77 -size 9152 +oid sha256:20b64234f3e8047502160c3c56841b8ddf659798a8b02fc72b5a21bc85dee920 +size 9212 diff --git a/git-repository/tests/fixtures/make_config_repo.sh b/git-repository/tests/fixtures/make_config_repo.sh index ff58143975f..46ee6267d6f 100644 --- a/git-repository/tests/fixtures/make_config_repo.sh +++ b/git-repository/tests/fixtures/make_config_repo.sh @@ -13,4 +13,14 @@ cat <>.git/config absolute-path = /etc/man.conf bad-user-path = ~noname/repo single-string = hello world + override = base + +[include] + path = ../a.config +EOF + + +cat <>a.config +[a] + override = from-a.config EOF diff --git a/git-repository/tests/repository/config.rs b/git-repository/tests/repository/config.rs index 9a49aedab01..c0a102f1a00 100644 --- a/git-repository/tests/repository/config.rs +++ b/git-repository/tests/repository/config.rs @@ -25,6 +25,8 @@ fn access_values() { "hello world" ); + assert_eq!(config.string("a.override").expect("present").as_ref(), "from-a.config"); + assert_eq!(config.boolean("core.missing"), None); assert_eq!(config.try_boolean("core.missing"), None); From be0971c5191f7866063ebcc0407331e683cf7d68 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 20 Jul 2022 15:56:23 +0800 Subject: [PATCH 291/366] only a select few early config attributes must be repo-local (#331) --- git-repository/src/config/cache.rs | 34 ++++++++++++++++-------------- git-repository/src/open.rs | 8 +++---- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/git-repository/src/config/cache.rs b/git-repository/src/config/cache.rs index efd4ae91175..9819542c342 100644 --- a/git-repository/src/config/cache.rs +++ b/git-repository/src/config/cache.rs @@ -14,7 +14,6 @@ pub(crate) struct StageOne { is_bare: bool, pub object_hash: git_hash::Kind, - use_multi_pack_index: bool, pub reflog: Option, } @@ -38,7 +37,6 @@ impl StageOne { }; let is_bare = config_bool(&config, "core.bare", false)?; - let use_multi_pack_index = config_bool(&config, "core.multiPackIndex", true)?; let repo_format_version = config .value::("core", None, "repositoryFormatVersion") .map_or(0, |v| v.to_decimal().unwrap_or_default()); @@ -57,23 +55,13 @@ impl StageOne { }) .transpose()? .unwrap_or(git_hash::Kind::Sha1); - let reflog = config.string("core", None, "logallrefupdates").map(|val| { - (val.eq_ignore_ascii_case(b"always")) - .then(|| git_ref::store::WriteReflog::Always) - .or_else(|| { - git_config::Boolean::try_from(val) - .ok() - .and_then(|b| b.is_true().then(|| git_ref::store::WriteReflog::Normal)) - }) - .unwrap_or(git_ref::store::WriteReflog::Disable) - }); + let reflog = query_refupdates(&config); Ok(StageOne { git_dir_config: config, buf, is_bare, object_hash, - use_multi_pack_index, reflog, }) } @@ -86,8 +74,7 @@ impl Cache { buf: _, is_bare, object_hash, - use_multi_pack_index, - reflog, + reflog: _, }: StageOne, git_dir: &std::path::Path, branch_name: Option<&git_ref::FullNameRef>, @@ -117,7 +104,6 @@ impl Cache { .path_filter("core", None, "excludesFile", &mut filter_config_section) .map(|p| p.interpolate(options.includes.interpolate).map(|p| p.into_owned())) .transpose()?; - let ignore_case = config_bool(&config, "core.ignoreCase", false)?; let mut hex_len = None; if let Some(hex_len_str) = config.string("core", None, "abbrev") { @@ -150,6 +136,9 @@ impl Cache { } } + let reflog = query_refupdates(&config); + let ignore_case = config_bool(&config, "core.ignoreCase", false)?; + let use_multi_pack_index = config_bool(&config, "core.multiPackIndex", true)?; Ok(Cache { resolved: config.into(), use_multi_pack_index, @@ -212,3 +201,16 @@ fn config_bool(config: &git_config::File<'_>, key: &str, default: bool) -> Resul key: key.into(), }) } + +fn query_refupdates(config: &git_config::File<'static>) -> Option { + config.string("core", None, "logallrefupdates").map(|val| { + (val.eq_ignore_ascii_case(b"always")) + .then(|| git_ref::store::WriteReflog::Always) + .or_else(|| { + git_config::Boolean::try_from(val) + .ok() + .and_then(|b| b.is_true().then(|| git_ref::store::WriteReflog::Normal)) + }) + .unwrap_or(git_ref::store::WriteReflog::Disable) + }) +} diff --git a/git-repository/src/open.rs b/git-repository/src/open.rs index f492feac75f..f2bd0d51402 100644 --- a/git-repository/src/open.rs +++ b/git-repository/src/open.rs @@ -270,10 +270,10 @@ impl ThreadSafeRepository { .map(|cd| git_dir.join(cd)); let common_dir_ref = common_dir.as_deref().unwrap_or(&git_dir); - let cache = crate::config::cache::StageOne::new(common_dir_ref, git_dir_trust)?; + let repo_config = crate::config::cache::StageOne::new(common_dir_ref, git_dir_trust)?; let mut refs = { - let reflog = cache.reflog.unwrap_or(git_ref::store::WriteReflog::Disable); - let object_hash = cache.object_hash; + let reflog = repo_config.reflog.unwrap_or(git_ref::store::WriteReflog::Disable); + let object_hash = repo_config.object_hash; match &common_dir { Some(common_dir) => crate::RefStore::for_linked_worktree(&git_dir, common_dir, reflog, object_hash), None => crate::RefStore::at(&git_dir, reflog, object_hash), @@ -281,7 +281,7 @@ impl ThreadSafeRepository { }; let head = refs.find("HEAD").ok(); let config = crate::config::Cache::from_stage_one( - cache, + repo_config, common_dir_ref, head.as_ref().and_then(|head| head.target.try_name()), filter_config_section.unwrap_or(crate::config::section::is_trusted), From cfda0c335d759cae0b23cef51f7b85a5f4b11e82 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 20 Jul 2022 16:57:38 +0800 Subject: [PATCH 292/366] Serialize lossily-read configuration files correctly anyway. (#331) That way round-tripping will work as before, without a special case to consider for the user of the API. --- git-config/src/file/access/mutate.rs | 2 +- git-config/src/file/access/read_only.rs | 10 +---- git-config/src/file/init/types.rs | 4 +- git-config/src/file/section/mod.rs | 59 +++++++++++++++++++++++-- git-config/src/file/write.rs | 24 +++++++--- git-config/src/parse/nom/tests.rs | 19 ++++++++ git-config/tests/file/write.rs | 21 ++++++++- 7 files changed, 117 insertions(+), 22 deletions(-) diff --git a/git-config/src/file/access/mutate.rs b/git-config/src/file/access/mutate.rs index f9abe20edf6..100d5fb285a 100644 --- a/git-config/src/file/access/mutate.rs +++ b/git-config/src/file/access/mutate.rs @@ -247,7 +247,7 @@ impl<'event> File<'event> { rhs: FrontMatterEvents<'a>, nl: &impl AsRef<[u8]>, ) { - if !ends_with_newline(lhs.as_ref(), nl) + if !ends_with_newline(lhs.as_ref(), nl, true) && !rhs.first().map_or(true, |e| e.to_bstr_lossy().starts_with(nl.as_ref())) { lhs.push(Event::Newline(Cow::Owned(nl.as_ref().into()))) diff --git a/git-config/src/file/access/read_only.rs b/git-config/src/file/access/read_only.rs index 37c6e6fd1ef..3bac7a7caa6 100644 --- a/git-config/src/file/access/read_only.rs +++ b/git-config/src/file/access/read_only.rs @@ -5,6 +5,7 @@ use bstr::BStr; use git_features::threading::OwnShared; use smallvec::SmallVec; +use crate::file::write::{extract_newline, platform_newline}; use crate::file::{Metadata, MetadataFilter}; use crate::parse::Event; use crate::{file, lookup, File}; @@ -279,13 +280,6 @@ impl<'event> File<'event> { /// /// Note that the first found newline is the one we use in the assumption of consistency. pub fn detect_newline_style(&self) -> &BStr { - fn extract_newline<'a, 'b>(e: &'a Event<'b>) -> Option<&'a BStr> { - match e { - Event::Newline(b) => b.as_ref().into(), - _ => None, - } - } - self.frontmatter_events .iter() .find_map(extract_newline) @@ -293,7 +287,7 @@ impl<'event> File<'event> { self.sections() .find_map(|s| s.body.as_ref().iter().find_map(extract_newline)) }) - .unwrap_or_else(|| if cfg!(windows) { "\r\n" } else { "\n" }.into()) + .unwrap_or_else(|| platform_newline()) } pub(crate) fn detect_newline_style_smallvec(&self) -> SmallVec<[u8; 2]> { diff --git a/git-config/src/file/init/types.rs b/git-config/src/file/init/types.rs index 76f5ce10e70..779931f5432 100644 --- a/git-config/src/file/init/types.rs +++ b/git-config/src/file/init/types.rs @@ -22,8 +22,8 @@ pub struct Options<'a> { pub includes: init::includes::Options<'a>, /// If true, only value-bearing parse events will be kept to reduce memory usage and increase performance. /// - /// Note that doing so will prevent [`write_to()`][crate::File::write_to()] to serialize itself meaningfully and correctly, - /// as newlines will be missing. Use this only if it's clear that serialization will not be attempted. + /// Note that doing so will degenerate [`write_to()`][crate::File::write_to()] and strip it off its comments + /// and additional whitespace entirely, but will otherwise be a valid configuration file. pub lossy: bool, } diff --git a/git-config/src/file/section/mod.rs b/git-config/src/file/section/mod.rs index 8631243a611..72d8b4eaa2e 100644 --- a/git-config/src/file/section/mod.rs +++ b/git-config/src/file/section/mod.rs @@ -1,12 +1,13 @@ use crate::file::{Metadata, Section, SectionMut}; -use crate::parse::section; +use crate::parse::{section, Event}; use crate::{file, parse}; -use bstr::BString; +use bstr::{BString, ByteSlice}; use smallvec::SmallVec; use std::borrow::Cow; use std::ops::Deref; pub(crate) mod body; +use crate::file::write::{extract_newline, platform_newline}; pub use body::{Body, BodyIter}; use git_features::threading::OwnShared; @@ -60,8 +61,60 @@ impl<'a> Section<'a> { /// as it was parsed. pub fn write_to(&self, mut out: impl std::io::Write) -> std::io::Result<()> { self.header.write_to(&mut out)?; - for event in self.body.as_ref() { + + if self.body.0.is_empty() { + return Ok(()); + } + + let nl = self + .body + .as_ref() + .iter() + .find_map(extract_newline) + .unwrap_or_else(|| platform_newline()); + + if self + .body + .0 + .iter() + .take_while(|e| !matches!(e, Event::SectionKey(_))) + .find(|e| e.to_bstr_lossy().contains_str(nl)) + .is_none() + { + out.write_all(nl)?; + } + + let mut saw_newline_after_value = true; + let mut in_key_value_pair = false; + for (idx, event) in self.body.as_ref().iter().enumerate() { + match event { + Event::SectionKey(_) => { + if !saw_newline_after_value { + out.write_all(nl)?; + } + saw_newline_after_value = false; + in_key_value_pair = true; + } + Event::Newline(_) if !in_key_value_pair => { + saw_newline_after_value = true; + } + Event::Value(_) | Event::ValueDone(_) => { + in_key_value_pair = false; + } + _ => {} + } event.write_to(&mut out)?; + if let Event::ValueNotDone(_) = event { + if self + .body + .0 + .get(idx + 1) + .filter(|e| matches!(e, Event::Newline(_))) + .is_none() + { + out.write_all(nl)?; + } + } } Ok(()) } diff --git a/git-config/src/file/write.rs b/git-config/src/file/write.rs index 821192f0335..305fbd3bce8 100644 --- a/git-config/src/file/write.rs +++ b/git-config/src/file/write.rs @@ -1,5 +1,6 @@ -use bstr::{BString, ByteSlice}; +use bstr::{BStr, BString, ByteSlice}; +use crate::parse::Event; use crate::File; impl File<'_> { @@ -23,7 +24,7 @@ impl File<'_> { event.write_to(&mut out)?; } - if !ends_with_newline(self.frontmatter_events.as_ref(), nl) && self.sections.iter().next().is_some() { + if !ends_with_newline(self.frontmatter_events.as_ref(), nl, true) && self.sections.iter().next().is_some() { out.write_all(nl)?; } } @@ -36,7 +37,7 @@ impl File<'_> { let section = self.sections.get(section_id).expect("known section-id"); section.write_to(&mut out)?; - prev_section_ended_with_newline = ends_with_newline(section.body.0.as_ref(), nl); + prev_section_ended_with_newline = ends_with_newline(section.body.0.as_ref(), nl, false); if let Some(post_matter) = self.frontmatter_post_section.get(section_id) { if !prev_section_ended_with_newline { out.write_all(nl)?; @@ -44,7 +45,7 @@ impl File<'_> { for event in post_matter { event.write_to(&mut out)?; } - prev_section_ended_with_newline = ends_with_newline(post_matter, nl); + prev_section_ended_with_newline = ends_with_newline(post_matter, nl, prev_section_ended_with_newline); } } @@ -56,9 +57,9 @@ impl File<'_> { } } -pub(crate) fn ends_with_newline(e: &[crate::parse::Event<'_>], nl: impl AsRef<[u8]>) -> bool { +pub(crate) fn ends_with_newline(e: &[crate::parse::Event<'_>], nl: impl AsRef<[u8]>, default: bool) -> bool { if e.is_empty() { - return true; + return default; } e.iter() .rev() @@ -66,3 +67,14 @@ pub(crate) fn ends_with_newline(e: &[crate::parse::Event<'_>], nl: impl AsRef<[u .find_map(|e| e.to_bstr_lossy().contains_str(nl.as_ref()).then(|| true)) .unwrap_or(false) } + +pub(crate) fn extract_newline<'a, 'b>(e: &'a Event<'b>) -> Option<&'a BStr> { + match e { + Event::Newline(b) => b.as_ref().into(), + _ => None, + } +} + +pub(crate) fn platform_newline() -> &'static BStr { + if cfg!(windows) { "\r\n" } else { "\n" }.into() +} diff --git a/git-config/src/parse/nom/tests.rs b/git-config/src/parse/nom/tests.rs index 4cc0396313c..b19ea16a470 100644 --- a/git-config/src/parse/nom/tests.rs +++ b/git-config/src/parse/nom/tests.rs @@ -396,6 +396,25 @@ mod section { 0 )) ); + + assert_eq!( + section(b"[hello] c\nd", &mut node).unwrap(), + fully_consumed(( + Section { + header: parsed_section_header("hello", None), + events: vec![ + whitespace_event(" "), + name_event("c"), + value_event(""), + newline_event(), + name_event("d"), + value_event("") + ] + .into() + }, + 1 + )) + ); } #[test] diff --git a/git-config/tests/file/write.rs b/git-config/tests/file/write.rs index 1d95774afb8..584ecb95285 100644 --- a/git-config/tests/file/write.rs +++ b/git-config/tests/file/write.rs @@ -1,4 +1,5 @@ use bstr::ByteVec; +use git_config::file::{init, Metadata}; use std::convert::TryFrom; #[test] @@ -62,10 +63,10 @@ fn complex_lossless_roundtrip() { ; more comments # another one - [test "sub-section \"special\" C:\\root"] + [test "sub-section \"special\" C:\\root"] ; section comment bool-explicit = false bool-implicit - integer-no-prefix = 10 + integer-no-prefix = 10 ; a value comment integer-prefix = 10g color = brightgreen red \ bold @@ -89,4 +90,20 @@ fn complex_lossless_roundtrip() { "#; let config = git_config::File::try_from(input).unwrap(); assert_eq!(config.to_bstring(), input); + + let lossy_config = git_config::File::from_bytes_owned( + &mut input.as_bytes().into(), + Metadata::api(), + init::Options { + lossy: true, + ..Default::default() + }, + ) + .unwrap(); + + let lossy_config: git_config::File = lossy_config.to_string().parse().unwrap(); + assert_eq!( + lossy_config, config, + "Even lossy configuration serializes properly to be able to restore all values" + ); } From 627a0e1e12e15a060a70d880ffdfb05f1f7db36c Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 20 Jul 2022 16:59:24 +0800 Subject: [PATCH 293/366] adapt to changes in git-config (#331) --- git-repository/src/config/snapshot.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/git-repository/src/config/snapshot.rs b/git-repository/src/config/snapshot.rs index 8de4e0b88f4..485532e2a63 100644 --- a/git-repository/src/config/snapshot.rs +++ b/git-repository/src/config/snapshot.rs @@ -96,10 +96,6 @@ impl<'repo> Snapshot<'repo> { impl Debug for Snapshot<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - if cfg!(debug_assertions) { - f.write_str(&self.repo.config.resolved.to_string()) - } else { - Debug::fmt(&self.repo.config.resolved, f) - } + f.write_str(&self.repo.config.resolved.to_string()) } } From dac146343a0fbe96b6c0990f4fd4e976e0359a7e Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 20 Jul 2022 17:30:17 +0800 Subject: [PATCH 294/366] an attempt to hack newline handling into place for windows newlines (#331) --- git-config/src/parse/nom/mod.rs | 19 ++++++++++++++----- git-config/src/parse/nom/tests.rs | 12 ++++++++++++ 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index d597dc68cd5..ece10e834b3 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -293,6 +293,7 @@ fn config_value<'a>(i: &'a [u8], receive_event: &mut impl FnMut(Event<'a>)) -> I /// Handles parsing of known-to-be values. This function handles both single /// line values as well as values that are continuations. +// TODO: rewrite this… it's crazy complicated and super hard to work with. fn value_impl<'a>(i: &'a [u8], receive_event: &mut impl FnMut(Event<'a>)) -> IResult<&'a [u8], usize> { let mut parsed_index: usize = 0; let mut offset: usize = 0; @@ -304,18 +305,25 @@ fn value_impl<'a>(i: &'a [u8], receive_event: &mut impl FnMut(Event<'a>)) -> IRe // Used to determine if we return a Value or Value{Not,}Done let mut partial_value_found = false; let mut index: usize = 0; + let mut cr_needs_newline = false; for c in i.iter() { - if was_prev_char_escape_char { + if was_prev_char_escape_char || cr_needs_newline { was_prev_char_escape_char = false; match c { - // We're escaping a newline, which means we've found a - // continuation. + b'\r' if !cr_needs_newline => { + cr_needs_newline = true; + continue; + } b'\n' => { partial_value_found = true; + let local_offset = if cr_needs_newline { 1 } else { 0 }; receive_event(Event::ValueNotDone(Cow::Borrowed(i[offset..index - 1].as_bstr()))); - receive_event(Event::Newline(Cow::Borrowed(i[index..=index].as_bstr()))); - offset = index + 1; + receive_event(Event::Newline(Cow::Borrowed({ + let end = index + local_offset; + i[index..=end].as_bstr() + }))); + offset = index + 1 + local_offset; parsed_index = 0; newlines += 1; } @@ -327,6 +335,7 @@ fn value_impl<'a>(i: &'a [u8], receive_event: &mut impl FnMut(Event<'a>)) -> IRe })); } } + cr_needs_newline = false; } else { match c { b'\n' => { diff --git a/git-config/src/parse/nom/tests.rs b/git-config/src/parse/nom/tests.rs index b19ea16a470..36e660e2677 100644 --- a/git-config/src/parse/nom/tests.rs +++ b/git-config/src/parse/nom/tests.rs @@ -543,6 +543,7 @@ mod section { mod value_continuation { use bstr::ByteSlice; + use crate::parse::tests::util::newline_custom_event; use crate::parse::{ section, tests::util::{into_events, newline_event, value_done_event, value_not_done_event}, @@ -578,6 +579,17 @@ mod value_continuation { value_done_event(" world") ]) ); + + let mut events = section::Events::default(); + assert_eq!(value_impl(b"hello\\\r\n world", &mut events).unwrap().0, b""); + assert_eq!( + events, + into_events(vec![ + value_not_done_event("hello"), + newline_custom_event("\r\n"), + value_done_event(" world") + ]) + ); } #[test] From e4d8fd72f1f648a29e56e487827f2328bfc08d03 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 20 Jul 2022 18:19:40 +0800 Subject: [PATCH 295/366] another failing tests that can't be fixed without a refactor (#331) --- git-config/src/parse/nom/tests.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/git-config/src/parse/nom/tests.rs b/git-config/src/parse/nom/tests.rs index 36e660e2677..cffbc75ffae 100644 --- a/git-config/src/parse/nom/tests.rs +++ b/git-config/src/parse/nom/tests.rs @@ -614,6 +614,7 @@ mod value_continuation { } #[test] + #[ignore] fn quote_split_over_two_lines_with_leftover_comment() { let mut events = section::Events::default(); assert_eq!(value_impl(b"\"\\\n;\";a", &mut events).unwrap().0, b";a"); @@ -625,6 +626,17 @@ mod value_continuation { value_done_event(";\"") ]) ); + + let mut events = section::Events::default(); + assert_eq!(value_impl(b"\"\\\r\n;\";a", &mut events).unwrap().0, b";a"); + assert_eq!( + events, + into_events(vec![ + value_not_done_event("\""), + newline_custom_event("\r\n"), + value_done_event(";\"") + ]) + ); } #[test] From b073e2930bed60ccedadd1709cfaa8889e02ffe3 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 20 Jul 2022 19:33:08 +0800 Subject: [PATCH 296/366] refactor (#331) --- git-config/src/parse/nom/mod.rs | 224 ++++++++++++++++-------------- git-config/src/parse/nom/tests.rs | 43 +++++- 2 files changed, 155 insertions(+), 112 deletions(-) diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index ece10e834b3..e9a18dde5a1 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -17,8 +17,8 @@ use nom::{ use crate::parse::{error::ParseNode, section, Comment, Error, Event}; -/// Attempt to zero-copy parse the provided bytes, passing results to `receive_event`. -pub fn from_bytes<'a>(input: &'a [u8], mut receive_event: impl FnMut(Event<'a>)) -> Result<(), Error> { +/// Attempt to zero-copy parse the provided bytes, passing results to `dispatch`. +pub fn from_bytes<'a>(input: &'a [u8], mut dispatch: impl FnMut(Event<'a>)) -> Result<(), Error> { let bom = unicode_bom::Bom::from(input); let mut newlines = 0; let (i, _) = fold_many0( @@ -31,7 +31,7 @@ pub fn from_bytes<'a>(input: &'a [u8], mut receive_event: impl FnMut(Event<'a>)) }), )), || (), - |_acc, event| receive_event(event), + |_acc, event| dispatch(event), )(&input[bom.len()..]) // I don't think this can panic. many0 errors if the child parser returns // a success where the input was not consumed, but alt will only return Ok @@ -47,7 +47,7 @@ pub fn from_bytes<'a>(input: &'a [u8], mut receive_event: impl FnMut(Event<'a>)) let mut node = ParseNode::SectionHeader; let res = fold_many1( - |i| section(i, &mut node, &mut receive_event), + |i| section(i, &mut node, &mut dispatch), || (), |_acc, additional_newlines| { newlines += additional_newlines; @@ -87,13 +87,9 @@ fn comment(i: &[u8]) -> IResult<&[u8], Comment<'_>> { #[cfg(test)] mod tests; -fn section<'a>( - i: &'a [u8], - node: &mut ParseNode, - receive_event: &mut impl FnMut(Event<'a>), -) -> IResult<&'a [u8], usize> { +fn section<'a>(i: &'a [u8], node: &mut ParseNode, dispatch: &mut impl FnMut(Event<'a>)) -> IResult<&'a [u8], usize> { let (mut i, header) = section_header(i)?; - receive_event(Event::SectionHeader(header)); + dispatch(Event::SectionHeader(header)); let mut newlines = 0; @@ -105,7 +101,7 @@ fn section<'a>( if let Ok((new_i, v)) = take_spaces(i) { if old_i != new_i { i = new_i; - receive_event(Event::Whitespace(Cow::Borrowed(v.as_bstr()))); + dispatch(Event::Whitespace(Cow::Borrowed(v.as_bstr()))); } } @@ -113,11 +109,11 @@ fn section<'a>( if old_i != new_i { i = new_i; newlines += new_newlines; - receive_event(Event::Newline(Cow::Borrowed(v.as_bstr()))); + dispatch(Event::Newline(Cow::Borrowed(v.as_bstr()))); } } - if let Ok((new_i, new_newlines)) = key_value_pair(i, node, receive_event) { + if let Ok((new_i, new_newlines)) = key_value_pair(i, node, dispatch) { if old_i != new_i { i = new_i; newlines += new_newlines; @@ -127,7 +123,7 @@ fn section<'a>( if let Ok((new_i, comment)) = comment(i) { if old_i != new_i { i = new_i; - receive_event(Event::Comment(comment)); + dispatch(Event::Comment(comment)); } } @@ -238,20 +234,20 @@ fn sub_section_delegate<'a>(i: &'a [u8], push_byte: &mut dyn FnMut(u8)) -> IResu fn key_value_pair<'a>( i: &'a [u8], node: &mut ParseNode, - receive_event: &mut impl FnMut(Event<'a>), + dispatch: &mut impl FnMut(Event<'a>), ) -> IResult<&'a [u8], usize> { *node = ParseNode::Name; let (i, name) = config_name(i)?; - receive_event(Event::SectionKey(section::Key(Cow::Borrowed(name)))); + dispatch(Event::SectionKey(section::Key(Cow::Borrowed(name)))); let (i, whitespace) = opt(take_spaces)(i)?; if let Some(whitespace) = whitespace { - receive_event(Event::Whitespace(Cow::Borrowed(whitespace))); + dispatch(Event::Whitespace(Cow::Borrowed(whitespace))); } *node = ParseNode::Value; - let (i, newlines) = config_value(i, receive_event)?; + let (i, newlines) = config_value(i, dispatch)?; Ok((i, newlines)) } @@ -276,125 +272,137 @@ fn config_name(i: &[u8]) -> IResult<&[u8], &BStr> { Ok((i, name.as_bstr())) } -fn config_value<'a>(i: &'a [u8], receive_event: &mut impl FnMut(Event<'a>)) -> IResult<&'a [u8], usize> { +fn config_value<'a>(i: &'a [u8], dispatch: &mut impl FnMut(Event<'a>)) -> IResult<&'a [u8], usize> { if let (i, Some(_)) = opt(char('='))(i)? { - receive_event(Event::KeyValueSeparator); + dispatch(Event::KeyValueSeparator); let (i, whitespace) = opt(take_spaces)(i)?; if let Some(whitespace) = whitespace { - receive_event(Event::Whitespace(Cow::Borrowed(whitespace))); + dispatch(Event::Whitespace(Cow::Borrowed(whitespace))); } - let (i, newlines) = value_impl(i, receive_event)?; + let (i, newlines) = value_impl(i, dispatch)?; Ok((i, newlines)) } else { - receive_event(Event::Value(Cow::Borrowed("".into()))); + dispatch(Event::Value(Cow::Borrowed("".into()))); Ok((i, 0)) } } /// Handles parsing of known-to-be values. This function handles both single /// line values as well as values that are continuations. -// TODO: rewrite this… it's crazy complicated and super hard to work with. -fn value_impl<'a>(i: &'a [u8], receive_event: &mut impl FnMut(Event<'a>)) -> IResult<&'a [u8], usize> { - let mut parsed_index: usize = 0; - let mut offset: usize = 0; - let mut newlines = 0; - - let mut was_prev_char_escape_char = false; - // This is required to ignore comment markers if they're in a quote. - let mut is_in_quotes = false; - // Used to determine if we return a Value or Value{Not,}Done - let mut partial_value_found = false; - let mut index: usize = 0; - let mut cr_needs_newline = false; - - for c in i.iter() { - if was_prev_char_escape_char || cr_needs_newline { - was_prev_char_escape_char = false; - match c { - b'\r' if !cr_needs_newline => { - cr_needs_newline = true; - continue; - } - b'\n' => { - partial_value_found = true; - let local_offset = if cr_needs_newline { 1 } else { 0 }; - receive_event(Event::ValueNotDone(Cow::Borrowed(i[offset..index - 1].as_bstr()))); - receive_event(Event::Newline(Cow::Borrowed({ - let end = index + local_offset; - i[index..=end].as_bstr() - }))); - offset = index + 1 + local_offset; - parsed_index = 0; - newlines += 1; +fn value_impl<'a>(i: &'a [u8], dispatch: &mut impl FnMut(Event<'a>)) -> IResult<&'a [u8], usize> { + let (i, value_end, newlines, mut dispatch) = { + let new_err = |code| nom::Err::Error(NomError { input: i, code }); + let mut value_end = None::; + let mut value_start: usize = 0; + let mut newlines = 0; + + let mut prev_char_was_backslash = false; + // This is required to ignore comment markers if they're in a quote. + let mut is_in_quotes = false; + // Used to determine if we return a Value or Value{Not,}Done + let mut partial_value_found = false; + let mut last_value_index: usize = 0; + let mut prev_c = None; + + let mut bytes = i.iter(); + while let Some(mut c) = bytes.next() { + if prev_char_was_backslash { + prev_char_was_backslash = false; + let mut consumed = 1; + if *c == b'\r' { + c = bytes.next().ok_or(new_err(ErrorKind::Escaped))?; + if *c != b'\n' { + return Err(new_err(ErrorKind::Tag)); + } + consumed += 1; } - b'n' | b't' | b'\\' | b'b' | b'"' => (), - _ => { - return Err(nom::Err::Error(NomError { - input: i, - code: ErrorKind::Escaped, - })); - } - } - cr_needs_newline = false; - } else { - match c { - b'\n' => { - parsed_index = index; - break; + + match c { + b'\n' => { + partial_value_found = true; + let backslash = 1; + dispatch(Event::ValueNotDone(Cow::Borrowed( + i[value_start..last_value_index - backslash].as_bstr(), + ))); + let nl_end = last_value_index + consumed; + dispatch(Event::Newline(Cow::Borrowed(i[last_value_index..nl_end].as_bstr()))); + value_start = nl_end; + value_end = None; + newlines += 1; + } + b'n' | b't' | b'\\' | b'b' | b'"' => (), + _ => { + return Err(new_err(ErrorKind::Escaped)); + } } - b';' | b'#' if !is_in_quotes => { - parsed_index = index; - break; + last_value_index += consumed; + } else { + match c { + b'\n' => { + if prev_c == Some(b'\r') { + last_value_index -= 1; + } + value_end = last_value_index.into(); + break; + } + b';' | b'#' if !is_in_quotes => { + value_end = last_value_index.into(); + break; + } + b'\\' => prev_char_was_backslash = true, + b'"' => is_in_quotes = !is_in_quotes, + _ => { + prev_c = Some(*c); + } } - b'\\' => was_prev_char_escape_char = true, - b'"' => is_in_quotes = !is_in_quotes, - _ => {} + last_value_index += 1; } } - index += 1; - } - if parsed_index == 0 { - if index != 0 { - parsed_index = i.len(); - } else { - // Didn't parse anything at all, newline straight away. - receive_event(Event::Value(Cow::Borrowed("".into()))); - return Ok((&i[0..], newlines)); + if prev_char_was_backslash { + return Err(new_err(ErrorKind::Escaped)); } - } - // Handle incomplete escape - if was_prev_char_escape_char { - return Err(nom::Err::Error(NomError { - input: i, - code: ErrorKind::Escaped, - })); - } + if is_in_quotes { + return Err(new_err(ErrorKind::Tag)); + } - // Handle incomplete quotes - if is_in_quotes { - return Err(nom::Err::Error(NomError { - input: i, - code: ErrorKind::Tag, - })); - } + let value_end = match value_end { + None => { + if last_value_index == 0 { + dispatch(Event::Value(Cow::Borrowed("".into()))); + return Ok((&i[0..], newlines)); + } else { + i.len() + } + } + Some(idx) => idx, + }; + + let dispatch = move |value: &'a [u8]| { + if partial_value_found { + dispatch(Event::ValueDone(Cow::Borrowed(value.as_bstr()))); + } else { + dispatch(Event::Value(Cow::Borrowed(value.as_bstr()))); + } + }; + (&i[value_start..], value_end - value_start, newlines, dispatch) + }; let (i, remainder_value) = { - let new_index = i[offset..parsed_index] + let value_end_no_trailing_whitespace = i[..value_end] .iter() .enumerate() .rev() - .find_map(|(idx, b)| (!b.is_ascii_whitespace()).then(|| offset + idx + 1)) + .find_map(|(idx, b)| (!b.is_ascii_whitespace()).then(|| idx + 1)) .unwrap_or(0); - (&i[new_index..], &i[offset..new_index]) + ( + &i[value_end_no_trailing_whitespace..], + &i[..value_end_no_trailing_whitespace], + ) }; - if partial_value_found { - receive_event(Event::ValueDone(Cow::Borrowed(remainder_value.as_bstr()))); - } else { - receive_event(Event::Value(Cow::Borrowed(remainder_value.as_bstr()))); - } + dispatch(remainder_value); Ok((i, newlines)) } diff --git a/git-config/src/parse/nom/tests.rs b/git-config/src/parse/nom/tests.rs index cffbc75ffae..2aa27aea8ae 100644 --- a/git-config/src/parse/nom/tests.rs +++ b/git-config/src/parse/nom/tests.rs @@ -246,6 +246,25 @@ mod section { 1 )), ); + assert_eq!( + section(b"[a] k = \r\n", &mut node).unwrap(), + fully_consumed(( + Section { + header: parsed_section_header("a", None), + events: vec![ + whitespace_event(" "), + name_event("k"), + whitespace_event(" "), + Event::KeyValueSeparator, + whitespace_event(" "), + value_event(""), + newline_custom_event("\r\n") + ] + .into(), + }, + 1 + )), + ); } #[test] @@ -590,6 +609,12 @@ mod value_continuation { value_done_event(" world") ]) ); + + let mut events = section::Events::default(); + assert!( + value_impl(b"hello\\\r\r\n world", &mut events).is_err(), + "\\r must be followed by \\n" + ); } #[test] @@ -614,7 +639,6 @@ mod value_continuation { } #[test] - #[ignore] fn quote_split_over_two_lines_with_leftover_comment() { let mut events = section::Events::default(); assert_eq!(value_impl(b"\"\\\n;\";a", &mut events).unwrap().0, b";a"); @@ -628,13 +652,13 @@ mod value_continuation { ); let mut events = section::Events::default(); - assert_eq!(value_impl(b"\"\\\r\n;\";a", &mut events).unwrap().0, b";a"); + assert_eq!(value_impl(b"\"a\\\r\nb;\";c", &mut events).unwrap().0, b";c"); assert_eq!( events, into_events(vec![ - value_not_done_event("\""), + value_not_done_event("\"a"), newline_custom_event("\r\n"), - value_done_event(";\"") + value_done_event("b;\"") ]) ); } @@ -714,6 +738,17 @@ mod value_no_continuation { assert_eq!(events, into_events(vec![value_event("hello")])); } + #[test] + fn windows_newline() { + let mut events = section::Events::default(); + assert_eq!(value_impl(b"hi\r\nrest", &mut events).unwrap().0, b"\r\nrest"); + assert_eq!(events, into_events(vec![value_event("hi")])); + + events.clear(); + assert_eq!(value_impl(b"hi\r\r\r\nrest", &mut events).unwrap().0, b"\r\r\r\nrest"); + assert_eq!(events, into_events(vec![value_event("hi")])); + } + #[test] fn no_comment_newline() { let mut events = section::Events::default(); From 15fee74fdfb5fc84349ac103cd5727332f3d2230 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 20 Jul 2022 21:22:18 +0800 Subject: [PATCH 297/366] thanks clippy --- git-config/src/file/section/mod.rs | 7 +++---- git-config/src/parse/nom/mod.rs | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/git-config/src/file/section/mod.rs b/git-config/src/file/section/mod.rs index 72d8b4eaa2e..cbdd21bebba 100644 --- a/git-config/src/file/section/mod.rs +++ b/git-config/src/file/section/mod.rs @@ -73,13 +73,12 @@ impl<'a> Section<'a> { .find_map(extract_newline) .unwrap_or_else(|| platform_newline()); - if self + if !self .body - .0 + .as_ref() .iter() .take_while(|e| !matches!(e, Event::SectionKey(_))) - .find(|e| e.to_bstr_lossy().contains_str(nl)) - .is_none() + .any(|e| e.to_bstr_lossy().contains_str(nl)) { out.write_all(nl)?; } diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index e9a18dde5a1..dbdf43313da 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -310,7 +310,7 @@ fn value_impl<'a>(i: &'a [u8], dispatch: &mut impl FnMut(Event<'a>)) -> IResult< prev_char_was_backslash = false; let mut consumed = 1; if *c == b'\r' { - c = bytes.next().ok_or(new_err(ErrorKind::Escaped))?; + c = bytes.next().ok_or_else(|| new_err(ErrorKind::Escaped))?; if *c != b'\n' { return Err(new_err(ErrorKind::Tag)); } From 15a379a85d59d83f3a0512b9e9fbff1774c9f561 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 20 Jul 2022 21:55:41 +0800 Subject: [PATCH 298/366] thanks fuzzy --- git-config/src/parse/nom/mod.rs | 21 ++++++++++++--------- git-config/tests/parse/from_bytes.rs | 8 ++++++++ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index dbdf43313da..c54a963a34b 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -157,6 +157,12 @@ fn section_header(i: &[u8]) -> IResult<&[u8], section::Header<'_>> { }, }; + if header.name.is_empty() { + return Err(nom::Err::Error(NomError { + input: i, + code: ErrorKind::NoneOf, + })); + } return Ok((i, header)); } @@ -302,7 +308,6 @@ fn value_impl<'a>(i: &'a [u8], dispatch: &mut impl FnMut(Event<'a>)) -> IResult< // Used to determine if we return a Value or Value{Not,}Done let mut partial_value_found = false; let mut last_value_index: usize = 0; - let mut prev_c = None; let mut bytes = i.iter(); while let Some(mut c) = bytes.next() { @@ -329,19 +334,19 @@ fn value_impl<'a>(i: &'a [u8], dispatch: &mut impl FnMut(Event<'a>)) -> IResult< value_start = nl_end; value_end = None; newlines += 1; + + last_value_index += consumed; + } + b'n' | b't' | b'\\' | b'b' | b'"' => { + last_value_index += 1; } - b'n' | b't' | b'\\' | b'b' | b'"' => (), _ => { return Err(new_err(ErrorKind::Escaped)); } } - last_value_index += consumed; } else { match c { b'\n' => { - if prev_c == Some(b'\r') { - last_value_index -= 1; - } value_end = last_value_index.into(); break; } @@ -351,9 +356,7 @@ fn value_impl<'a>(i: &'a [u8], dispatch: &mut impl FnMut(Event<'a>)) -> IResult< } b'\\' => prev_char_was_backslash = true, b'"' => is_in_quotes = !is_in_quotes, - _ => { - prev_c = Some(*c); - } + _ => {} } last_value_index += 1; } diff --git a/git-config/tests/parse/from_bytes.rs b/git-config/tests/parse/from_bytes.rs index 7b24f2b58f3..b12e09d94ff 100644 --- a/git-config/tests/parse/from_bytes.rs +++ b/git-config/tests/parse/from_bytes.rs @@ -2,6 +2,14 @@ use git_config::parse::Events; use super::*; +#[test] +fn fuzz() { + assert!( + Events::from_str("[]A=\\\\\r\\\n\n").is_err(), + "empty sections are not allowed, and it won't crash either" + ); +} + #[test] #[rustfmt::skip] fn complex() { From bd3f4d014dd7df7a1e25defa8eea7253eec1560a Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 20 Jul 2022 22:21:19 +0800 Subject: [PATCH 299/366] move `Env` test utility into `git-testtools` (#331) --- Cargo.lock | 80 ++++++++++++++++++- git-config/src/file/includes.rs | 5 +- git-config/tests/file/init/from_env.rs | 29 +------ .../includes/conditional/gitdir/mod.rs | 7 +- tests/tools/src/lib.rs | 26 ++++++ 5 files changed, 111 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1a5f65bad4a..e8749c27e1e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -943,6 +943,21 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" +[[package]] +name = "futures" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.21" @@ -959,6 +974,17 @@ version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +[[package]] +name = "futures-executor" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.21" @@ -986,6 +1012,29 @@ version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "generic-array" version = "0.14.5" @@ -1100,7 +1149,7 @@ dependencies = [ "nom", "serde", "serde_derive", - "serial_test", + "serial_test 0.7.0", "smallvec", "tempfile", "thiserror", @@ -1467,6 +1516,7 @@ dependencies = [ "git-worktree", "is_ci", "log", + "serial_test 0.8.0", "signal-hook", "tempfile", "thiserror", @@ -2587,7 +2637,20 @@ dependencies = [ "lazy_static", "log", "parking_lot 0.12.1", - "serial_test_derive", + "serial_test_derive 0.7.0", +] + +[[package]] +name = "serial_test" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eec42e7232e5ca56aa59d63af3c7f991fe71ee6a3ddd2d3480834cf3902b007" +dependencies = [ + "futures", + "lazy_static", + "log", + "parking_lot 0.12.1", + "serial_test_derive 0.8.0", ] [[package]] @@ -2603,6 +2666,19 @@ dependencies = [ "syn", ] +[[package]] +name = "serial_test_derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1b95bb2f4f624565e8fe8140c789af7e2082c0e0561b5a82a1b678baa9703dc" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "sha-1" version = "0.10.0" diff --git a/git-config/src/file/includes.rs b/git-config/src/file/includes.rs index b2bd04a7275..b1a2dc0d6b0 100644 --- a/git-config/src/file/includes.rs +++ b/git-config/src/file/includes.rs @@ -325,7 +325,7 @@ mod types { pub conditional: conditional::Context<'a>, } - impl Options<'_> { + impl<'a> Options<'a> { /// Provide options to never follow include directives at all. pub fn no_includes() -> Self { Options { @@ -335,9 +335,6 @@ mod types { conditional: Default::default(), } } - } - - impl<'a> Options<'a> { /// Provide options to follow includes like git does, provided the required `conditional` and `interpolate` contexts /// to support `gitdir` and `onbranch` based `includeIf` directives as well as standard `include.path` resolution. /// Note that the follow-mode is `git`-style, following at most 10 indirections while diff --git a/git-config/tests/file/init/from_env.rs b/git-config/tests/file/init/from_env.rs index a8476ad4796..1b9df74502b 100644 --- a/git-config/tests/file/init/from_env.rs +++ b/git-config/tests/file/init/from_env.rs @@ -1,39 +1,14 @@ -use std::{borrow::Cow, env, fs}; +use std::{borrow::Cow, fs}; use git_config::file::includes; use git_config::file::init; use git_config::{file::init::from_env, File}; +use git_testtools::Env; use serial_test::serial; use tempfile::tempdir; use crate::file::init::from_paths::escape_backslashes; -pub struct Env<'a> { - altered_vars: Vec<&'a str>, -} - -impl<'a> Env<'a> { - pub(crate) fn new() -> Self { - Env { - altered_vars: Vec::new(), - } - } - - pub(crate) fn set(mut self, var: &'a str, value: impl Into) -> Self { - env::set_var(var, value.into()); - self.altered_vars.push(var); - self - } -} - -impl<'a> Drop for Env<'a> { - fn drop(&mut self) { - for var in &self.altered_vars { - env::remove_var(var); - } - } -} - #[test] #[serial] fn empty_without_relevant_environment() { diff --git a/git-config/tests/file/init/from_paths/includes/conditional/gitdir/mod.rs b/git-config/tests/file/init/from_paths/includes/conditional/gitdir/mod.rs index 43ed204ca51..3732798ed7f 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/gitdir/mod.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/gitdir/mod.rs @@ -1,5 +1,6 @@ mod util; +use git_testtools::Env; use serial_test::serial; use util::{assert_section_value, Condition, GitEnv}; @@ -90,7 +91,7 @@ fn dot_slash_from_environment_causes_error() -> crate::Result { let env = GitEnv::repo_name("worktree")?; { - let _environment = crate::file::init::from_env::Env::new() + let _environment = Env::new() .set("GIT_CONFIG_COUNT", "1") .set( "GIT_CONFIG_KEY_0", @@ -112,7 +113,7 @@ fn dot_slash_from_environment_causes_error() -> crate::Result { let absolute_path = escape_backslashes(env.home_dir().join("include.config")); { - let _environment = crate::file::init::from_env::Env::new() + let _environment = Env::new() .set("GIT_CONFIG_COUNT", "1") .set("GIT_CONFIG_KEY_0", "includeIf.gitdir:./worktree/.path") .set("GIT_CONFIG_VALUE_0", &absolute_path); @@ -130,7 +131,7 @@ fn dot_slash_from_environment_causes_error() -> crate::Result { } { - let _environment = crate::file::init::from_env::Env::new() + let _environment = Env::new() .set("GIT_CONFIG_COUNT", "1") .set( "GIT_CONFIG_KEY_0", diff --git a/tests/tools/src/lib.rs b/tests/tools/src/lib.rs index bffdf6454ae..c52d3486e4d 100644 --- a/tests/tools/src/lib.rs +++ b/tests/tools/src/lib.rs @@ -409,3 +409,29 @@ pub fn sleep_forever() -> ! { std::thread::sleep(std::time::Duration::from_secs(u64::MAX)) } } + +pub struct Env<'a> { + altered_vars: Vec<&'a str>, +} + +impl<'a> Env<'a> { + pub fn new() -> Self { + Env { + altered_vars: Vec::new(), + } + } + + pub fn set(mut self, var: &'a str, value: impl Into) -> Self { + std::env::set_var(var, value.into()); + self.altered_vars.push(var); + self + } +} + +impl<'a> Drop for Env<'a> { + fn drop(&mut self) { + for var in &self.altered_vars { + std::env::remove_var(var); + } + } +} From ebedd03e119aa5d46da07e577bfccad621eaecb5 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 20 Jul 2022 23:12:16 +0800 Subject: [PATCH 300/366] feat: repository now initializes global configuration files and resolves includes (#331) --- git-repository/Cargo.toml | 1 + git-repository/src/config/cache.rs | 64 +++++++++++++++++-- git-repository/src/open.rs | 3 +- git-repository/src/repository/permissions.rs | 29 ++++----- .../make_config_repo.tar.xz | 4 +- .../tests/fixtures/make_config_repo.sh | 16 ++++- git-repository/tests/repository/config.rs | 35 +++++++++- 7 files changed, 124 insertions(+), 28 deletions(-) diff --git a/git-repository/Cargo.toml b/git-repository/Cargo.toml index 4adf5161d0c..1d36fbda907 100644 --- a/git-repository/Cargo.toml +++ b/git-repository/Cargo.toml @@ -103,6 +103,7 @@ git-testtools = { path = "../tests/tools" } is_ci = "1.1.1" anyhow = "1" tempfile = "3.2.0" +serial_test = "0.8.0" [package.metadata.docs.rs] features = ["document-features", "max-performance", "one-stop-shop", "unstable", "blocking-network-client"] diff --git a/git-repository/src/config/cache.rs b/git-repository/src/config/cache.rs index 9819542c342..210cba78231 100644 --- a/git-repository/src/config/cache.rs +++ b/git-repository/src/config/cache.rs @@ -1,9 +1,10 @@ use std::{convert::TryFrom, path::PathBuf}; +use crate::repository; use git_config::{Boolean, Integer}; use super::{Cache, Error}; -use crate::{bstr::ByteSlice, permission}; +use crate::bstr::ByteSlice; /// A utility to deal with the cyclic dependency between the ref store and the configuration. The ref-store needs the /// object hash kind, and the configuration needs the current branch name to resolve conditional includes with `onbranch`. @@ -70,7 +71,7 @@ impl StageOne { impl Cache { pub fn from_stage_one( StageOne { - git_dir_config: mut config, + mut git_dir_config, buf: _, is_bare, object_hash, @@ -79,15 +80,17 @@ impl Cache { git_dir: &std::path::Path, branch_name: Option<&git_ref::FullNameRef>, mut filter_config_section: fn(&git_config::file::Metadata) -> bool, - xdg_config_home_env: permission::env_var::Resource, - home_env: permission::env_var::Resource, git_install_dir: Option<&std::path::Path>, + repository::permissions::Environment { + git_prefix, + home: home_env, + xdg_config_home: xdg_config_home_env, + }: repository::permissions::Environment, ) -> Result { let home = std::env::var_os("HOME") .map(PathBuf::from) .and_then(|home| home_env.check(home).ok().flatten()); - // TODO: don't forget to use the canonicalized home for initializing the stacked config. - // like git here: https://github.com/git/git/blob/master/config.c#L208:L208 + let options = git_config::file::init::Options { lossy: !cfg!(debug_assertions), includes: git_config::file::includes::Options::follow( @@ -98,6 +101,55 @@ impl Cache { }, ), }; + + let mut config = { + let home_env = &home_env; + let xdg_config_home_env = &xdg_config_home_env; + let git_prefix = &git_prefix; + let metas = [git_config::source::Kind::System, git_config::source::Kind::Global] + .iter() + .flat_map(|kind| kind.sources()) + .filter_map(|source| { + let path = source + .storage_location(&mut |name| { + match name { + git_ if git_.starts_with("GIT_") => Some(git_prefix), + "XDG_CONFIG_HOME" => Some(xdg_config_home_env), + "HOME" => Some(home_env), + _ => None, + } + .and_then(|perm| std::env::var_os(name).and_then(|val| perm.check(val).ok().flatten())) + }) + .and_then(|p| p.is_file().then(|| p)) // todo: allow it to skip non-existing ones in the options to save check + .map(|p| p.into_owned()); + + git_config::file::Metadata { + path, + source: *source, + level: 0, + trust: git_sec::Trust::Full, + } + .into() + }); + + let no_include_options = git_config::file::init::Options { + includes: git_config::file::includes::Options::no_includes(), + ..options + }; + git_dir_config.resolve_includes(options)?; + match git_config::File::from_paths_metadata(metas, no_include_options).map_err(|err| match err { + git_config::file::init::from_paths::Error::Init(err) => Error::from(err), + git_config::file::init::from_paths::Error::Io(err) => err.into(), + })? { + Some(mut globals) => { + globals.resolve_includes(options)?; + globals.append(git_dir_config); + globals + } + None => git_dir_config, + } + }; + config.resolve_includes(options)?; let excludes_file = config diff --git a/git-repository/src/open.rs b/git-repository/src/open.rs index f2bd0d51402..352d8220d4b 100644 --- a/git-repository/src/open.rs +++ b/git-repository/src/open.rs @@ -285,9 +285,8 @@ impl ThreadSafeRepository { common_dir_ref, head.as_ref().and_then(|head| head.target.try_name()), filter_config_section.unwrap_or(crate::config::section::is_trusted), - env.xdg_config_home.clone(), - env.home.clone(), crate::path::install_dir().ok().as_deref(), + env.clone(), )?; if **git_dir_perm != git_sec::ReadWrite::all() { diff --git a/git-repository/src/repository/permissions.rs b/git-repository/src/repository/permissions.rs index 52dee4393a0..0645b4788bb 100644 --- a/git-repository/src/repository/permissions.rs +++ b/git-repository/src/repository/permissions.rs @@ -26,17 +26,24 @@ pub struct Environment { pub git_prefix: permission::env_var::Resource, } +impl Environment { + /// Allow access to the entire environment. + pub fn allow_all() -> Self { + Environment { + xdg_config_home: Access::resource(git_sec::Permission::Allow), + home: Access::resource(git_sec::Permission::Allow), + git_prefix: Access::resource(git_sec::Permission::Allow), + } + } +} + impl Permissions { /// Return permissions similar to what git does when the repository isn't owned by the current user, /// thus refusing all operations in it. pub fn strict() -> Self { Permissions { git_dir: Access::resource(git_sec::ReadWrite::READ), - env: Environment { - xdg_config_home: Access::resource(git_sec::Permission::Allow), - home: Access::resource(git_sec::Permission::Allow), - git_prefix: Access::resource(git_sec::Permission::Allow), - }, + env: Environment::allow_all(), } } @@ -48,11 +55,7 @@ impl Permissions { pub fn secure() -> Self { Permissions { git_dir: Access::resource(git_sec::ReadWrite::all()), - env: Environment { - xdg_config_home: Access::resource(git_sec::Permission::Allow), - home: Access::resource(git_sec::Permission::Allow), - git_prefix: Access::resource(git_sec::Permission::Allow), - }, + env: Environment::allow_all(), } } @@ -61,11 +64,7 @@ impl Permissions { pub fn all() -> Self { Permissions { git_dir: Access::resource(git_sec::ReadWrite::all()), - env: Environment { - xdg_config_home: Access::resource(git_sec::Permission::Allow), - home: Access::resource(git_sec::Permission::Allow), - git_prefix: Access::resource(git_sec::Permission::Allow), - }, + env: Environment::allow_all(), } } } diff --git a/git-repository/tests/fixtures/generated-archives/make_config_repo.tar.xz b/git-repository/tests/fixtures/generated-archives/make_config_repo.tar.xz index 3e468db3aec..a01be7e4513 100644 --- a/git-repository/tests/fixtures/generated-archives/make_config_repo.tar.xz +++ b/git-repository/tests/fixtures/generated-archives/make_config_repo.tar.xz @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:20b64234f3e8047502160c3c56841b8ddf659798a8b02fc72b5a21bc85dee920 -size 9212 +oid sha256:5c16dcf86219e5dd5755e21472c40d289b246d62fb92594378f166a4920bad58 +size 9276 diff --git a/git-repository/tests/fixtures/make_config_repo.sh b/git-repository/tests/fixtures/make_config_repo.sh index 46ee6267d6f..0438d6a32d0 100644 --- a/git-repository/tests/fixtures/make_config_repo.sh +++ b/git-repository/tests/fixtures/make_config_repo.sh @@ -3,7 +3,7 @@ set -eu -o pipefail git init -q -cat <>.git/config +cat <>.git/config [a] bool = on bad-bool = zero @@ -20,7 +20,19 @@ cat <>.git/config EOF -cat <>a.config +cat <>a.config [a] override = from-a.config EOF +cat <>b.config +[a] + system-override = from-b.config +EOF + +cat <>system.config +[a] + system = from-system.config + system-override = base +[include] + path = ./b.config +EOF diff --git a/git-repository/tests/repository/config.rs b/git-repository/tests/repository/config.rs index c0a102f1a00..ba4265e6b5c 100644 --- a/git-repository/tests/repository/config.rs +++ b/git-repository/tests/repository/config.rs @@ -1,12 +1,37 @@ use crate::named_repo; use git_repository as git; +use git_sec::{Access, Permission}; +use git_testtools::Env; +use serial_test::serial; use std::path::Path; #[test] +#[serial] fn access_values() { for trust in [git_sec::Trust::Full, git_sec::Trust::Reduced] { let repo = named_repo("make_config_repo.sh").unwrap(); - let repo = git::open_opts(repo.git_dir(), repo.open_options().clone().with(trust)).unwrap(); + let _env = Env::new().set( + "GIT_CONFIG_SYSTEM", + repo.work_dir() + .expect("present") + .join("system.config") + .canonicalize() + .unwrap() + .display() + .to_string(), + ); + let repo = git::open_opts( + repo.git_dir(), + repo.open_options().clone().with(trust).permissions(git::Permissions { + env: git::permissions::Environment { + xdg_config_home: Access::resource(Permission::Deny), + home: Access::resource(Permission::Deny), + ..git::permissions::Environment::allow_all() + }, + ..Default::default() + }), + ) + .unwrap(); let config = repo.config_snapshot(); @@ -26,6 +51,14 @@ fn access_values() { ); assert_eq!(config.string("a.override").expect("present").as_ref(), "from-a.config"); + assert_eq!( + config.string("a.system").expect("present").as_ref(), + "from-system.config" + ); + assert_eq!( + config.string("a.system-override").expect("present").as_ref(), + "from-b.config" + ); assert_eq!(config.boolean("core.missing"), None); assert_eq!(config.try_boolean("core.missing"), None); From 49f5a5415c119267ea37e20fb198df80f621cbde Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 20 Jul 2022 23:15:39 +0800 Subject: [PATCH 301/366] thanks clippy --- tests/tools/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/tools/src/lib.rs b/tests/tools/src/lib.rs index c52d3486e4d..4f9e7608214 100644 --- a/tests/tools/src/lib.rs +++ b/tests/tools/src/lib.rs @@ -410,6 +410,7 @@ pub fn sleep_forever() -> ! { } } +#[derive(Default)] pub struct Env<'a> { altered_vars: Vec<&'a str>, } From 95ed219c5f414b6fa96d80eacf297f24d823a4fe Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 20 Jul 2022 23:21:52 +0800 Subject: [PATCH 302/366] refactor (#331) --- git-repository/src/config/cache.rs | 31 ++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/git-repository/src/config/cache.rs b/git-repository/src/config/cache.rs index 210cba78231..de4981f761f 100644 --- a/git-repository/src/config/cache.rs +++ b/git-repository/src/config/cache.rs @@ -132,22 +132,25 @@ impl Cache { .into() }); - let no_include_options = git_config::file::init::Options { - includes: git_config::file::includes::Options::no_includes(), - ..options - }; - git_dir_config.resolve_includes(options)?; - match git_config::File::from_paths_metadata(metas, no_include_options).map_err(|err| match err { + let mut globals = git_config::File::from_paths_metadata( + metas, + git_config::file::init::Options { + includes: git_config::file::includes::Options::no_includes(), + ..options + }, + ) + .map_err(|err| match err { git_config::file::init::from_paths::Error::Init(err) => Error::from(err), git_config::file::init::from_paths::Error::Io(err) => err.into(), - })? { - Some(mut globals) => { - globals.resolve_includes(options)?; - globals.append(git_dir_config); - globals - } - None => git_dir_config, - } + })? + .unwrap_or_default(); + + // TODO: resolve should also work after append, but that needs it to use paths from metadata. + git_dir_config.resolve_includes(options)?; + globals.resolve_includes(options)?; + + globals.append(git_dir_config); + globals }; config.resolve_includes(options)?; From ec21e95f4d9ffac771410947923f27187e88321a Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 20 Jul 2022 23:31:21 +0800 Subject: [PATCH 303/366] refactor (#331) Make use of per-section metadata to make include resolution more powerful and less surprising. --- git-config/src/file/includes.rs | 34 +++++++--------------------- git-config/src/file/init/from_env.rs | 9 ++++---- git-config/src/file/init/mod.rs | 5 ++-- 3 files changed, 14 insertions(+), 34 deletions(-) diff --git a/git-config/src/file/includes.rs b/git-config/src/file/includes.rs index b1a2dc0d6b0..47947364691 100644 --- a/git-config/src/file/includes.rs +++ b/git-config/src/file/includes.rs @@ -27,22 +27,16 @@ impl File<'static> { /// We can fix this by 'splitting' the inlcude section if needed so the included sections are put into the right place. pub fn resolve_includes(&mut self, options: init::Options<'_>) -> Result<(), Error> { let mut buf = Vec::new(); - resolve(self, OwnShared::clone(&self.meta), &mut buf, options) + resolve(self, &mut buf, options) } } -pub(crate) fn resolve( - config: &mut File<'static>, - meta: OwnShared, - buf: &mut Vec, - options: init::Options<'_>, -) -> Result<(), Error> { - resolve_includes_recursive(config, meta, 0, buf, options) +pub(crate) fn resolve(config: &mut File<'static>, buf: &mut Vec, options: init::Options<'_>) -> Result<(), Error> { + resolve_includes_recursive(config, 0, buf, options) } fn resolve_includes_recursive( target_config: &mut File<'static>, - meta: OwnShared, depth: u8, buf: &mut Vec, options: init::Options<'_>, @@ -57,8 +51,6 @@ fn resolve_includes_recursive( }; } - let target_config_path = meta.path.as_deref(); - let mut section_ids_and_include_paths = Vec::new(); for (id, section) in target_config .section_order @@ -71,6 +63,7 @@ fn resolve_includes_recursive( detach_include_paths(&mut section_ids_and_include_paths, section, id) } else if header_name == "includeIf" { if let Some(condition) = &header.subsection_name { + let target_config_path = section.meta.path.as_deref(); if include_condition_match(condition.as_ref(), target_config_path, options.includes)? { detach_include_paths(&mut section_ids_and_include_paths, section, id) } @@ -78,27 +71,19 @@ fn resolve_includes_recursive( } } - append_followed_includes_recursively( - section_ids_and_include_paths, - target_config, - target_config_path, - depth, - meta.clone(), - options, - buf, - ) + append_followed_includes_recursively(section_ids_and_include_paths, target_config, depth, options, buf) } fn append_followed_includes_recursively( section_ids_and_include_paths: Vec<(SectionId, crate::Path<'_>)>, target_config: &mut File<'static>, - target_config_path: Option<&Path>, depth: u8, - meta: OwnShared, options: init::Options<'_>, buf: &mut Vec, ) -> Result<(), Error> { for (section_id, config_path) in section_ids_and_include_paths { + let meta = OwnShared::clone(&target_config.sections[§ion_id].meta); + let target_config_path = meta.path.as_deref(); let config_path = resolve_path(config_path, target_config_path, options.includes.interpolate)?; if !config_path.is_file() { continue; @@ -106,7 +91,6 @@ fn append_followed_includes_recursively( buf.clear(); std::io::copy(&mut std::fs::File::open(&config_path)?, buf)?; - let config_meta = Metadata { path: Some(config_path), trust: meta.trust, @@ -124,9 +108,7 @@ fn append_followed_includes_recursively( init::Error::Interpolate(err) => Error::Interpolate(err), init::Error::Includes(_) => unreachable!("BUG: {:?} not possible due to no-follow options", err), })?; - let config_meta = include_config.meta_owned(); - - resolve_includes_recursive(&mut include_config, config_meta, depth + 1, buf, options)?; + resolve_includes_recursive(&mut include_config, depth + 1, buf, options)?; target_config.append_or_insert(include_config, Some(section_id)); } diff --git a/git-config/src/file/init/from_env.rs b/git-config/src/file/init/from_env.rs index 0f4ffeed6eb..3ef7cf35056 100644 --- a/git-config/src/file/init/from_env.rs +++ b/git-config/src/file/init/from_env.rs @@ -1,4 +1,3 @@ -use git_features::threading::OwnShared; use std::borrow::Cow; use std::convert::TryFrom; @@ -46,13 +45,13 @@ impl File<'static> { return Ok(None); } - let meta = OwnShared::new(file::Metadata { + let meta = file::Metadata { path: None, source: crate::Source::Env, level: 0, trust: git_sec::Trust::Full, - }); - let mut config = File::new(OwnShared::clone(&meta)); + }; + let mut config = File::new(meta); for i in 0..count { let key = env::var(format!("GIT_CONFIG_KEY_{}", i)).map_err(|_| Error::InvalidKeyId { key_id: i })?; let value = env::var_os(format!("GIT_CONFIG_VALUE_{}", i)).ok_or(Error::InvalidValueId { value_id: i })?; @@ -76,7 +75,7 @@ impl File<'static> { } let mut buf = Vec::new(); - init::includes::resolve(&mut config, meta, &mut buf, options)?; + init::includes::resolve(&mut config, &mut buf, options)?; Ok(Some(config)) } } diff --git a/git-config/src/file/init/mod.rs b/git-config/src/file/init/mod.rs index 979445b16af..b49a8f89d62 100644 --- a/git-config/src/file/init/mod.rs +++ b/git-config/src/file/init/mod.rs @@ -71,13 +71,12 @@ impl File<'static> { meta: impl Into>, options: Options<'_>, ) -> Result { - let meta = meta.into(); let mut config = Self::from_parse_events_no_includes( parse::Events::from_bytes_owned(input_and_buf, options.to_event_filter()).map_err(Error::from)?, - OwnShared::clone(&meta), + meta, ); - includes::resolve(&mut config, meta, input_and_buf, options).map_err(Error::from)?; + includes::resolve(&mut config, input_and_buf, options).map_err(Error::from)?; Ok(config) } } From 14ba8834b8738817d2bfb0ca66d1fb86fc8f3075 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 20 Jul 2022 23:31:54 +0800 Subject: [PATCH 304/366] adapt to changes in git-config (#331) --- git-repository/src/config/cache.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/git-repository/src/config/cache.rs b/git-repository/src/config/cache.rs index de4981f761f..e28defdaf09 100644 --- a/git-repository/src/config/cache.rs +++ b/git-repository/src/config/cache.rs @@ -71,7 +71,7 @@ impl StageOne { impl Cache { pub fn from_stage_one( StageOne { - mut git_dir_config, + git_dir_config, buf: _, is_bare, object_hash, @@ -102,7 +102,7 @@ impl Cache { ), }; - let mut config = { + let config = { let home_env = &home_env; let xdg_config_home_env = &xdg_config_home_env; let git_prefix = &git_prefix; @@ -145,16 +145,11 @@ impl Cache { })? .unwrap_or_default(); - // TODO: resolve should also work after append, but that needs it to use paths from metadata. - git_dir_config.resolve_includes(options)?; - globals.resolve_includes(options)?; - globals.append(git_dir_config); + globals.resolve_includes(options)?; globals }; - config.resolve_includes(options)?; - let excludes_file = config .path_filter("core", None, "excludesFile", &mut filter_config_section) .map(|p| p.interpolate(options.includes.interpolate).map(|p| p.into_owned())) From acb4520a88ab083640c80a7f23a56a2ca3cda335 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 21 Jul 2022 09:30:07 +0800 Subject: [PATCH 305/366] Add a way to load multiple configuration files without allocating a read buffer (#331) --- git-config/src/file/init/from_paths.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/git-config/src/file/init/from_paths.rs b/git-config/src/file/init/from_paths.rs index ef2a7f691a4..71468e25b66 100644 --- a/git-config/src/file/init/from_paths.rs +++ b/git-config/src/file/init/from_paths.rs @@ -39,8 +39,18 @@ impl File<'static> { path_meta: impl IntoIterator>, options: Options<'_>, ) -> Result, Error> { - let mut target = None; let mut buf = Vec::with_capacity(512); + Self::from_paths_metadata_buf(path_meta, &mut buf, options) + } + + /// Like [from_paths_metadata()][Self::from_paths_metadata()], but will use `buf` to temporarily store the config file + /// contents for parsing instead of allocating an own buffer. + pub fn from_paths_metadata_buf( + path_meta: impl IntoIterator>, + buf: &mut Vec, + options: Options<'_>, + ) -> Result, Error> { + let mut target = None; let mut seen = BTreeSet::default(); for (path, mut meta) in path_meta.into_iter().filter_map(|meta| { let mut meta = meta.into(); @@ -51,10 +61,10 @@ impl File<'static> { } buf.clear(); - std::io::copy(&mut std::fs::File::open(&path)?, &mut buf)?; + std::io::copy(&mut std::fs::File::open(&path)?, buf)?; meta.path = Some(path); - let config = Self::from_bytes_owned(&mut buf, meta, options)?; + let config = Self::from_bytes_owned(buf, meta, options)?; match &mut target { None => { target = Some(config); From e9afedeebafb70d81a8fa2e6dc320b387e6ee926 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 21 Jul 2022 09:31:36 +0800 Subject: [PATCH 306/366] adjust to changes in `git-config` for greater efficiency (#331) --- git-repository/src/config/cache.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/git-repository/src/config/cache.rs b/git-repository/src/config/cache.rs index e28defdaf09..952ac7f3ed3 100644 --- a/git-repository/src/config/cache.rs +++ b/git-repository/src/config/cache.rs @@ -72,7 +72,7 @@ impl Cache { pub fn from_stage_one( StageOne { git_dir_config, - buf: _, + mut buf, is_bare, object_hash, reflog: _, @@ -132,8 +132,9 @@ impl Cache { .into() }); - let mut globals = git_config::File::from_paths_metadata( + let mut globals = git_config::File::from_paths_metadata_buf( metas, + &mut buf, git_config::file::init::Options { includes: git_config::file::includes::Options::no_includes(), ..options From f9ce1b5411f1ac788f71060ecf785dda9dfd87bf Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 21 Jul 2022 10:31:40 +0800 Subject: [PATCH 307/366] feat: `File::from_git_dir()` as comfortable way to instantiate most complete git configuration. (#331) --- git-config/src/file/includes.rs | 4 +- git-config/src/file/init/comfort.rs | 68 +++++++++++++++++-- git-config/src/values/path.rs | 12 +++- git-config/tests/file/init/comfort.rs | 59 ++++++++++++++++ .../fixtures/generated-archives/.gitignore | 1 + git-config/tests/fixtures/make_config_repo.sh | 50 ++++++++++++++ 6 files changed, 187 insertions(+), 7 deletions(-) create mode 100644 git-config/tests/fixtures/generated-archives/.gitignore create mode 100644 git-config/tests/fixtures/make_config_repo.sh diff --git a/git-config/src/file/includes.rs b/git-config/src/file/includes.rs index 47947364691..1dd36f0d404 100644 --- a/git-config/src/file/includes.rs +++ b/git-config/src/file/includes.rs @@ -309,7 +309,7 @@ mod types { impl<'a> Options<'a> { /// Provide options to never follow include directives at all. - pub fn no_includes() -> Self { + pub fn no_follow() -> Self { Options { max_depth: 0, error_on_max_depth_exceeded: false, @@ -357,7 +357,7 @@ mod types { impl Default for Options<'_> { fn default() -> Self { - Self::no_includes() + Self::no_follow() } } diff --git a/git-config/src/file/init/comfort.rs b/git-config/src/file/init/comfort.rs index 9454d74d6a0..db671a82fdb 100644 --- a/git-config/src/file/init/comfort.rs +++ b/git-config/src/file/init/comfort.rs @@ -1,5 +1,5 @@ use crate::file::{init, Metadata}; -use crate::{source, File}; +use crate::{path, source, File, Source}; use std::path::PathBuf; /// Easy-instantiation of typical non-repository git configuration files with all configuration defaulting to typical values. @@ -68,8 +68,68 @@ impl File<'static> { /// An easy way to provide complete configuration for a repository. impl File<'static> { - /// TODO - pub fn from_git_dir(_dir: impl AsRef) -> Result, init::from_paths::Error> { - todo!() + /// This configuration type includes the following sources, in order of precedence: + /// + /// - globals + /// - repository-local by loading `dir`/config + /// - environment + /// + /// Note that `dir` is the `.git` dir to load the configuration from, not the configuration file. + /// + /// Includes will be resolved within limits as some information like the git installation directory is missing to interpolate + /// paths with as well as git repository information like the branch name. + pub fn from_git_dir(dir: impl Into) -> Result, from_git_dir::Error> { + let (mut local, git_dir) = { + let source = Source::Local; + let mut path = dir.into(); + path.push( + source + .storage_location(&mut |n| std::env::var_os(n)) + .expect("location available for local"), + ); + let local = Self::from_path_no_includes(&path, source)?; + path.pop(); + (local, path) + }; + + let home = std::env::var("HOME").ok().map(PathBuf::from); + let options = init::Options { + includes: init::includes::Options::follow( + path::interpolate::Context { + home_dir: home.as_deref(), + ..Default::default() + }, + init::includes::conditional::Context { + git_dir: Some(git_dir.as_ref()), + branch_name: None, + }, + ), + lossy: false, + }; + + let mut globals = Self::new_globals()?; + globals.resolve_includes(options)?; + local.resolve_includes(options)?; + + globals.append(local).append(Self::new_environment_overrides()?); + Ok(globals) + } +} + +/// +pub mod from_git_dir { + use crate::file::init; + + /// The error returned by [`File::from_git_dir()`][crate::File::from_git_dir()]. + #[derive(Debug, thiserror::Error)] + pub enum Error { + #[error(transparent)] + FromPaths(#[from] init::from_paths::Error), + #[error(transparent)] + FromEnv(#[from] init::from_env::Error), + #[error(transparent)] + Init(#[from] init::Error), + #[error(transparent)] + Includes(#[from] init::includes::Error), } } diff --git a/git-config/src/values/path.rs b/git-config/src/values/path.rs index 95bfdc67bfe..3833efdf271 100644 --- a/git-config/src/values/path.rs +++ b/git-config/src/values/path.rs @@ -9,7 +9,7 @@ pub mod interpolate { use std::path::PathBuf; /// Options for interpolating paths with [`Path::interpolate()`][crate::Path::interpolate()]. - #[derive(Clone, Copy, Default)] + #[derive(Clone, Copy)] pub struct Context<'a> { /// The location where gitoxide or git is installed. If `None`, `%(prefix)` in paths will cause an error. pub git_install_dir: Option<&'a std::path::Path>, @@ -19,6 +19,16 @@ pub mod interpolate { pub home_for_user: Option Option>, } + impl Default for Context<'_> { + fn default() -> Self { + Context { + git_install_dir: None, + home_dir: None, + home_for_user: Some(home_for_user), + } + } + } + /// The error returned by [`Path::interpolate()`][crate::Path::interpolate()]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] diff --git a/git-config/tests/file/init/comfort.rs b/git-config/tests/file/init/comfort.rs index 7d3cf8ea674..88c74f6b942 100644 --- a/git-config/tests/file/init/comfort.rs +++ b/git-config/tests/file/init/comfort.rs @@ -1,4 +1,5 @@ use git_config::source; +use git_testtools::Env; use serial_test::serial; #[test] @@ -16,3 +17,61 @@ fn new_environment_overrides() { let config = git_config::File::new_environment_overrides().unwrap(); assert!(config.is_void()); } + +#[test] +#[serial] +fn from_git_dir() -> crate::Result { + let worktree_dir = git_testtools::scripted_fixture_repo_read_only("make_config_repo.sh")?; + let git_dir = worktree_dir.join(".git"); + let worktree_dir = worktree_dir.canonicalize()?; + let _env = Env::new() + .set( + "GIT_CONFIG_SYSTEM", + worktree_dir.join("system.config").display().to_string(), + ) + .set("HOME", worktree_dir.display().to_string()) + .set("GIT_CONFIG_COUNT", "1") + .set("GIT_CONFIG_KEY_0", "include.path") + .set( + "GIT_CONFIG_VALUE_0", + worktree_dir.join("c.config").display().to_string(), + ); + + let config = git_config::File::from_git_dir(git_dir)?; + assert_eq!( + config.string("a", None, "local").expect("present").as_ref(), + "value", + "a value from the local repo configuration" + ); + assert_eq!( + config.string("a", None, "local-include").expect("present").as_ref(), + "from-a.config", + "an override from a local repo include" + ); + assert_eq!( + config.string("a", None, "system").expect("present").as_ref(), + "from-system.config", + "system configuration can be overridden with GIT_CONFIG_SYSTEM" + ); + assert_eq!( + config.string("a", None, "system-override").expect("present").as_ref(), + "from-b.config", + "globals resolve their includes" + ); + assert_eq!( + config.string("a", None, "user").expect("present").as_ref(), + "from-user.config", + "per-user configuration" + ); + assert_eq!( + config.string("a", None, "git").expect("present").as_ref(), + "git-application", + "we load the XDG directories, based on the HOME fallback" + ); + assert_eq!( + config.string("env", None, "override").expect("present").as_ref(), + "from-c.config", + "environment includes are resolved" + ); + Ok(()) +} diff --git a/git-config/tests/fixtures/generated-archives/.gitignore b/git-config/tests/fixtures/generated-archives/.gitignore new file mode 100644 index 00000000000..aca5621b76a --- /dev/null +++ b/git-config/tests/fixtures/generated-archives/.gitignore @@ -0,0 +1 @@ +/make_config_repo.tar.xz diff --git a/git-config/tests/fixtures/make_config_repo.sh b/git-config/tests/fixtures/make_config_repo.sh new file mode 100644 index 00000000000..d4d3deb49d9 --- /dev/null +++ b/git-config/tests/fixtures/make_config_repo.sh @@ -0,0 +1,50 @@ +#!/bin/bash +set -eu -o pipefail + +git init -q + +cat <>.git/config +[a] + local = value + +[include] + path = ../a.config +EOF + + +cat <>a.config +[a] + local-include = from-a.config +EOF + +cat <>system.config +[a] + system = from-system.config + system-override = base +[include] + path = ./b.config +EOF + +cat <>.gitconfig +[a] + user = from-user.config +EOF + +cat <>b.config +[a] + system-override = from-b.config +EOF + +cat <>c.config +[env] + override = from-c.config +EOF + +mkdir -p .config/git +( + cd .config/git + cat <>config + [a] + git = git-application +EOF +) From 3c57344325ad20ae891824cd8791d2d17f4148e5 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 21 Jul 2022 10:33:05 +0800 Subject: [PATCH 308/366] adapt to changes in `git-config` (#331) --- git-repository/src/config/cache.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git-repository/src/config/cache.rs b/git-repository/src/config/cache.rs index 952ac7f3ed3..9f13cb6a478 100644 --- a/git-repository/src/config/cache.rs +++ b/git-repository/src/config/cache.rs @@ -32,7 +32,7 @@ impl StageOne { .with(git_dir_trust), git_config::file::init::Options { lossy: !cfg!(debug_assertions), - includes: git_config::file::includes::Options::no_includes(), + includes: git_config::file::includes::Options::no_follow(), }, )? }; @@ -136,7 +136,7 @@ impl Cache { metas, &mut buf, git_config::file::init::Options { - includes: git_config::file::includes::Options::no_includes(), + includes: git_config::file::includes::Options::no_follow(), ..options }, ) From 989603efcdf0064e2bb7d48100391cabc810204d Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 21 Jul 2022 10:53:54 +0800 Subject: [PATCH 309/366] Allow to skip non-existing input paths without error (#331) That way it's possible for the caller to avoid having to check for existence themselves. --- git-config/src/file/init/from_paths.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/git-config/src/file/init/from_paths.rs b/git-config/src/file/init/from_paths.rs index 71468e25b66..1cc44abffe9 100644 --- a/git-config/src/file/init/from_paths.rs +++ b/git-config/src/file/init/from_paths.rs @@ -35,19 +35,25 @@ impl File<'static> { /// Constructs a `git-config` file from the provided metadata, which must include a path to read from or be ignored. /// Returns `Ok(None)` if there was not a single input path provided, which is a possibility due to /// [`Metadata::path`] being an `Option`. + /// If an input path doesn't exist, the entire operation will abort. See [`from_paths_metadata_buf()`][Self::from_paths_metadata_buf()] + /// for a more powerful version of this method. pub fn from_paths_metadata( path_meta: impl IntoIterator>, options: Options<'_>, ) -> Result, Error> { let mut buf = Vec::with_capacity(512); - Self::from_paths_metadata_buf(path_meta, &mut buf, options) + let err_on_nonexisting_paths = true; + Self::from_paths_metadata_buf(path_meta, &mut buf, err_on_nonexisting_paths, options) } /// Like [from_paths_metadata()][Self::from_paths_metadata()], but will use `buf` to temporarily store the config file /// contents for parsing instead of allocating an own buffer. + /// + /// If `err_on_nonexisting_paths` is false, instead of aborting with error, we will continue to the next path instead. pub fn from_paths_metadata_buf( path_meta: impl IntoIterator>, buf: &mut Vec, + err_on_non_existing_paths: bool, options: Options<'_>, ) -> Result, Error> { let mut target = None; @@ -61,7 +67,14 @@ impl File<'static> { } buf.clear(); - std::io::copy(&mut std::fs::File::open(&path)?, buf)?; + std::io::copy( + &mut match std::fs::File::open(&path) { + Ok(f) => f, + Err(err) if !err_on_non_existing_paths && err.kind() == std::io::ErrorKind::NotFound => continue, + Err(err) => return Err(err.into()), + }, + buf, + )?; meta.path = Some(path); let config = Self::from_bytes_owned(buf, meta, options)?; From b52b5407638adef2216aeb4215a7c0437d6ee2d5 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 21 Jul 2022 10:56:40 +0800 Subject: [PATCH 310/366] adapt to changes in `git-config` (#331) --- git-repository/src/config/cache.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/git-repository/src/config/cache.rs b/git-repository/src/config/cache.rs index 9f13cb6a478..97d24fba28a 100644 --- a/git-repository/src/config/cache.rs +++ b/git-repository/src/config/cache.rs @@ -120,7 +120,6 @@ impl Cache { } .and_then(|perm| std::env::var_os(name).and_then(|val| perm.check(val).ok().flatten())) }) - .and_then(|p| p.is_file().then(|| p)) // todo: allow it to skip non-existing ones in the options to save check .map(|p| p.into_owned()); git_config::file::Metadata { @@ -132,9 +131,11 @@ impl Cache { .into() }); + let err_on_nonexisting_paths = false; let mut globals = git_config::File::from_paths_metadata_buf( metas, &mut buf, + err_on_nonexisting_paths, git_config::file::init::Options { includes: git_config::file::includes::Options::no_follow(), ..options From faaf791cc960c37b180ddef9792dfabc7d106138 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 22 Jul 2022 09:32:40 +0800 Subject: [PATCH 311/366] make obvious what plumbing and porcelain really are (#331) --- Cargo.toml | 4 ++-- src/{porcelain-cli.rs => ein.rs} | 0 src/{plumbing-cli.rs => gix.rs} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename src/{porcelain-cli.rs => ein.rs} (100%) rename src/{plumbing-cli.rs => gix.rs} (100%) diff --git a/Cargo.toml b/Cargo.toml index 78b4a8d224b..25c5780d593 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,14 +13,14 @@ resolver = "2" [[bin]] name = "ein" -path = "src/porcelain-cli.rs" +path = "src/ein.rs" test = false doctest = false [[bin]] name = "gix" -path = "src/plumbing-cli.rs" +path = "src/gix.rs" test = false doctest = false diff --git a/src/porcelain-cli.rs b/src/ein.rs similarity index 100% rename from src/porcelain-cli.rs rename to src/ein.rs diff --git a/src/plumbing-cli.rs b/src/gix.rs similarity index 100% rename from src/plumbing-cli.rs rename to src/gix.rs From f4e1810fb711d57778be79c88f49aa583821abab Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 22 Jul 2022 09:46:08 +0800 Subject: [PATCH 312/366] first step towards moving all repository-commands one level up. (#331) --- src/plumbing/main.rs | 351 ++++++++++++++++++++-------------------- src/plumbing/options.rs | 13 +- 2 files changed, 181 insertions(+), 183 deletions(-) diff --git a/src/plumbing/main.rs b/src/plumbing/main.rs index b30a19098c1..1f399b6ffa3 100644 --- a/src/plumbing/main.rs +++ b/src/plumbing/main.rs @@ -53,6 +53,9 @@ pub fn main() -> Result<()> { let format = args.format; let cmd = args.cmd; let object_hash = args.object_hash; + use git_repository as git; + let repository = args.repository; + let repository = move || git::ThreadSafeRepository::discover(repository); let progress; let progress_keep_open; @@ -156,191 +159,187 @@ pub fn main() -> Result<()> { }, ), }, - Subcommands::Repository(repo::Platform { repository, cmd }) => { - use git_repository as git; - let repository = git::ThreadSafeRepository::discover(repository)?; - match cmd { - repo::Subcommands::Revision { cmd } => match cmd { - repo::revision::Subcommands::Explain { spec } => prepare_and_run( - "repository-commit-describe", - verbose, - progress, - progress_keep_open, - None, - move |_progress, out, _err| core::repository::revision::explain(repository.into(), spec, out), - ), - }, - repo::Subcommands::Commit { cmd } => match cmd { - repo::commit::Subcommands::Describe { - annotated_tags, - all_refs, - first_parent, - always, - long, - statistics, - max_candidates, - rev_spec, - } => prepare_and_run( - "repository-commit-describe", - verbose, - progress, - progress_keep_open, - None, - move |_progress, out, err| { - core::repository::commit::describe( - repository.into(), - rev_spec.as_deref(), - out, - err, - core::repository::commit::describe::Options { - all_tags: !annotated_tags, - all_refs, - long_format: long, - first_parent, - statistics, - max_candidates, - always, - }, - ) - }, - ), - }, - repo::Subcommands::Exclude { cmd } => match cmd { - repo::exclude::Subcommands::Query { - patterns, - pathspecs, - show_ignore_patterns, - } => prepare_and_run( - "repository-exclude-query", - verbose, - progress, - progress_keep_open, - None, - move |_progress, out, _err| { - use git::bstr::ByteSlice; - core::repository::exclude::query( - repository.into(), - if pathspecs.is_empty() { - Box::new( - stdin_or_bail()? - .byte_lines() - .filter_map(Result::ok) - .filter_map(|line| git::path::Spec::from_bytes(line.as_bstr())), - ) as Box> - } else { - Box::new(pathspecs.into_iter()) - }, - out, - core::repository::exclude::query::Options { - format, - show_ignore_patterns, - overrides: patterns, - }, - ) - }, - ), - }, - repo::Subcommands::Mailmap { cmd } => match cmd { - repo::mailmap::Subcommands::Entries => prepare_and_run( - "repository-mailmap-entries", - verbose, - progress, - progress_keep_open, - None, - move |_progress, out, err| { - core::repository::mailmap::entries(repository.into(), format, out, err) - }, - ), - }, - repo::Subcommands::Odb { cmd } => match cmd { - repo::odb::Subcommands::Entries => prepare_and_run( - "repository-odb-entries", - verbose, - progress, - progress_keep_open, - None, - move |_progress, out, _err| core::repository::odb::entries(repository.into(), format, out), - ), - repo::odb::Subcommands::Info => prepare_and_run( - "repository-odb-info", - verbose, - progress, - progress_keep_open, - None, - move |_progress, out, err| core::repository::odb::info(repository.into(), format, out, err), - ), - }, - repo::Subcommands::Tree { cmd } => match cmd { - repo::tree::Subcommands::Entries { - treeish, - recursive, - extended, - } => prepare_and_run( - "repository-tree-entries", - verbose, - progress, - progress_keep_open, - None, - move |_progress, out, _err| { - core::repository::tree::entries( - repository.into(), - treeish.as_deref(), - recursive, - extended, - format, - out, - ) - }, - ), - repo::tree::Subcommands::Info { treeish, extended } => prepare_and_run( - "repository-tree-info", - verbose, - progress, - progress_keep_open, - None, - move |_progress, out, err| { - core::repository::tree::info( - repository.into(), - treeish.as_deref(), - extended, - format, - out, - err, - ) - }, - ), - }, - repo::Subcommands::Verify { - args: - pack::VerifyOptions { - statistics, - algorithm, - decode, - re_encode, - }, + Subcommands::Repository(repo::Platform { cmd }) => match cmd { + repo::Subcommands::Revision { cmd } => match cmd { + repo::revision::Subcommands::Explain { spec } => prepare_and_run( + "repository-commit-describe", + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, _err| core::repository::revision::explain(repository()?.into(), spec, out), + ), + }, + repo::Subcommands::Commit { cmd } => match cmd { + repo::commit::Subcommands::Describe { + annotated_tags, + all_refs, + first_parent, + always, + long, + statistics, + max_candidates, + rev_spec, } => prepare_and_run( - "repository-verify", + "repository-commit-describe", verbose, progress, progress_keep_open, - core::repository::verify::PROGRESS_RANGE, - move |progress, out, _err| { - core::repository::verify::integrity( - repository.into(), + None, + move |_progress, out, err| { + core::repository::commit::describe( + repository()?.into(), + rev_spec.as_deref(), out, - progress, - &should_interrupt, - core::repository::verify::Context { - output_statistics: statistics.then(|| format), - algorithm, - verify_mode: verify_mode(decode, re_encode), - thread_limit, + err, + core::repository::commit::describe::Options { + all_tags: !annotated_tags, + all_refs, + long_format: long, + first_parent, + statistics, + max_candidates, + always, }, ) }, ), - } - } + }, + repo::Subcommands::Exclude { cmd } => match cmd { + repo::exclude::Subcommands::Query { + patterns, + pathspecs, + show_ignore_patterns, + } => prepare_and_run( + "repository-exclude-query", + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, _err| { + use git::bstr::ByteSlice; + core::repository::exclude::query( + repository()?.into(), + if pathspecs.is_empty() { + Box::new( + stdin_or_bail()? + .byte_lines() + .filter_map(Result::ok) + .filter_map(|line| git::path::Spec::from_bytes(line.as_bstr())), + ) as Box> + } else { + Box::new(pathspecs.into_iter()) + }, + out, + core::repository::exclude::query::Options { + format, + show_ignore_patterns, + overrides: patterns, + }, + ) + }, + ), + }, + repo::Subcommands::Mailmap { cmd } => match cmd { + repo::mailmap::Subcommands::Entries => prepare_and_run( + "repository-mailmap-entries", + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, err| { + core::repository::mailmap::entries(repository()?.into(), format, out, err) + }, + ), + }, + repo::Subcommands::Odb { cmd } => match cmd { + repo::odb::Subcommands::Entries => prepare_and_run( + "repository-odb-entries", + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, _err| core::repository::odb::entries(repository()?.into(), format, out), + ), + repo::odb::Subcommands::Info => prepare_and_run( + "repository-odb-info", + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, err| core::repository::odb::info(repository()?.into(), format, out, err), + ), + }, + repo::Subcommands::Tree { cmd } => match cmd { + repo::tree::Subcommands::Entries { + treeish, + recursive, + extended, + } => prepare_and_run( + "repository-tree-entries", + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, _err| { + core::repository::tree::entries( + repository()?.into(), + treeish.as_deref(), + recursive, + extended, + format, + out, + ) + }, + ), + repo::tree::Subcommands::Info { treeish, extended } => prepare_and_run( + "repository-tree-info", + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, err| { + core::repository::tree::info( + repository()?.into(), + treeish.as_deref(), + extended, + format, + out, + err, + ) + }, + ), + }, + repo::Subcommands::Verify { + args: + pack::VerifyOptions { + statistics, + algorithm, + decode, + re_encode, + }, + } => prepare_and_run( + "repository-verify", + verbose, + progress, + progress_keep_open, + core::repository::verify::PROGRESS_RANGE, + move |progress, out, _err| { + core::repository::verify::integrity( + repository()?.into(), + out, + progress, + &should_interrupt, + core::repository::verify::Context { + output_statistics: statistics.then(|| format), + algorithm, + verify_mode: verify_mode(decode, re_encode), + thread_limit, + }, + ) + }, + ), + }, Subcommands::Pack(subcommands) => match subcommands { pack::Subcommands::Create { repository, diff --git a/src/plumbing/options.rs b/src/plumbing/options.rs index 7a0f73b3e46..bc56f915c4c 100644 --- a/src/plumbing/options.rs +++ b/src/plumbing/options.rs @@ -1,10 +1,15 @@ use gitoxide_core as core; +use std::path::PathBuf; #[derive(Debug, clap::Parser)] #[clap(name = "gix-plumbing", about = "The git underworld", version = clap::crate_version!())] #[clap(subcommand_required = true)] #[clap(arg_required_else_help = true)] pub struct Args { + /// The repository to access. + #[clap(short = 'r', long, default_value = ".")] + pub repository: PathBuf, + #[clap(long, short = 't')] /// The amount of threads to use for some operations. /// @@ -49,7 +54,7 @@ pub enum Subcommands { /// Subcommands for interacting with packs and their indices. #[clap(subcommand)] Pack(pack::Subcommands), - /// Subcommands for interacting with git remotes, e.g. git repositories hosted on servers. + /// Subcommands for interacting with git remote server. #[clap(subcommand)] #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] Remote(remote::Subcommands), @@ -329,14 +334,8 @@ pub mod pack { /// pub mod repo { - use std::path::PathBuf; - #[derive(Debug, clap::Parser)] pub struct Platform { - /// The repository to access. - #[clap(short = 'r', long, default_value = ".")] - pub repository: PathBuf, - /// Subcommands #[clap(subcommand)] pub cmd: Subcommands, From 141c5f1145f9d3864e2d879089c66c62f38a2b5d Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 22 Jul 2022 10:14:08 +0800 Subject: [PATCH 313/366] migrate mailmap to the new 'free' section (#331) --- src/plumbing/main.rs | 24 ++++++++-------- src/plumbing/options.rs | 61 +++++++++++++++++++++++++---------------- 2 files changed, 50 insertions(+), 35 deletions(-) diff --git a/src/plumbing/main.rs b/src/plumbing/main.rs index 1f399b6ffa3..fe1928f67c8 100644 --- a/src/plumbing/main.rs +++ b/src/plumbing/main.rs @@ -16,7 +16,7 @@ use gitoxide_core::pack::verify; #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] use crate::plumbing::options::remote; use crate::{ - plumbing::options::{commitgraph, index, mailmap, pack, pack::multi_index, repo, Args, Subcommands}, + plumbing::options::{commitgraph, free, index, pack, pack::multi_index, repo, Args, Subcommands}, shared::pretty::prepare_and_run, }; @@ -77,16 +77,6 @@ pub fn main() -> Result<()> { })?; match cmd { - Subcommands::Mailmap(mailmap::Platform { path, cmd }) => match cmd { - mailmap::Subcommands::Verify => prepare_and_run( - "mailmap-verify", - verbose, - progress, - progress_keep_open, - core::mailmap::PROGRESS_RANGE, - move |_progress, out, _err| core::mailmap::verify(path, format, out), - ), - }, Subcommands::Index(index::Platform { object_hash, index_path, @@ -159,6 +149,18 @@ pub fn main() -> Result<()> { }, ), }, + Subcommands::Free(free::Subcommands::Mailmap { cmd }) => match cmd { + free::mailmap::Platform { path, cmd } => match cmd { + free::mailmap::Subcommands::Verify => prepare_and_run( + "mailmap-verify", + verbose, + progress, + progress_keep_open, + core::mailmap::PROGRESS_RANGE, + move |_progress, out, _err| core::mailmap::verify(path, format, out), + ), + }, + }, Subcommands::Repository(repo::Platform { cmd }) => match cmd { repo::Subcommands::Revision { cmd } => match cmd { repo::revision::Subcommands::Explain { spec } => prepare_and_run( diff --git a/src/plumbing/options.rs b/src/plumbing/options.rs index bc56f915c4c..68a45851e9e 100644 --- a/src/plumbing/options.rs +++ b/src/plumbing/options.rs @@ -65,8 +65,9 @@ pub enum Subcommands { Index(index::Platform), /// Subcommands for interacting with entire git repositories Repository(repo::Platform), - /// Subcommands for interacting with mailmaps - Mailmap(mailmap::Platform), + /// Subcommands that need no git repository to run. + #[clap(subcommand)] + Free(free::Subcommands), } /// @@ -332,6 +333,40 @@ pub mod pack { } } +/// +pub mod free { + #[derive(Debug, clap::Subcommand)] + pub enum Subcommands { + /// Subcommands for interacting with mailmaps + Mailmap { + #[clap(flatten)] + cmd: mailmap::Platform, + }, + } + + /// + pub mod mailmap { + use std::path::PathBuf; + + #[derive(Debug, clap::Parser)] + pub struct Platform { + /// The path to the mailmap file. + #[clap(short = 'p', long, default_value = ".mailmap")] + pub path: PathBuf, + + /// Subcommands + #[clap(subcommand)] + pub cmd: Subcommands, + } + + #[derive(Debug, clap::Subcommand)] + pub enum Subcommands { + /// Parse all entries in the mailmap and report malformed lines or collisions. + Verify, + } + } +} + /// pub mod repo { #[derive(Debug, clap::Parser)] @@ -551,28 +586,6 @@ pub mod index { } } -/// -pub mod mailmap { - use std::path::PathBuf; - - #[derive(Debug, clap::Parser)] - pub struct Platform { - /// The path to the mailmap file. - #[clap(short = 'p', long, default_value = ".mailmap")] - pub path: PathBuf, - - /// Subcommands - #[clap(subcommand)] - pub cmd: Subcommands, - } - - #[derive(Debug, clap::Subcommand)] - pub enum Subcommands { - /// Parse all entries in the mailmap and report malformed lines or collisions. - Verify, - } -} - /// pub mod commitgraph { use std::path::PathBuf; From 1cdecbc583ae412e7f25cade73b46e00a182125f Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 22 Jul 2022 10:22:27 +0800 Subject: [PATCH 314/366] move 'pack' to 'free' (#331) --- src/plumbing/main.rs | 518 ++++++++++++++++++++-------------------- src/plumbing/options.rs | 482 ++++++++++++++++++------------------- 2 files changed, 503 insertions(+), 497 deletions(-) diff --git a/src/plumbing/main.rs b/src/plumbing/main.rs index fe1928f67c8..caa9bd4ea86 100644 --- a/src/plumbing/main.rs +++ b/src/plumbing/main.rs @@ -16,7 +16,7 @@ use gitoxide_core::pack::verify; #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] use crate::plumbing::options::remote; use crate::{ - plumbing::options::{commitgraph, free, index, pack, pack::multi_index, repo, Args, Subcommands}, + plumbing::options::{commitgraph, free, index, repo, Args, Subcommands}, shared::pretty::prepare_and_run, }; @@ -149,16 +149,270 @@ pub fn main() -> Result<()> { }, ), }, - Subcommands::Free(free::Subcommands::Mailmap { cmd }) => match cmd { - free::mailmap::Platform { path, cmd } => match cmd { - free::mailmap::Subcommands::Verify => prepare_and_run( - "mailmap-verify", + Subcommands::Free(subcommands) => match subcommands { + free::Subcommands::Mailmap { cmd } => match cmd { + free::mailmap::Platform { path, cmd } => match cmd { + free::mailmap::Subcommands::Verify => prepare_and_run( + "mailmap-verify", + verbose, + progress, + progress_keep_open, + core::mailmap::PROGRESS_RANGE, + move |_progress, out, _err| core::mailmap::verify(path, format, out), + ), + }, + }, + free::Subcommands::Pack(subcommands) => match subcommands { + free::pack::Subcommands::Create { + repository, + expansion, + thin, + statistics, + nondeterministic_count, + tips, + pack_cache_size_mb, + counting_threads, + object_cache_size_mb, + output_directory, + } => { + let has_tips = !tips.is_empty(); + prepare_and_run( + "pack-create", + verbose, + progress, + progress_keep_open, + core::pack::create::PROGRESS_RANGE, + move |progress, out, _err| { + let input = if has_tips { None } else { stdin_or_bail()?.into() }; + let repository = repository.unwrap_or_else(|| PathBuf::from(".")); + let context = core::pack::create::Context { + thread_limit, + thin, + nondeterministic_thread_count: nondeterministic_count.then(|| counting_threads), + pack_cache_size_in_bytes: pack_cache_size_mb.unwrap_or(0) * 1_000_000, + object_cache_size_in_bytes: object_cache_size_mb.unwrap_or(0) * 1_000_000, + statistics: if statistics { Some(format) } else { None }, + out, + expansion: expansion.unwrap_or(if has_tips { + core::pack::create::ObjectExpansion::TreeTraversal + } else { + core::pack::create::ObjectExpansion::None + }), + }; + core::pack::create(repository, tips, input, output_directory, progress, context) + }, + ) + } + #[cfg(feature = "gitoxide-core-async-client")] + pack::Subcommands::Receive { + protocol, + url, + directory, + refs, + refs_directory, + } => { + let (_handle, progress) = + async_util::prepare(verbose, "pack-receive", core::pack::receive::PROGRESS_RANGE); + let fut = core::pack::receive( + protocol, + &url, + directory, + refs_directory, + refs.into_iter().map(|s| s.into()).collect(), + git_features::progress::DoOrDiscard::from(progress), + core::pack::receive::Context { + thread_limit, + format, + out: std::io::stdout(), + should_interrupt, + object_hash, + }, + ); + return futures_lite::future::block_on(fut); + } + #[cfg(feature = "gitoxide-core-blocking-client")] + free::pack::Subcommands::Receive { + protocol, + url, + directory, + refs, + refs_directory, + } => prepare_and_run( + "pack-receive", verbose, progress, progress_keep_open, - core::mailmap::PROGRESS_RANGE, - move |_progress, out, _err| core::mailmap::verify(path, format, out), + core::pack::receive::PROGRESS_RANGE, + move |progress, out, _err| { + core::pack::receive( + protocol, + &url, + directory, + refs_directory, + refs.into_iter().map(|r| r.into()).collect(), + progress, + core::pack::receive::Context { + thread_limit, + format, + should_interrupt, + out, + object_hash, + }, + ) + }, + ), + free::pack::Subcommands::Explode { + check, + sink_compress, + delete_pack, + pack_path, + object_path, + verify, + } => prepare_and_run( + "pack-explode", + verbose, + progress, + progress_keep_open, + None, + move |progress, _out, _err| { + core::pack::explode::pack_or_pack_index( + pack_path, + object_path, + check, + progress, + core::pack::explode::Context { + thread_limit, + delete_pack, + sink_compress, + verify, + should_interrupt, + object_hash, + }, + ) + }, ), + free::pack::Subcommands::Verify { + args: + free::pack::VerifyOptions { + algorithm, + decode, + re_encode, + statistics, + }, + path, + } => prepare_and_run( + "pack-verify", + verbose, + progress, + progress_keep_open, + verify::PROGRESS_RANGE, + move |progress, out, err| { + let mode = verify_mode(decode, re_encode); + let output_statistics = if statistics { Some(format) } else { None }; + verify::pack_or_pack_index( + path, + progress, + verify::Context { + output_statistics, + out, + err, + thread_limit, + mode, + algorithm, + should_interrupt: &should_interrupt, + object_hash, + }, + ) + }, + ) + .map(|_| ()), + free::pack::Subcommands::MultiIndex(free::pack::multi_index::Platform { multi_index_path, cmd }) => { + match cmd { + free::pack::multi_index::Subcommands::Entries => prepare_and_run( + "pack-multi-index-entries", + verbose, + progress, + progress_keep_open, + core::pack::multi_index::PROGRESS_RANGE, + move |_progress, out, _err| core::pack::multi_index::entries(multi_index_path, format, out), + ), + free::pack::multi_index::Subcommands::Info => prepare_and_run( + "pack-multi-index-info", + verbose, + progress, + progress_keep_open, + core::pack::multi_index::PROGRESS_RANGE, + move |_progress, out, err| { + core::pack::multi_index::info(multi_index_path, format, out, err) + }, + ), + free::pack::multi_index::Subcommands::Verify => prepare_and_run( + "pack-multi-index-verify", + verbose, + progress, + progress_keep_open, + core::pack::multi_index::PROGRESS_RANGE, + move |progress, _out, _err| { + core::pack::multi_index::verify(multi_index_path, progress, &should_interrupt) + }, + ), + free::pack::multi_index::Subcommands::Create { index_paths } => prepare_and_run( + "pack-multi-index-create", + verbose, + progress, + progress_keep_open, + core::pack::multi_index::PROGRESS_RANGE, + move |progress, _out, _err| { + core::pack::multi_index::create( + index_paths, + multi_index_path, + progress, + &should_interrupt, + object_hash, + ) + }, + ), + } + } + free::pack::Subcommands::Index(subcommands) => match subcommands { + free::pack::index::Subcommands::Create { + iteration_mode, + pack_path, + directory, + } => prepare_and_run( + "pack-index-create", + verbose, + progress, + progress_keep_open, + core::pack::index::PROGRESS_RANGE, + move |progress, out, _err| { + use gitoxide_core::pack::index::PathOrRead; + let input = if let Some(path) = pack_path { + PathOrRead::Path(path) + } else { + if atty::is(atty::Stream::Stdin) { + anyhow::bail!( + "Refusing to read from standard input as no path is given, but it's a terminal." + ) + } + PathOrRead::Read(Box::new(std::io::stdin())) + }; + core::pack::index::from_pack( + input, + directory, + progress, + core::pack::index::Context { + thread_limit, + iteration_mode, + format, + out, + object_hash, + should_interrupt: &git_repository::interrupt::IS_INTERRUPTED, + }, + ) + }, + ), + }, }, }, Subcommands::Repository(repo::Platform { cmd }) => match cmd { @@ -314,7 +568,7 @@ pub fn main() -> Result<()> { }, repo::Subcommands::Verify { args: - pack::VerifyOptions { + free::pack::VerifyOptions { statistics, algorithm, decode, @@ -342,254 +596,6 @@ pub fn main() -> Result<()> { }, ), }, - Subcommands::Pack(subcommands) => match subcommands { - pack::Subcommands::Create { - repository, - expansion, - thin, - statistics, - nondeterministic_count, - tips, - pack_cache_size_mb, - counting_threads, - object_cache_size_mb, - output_directory, - } => { - let has_tips = !tips.is_empty(); - prepare_and_run( - "pack-create", - verbose, - progress, - progress_keep_open, - core::pack::create::PROGRESS_RANGE, - move |progress, out, _err| { - let input = if has_tips { None } else { stdin_or_bail()?.into() }; - let repository = repository.unwrap_or_else(|| PathBuf::from(".")); - let context = core::pack::create::Context { - thread_limit, - thin, - nondeterministic_thread_count: nondeterministic_count.then(|| counting_threads), - pack_cache_size_in_bytes: pack_cache_size_mb.unwrap_or(0) * 1_000_000, - object_cache_size_in_bytes: object_cache_size_mb.unwrap_or(0) * 1_000_000, - statistics: if statistics { Some(format) } else { None }, - out, - expansion: expansion.unwrap_or(if has_tips { - core::pack::create::ObjectExpansion::TreeTraversal - } else { - core::pack::create::ObjectExpansion::None - }), - }; - core::pack::create(repository, tips, input, output_directory, progress, context) - }, - ) - } - #[cfg(feature = "gitoxide-core-async-client")] - pack::Subcommands::Receive { - protocol, - url, - directory, - refs, - refs_directory, - } => { - let (_handle, progress) = - async_util::prepare(verbose, "pack-receive", core::pack::receive::PROGRESS_RANGE); - let fut = core::pack::receive( - protocol, - &url, - directory, - refs_directory, - refs.into_iter().map(|s| s.into()).collect(), - git_features::progress::DoOrDiscard::from(progress), - core::pack::receive::Context { - thread_limit, - format, - out: std::io::stdout(), - should_interrupt, - object_hash, - }, - ); - return futures_lite::future::block_on(fut); - } - #[cfg(feature = "gitoxide-core-blocking-client")] - pack::Subcommands::Receive { - protocol, - url, - directory, - refs, - refs_directory, - } => prepare_and_run( - "pack-receive", - verbose, - progress, - progress_keep_open, - core::pack::receive::PROGRESS_RANGE, - move |progress, out, _err| { - core::pack::receive( - protocol, - &url, - directory, - refs_directory, - refs.into_iter().map(|r| r.into()).collect(), - progress, - core::pack::receive::Context { - thread_limit, - format, - should_interrupt, - out, - object_hash, - }, - ) - }, - ), - pack::Subcommands::Explode { - check, - sink_compress, - delete_pack, - pack_path, - object_path, - verify, - } => prepare_and_run( - "pack-explode", - verbose, - progress, - progress_keep_open, - None, - move |progress, _out, _err| { - core::pack::explode::pack_or_pack_index( - pack_path, - object_path, - check, - progress, - core::pack::explode::Context { - thread_limit, - delete_pack, - sink_compress, - verify, - should_interrupt, - object_hash, - }, - ) - }, - ), - pack::Subcommands::Verify { - args: - pack::VerifyOptions { - algorithm, - decode, - re_encode, - statistics, - }, - path, - } => prepare_and_run( - "pack-verify", - verbose, - progress, - progress_keep_open, - verify::PROGRESS_RANGE, - move |progress, out, err| { - let mode = verify_mode(decode, re_encode); - let output_statistics = if statistics { Some(format) } else { None }; - verify::pack_or_pack_index( - path, - progress, - verify::Context { - output_statistics, - out, - err, - thread_limit, - mode, - algorithm, - should_interrupt: &should_interrupt, - object_hash, - }, - ) - }, - ) - .map(|_| ()), - pack::Subcommands::MultiIndex(multi_index::Platform { multi_index_path, cmd }) => match cmd { - pack::multi_index::Subcommands::Entries => prepare_and_run( - "pack-multi-index-entries", - verbose, - progress, - progress_keep_open, - core::pack::multi_index::PROGRESS_RANGE, - move |_progress, out, _err| core::pack::multi_index::entries(multi_index_path, format, out), - ), - pack::multi_index::Subcommands::Info => prepare_and_run( - "pack-multi-index-info", - verbose, - progress, - progress_keep_open, - core::pack::multi_index::PROGRESS_RANGE, - move |_progress, out, err| core::pack::multi_index::info(multi_index_path, format, out, err), - ), - pack::multi_index::Subcommands::Verify => prepare_and_run( - "pack-multi-index-verify", - verbose, - progress, - progress_keep_open, - core::pack::multi_index::PROGRESS_RANGE, - move |progress, _out, _err| { - core::pack::multi_index::verify(multi_index_path, progress, &should_interrupt) - }, - ), - pack::multi_index::Subcommands::Create { index_paths } => prepare_and_run( - "pack-multi-index-create", - verbose, - progress, - progress_keep_open, - core::pack::multi_index::PROGRESS_RANGE, - move |progress, _out, _err| { - core::pack::multi_index::create( - index_paths, - multi_index_path, - progress, - &should_interrupt, - object_hash, - ) - }, - ), - }, - pack::Subcommands::Index(subcommands) => match subcommands { - pack::index::Subcommands::Create { - iteration_mode, - pack_path, - directory, - } => prepare_and_run( - "pack-index-create", - verbose, - progress, - progress_keep_open, - core::pack::index::PROGRESS_RANGE, - move |progress, out, _err| { - use gitoxide_core::pack::index::PathOrRead; - let input = if let Some(path) = pack_path { - PathOrRead::Path(path) - } else { - if atty::is(atty::Stream::Stdin) { - anyhow::bail!( - "Refusing to read from standard input as no path is given, but it's a terminal." - ) - } - PathOrRead::Read(Box::new(std::io::stdin())) - }; - core::pack::index::from_pack( - input, - directory, - progress, - core::pack::index::Context { - thread_limit, - iteration_mode, - format, - out, - object_hash, - should_interrupt: &git_repository::interrupt::IS_INTERRUPTED, - }, - ) - }, - ), - }, - }, #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] Subcommands::Remote(subcommands) => match subcommands { #[cfg(feature = "gitoxide-core-async-client")] diff --git a/src/plumbing/options.rs b/src/plumbing/options.rs index 68a45851e9e..72c9e62fd86 100644 --- a/src/plumbing/options.rs +++ b/src/plumbing/options.rs @@ -51,9 +51,6 @@ pub struct Args { #[derive(Debug, clap::Subcommand)] pub enum Subcommands { - /// Subcommands for interacting with packs and their indices. - #[clap(subcommand)] - Pack(pack::Subcommands), /// Subcommands for interacting with git remote server. #[clap(subcommand)] #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] @@ -71,277 +68,280 @@ pub enum Subcommands { } /// -pub mod pack { - use std::{ffi::OsString, path::PathBuf}; - - use gitoxide_core as core; - +pub mod free { #[derive(Debug, clap::Subcommand)] pub enum Subcommands { - /// Subcommands for interacting with pack indices (.idx) + /// Subcommands for interacting with mailmaps + Mailmap { + #[clap(flatten)] + cmd: mailmap::Platform, + }, + /// Subcommands for interacting with pack files and indices #[clap(subcommand)] - Index(index::Subcommands), - /// Subcommands for interacting with multi-pack indices (named "multi-pack-index") - MultiIndex(multi_index::Platform), - /// Create a new pack with a set of objects. - Create { - #[clap(long, short = 'r')] - /// the directory containing the '.git' repository from which objects should be read. - repository: Option, + Pack(pack::Subcommands), + } - #[clap(long, short = 'e', possible_values(core::pack::create::ObjectExpansion::variants()))] - /// the way objects are expanded. They differ in costs. - /// - /// Possible values are "none" and "tree-traversal". Default is "none". - expansion: Option, + /// + pub mod pack { + use std::{ffi::OsString, path::PathBuf}; - #[clap(long, default_value_t = 3, requires = "nondeterministic-count")] - /// The amount of threads to use when counting and the `--nondeterminisitc-count` flag is set, defaulting - /// to the globally configured threads. - /// - /// Use it to have different trade-offs between counting performance and cost in terms of CPU, as the scaling - /// here is everything but linear. The effectiveness of each core seems to be no more than 30%. - counting_threads: usize, + use gitoxide_core as core; - #[clap(long)] - /// if set, the counting phase may be accelerated using multithreading. - /// - /// On the flip side, however, one will loose deterministic counting results which affects the - /// way the resulting pack is structured. - nondeterministic_count: bool, + #[derive(Debug, clap::Subcommand)] + pub enum Subcommands { + /// Subcommands for interacting with pack indices (.idx) + #[clap(subcommand)] + Index(index::Subcommands), + /// Subcommands for interacting with multi-pack indices (named "multi-pack-index") + MultiIndex(multi_index::Platform), + /// Create a new pack with a set of objects. + Create { + #[clap(long, short = 'r')] + /// the directory containing the '.git' repository from which objects should be read. + repository: Option, - #[clap(long, short = 's')] - /// If set statistical information will be presented to inform about pack creation details. - /// It's a form of instrumentation for developers to help improve pack generation. - statistics: bool, + #[clap(long, short = 'e', possible_values(core::pack::create::ObjectExpansion::variants()))] + /// the way objects are expanded. They differ in costs. + /// + /// Possible values are "none" and "tree-traversal". Default is "none". + expansion: Option, - #[clap(long)] - /// The size in megabytes for a cache to speed up pack access for packs with long delta chains. - /// It is shared among all threads, so 4 threads would use their own cache 1/4th of the size. - /// - /// If unset, no cache will be used. - pack_cache_size_mb: Option, + #[clap(long, default_value_t = 3, requires = "nondeterministic-count")] + /// The amount of threads to use when counting and the `--nondeterminisitc-count` flag is set, defaulting + /// to the globally configured threads. + /// + /// Use it to have different trade-offs between counting performance and cost in terms of CPU, as the scaling + /// here is everything but linear. The effectiveness of each core seems to be no more than 30%. + counting_threads: usize, - #[clap(long)] - /// The size in megabytes for a cache to speed up accessing entire objects, bypassing object database access when hit. - /// It is shared among all threads, so 4 threads would use their own cache 1/4th of the size. - /// - /// This cache type is currently only effective when using the 'diff-tree' object expansion. - /// - /// If unset, no cache will be used. - object_cache_size_mb: Option, + #[clap(long)] + /// if set, the counting phase may be accelerated using multithreading. + /// + /// On the flip side, however, one will loose deterministic counting results which affects the + /// way the resulting pack is structured. + nondeterministic_count: bool, - #[clap(long)] - /// if set, delta-objects whose base object wouldn't be in the pack will not be recompressed as base object, but instead - /// refer to its base object using its object id. - /// - /// This allows for smaller packs but requires the receiver of the pack to resolve these ids before storing the pack. - /// Packs produced with this option enabled are only valid in transit, but not at rest. - thin: bool, + #[clap(long, short = 's')] + /// If set statistical information will be presented to inform about pack creation details. + /// It's a form of instrumentation for developers to help improve pack generation. + statistics: bool, - /// The directory into which to write the pack file. - #[clap(long, short = 'o')] - output_directory: Option, + #[clap(long)] + /// The size in megabytes for a cache to speed up pack access for packs with long delta chains. + /// It is shared among all threads, so 4 threads would use their own cache 1/4th of the size. + /// + /// If unset, no cache will be used. + pack_cache_size_mb: Option, - /// The tips from which to start the commit graph iteration, either as fully qualified commit hashes - /// or as branch names. - /// - /// If empty, we expect to read objects on stdin and default to 'none' as expansion mode. - /// Otherwise the expansion mode is 'tree-traversal' by default. - tips: Vec, - }, - /// Use the git-protocol to receive a pack, emulating a clone. - #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] - Receive { - /// The protocol version to use. Valid values are 1 and 2 - #[clap(long, short = 'p')] - protocol: Option, + #[clap(long)] + /// The size in megabytes for a cache to speed up accessing entire objects, bypassing object database access when hit. + /// It is shared among all threads, so 4 threads would use their own cache 1/4th of the size. + /// + /// This cache type is currently only effective when using the 'diff-tree' object expansion. + /// + /// If unset, no cache will be used. + object_cache_size_mb: Option, - /// the directory into which to write references. Existing files will be overwritten. - /// - /// Note that the directory will be created if needed. - #[clap(long, short = 'd')] - refs_directory: Option, + #[clap(long)] + /// if set, delta-objects whose base object wouldn't be in the pack will not be recompressed as base object, but instead + /// refer to its base object using its object id. + /// + /// This allows for smaller packs but requires the receiver of the pack to resolve these ids before storing the pack. + /// Packs produced with this option enabled are only valid in transit, but not at rest. + thin: bool, - /// The URLs or path from which to receive the pack. - /// - /// See here for a list of supported URLs: - url: String, + /// The directory into which to write the pack file. + #[clap(long, short = 'o')] + output_directory: Option, - /// If set once or more times, these references will be fetched instead of all advertised ones. - /// - /// Note that this requires a reasonably modern git server. - #[clap(long = "reference", short = 'r')] - refs: Vec, + /// The tips from which to start the commit graph iteration, either as fully qualified commit hashes + /// or as branch names. + /// + /// If empty, we expect to read objects on stdin and default to 'none' as expansion mode. + /// Otherwise the expansion mode is 'tree-traversal' by default. + tips: Vec, + }, + /// Use the git-protocol to receive a pack, emulating a clone. + #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] + Receive { + /// The protocol version to use. Valid values are 1 and 2 + #[clap(long, short = 'p')] + protocol: Option, - /// The directory into which to write the received pack and index. - /// - /// If unset, they will be discarded. - directory: Option, - }, - /// Dissolve a pack into its loose objects. - /// - /// Note that this effectively removes delta compression for an average compression of 2x, creating one file per object in the process. - /// Thus this should only be done to dissolve small packs after a fetch. - Explode { - #[clap(long)] - /// Read written objects back and assert they match their source. Fail the operation otherwise. - /// - /// Only relevant if an object directory is set. - verify: bool, + /// the directory into which to write references. Existing files will be overwritten. + /// + /// Note that the directory will be created if needed. + #[clap(long, short = 'd')] + refs_directory: Option, - /// delete the pack and index file after the operation is successful - #[clap(long)] - delete_pack: bool, + /// The URLs or path from which to receive the pack. + /// + /// See here for a list of supported URLs: + url: String, - /// The amount of checks to run - #[clap( - long, - short = 'c', - default_value = "all", - possible_values(core::pack::explode::SafetyCheck::variants()) - )] - check: core::pack::explode::SafetyCheck, + /// If set once or more times, these references will be fetched instead of all advertised ones. + /// + /// Note that this requires a reasonably modern git server. + #[clap(long = "reference", short = 'r')] + refs: Vec, - /// Compress bytes even when using the sink, i.e. no object directory is specified + /// The directory into which to write the received pack and index. + /// + /// If unset, they will be discarded. + directory: Option, + }, + /// Dissolve a pack into its loose objects. /// - /// This helps to determine overhead related to compression. If unset, the sink will - /// only create hashes from bytes, which is usually limited by the speed at which input - /// can be obtained. - #[clap(long)] - sink_compress: bool, - - /// The '.pack' or '.idx' file to explode into loose objects - pack_path: PathBuf, - - /// The path into which all objects should be written. Commonly '.git/objects' - object_path: Option, - }, - /// Verify the integrity of a pack, index or multi-index file - Verify { - #[clap(flatten)] - args: VerifyOptions, - - /// The '.pack', '.idx' or 'multi-pack-index' file to validate. - path: PathBuf, - }, - } + /// Note that this effectively removes delta compression for an average compression of 2x, creating one file per object in the process. + /// Thus this should only be done to dissolve small packs after a fetch. + Explode { + #[clap(long)] + /// Read written objects back and assert they match their source. Fail the operation otherwise. + /// + /// Only relevant if an object directory is set. + verify: bool, - #[derive(Debug, clap::Parser)] - pub struct VerifyOptions { - /// output statistical information - #[clap(long, short = 's')] - pub statistics: bool, - /// The algorithm used to verify packs. They differ in costs. - #[clap( - long, - short = 'a', - default_value = "less-time", - possible_values(core::pack::verify::Algorithm::variants()) - )] - pub algorithm: core::pack::verify::Algorithm, - - #[clap(long, conflicts_with("re-encode"))] - /// Decode and parse tags, commits and trees to validate their correctness beyond hashing correctly. - /// - /// Malformed objects should not usually occur, but could be injected on purpose or accident. - /// This will reduce overall performance. - pub decode: bool, + /// delete the pack and index file after the operation is successful + #[clap(long)] + delete_pack: bool, - #[clap(long)] - /// Decode and parse tags, commits and trees to validate their correctness, and re-encode them. - /// - /// This flag is primarily to test the implementation of encoding, and requires to decode the object first. - /// Encoding an object after decoding it should yield exactly the same bytes. - /// This will reduce overall performance even more, as re-encoding requires to transform zero-copy objects into - /// owned objects, causing plenty of allocation to occour. - pub re_encode: bool, - } + /// The amount of checks to run + #[clap( + long, + short = 'c', + default_value = "all", + possible_values(core::pack::explode::SafetyCheck::variants()) + )] + check: core::pack::explode::SafetyCheck, - /// - pub mod multi_index { - use std::path::PathBuf; + /// Compress bytes even when using the sink, i.e. no object directory is specified + /// + /// This helps to determine overhead related to compression. If unset, the sink will + /// only create hashes from bytes, which is usually limited by the speed at which input + /// can be obtained. + #[clap(long)] + sink_compress: bool, - #[derive(Debug, clap::Parser)] - pub struct Platform { - /// The path to the index file. - #[clap(short = 'i', long, default_value = ".git/objects/pack/multi-pack-index")] - pub multi_index_path: PathBuf, + /// The '.pack' or '.idx' file to explode into loose objects + pack_path: PathBuf, - /// Subcommands - #[clap(subcommand)] - pub cmd: Subcommands, - } + /// The path into which all objects should be written. Commonly '.git/objects' + object_path: Option, + }, + /// Verify the integrity of a pack, index or multi-index file + Verify { + #[clap(flatten)] + args: VerifyOptions, - #[derive(Debug, clap::Subcommand)] - pub enum Subcommands { - /// Display all entries of a multi-index: - Entries, - /// Print general information about a multi-index file - Info, - /// Verify a multi-index quickly without inspecting objects themselves - Verify, - /// Create a multi-pack index from one or more pack index files, overwriting possibloy existing files. - Create { - /// Paths to the pack index files to read (with .idx extension). - /// - /// Note for the multi-index to be useful, it should be side-by-side with the supplied `.idx` files. - #[clap(required = true)] - index_paths: Vec, + /// The '.pack', '.idx' or 'multi-pack-index' file to validate. + path: PathBuf, }, } - } - - /// - pub mod index { - use std::path::PathBuf; - use gitoxide_core as core; + #[derive(Debug, clap::Parser)] + pub struct VerifyOptions { + /// output statistical information + #[clap(long, short = 's')] + pub statistics: bool, + /// The algorithm used to verify packs. They differ in costs. + #[clap( + long, + short = 'a', + default_value = "less-time", + possible_values(core::pack::verify::Algorithm::variants()) + )] + pub algorithm: core::pack::verify::Algorithm, - #[derive(Debug, clap::Subcommand)] - pub enum Subcommands { - /// create a pack index from a pack data file. - Create { - /// Specify how to iterate the pack, defaults to 'verify' - /// - /// Valid values are - /// - /// **as-is** do not do anything and expect the pack file to be valid as per the trailing hash, - /// **verify** the input ourselves and validate that it matches with the hash provided in the pack, - /// **restore** hash the input ourselves and ignore failing entries, instead finish the pack with the hash we computed - /// to keep as many objects as possible. - #[clap( - long, - short = 'i', - default_value = "verify", - possible_values(core::pack::index::IterationMode::variants()) - )] - iteration_mode: core::pack::index::IterationMode, + #[clap(long, conflicts_with("re-encode"))] + /// Decode and parse tags, commits and trees to validate their correctness beyond hashing correctly. + /// + /// Malformed objects should not usually occur, but could be injected on purpose or accident. + /// This will reduce overall performance. + pub decode: bool, - /// Path to the pack file to read (with .pack extension). - /// - /// If unset, the pack file is expected on stdin. - #[clap(long, short = 'p')] - pack_path: Option, + #[clap(long)] + /// Decode and parse tags, commits and trees to validate their correctness, and re-encode them. + /// + /// This flag is primarily to test the implementation of encoding, and requires to decode the object first. + /// Encoding an object after decoding it should yield exactly the same bytes. + /// This will reduce overall performance even more, as re-encoding requires to transform zero-copy objects into + /// owned objects, causing plenty of allocation to occour. + pub re_encode: bool, + } - /// The folder into which to place the pack and the generated index file - /// - /// If unset, only informational output will be provided to standard output. - directory: Option, - }, + /// + pub mod multi_index { + use std::path::PathBuf; + + #[derive(Debug, clap::Parser)] + pub struct Platform { + /// The path to the index file. + #[clap(short = 'i', long, default_value = ".git/objects/pack/multi-pack-index")] + pub multi_index_path: PathBuf, + + /// Subcommands + #[clap(subcommand)] + pub cmd: Subcommands, + } + + #[derive(Debug, clap::Subcommand)] + pub enum Subcommands { + /// Display all entries of a multi-index: + Entries, + /// Print general information about a multi-index file + Info, + /// Verify a multi-index quickly without inspecting objects themselves + Verify, + /// Create a multi-pack index from one or more pack index files, overwriting possibloy existing files. + Create { + /// Paths to the pack index files to read (with .idx extension). + /// + /// Note for the multi-index to be useful, it should be side-by-side with the supplied `.idx` files. + #[clap(required = true)] + index_paths: Vec, + }, + } } - } -} -/// -pub mod free { - #[derive(Debug, clap::Subcommand)] - pub enum Subcommands { - /// Subcommands for interacting with mailmaps - Mailmap { - #[clap(flatten)] - cmd: mailmap::Platform, - }, + /// + pub mod index { + use std::path::PathBuf; + + use gitoxide_core as core; + + #[derive(Debug, clap::Subcommand)] + pub enum Subcommands { + /// create a pack index from a pack data file. + Create { + /// Specify how to iterate the pack, defaults to 'verify' + /// + /// Valid values are + /// + /// **as-is** do not do anything and expect the pack file to be valid as per the trailing hash, + /// **verify** the input ourselves and validate that it matches with the hash provided in the pack, + /// **restore** hash the input ourselves and ignore failing entries, instead finish the pack with the hash we computed + /// to keep as many objects as possible. + #[clap( + long, + short = 'i', + default_value = "verify", + possible_values(core::pack::index::IterationMode::variants()) + )] + iteration_mode: core::pack::index::IterationMode, + + /// Path to the pack file to read (with .pack extension). + /// + /// If unset, the pack file is expected on stdin. + #[clap(long, short = 'p')] + pack_path: Option, + + /// The folder into which to place the pack and the generated index file + /// + /// If unset, only informational output will be provided to standard output. + directory: Option, + }, + } + } } /// @@ -382,7 +382,7 @@ pub mod repo { /// Verify the integrity of the entire repository Verify { #[clap(flatten)] - args: super::pack::VerifyOptions, + args: super::free::pack::VerifyOptions, }, /// Interact with commit objects. Commit { From 83585bdfccdc42b5307255b2d56d8cb12d4136cb Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 22 Jul 2022 10:26:22 +0800 Subject: [PATCH 315/366] move index to 'free' (#331) --- src/plumbing/main.rs | 146 ++++++++++++++++++++-------------------- src/plumbing/options.rs | 102 ++++++++++++++-------------- 2 files changed, 124 insertions(+), 124 deletions(-) diff --git a/src/plumbing/main.rs b/src/plumbing/main.rs index caa9bd4ea86..1d8e1686ba8 100644 --- a/src/plumbing/main.rs +++ b/src/plumbing/main.rs @@ -16,7 +16,7 @@ use gitoxide_core::pack::verify; #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] use crate::plumbing::options::remote; use crate::{ - plumbing::options::{commitgraph, free, index, repo, Args, Subcommands}, + plumbing::options::{commitgraph, free, repo, Args, Subcommands}, shared::pretty::prepare_and_run, }; @@ -77,79 +77,79 @@ pub fn main() -> Result<()> { })?; match cmd { - Subcommands::Index(index::Platform { - object_hash, - index_path, - cmd, - }) => match cmd { - index::Subcommands::CheckoutExclusive { - directory, - empty_files, - repository, - keep_going, - } => prepare_and_run( - "index-checkout", - verbose, - progress, - progress_keep_open, - None, - move |progress, _out, err| { - core::index::checkout_exclusive( - index_path, - directory, - repository, - err, - progress, - &should_interrupt, - core::index::checkout_exclusive::Options { - index: core::index::Options { object_hash, format }, - empty_files, - keep_going, - thread_limit, - }, - ) - }, - ), - index::Subcommands::Info { no_details } => prepare_and_run( - "index-entries", - verbose, - progress, - progress_keep_open, - None, - move |_progress, out, err| { - core::index::information( - index_path, - out, - err, - core::index::information::Options { - index: core::index::Options { object_hash, format }, - extension_details: !no_details, - }, - ) - }, - ), - index::Subcommands::Entries => prepare_and_run( - "index-entries", - verbose, - progress, - progress_keep_open, - None, - move |_progress, out, _err| { - core::index::entries(index_path, out, core::index::Options { object_hash, format }) - }, - ), - index::Subcommands::Verify => prepare_and_run( - "index-verify", - verbose, - progress, - progress_keep_open, - None, - move |_progress, out, _err| { - core::index::verify(index_path, out, core::index::Options { object_hash, format }) - }, - ), - }, Subcommands::Free(subcommands) => match subcommands { + free::Subcommands::Index(free::index::Platform { + object_hash, + index_path, + cmd, + }) => match cmd { + free::index::Subcommands::CheckoutExclusive { + directory, + empty_files, + repository, + keep_going, + } => prepare_and_run( + "index-checkout", + verbose, + progress, + progress_keep_open, + None, + move |progress, _out, err| { + core::index::checkout_exclusive( + index_path, + directory, + repository, + err, + progress, + &should_interrupt, + core::index::checkout_exclusive::Options { + index: core::index::Options { object_hash, format }, + empty_files, + keep_going, + thread_limit, + }, + ) + }, + ), + free::index::Subcommands::Info { no_details } => prepare_and_run( + "index-entries", + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, err| { + core::index::information( + index_path, + out, + err, + core::index::information::Options { + index: core::index::Options { object_hash, format }, + extension_details: !no_details, + }, + ) + }, + ), + free::index::Subcommands::Entries => prepare_and_run( + "index-entries", + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, _err| { + core::index::entries(index_path, out, core::index::Options { object_hash, format }) + }, + ), + free::index::Subcommands::Verify => prepare_and_run( + "index-verify", + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, _err| { + core::index::verify(index_path, out, core::index::Options { object_hash, format }) + }, + ), + }, free::Subcommands::Mailmap { cmd } => match cmd { free::mailmap::Platform { path, cmd } => match cmd { free::mailmap::Subcommands::Verify => prepare_and_run( diff --git a/src/plumbing/options.rs b/src/plumbing/options.rs index 72c9e62fd86..9b08ca5d0b0 100644 --- a/src/plumbing/options.rs +++ b/src/plumbing/options.rs @@ -58,8 +58,6 @@ pub enum Subcommands { /// Subcommands for interacting with commit-graphs #[clap(subcommand)] CommitGraph(commitgraph::Subcommands), - /// Subcommands for interacting with a worktree index, typically at .git/index - Index(index::Platform), /// Subcommands for interacting with entire git repositories Repository(repo::Platform), /// Subcommands that need no git repository to run. @@ -79,6 +77,57 @@ pub mod free { /// Subcommands for interacting with pack files and indices #[clap(subcommand)] Pack(pack::Subcommands), + /// Subcommands for interacting with a worktree index, typically at .git/index + Index(index::Platform), + } + + pub mod index { + use std::path::PathBuf; + + #[derive(Debug, clap::Parser)] + pub struct Platform { + /// The object format to assume when reading files that don't inherently know about it, or when writing files. + #[clap(long, default_value_t = git_repository::hash::Kind::default(), possible_values(&["SHA1"]))] + pub object_hash: git_repository::hash::Kind, + + /// The path to the index file. + #[clap(short = 'i', long, default_value = ".git/index")] + pub index_path: PathBuf, + + /// Subcommands + #[clap(subcommand)] + pub cmd: Subcommands, + } + + #[derive(Debug, clap::Subcommand)] + pub enum Subcommands { + /// Validate constraints and assumptions of an index along with its integrity. + Verify, + /// Print all entries to standard output + Entries, + /// Print information about the index structure + Info { + /// Do not extract specific extension information to gain only a superficial idea of the index's composition. + #[clap(long)] + no_details: bool, + }, + /// Checkout the index into a directory with exclusive write access, similar to what would happen during clone. + CheckoutExclusive { + /// The path to `.git` repository from which objects can be obtained to write the actual files referenced + /// in the index. Use this measure the impact on extracting objects on overall performance. + #[clap(long, short = 'r')] + repository: Option, + /// Ignore errors and keep checking out as many files as possible, and report all errors at the end of the operation. + #[clap(long, short = 'k')] + keep_going: bool, + /// Enable to query the object database yet write only empty files. This is useful to measure the overhead of ODB query + /// compared to writing the bytes to disk. + #[clap(long, short = 'e', requires = "repository")] + empty_files: bool, + /// The directory into which to write all index entries. + directory: PathBuf, + }, + } } /// @@ -537,55 +586,6 @@ pub mod repo { } /// -pub mod index { - use std::path::PathBuf; - - #[derive(Debug, clap::Parser)] - pub struct Platform { - /// The object format to assume when reading files that don't inherently know about it, or when writing files. - #[clap(long, default_value_t = git_repository::hash::Kind::default(), possible_values(&["SHA1"]))] - pub object_hash: git_repository::hash::Kind, - - /// The path to the index file. - #[clap(short = 'i', long, default_value = ".git/index")] - pub index_path: PathBuf, - - /// Subcommands - #[clap(subcommand)] - pub cmd: Subcommands, - } - - #[derive(Debug, clap::Subcommand)] - pub enum Subcommands { - /// Validate constraints and assumptions of an index along with its integrity. - Verify, - /// Print all entries to standard output - Entries, - /// Print information about the index structure - Info { - /// Do not extract specific extension information to gain only a superficial idea of the index's composition. - #[clap(long)] - no_details: bool, - }, - /// Checkout the index into a directory with exclusive write access, similar to what would happen during clone. - CheckoutExclusive { - /// The path to `.git` repository from which objects can be obtained to write the actual files referenced - /// in the index. Use this measure the impact on extracting objects on overall performance. - #[clap(long, short = 'r')] - repository: Option, - /// Ignore errors and keep checking out as many files as possible, and report all errors at the end of the operation. - #[clap(long, short = 'k')] - keep_going: bool, - /// Enable to query the object database yet write only empty files. This is useful to measure the overhead of ODB query - /// compared to writing the bytes to disk. - #[clap(long, short = 'e', requires = "repository")] - empty_files: bool, - /// The directory into which to write all index entries. - directory: PathBuf, - }, - } -} - /// pub mod commitgraph { use std::path::PathBuf; From f99c3b29cea30f1cbbea7e5855abfec3de6ca630 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 22 Jul 2022 10:28:20 +0800 Subject: [PATCH 316/366] move commitgraph to 'free' (#331) --- src/plumbing/main.rs | 44 ++++++++++++++++++++--------------------- src/plumbing/options.rs | 41 +++++++++++++++++++------------------- 2 files changed, 42 insertions(+), 43 deletions(-) diff --git a/src/plumbing/main.rs b/src/plumbing/main.rs index 1d8e1686ba8..117ded16336 100644 --- a/src/plumbing/main.rs +++ b/src/plumbing/main.rs @@ -16,7 +16,7 @@ use gitoxide_core::pack::verify; #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] use crate::plumbing::options::remote; use crate::{ - plumbing::options::{commitgraph, free, repo, Args, Subcommands}, + plumbing::options::{free, repo, Args, Subcommands}, shared::pretty::prepare_and_run, }; @@ -78,6 +78,27 @@ pub fn main() -> Result<()> { match cmd { Subcommands::Free(subcommands) => match subcommands { + free::Subcommands::CommitGraph(subcommands) => match subcommands { + free::commitgraph::Subcommands::Verify { path, statistics } => prepare_and_run( + "commitgraph-verify", + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, err| { + let output_statistics = if statistics { Some(format) } else { None }; + core::commitgraph::verify::graph_or_file( + path, + core::commitgraph::verify::Context { + err, + out, + output_statistics, + }, + ) + }, + ) + .map(|_| ()), + }, free::Subcommands::Index(free::index::Platform { object_hash, index_path, @@ -635,27 +656,6 @@ pub fn main() -> Result<()> { }, ), }, - Subcommands::CommitGraph(subcommands) => match subcommands { - commitgraph::Subcommands::Verify { path, statistics } => prepare_and_run( - "commitgraph-verify", - verbose, - progress, - progress_keep_open, - None, - move |_progress, out, err| { - let output_statistics = if statistics { Some(format) } else { None }; - core::commitgraph::verify::graph_or_file( - path, - core::commitgraph::verify::Context { - err, - out, - output_statistics, - }, - ) - }, - ) - .map(|_| ()), - }, }?; Ok(()) } diff --git a/src/plumbing/options.rs b/src/plumbing/options.rs index 9b08ca5d0b0..8ce1bc55fed 100644 --- a/src/plumbing/options.rs +++ b/src/plumbing/options.rs @@ -55,9 +55,6 @@ pub enum Subcommands { #[clap(subcommand)] #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] Remote(remote::Subcommands), - /// Subcommands for interacting with commit-graphs - #[clap(subcommand)] - CommitGraph(commitgraph::Subcommands), /// Subcommands for interacting with entire git repositories Repository(repo::Platform), /// Subcommands that need no git repository to run. @@ -69,6 +66,9 @@ pub enum Subcommands { pub mod free { #[derive(Debug, clap::Subcommand)] pub enum Subcommands { + /// Subcommands for interacting with commit-graphs + #[clap(subcommand)] + CommitGraph(commitgraph::Subcommands), /// Subcommands for interacting with mailmaps Mailmap { #[clap(flatten)] @@ -81,6 +81,23 @@ pub mod free { Index(index::Platform), } + /// + pub mod commitgraph { + use std::path::PathBuf; + + #[derive(Debug, clap::Subcommand)] + pub enum Subcommands { + /// Verify the integrity of a commit graph + Verify { + /// The path to '.git/objects/info/', '.git/objects/info/commit-graphs/', or '.git/objects/info/commit-graph' to validate. + path: PathBuf, + /// output statistical information about the pack + #[clap(long, short = 's')] + statistics: bool, + }, + } + } + pub mod index { use std::path::PathBuf; @@ -585,24 +602,6 @@ pub mod repo { } } -/// -/// -pub mod commitgraph { - use std::path::PathBuf; - - #[derive(Debug, clap::Subcommand)] - pub enum Subcommands { - /// Verify the integrity of a commit graph - Verify { - /// The path to '.git/objects/info/', '.git/objects/info/commit-graphs/', or '.git/objects/info/commit-graph' to validate. - path: PathBuf, - /// output statistical information about the pack - #[clap(long, short = 's')] - statistics: bool, - }, - } -} - /// #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] pub mod remote { From 8967fcd009260c2d32881866244ba673894775f2 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 22 Jul 2022 10:30:09 +0800 Subject: [PATCH 317/366] move 'remote' to 'free' (#331) --- src/plumbing/main.rs | 82 ++++++++++++++++++++--------------------- src/plumbing/options.rs | 56 ++++++++++++++-------------- 2 files changed, 68 insertions(+), 70 deletions(-) diff --git a/src/plumbing/main.rs b/src/plumbing/main.rs index 117ded16336..32beff14470 100644 --- a/src/plumbing/main.rs +++ b/src/plumbing/main.rs @@ -13,8 +13,6 @@ use git_repository::bstr::io::BufReadExt; use gitoxide_core as core; use gitoxide_core::pack::verify; -#[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] -use crate::plumbing::options::remote; use crate::{ plumbing::options::{free, repo, Args, Subcommands}, shared::pretty::prepare_and_run, @@ -78,6 +76,45 @@ pub fn main() -> Result<()> { match cmd { Subcommands::Free(subcommands) => match subcommands { + #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] + free::Subcommands::Remote(subcommands) => match subcommands { + #[cfg(feature = "gitoxide-core-async-client")] + free::remote::Subcommands::RefList { protocol, url } => { + let (_handle, progress) = + async_util::prepare(verbose, "remote-ref-list", Some(core::remote::refs::PROGRESS_RANGE)); + let fut = core::remote::refs::list( + protocol, + &url, + git_features::progress::DoOrDiscard::from(progress), + core::remote::refs::Context { + thread_limit, + format, + out: std::io::stdout(), + }, + ); + return futures_lite::future::block_on(fut); + } + #[cfg(feature = "gitoxide-core-blocking-client")] + free::remote::Subcommands::RefList { protocol, url } => prepare_and_run( + "remote-ref-list", + verbose, + progress, + progress_keep_open, + core::remote::refs::PROGRESS_RANGE, + move |progress, out, _err| { + core::remote::refs::list( + protocol, + &url, + progress, + core::remote::refs::Context { + thread_limit, + format, + out, + }, + ) + }, + ), + }, free::Subcommands::CommitGraph(subcommands) => match subcommands { free::commitgraph::Subcommands::Verify { path, statistics } => prepare_and_run( "commitgraph-verify", @@ -225,7 +262,7 @@ pub fn main() -> Result<()> { ) } #[cfg(feature = "gitoxide-core-async-client")] - pack::Subcommands::Receive { + free::pack::Subcommands::Receive { protocol, url, directory, @@ -617,45 +654,6 @@ pub fn main() -> Result<()> { }, ), }, - #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] - Subcommands::Remote(subcommands) => match subcommands { - #[cfg(feature = "gitoxide-core-async-client")] - remote::Subcommands::RefList { protocol, url } => { - let (_handle, progress) = - async_util::prepare(verbose, "remote-ref-list", Some(core::remote::refs::PROGRESS_RANGE)); - let fut = core::remote::refs::list( - protocol, - &url, - git_features::progress::DoOrDiscard::from(progress), - core::remote::refs::Context { - thread_limit, - format, - out: std::io::stdout(), - }, - ); - return futures_lite::future::block_on(fut); - } - #[cfg(feature = "gitoxide-core-blocking-client")] - remote::Subcommands::RefList { protocol, url } => prepare_and_run( - "remote-ref-list", - verbose, - progress, - progress_keep_open, - core::remote::refs::PROGRESS_RANGE, - move |progress, out, _err| { - core::remote::refs::list( - protocol, - &url, - progress, - core::remote::refs::Context { - thread_limit, - format, - out, - }, - ) - }, - ), - }, }?; Ok(()) } diff --git a/src/plumbing/options.rs b/src/plumbing/options.rs index 8ce1bc55fed..a938beade68 100644 --- a/src/plumbing/options.rs +++ b/src/plumbing/options.rs @@ -51,10 +51,6 @@ pub struct Args { #[derive(Debug, clap::Subcommand)] pub enum Subcommands { - /// Subcommands for interacting with git remote server. - #[clap(subcommand)] - #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] - Remote(remote::Subcommands), /// Subcommands for interacting with entire git repositories Repository(repo::Platform), /// Subcommands that need no git repository to run. @@ -66,6 +62,10 @@ pub enum Subcommands { pub mod free { #[derive(Debug, clap::Subcommand)] pub enum Subcommands { + /// Subcommands for interacting with git remote server. + #[clap(subcommand)] + #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] + Remote(remote::Subcommands), /// Subcommands for interacting with commit-graphs #[clap(subcommand)] CommitGraph(commitgraph::Subcommands), @@ -81,6 +81,30 @@ pub mod free { Index(index::Platform), } + /// + #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] + pub mod remote { + use gitoxide_core as core; + + #[derive(Debug, clap::Subcommand)] + pub enum Subcommands { + /// List remote references from a remote identified by a url. + /// + /// This is the plumbing equivalent of `git ls-remote`. + /// Supported URLs are documented here: + RefList { + /// The protocol version to use. Valid values are 1 and 2 + #[clap(long, short = 'p')] + protocol: Option, + + /// the URLs or path from which to receive references + /// + /// See here for a list of supported URLs: + url: String, + }, + } + } + /// pub mod commitgraph { use std::path::PathBuf; @@ -601,27 +625,3 @@ pub mod repo { } } } - -/// -#[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] -pub mod remote { - use gitoxide_core as core; - - #[derive(Debug, clap::Subcommand)] - pub enum Subcommands { - /// List remote references from a remote identified by a url. - /// - /// This is the plumbing equivalent of `git ls-remote`. - /// Supported URLs are documented here: - RefList { - /// The protocol version to use. Valid values are 1 and 2 - #[clap(long, short = 'p')] - protocol: Option, - - /// the URLs or path from which to receive references - /// - /// See here for a list of supported URLs: - url: String, - }, - } -} From c9c78e86c387c09838404c90de420892f41f4356 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 22 Jul 2022 10:33:14 +0800 Subject: [PATCH 318/366] move 'revision' one level up (#331) --- src/plumbing/main.rs | 21 +++++++++++---------- src/plumbing/options.rs | 28 ++++++++++++++-------------- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/plumbing/main.rs b/src/plumbing/main.rs index 32beff14470..49cca1b5a79 100644 --- a/src/plumbing/main.rs +++ b/src/plumbing/main.rs @@ -13,6 +13,7 @@ use git_repository::bstr::io::BufReadExt; use gitoxide_core as core; use gitoxide_core::pack::verify; +use crate::plumbing::options::revision; use crate::{ plumbing::options::{free, repo, Args, Subcommands}, shared::pretty::prepare_and_run, @@ -473,17 +474,17 @@ pub fn main() -> Result<()> { }, }, }, + Subcommands::Revision { cmd } => match cmd { + revision::Subcommands::Explain { spec } => prepare_and_run( + "repository-commit-describe", + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, _err| core::repository::revision::explain(repository()?.into(), spec, out), + ), + }, Subcommands::Repository(repo::Platform { cmd }) => match cmd { - repo::Subcommands::Revision { cmd } => match cmd { - repo::revision::Subcommands::Explain { spec } => prepare_and_run( - "repository-commit-describe", - verbose, - progress, - progress_keep_open, - None, - move |_progress, out, _err| core::repository::revision::explain(repository()?.into(), spec, out), - ), - }, repo::Subcommands::Commit { cmd } => match cmd { repo::commit::Subcommands::Describe { annotated_tags, diff --git a/src/plumbing/options.rs b/src/plumbing/options.rs index a938beade68..3890dff9fa7 100644 --- a/src/plumbing/options.rs +++ b/src/plumbing/options.rs @@ -51,6 +51,11 @@ pub struct Args { #[derive(Debug, clap::Subcommand)] pub enum Subcommands { + /// Query and obtain information about revisions. + Revision { + #[clap(subcommand)] + cmd: revision::Subcommands, + }, /// Subcommands for interacting with entire git repositories Repository(repo::Platform), /// Subcommands that need no git repository to run. @@ -58,6 +63,15 @@ pub enum Subcommands { Free(free::Subcommands), } +pub mod revision { + #[derive(Debug, clap::Subcommand)] + #[clap(visible_alias = "rev")] + pub enum Subcommands { + /// Provide the revision specification like `@~1` to explain. + Explain { spec: std::ffi::OsString }, + } +} + /// pub mod free { #[derive(Debug, clap::Subcommand)] @@ -499,20 +513,6 @@ pub mod repo { #[clap(subcommand)] cmd: exclude::Subcommands, }, - /// Query and obtain information about revisions. - Revision { - #[clap(subcommand)] - cmd: revision::Subcommands, - }, - } - - pub mod revision { - #[derive(Debug, clap::Subcommand)] - #[clap(visible_alias = "rev")] - pub enum Subcommands { - /// Provide the revision specification like `@~1` to explain. - Explain { spec: std::ffi::OsString }, - } } pub mod exclude { From ac7d99ac42ff8561e81f476856d0bbe86b5fa627 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 22 Jul 2022 10:41:38 +0800 Subject: [PATCH 319/366] move 'verify' up one level (#331) --- src/plumbing/main.rs | 58 ++++++++++++++++++++--------------------- src/plumbing/options.rs | 10 +++---- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/plumbing/main.rs b/src/plumbing/main.rs index 49cca1b5a79..712d0d21c53 100644 --- a/src/plumbing/main.rs +++ b/src/plumbing/main.rs @@ -474,6 +474,35 @@ pub fn main() -> Result<()> { }, }, }, + Subcommands::Verify { + args: + free::pack::VerifyOptions { + statistics, + algorithm, + decode, + re_encode, + }, + } => prepare_and_run( + "repository-verify", + verbose, + progress, + progress_keep_open, + core::repository::verify::PROGRESS_RANGE, + move |progress, out, _err| { + core::repository::verify::integrity( + repository()?.into(), + out, + progress, + &should_interrupt, + core::repository::verify::Context { + output_statistics: statistics.then(|| format), + algorithm, + verify_mode: verify_mode(decode, re_encode), + thread_limit, + }, + ) + }, + ), Subcommands::Revision { cmd } => match cmd { revision::Subcommands::Explain { spec } => prepare_and_run( "repository-commit-describe", @@ -625,35 +654,6 @@ pub fn main() -> Result<()> { }, ), }, - repo::Subcommands::Verify { - args: - free::pack::VerifyOptions { - statistics, - algorithm, - decode, - re_encode, - }, - } => prepare_and_run( - "repository-verify", - verbose, - progress, - progress_keep_open, - core::repository::verify::PROGRESS_RANGE, - move |progress, out, _err| { - core::repository::verify::integrity( - repository()?.into(), - out, - progress, - &should_interrupt, - core::repository::verify::Context { - output_statistics: statistics.then(|| format), - algorithm, - verify_mode: verify_mode(decode, re_encode), - thread_limit, - }, - ) - }, - ), }, }?; Ok(()) diff --git a/src/plumbing/options.rs b/src/plumbing/options.rs index 3890dff9fa7..afe1d05b0e2 100644 --- a/src/plumbing/options.rs +++ b/src/plumbing/options.rs @@ -51,6 +51,11 @@ pub struct Args { #[derive(Debug, clap::Subcommand)] pub enum Subcommands { + /// Verify the integrity of the entire repository + Verify { + #[clap(flatten)] + args: free::pack::VerifyOptions, + }, /// Query and obtain information about revisions. Revision { #[clap(subcommand)] @@ -483,11 +488,6 @@ pub mod repo { #[derive(Debug, clap::Subcommand)] #[clap(visible_alias = "repo")] pub enum Subcommands { - /// Verify the integrity of the entire repository - Verify { - #[clap(flatten)] - args: super::free::pack::VerifyOptions, - }, /// Interact with commit objects. Commit { #[clap(subcommand)] From 72876f1fd65efc816b704db6880ab881c89cff01 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 22 Jul 2022 10:42:44 +0800 Subject: [PATCH 320/366] move 'commit' up one level (#331) --- src/plumbing/main.rs | 72 ++++++++++++++++----------------- src/plumbing/options.rs | 88 ++++++++++++++++++++--------------------- 2 files changed, 80 insertions(+), 80 deletions(-) diff --git a/src/plumbing/main.rs b/src/plumbing/main.rs index 712d0d21c53..100577b5446 100644 --- a/src/plumbing/main.rs +++ b/src/plumbing/main.rs @@ -13,7 +13,7 @@ use git_repository::bstr::io::BufReadExt; use gitoxide_core as core; use gitoxide_core::pack::verify; -use crate::plumbing::options::revision; +use crate::plumbing::options::{commit, revision}; use crate::{ plumbing::options::{free, repo, Args, Subcommands}, shared::pretty::prepare_and_run, @@ -513,42 +513,42 @@ pub fn main() -> Result<()> { move |_progress, out, _err| core::repository::revision::explain(repository()?.into(), spec, out), ), }, + Subcommands::Commit { cmd } => match cmd { + commit::Subcommands::Describe { + annotated_tags, + all_refs, + first_parent, + always, + long, + statistics, + max_candidates, + rev_spec, + } => prepare_and_run( + "repository-commit-describe", + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, err| { + core::repository::commit::describe( + repository()?.into(), + rev_spec.as_deref(), + out, + err, + core::repository::commit::describe::Options { + all_tags: !annotated_tags, + all_refs, + long_format: long, + first_parent, + statistics, + max_candidates, + always, + }, + ) + }, + ), + }, Subcommands::Repository(repo::Platform { cmd }) => match cmd { - repo::Subcommands::Commit { cmd } => match cmd { - repo::commit::Subcommands::Describe { - annotated_tags, - all_refs, - first_parent, - always, - long, - statistics, - max_candidates, - rev_spec, - } => prepare_and_run( - "repository-commit-describe", - verbose, - progress, - progress_keep_open, - None, - move |_progress, out, err| { - core::repository::commit::describe( - repository()?.into(), - rev_spec.as_deref(), - out, - err, - core::repository::commit::describe::Options { - all_tags: !annotated_tags, - all_refs, - long_format: long, - first_parent, - statistics, - max_candidates, - always, - }, - ) - }, - ), - }, repo::Subcommands::Exclude { cmd } => match cmd { repo::exclude::Subcommands::Query { patterns, diff --git a/src/plumbing/options.rs b/src/plumbing/options.rs index afe1d05b0e2..dbe8ecbdf65 100644 --- a/src/plumbing/options.rs +++ b/src/plumbing/options.rs @@ -51,6 +51,11 @@ pub struct Args { #[derive(Debug, clap::Subcommand)] pub enum Subcommands { + /// Interact with commit objects. + Commit { + #[clap(subcommand)] + cmd: commit::Subcommands, + }, /// Verify the integrity of the entire repository Verify { #[clap(flatten)] @@ -68,6 +73,45 @@ pub enum Subcommands { Free(free::Subcommands), } +pub mod commit { + #[derive(Debug, clap::Subcommand)] + pub enum Subcommands { + /// Describe the current commit or the given one using the name of the closest annotated tag in its ancestry. + Describe { + /// Use annotated tag references only, not all tags. + #[clap(long, short = 't', conflicts_with("all-refs"))] + annotated_tags: bool, + + /// Use all references under the `ref/` namespaces, which includes tag references, local and remote branches. + #[clap(long, short = 'a', conflicts_with("annotated-tags"))] + all_refs: bool, + + /// Only follow the first parent when traversing the commit graph. + #[clap(long, short = 'f')] + first_parent: bool, + + /// Always display the long format, even if that would not be necessary as the id is located directly on a reference. + #[clap(long, short = 'l')] + long: bool, + + /// Consider only the given `n` candidates. This can take longer, but potentially produces more accurate results. + #[clap(long, short = 'c', default_value = "10")] + max_candidates: usize, + + /// Print information on stderr to inform about performance statistics + #[clap(long, short = 's')] + statistics: bool, + + #[clap(long)] + /// If there was no way to describe the commit, fallback to using the abbreviated input revision. + always: bool, + + /// A specification of the revision to use, or the current `HEAD` if unset. + rev_spec: Option, + }, + } +} + pub mod revision { #[derive(Debug, clap::Subcommand)] #[clap(visible_alias = "rev")] @@ -488,11 +532,6 @@ pub mod repo { #[derive(Debug, clap::Subcommand)] #[clap(visible_alias = "repo")] pub enum Subcommands { - /// Interact with commit objects. - Commit { - #[clap(subcommand)] - cmd: commit::Subcommands, - }, /// Interact with tree objects. Tree { #[clap(subcommand)] @@ -559,45 +598,6 @@ pub mod repo { } } - pub mod commit { - #[derive(Debug, clap::Subcommand)] - pub enum Subcommands { - /// Describe the current commit or the given one using the name of the closest annotated tag in its ancestry. - Describe { - /// Use annotated tag references only, not all tags. - #[clap(long, short = 't', conflicts_with("all-refs"))] - annotated_tags: bool, - - /// Use all references under the `ref/` namespaces, which includes tag references, local and remote branches. - #[clap(long, short = 'a', conflicts_with("annotated-tags"))] - all_refs: bool, - - /// Only follow the first parent when traversing the commit graph. - #[clap(long, short = 'f')] - first_parent: bool, - - /// Always display the long format, even if that would not be necessary as the id is located directly on a reference. - #[clap(long, short = 'l')] - long: bool, - - /// Consider only the given `n` candidates. This can take longer, but potentially produces more accurate results. - #[clap(long, short = 'c', default_value = "10")] - max_candidates: usize, - - /// Print information on stderr to inform about performance statistics - #[clap(long, short = 's')] - statistics: bool, - - #[clap(long)] - /// If there was no way to describe the commit, fallback to using the abbreviated input revision. - always: bool, - - /// A specification of the revision to use, or the current `HEAD` if unset. - rev_spec: Option, - }, - } - } - pub mod tree { #[derive(Debug, clap::Subcommand)] pub enum Subcommands { From 38a8350d75720a8455e9c55d12f7cdf4b1742e56 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 22 Jul 2022 10:44:46 +0800 Subject: [PATCH 321/366] move 'tree' up one level (#331) --- src/plumbing/main.rs | 75 +++++++++++++++++++---------------------- src/plumbing/options.rs | 64 +++++++++++++++++------------------ 2 files changed, 66 insertions(+), 73 deletions(-) diff --git a/src/plumbing/main.rs b/src/plumbing/main.rs index 100577b5446..807a8d9f0c9 100644 --- a/src/plumbing/main.rs +++ b/src/plumbing/main.rs @@ -13,7 +13,7 @@ use git_repository::bstr::io::BufReadExt; use gitoxide_core as core; use gitoxide_core::pack::verify; -use crate::plumbing::options::{commit, revision}; +use crate::plumbing::options::{commit, revision, tree}; use crate::{ plumbing::options::{free, repo, Args, Subcommands}, shared::pretty::prepare_and_run, @@ -548,6 +548,39 @@ pub fn main() -> Result<()> { }, ), }, + Subcommands::Tree { cmd } => match cmd { + tree::Subcommands::Entries { + treeish, + recursive, + extended, + } => prepare_and_run( + "repository-tree-entries", + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, _err| { + core::repository::tree::entries( + repository()?.into(), + treeish.as_deref(), + recursive, + extended, + format, + out, + ) + }, + ), + tree::Subcommands::Info { treeish, extended } => prepare_and_run( + "repository-tree-info", + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, err| { + core::repository::tree::info(repository()?.into(), treeish.as_deref(), extended, format, out, err) + }, + ), + }, Subcommands::Repository(repo::Platform { cmd }) => match cmd { repo::Subcommands::Exclude { cmd } => match cmd { repo::exclude::Subcommands::Query { @@ -614,46 +647,6 @@ pub fn main() -> Result<()> { move |_progress, out, err| core::repository::odb::info(repository()?.into(), format, out, err), ), }, - repo::Subcommands::Tree { cmd } => match cmd { - repo::tree::Subcommands::Entries { - treeish, - recursive, - extended, - } => prepare_and_run( - "repository-tree-entries", - verbose, - progress, - progress_keep_open, - None, - move |_progress, out, _err| { - core::repository::tree::entries( - repository()?.into(), - treeish.as_deref(), - recursive, - extended, - format, - out, - ) - }, - ), - repo::tree::Subcommands::Info { treeish, extended } => prepare_and_run( - "repository-tree-info", - verbose, - progress, - progress_keep_open, - None, - move |_progress, out, err| { - core::repository::tree::info( - repository()?.into(), - treeish.as_deref(), - extended, - format, - out, - err, - ) - }, - ), - }, }, }?; Ok(()) diff --git a/src/plumbing/options.rs b/src/plumbing/options.rs index dbe8ecbdf65..9c4cd5409fb 100644 --- a/src/plumbing/options.rs +++ b/src/plumbing/options.rs @@ -51,6 +51,11 @@ pub struct Args { #[derive(Debug, clap::Subcommand)] pub enum Subcommands { + /// Interact with tree objects. + Tree { + #[clap(subcommand)] + cmd: tree::Subcommands, + }, /// Interact with commit objects. Commit { #[clap(subcommand)] @@ -73,6 +78,33 @@ pub enum Subcommands { Free(free::Subcommands), } +pub mod tree { + #[derive(Debug, clap::Subcommand)] + pub enum Subcommands { + /// Print entries in a given tree + Entries { + /// Traverse the entire tree and its subtrees respectively, not only this tree. + #[clap(long, short = 'r')] + recursive: bool, + + /// Provide files size as well. This is expensive as the object is decoded entirely. + #[clap(long, short = 'e')] + extended: bool, + + /// The tree to traverse, or the tree at `HEAD` if unspecified. + treeish: Option, + }, + /// Provide information about a tree. + Info { + /// Provide files size as well. This is expensive as the object is decoded entirely. + #[clap(long, short = 'e')] + extended: bool, + /// The tree to traverse, or the tree at `HEAD` if unspecified. + treeish: Option, + }, + } +} + pub mod commit { #[derive(Debug, clap::Subcommand)] pub enum Subcommands { @@ -532,11 +564,6 @@ pub mod repo { #[derive(Debug, clap::Subcommand)] #[clap(visible_alias = "repo")] pub enum Subcommands { - /// Interact with tree objects. - Tree { - #[clap(subcommand)] - cmd: tree::Subcommands, - }, /// Interact with the object database. Odb { #[clap(subcommand)] @@ -597,31 +624,4 @@ pub mod repo { Info, } } - - pub mod tree { - #[derive(Debug, clap::Subcommand)] - pub enum Subcommands { - /// Print entries in a given tree - Entries { - /// Traverse the entire tree and its subtrees respectively, not only this tree. - #[clap(long, short = 'r')] - recursive: bool, - - /// Provide files size as well. This is expensive as the object is decoded entirely. - #[clap(long, short = 'e')] - extended: bool, - - /// The tree to traverse, or the tree at `HEAD` if unspecified. - treeish: Option, - }, - /// Provide information about a tree. - Info { - /// Provide files size as well. This is expensive as the object is decoded entirely. - #[clap(long, short = 'e')] - extended: bool, - /// The tree to traverse, or the tree at `HEAD` if unspecified. - treeish: Option, - }, - } - } } From 0ed65da9b66d4cc3c85d3b70fa4bc383c7a0d1a3 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 22 Jul 2022 10:46:01 +0800 Subject: [PATCH 322/366] move 'odb' up one level (#331) --- src/plumbing/main.rs | 38 +++++++++++++++++++------------------- src/plumbing/options.rs | 30 +++++++++++++++--------------- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/plumbing/main.rs b/src/plumbing/main.rs index 807a8d9f0c9..9641fededec 100644 --- a/src/plumbing/main.rs +++ b/src/plumbing/main.rs @@ -13,7 +13,7 @@ use git_repository::bstr::io::BufReadExt; use gitoxide_core as core; use gitoxide_core::pack::verify; -use crate::plumbing::options::{commit, revision, tree}; +use crate::plumbing::options::{commit, odb, revision, tree}; use crate::{ plumbing::options::{free, repo, Args, Subcommands}, shared::pretty::prepare_and_run, @@ -581,6 +581,24 @@ pub fn main() -> Result<()> { }, ), }, + Subcommands::Odb { cmd } => match cmd { + odb::Subcommands::Entries => prepare_and_run( + "repository-odb-entries", + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, _err| core::repository::odb::entries(repository()?.into(), format, out), + ), + odb::Subcommands::Info => prepare_and_run( + "repository-odb-info", + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, err| core::repository::odb::info(repository()?.into(), format, out, err), + ), + }, Subcommands::Repository(repo::Platform { cmd }) => match cmd { repo::Subcommands::Exclude { cmd } => match cmd { repo::exclude::Subcommands::Query { @@ -629,24 +647,6 @@ pub fn main() -> Result<()> { }, ), }, - repo::Subcommands::Odb { cmd } => match cmd { - repo::odb::Subcommands::Entries => prepare_and_run( - "repository-odb-entries", - verbose, - progress, - progress_keep_open, - None, - move |_progress, out, _err| core::repository::odb::entries(repository()?.into(), format, out), - ), - repo::odb::Subcommands::Info => prepare_and_run( - "repository-odb-info", - verbose, - progress, - progress_keep_open, - None, - move |_progress, out, err| core::repository::odb::info(repository()?.into(), format, out, err), - ), - }, }, }?; Ok(()) diff --git a/src/plumbing/options.rs b/src/plumbing/options.rs index 9c4cd5409fb..e4b8d9cb0c3 100644 --- a/src/plumbing/options.rs +++ b/src/plumbing/options.rs @@ -51,6 +51,11 @@ pub struct Args { #[derive(Debug, clap::Subcommand)] pub enum Subcommands { + /// Interact with the object database. + Odb { + #[clap(subcommand)] + cmd: odb::Subcommands, + }, /// Interact with tree objects. Tree { #[clap(subcommand)] @@ -78,6 +83,16 @@ pub enum Subcommands { Free(free::Subcommands), } +pub mod odb { + #[derive(Debug, clap::Subcommand)] + pub enum Subcommands { + /// Print all object names. + Entries, + /// Provide general information about the object database. + Info, + } +} + pub mod tree { #[derive(Debug, clap::Subcommand)] pub enum Subcommands { @@ -564,11 +579,6 @@ pub mod repo { #[derive(Debug, clap::Subcommand)] #[clap(visible_alias = "repo")] pub enum Subcommands { - /// Interact with the object database. - Odb { - #[clap(subcommand)] - cmd: odb::Subcommands, - }, /// Interact with the mailmap. Mailmap { #[clap(subcommand)] @@ -614,14 +624,4 @@ pub mod repo { Entries, } } - - pub mod odb { - #[derive(Debug, clap::Subcommand)] - pub enum Subcommands { - /// Print all object names. - Entries, - /// Provide general information about the object database. - Info, - } - } } From 5cf08ce3d04d635bbfee169cb77ce259efbf6bc3 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 22 Jul 2022 10:47:17 +0800 Subject: [PATCH 323/366] move 'mailmap' up one level (#331) --- src/plumbing/main.rs | 24 +++++++++++------------- src/plumbing/options.rs | 26 +++++++++++++------------- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/src/plumbing/main.rs b/src/plumbing/main.rs index 9641fededec..5ae821550ee 100644 --- a/src/plumbing/main.rs +++ b/src/plumbing/main.rs @@ -13,7 +13,7 @@ use git_repository::bstr::io::BufReadExt; use gitoxide_core as core; use gitoxide_core::pack::verify; -use crate::plumbing::options::{commit, odb, revision, tree}; +use crate::plumbing::options::{commit, mailmap, odb, revision, tree}; use crate::{ plumbing::options::{free, repo, Args, Subcommands}, shared::pretty::prepare_and_run, @@ -599,6 +599,16 @@ pub fn main() -> Result<()> { move |_progress, out, err| core::repository::odb::info(repository()?.into(), format, out, err), ), }, + Subcommands::Mailmap { cmd } => match cmd { + mailmap::Subcommands::Entries => prepare_and_run( + "repository-mailmap-entries", + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, err| core::repository::mailmap::entries(repository()?.into(), format, out, err), + ), + }, Subcommands::Repository(repo::Platform { cmd }) => match cmd { repo::Subcommands::Exclude { cmd } => match cmd { repo::exclude::Subcommands::Query { @@ -635,18 +645,6 @@ pub fn main() -> Result<()> { }, ), }, - repo::Subcommands::Mailmap { cmd } => match cmd { - repo::mailmap::Subcommands::Entries => prepare_and_run( - "repository-mailmap-entries", - verbose, - progress, - progress_keep_open, - None, - move |_progress, out, err| { - core::repository::mailmap::entries(repository()?.into(), format, out, err) - }, - ), - }, }, }?; Ok(()) diff --git a/src/plumbing/options.rs b/src/plumbing/options.rs index e4b8d9cb0c3..9d20138cf67 100644 --- a/src/plumbing/options.rs +++ b/src/plumbing/options.rs @@ -76,6 +76,11 @@ pub enum Subcommands { #[clap(subcommand)] cmd: revision::Subcommands, }, + /// Interact with the mailmap. + Mailmap { + #[clap(subcommand)] + cmd: mailmap::Subcommands, + }, /// Subcommands for interacting with entire git repositories Repository(repo::Platform), /// Subcommands that need no git repository to run. @@ -83,6 +88,14 @@ pub enum Subcommands { Free(free::Subcommands), } +pub mod mailmap { + #[derive(Debug, clap::Subcommand)] + pub enum Subcommands { + /// Print all entries in configured mailmaps, inform about errors as well. + Entries, + } +} + pub mod odb { #[derive(Debug, clap::Subcommand)] pub enum Subcommands { @@ -579,11 +592,6 @@ pub mod repo { #[derive(Debug, clap::Subcommand)] #[clap(visible_alias = "repo")] pub enum Subcommands { - /// Interact with the mailmap. - Mailmap { - #[clap(subcommand)] - cmd: mailmap::Subcommands, - }, /// Interact with the exclude files like .gitignore. Exclude { #[clap(subcommand)] @@ -616,12 +624,4 @@ pub mod repo { }, } } - - pub mod mailmap { - #[derive(Debug, clap::Subcommand)] - pub enum Subcommands { - /// Print all entries in configured mailmaps, inform about errors as well. - Entries, - } - } } From 8e5b796ea3fd760839f3c29a4f65bb42b1f3e893 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 22 Jul 2022 10:48:37 +0800 Subject: [PATCH 324/366] move 'exclude' up one level and dissolve 'repo' subcommand (#331) --- src/plumbing/main.rs | 74 ++++++++++++++++++++--------------------- src/plumbing/options.rs | 65 +++++++++++++----------------------- 2 files changed, 60 insertions(+), 79 deletions(-) diff --git a/src/plumbing/main.rs b/src/plumbing/main.rs index 5ae821550ee..a85f962dbc4 100644 --- a/src/plumbing/main.rs +++ b/src/plumbing/main.rs @@ -13,9 +13,9 @@ use git_repository::bstr::io::BufReadExt; use gitoxide_core as core; use gitoxide_core::pack::verify; -use crate::plumbing::options::{commit, mailmap, odb, revision, tree}; +use crate::plumbing::options::{commit, exclude, mailmap, odb, revision, tree}; use crate::{ - plumbing::options::{free, repo, Args, Subcommands}, + plumbing::options::{free, Args, Subcommands}, shared::pretty::prepare_and_run, }; @@ -609,42 +609,40 @@ pub fn main() -> Result<()> { move |_progress, out, err| core::repository::mailmap::entries(repository()?.into(), format, out, err), ), }, - Subcommands::Repository(repo::Platform { cmd }) => match cmd { - repo::Subcommands::Exclude { cmd } => match cmd { - repo::exclude::Subcommands::Query { - patterns, - pathspecs, - show_ignore_patterns, - } => prepare_and_run( - "repository-exclude-query", - verbose, - progress, - progress_keep_open, - None, - move |_progress, out, _err| { - use git::bstr::ByteSlice; - core::repository::exclude::query( - repository()?.into(), - if pathspecs.is_empty() { - Box::new( - stdin_or_bail()? - .byte_lines() - .filter_map(Result::ok) - .filter_map(|line| git::path::Spec::from_bytes(line.as_bstr())), - ) as Box> - } else { - Box::new(pathspecs.into_iter()) - }, - out, - core::repository::exclude::query::Options { - format, - show_ignore_patterns, - overrides: patterns, - }, - ) - }, - ), - }, + Subcommands::Exclude { cmd } => match cmd { + exclude::Subcommands::Query { + patterns, + pathspecs, + show_ignore_patterns, + } => prepare_and_run( + "repository-exclude-query", + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, _err| { + use git::bstr::ByteSlice; + core::repository::exclude::query( + repository()?.into(), + if pathspecs.is_empty() { + Box::new( + stdin_or_bail()? + .byte_lines() + .filter_map(Result::ok) + .filter_map(|line| git::path::Spec::from_bytes(line.as_bstr())), + ) as Box> + } else { + Box::new(pathspecs.into_iter()) + }, + out, + core::repository::exclude::query::Options { + format, + show_ignore_patterns, + overrides: patterns, + }, + ) + }, + ), }, }?; Ok(()) diff --git a/src/plumbing/options.rs b/src/plumbing/options.rs index 9d20138cf67..e493121e12b 100644 --- a/src/plumbing/options.rs +++ b/src/plumbing/options.rs @@ -81,8 +81,11 @@ pub enum Subcommands { #[clap(subcommand)] cmd: mailmap::Subcommands, }, - /// Subcommands for interacting with entire git repositories - Repository(repo::Platform), + /// Interact with the exclude files like .gitignore. + Exclude { + #[clap(subcommand)] + cmd: exclude::Subcommands, + }, /// Subcommands that need no git repository to run. #[clap(subcommand)] Free(free::Subcommands), @@ -580,48 +583,28 @@ pub mod free { } } -/// -pub mod repo { - #[derive(Debug, clap::Parser)] - pub struct Platform { - /// Subcommands - #[clap(subcommand)] - pub cmd: Subcommands, - } +pub mod exclude { + use std::ffi::OsString; + + use git_repository as git; #[derive(Debug, clap::Subcommand)] - #[clap(visible_alias = "repo")] pub enum Subcommands { - /// Interact with the exclude files like .gitignore. - Exclude { - #[clap(subcommand)] - cmd: exclude::Subcommands, + /// Check if path-specs are excluded and print the result similar to `git check-ignore`. + Query { + /// Show actual ignore patterns instead of un-excluding an entry. + /// + /// That way one can understand why an entry might not be excluded. + #[clap(long, short = 'i')] + show_ignore_patterns: bool, + /// Additional patterns to use for exclusions. They have the highest priority. + /// + /// Useful for undoing previous patterns using the '!' prefix. + #[clap(long, short = 'p')] + patterns: Vec, + /// The git path specifications to check for exclusion, or unset to read from stdin one per line. + #[clap(parse(try_from_os_str = std::convert::TryFrom::try_from))] + pathspecs: Vec, }, } - - pub mod exclude { - use std::ffi::OsString; - - use git_repository as git; - - #[derive(Debug, clap::Subcommand)] - pub enum Subcommands { - /// Check if path-specs are excluded and print the result similar to `git check-ignore`. - Query { - /// Show actual ignore patterns instead of un-excluding an entry. - /// - /// That way one can understand why an entry might not be excluded. - #[clap(long, short = 'i')] - show_ignore_patterns: bool, - /// Additional patterns to use for exclusions. They have the highest priority. - /// - /// Useful for undoing previous patterns using the '!' prefix. - #[clap(long, short = 'p')] - patterns: Vec, - /// The git path specifications to check for exclusion, or unset to read from stdin one per line. - #[clap(parse(try_from_os_str = std::convert::TryFrom::try_from))] - pathspecs: Vec, - }, - } - } } From a437abe8e77ad07bf25a16f19ca046ebdaef42d6 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 22 Jul 2022 10:58:31 +0800 Subject: [PATCH 325/366] refactor (#331) --- src/plumbing/main.rs | 12 ++++++------ src/plumbing/options.rs | 36 ++++++++++++------------------------ 2 files changed, 18 insertions(+), 30 deletions(-) diff --git a/src/plumbing/main.rs b/src/plumbing/main.rs index a85f962dbc4..573cd8411c9 100644 --- a/src/plumbing/main.rs +++ b/src/plumbing/main.rs @@ -503,7 +503,7 @@ pub fn main() -> Result<()> { ) }, ), - Subcommands::Revision { cmd } => match cmd { + Subcommands::Revision(cmd) => match cmd { revision::Subcommands::Explain { spec } => prepare_and_run( "repository-commit-describe", verbose, @@ -513,7 +513,7 @@ pub fn main() -> Result<()> { move |_progress, out, _err| core::repository::revision::explain(repository()?.into(), spec, out), ), }, - Subcommands::Commit { cmd } => match cmd { + Subcommands::Commit(cmd) => match cmd { commit::Subcommands::Describe { annotated_tags, all_refs, @@ -548,7 +548,7 @@ pub fn main() -> Result<()> { }, ), }, - Subcommands::Tree { cmd } => match cmd { + Subcommands::Tree(cmd) => match cmd { tree::Subcommands::Entries { treeish, recursive, @@ -581,7 +581,7 @@ pub fn main() -> Result<()> { }, ), }, - Subcommands::Odb { cmd } => match cmd { + Subcommands::Odb(cmd) => match cmd { odb::Subcommands::Entries => prepare_and_run( "repository-odb-entries", verbose, @@ -599,7 +599,7 @@ pub fn main() -> Result<()> { move |_progress, out, err| core::repository::odb::info(repository()?.into(), format, out, err), ), }, - Subcommands::Mailmap { cmd } => match cmd { + Subcommands::Mailmap(cmd) => match cmd { mailmap::Subcommands::Entries => prepare_and_run( "repository-mailmap-entries", verbose, @@ -609,7 +609,7 @@ pub fn main() -> Result<()> { move |_progress, out, err| core::repository::mailmap::entries(repository()?.into(), format, out, err), ), }, - Subcommands::Exclude { cmd } => match cmd { + Subcommands::Exclude(cmd) => match cmd { exclude::Subcommands::Query { patterns, pathspecs, diff --git a/src/plumbing/options.rs b/src/plumbing/options.rs index e493121e12b..4ccff644cf9 100644 --- a/src/plumbing/options.rs +++ b/src/plumbing/options.rs @@ -52,40 +52,28 @@ pub struct Args { #[derive(Debug, clap::Subcommand)] pub enum Subcommands { /// Interact with the object database. - Odb { - #[clap(subcommand)] - cmd: odb::Subcommands, - }, + #[clap(subcommand)] + Odb(odb::Subcommands), /// Interact with tree objects. - Tree { - #[clap(subcommand)] - cmd: tree::Subcommands, - }, + #[clap(subcommand)] + Tree(tree::Subcommands), /// Interact with commit objects. - Commit { - #[clap(subcommand)] - cmd: commit::Subcommands, - }, + #[clap(subcommand)] + Commit(commit::Subcommands), /// Verify the integrity of the entire repository Verify { #[clap(flatten)] args: free::pack::VerifyOptions, }, /// Query and obtain information about revisions. - Revision { - #[clap(subcommand)] - cmd: revision::Subcommands, - }, + #[clap(subcommand)] + Revision(revision::Subcommands), /// Interact with the mailmap. - Mailmap { - #[clap(subcommand)] - cmd: mailmap::Subcommands, - }, + #[clap(subcommand)] + Mailmap(mailmap::Subcommands), /// Interact with the exclude files like .gitignore. - Exclude { - #[clap(subcommand)] - cmd: exclude::Subcommands, - }, + #[clap(subcommand)] + Exclude(exclude::Subcommands), /// Subcommands that need no git repository to run. #[clap(subcommand)] Free(free::Subcommands), From d99453ebeb970ed493be236def299d1e82b01f83 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 22 Jul 2022 11:12:07 +0800 Subject: [PATCH 326/366] feat: `gix config` lists all entries of all configuration files git considers. (#331) Filters allow to narrow down the output. --- git-repository/src/repository/config.rs | 1 + gitoxide-core/src/repository/config.rs | 18 +++++++++++++++ gitoxide-core/src/repository/mod.rs | 2 ++ src/plumbing/main.rs | 29 ++++++++++++++++--------- src/plumbing/options.rs | 11 ++++++++++ 5 files changed, 51 insertions(+), 10 deletions(-) create mode 100644 gitoxide-core/src/repository/config.rs diff --git a/git-repository/src/repository/config.rs b/git-repository/src/repository/config.rs index 6809d074aa9..dd5cf205356 100644 --- a/git-repository/src/repository/config.rs +++ b/git-repository/src/repository/config.rs @@ -2,6 +2,7 @@ use crate::config; /// Configuration impl crate::Repository { + /// Return /// Return a snapshot of the configuration as seen upon opening the repository. pub fn config_snapshot(&self) -> config::Snapshot<'_> { config::Snapshot { repo: self } diff --git a/gitoxide-core/src/repository/config.rs b/gitoxide-core/src/repository/config.rs new file mode 100644 index 00000000000..60f58632540 --- /dev/null +++ b/gitoxide-core/src/repository/config.rs @@ -0,0 +1,18 @@ +use crate::OutputFormat; +use anyhow::{bail, Result}; +use git_repository as git; + +pub fn list( + repo: git::Repository, + _filters: Vec, + format: OutputFormat, + out: impl std::io::Write, +) -> Result<()> { + if format != OutputFormat::Human { + bail!("Only human output format is supported at the moment"); + } + let config = repo.config_snapshot(); + let config = config.plumbing(); + config.write_to(out)?; + Ok(()) +} diff --git a/gitoxide-core/src/repository/mod.rs b/gitoxide-core/src/repository/mod.rs index d7d620fb28c..71ce148d319 100644 --- a/gitoxide-core/src/repository/mod.rs +++ b/gitoxide-core/src/repository/mod.rs @@ -14,6 +14,8 @@ pub fn init(directory: Option) -> Result Result<()> { })?; match cmd { + Subcommands::Config(config::Platform { filter }) => prepare_and_run( + "config-list", + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, _err| core::repository::config::list(repository()?.into(), filter, format, out), + ) + .map(|_| ()), Subcommands::Free(subcommands) => match subcommands { #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] free::Subcommands::Remote(subcommands) => match subcommands { @@ -483,7 +492,7 @@ pub fn main() -> Result<()> { re_encode, }, } => prepare_and_run( - "repository-verify", + "verify", verbose, progress, progress_keep_open, @@ -505,7 +514,7 @@ pub fn main() -> Result<()> { ), Subcommands::Revision(cmd) => match cmd { revision::Subcommands::Explain { spec } => prepare_and_run( - "repository-commit-describe", + "commit-describe", verbose, progress, progress_keep_open, @@ -524,7 +533,7 @@ pub fn main() -> Result<()> { max_candidates, rev_spec, } => prepare_and_run( - "repository-commit-describe", + "commit-describe", verbose, progress, progress_keep_open, @@ -554,7 +563,7 @@ pub fn main() -> Result<()> { recursive, extended, } => prepare_and_run( - "repository-tree-entries", + "tree-entries", verbose, progress, progress_keep_open, @@ -571,7 +580,7 @@ pub fn main() -> Result<()> { }, ), tree::Subcommands::Info { treeish, extended } => prepare_and_run( - "repository-tree-info", + "tree-info", verbose, progress, progress_keep_open, @@ -583,7 +592,7 @@ pub fn main() -> Result<()> { }, Subcommands::Odb(cmd) => match cmd { odb::Subcommands::Entries => prepare_and_run( - "repository-odb-entries", + "odb-entries", verbose, progress, progress_keep_open, @@ -591,7 +600,7 @@ pub fn main() -> Result<()> { move |_progress, out, _err| core::repository::odb::entries(repository()?.into(), format, out), ), odb::Subcommands::Info => prepare_and_run( - "repository-odb-info", + "odb-info", verbose, progress, progress_keep_open, @@ -601,7 +610,7 @@ pub fn main() -> Result<()> { }, Subcommands::Mailmap(cmd) => match cmd { mailmap::Subcommands::Entries => prepare_and_run( - "repository-mailmap-entries", + "mailmap-entries", verbose, progress, progress_keep_open, @@ -615,7 +624,7 @@ pub fn main() -> Result<()> { pathspecs, show_ignore_patterns, } => prepare_and_run( - "repository-exclude-query", + "exclude-query", verbose, progress, progress_keep_open, diff --git a/src/plumbing/options.rs b/src/plumbing/options.rs index 4ccff644cf9..9c49b160544 100644 --- a/src/plumbing/options.rs +++ b/src/plumbing/options.rs @@ -74,11 +74,22 @@ pub enum Subcommands { /// Interact with the exclude files like .gitignore. #[clap(subcommand)] Exclude(exclude::Subcommands), + Config(config::Platform), /// Subcommands that need no git repository to run. #[clap(subcommand)] Free(free::Subcommands), } +pub mod config { + /// Print all entries in a configuration file or access other sub-commands + #[derive(Debug, clap::Parser)] + #[clap(subcommand_required(false))] + pub struct Platform { + /// The filter terms to limit the output to matching sections and values only. + pub filter: Vec, + } +} + pub mod mailmap { #[derive(Debug, clap::Subcommand)] pub enum Subcommands { From 657080829867d9dcb0c9b9cb6c1c8126c4df3783 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 22 Jul 2022 12:07:16 +0800 Subject: [PATCH 327/366] feat: `git-config` is now accessible in `git-repository::config`. (#331) --- git-repository/src/config/mod.rs | 4 ++++ git-repository/src/lib.rs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/git-repository/src/config/mod.rs b/git-repository/src/config/mod.rs index d2e31a8be61..ce547f800fb 100644 --- a/git-repository/src/config/mod.rs +++ b/git-repository/src/config/mod.rs @@ -2,6 +2,8 @@ use crate::repository::identity; use crate::{bstr::BString, permission, Repository}; use git_features::threading::OnceCell; +pub use git_config::*; + pub(crate) mod cache; mod snapshot; @@ -18,7 +20,9 @@ pub(crate) mod section { } } +/// The error returned when failing to initialize the repository configuration. #[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] pub enum Error { #[error("Could not read configuration file")] Io(#[from] std::io::Error), diff --git a/git-repository/src/lib.rs b/git-repository/src/lib.rs index d4bd82d34c1..f0ece8c1221 100644 --- a/git-repository/src/lib.rs +++ b/git-repository/src/lib.rs @@ -297,7 +297,7 @@ pub mod create; pub mod open; /// -mod config; +pub mod config; /// pub mod mailmap { From eda39ec7d736d49af1ad9e2ad775e4aa12b264b7 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 21 Jul 2022 11:04:17 +0800 Subject: [PATCH 328/366] feat: `gix config` with section and sub-section filtering. (#331) --- gitoxide-core/src/repository/config.rs | 85 +++++++++++++++++++++++++- src/plumbing/options.rs | 5 +- 2 files changed, 86 insertions(+), 4 deletions(-) diff --git a/gitoxide-core/src/repository/config.rs b/gitoxide-core/src/repository/config.rs index 60f58632540..b9654a03e30 100644 --- a/gitoxide-core/src/repository/config.rs +++ b/gitoxide-core/src/repository/config.rs @@ -4,15 +4,94 @@ use git_repository as git; pub fn list( repo: git::Repository, - _filters: Vec, + filters: Vec, format: OutputFormat, - out: impl std::io::Write, + mut out: impl std::io::Write, ) -> Result<()> { if format != OutputFormat::Human { bail!("Only human output format is supported at the moment"); } let config = repo.config_snapshot(); let config = config.plumbing(); - config.write_to(out)?; + if let Some(frontmatter) = config.frontmatter() { + for event in frontmatter { + event.write_to(&mut out)?; + } + } + let filters: Vec<_> = filters.into_iter().map(Filter::new).collect(); + let mut last_meta = None; + for (section, matter) in config.sections_and_postmatter() { + if !filters.is_empty() && !filters.iter().any(|filter| filter.matches_section(section)) { + continue; + } + + let meta = section.meta(); + if last_meta.map_or(true, |last| last != meta) { + write_meta(meta, &mut out)?; + } + last_meta = Some(meta); + + section.write_to(&mut out)?; + for event in matter { + event.write_to(&mut out)?; + } + writeln!(&mut out)?; + } Ok(()) } + +struct Filter { + name: String, + subsection: Option, +} + +impl Filter { + fn new(input: String) -> Self { + match git::config::parse::key(&input) { + Some(key) => Filter { + name: key.section_name.into(), + subsection: key.subsection_name.map(ToOwned::to_owned), + }, + None => Filter { + name: input, + subsection: None, + }, + } + } + + fn matches_section(&self, section: &git::config::file::Section<'_>) -> bool { + let ignore_case = git::glob::wildmatch::Mode::IGNORE_CASE; + + if !git::glob::wildmatch(self.name.as_bytes().into(), section.header().name(), ignore_case) { + return false; + } + match (self.subsection.as_deref(), section.header().subsection_name()) { + (Some(filter), Some(name)) => { + if !git::glob::wildmatch(filter.as_bytes().into(), name, ignore_case) { + return false; + } + } + (None, None) | (None, Some(_)) => {} + _ => return false, + }; + true + } +} + +fn write_meta(meta: &git::config::file::Metadata, out: &mut impl std::io::Write) -> std::io::Result<()> { + writeln!( + out, + "# From '{}' ({:?}{}{})", + meta.path + .as_deref() + .map(|p| p.display().to_string()) + .unwrap_or_else(|| "memory".into()), + meta.source, + (meta.level != 0) + .then(|| format!(", include level {}", meta.level)) + .unwrap_or_default(), + (meta.trust != git::sec::Trust::Full) + .then(|| "untrusted") + .unwrap_or_default() + ) +} diff --git a/src/plumbing/options.rs b/src/plumbing/options.rs index 9c49b160544..abed5255313 100644 --- a/src/plumbing/options.rs +++ b/src/plumbing/options.rs @@ -85,7 +85,10 @@ pub mod config { #[derive(Debug, clap::Parser)] #[clap(subcommand_required(false))] pub struct Platform { - /// The filter terms to limit the output to matching sections and values only. + /// The filter terms to limit the output to matching sections and subsections only. + /// + /// Typical filters are `branch` or `remote.origin` or `remote.or*` - git-style globs are supported + /// and comparisons are case-insensitive. pub filter: Vec, } } From 1bc96bf378d198b012efce9ec9e5b244a91f62bc Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 21 Jul 2022 11:57:39 +0800 Subject: [PATCH 329/366] feat: following includes is now non-fatal by default (#331) Otherwise it would be relatively easy to fail gitoxide startup, and we want to be closer to the behaviour in git which ignores most of the errors. --- .../src/file/{includes.rs => includes/mod.rs} | 183 ++++++------------ git-config/src/file/includes/types.rs | 132 +++++++++++++ git-config/tests/file/init/from_env.rs | 3 +- .../init/from_paths/includes/unconditional.rs | 6 +- git-config/tests/file/mod.rs | 1 + git-config/tests/file/resolve_includes.rs | 39 ++++ 6 files changed, 235 insertions(+), 129 deletions(-) rename git-config/src/file/{includes.rs => includes/mod.rs} (62%) create mode 100644 git-config/src/file/includes/types.rs create mode 100644 git-config/tests/file/resolve_includes.rs diff --git a/git-config/src/file/includes.rs b/git-config/src/file/includes/mod.rs similarity index 62% rename from git-config/src/file/includes.rs rename to git-config/src/file/includes/mod.rs index 1dd36f0d404..4cbbec57980 100644 --- a/git-config/src/file/includes.rs +++ b/git-config/src/file/includes/mod.rs @@ -7,8 +7,8 @@ use bstr::{BStr, BString, ByteSlice, ByteVec}; use git_features::threading::OwnShared; use git_ref::Category; -use crate::file::{init, Metadata, SectionId}; -use crate::{file, File}; +use crate::file::{includes, init, Metadata, SectionId}; +use crate::{file, path, File}; impl File<'static> { /// Traverse all `include` and `includeIf` directives found in this instance and follow them, loading the @@ -42,7 +42,7 @@ fn resolve_includes_recursive( options: init::Options<'_>, ) -> Result<(), Error> { if depth == options.includes.max_depth { - return if options.includes.error_on_max_depth_exceeded { + return if options.includes.err_on_max_depth_exceeded { Err(Error::IncludeDepthExceeded { max_depth: options.includes.max_depth, }) @@ -84,7 +84,10 @@ fn append_followed_includes_recursively( for (section_id, config_path) in section_ids_and_include_paths { let meta = OwnShared::clone(&target_config.sections[§ion_id].meta); let target_config_path = meta.path.as_deref(); - let config_path = resolve_path(config_path, target_config_path, options.includes.interpolate)?; + let config_path = match resolve_path(config_path, target_config_path, options.includes)? { + Some(p) => p, + None => continue, + }; if !config_path.is_file() { continue; } @@ -189,14 +192,25 @@ fn gitdir_matches( Options { conditional: conditional::Context { git_dir, .. }, interpolate: context, + err_on_interpolation_failure, + err_on_missing_config_path, .. }: Options<'_>, wildmatch_mode: git_glob::wildmatch::Mode, ) -> Result { + if !err_on_interpolation_failure && git_dir.is_none() { + return Ok(false); + } let git_dir = git_path::to_unix_separators_on_windows(git_path::into_bstr(git_dir.ok_or(Error::MissingGitDir)?)); let mut pattern_path: Cow<'_, _> = { - let path = crate::Path::from(Cow::Borrowed(condition_path)).interpolate(context)?; + let path = match check_interpolation_result( + err_on_interpolation_failure, + crate::Path::from(Cow::Borrowed(condition_path)).interpolate(context), + )? { + Some(p) => p, + None => return Ok(false), + }; git_path::into_bstr(path).into_owned().into() }; // NOTE: yes, only if we do path interpolation will the slashes be forced to unix separators on windows @@ -205,6 +219,9 @@ fn gitdir_matches( } if let Some(relative_pattern_path) = pattern_path.strip_prefix(b"./") { + if !err_on_missing_config_path && target_config_path.is_none() { + return Ok(false); + } let parent_dir = target_config_path .ok_or(Error::MissingConfigPath)? .parent() @@ -243,13 +260,44 @@ fn gitdir_matches( )) } +fn check_interpolation_result( + disable: bool, + res: Result, path::interpolate::Error>, +) -> Result>, path::interpolate::Error> { + if disable { + return res.map(Some); + } + match res { + Ok(good) => Ok(good.into()), + Err(err) => match err { + path::interpolate::Error::Missing { .. } | path::interpolate::Error::UserInterpolationUnsupported => { + Ok(None) + } + path::interpolate::Error::UsernameConversion(_) | path::interpolate::Error::Utf8Conversion { .. } => { + Err(err) + } + }, + } +} + fn resolve_path( path: crate::Path<'_>, target_config_path: Option<&Path>, - context: crate::path::interpolate::Context<'_>, -) -> Result { - let path = path.interpolate(context)?; + includes::Options { + interpolate: context, + err_on_interpolation_failure, + err_on_missing_config_path, + .. + }: includes::Options<'_>, +) -> Result, Error> { + let path = match check_interpolation_result(err_on_interpolation_failure, path.interpolate(context))? { + Some(p) => p, + None => return Ok(None), + }; let path: PathBuf = if path.is_relative() { + if !err_on_missing_config_path && target_config_path.is_none() { + return Ok(None); + } target_config_path .ok_or(Error::MissingConfigPath)? .parent() @@ -258,123 +306,8 @@ fn resolve_path( } else { path.into() }; - Ok(path) + Ok(Some(path)) } -mod types { - use crate::parse; - use crate::path::interpolate; - - /// The error returned when following includes. - #[derive(Debug, thiserror::Error)] - #[allow(missing_docs)] - pub enum Error { - #[error(transparent)] - Io(#[from] std::io::Error), - #[error(transparent)] - Parse(#[from] parse::Error), - #[error(transparent)] - Interpolate(#[from] interpolate::Error), - #[error("The maximum allowed length {} of the file include chain built by following nested resolve_includes is exceeded", .max_depth)] - IncludeDepthExceeded { max_depth: u8 }, - #[error( - "Include paths from environment variables must not be relative as no config file paths exists as root" - )] - MissingConfigPath, - #[error("The git directory must be provided to support `gitdir:` conditional includes")] - MissingGitDir, - #[error(transparent)] - Realpath(#[from] git_path::realpath::Error), - } - - /// Options to handle includes, like `include.path` or `includeIf..path`, - #[derive(Clone, Copy)] - pub struct Options<'a> { - /// The maximum allowed length of the file include chain built by following nested resolve_includes where base level is depth = 0. - pub max_depth: u8, - /// When max depth is exceeded while following nested includes, - /// return an error if true or silently stop following resolve_includes. - /// - /// Setting this value to false allows to read configuration with cycles, - /// which otherwise always results in an error. - pub error_on_max_depth_exceeded: bool, - - /// Used during path interpolation, both for include paths before trying to read the file, and for - /// paths used in conditional `gitdir` includes. - pub interpolate: interpolate::Context<'a>, - - /// Additional context for conditional includes to work. - pub conditional: conditional::Context<'a>, - } - - impl<'a> Options<'a> { - /// Provide options to never follow include directives at all. - pub fn no_follow() -> Self { - Options { - max_depth: 0, - error_on_max_depth_exceeded: false, - interpolate: Default::default(), - conditional: Default::default(), - } - } - /// Provide options to follow includes like git does, provided the required `conditional` and `interpolate` contexts - /// to support `gitdir` and `onbranch` based `includeIf` directives as well as standard `include.path` resolution. - /// Note that the follow-mode is `git`-style, following at most 10 indirections while - /// producing an error if the depth is exceeded. - pub fn follow(interpolate: interpolate::Context<'a>, conditional: conditional::Context<'a>) -> Self { - Options { - max_depth: 10, - error_on_max_depth_exceeded: true, - interpolate, - conditional, - } - } - - /// Like [`follow`][Options::follow()], but without information to resolve `includeIf` directories as well as default - /// configuration to allow resolving `~username/` path. `home_dir` is required to resolve `~/` paths if set. - /// Note that `%(prefix)` paths cannot be interpolated with this configuration, use [`follow()`][Options::follow()] - /// instead for complete control. - pub fn follow_without_conditional(home_dir: Option<&'a std::path::Path>) -> Self { - Options { - max_depth: 10, - error_on_max_depth_exceeded: true, - interpolate: interpolate::Context { - git_install_dir: None, - home_dir, - home_for_user: Some(interpolate::home_for_user), - }, - conditional: Default::default(), - } - } - - /// Set the context used for interpolation when interpolating paths to include as well as the paths - /// in `gitdir` conditional includes. - pub fn interpolate_with(mut self, context: interpolate::Context<'a>) -> Self { - self.interpolate = context; - self - } - } - - impl Default for Options<'_> { - fn default() -> Self { - Self::no_follow() - } - } - - /// - pub mod conditional { - /// Options to handle conditional includes like `includeIf..path`. - #[derive(Clone, Copy, Default)] - pub struct Context<'a> { - /// The location of the .git directory. If `None`, `gitdir` conditions cause an error. - /// - /// Used for conditional includes, e.g. `includeIf.gitdir:…` or `includeIf:gitdir/i…`. - pub git_dir: Option<&'a std::path::Path>, - /// The name of the branch that is currently checked out. If `None`, `onbranch` conditions cause an error. - /// - /// Used for conditional includes, e.g. `includeIf.onbranch:main.…` - pub branch_name: Option<&'a git_ref::FullNameRef>, - } - } -} +mod types; pub use types::{conditional, Error, Options}; diff --git a/git-config/src/file/includes/types.rs b/git-config/src/file/includes/types.rs new file mode 100644 index 00000000000..b97b28e8e99 --- /dev/null +++ b/git-config/src/file/includes/types.rs @@ -0,0 +1,132 @@ +use crate::parse; +use crate::path::interpolate; + +/// The error returned when following includes. +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error { + #[error(transparent)] + Io(#[from] std::io::Error), + #[error(transparent)] + Parse(#[from] parse::Error), + #[error(transparent)] + Interpolate(#[from] interpolate::Error), + #[error("The maximum allowed length {} of the file include chain built by following nested resolve_includes is exceeded", .max_depth)] + IncludeDepthExceeded { max_depth: u8 }, + #[error("Include paths from environment variables must not be relative as no config file paths exists as root")] + MissingConfigPath, + #[error("The git directory must be provided to support `gitdir:` conditional includes")] + MissingGitDir, + #[error(transparent)] + Realpath(#[from] git_path::realpath::Error), +} + +/// Options to handle includes, like `include.path` or `includeIf..path`, +#[derive(Clone, Copy)] +pub struct Options<'a> { + /// The maximum allowed length of the file include chain built by following nested resolve_includes where base level is depth = 0. + pub max_depth: u8, + /// When max depth is exceeded while following nested includes, + /// return an error if true or silently stop following resolve_includes. + /// + /// Setting this value to false allows to read configuration with cycles, + /// which otherwise always results in an error. + pub err_on_max_depth_exceeded: bool, + /// If true, default false, failing to interpolate paths will result in an error. + /// + /// Interpolation also happens if paths in conditional includes can't be interpolated. + pub err_on_interpolation_failure: bool, + /// If true, default true, configuration not originating from a path will cause errors when trying to resolve + /// relative include paths (which would require the including configuration's path). + pub err_on_missing_config_path: bool, + /// Used during path interpolation, both for include paths before trying to read the file, and for + /// paths used in conditional `gitdir` includes. + pub interpolate: interpolate::Context<'a>, + + /// Additional context for conditional includes to work. + pub conditional: conditional::Context<'a>, +} + +impl<'a> Options<'a> { + /// Provide options to never follow include directives at all. + pub fn no_follow() -> Self { + Options { + max_depth: 0, + err_on_max_depth_exceeded: false, + err_on_interpolation_failure: false, + err_on_missing_config_path: false, + interpolate: Default::default(), + conditional: Default::default(), + } + } + /// Provide options to follow includes like git does, provided the required `conditional` and `interpolate` contexts + /// to support `gitdir` and `onbranch` based `includeIf` directives as well as standard `include.path` resolution. + /// Note that the follow-mode is `git`-style, following at most 10 indirections while + /// producing an error if the depth is exceeded. + pub fn follow(interpolate: interpolate::Context<'a>, conditional: conditional::Context<'a>) -> Self { + Options { + max_depth: 10, + err_on_max_depth_exceeded: true, + err_on_interpolation_failure: false, + err_on_missing_config_path: true, + interpolate, + conditional, + } + } + + /// For use with `follow` type options, cause failure if an include path couldn't be interpolated or the depth limit is exceeded. + pub fn strict(mut self) -> Self { + self.err_on_interpolation_failure = true; + self.err_on_max_depth_exceeded = true; + self.err_on_missing_config_path = true; + self + } + + /// Like [`follow`][Options::follow()], but without information to resolve `includeIf` directories as well as default + /// configuration to allow resolving `~username/` path. `home_dir` is required to resolve `~/` paths if set. + /// Note that `%(prefix)` paths cannot be interpolated with this configuration, use [`follow()`][Options::follow()] + /// instead for complete control. + pub fn follow_without_conditional(home_dir: Option<&'a std::path::Path>) -> Self { + Options { + max_depth: 10, + err_on_max_depth_exceeded: true, + err_on_interpolation_failure: false, + err_on_missing_config_path: true, + interpolate: interpolate::Context { + git_install_dir: None, + home_dir, + home_for_user: Some(interpolate::home_for_user), + }, + conditional: Default::default(), + } + } + + /// Set the context used for interpolation when interpolating paths to include as well as the paths + /// in `gitdir` conditional includes. + pub fn interpolate_with(mut self, context: interpolate::Context<'a>) -> Self { + self.interpolate = context; + self + } +} + +impl Default for Options<'_> { + fn default() -> Self { + Self::no_follow() + } +} + +/// +pub mod conditional { + /// Options to handle conditional includes like `includeIf..path`. + #[derive(Clone, Copy, Default)] + pub struct Context<'a> { + /// The location of the .git directory. If `None`, `gitdir` conditions cause an error. + /// + /// Used for conditional includes, e.g. `includeIf.gitdir:…` or `includeIf:gitdir/i…`. + pub git_dir: Option<&'a std::path::Path>, + /// The name of the branch that is currently checked out. If `None`, `onbranch` conditions cause an error. + /// + /// Used for conditional includes, e.g. `includeIf.onbranch:main.…` + pub branch_name: Option<&'a git_ref::FullNameRef>, + } +} diff --git a/git-config/tests/file/init/from_env.rs b/git-config/tests/file/init/from_env.rs index 1b9df74502b..f8dca3efebc 100644 --- a/git-config/tests/file/init/from_env.rs +++ b/git-config/tests/file/init/from_env.rs @@ -90,7 +90,8 @@ fn error_on_relative_paths_in_include_paths() { includes: includes::Options { max_depth: 1, ..Default::default() - }, + } + .strict(), ..Default::default() }); assert!(matches!( diff --git a/git-config/tests/file/init/from_paths/includes/unconditional.rs b/git-config/tests/file/init/from_paths/includes/unconditional.rs index c5e525bb3c2..23184b087af 100644 --- a/git-config/tests/file/init/from_paths/includes/unconditional.rs +++ b/git-config/tests/file/init/from_paths/includes/unconditional.rs @@ -124,7 +124,7 @@ fn respect_max_depth() -> crate::Result { init::Options { includes: includes::Options { max_depth, - error_on_max_depth_exceeded, + err_on_max_depth_exceeded: error_on_max_depth_exceeded, ..Default::default() }, ..Default::default() @@ -247,7 +247,7 @@ fn cycle_detection() -> crate::Result { let options = init::Options { includes: includes::Options { max_depth: 4, - error_on_max_depth_exceeded: true, + err_on_max_depth_exceeded: true, ..Default::default() }, ..Default::default() @@ -263,7 +263,7 @@ fn cycle_detection() -> crate::Result { let options = init::Options { includes: includes::Options { max_depth: 4, - error_on_max_depth_exceeded: false, + err_on_max_depth_exceeded: false, ..Default::default() }, ..Default::default() diff --git a/git-config/tests/file/mod.rs b/git-config/tests/file/mod.rs index 1a7df50c946..2a1133acc15 100644 --- a/git-config/tests/file/mod.rs +++ b/git-config/tests/file/mod.rs @@ -29,4 +29,5 @@ mod access; mod impls; mod init; mod mutable; +mod resolve_includes; mod write; diff --git a/git-config/tests/file/resolve_includes.rs b/git-config/tests/file/resolve_includes.rs new file mode 100644 index 00000000000..2ec3b9a53b2 --- /dev/null +++ b/git-config/tests/file/resolve_includes.rs @@ -0,0 +1,39 @@ +use git_config::file; +use git_config::file::init; + +#[test] +fn missing_includes_are_ignored_by_default() -> crate::Result { + let input = r#" + [include] + path = /etc/absolute/missing.config + path = relative-missing.config + path = ./also-relative-missing.config + path = %(prefix)/no-install.config + path = ~/no-user.config + + [includeIf "onbranch:no-branch"] + path = no-branch-provided.config + [includeIf "gitdir:./no-git-dir"] + path = no-git-dir.config + "#; + + let mut config: git_config::File<'_> = input.parse()?; + + let mut follow_options = file::includes::Options::follow(Default::default(), Default::default()); + follow_options.err_on_missing_config_path = false; + config.resolve_includes(init::Options { + includes: follow_options, + ..Default::default() + })?; + + assert!( + config + .resolve_includes(init::Options { + includes: follow_options.strict(), + ..Default::default() + }) + .is_err(), + "strict mode fails if something couldn't be interpolated" + ); + Ok(()) +} From 1954ef096a58aedb9f568a01e439d5a5cb46c40d Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 21 Jul 2022 14:18:42 +0800 Subject: [PATCH 330/366] remove `Permissions` as there is no need for that here. (#331) The plumbing-level initialization calls are powerful enough, and more permissions are implemented in `git-repository`. --- git-config/src/file/includes/mod.rs | 3 ++ git-config/src/file/init/from_env.rs | 2 +- git-config/src/lib.rs | 3 -- git-config/src/permissions.rs | 55 ---------------------------- 4 files changed, 4 insertions(+), 59 deletions(-) delete mode 100644 git-config/src/permissions.rs diff --git a/git-config/src/file/includes/mod.rs b/git-config/src/file/includes/mod.rs index 4cbbec57980..3eea52ce88d 100644 --- a/git-config/src/file/includes/mod.rs +++ b/git-config/src/file/includes/mod.rs @@ -26,6 +26,9 @@ impl File<'static> { /// which later overwrite portions of the included file, which seems unusual as these would be related to `includes`. /// We can fix this by 'splitting' the inlcude section if needed so the included sections are put into the right place. pub fn resolve_includes(&mut self, options: init::Options<'_>) -> Result<(), Error> { + if options.includes.max_depth == 0 { + return Ok(()); + } let mut buf = Vec::new(); resolve(self, &mut buf, options) } diff --git a/git-config/src/file/init/from_env.rs b/git-config/src/file/init/from_env.rs index 3ef7cf35056..0fc923bd639 100644 --- a/git-config/src/file/init/from_env.rs +++ b/git-config/src/file/init/from_env.rs @@ -31,7 +31,7 @@ impl File<'static> { /// Generates a config from `GIT_CONFIG_*` environment variables or returns `Ok(None)` if no configuration was found. /// See [`git-config`'s documentation] for more information on the environment variables in question. /// - /// With `options` configured, it's possible `include.path` directives as well. + /// With `options` configured, it's possible to resolve `include.path` or `includeIf..path` directives as well. /// /// [`git-config`'s documentation]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-GITCONFIGCOUNT pub fn from_env(options: init::Options<'_>) -> Result>, Error> { diff --git a/git-config/src/lib.rs b/git-config/src/lib.rs index 2ecf0ded7f0..735b65439d0 100644 --- a/git-config/src/lib.rs +++ b/git-config/src/lib.rs @@ -51,6 +51,3 @@ mod types; pub use types::{Boolean, Color, File, Integer, Path, Source}; /// pub mod source; - -mod permissions; -pub use permissions::Permissions; diff --git a/git-config/src/permissions.rs b/git-config/src/permissions.rs deleted file mode 100644 index 4934fc3057a..00000000000 --- a/git-config/src/permissions.rs +++ /dev/null @@ -1,55 +0,0 @@ -/// Configure security relevant options when loading a git configuration. -#[derive(Copy, Clone, Ord, PartialOrd, PartialEq, Eq, Debug, Hash)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] -pub struct Permissions { - /// How to use the system configuration. - /// This is defined as `$(prefix)/etc/gitconfig` on unix. - pub system: git_sec::Permission, - /// How to use the global configuration. - /// This is usually `~/.gitconfig`. - pub global: git_sec::Permission, - /// How to use the user configuration. - /// Second user-specific configuration path; if `$XDG_CONFIG_HOME` is not - /// set or empty, `$HOME/.config/git/config` will be used. - pub user: git_sec::Permission, - /// How to use the repository configuration. - pub local: git_sec::Permission, - /// How to use worktree configuration from `config.worktree`. - // TODO: figure out how this really applies and provide more information here. - pub worktree: git_sec::Permission, - /// How to use the configuration from environment variables. - pub env: git_sec::Permission, - /// What to do when include files are encountered in loaded configuration. - pub includes: git_sec::Permission, -} - -impl Permissions { - /// Allow everything which usually relates to a fully trusted environment - pub fn all() -> Self { - use git_sec::Permission::*; - Permissions { - system: Allow, - global: Allow, - user: Allow, - local: Allow, - worktree: Allow, - env: Allow, - includes: Allow, - } - } - - /// If in doubt, this configuration can be used to safely load configuration from sources which is usually trusted, - /// that is system and user configuration. Do load any configuration that isn't trusted as it's now owned by the current user. - pub fn secure() -> Self { - use git_sec::Permission::*; - Permissions { - system: Allow, - global: Allow, - user: Allow, - local: Deny, - worktree: Deny, - env: Allow, - includes: Deny, - } - } -} From 840d9a3018d11146bb8e80fc92693c65eb534d91 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 21 Jul 2022 14:19:36 +0800 Subject: [PATCH 331/366] feat: permissions for configuration. (#331) It provides fine-grained control over what sources to load. --- git-repository/src/config/cache.rs | 35 +++++++++--- git-repository/src/config/mod.rs | 2 + git-repository/src/lib.rs | 5 +- git-repository/src/open.rs | 13 +++-- git-repository/src/repository/permissions.rs | 53 +++++++++++++++++-- .../make_config_repo.tar.xz | 4 +- .../tests/fixtures/make_config_repo.sh | 11 +++- git-repository/tests/git.rs | 37 +++++++++++-- git-repository/tests/repository/config.rs | 31 ++++++----- 9 files changed, 153 insertions(+), 38 deletions(-) diff --git a/git-repository/src/config/cache.rs b/git-repository/src/config/cache.rs index 97d24fba28a..d2ecfb34ac3 100644 --- a/git-repository/src/config/cache.rs +++ b/git-repository/src/config/cache.rs @@ -86,6 +86,13 @@ impl Cache { home: home_env, xdg_config_home: xdg_config_home_env, }: repository::permissions::Environment, + repository::permissions::Config { + system: use_system, + git: use_git, + user: use_user, + env: use_env, + includes: use_includes, + }: repository::permissions::Config, ) -> Result { let home = std::env::var_os("HOME") .map(PathBuf::from) @@ -93,13 +100,17 @@ impl Cache { let options = git_config::file::init::Options { lossy: !cfg!(debug_assertions), - includes: git_config::file::includes::Options::follow( - interpolate_context(git_install_dir, home.as_deref()), - git_config::file::includes::conditional::Context { - git_dir: git_dir.into(), - branch_name, - }, - ), + includes: if use_includes { + git_config::file::includes::Options::follow( + interpolate_context(git_install_dir, home.as_deref()), + git_config::file::includes::conditional::Context { + git_dir: git_dir.into(), + branch_name, + }, + ) + } else { + git_config::file::includes::Options::no_follow() + }, }; let config = { @@ -110,6 +121,13 @@ impl Cache { .iter() .flat_map(|kind| kind.sources()) .filter_map(|source| { + if !use_system && *source == git_config::Source::System { + return None; + } else if !use_git && *source == git_config::Source::Git { + return None; + } else if !use_user && *source == git_config::Source::User { + return None; + } let path = source .storage_location(&mut |name| { match name { @@ -149,6 +167,9 @@ impl Cache { globals.append(git_dir_config); globals.resolve_includes(options)?; + if use_env { + globals.append(git_config::File::from_env(options)?.unwrap_or_default()); + } globals }; diff --git a/git-repository/src/config/mod.rs b/git-repository/src/config/mod.rs index dd9e4b2b28b..10b343de796 100644 --- a/git-repository/src/config/mod.rs +++ b/git-repository/src/config/mod.rs @@ -24,6 +24,8 @@ pub enum Error { Init(#[from] git_config::file::init::Error), #[error(transparent)] ResolveIncludes(#[from] git_config::file::includes::Error), + #[error(transparent)] + FromEnv(#[from] git_config::file::init::from_env::Error), #[error("Cannot handle objects formatted as {:?}", .name)] UnsupportedObjectFormat { name: BString }, #[error("The value for '{}' cannot be empty", .key)] diff --git a/git-repository/src/lib.rs b/git-repository/src/lib.rs index 587e27c0b58..c49ff0efd9a 100644 --- a/git-repository/src/lib.rs +++ b/git-repository/src/lib.rs @@ -283,7 +283,7 @@ pub mod permission { } /// pub mod permissions { - pub use crate::repository::permissions::Environment; + pub use crate::repository::permissions::{Config, Environment}; } pub use repository::permissions::Permissions; @@ -430,7 +430,8 @@ pub mod discover { ) -> Result { let (path, trust) = upwards_opts(directory, options)?; let (git_dir, worktree_dir) = path.into_repository_and_work_tree_directories(); - let options = trust_map.into_value_by_level(trust); + let mut options = trust_map.into_value_by_level(trust); + options.git_dir_trust = trust.into(); Self::open_from_paths(git_dir, worktree_dir, options).map_err(Into::into) } diff --git a/git-repository/src/open.rs b/git-repository/src/open.rs index 352d8220d4b..003437bb47a 100644 --- a/git-repository/src/open.rs +++ b/git-repository/src/open.rs @@ -254,10 +254,12 @@ impl ThreadSafeRepository { object_store_slots, filter_config_section, ref replacement_objects, - permissions: Permissions { - git_dir: ref git_dir_perm, - ref env, - }, + permissions: + Permissions { + git_dir: ref git_dir_perm, + ref env, + config, + }, } = options; let git_dir_trust = git_dir_trust.expect("trust must be been determined by now"); @@ -287,6 +289,7 @@ impl ThreadSafeRepository { filter_config_section.unwrap_or(crate::config::section::is_trusted), crate::path::install_dir().ok().as_deref(), env.clone(), + config, )?; if **git_dir_perm != git_sec::ReadWrite::all() { @@ -361,7 +364,7 @@ mod tests { fn size_of_options() { assert_eq!( std::mem::size_of::(), - 64, + 72, "size shouldn't change without us knowing" ); } diff --git a/git-repository/src/repository/permissions.rs b/git-repository/src/repository/permissions.rs index 0645b4788bb..3abc44b9ae0 100644 --- a/git-repository/src/repository/permissions.rs +++ b/git-repository/src/repository/permissions.rs @@ -11,6 +11,48 @@ pub struct Permissions { pub git_dir: Access, /// Permissions related to the environment pub env: Environment, + /// Permissions related to the handling of git configuration. + pub config: Config, +} + +/// Configure security relevant options when loading a git configuration. +#[derive(Copy, Clone, Ord, PartialOrd, PartialEq, Eq, Debug, Hash)] +pub struct Config { + /// Whether to use the system configuration. + /// This is defined as `$(prefix)/etc/gitconfig` on unix. + pub system: bool, + /// Whether to use the git application configuration. + /// + /// A platform defined location for where a user's git application configuration should be located. + /// If `$XDG_CONFIG_HOME` is not set or empty, `$HOME/.config/git/config` will be used + /// on unix. + pub git: bool, + /// Whether to use the user configuration. + /// This is usually `~/.gitconfig` on unix. + pub user: bool, + /// Whether to use worktree configuration from `config.worktree`. + // TODO: figure out how this really applies and provide more information here. + // pub worktree: bool, + /// Whether to use the configuration from environment variables. + pub env: bool, + /// Whether to follow include files are encountered in loaded configuration, + /// via `include` and `includeIf` sections. + /// + /// Note that this needs access to `GIT_*` prefixed environment variables. + pub includes: bool, +} + +impl Config { + /// Allow everything which usually relates to a fully trusted environment + pub fn all() -> Self { + Config { + system: true, + git: true, + user: true, + env: true, + includes: true, + } + } } /// Permissions related to the usage of environment variables @@ -28,7 +70,7 @@ pub struct Environment { impl Environment { /// Allow access to the entire environment. - pub fn allow_all() -> Self { + pub fn all() -> Self { Environment { xdg_config_home: Access::resource(git_sec::Permission::Allow), home: Access::resource(git_sec::Permission::Allow), @@ -43,7 +85,8 @@ impl Permissions { pub fn strict() -> Self { Permissions { git_dir: Access::resource(git_sec::ReadWrite::READ), - env: Environment::allow_all(), + env: Environment::all(), + config: Config::all(), } } @@ -55,7 +98,8 @@ impl Permissions { pub fn secure() -> Self { Permissions { git_dir: Access::resource(git_sec::ReadWrite::all()), - env: Environment::allow_all(), + env: Environment::all(), + config: Config::all(), } } @@ -64,7 +108,8 @@ impl Permissions { pub fn all() -> Self { Permissions { git_dir: Access::resource(git_sec::ReadWrite::all()), - env: Environment::allow_all(), + env: Environment::all(), + config: Config::all(), } } } diff --git a/git-repository/tests/fixtures/generated-archives/make_config_repo.tar.xz b/git-repository/tests/fixtures/generated-archives/make_config_repo.tar.xz index a01be7e4513..724cde3c0ce 100644 --- a/git-repository/tests/fixtures/generated-archives/make_config_repo.tar.xz +++ b/git-repository/tests/fixtures/generated-archives/make_config_repo.tar.xz @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5c16dcf86219e5dd5755e21472c40d289b246d62fb92594378f166a4920bad58 -size 9276 +oid sha256:506336963df7857dadb52b694cdaa19679b63419f114bc91ae0ef25728a5c01a +size 9320 diff --git a/git-repository/tests/fixtures/make_config_repo.sh b/git-repository/tests/fixtures/make_config_repo.sh index 0438d6a32d0..52c123a7f1c 100644 --- a/git-repository/tests/fixtures/make_config_repo.sh +++ b/git-repository/tests/fixtures/make_config_repo.sh @@ -11,9 +11,11 @@ cat <>.git/config int-overflowing = 9999999999999g relative-path = ./something absolute-path = /etc/man.conf + bad-home-path = ~/repo bad-user-path = ~noname/repo single-string = hello world - override = base + local-override = base + env-override = base [include] path = ../a.config @@ -22,13 +24,18 @@ EOF cat <>a.config [a] - override = from-a.config + local-override = from-a.config EOF cat <>b.config [a] system-override = from-b.config EOF +cat <>c.config +[a] + env-override = from-c.config +EOF + cat <>system.config [a] system = from-system.config diff --git a/git-repository/tests/git.rs b/git-repository/tests/git.rs index 75a31893a0d..f67f7272a1f 100644 --- a/git-repository/tests/git.rs +++ b/git-repository/tests/git.rs @@ -1,21 +1,50 @@ -use git_repository::{Repository, ThreadSafeRepository}; +use git_repository::{open, permission, permissions, Permissions, Repository, ThreadSafeRepository}; type Result = std::result::Result>; fn repo(name: &str) -> Result { let repo_path = git_testtools::scripted_fixture_repo_read_only(name)?; - Ok(ThreadSafeRepository::open(repo_path)?) + Ok(ThreadSafeRepository::open_opts(repo_path, restricted())?) } fn named_repo(name: &str) -> Result { let repo_path = git_testtools::scripted_fixture_repo_read_only(name)?; - Ok(ThreadSafeRepository::open(repo_path)?.to_thread_local()) + Ok(ThreadSafeRepository::open_opts(repo_path, restricted())?.to_thread_local()) +} + +fn restricted() -> open::Options { + open::Options::default().permissions(Permissions { + config: permissions::Config { + system: false, + git: false, + user: false, + env: false, + includes: false, + }, + env: { + let deny = permission::env_var::Resource::resource(git_sec::Permission::Deny); + permissions::Environment { + xdg_config_home: deny.clone(), + home: deny.clone(), + git_prefix: deny, + } + }, + ..Permissions::default() + }) } fn repo_rw(name: &str) -> Result<(Repository, tempfile::TempDir)> { let repo_path = git_testtools::scripted_fixture_repo_writable(name)?; Ok(( - ThreadSafeRepository::discover(repo_path.path())?.to_thread_local(), + ThreadSafeRepository::discover_opts( + repo_path.path(), + Default::default(), + git_sec::trust::Mapping { + full: restricted(), + reduced: restricted(), + }, + )? + .to_thread_local(), repo_path, )) } diff --git a/git-repository/tests/repository/config.rs b/git-repository/tests/repository/config.rs index ba4265e6b5c..b764876abce 100644 --- a/git-repository/tests/repository/config.rs +++ b/git-repository/tests/repository/config.rs @@ -10,23 +10,22 @@ use std::path::Path; fn access_values() { for trust in [git_sec::Trust::Full, git_sec::Trust::Reduced] { let repo = named_repo("make_config_repo.sh").unwrap(); - let _env = Env::new().set( - "GIT_CONFIG_SYSTEM", - repo.work_dir() - .expect("present") - .join("system.config") - .canonicalize() - .unwrap() - .display() - .to_string(), - ); + let work_dir = repo.work_dir().expect("present").canonicalize().unwrap(); + let _env = Env::new() + .set( + "GIT_CONFIG_SYSTEM", + work_dir.join("system.config").display().to_string(), + ) + .set("GIT_CONFIG_COUNT", "1") + .set("GIT_CONFIG_KEY_0", "include.path") + .set("GIT_CONFIG_VALUE_0", work_dir.join("c.config").display().to_string()); let repo = git::open_opts( repo.git_dir(), repo.open_options().clone().with(trust).permissions(git::Permissions { env: git::permissions::Environment { xdg_config_home: Access::resource(Permission::Deny), home: Access::resource(Permission::Deny), - ..git::permissions::Environment::allow_all() + ..git::permissions::Environment::all() }, ..Default::default() }), @@ -50,7 +49,10 @@ fn access_values() { "hello world" ); - assert_eq!(config.string("a.override").expect("present").as_ref(), "from-a.config"); + assert_eq!( + config.string("a.local-override").expect("present").as_ref(), + "from-a.config" + ); assert_eq!( config.string("a.system").expect("present").as_ref(), "from-system.config" @@ -60,6 +62,11 @@ fn access_values() { "from-b.config" ); + assert_eq!( + config.string("a.env-override").expect("present").as_ref(), + "from-c.config" + ); + assert_eq!(config.boolean("core.missing"), None); assert_eq!(config.try_boolean("core.missing"), None); From b630543669af5289508ce066bd026e2b9a9d5044 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 21 Jul 2022 14:21:50 +0800 Subject: [PATCH 332/366] thanks clippy --- git-repository/src/config/cache.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/git-repository/src/config/cache.rs b/git-repository/src/config/cache.rs index d2ecfb34ac3..231d3403318 100644 --- a/git-repository/src/config/cache.rs +++ b/git-repository/src/config/cache.rs @@ -121,12 +121,11 @@ impl Cache { .iter() .flat_map(|kind| kind.sources()) .filter_map(|source| { - if !use_system && *source == git_config::Source::System { - return None; - } else if !use_git && *source == git_config::Source::Git { - return None; - } else if !use_user && *source == git_config::Source::User { - return None; + match source { + git_config::Source::System if !use_system => return None, + git_config::Source::Git if !use_git => return None, + git_config::Source::User if !use_user => return None, + _ => {} } let path = source .storage_location(&mut |name| { From 1b765ec6ae70d1f4cc5a885b3c68d6f3335ba827 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 21 Jul 2022 15:01:44 +0800 Subject: [PATCH 333/366] feat: respect `safe.directory`. (#331) In practice, this code will rarely be hit as it would require very strict settings that forbid any operation within a non-owned git directory. --- git-repository/src/config/cache.rs | 5 +-- git-repository/src/open.rs | 47 ++++++++++++++++++-- git-repository/src/repository/permissions.rs | 6 +++ 3 files changed, 51 insertions(+), 7 deletions(-) diff --git a/git-repository/src/config/cache.rs b/git-repository/src/config/cache.rs index 231d3403318..3c36ddce7f7 100644 --- a/git-repository/src/config/cache.rs +++ b/git-repository/src/config/cache.rs @@ -81,6 +81,7 @@ impl Cache { branch_name: Option<&git_ref::FullNameRef>, mut filter_config_section: fn(&git_config::file::Metadata) -> bool, git_install_dir: Option<&std::path::Path>, + home: Option<&std::path::Path>, repository::permissions::Environment { git_prefix, home: home_env, @@ -94,10 +95,6 @@ impl Cache { includes: use_includes, }: repository::permissions::Config, ) -> Result { - let home = std::env::var_os("HOME") - .map(PathBuf::from) - .and_then(|home| home_env.check(home).ok().flatten()); - let options = git_config::file::init::Options { lossy: !cfg!(debug_assertions), includes: if use_includes { diff --git a/git-repository/src/open.rs b/git-repository/src/open.rs index 003437bb47a..7e24f3169f5 100644 --- a/git-repository/src/open.rs +++ b/git-repository/src/open.rs @@ -2,6 +2,7 @@ use std::path::PathBuf; use git_features::threading::OwnShared; +use crate::config::cache::interpolate_context; use crate::{Permissions, ThreadSafeRepository}; /// A way to configure the usage of replacement objects, see `git replace`. @@ -282,19 +283,59 @@ impl ThreadSafeRepository { } }; let head = refs.find("HEAD").ok(); + let git_install_dir = crate::path::install_dir().ok(); + let home = std::env::var_os("HOME") + .map(PathBuf::from) + .and_then(|home| env.home.check(home).ok().flatten()); let config = crate::config::Cache::from_stage_one( repo_config, common_dir_ref, head.as_ref().and_then(|head| head.target.try_name()), filter_config_section.unwrap_or(crate::config::section::is_trusted), - crate::path::install_dir().ok().as_deref(), + git_install_dir.as_deref(), + home.as_deref(), env.clone(), config, )?; if **git_dir_perm != git_sec::ReadWrite::all() { - // TODO: respect `save.directory`, which needs global configuration to later combine. Probably have to do the check later. - return Err(Error::UnsafeGitDir { path: git_dir }); + let mut is_safe = false; + let git_dir = match git_path::realpath(&git_dir) { + Ok(p) => p, + Err(_) => git_dir.clone(), + }; + for safe_dir in config + .resolved + .strings_filter("safe", None, "directory", &mut |meta| { + let kind = meta.source.kind(); + kind == git_config::source::Kind::System || kind == git_config::source::Kind::Global + }) + .unwrap_or_default() + { + if safe_dir.as_ref() == "*" { + is_safe = true; + continue; + } + if safe_dir.is_empty() { + is_safe = false; + continue; + } + if !is_safe { + let safe_dir = match git_config::Path::from(std::borrow::Cow::Borrowed(safe_dir.as_ref())) + .interpolate(interpolate_context(git_install_dir.as_deref(), home.as_deref())) + { + Ok(path) => path, + Err(_) => git_path::from_bstr(safe_dir), + }; + if safe_dir == git_dir { + is_safe = true; + continue; + } + } + } + if !is_safe { + return Err(Error::UnsafeGitDir { path: git_dir }); + } } match worktree_dir { diff --git a/git-repository/src/repository/permissions.rs b/git-repository/src/repository/permissions.rs index 3abc44b9ae0..83c5e4b5367 100644 --- a/git-repository/src/repository/permissions.rs +++ b/git-repository/src/repository/permissions.rs @@ -55,6 +55,12 @@ impl Config { } } +impl Default for Config { + fn default() -> Self { + Self::all() + } +} + /// Permissions related to the usage of environment variables #[derive(Debug, Clone)] pub struct Environment { From 0346aaaeccfe18a443410652cada7b14eb34d8b9 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 21 Jul 2022 15:04:19 +0800 Subject: [PATCH 334/366] thanks clippy --- git-repository/src/config/cache.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/git-repository/src/config/cache.rs b/git-repository/src/config/cache.rs index 3c36ddce7f7..846176a88dc 100644 --- a/git-repository/src/config/cache.rs +++ b/git-repository/src/config/cache.rs @@ -69,6 +69,7 @@ impl StageOne { } impl Cache { + #[allow(clippy::too_many_arguments)] pub fn from_stage_one( StageOne { git_dir_config, @@ -99,7 +100,7 @@ impl Cache { lossy: !cfg!(debug_assertions), includes: if use_includes { git_config::file::includes::Options::follow( - interpolate_context(git_install_dir, home.as_deref()), + interpolate_context(git_install_dir, home), git_config::file::includes::conditional::Context { git_dir: git_dir.into(), branch_name, From 4f613120f9f761b86fc7eb16227d08fc5b9828d8 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 21 Jul 2022 15:07:24 +0800 Subject: [PATCH 335/366] refactor (#331) --- git-repository/src/open.rs | 88 ++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 38 deletions(-) diff --git a/git-repository/src/open.rs b/git-repository/src/open.rs index 7e24f3169f5..e4047ca90dd 100644 --- a/git-repository/src/open.rs +++ b/git-repository/src/open.rs @@ -2,6 +2,7 @@ use std::path::PathBuf; use git_features::threading::OwnShared; +use crate::config; use crate::config::cache::interpolate_context; use crate::{Permissions, ThreadSafeRepository}; @@ -287,7 +288,7 @@ impl ThreadSafeRepository { let home = std::env::var_os("HOME") .map(PathBuf::from) .and_then(|home| env.home.check(home).ok().flatten()); - let config = crate::config::Cache::from_stage_one( + let config = config::Cache::from_stage_one( repo_config, common_dir_ref, head.as_ref().and_then(|head| head.target.try_name()), @@ -299,43 +300,7 @@ impl ThreadSafeRepository { )?; if **git_dir_perm != git_sec::ReadWrite::all() { - let mut is_safe = false; - let git_dir = match git_path::realpath(&git_dir) { - Ok(p) => p, - Err(_) => git_dir.clone(), - }; - for safe_dir in config - .resolved - .strings_filter("safe", None, "directory", &mut |meta| { - let kind = meta.source.kind(); - kind == git_config::source::Kind::System || kind == git_config::source::Kind::Global - }) - .unwrap_or_default() - { - if safe_dir.as_ref() == "*" { - is_safe = true; - continue; - } - if safe_dir.is_empty() { - is_safe = false; - continue; - } - if !is_safe { - let safe_dir = match git_config::Path::from(std::borrow::Cow::Borrowed(safe_dir.as_ref())) - .interpolate(interpolate_context(git_install_dir.as_deref(), home.as_deref())) - { - Ok(path) => path, - Err(_) => git_path::from_bstr(safe_dir), - }; - if safe_dir == git_dir { - is_safe = true; - continue; - } - } - } - if !is_safe { - return Err(Error::UnsafeGitDir { path: git_dir }); - } + check_safe_directories(&git_dir, git_install_dir.as_deref(), home.as_deref(), &config)?; } match worktree_dir { @@ -397,6 +362,53 @@ impl ThreadSafeRepository { } } +fn check_safe_directories( + git_dir: &std::path::Path, + git_install_dir: Option<&std::path::Path>, + home: Option<&std::path::Path>, + config: &config::Cache, +) -> Result<(), Error> { + let mut is_safe = false; + let git_dir = match git_path::realpath(git_dir) { + Ok(p) => p, + Err(_) => git_dir.to_owned(), + }; + for safe_dir in config + .resolved + .strings_filter("safe", None, "directory", &mut |meta| { + let kind = meta.source.kind(); + kind == git_config::source::Kind::System || kind == git_config::source::Kind::Global + }) + .unwrap_or_default() + { + if safe_dir.as_ref() == "*" { + is_safe = true; + continue; + } + if safe_dir.is_empty() { + is_safe = false; + continue; + } + if !is_safe { + let safe_dir = match git_config::Path::from(std::borrow::Cow::Borrowed(safe_dir.as_ref())) + .interpolate(interpolate_context(git_install_dir, home)) + { + Ok(path) => path, + Err(_) => git_path::from_bstr(safe_dir), + }; + if safe_dir == git_dir { + is_safe = true; + continue; + } + } + } + if is_safe { + Ok(()) + } else { + Err(Error::UnsafeGitDir { path: git_dir }) + } +} + #[cfg(test)] mod tests { use super::*; From aeda76ed500d2edba62747d667227f2664edd267 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 21 Jul 2022 17:47:41 +0800 Subject: [PATCH 336/366] feat: `Time::is_set()` to see if the time is more than just the default. (#331) --- git-date/src/time.rs | 5 +++++ git-date/tests/time/mod.rs | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/git-date/src/time.rs b/git-date/src/time.rs index 1fa1f77881d..6b145c2b777 100644 --- a/git-date/src/time.rs +++ b/git-date/src/time.rs @@ -41,6 +41,11 @@ impl Time { } } + /// Return true if this time has been initialized to anything non-default, i.e. 0. + pub fn is_set(&self) -> bool { + *self != Self::default() + } + /// Return the passed seconds since epoch since this signature was made. pub fn seconds(&self) -> u32 { self.seconds_since_unix_epoch diff --git a/git-date/tests/time/mod.rs b/git-date/tests/time/mod.rs index 72e5b7ea4a2..cc694654b61 100644 --- a/git-date/tests/time/mod.rs +++ b/git-date/tests/time/mod.rs @@ -1,6 +1,16 @@ use bstr::ByteSlice; use git_date::{time::Sign, Time}; +#[test] +fn is_set() { + assert!(!Time::default().is_set()); + assert!(Time { + seconds_since_unix_epoch: 1, + ..Default::default() + } + .is_set()); +} + #[test] fn write_to() -> Result<(), Box> { for (time, expected) in &[ From 780f14f5c270802e51cf039639c2fbdb5ac5a85e Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 21 Jul 2022 17:48:37 +0800 Subject: [PATCH 337/366] a first sketch on how identity management could look like. (#331) This, however, will need some additional support for getting the current time alnog with the typical dance related to local time access. --- Cargo.lock | 1 + git-features/Cargo.toml | 3 + git-features/src/threading.rs | 6 ++ git-repository/Cargo.toml | 2 +- git-repository/src/config/cache.rs | 13 +++++ git-repository/src/config/mod.rs | 6 ++ git-repository/src/open.rs | 29 ++++++++- git-repository/src/repository/config.rs | 6 ++ git-repository/src/repository/identity.rs | 71 +++++++++++++++++++++++ git-repository/src/repository/mod.rs | 48 +++------------ git-repository/src/types.rs | 3 - git-repository/tests/git.rs | 21 +------ git-repository/tests/repository/mod.rs | 11 ++++ 13 files changed, 156 insertions(+), 64 deletions(-) create mode 100644 git-repository/src/repository/identity.rs diff --git a/Cargo.lock b/Cargo.lock index e8749c27e1e..fc792b31950 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1232,6 +1232,7 @@ dependencies = [ "jwalk", "libc", "num_cpus", + "once_cell", "parking_lot 0.12.1", "prodash", "quick-error", diff --git a/git-features/Cargo.toml b/git-features/Cargo.toml index 778cfcfb5df..f7be1aa62dc 100644 --- a/git-features/Cargo.toml +++ b/git-features/Cargo.toml @@ -117,6 +117,9 @@ quick-error = { version = "2.0.0", optional = true } ## make the `time` module available with access to the local time as configured by the system. time = { version = "0.3.2", optional = true, default-features = false, features = ["local-offset"] } +## If enabled, OnceCell will be made available for interior mutability either in sync or unsync forms. +once_cell = { version = "1.13.0", optional = true } + document-features = { version = "0.2.0", optional = true } [target.'cfg(unix)'.dependencies] diff --git a/git-features/src/threading.rs b/git-features/src/threading.rs index b262454b308..ff0c819a5e5 100644 --- a/git-features/src/threading.rs +++ b/git-features/src/threading.rs @@ -6,6 +6,9 @@ mod _impl { use std::sync::Arc; + /// A thread-safe cell which can be written to only once. + #[cfg(feature = "once_cell")] + pub type OnceCell = once_cell::sync::OnceCell; /// A reference counted pointer type for shared ownership. pub type OwnShared = Arc; /// A synchronization primitive which can start read-only and transition to support mutation. @@ -53,6 +56,9 @@ mod _impl { rc::Rc, }; + /// A thread-safe cell which can be written to only once. + #[cfg(feature = "once_cell")] + pub type OnceCell = once_cell::unsync::OnceCell; /// A reference counted pointer type for shared ownership. pub type OwnShared = Rc; /// A synchronization primitive which can start read-only and transition to support mutation. diff --git a/git-repository/Cargo.toml b/git-repository/Cargo.toml index 1d36fbda907..dac0c02961c 100644 --- a/git-repository/Cargo.toml +++ b/git-repository/Cargo.toml @@ -78,7 +78,7 @@ git-protocol = { version = "^0.18.0", path = "../git-protocol", optional = true git-transport = { version = "^0.19.0", path = "../git-transport", optional = true } git-diff = { version = "^0.16.0", path = "../git-diff", optional = true } git-mailmap = { version = "^0.2.0", path = "../git-mailmap", optional = true } -git-features = { version = "^0.21.1", path = "../git-features", features = ["progress"] } +git-features = { version = "^0.21.1", path = "../git-features", features = ["progress", "once_cell"] } # unstable only git-attributes = { version = "^0.3.0", path = "../git-attributes", optional = true } diff --git a/git-repository/src/config/cache.rs b/git-repository/src/config/cache.rs index 846176a88dc..eb8415a3ce9 100644 --- a/git-repository/src/config/cache.rs +++ b/git-repository/src/config/cache.rs @@ -5,6 +5,7 @@ use git_config::{Boolean, Integer}; use super::{Cache, Error}; use crate::bstr::ByteSlice; +use crate::repository::identity; /// A utility to deal with the cyclic dependency between the ref store and the configuration. The ref-store needs the /// object hash kind, and the configuration needs the current branch name to resolve conditional includes with `onbranch`. @@ -18,6 +19,7 @@ pub(crate) struct StageOne { pub reflog: Option, } +/// Initialization impl StageOne { pub fn new(git_dir: &std::path::Path, git_dir_trust: git_sec::Trust) -> Result { let mut buf = Vec::with_capacity(512); @@ -68,6 +70,7 @@ impl StageOne { } } +/// Initialization impl Cache { #[allow(clippy::too_many_arguments)] pub fn from_stage_one( @@ -220,6 +223,8 @@ impl Cache { excludes_file, xdg_config_home_env, home_env, + personas: Default::default(), + git_prefix, }) } @@ -250,6 +255,14 @@ impl Cache { } } +/// Access +impl Cache { + pub fn personas(&self) -> &identity::Personas { + self.personas + .get_or_init(|| identity::Personas::from_config_and_env(&self.resolved, &self.git_prefix)) + } +} + pub(crate) fn interpolate_context<'a>( git_install_dir: Option<&'a std::path::Path>, home_dir: Option<&'a std::path::Path>, diff --git a/git-repository/src/config/mod.rs b/git-repository/src/config/mod.rs index 10b343de796..d2e31a8be61 100644 --- a/git-repository/src/config/mod.rs +++ b/git-repository/src/config/mod.rs @@ -1,4 +1,6 @@ +use crate::repository::identity; use crate::{bstr::BString, permission, Repository}; +use git_features::threading::OnceCell; pub(crate) mod cache; mod snapshot; @@ -52,6 +54,8 @@ pub(crate) struct Cache { pub use_multi_pack_index: bool, /// The representation of `core.logallrefupdates`, or `None` if the variable wasn't set. pub reflog: Option, + /// identities for later use, lazy initialization. + pub personas: OnceCell, /// If true, we are on a case-insensitive file system. #[cfg_attr(not(feature = "git-index"), allow(dead_code))] pub ignore_case: bool, @@ -63,5 +67,7 @@ pub(crate) struct Cache { xdg_config_home_env: permission::env_var::Resource, /// Define how we can use values obtained with `xdg_config(…)`. and its `HOME` variable. home_env: permission::env_var::Resource, + /// How to use git-prefixed environment variables + git_prefix: permission::env_var::Resource, // TODO: make core.precomposeUnicode available as well. } diff --git a/git-repository/src/open.rs b/git-repository/src/open.rs index e4047ca90dd..a93ed2b2af8 100644 --- a/git-repository/src/open.rs +++ b/git-repository/src/open.rs @@ -2,8 +2,8 @@ use std::path::PathBuf; use git_features::threading::OwnShared; -use crate::config; use crate::config::cache::interpolate_context; +use crate::{config, permission, permissions}; use crate::{Permissions, ThreadSafeRepository}; /// A way to configure the usage of replacement objects, see `git replace`. @@ -98,6 +98,33 @@ impl EnvironmentOverrides { } } +/// Instantiation +impl Options { + /// Options configured to prevent accessing anything else than the repository configuration file, prohibiting + /// accessing the environment or spreading beyond the git repository location. + pub fn isolated() -> Self { + Options::default().permissions(Permissions { + config: permissions::Config { + system: false, + git: false, + user: false, + env: false, + includes: false, + }, + env: { + let deny = permission::env_var::Resource::resource(git_sec::Permission::Deny); + permissions::Environment { + xdg_config_home: deny.clone(), + home: deny.clone(), + git_prefix: deny, + } + }, + ..Permissions::default() + }) + } +} + +/// Builder methods impl Options { /// Set the amount of slots to use for the object database. It's a value that doesn't need changes on the client, typically, /// but should be controlled on the server. diff --git a/git-repository/src/repository/config.rs b/git-repository/src/repository/config.rs index 0273d19d784..6809d074aa9 100644 --- a/git-repository/src/repository/config.rs +++ b/git-repository/src/repository/config.rs @@ -1,5 +1,6 @@ use crate::config; +/// Configuration impl crate::Repository { /// Return a snapshot of the configuration as seen upon opening the repository. pub fn config_snapshot(&self) -> config::Snapshot<'_> { @@ -10,4 +11,9 @@ impl crate::Repository { pub fn open_options(&self) -> &crate::open::Options { &self.options } + + /// The kind of object hash the repository is configured to use. + pub fn object_hash(&self) -> git_hash::Kind { + self.config.object_hash + } } diff --git a/git-repository/src/repository/identity.rs b/git-repository/src/repository/identity.rs new file mode 100644 index 00000000000..f145b4e9f54 --- /dev/null +++ b/git-repository/src/repository/identity.rs @@ -0,0 +1,71 @@ +use crate::bstr::BString; +use crate::permission; +use git_actor::SignatureRef; + +/// Identity handling. +impl crate::Repository { + /// Return a crate-specific constant signature with [`Time`][git_actor::Time] set to now, + /// in a similar vein as the default that git chooses if there is nothing configured. + /// + /// This can be useful as fallback for an unset `committer` or `author`. + pub fn user_default() -> SignatureRef<'static> { + SignatureRef { + name: "gitoxide".into(), + email: "gitoxide@localhost".into(), + time: Default::default(), + } + } + + // TODO: actual implementation + /// Return the committer as configured by this repository, which is determined by… + /// + /// * …the git configuration `committer.name|email`… + /// * …the `GIT_(COMMITTER)_(NAME|EMAIL|DATE)` and `EMAIL` environment variables… + /// * …the configuration for `user.name|email` as fallback… + /// + /// …and in that order, or `None` if there was nothing configured. In that case, one may use the + /// [`user_default()`][Self::user_default()] method. + /// + /// The values are cached when the repository is instantiated. + pub fn committer(&self) -> git_actor::Signature { + git_actor::Signature::empty() + } + + /// + pub fn committer2(&self) -> Option> { + let p = self.config.personas(); + + git_actor::SignatureRef { + name: p.committer.name.as_ref().or(p.user.name.as_ref()).map(|v| v.as_ref())?, + email: p + .committer + .email + .as_ref() + .or(p.user.email.as_ref()) + .map(|v| v.as_ref())?, + time: p.committer.time.unwrap_or_else(|| todo!("get local time")), + } + .into() + } +} + +#[derive(Debug, Clone)] +pub(crate) struct Entity { + pub name: Option, + pub email: Option, + /// A time parsed from an environment variable. + pub time: Option, +} + +#[derive(Debug, Clone)] +pub(crate) struct Personas { + user: Entity, + committer: Entity, + // author: Entity, +} + +impl Personas { + pub fn from_config_and_env(_config: &git_config::File<'_>, _git_env: &permission::env_var::Resource) -> Self { + todo!() + } +} diff --git a/git-repository/src/repository/mod.rs b/git-repository/src/repository/mod.rs index c2e0edaf69a..c8d7f59bf81 100644 --- a/git-repository/src/repository/mod.rs +++ b/git-repository/src/repository/mod.rs @@ -19,49 +19,17 @@ impl crate::Repository { } } -/// Everything else -impl crate::Repository { - // TODO: actual implementation - /// Return the committer as configured by this repository, which is determined by… - /// - /// * …the git configuration… - /// * …the GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL|DATE) environment variables… - /// - /// …and in that order. - pub fn committer(&self) -> git_actor::Signature { - // TODO: actually do the work, probably that should be cached and be refreshable - git_actor::Signature::empty() - } - - /// The kind of object hash the repository is configured to use. - pub fn object_hash(&self) -> git_hash::Kind { - self.config.object_hash - } -} - -mod worktree; - -/// Various permissions for parts of git repositories. -pub(crate) mod permissions; - +mod cache; mod config; - +pub(crate) mod identity; +mod impls; mod init; - mod location; - +mod object; +pub(crate) mod permissions; +mod reference; +mod remote; mod snapshots; - mod state; - -mod impls; - -mod cache; - -mod reference; - -mod object; - mod thread_safe; - -mod remote; +mod worktree; diff --git a/git-repository/src/types.rs b/git-repository/src/types.rs index e3920adec36..ec1ee0a247e 100644 --- a/git-repository/src/types.rs +++ b/git-repository/src/types.rs @@ -157,9 +157,6 @@ pub struct ThreadSafeRepository { pub work_tree: Option, /// The path to the common directory if this is a linked worktree repository or it is otherwise set. pub common_dir: Option, - // TODO: git-config should be here - it's read a lot but not written much in must applications, so shouldn't be in `State`. - // Probably it's best reload it on signal (in servers) or refresh it when it's known to have been changed similar to how - // packs are refreshed. This would be `git_config::fs::Config` when ready. pub(crate) config: crate::config::Cache, /// options obtained when instantiating this repository for use when following linked worktrees. pub(crate) linked_worktree_options: crate::open::Options, diff --git a/git-repository/tests/git.rs b/git-repository/tests/git.rs index f67f7272a1f..fbda34ca007 100644 --- a/git-repository/tests/git.rs +++ b/git-repository/tests/git.rs @@ -1,4 +1,4 @@ -use git_repository::{open, permission, permissions, Permissions, Repository, ThreadSafeRepository}; +use git_repository::{open, Repository, ThreadSafeRepository}; type Result = std::result::Result>; @@ -13,24 +13,7 @@ fn named_repo(name: &str) -> Result { } fn restricted() -> open::Options { - open::Options::default().permissions(Permissions { - config: permissions::Config { - system: false, - git: false, - user: false, - env: false, - includes: false, - }, - env: { - let deny = permission::env_var::Resource::resource(git_sec::Permission::Deny); - permissions::Environment { - xdg_config_home: deny.clone(), - home: deny.clone(), - git_prefix: deny, - } - }, - ..Permissions::default() - }) + open::Options::isolated() } fn repo_rw(name: &str) -> Result<(Repository, tempfile::TempDir)> { diff --git a/git-repository/tests/repository/mod.rs b/git-repository/tests/repository/mod.rs index dbc256ac1e3..c3161eff34c 100644 --- a/git-repository/tests/repository/mod.rs +++ b/git-repository/tests/repository/mod.rs @@ -1,6 +1,17 @@ +use git_repository::Repository; + mod config; mod object; mod reference; mod remote; mod state; mod worktree; + +#[test] +fn size_in_memory() { + assert_eq!( + std::mem::size_of::(), + 624, + "size of Repository shouldn't change without us noticing, it's meant to be cloned" + ); +} From c76fde7de278b49ded13b655d5345e4eb8c1b134 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 21 Jul 2022 19:30:10 +0800 Subject: [PATCH 338/366] feat: initialize `Time` from `now_utc` and `now_local` (#331) Localtime support depends on some other factors now, but that will only get better over time. We might have to document `unsound_local_time` at some point. --- git-date/Cargo.toml | 1 + git-date/src/time.rs | 61 +++++++++++++++++++++++++++++++++++++++- git-features/src/time.rs | 35 ----------------------- 3 files changed, 61 insertions(+), 36 deletions(-) delete mode 100644 git-features/src/time.rs diff --git a/git-date/Cargo.toml b/git-date/Cargo.toml index 269585540b2..d3e5a9e934f 100644 --- a/git-date/Cargo.toml +++ b/git-date/Cargo.toml @@ -20,6 +20,7 @@ serde1 = ["serde", "bstr/serde1"] bstr = { version = "0.2.13", default-features = false, features = ["std"]} serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]} itoa = "1.0.1" +time = { version = "0.3.2", default-features = false, features = ["local-offset"] } document-features = { version = "0.2.0", optional = true } diff --git a/git-date/src/time.rs b/git-date/src/time.rs index 6b145c2b777..d94614b2f4e 100644 --- a/git-date/src/time.rs +++ b/git-date/src/time.rs @@ -1,4 +1,6 @@ +use std::convert::TryInto; use std::io; +use std::ops::Sub; use crate::Time; @@ -31,6 +33,7 @@ impl Default for Time { } } +/// Instantiation impl Time { /// Create a new instance from seconds and offset. pub fn new(seconds_since_unix_epoch: u32, offset_in_seconds: i32) -> Self { @@ -41,6 +44,61 @@ impl Time { } } + /// Return the current time without figuring out a timezone offset + pub fn now_utc() -> Self { + let seconds_since_unix_epoch = time::OffsetDateTime::now_utc() + .sub(std::time::SystemTime::UNIX_EPOCH) + .whole_seconds() + .try_into() + .expect("this is not year 2038"); + Self { + seconds_since_unix_epoch, + offset_in_seconds: 0, + sign: Sign::Plus, + } + } + + /// Return the current local time, or `None` if the local time wasn't available. + pub fn now_local() -> Option { + let now = time::OffsetDateTime::now_utc(); + let seconds_since_unix_epoch = now + .sub(std::time::SystemTime::UNIX_EPOCH) + .whole_seconds() + .try_into() + .expect("this is not year 2038"); + // TODO: make this work without cfg(unsound_local_offset), see + // https://github.com/time-rs/time/issues/293#issuecomment-909158529 + let offset = time::UtcOffset::local_offset_at(now).ok()?; + Self { + seconds_since_unix_epoch, + offset_in_seconds: offset.whole_seconds(), + sign: Sign::Plus, + } + .into() + } + + /// Return the current local time, or the one at UTC if the local time wasn't available. + pub fn now_local_or_utc() -> Self { + let now = time::OffsetDateTime::now_utc(); + let seconds_since_unix_epoch = now + .sub(std::time::SystemTime::UNIX_EPOCH) + .whole_seconds() + .try_into() + .expect("this is not year 2038"); + // TODO: make this work without cfg(unsound_local_offset), see + // https://github.com/time-rs/time/issues/293#issuecomment-909158529 + let offset_in_seconds = time::UtcOffset::local_offset_at(now) + .map(|ofs| ofs.whole_seconds()) + .unwrap_or(0); + Self { + seconds_since_unix_epoch, + offset_in_seconds, + sign: Sign::Plus, + } + } +} + +impl Time { /// Return true if this time has been initialized to anything non-default, i.e. 0. pub fn is_set(&self) -> bool { *self != Self::default() @@ -79,7 +137,8 @@ impl Time { } out.write_all(itoa.format(minutes).as_bytes()).map(|_| ()) } - /// Computes the number of bytes necessary to render this time + + /// Computes the number of bytes necessary to render this time. pub fn size(&self) -> usize { // TODO: this is not year 2038 safe…but we also can't parse larger numbers (or represent them) anyway. It's a trap nonetheless // that can be fixed by increasing the size to usize. diff --git a/git-features/src/time.rs b/git-features/src/time.rs deleted file mode 100644 index e0a6b5b712e..00000000000 --- a/git-features/src/time.rs +++ /dev/null @@ -1,35 +0,0 @@ -/// -pub mod tz { - mod error { - use std::fmt; - - /// The error returned by [`current_utc_offset()`][super::current_utc_offset()] - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - pub struct Error; - - impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("The system's UTC offset could not be determined") - } - } - - impl std::error::Error for Error {} - } - pub use error::Error; - - /// The UTC offset in seconds - pub type UTCOffsetInSeconds = i32; - - /// Return time offset in seconds from UTC based on the current timezone. - /// - /// Note that there may be various legitimate reasons for failure, which should be accounted for. - pub fn current_utc_offset() -> Result { - // TODO: make this work without cfg(unsound_local_offset), see - // https://github.com/time-rs/time/issues/293#issuecomment-909158529 - // TODO: get a function to return the current time as well to avoid double-lookups - // (to get the offset, the current time is needed) - time::UtcOffset::current_local_offset() - .map(|ofs| ofs.whole_seconds()) - .map_err(|_| Error) - } -} From 89a41bf2b37db29b9983b4e5492cfd67ed490b23 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 21 Jul 2022 19:37:26 +0800 Subject: [PATCH 339/366] change!: remove local-time-support feature toggle. (#331) We treat local time as default feature without a lot of fuzz, and will eventually document that definitive support needs a compile time switch in the compiler (`--cfg unsound_local_offset` or something). One day it will perish. Failure is possible anyway and we will write code to deal with it while minimizing the amount of system time fetches when asking for the current local time. --- Cargo.lock | 2 +- Cargo.toml | 2 +- Makefile | 9 ++--- git-actor/Cargo.toml | 3 -- git-actor/src/signature/mod.rs | 72 ---------------------------------- git-features/Cargo.toml | 3 -- git-features/src/lib.rs | 4 -- git-repository/Cargo.toml | 4 +- gitoxide-core/Cargo.toml | 2 - 9 files changed, 6 insertions(+), 95 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fc792b31950..4375924471f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1187,6 +1187,7 @@ dependencies = [ "git-testtools", "itoa 1.0.2", "serde", + "time", ] [[package]] @@ -1238,7 +1239,6 @@ dependencies = [ "quick-error", "sha-1", "sha1_smol", - "time", "walkdir", ] diff --git a/Cargo.toml b/Cargo.toml index 167ad114203..78b4a8d224b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ fast = ["git-features/parallel", "git-features/fast-sha1", "git-features/zlib-ng ## Use `clap` 3.0 to build the prettiest, best documented and most user-friendly CLI at the expense of binary size. ## Provides a terminal user interface for detailed and exhaustive progress. ## Provides a line renderer for leaner progress display, without the need for a full-blown TUI. -pretty-cli = [ "gitoxide-core/serde1", "prodash/progress-tree", "prodash/progress-tree-log", "prodash/local-time", "gitoxide-core/local-time-support", "env_logger/humantime", "env_logger/termcolor", "env_logger/atty" ] +pretty-cli = [ "gitoxide-core/serde1", "prodash/progress-tree", "prodash/progress-tree-log", "prodash/local-time", "env_logger/humantime", "env_logger/termcolor", "env_logger/atty" ] ## The `--verbose` flag will be powered by an interactive progress mechanism that doubles as log as well as interactive progress ## that appears after a short duration. diff --git a/Makefile b/Makefile index a45d05926a9..9a1bb88616c 100644 --- a/Makefile +++ b/Makefile @@ -54,7 +54,7 @@ clippy: ## Run cargo clippy on all crates cargo clippy --all --no-default-features --features lean-async --tests check-msrv: ## run cargo msrv to validate the current msrv requirements, similar to what CI does - cd git-repository && cargo check --package git-repository --no-default-features --features async-network-client,unstable,local-time-support,max-performance + cd git-repository && cargo check --package git-repository --no-default-features --features async-network-client,unstable,max-performance check: ## Build all code in suitable configurations cargo check --all @@ -63,12 +63,9 @@ check: ## Build all code in suitable configurations cargo check --no-default-features --features lean cargo check --no-default-features --features lean-async cargo check --no-default-features --features max - cd git-actor && cargo check \ - && cargo check --features local-time-support cd gitoxide-core && cargo check \ && cargo check --features blocking-client \ - && cargo check --features async-client \ - && cargo check --features local-time-support + && cargo check --features async-client cd gitoxide-core && if cargo check --all-features 2>/dev/null; then false; else true; fi cd git-hash && cargo check --all-features \ && cargo check @@ -276,7 +273,7 @@ bench-git-config: check-msrv-on-ci: ## Check the minimal support rust version for currently installed Rust version rustc --version cargo check --package git-repository - cargo check --package git-repository --no-default-features --features async-network-client,unstable,local-time-support,max-performance + cargo check --package git-repository --no-default-features --features async-network-client,unstable,max-performance ##@ Maintenance diff --git a/git-actor/Cargo.toml b/git-actor/Cargo.toml index f11a3ff4267..91c638e49fe 100644 --- a/git-actor/Cargo.toml +++ b/git-actor/Cargo.toml @@ -15,9 +15,6 @@ doctest = false ## Data structures implement `serde::Serialize` and `serde::Deserialize`. serde1 = ["serde", "bstr/serde1", "git-date/serde1"] -## Make `Signature` initializers use the local time (with UTC offset) available. -local-time-support = ["git-features/time"] - [dependencies] git-features = { version = "^0.21.1", path = "../git-features", optional = true } git-date = { version = "^0.0.1", path = "../git-date" } diff --git a/git-actor/src/signature/mod.rs b/git-actor/src/signature/mod.rs index f928da713d9..b8ec635ed9b 100644 --- a/git-actor/src/signature/mod.rs +++ b/git-actor/src/signature/mod.rs @@ -124,78 +124,6 @@ mod write { } } -mod init { - use bstr::BString; - - use crate::{Signature, Time}; - - impl Signature { - /// Return an actor identified `name` and `email` at the current local time, that is a time with a timezone offset from - /// UTC based on the hosts configuration. - #[cfg(feature = "local-time-support")] - pub fn now_local( - name: impl Into, - email: impl Into, - ) -> Result { - let offset = git_features::time::tz::current_utc_offset()?; - Ok(Signature { - name: name.into(), - email: email.into(), - time: Time { - seconds_since_unix_epoch: std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .expect("the system time doesn't run backwards that much") - .as_secs() as u32, - offset_in_seconds: offset, - sign: offset.into(), - }, - }) - } - - /// Return an actor identified `name` and `email` at the current local time, or UTC time if the current time zone could - /// not be obtained. - #[cfg(feature = "local-time-support")] - pub fn now_local_or_utc(name: impl Into, email: impl Into) -> Self { - let offset = git_features::time::tz::current_utc_offset().unwrap_or(0); - Signature { - name: name.into(), - email: email.into(), - time: Time { - seconds_since_unix_epoch: std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .expect("the system time doesn't run backwards that much") - .as_secs() as u32, - offset_in_seconds: offset, - sign: offset.into(), - }, - } - } - - /// Return an actor identified by `name` and `email` at the current time in UTC. - /// - /// This would be most useful for bot users, otherwise the [`now_local()`][Signature::now_local()] method should be preferred. - pub fn now_utc(name: impl Into, email: impl Into) -> Self { - let utc_offset = 0; - Signature { - name: name.into(), - email: email.into(), - time: Time { - seconds_since_unix_epoch: seconds_since_unix_epoch(), - offset_in_seconds: utc_offset, - sign: utc_offset.into(), - }, - } - } - } - - fn seconds_since_unix_epoch() -> u32 { - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .expect("the system time doesn't run backwards that much") - .as_secs() as u32 - } -} - /// mod decode; pub use decode::decode; diff --git a/git-features/Cargo.toml b/git-features/Cargo.toml index f7be1aa62dc..8d797448568 100644 --- a/git-features/Cargo.toml +++ b/git-features/Cargo.toml @@ -114,9 +114,6 @@ bytes = { version = "1.0.0", optional = true } flate2 = { version = "1.0.17", optional = true, default-features = false } quick-error = { version = "2.0.0", optional = true } -## make the `time` module available with access to the local time as configured by the system. -time = { version = "0.3.2", optional = true, default-features = false, features = ["local-offset"] } - ## If enabled, OnceCell will be made available for interior mutability either in sync or unsync forms. once_cell = { version = "1.13.0", optional = true } diff --git a/git-features/src/lib.rs b/git-features/src/lib.rs index f8af9589ddd..2d2b55f084a 100644 --- a/git-features/src/lib.rs +++ b/git-features/src/lib.rs @@ -31,10 +31,6 @@ pub mod threading; #[cfg(feature = "zlib")] pub mod zlib; -/// -#[cfg(feature = "time")] -pub mod time; - /// pub mod iter { /// An iterator over chunks of input, producing `Vec` with a size of `size`, with the last chunk being the remainder and thus diff --git a/git-repository/Cargo.toml b/git-repository/Cargo.toml index dac0c02961c..fb38f5f7137 100644 --- a/git-repository/Cargo.toml +++ b/git-repository/Cargo.toml @@ -36,7 +36,7 @@ blocking-http-transport = ["git-transport/http-client-curl"] ## Provide additional non-networked functionality like `git-url` and `git-diff`. local = [ "git-url", "git-diff" ] ## Turns on access to all stable features that are unrelated to networking. -one-stop-shop = [ "local", "local-time-support" ] +one-stop-shop = [ "local" ] #! ### Other @@ -45,8 +45,6 @@ serde1 = ["git-pack/serde1", "git-object/serde1", "git-protocol/serde1", "git-tr ## Activate other features that maximize performance, like usage of threads, `zlib-ng` and access to caching in object databases. ## **Note** that max-performance = ["git-features/parallel", "git-features/zlib-ng-compat", "git-pack/pack-cache-lru-static", "git-pack/pack-cache-lru-dynamic"] -## Functions dealing with time may include the local timezone offset, not just UTC with the offset being zero. -local-time-support = ["git-actor/local-time-support"] ## Re-export stability tier 2 crates for convenience and make `Repository` struct fields with types from these crates publicly accessible. ## Doing so is less stable than the stability tier 1 that `git-repository` is a member of. unstable = ["git-index", "git-mailmap", "git-glob", "git-credentials", "git-attributes"] diff --git a/gitoxide-core/Cargo.toml b/gitoxide-core/Cargo.toml index 2a9d754c968..f08f3d72901 100644 --- a/gitoxide-core/Cargo.toml +++ b/gitoxide-core/Cargo.toml @@ -32,8 +32,6 @@ async-client = ["git-repository/async-network-client", "async-trait", "futures-i #! ### Other ## Data structures implement `serde::Serialize` and `serde::Deserialize`. serde1 = ["git-commitgraph/serde1", "git-repository/serde1", "serde_json", "serde"] -## Functions dealing with time may include the local timezone offset, not just UTC with the offset being zero. -local-time-support = ["git-repository/local-time-support"] [dependencies] From 330d0a19d54aabac868b76ef6281fffdbdcde53c Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 21 Jul 2022 22:05:44 +0800 Subject: [PATCH 340/366] first sketch of using configuration and environment variables for author/committer (#331) --- Cargo.lock | 1 + git-config/src/file/access/read_only.rs | 4 +- git-repository/Cargo.toml | 1 + git-repository/src/lib.rs | 3 + git-repository/src/repository/identity.rs | 69 +++++++++++++++++++++-- 5 files changed, 72 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4375924471f..4a5a48b1629 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1492,6 +1492,7 @@ dependencies = [ "git-attributes", "git-config", "git-credentials", + "git-date", "git-diff", "git-discover", "git-features", diff --git a/git-config/src/file/access/read_only.rs b/git-config/src/file/access/read_only.rs index 3bac7a7caa6..7891d2a0b76 100644 --- a/git-config/src/file/access/read_only.rs +++ b/git-config/src/file/access/read_only.rs @@ -125,7 +125,7 @@ impl<'event> File<'event> { /// Returns the last found immutable section with a given `name` and optional `subsection_name`. pub fn section( - &mut self, + &self, name: impl AsRef, subsection_name: Option<&str>, ) -> Result<&file::Section<'event>, lookup::existing::Error> { @@ -139,7 +139,7 @@ impl<'event> File<'event> { /// If there are sections matching `section_name` and `subsection_name` but the `filter` rejects all of them, `Ok(None)` /// is returned. pub fn section_filter<'a>( - &'a mut self, + &'a self, name: impl AsRef, subsection_name: Option<&str>, filter: &mut MetadataFilter, diff --git a/git-repository/Cargo.toml b/git-repository/Cargo.toml index fb38f5f7137..4c9dfcb8391 100644 --- a/git-repository/Cargo.toml +++ b/git-repository/Cargo.toml @@ -60,6 +60,7 @@ git-tempfile = { version = "^2.0.0", path = "../git-tempfile" } git-lock = { version = "^2.0.0", path = "../git-lock" } git-validate = { version = "^0.5.4", path = "../git-validate" } git-sec = { version = "^0.3.0", path = "../git-sec", features = ["thiserror"] } +git-date = { version = "^0.0.1", path = "../git-date" } git-config = { version = "^0.6.0", path = "../git-config" } git-odb = { version = "^0.31.0", path = "../git-odb" } diff --git a/git-repository/src/lib.rs b/git-repository/src/lib.rs index c49ff0efd9a..d4bd82d34c1 100644 --- a/git-repository/src/lib.rs +++ b/git-repository/src/lib.rs @@ -88,6 +88,7 @@ //! * [`url`] //! * [`actor`] //! * [`bstr`][bstr] +//! * [`date`] //! * [`mod@discover`] //! * [`index`] //! * [`glob`] @@ -129,6 +130,8 @@ pub use git_actor as actor; pub use git_attributes as attrs; #[cfg(all(feature = "unstable", feature = "git-credentials"))] pub use git_credentials as credentials; +#[cfg(feature = "unstable")] +pub use git_date as date; #[cfg(all(feature = "unstable", feature = "git-diff"))] pub use git_diff as diff; use git_features::threading::OwnShared; diff --git a/git-repository/src/repository/identity.rs b/git-repository/src/repository/identity.rs index f145b4e9f54..14e830f5471 100644 --- a/git-repository/src/repository/identity.rs +++ b/git-repository/src/repository/identity.rs @@ -1,6 +1,8 @@ use crate::bstr::BString; use crate::permission; use git_actor::SignatureRef; +use git_config::File; +use std::borrow::Cow; /// Identity handling. impl crate::Repository { @@ -43,7 +45,19 @@ impl crate::Repository { .as_ref() .or(p.user.email.as_ref()) .map(|v| v.as_ref())?, - time: p.committer.time.unwrap_or_else(|| todo!("get local time")), + time: p.committer.time.unwrap_or_else(|| git_date::Time::now_local_or_utc()), + } + .into() + } + + /// + pub fn author(&self) -> Option> { + let p = self.config.personas(); + + git_actor::SignatureRef { + name: p.author.name.as_ref().or(p.user.name.as_ref()).map(|v| v.as_ref())?, + email: p.author.email.as_ref().or(p.user.email.as_ref()).map(|v| v.as_ref())?, + time: p.author.time.unwrap_or_else(|| git_date::Time::now_local_or_utc()), } .into() } @@ -61,11 +75,58 @@ pub(crate) struct Entity { pub(crate) struct Personas { user: Entity, committer: Entity, - // author: Entity, + author: Entity, } impl Personas { - pub fn from_config_and_env(_config: &git_config::File<'_>, _git_env: &permission::env_var::Resource) -> Self { - todo!() + pub fn from_config_and_env(config: &git_config::File<'_>, git_env: &permission::env_var::Resource) -> Self { + fn env_var(name: &str) -> Option { + std::env::var_os(name).map(|value| git_path::into_bstr(Cow::Owned(value.into())).into_owned()) + } + fn entity_in_section(name: &str, config: &File<'_>) -> (Option, Option) { + config + .section(name, None) + .map(|section| { + ( + section.value("name").map(|v| v.into_owned()), + section.value("email").map(|v| v.into_owned()), + ) + }) + .unwrap_or_default() + } + + let (mut committer_name, mut committer_email) = entity_in_section("committer", config); + let mut committer_date = None; + let ((mut author_name, mut author_email), mut author_date) = (entity_in_section("author", config), None); + let (user_name, mut user_email) = entity_in_section("user", config); + + if git_env.eq(&git_sec::Permission::Allow) { + committer_name = committer_name.or_else(|| env_var("GIT_COMMITTER_NAME")); + committer_email = committer_email.or_else(|| env_var("GIT_COMMITTER_EMAIL")); + committer_date = env_var("GIT_COMMITTER_DATE").and_then(|date| git_date::parse(date.as_ref())); + + author_name = author_name.or_else(|| env_var("GIT_AUTHOR_NAME")); + author_email = author_email.or_else(|| env_var("GIT_AUTHOR_EMAIL")); + author_date = env_var("GIT_AUTHOR_DATE").and_then(|date| git_date::parse(date.as_ref())); + + user_email = user_email.or_else(|| env_var("EMAIL")); // NOTE: we don't have permission for this specific one… + } + Personas { + user: Entity { + name: user_name, + email: user_email, + time: None, + }, + committer: Entity { + name: committer_name, + email: committer_email, + time: committer_date, + }, + author: Entity { + name: author_name, + email: author_email, + time: author_date, + }, + } } } From f932cea68ece997f711add3368db53aeb8cdf064 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 21 Jul 2022 22:21:35 +0800 Subject: [PATCH 341/366] change!: `Repository::committer()` now returns an `Option`, see `::committer_or_default()` for a method that doesn't. (#331) --- git-repository/src/repository/identity.rs | 44 ++++++++++++++++------ git-repository/src/repository/reference.rs | 10 ++--- git-repository/tests/repository/mod.rs | 2 +- git-repository/tests/repository/object.rs | 4 +- 4 files changed, 39 insertions(+), 21 deletions(-) diff --git a/git-repository/src/repository/identity.rs b/git-repository/src/repository/identity.rs index 14e830f5471..c5aa69a3f26 100644 --- a/git-repository/src/repository/identity.rs +++ b/git-repository/src/repository/identity.rs @@ -10,7 +10,11 @@ impl crate::Repository { /// in a similar vein as the default that git chooses if there is nothing configured. /// /// This can be useful as fallback for an unset `committer` or `author`. - pub fn user_default() -> SignatureRef<'static> { + /// + /// # Note + /// + /// The values are cached when the repository is instantiated. + pub fn user_default(&self) -> SignatureRef<'_> { SignatureRef { name: "gitoxide".into(), email: "gitoxide@localhost".into(), @@ -18,23 +22,19 @@ impl crate::Repository { } } - // TODO: actual implementation /// Return the committer as configured by this repository, which is determined by… /// /// * …the git configuration `committer.name|email`… - /// * …the `GIT_(COMMITTER)_(NAME|EMAIL|DATE)` and `EMAIL` environment variables… + /// * …the `GIT_COMMITTER_(NAME|EMAIL|DATE)` environment variables… /// * …the configuration for `user.name|email` as fallback… /// /// …and in that order, or `None` if there was nothing configured. In that case, one may use the - /// [`user_default()`][Self::user_default()] method. + /// [`committer_or_default()`][Self::committer_or_default()] method. /// - /// The values are cached when the repository is instantiated. - pub fn committer(&self) -> git_actor::Signature { - git_actor::Signature::empty() - } - + /// # Note /// - pub fn committer2(&self) -> Option> { + /// The values are cached when the repository is instantiated. + pub fn committer(&self) -> Option> { let p = self.config.personas(); git_actor::SignatureRef { @@ -50,7 +50,23 @@ impl crate::Repository { .into() } + /// Like [`committer()`][Self::committer()], but may use a default value in case nothing is configured. + pub fn committer_or_default(&self) -> git_actor::SignatureRef<'_> { + self.committer().unwrap_or_else(|| self.user_default()) + } + + /// Return the author as configured by this repository, which is determined by… /// + /// * …the git configuration `author.name|email`… + /// * …the `GIT_AUTHOR_(NAME|EMAIL|DATE)` environment variables… + /// * …the configuration for `user.name|email` as fallback… + /// + /// …and in that order, or `None` if there was nothing configured. In that case, one may use the + /// [`author_or_default()`][Self::author_or_default()] method. + /// + /// # Note + /// + /// The values are cached when the repository is instantiated. pub fn author(&self) -> Option> { let p = self.config.personas(); @@ -61,6 +77,11 @@ impl crate::Repository { } .into() } + + /// Like [`author()`][Self::author()], but may use a default value in case nothing is configured. + pub fn author_or_default(&self) -> git_actor::SignatureRef<'_> { + self.author().unwrap_or_else(|| self.user_default()) + } } #[derive(Debug, Clone)] @@ -97,7 +118,8 @@ impl Personas { let (mut committer_name, mut committer_email) = entity_in_section("committer", config); let mut committer_date = None; - let ((mut author_name, mut author_email), mut author_date) = (entity_in_section("author", config), None); + let (mut author_name, mut author_email) = entity_in_section("author", config); + let mut author_date = None; let (user_name, mut user_email) = entity_in_section("user", config); if git_env.eq(&git_sec::Permission::Allow) { diff --git a/git-repository/src/repository/reference.rs b/git-repository/src/repository/reference.rs index 87826b85b35..cf560b4766a 100644 --- a/git-repository/src/repository/reference.rs +++ b/git-repository/src/repository/reference.rs @@ -150,18 +150,14 @@ impl crate::Repository { lock_mode: lock::acquire::Fail, log_committer: Option<&actor::Signature>, ) -> Result, reference::edit::Error> { - let committer_storage; let committer = match log_committer { - Some(c) => c, - None => { - committer_storage = self.committer(); - &committer_storage - } + Some(c) => c.to_ref(), + None => self.committer_or_default(), }; self.refs .transaction() .prepare(edits, lock_mode)? - .commit(committer.to_ref()) + .commit(committer) .map_err(Into::into) } diff --git a/git-repository/tests/repository/mod.rs b/git-repository/tests/repository/mod.rs index c3161eff34c..f36fefa3fed 100644 --- a/git-repository/tests/repository/mod.rs +++ b/git-repository/tests/repository/mod.rs @@ -11,7 +11,7 @@ mod worktree; fn size_in_memory() { assert_eq!( std::mem::size_of::(), - 624, + 688, "size of Repository shouldn't change without us noticing, it's meant to be cloned" ); } diff --git a/git-repository/tests/repository/object.rs b/git-repository/tests/repository/object.rs index d6944fe46ef..b9d789abe31 100644 --- a/git-repository/tests/repository/object.rs +++ b/git-repository/tests/repository/object.rs @@ -57,7 +57,7 @@ mod tag { "v1.0.0", ¤t_head_id, git_object::Kind::Commit, - Some(repo.committer().to_ref()), + Some(repo.committer_or_default()), message, git_ref::transaction::PreviousValue::MustNotExist, )?; @@ -68,7 +68,7 @@ mod tag { assert_eq!(tag.name, "v1.0.0"); assert_eq!(current_head_id, tag.target(), "the tag points to the commit"); assert_eq!(tag.target_kind, git_object::Kind::Commit); - assert_eq!(*tag.tagger.as_ref().expect("tagger"), repo.committer().to_ref()); + assert_eq!(*tag.tagger.as_ref().expect("tagger"), repo.committer_or_default()); assert_eq!(tag.message, message); Ok(()) } From 68f4bc2570d455c762da7e3d675b9b507cec69bb Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 21 Jul 2022 22:30:58 +0800 Subject: [PATCH 342/366] change!: Make `SignatureRef<'_>` mandatory for editing reference changelogs. (#331) If defaults are desired, these can be set by the caller. --- git-repository/src/repository/object.rs | 2 +- git-repository/src/repository/reference.rs | 16 ++++++---------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/git-repository/src/repository/object.rs b/git-repository/src/repository/object.rs index a09bdf56702..4e5f8cd219a 100644 --- a/git-repository/src/repository/object.rs +++ b/git-repository/src/repository/object.rs @@ -171,7 +171,7 @@ impl crate::Repository { deref: true, }, git_lock::acquire::Fail::Immediately, - Some(&commit.committer), + commit.committer.to_ref(), )?; Ok(commit_id) } diff --git a/git-repository/src/repository/reference.rs b/git-repository/src/repository/reference.rs index cf560b4766a..279a6de9d5d 100644 --- a/git-repository/src/repository/reference.rs +++ b/git-repository/src/repository/reference.rs @@ -36,11 +36,11 @@ impl crate::Repository { deref: false, }, DEFAULT_LOCK_MODE, - None, + self.committer_or_default(), )?; assert_eq!(edits.len(), 1, "reference splits should ever happen"); let edit = edits.pop().expect("exactly one item"); - Ok(crate::Reference { + Ok(Reference { inner: git_ref::Reference { name: edit.name, target: id.into(), @@ -110,7 +110,7 @@ impl crate::Repository { deref: false, }, DEFAULT_LOCK_MODE, - None, + self.committer_or_default(), )?; assert_eq!( edits.len(), @@ -134,7 +134,7 @@ impl crate::Repository { &self, edit: RefEdit, lock_mode: lock::acquire::Fail, - log_committer: Option<&actor::Signature>, + log_committer: actor::SignatureRef<'_>, ) -> Result, reference::edit::Error> { self.edit_references(Some(edit), lock_mode, log_committer) } @@ -148,16 +148,12 @@ impl crate::Repository { &self, edits: impl IntoIterator, lock_mode: lock::acquire::Fail, - log_committer: Option<&actor::Signature>, + log_committer: actor::SignatureRef<'_>, ) -> Result, reference::edit::Error> { - let committer = match log_committer { - Some(c) => c.to_ref(), - None => self.committer_or_default(), - }; self.refs .transaction() .prepare(edits, lock_mode)? - .commit(committer) + .commit(log_committer) .map_err(Into::into) } From fddc7206476423a6964d61acd060305572ecd02b Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 21 Jul 2022 22:32:22 +0800 Subject: [PATCH 343/366] thanks clippy --- Makefile | 1 - .../file/init/from_paths/includes/conditional/onbranch.rs | 2 +- git-repository/src/repository/identity.rs | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 9a1bb88616c..f8e6a689844 100644 --- a/Makefile +++ b/Makefile @@ -97,7 +97,6 @@ check: ## Build all code in suitable configurations && cargo check --features rustsha1 \ && cargo check --features fast-sha1 \ && cargo check --features progress \ - && cargo check --features time \ && cargo check --features io-pipe \ && cargo check --features crc32 \ && cargo check --features zlib \ diff --git a/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs b/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs index 4d001f4d281..a2abcc01e82 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs @@ -284,7 +284,7 @@ value = branch-override-by-include }), git_repository::lock::acquire::Fail::Immediately, )? - .commit(repo.committer().to_ref())?; + .commit(repo.committer_or_default())?; let dir = assure_git_agrees(expect, dir)?; Ok(GitEnv { repo, dir }) diff --git a/git-repository/src/repository/identity.rs b/git-repository/src/repository/identity.rs index c5aa69a3f26..44250c221e7 100644 --- a/git-repository/src/repository/identity.rs +++ b/git-repository/src/repository/identity.rs @@ -45,7 +45,7 @@ impl crate::Repository { .as_ref() .or(p.user.email.as_ref()) .map(|v| v.as_ref())?, - time: p.committer.time.unwrap_or_else(|| git_date::Time::now_local_or_utc()), + time: p.committer.time.unwrap_or_else(git_date::Time::now_local_or_utc), } .into() } @@ -73,7 +73,7 @@ impl crate::Repository { git_actor::SignatureRef { name: p.author.name.as_ref().or(p.user.name.as_ref()).map(|v| v.as_ref())?, email: p.author.email.as_ref().or(p.user.email.as_ref()).map(|v| v.as_ref())?, - time: p.author.time.unwrap_or_else(|| git_date::Time::now_local_or_utc()), + time: p.author.time.unwrap_or_else(git_date::Time::now_local_or_utc), } .into() } From ad4020224114127612eaf5d1e732baf81818812d Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 21 Jul 2022 22:46:02 +0800 Subject: [PATCH 344/366] default user signature now with 'now' time, like advertised. (#331) --- git-repository/src/repository/identity.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-repository/src/repository/identity.rs b/git-repository/src/repository/identity.rs index 44250c221e7..d3d28081e68 100644 --- a/git-repository/src/repository/identity.rs +++ b/git-repository/src/repository/identity.rs @@ -18,7 +18,7 @@ impl crate::Repository { SignatureRef { name: "gitoxide".into(), email: "gitoxide@localhost".into(), - time: Default::default(), + time: git_date::Time::now_local_or_utc(), } } From 654b521323a5822cbb86e57bee159d90576fa5ff Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 21 Jul 2022 22:56:00 +0800 Subject: [PATCH 345/366] feat: expose `on_ci` in the top-level. (#331) --- tests/tools/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/tools/src/lib.rs b/tests/tools/src/lib.rs index 4f9e7608214..77fdbc0a818 100644 --- a/tests/tools/src/lib.rs +++ b/tests/tools/src/lib.rs @@ -12,6 +12,8 @@ use io_close::Close; use nom::error::VerboseError; use once_cell::sync::Lazy; use parking_lot::Mutex; + +pub use is_ci; pub use tempfile; pub type Result = std::result::Result>; From 5e0f889c1edb862d698a2d344a61f12ab3b6ade7 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 21 Jul 2022 22:56:14 +0800 Subject: [PATCH 346/366] exclude particular assertion which fails on the linux CI. (#331) --- git-config/tests/file/init/comfort.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/git-config/tests/file/init/comfort.rs b/git-config/tests/file/init/comfort.rs index 88c74f6b942..0eee42ea067 100644 --- a/git-config/tests/file/init/comfort.rs +++ b/git-config/tests/file/init/comfort.rs @@ -63,15 +63,19 @@ fn from_git_dir() -> crate::Result { "from-user.config", "per-user configuration" ); - assert_eq!( - config.string("a", None, "git").expect("present").as_ref(), - "git-application", - "we load the XDG directories, based on the HOME fallback" - ); assert_eq!( config.string("env", None, "override").expect("present").as_ref(), "from-c.config", "environment includes are resolved" ); + + // on CI this file actually exists in xdg home and our values aren't present + if !(cfg!(unix) && git_testtools::is_ci::cached()) { + assert_eq!( + config.string("a", None, "git").expect("present").as_ref(), + "git-application", + "we load the XDG directories, based on the HOME fallback" + ); + } Ok(()) } From 4dc6594686478d9d6cd09e2ba02048624c3577e7 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 22 Jul 2022 08:24:29 +0800 Subject: [PATCH 347/366] refactor (#331) --- git-config/src/file/includes/mod.rs | 4 ++-- git-repository/src/config/cache.rs | 11 +++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/git-config/src/file/includes/mod.rs b/git-config/src/file/includes/mod.rs index 3eea52ce88d..696ffbe1be3 100644 --- a/git-config/src/file/includes/mod.rs +++ b/git-config/src/file/includes/mod.rs @@ -104,8 +104,8 @@ fn append_followed_includes_recursively( source: meta.source, }; let no_follow_options = init::Options { - lossy: options.lossy, - ..Default::default() + includes: includes::Options::no_follow(), + ..options }; let mut include_config = diff --git a/git-repository/src/config/cache.rs b/git-repository/src/config/cache.rs index eb8415a3ce9..a8b9c490e3c 100644 --- a/git-repository/src/config/cache.rs +++ b/git-repository/src/config/cache.rs @@ -33,8 +33,8 @@ impl StageOne { .at(config_path) .with(git_dir_trust), git_config::file::init::Options { - lossy: !cfg!(debug_assertions), includes: git_config::file::includes::Options::no_follow(), + ..base_options() }, )? }; @@ -100,7 +100,6 @@ impl Cache { }: repository::permissions::Config, ) -> Result { let options = git_config::file::init::Options { - lossy: !cfg!(debug_assertions), includes: if use_includes { git_config::file::includes::Options::follow( interpolate_context(git_install_dir, home), @@ -112,6 +111,7 @@ impl Cache { } else { git_config::file::includes::Options::no_follow() }, + ..base_options() }; let config = { @@ -274,6 +274,13 @@ pub(crate) fn interpolate_context<'a>( } } +fn base_options() -> git_config::file::init::Options<'static> { + git_config::file::init::Options { + lossy: !cfg!(debug_assertions), + ..Default::default() + } +} + fn config_bool(config: &git_config::File<'_>, key: &str, default: bool) -> Result { let (section, key) = key.split_once('.').expect("valid section.key format"); config From 6d2e53c32145770e8314f0879d6d769090667f90 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 22 Jul 2022 09:20:07 +0800 Subject: [PATCH 348/366] tests for author/committer/user (#331) --- .../make_config_repo.tar.xz | 4 +-- .../tests/fixtures/make_config_repo.sh | 8 +++++ git-repository/tests/repository/config.rs | 33 +++++++++++++++++++ 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/git-repository/tests/fixtures/generated-archives/make_config_repo.tar.xz b/git-repository/tests/fixtures/generated-archives/make_config_repo.tar.xz index 724cde3c0ce..43e28efd2a4 100644 --- a/git-repository/tests/fixtures/generated-archives/make_config_repo.tar.xz +++ b/git-repository/tests/fixtures/generated-archives/make_config_repo.tar.xz @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:506336963df7857dadb52b694cdaa19679b63419f114bc91ae0ef25728a5c01a -size 9320 +oid sha256:035cf83cc4e212ac2df20c905dd09f27142832fc5ad5c7950d9b85ea51a672cd +size 9360 diff --git a/git-repository/tests/fixtures/make_config_repo.sh b/git-repository/tests/fixtures/make_config_repo.sh index 52c123a7f1c..04958fa98c6 100644 --- a/git-repository/tests/fixtures/make_config_repo.sh +++ b/git-repository/tests/fixtures/make_config_repo.sh @@ -19,12 +19,20 @@ cat <>.git/config [include] path = ../a.config + +[user] + name = user + email = user@email EOF cat <>a.config [a] local-override = from-a.config + +[committer] + name = committer + email = committer@email EOF cat <>b.config [a] diff --git a/git-repository/tests/repository/config.rs b/git-repository/tests/repository/config.rs index b764876abce..7f0c8f0d10b 100644 --- a/git-repository/tests/repository/config.rs +++ b/git-repository/tests/repository/config.rs @@ -16,6 +16,9 @@ fn access_values() { "GIT_CONFIG_SYSTEM", work_dir.join("system.config").display().to_string(), ) + .set("GIT_AUTHOR_NAME", "author") + .set("GIT_AUTHOR_EMAIL", "author@email") + .set("GIT_AUTHOR_DATE", "1979-02-26 18:30:00") .set("GIT_CONFIG_COUNT", "1") .set("GIT_CONFIG_KEY_0", "include.path") .set("GIT_CONFIG_VALUE_0", work_dir.join("c.config").display().to_string()); @@ -32,6 +35,36 @@ fn access_values() { ) .unwrap(); + assert_eq!( + repo.author(), + Some(git_actor::SignatureRef { + name: "author".into(), + email: "author@email".into(), + time: git_date::Time { + seconds_since_unix_epoch: 42, + offset_in_seconds: 1800, + sign: git_date::time::Sign::Plus + } + }), + "the only parsesable marker time we know right now, indicating time parse success" + ); + assert_eq!( + repo.committer(), + Some(git_actor::SignatureRef { + name: "committer".into(), + email: "committer@email".into(), + time: git_date::Time::now_local_or_utc() + }) + ); + assert_eq!( + repo.user_default(), + git_actor::SignatureRef { + name: "gitoxide".into(), + email: "gitoxide@localhost".into(), + time: git_date::Time::now_local_or_utc() + } + ); + let config = repo.config_snapshot(); assert_eq!(config.boolean("core.bare"), Some(false)); From c19d9fdc569528972f7f6255760ae86ba99848cc Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 22 Jul 2022 12:10:47 +0800 Subject: [PATCH 349/366] update README with `gix config` information (#331) --- README.md | 91 +++++++++++++------------- gitoxide-core/src/repository/config.rs | 2 +- 2 files changed, 47 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 9b94d1bc376..66de18fd25d 100644 --- a/README.md +++ b/README.md @@ -27,52 +27,53 @@ Please see _'Development Status'_ for a listing of all crates and their capabili * Based on the [git-hours] algorithm. * See the [discussion][git-hours-discussion] for some performance data. * **the `gix` program** _(plumbing)_ - lower level commands for use in automation - * **pack** - * [x] [verify](https://asciinema.org/a/352942) - * [x] [index verify](https://asciinema.org/a/352945) including each object sha1 and statistics - * [x] [explode](https://asciinema.org/a/352951), useful for transforming packs into loose objects for inspection or restoration - * [x] verify written objects (by reading them back from disk) - * [x] [receive](https://asciinema.org/a/359321) - receive a whole pack produced by **pack-send** or _git-upload-pack_, useful for `clone` like operations. - * [x] **create** - create a pack from given objects or tips of the commit graph. - * [ ] **send** - create a pack and send it using the pack protocol to stdout, similar to 'git-upload-pack', - for consumption by **pack-receive** or _git-receive-pack_ - - **multi-index** - * [x] **info** - print information about the file - * [x] **create** - create a multi-index from pack indices - * [x] **verify** - check the file for consistency - * [x] **entries** - list all entries of the file - - **index** - * [x] [create](https://asciinema.org/a/352941) - create an index file by streaming a pack file as done during clone - * [x] support for thin packs (as needed for fetch/pull) - * **commit-graph** - * [x] **verify** - assure that a commit-graph is consistent + * **config** - list the complete git configuration in human-readable form and optionally filter sections by name. + * **exclude** + * [x] **query** - check if path specs are excluded via gits exclusion rules like `.gitignore`. + * **verify** - validate a whole repository, for now only the object database. + * **commit** + * [x] **describe** - identify a commit by its closest tag in its past + * **tree** + * [x] **entries** - list tree entries for a single tree or recursively + * [x] **info** - display tree statistics + * **odb** + * [x] **info** - display odb statistics + * [x] **entries** - display all object ids in the object database * **mailmap** - * [x] **verify** - check entries of a mailmap file for parse errors and display them - * **repository** - * **exclude** - * [x] **query** - check if path specs are excluded via gits exclusion rules like `.gitignore`. - * **verify** - validate a whole repository, for now only the object database. - * **commit** - * [x] **describe** - identify a commit by its closest tag in its past - * **tree** - * [x] **entries** - list tree entries for a single tree or recursively - * [x] **info** - display tree statistics - * **odb** - * [x] **info** - display odb statistics - * [x] **entries** - display all object ids in the object database - * **mailmap** - * [x] **entries** - display all entries of the aggregated mailmap git would use for substitution - * **revision** - * [ ] **explain** - show what would be done while parsing a revision specification like `HEAD~1` - * **index** - * [x] **entries** - show detailed entry information for human or machine consumption (via JSON) - * [x] **verify** - check the index for consistency - * [x] **info** - display general information about the index itself, with detailed extension information by default - * [x] detailed information about the TREE extension - * [ ] …other extensions details aren't implemented yet - * [x] **checkout-exclusive** - a predecessor of `git worktree`, providing flexible options to evaluate checkout performance from an index and/or an object database. - * **remote** - * [ref-list](https://asciinema.org/a/359320) - list all (or given) references from a remote at the given URL + * [x] **entries** - display all entries of the aggregated mailmap git would use for substitution + * **revision** + * [ ] **explain** - show what would be done while parsing a revision specification like `HEAD~1` + * **free** - no git repository necessary + * **pack** + * [x] [verify](https://asciinema.org/a/352942) + * [x] [index verify](https://asciinema.org/a/352945) including each object sha1 and statistics + * [x] [explode](https://asciinema.org/a/352951), useful for transforming packs into loose objects for inspection or restoration + * [x] verify written objects (by reading them back from disk) + * [x] [receive](https://asciinema.org/a/359321) - receive a whole pack produced by **pack-send** or _git-upload-pack_, useful for `clone` like operations. + * [x] **create** - create a pack from given objects or tips of the commit graph. + * [ ] **send** - create a pack and send it using the pack protocol to stdout, similar to 'git-upload-pack', + for consumption by **pack-receive** or _git-receive-pack_ + - **multi-index** + * [x] **info** - print information about the file + * [x] **create** - create a multi-index from pack indices + * [x] **verify** - check the file for consistency + * [x] **entries** - list all entries of the file + - **index** + * [x] [create](https://asciinema.org/a/352941) - create an index file by streaming a pack file as done during clone + * [x] support for thin packs (as needed for fetch/pull) + * **commit-graph** + * [x] **verify** - assure that a commit-graph is consistent + * **mailmap** + * [x] **verify** - check entries of a mailmap file for parse errors and display them + * **index** + * [x] **entries** - show detailed entry information for human or machine consumption (via JSON) + * [x] **verify** - check the index for consistency + * [x] **info** - display general information about the index itself, with detailed extension information by default + * [x] detailed information about the TREE extension + * [ ] …other extensions details aren't implemented yet + * [x] **checkout-exclusive** - a predecessor of `git worktree`, providing flexible options to evaluate checkout performance from an index and/or an object database. + * **remote** + * [ref-list](https://asciinema.org/a/359320) - list all (or given) references from a remote at the given URL [skim]: https://github.com/lotabout/skim [git-hours]: https://github.com/kimmobrunfeldt/git-hours/blob/8aaeee237cb9d9028e7a2592a25ad8468b1f45e4/index.js#L114-L143 diff --git a/gitoxide-core/src/repository/config.rs b/gitoxide-core/src/repository/config.rs index b9654a03e30..48633dfd77d 100644 --- a/gitoxide-core/src/repository/config.rs +++ b/gitoxide-core/src/repository/config.rs @@ -91,7 +91,7 @@ fn write_meta(meta: &git::config::file::Metadata, out: &mut impl std::io::Write) .then(|| format!(", include level {}", meta.level)) .unwrap_or_default(), (meta.trust != git::sec::Trust::Full) - .then(|| "untrusted") + .then(|| ", untrusted") .unwrap_or_default() ) } From 59b95c94aacac174e374048b7d11d2c0984a19e0 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 22 Jul 2022 13:15:42 +0800 Subject: [PATCH 350/366] fix journey tests after `gix` restructuring (#331) --- src/plumbing/options.rs | 1 + tests/journey/gix.sh | 889 +++++++++--------- .../verify/statistics-json-success | 0 .../commit-graph/verify/statistics-success | 0 .../broken-delete-pack-to-sink-failure | 0 ...en-delete-pack-to-sink-skip-checks-success | 0 ...jects-dir-skip-checks-success-tree-zlib-ng | 0 .../pack/explode/missing-objects-dir-fail | 0 .../pack/explode/to-sink-delete-pack-success | 0 .../pack/explode/to-sink-success | 0 .../pack/explode/with-objects-dir-success | 0 .../explode/with-objects-dir-success-tree | 0 .../create/no-output-dir-as-json-success | 0 .../pack/index/create/no-output-dir-success | 0 .../pack/index/create/output-dir-content | 0 .../create/output-dir-restore-as-json-success | 0 .../index/create/output-dir-restore-success | 0 .../pack/index/create/output-dir-success | 0 .../pack/receive/file-v-any-no-output | 0 .../pack/receive/file-v-any-no-output-json | 0 ...le-v-any-no-output-non-existing-single-ref | 0 .../receive/file-v-any-no-output-single-ref | 0 .../file-v-any-no-output-wanted-ref-p1 | 0 .../pack/receive/file-v-any-with-output | 0 .../pack/receive/ls-in-output-dir | 0 .../{ => no-repo}/pack/receive/repo-refs/HEAD | 0 .../pack/receive/repo-refs/refs/heads/dev | 0 .../pack/receive/repo-refs/refs/heads/main | 0 .../receive/repo-refs/refs/tags/annotated | 0 .../receive/repo-refs/refs/tags/unannotated | 0 .../{ => no-repo}/pack/verify/index-failure | 0 .../{ => no-repo}/pack/verify/index-success | 0 .../verify/index-with-statistics-json-success | 0 .../pack/verify/index-with-statistics-success | 0 .../verify/multi-index/fast-index-success | 0 .../pack/verify/multi-index/index-success | 0 .../index-with-statistics-json-success | 0 .../multi-index/index-with-statistics-success | 0 .../{ => no-repo}/pack/verify/success | 0 .../remote/ref-list}/file-v-any | 0 .../remote/ref-list}/file-v-any-json | 0 ...s-dir-skip-checks-success-tree-miniz-oxide | 54 -- ...ack receive-no-networking-in-small-failure | 6 - .../statistics-json-success | 8 - .../commit-graph-verify/statistics-success | 6 - .../broken-delete-pack-to-sink-failure | 5 - ...en-delete-pack-to-sink-skip-checks-success | 0 ...s-dir-skip-checks-success-tree-miniz-oxide | 54 -- ...jects-dir-skip-checks-success-tree-zlib-ng | 56 -- .../pack-explode/missing-objects-dir-fail | 1 - .../pack-explode/to-sink-delete-pack-success | 0 .../plumbing/pack-explode/to-sink-success | 0 .../pack-explode/with-objects-dir-success | 0 .../with-objects-dir-success-tree | 61 -- .../no-output-dir-as-json-success | 57 -- .../no-output-dir-success | 2 - .../pack-index-from-data/output-dir-content | 2 - .../output-dir-restore-as-json-success | 57 -- .../output-dir-restore-success | 2 - .../pack-index-from-data/output-dir-success | 2 - .../pack-receive/file-v-any-no-output | 8 - .../pack-receive/file-v-any-no-output-json | 45 - ...le-v-any-no-output-non-existing-single-ref | 5 - .../file-v-any-no-output-single-ref | 2 - .../file-v-any-no-output-wanted-ref-p1 | 4 - .../pack-receive/file-v-any-with-output | 8 - .../plumbing/pack-receive/ls-in-output-dir | 2 - ...ack-receive-no-networking-in-small-failure | 1 - .../plumbing/pack-receive/repo-refs/HEAD | 1 - .../pack-receive/repo-refs/refs/heads/dev | 1 - .../pack-receive/repo-refs/refs/heads/main | 1 - .../repo-refs/refs/tags/annotated | 1 - .../repo-refs/refs/tags/unannotated | 1 - .../plumbing/pack-verify/index-failure | 6 - .../plumbing/pack-verify/index-success | 0 .../index-with-statistics-json-success | 26 - .../pack-verify/index-with-statistics-success | 31 - .../plumbing/plumbing/pack-verify/success | 0 ...te-ref-list-no-networking-in-small-failure | 1 - .../plumbing/remote/ref-list/file-v-any | 5 - .../plumbing/remote/ref-list/file-v-any-json | 34 - ...te ref-list-no-networking-in-small-failure | 6 - 82 files changed, 447 insertions(+), 1005 deletions(-) rename tests/snapshots/plumbing/{ => no-repo}/commit-graph/verify/statistics-json-success (100%) rename tests/snapshots/plumbing/{ => no-repo}/commit-graph/verify/statistics-success (100%) rename tests/snapshots/plumbing/{ => no-repo}/pack/explode/broken-delete-pack-to-sink-failure (100%) rename tests/snapshots/plumbing/{ => no-repo}/pack/explode/broken-delete-pack-to-sink-skip-checks-success (100%) rename tests/snapshots/plumbing/{ => no-repo}/pack/explode/broken-with-objects-dir-skip-checks-success-tree-zlib-ng (100%) rename tests/snapshots/plumbing/{ => no-repo}/pack/explode/missing-objects-dir-fail (100%) rename tests/snapshots/plumbing/{ => no-repo}/pack/explode/to-sink-delete-pack-success (100%) rename tests/snapshots/plumbing/{ => no-repo}/pack/explode/to-sink-success (100%) rename tests/snapshots/plumbing/{ => no-repo}/pack/explode/with-objects-dir-success (100%) rename tests/snapshots/plumbing/{ => no-repo}/pack/explode/with-objects-dir-success-tree (100%) rename tests/snapshots/plumbing/{ => no-repo}/pack/index/create/no-output-dir-as-json-success (100%) rename tests/snapshots/plumbing/{ => no-repo}/pack/index/create/no-output-dir-success (100%) rename tests/snapshots/plumbing/{ => no-repo}/pack/index/create/output-dir-content (100%) rename tests/snapshots/plumbing/{ => no-repo}/pack/index/create/output-dir-restore-as-json-success (100%) rename tests/snapshots/plumbing/{ => no-repo}/pack/index/create/output-dir-restore-success (100%) rename tests/snapshots/plumbing/{ => no-repo}/pack/index/create/output-dir-success (100%) rename tests/snapshots/plumbing/{ => no-repo}/pack/receive/file-v-any-no-output (100%) rename tests/snapshots/plumbing/{ => no-repo}/pack/receive/file-v-any-no-output-json (100%) rename tests/snapshots/plumbing/{ => no-repo}/pack/receive/file-v-any-no-output-non-existing-single-ref (100%) rename tests/snapshots/plumbing/{ => no-repo}/pack/receive/file-v-any-no-output-single-ref (100%) rename tests/snapshots/plumbing/{ => no-repo}/pack/receive/file-v-any-no-output-wanted-ref-p1 (100%) rename tests/snapshots/plumbing/{ => no-repo}/pack/receive/file-v-any-with-output (100%) rename tests/snapshots/plumbing/{ => no-repo}/pack/receive/ls-in-output-dir (100%) rename tests/snapshots/plumbing/{ => no-repo}/pack/receive/repo-refs/HEAD (100%) rename tests/snapshots/plumbing/{ => no-repo}/pack/receive/repo-refs/refs/heads/dev (100%) rename tests/snapshots/plumbing/{ => no-repo}/pack/receive/repo-refs/refs/heads/main (100%) rename tests/snapshots/plumbing/{ => no-repo}/pack/receive/repo-refs/refs/tags/annotated (100%) rename tests/snapshots/plumbing/{ => no-repo}/pack/receive/repo-refs/refs/tags/unannotated (100%) rename tests/snapshots/plumbing/{ => no-repo}/pack/verify/index-failure (100%) rename tests/snapshots/plumbing/{ => no-repo}/pack/verify/index-success (100%) rename tests/snapshots/plumbing/{ => no-repo}/pack/verify/index-with-statistics-json-success (100%) rename tests/snapshots/plumbing/{ => no-repo}/pack/verify/index-with-statistics-success (100%) rename tests/snapshots/plumbing/{ => no-repo}/pack/verify/multi-index/fast-index-success (100%) rename tests/snapshots/plumbing/{ => no-repo}/pack/verify/multi-index/index-success (100%) rename tests/snapshots/plumbing/{ => no-repo}/pack/verify/multi-index/index-with-statistics-json-success (100%) rename tests/snapshots/plumbing/{ => no-repo}/pack/verify/multi-index/index-with-statistics-success (100%) rename tests/snapshots/plumbing/{ => no-repo}/pack/verify/success (100%) rename tests/snapshots/plumbing/{plumbing/remote-ref-list => no-repo/remote/ref-list}/file-v-any (100%) rename tests/snapshots/plumbing/{plumbing/remote-ref-list => no-repo/remote/ref-list}/file-v-any-json (100%) delete mode 100644 tests/snapshots/plumbing/pack/explode/broken-with-objects-dir-skip-checks-success-tree-miniz-oxide delete mode 100644 tests/snapshots/plumbing/pack/receive/pack receive-no-networking-in-small-failure delete mode 100644 tests/snapshots/plumbing/plumbing/commit-graph-verify/statistics-json-success delete mode 100644 tests/snapshots/plumbing/plumbing/commit-graph-verify/statistics-success delete mode 100644 tests/snapshots/plumbing/plumbing/pack-explode/broken-delete-pack-to-sink-failure delete mode 100644 tests/snapshots/plumbing/plumbing/pack-explode/broken-delete-pack-to-sink-skip-checks-success delete mode 100644 tests/snapshots/plumbing/plumbing/pack-explode/broken-with-objects-dir-skip-checks-success-tree-miniz-oxide delete mode 100644 tests/snapshots/plumbing/plumbing/pack-explode/broken-with-objects-dir-skip-checks-success-tree-zlib-ng delete mode 100644 tests/snapshots/plumbing/plumbing/pack-explode/missing-objects-dir-fail delete mode 100644 tests/snapshots/plumbing/plumbing/pack-explode/to-sink-delete-pack-success delete mode 100644 tests/snapshots/plumbing/plumbing/pack-explode/to-sink-success delete mode 100644 tests/snapshots/plumbing/plumbing/pack-explode/with-objects-dir-success delete mode 100644 tests/snapshots/plumbing/plumbing/pack-explode/with-objects-dir-success-tree delete mode 100644 tests/snapshots/plumbing/plumbing/pack-index-from-data/no-output-dir-as-json-success delete mode 100644 tests/snapshots/plumbing/plumbing/pack-index-from-data/no-output-dir-success delete mode 100644 tests/snapshots/plumbing/plumbing/pack-index-from-data/output-dir-content delete mode 100644 tests/snapshots/plumbing/plumbing/pack-index-from-data/output-dir-restore-as-json-success delete mode 100644 tests/snapshots/plumbing/plumbing/pack-index-from-data/output-dir-restore-success delete mode 100644 tests/snapshots/plumbing/plumbing/pack-index-from-data/output-dir-success delete mode 100644 tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-no-output delete mode 100644 tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-no-output-json delete mode 100644 tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-no-output-non-existing-single-ref delete mode 100644 tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-no-output-single-ref delete mode 100644 tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-no-output-wanted-ref-p1 delete mode 100644 tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-with-output delete mode 100644 tests/snapshots/plumbing/plumbing/pack-receive/ls-in-output-dir delete mode 100644 tests/snapshots/plumbing/plumbing/pack-receive/pack-receive-no-networking-in-small-failure delete mode 100644 tests/snapshots/plumbing/plumbing/pack-receive/repo-refs/HEAD delete mode 100644 tests/snapshots/plumbing/plumbing/pack-receive/repo-refs/refs/heads/dev delete mode 100644 tests/snapshots/plumbing/plumbing/pack-receive/repo-refs/refs/heads/main delete mode 100644 tests/snapshots/plumbing/plumbing/pack-receive/repo-refs/refs/tags/annotated delete mode 100644 tests/snapshots/plumbing/plumbing/pack-receive/repo-refs/refs/tags/unannotated delete mode 100644 tests/snapshots/plumbing/plumbing/pack-verify/index-failure delete mode 100644 tests/snapshots/plumbing/plumbing/pack-verify/index-success delete mode 100644 tests/snapshots/plumbing/plumbing/pack-verify/index-with-statistics-json-success delete mode 100644 tests/snapshots/plumbing/plumbing/pack-verify/index-with-statistics-success delete mode 100644 tests/snapshots/plumbing/plumbing/pack-verify/success delete mode 100644 tests/snapshots/plumbing/plumbing/remote-ref-list/remote-ref-list-no-networking-in-small-failure delete mode 100644 tests/snapshots/plumbing/remote/ref-list/file-v-any delete mode 100644 tests/snapshots/plumbing/remote/ref-list/file-v-any-json delete mode 100644 tests/snapshots/plumbing/remote/ref-list/remote ref-list-no-networking-in-small-failure diff --git a/src/plumbing/options.rs b/src/plumbing/options.rs index abed5255313..58dde701425 100644 --- a/src/plumbing/options.rs +++ b/src/plumbing/options.rs @@ -189,6 +189,7 @@ pub mod revision { /// pub mod free { #[derive(Debug, clap::Subcommand)] + #[clap(visible_alias = "no-repo")] pub enum Subcommands { /// Subcommands for interacting with git remote server. #[clap(subcommand)] diff --git a/tests/journey/gix.sh b/tests/journey/gix.sh index ddc4475098a..4b1b902e149 100644 --- a/tests/journey/gix.sh +++ b/tests/journey/gix.sh @@ -46,8 +46,8 @@ title "git-tempfile crate" ) ) -title "gix repository" -(when "running 'repository'" +title "gix (with repository)" +(with "a git repository" snapshot="$snapshot/repository" (small-repo-in-sandbox (with "the 'verify' sub-command" @@ -55,14 +55,14 @@ title "gix repository" (with 'human output format' it "generates correct output" && { WITH_SNAPSHOT="$snapshot/success-format-human" \ - expect_run $SUCCESSFULLY "$exe_plumbing" --format human repo verify -s + expect_run $SUCCESSFULLY "$exe_plumbing" --format human verify -s } ) if test "$kind" = "max"; then (with "--format json" it "generates the correct output in JSON format" && { WITH_SNAPSHOT="$snapshot/success-format-json" \ - expect_run $SUCCESSFULLY "$exe_plumbing" --format json repository verify --statistics + expect_run $SUCCESSFULLY "$exe_plumbing" --format json verify --statistics } ) fi @@ -70,570 +70,573 @@ title "gix repository" ) ) -title "gix pack" -(when "running 'pack'" - snapshot="$snapshot/pack" +(with "gix free" + snapshot="$snapshot/no-repo" + title "gix free pack" + (when "running 'pack'" + snapshot="$snapshot/pack" - title "gix pack receive" - (with "the 'receive' sub-command" - snapshot="$snapshot/receive" - (small-repo-in-sandbox - if [[ "$kind" != 'small' ]]; then + title "gix free pack receive" + (with "the 'receive' sub-command" + snapshot="$snapshot/receive" + (small-repo-in-sandbox + if [[ "$kind" != 'small' ]]; then - if [[ "$kind" != 'async' ]]; then - (with "file:// protocol" - (with "version 1" - (with "NO output directory" - it "generates the correct output" && { - WITH_SNAPSHOT="$snapshot/file-v-any-no-output" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack receive -p 1 .git - } - ) - (with "output directory" - mkdir out - it "generates the correct output" && { - WITH_SNAPSHOT="$snapshot/file-v-any-with-output" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack receive -p 1 .git out/ - } - it "creates an index and a pack in the output directory" && { - WITH_SNAPSHOT="$snapshot/ls-in-output-dir" \ - expect_run $SUCCESSFULLY ls out/ - } - (with "--write-refs set" + if [[ "$kind" != 'async' ]]; then + (with "file:// protocol" + (with "version 1" + (with "NO output directory" + it "generates the correct output" && { + WITH_SNAPSHOT="$snapshot/file-v-any-no-output" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack receive -p 1 .git + } + ) + (with "output directory" + mkdir out it "generates the correct output" && { WITH_SNAPSHOT="$snapshot/file-v-any-with-output" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack receive -p 1 --refs-directory out/all-refs .git out/ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack receive -p 1 .git out/ } - it "writes references into the refs folder of the output directory" && { - expect_snapshot "$snapshot/repo-refs" out/all-refs + it "creates an index and a pack in the output directory" && { + WITH_SNAPSHOT="$snapshot/ls-in-output-dir" \ + expect_run $SUCCESSFULLY ls out/ } + (with "--write-refs set" + it "generates the correct output" && { + WITH_SNAPSHOT="$snapshot/file-v-any-with-output" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack receive -p 1 --refs-directory out/all-refs .git out/ + } + it "writes references into the refs folder of the output directory" && { + expect_snapshot "$snapshot/repo-refs" out/all-refs + } + ) + rm -Rf out ) - rm -Rf out - ) - if test "$kind" = "max"; then - (with "--format json" - it "generates the correct output in JSON format" && { - WITH_SNAPSHOT="$snapshot/file-v-any-no-output-json" \ - expect_run $SUCCESSFULLY "$exe_plumbing" --format json pack receive --protocol 1 .git - } - ) - fi - ) - (with "version 2" - (with "NO output directory" - it "generates the correct output" && { - WITH_SNAPSHOT="$snapshot/file-v-any-no-output" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack receive -p 2 .git - } - ) - (with "output directory" - mkdir out/ - it "generates the correct output" && { - WITH_SNAPSHOT="$snapshot/file-v-any-with-output" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack receive .git out/ - } - it "creates an index and a pack in the output directory" && { - WITH_SNAPSHOT="$snapshot/ls-in-output-dir" \ - expect_run $SUCCESSFULLY ls out/ - } - rm -Rf out - ) - if test "$kind" = "max"; then - (with "--format json" - it "generates the correct output in JSON format" && { - WITH_SNAPSHOT="$snapshot/file-v-any-no-output-json" \ - expect_run $SUCCESSFULLY "$exe_plumbing" --format json pack receive --protocol 2 .git - } + if test "$kind" = "max"; then + (with "--format json" + it "generates the correct output in JSON format" && { + WITH_SNAPSHOT="$snapshot/file-v-any-no-output-json" \ + expect_run $SUCCESSFULLY "$exe_plumbing" --format json free pack receive --protocol 1 .git + } + ) + fi ) - fi - ) - ) - fi - (with "git:// protocol" - launch-git-daemon - (with "version 1" - (with "NO output directory" - (with "no wanted refs" + (with "version 2" + (with "NO output directory" it "generates the correct output" && { WITH_SNAPSHOT="$snapshot/file-v-any-no-output" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack receive -p 1 git://localhost/ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack receive -p 2 .git } ) - (with "wanted refs" + (with "output directory" + mkdir out/ it "generates the correct output" && { - WITH_SNAPSHOT="$snapshot/file-v-any-no-output-wanted-ref-p1" \ - expect_run $WITH_FAILURE "$exe_plumbing" pack receive -p 1 git://localhost/ -r =refs/heads/main + WITH_SNAPSHOT="$snapshot/file-v-any-with-output" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack receive .git out/ + } + it "creates an index and a pack in the output directory" && { + WITH_SNAPSHOT="$snapshot/ls-in-output-dir" \ + expect_run $SUCCESSFULLY ls out/ } + rm -Rf out ) - ) - (with "output directory" - mkdir out - it "generates the correct output" && { - WITH_SNAPSHOT="$snapshot/file-v-any-with-output" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack receive -p 1 git://localhost/ out/ - } + if test "$kind" = "max"; then + (with "--format json" + it "generates the correct output in JSON format" && { + WITH_SNAPSHOT="$snapshot/file-v-any-no-output-json" \ + expect_run $SUCCESSFULLY "$exe_plumbing" --format json free pack receive --protocol 2 .git + } + ) + fi ) ) - (with "version 2" - (with "NO output directory" - (with "NO wanted refs" - it "generates the correct output" && { - WITH_SNAPSHOT="$snapshot/file-v-any-no-output" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack receive -p 2 git://localhost/ - } + fi + (with "git:// protocol" + launch-git-daemon + (with "version 1" + (with "NO output directory" + (with "no wanted refs" + it "generates the correct output" && { + WITH_SNAPSHOT="$snapshot/file-v-any-no-output" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack receive -p 1 git://localhost/ + } + ) + (with "wanted refs" + it "generates the correct output" && { + WITH_SNAPSHOT="$snapshot/file-v-any-no-output-wanted-ref-p1" \ + expect_run $WITH_FAILURE "$exe_plumbing" free pack receive -p 1 git://localhost/ -r =refs/heads/main + } + ) ) - (with "wanted refs" + (with "output directory" + mkdir out it "generates the correct output" && { - WITH_SNAPSHOT="$snapshot/file-v-any-no-output-single-ref" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack receive -p 2 git://localhost/ -r refs/heads/main + WITH_SNAPSHOT="$snapshot/file-v-any-with-output" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack receive -p 1 git://localhost/ out/ } - (when "ref does not exist" - it "fails with a detailed error message including what the server said" && { - WITH_SNAPSHOT="$snapshot/file-v-any-no-output-non-existing-single-ref" \ - expect_run $WITH_FAILURE "$exe_plumbing" pack receive -p 2 git://localhost/ -r refs/heads/does-not-exist + ) + ) + (with "version 2" + (with "NO output directory" + (with "NO wanted refs" + it "generates the correct output" && { + WITH_SNAPSHOT="$snapshot/file-v-any-no-output" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack receive -p 2 git://localhost/ + } + ) + (with "wanted refs" + it "generates the correct output" && { + WITH_SNAPSHOT="$snapshot/file-v-any-no-output-single-ref" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack receive -p 2 git://localhost/ -r refs/heads/main } + (when "ref does not exist" + it "fails with a detailed error message including what the server said" && { + WITH_SNAPSHOT="$snapshot/file-v-any-no-output-non-existing-single-ref" \ + expect_run $WITH_FAILURE "$exe_plumbing" free pack receive -p 2 git://localhost/ -r refs/heads/does-not-exist + } + ) ) ) - ) - (with "output directory" - it "generates the correct output" && { - WITH_SNAPSHOT="$snapshot/file-v-any-with-output" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack receive git://localhost/ out/ - } + (with "output directory" + it "generates the correct output" && { + WITH_SNAPSHOT="$snapshot/file-v-any-with-output" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack receive git://localhost/ out/ + } + ) ) ) - ) - (on_ci - if test "$kind" = "max"; then - (with "https:// protocol" - (with "version 1" - it "works" && { - expect_run $SUCCESSFULLY "$exe_plumbing" pack receive -p 1 https://github.com/byron/gitoxide - } - ) - (with "version 2" - it "works" && { - expect_run $SUCCESSFULLY "$exe_plumbing" pack receive -p 2 https://github.com/byron/gitoxide - } + (on_ci + if test "$kind" = "max"; then + (with "https:// protocol" + (with "version 1" + it "works" && { + expect_run $SUCCESSFULLY "$exe_plumbing" free pack receive -p 1 https://github.com/byron/gitoxide + } + ) + (with "version 2" + it "works" && { + expect_run $SUCCESSFULLY "$exe_plumbing" free pack receive -p 2 https://github.com/byron/gitoxide + } + ) ) + fi ) + elif [[ "$kind" = "small" ]]; then + it "fails as the CLI doesn't have networking in 'small' mode" && { + WITH_SNAPSHOT="$snapshot/pack receive-no-networking-in-small-failure" \ + expect_run 2 "$exe_plumbing" free pack receive -p 1 .git + } fi ) - elif [[ "$kind" = "small" ]]; then - it "fails as the CLI doesn't have networking in 'small' mode" && { - WITH_SNAPSHOT="$snapshot/pack receive-no-networking-in-small-failure" \ - expect_run 2 "$exe_plumbing" pack receive -p 1 .git - } - fi ) - ) - (with "the 'index' sub-command" - snapshot="$snapshot/index" - title "gix pack index create" - (with "the 'create' sub-command" - snapshot="$snapshot/create" - PACK_FILE="$fixtures/packs/pack-11fdfa9e156ab73caae3b6da867192221f2089c2.pack" - (with "a valid and complete pack file" - (with "NO output directory specified" - (with "pack file passed as file" - it "generates an index into a sink and outputs pack and index information" && { - WITH_SNAPSHOT="$snapshot/no-output-dir-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack index create -p "$PACK_FILE" - } - ) - (with "pack file passed from stdin" - it "generates an index into a sink and outputs pack and index information" && { - WITH_SNAPSHOT="$snapshot/no-output-dir-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack index create < "$PACK_FILE" - } - if test "$kind" = "max"; then - (with "--format json" - it "generates the index into a sink and outputs information as JSON" && { - WITH_SNAPSHOT="$snapshot/no-output-dir-as-json-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" --format json pack index create < "$PACK_FILE" + (with "the 'index' sub-command" + snapshot="$snapshot/index" + title "gix free pack index create" + (with "the 'create' sub-command" + snapshot="$snapshot/create" + PACK_FILE="$fixtures/packs/pack-11fdfa9e156ab73caae3b6da867192221f2089c2.pack" + (with "a valid and complete pack file" + (with "NO output directory specified" + (with "pack file passed as file" + it "generates an index into a sink and outputs pack and index information" && { + WITH_SNAPSHOT="$snapshot/no-output-dir-success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack index create -p "$PACK_FILE" } ) - fi + (with "pack file passed from stdin" + it "generates an index into a sink and outputs pack and index information" && { + WITH_SNAPSHOT="$snapshot/no-output-dir-success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack index create < "$PACK_FILE" + } + if test "$kind" = "max"; then + (with "--format json" + it "generates the index into a sink and outputs information as JSON" && { + WITH_SNAPSHOT="$snapshot/no-output-dir-as-json-success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" --format json free pack index create < "$PACK_FILE" + } + ) + fi + ) ) - ) - (sandbox - (with "with an output directory specified" - it "generates an index and outputs information" && { - WITH_SNAPSHOT="$snapshot/output-dir-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack index create -p "$PACK_FILE" "$PWD" - } - it "writes the index and pack into the directory (they have the same names, different suffixes)" && { - WITH_SNAPSHOT="$snapshot/output-dir-content" \ - expect_run $SUCCESSFULLY ls - } + (sandbox + (with "with an output directory specified" + it "generates an index and outputs information" && { + WITH_SNAPSHOT="$snapshot/output-dir-success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack index create -p "$PACK_FILE" "$PWD" + } + it "writes the index and pack into the directory (they have the same names, different suffixes)" && { + WITH_SNAPSHOT="$snapshot/output-dir-content" \ + expect_run $SUCCESSFULLY ls + } + ) ) ) - ) - (with "'restore' iteration mode" - (sandbox - cp "${PACK_FILE}" . - PACK_FILE="${PACK_FILE##*/}" - "$jtt" mess-in-the-middle "${PACK_FILE}" - - it "generates an index and outputs information (instead of failing)" && { - WITH_SNAPSHOT="$snapshot/output-dir-restore-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack index create -i restore -p "$PACK_FILE" "$PWD" - } + (with "'restore' iteration mode" + (sandbox + cp "${PACK_FILE}" . + PACK_FILE="${PACK_FILE##*/}" + "$jtt" mess-in-the-middle "${PACK_FILE}" - if test "$kind" = "max"; then - (with "--format json and the very same output directory" - it "generates the index, overwriting existing files, and outputs information as JSON" && { - WITH_SNAPSHOT="$snapshot/output-dir-restore-as-json-success" \ - SNAPSHOT_FILTER=remove-paths \ - expect_run $SUCCESSFULLY "$exe_plumbing" --format json pack index create -i restore $PWD < "$PACK_FILE" + it "generates an index and outputs information (instead of failing)" && { + WITH_SNAPSHOT="$snapshot/output-dir-restore-success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack index create -i restore -p "$PACK_FILE" "$PWD" } - ) - fi - ) - ) - ) - ) - title "gix pack multi-index" - (with "the 'multi-index' sub-command" - snapshot="$snapshot/multi-index" - title "gix pack multi-index create" - (with "the 'create' sub-command" - snapshot="$snapshot/create" - (with 'multiple pack indices' - (sandbox - it "creates a multi-index successfully" && { - expect_run $SUCCESSFULLY "$exe_plumbing" pack multi-index -i multi-pack-index create $fixtures/packs/pack-*.idx + if test "$kind" = "max"; then + (with "--format json and the very same output directory" + it "generates the index, overwriting existing files, and outputs information as JSON" && { + WITH_SNAPSHOT="$snapshot/output-dir-restore-as-json-success" \ + SNAPSHOT_FILTER=remove-paths \ + expect_run $SUCCESSFULLY "$exe_plumbing" --format json free pack index create -i restore $PWD < "$PACK_FILE" } ) + fi ) + ) ) - ) - - title "gix pack explode" - (with "the 'explode' sub-command" - snapshot="$snapshot/explode" - PACK_FILE="$fixtures/packs/pack-11fdfa9e156ab73caae3b6da867192221f2089c2" - (with "no objects directory specified" - it "explodes the pack successfully and with desired output" && { - WITH_SNAPSHOT="$snapshot/to-sink-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack explode "${PACK_FILE}.idx" - } - - (when "using the --delete-pack flag" - (sandbox - (with "a valid pack" - cp "${PACK_FILE}".idx "${PACK_FILE}".pack . - PACK_FILE="${PACK_FILE##*/}" - it "explodes the pack successfully and deletes the original pack and index" && { - WITH_SNAPSHOT="$snapshot/to-sink-delete-pack-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack explode --check skip-file-checksum --delete-pack "${PACK_FILE}.pack" - } - it "removes the original files" && { - expect_run $WITH_FAILURE test -e "${PACK_FILE}".pack - expect_run $WITH_FAILURE test -e "${PACK_FILE}".idx - } - ) - (with "a pack file that is invalid somewhere" - cp ${PACK_FILE}.idx ${PACK_FILE}.pack . - PACK_FILE="${PACK_FILE##*/}" - "$jtt" mess-in-the-middle "${PACK_FILE}".pack - - (with "and all safety checks" - it "does not explode the file at all" && { - WITH_SNAPSHOT="$snapshot/broken-delete-pack-to-sink-failure" \ - expect_run $WITH_FAILURE "$exe_plumbing" pack explode --sink-compress --check all --delete-pack "${PACK_FILE}.pack" - } + ) - it "did not touch index or pack file" && { - expect_exists "${PACK_FILE}".pack - expect_exists "${PACK_FILE}".idx - } + title "gix free pack multi-index" + (with "the 'multi-index' sub-command" + snapshot="$snapshot/multi-index" + title "gix free pack multi-index create" + (with "the 'create' sub-command" + snapshot="$snapshot/create" + (with 'multiple pack indices' + (sandbox + it "creates a multi-index successfully" && { + expect_run $SUCCESSFULLY "$exe_plumbing" free pack multi-index -i multi-pack-index create $fixtures/packs/pack-*.idx + } + ) ) + ) + ) - (with "and no safety checks at all (and an output directory)" - it "does explode the file" && { - WITH_SNAPSHOT="$snapshot/broken-delete-pack-to-sink-skip-checks-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack explode --verify --check skip-file-and-object-checksum-and-no-abort-on-decode \ - --delete-pack "${PACK_FILE}.pack" . - } + title "gix free pack explode" + (with "the 'explode' sub-command" + snapshot="$snapshot/explode" + PACK_FILE="$fixtures/packs/pack-11fdfa9e156ab73caae3b6da867192221f2089c2" + (with "no objects directory specified" + it "explodes the pack successfully and with desired output" && { + WITH_SNAPSHOT="$snapshot/to-sink-success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" no-repo pack explode "${PACK_FILE}.idx" + } + (when "using the --delete-pack flag" + (sandbox + (with "a valid pack" + cp "${PACK_FILE}".idx "${PACK_FILE}".pack . + PACK_FILE="${PACK_FILE##*/}" + it "explodes the pack successfully and deletes the original pack and index" && { + WITH_SNAPSHOT="$snapshot/to-sink-delete-pack-success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack explode --check skip-file-checksum --delete-pack "${PACK_FILE}.pack" + } it "removes the original files" && { expect_run $WITH_FAILURE test -e "${PACK_FILE}".pack expect_run $WITH_FAILURE test -e "${PACK_FILE}".idx } + ) + (with "a pack file that is invalid somewhere" + cp ${PACK_FILE}.idx ${PACK_FILE}.pack . + PACK_FILE="${PACK_FILE##*/}" + "$jtt" mess-in-the-middle "${PACK_FILE}".pack + + (with "and all safety checks" + it "does not explode the file at all" && { + WITH_SNAPSHOT="$snapshot/broken-delete-pack-to-sink-failure" \ + expect_run $WITH_FAILURE "$exe_plumbing" free pack explode --sink-compress --check all --delete-pack "${PACK_FILE}.pack" + } - (with_program tree + it "did not touch index or pack file" && { + expect_exists "${PACK_FILE}".pack + expect_exists "${PACK_FILE}".idx + } + ) + + (with "and no safety checks at all (and an output directory)" + it "does explode the file" && { + WITH_SNAPSHOT="$snapshot/broken-delete-pack-to-sink-skip-checks-success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack explode --verify --check skip-file-and-object-checksum-and-no-abort-on-decode \ + --delete-pack "${PACK_FILE}.pack" . + } - if test "$kind" = "small"; then - suffix=miniz-oxide - else - suffix=zlib-ng - fi - it "creates all pack objects, but the broken ones" && { - WITH_SNAPSHOT="$snapshot/broken-with-objects-dir-skip-checks-success-tree-$suffix" \ - expect_run $SUCCESSFULLY tree + it "removes the original files" && { + expect_run $WITH_FAILURE test -e "${PACK_FILE}".pack + expect_run $WITH_FAILURE test -e "${PACK_FILE}".idx } + + (with_program tree + + if test "$kind" = "small"; then + suffix=miniz-oxide + else + suffix=zlib-ng + fi + it "creates all pack objects, but the broken ones" && { + WITH_SNAPSHOT="$snapshot/broken-with-objects-dir-skip-checks-success-tree-$suffix" \ + expect_run $SUCCESSFULLY tree + } + ) ) ) ) ) ) - ) - (with "a non-existing directory specified" - it "fails with a helpful error message" && { - WITH_SNAPSHOT="$snapshot/missing-objects-dir-fail" \ - expect_run $WITH_FAILURE "$exe_plumbing" pack explode -c skip-file-and-object-checksum "${PACK_FILE}.idx" does-not-exist - } - ) - (with "an existing directory specified" - (sandbox - it "succeeds" && { - WITH_SNAPSHOT="$snapshot/with-objects-dir-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack explode -c skip-file-and-object-checksum-and-no-abort-on-decode \ - "${PACK_FILE}.pack" . + (with "a non-existing directory specified" + it "fails with a helpful error message" && { + WITH_SNAPSHOT="$snapshot/missing-objects-dir-fail" \ + expect_run $WITH_FAILURE "$exe_plumbing" free pack explode -c skip-file-and-object-checksum "${PACK_FILE}.idx" does-not-exist } - - (with_program tree - it "creates all pack objects" && { - WITH_SNAPSHOT="$snapshot/with-objects-dir-success-tree" \ - expect_run $SUCCESSFULLY tree + ) + (with "an existing directory specified" + (sandbox + it "succeeds" && { + WITH_SNAPSHOT="$snapshot/with-objects-dir-success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack explode -c skip-file-and-object-checksum-and-no-abort-on-decode \ + "${PACK_FILE}.pack" . } + + (with_program tree + it "creates all pack objects" && { + WITH_SNAPSHOT="$snapshot/with-objects-dir-success-tree" \ + expect_run $SUCCESSFULLY tree + } + ) ) ) ) - ) - - title "gix pack verify" - (with "the 'verify' sub-command" - snapshot="$snapshot/verify" - (with "a valid pack file" - PACK_FILE="$fixtures/packs/pack-11fdfa9e156ab73caae3b6da867192221f2089c2.pack" - it "verifies the pack successfully and with desired output" && { - WITH_SNAPSHOT="$snapshot/success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack verify "$PACK_FILE" - } - ) - (with "a valid pack INDEX file" - MULTI_PACK_INDEX="$fixtures/packs/pack-11fdfa9e156ab73caae3b6da867192221f2089c2.idx" - (with "no statistics" - it "verifies the pack index successfully and with desired output" && { - WITH_SNAPSHOT="$snapshot/index-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack verify "$MULTI_PACK_INDEX" - } - ) - (with "statistics" - it "verifies the pack index successfully and with desired output" && { - WITH_SNAPSHOT="$snapshot/index-with-statistics-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack verify --statistics "$MULTI_PACK_INDEX" - } - (with "and the less-memory algorithm" - it "verifies the pack index successfully and with desired output" && { - WITH_SNAPSHOT="$snapshot/index-with-statistics-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack verify --algorithm less-memory --statistics "$MULTI_PACK_INDEX" - } - ) - ) - (with "decode" - it "verifies the pack index successfully and with desired output, and decodes all objects" && { - WITH_SNAPSHOT="$snapshot/index-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack verify --algorithm less-memory --decode "$MULTI_PACK_INDEX" - } - ) - (with "re-encode" - it "verifies the pack index successfully and with desired output, and re-encodes all objects" && { - WITH_SNAPSHOT="$snapshot/index-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack verify --algorithm less-time --re-encode "$MULTI_PACK_INDEX" - } - ) - if test "$kind" = "max"; then - (with "statistics (JSON)" - it "verifies the pack index successfully and with desired output" && { - WITH_SNAPSHOT="$snapshot/index-with-statistics-json-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" --format json --threads 1 pack verify --statistics "$MULTI_PACK_INDEX" + title "gix free pack verify" + (with "the 'verify' sub-command" + snapshot="$snapshot/verify" + (with "a valid pack file" + PACK_FILE="$fixtures/packs/pack-11fdfa9e156ab73caae3b6da867192221f2089c2.pack" + it "verifies the pack successfully and with desired output" && { + WITH_SNAPSHOT="$snapshot/success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack verify "$PACK_FILE" } ) - fi - ) - (with "a valid multi-pack index" - snapshot="$snapshot/multi-index" - (sandbox - MULTI_PACK_INDEX=multi-pack-index - cp $fixtures/packs/pack-* . - $exe_plumbing pack multi-index -i $MULTI_PACK_INDEX create *.idx - - (when "using fast validation via 'pack multi-index verify'" - it "verifies the pack index successfully and with desired output" && { - WITH_SNAPSHOT="$snapshot/fast-index-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack multi-index -i "$MULTI_PACK_INDEX" verify - } - ) - + (with "a valid pack INDEX file" + MULTI_PACK_INDEX="$fixtures/packs/pack-11fdfa9e156ab73caae3b6da867192221f2089c2.idx" (with "no statistics" it "verifies the pack index successfully and with desired output" && { WITH_SNAPSHOT="$snapshot/index-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack verify "$MULTI_PACK_INDEX" + expect_run $SUCCESSFULLY "$exe_plumbing" free pack verify "$MULTI_PACK_INDEX" } ) (with "statistics" it "verifies the pack index successfully and with desired output" && { WITH_SNAPSHOT="$snapshot/index-with-statistics-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack verify --statistics "$MULTI_PACK_INDEX" + expect_run $SUCCESSFULLY "$exe_plumbing" free pack verify --statistics "$MULTI_PACK_INDEX" } (with "and the less-memory algorithm" it "verifies the pack index successfully and with desired output" && { WITH_SNAPSHOT="$snapshot/index-with-statistics-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack verify --algorithm less-memory --statistics "$MULTI_PACK_INDEX" + expect_run $SUCCESSFULLY "$exe_plumbing" free pack verify --algorithm less-memory --statistics "$MULTI_PACK_INDEX" } ) ) (with "decode" it "verifies the pack index successfully and with desired output, and decodes all objects" && { WITH_SNAPSHOT="$snapshot/index-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack verify --algorithm less-memory --decode "$MULTI_PACK_INDEX" + expect_run $SUCCESSFULLY "$exe_plumbing" free pack verify --algorithm less-memory --decode "$MULTI_PACK_INDEX" } ) (with "re-encode" it "verifies the pack index successfully and with desired output, and re-encodes all objects" && { WITH_SNAPSHOT="$snapshot/index-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack verify --algorithm less-time --re-encode "$MULTI_PACK_INDEX" + expect_run $SUCCESSFULLY "$exe_plumbing" free pack verify --algorithm less-time --re-encode "$MULTI_PACK_INDEX" } ) if test "$kind" = "max"; then (with "statistics (JSON)" it "verifies the pack index successfully and with desired output" && { WITH_SNAPSHOT="$snapshot/index-with-statistics-json-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" --format json --threads 1 pack verify --statistics "$MULTI_PACK_INDEX" + expect_run $SUCCESSFULLY "$exe_plumbing" --format json --threads 1 free pack verify --statistics "$MULTI_PACK_INDEX" } ) fi ) - ) - (sandbox - (with "an INvalid pack INDEX file" - MULTI_PACK_INDEX="$fixtures/packs/pack-11fdfa9e156ab73caae3b6da867192221f2089c2.idx" - cp $MULTI_PACK_INDEX index.idx - echo $'\0' >> index.idx - it "fails to verify the pack index and with desired output" && { - WITH_SNAPSHOT="$snapshot/index-failure" \ - expect_run $WITH_FAILURE "$exe_plumbing" pack verify index.idx - } - ) - ) - ) -) + (with "a valid multi-pack index" + snapshot="$snapshot/multi-index" + (sandbox + MULTI_PACK_INDEX=multi-pack-index + cp $fixtures/packs/pack-* . + $exe_plumbing free pack multi-index -i $MULTI_PACK_INDEX create *.idx -title "gix remote" -(when "running 'remote'" - snapshot="$snapshot/remote" - title "gix remote ref-list" - (with "the 'ref-list' subcommand" - snapshot="$snapshot/ref-list" - (small-repo-in-sandbox - if [[ "$kind" != "small" ]]; then - - if [[ "$kind" != "async" ]]; then - (with "file:// protocol" - (with "version 1" - it "generates the correct output" && { - WITH_SNAPSHOT="$snapshot/file-v-any" \ - expect_run $SUCCESSFULLY "$exe_plumbing" remote ref-list -p 1 .git - } - ) - (with "version 2" - it "generates the correct output" && { - WITH_SNAPSHOT="$snapshot/file-v-any" \ - expect_run $SUCCESSFULLY "$exe_plumbing" remote ref-list --protocol 2 "$PWD/.git" - } + (when "using fast validation via 'pack multi-index verify'" + it "verifies the pack index successfully and with desired output" && { + WITH_SNAPSHOT="$snapshot/fast-index-success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack multi-index -i "$MULTI_PACK_INDEX" verify + } + ) + + (with "no statistics" + it "verifies the pack index successfully and with desired output" && { + WITH_SNAPSHOT="$snapshot/index-success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack verify "$MULTI_PACK_INDEX" + } + ) + (with "statistics" + it "verifies the pack index successfully and with desired output" && { + WITH_SNAPSHOT="$snapshot/index-with-statistics-success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack verify --statistics "$MULTI_PACK_INDEX" + } + + (with "and the less-memory algorithm" + it "verifies the pack index successfully and with desired output" && { + WITH_SNAPSHOT="$snapshot/index-with-statistics-success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack verify --algorithm less-memory --statistics "$MULTI_PACK_INDEX" + } + ) + ) + (with "decode" + it "verifies the pack index successfully and with desired output, and decodes all objects" && { + WITH_SNAPSHOT="$snapshot/index-success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack verify --algorithm less-memory --decode "$MULTI_PACK_INDEX" + } + ) + (with "re-encode" + it "verifies the pack index successfully and with desired output, and re-encodes all objects" && { + WITH_SNAPSHOT="$snapshot/index-success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack verify --algorithm less-time --re-encode "$MULTI_PACK_INDEX" + } + ) + if test "$kind" = "max"; then + (with "statistics (JSON)" + it "verifies the pack index successfully and with desired output" && { + WITH_SNAPSHOT="$snapshot/index-with-statistics-json-success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" --format json --threads 1 free pack verify --statistics "$MULTI_PACK_INDEX" + } + ) + fi ) - if test "$kind" = "max"; then - (with "--format json" - it "generates the correct output in JSON format" && { - WITH_SNAPSHOT="$snapshot/file-v-any-json" \ - expect_run $SUCCESSFULLY "$exe_plumbing" --format json remote ref-list .git + ) + (sandbox + (with "an INvalid pack INDEX file" + MULTI_PACK_INDEX="$fixtures/packs/pack-11fdfa9e156ab73caae3b6da867192221f2089c2.idx" + cp $MULTI_PACK_INDEX index.idx + echo $'\0' >> index.idx + it "fails to verify the pack index and with desired output" && { + WITH_SNAPSHOT="$snapshot/index-failure" \ + expect_run $WITH_FAILURE "$exe_plumbing" free pack verify index.idx } ) - fi ) - fi + ) + ) - (with "git:// protocol" - launch-git-daemon - (with "version 1" - it "generates the correct output" && { - WITH_SNAPSHOT="$snapshot/file-v-any" \ - expect_run $SUCCESSFULLY "$exe_plumbing" remote ref-list -p 1 git://localhost/ - } - ) - (with "version 2" - it "generates the correct output" && { - WITH_SNAPSHOT="$snapshot/file-v-any" \ - expect_run $SUCCESSFULLY "$exe_plumbing" remote ref-list -p 2 git://localhost/ - } + title "gix free remote" + (when "running 'remote'" + snapshot="$snapshot/remote" + title "gix remote ref-list" + (with "the 'ref-list' subcommand" + snapshot="$snapshot/ref-list" + (small-repo-in-sandbox + if [[ "$kind" != "small" ]]; then + + if [[ "$kind" != "async" ]]; then + (with "file:// protocol" + (with "version 1" + it "generates the correct output" && { + WITH_SNAPSHOT="$snapshot/file-v-any" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free remote ref-list -p 1 .git + } + ) + (with "version 2" + it "generates the correct output" && { + WITH_SNAPSHOT="$snapshot/file-v-any" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free remote ref-list --protocol 2 "$PWD/.git" + } + ) + if test "$kind" = "max"; then + (with "--format json" + it "generates the correct output in JSON format" && { + WITH_SNAPSHOT="$snapshot/file-v-any-json" \ + expect_run $SUCCESSFULLY "$exe_plumbing" --format json free remote ref-list .git + } + ) + fi ) - ) - if [[ "$kind" == "small" ]]; then - (with "https:// protocol (in small builds)" - it "fails as http is not compiled in" && { - WITH_SNAPSHOT="$snapshot/fail-http-in-small" \ - expect_run $WITH_FAILURE "$exe_plumbing" remote ref-list -p 1 https://github.com/byron/gitoxide - } - ) - fi - (on_ci - if [[ "$kind" = "max" ]]; then - (with "https:// protocol" + fi + + (with "git:// protocol" + launch-git-daemon (with "version 1" it "generates the correct output" && { - expect_run $SUCCESSFULLY "$exe_plumbing" remote ref-list -p 1 https://github.com/byron/gitoxide + WITH_SNAPSHOT="$snapshot/file-v-any" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free remote ref-list -p 1 git://localhost/ } ) (with "version 2" it "generates the correct output" && { - expect_run $SUCCESSFULLY "$exe_plumbing" remote ref-list -p 2 https://github.com/byron/gitoxide + WITH_SNAPSHOT="$snapshot/file-v-any" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free remote ref-list -p 2 git://localhost/ } ) ) + if [[ "$kind" == "small" ]]; then + (with "https:// protocol (in small builds)" + it "fails as http is not compiled in" && { + WITH_SNAPSHOT="$snapshot/fail-http-in-small" \ + expect_run $WITH_FAILURE "$exe_plumbing" free remote ref-list -p 1 https://github.com/byron/gitoxide + } + ) + fi + (on_ci + if [[ "$kind" = "max" ]]; then + (with "https:// protocol" + (with "version 1" + it "generates the correct output" && { + expect_run $SUCCESSFULLY "$exe_plumbing" free remote ref-list -p 1 https://github.com/byron/gitoxide + } + ) + (with "version 2" + it "generates the correct output" && { + expect_run $SUCCESSFULLY "$exe_plumbing" free remote ref-list -p 2 https://github.com/byron/gitoxide + } + ) + ) + fi + ) + else + it "fails as the CLI doesn't include networking in 'small' mode" && { + WITH_SNAPSHOT="$snapshot/remote ref-list-no-networking-in-small-failure" \ + expect_run 2 "$exe_plumbing" free remote ref-list -p 1 .git + } fi ) - else - it "fails as the CLI doesn't include networking in 'small' mode" && { - WITH_SNAPSHOT="$snapshot/remote ref-list-no-networking-in-small-failure" \ - expect_run 2 "$exe_plumbing" remote ref-list -p 1 .git - } - fi ) ) -) -title "gix commit-graph" -(when "running 'commit-graph'" - snapshot="$snapshot/commit-graph" - title "gix commit-graph verify" - (with "the 'verify' sub-command" - snapshot="$snapshot/verify" + title "gix free commit-graph" + (when "running 'commit-graph'" + snapshot="$snapshot/commit-graph" + title "gix free commit-graph verify" + (with "the 'verify' sub-command" + snapshot="$snapshot/verify" - (small-repo-in-sandbox - (with "a valid and complete commit-graph file" - git commit-graph write --reachable - (with "statistics" - it "generates the correct output" && { - WITH_SNAPSHOT="$snapshot/statistics-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" commit-graph verify -s .git/objects/info - } - ) - if test "$kind" = "max"; then - (with "statistics --format json" - it "generates the correct output" && { - WITH_SNAPSHOT="$snapshot/statistics-json-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" --format json commit-graph verify -s .git/objects/info - } + (small-repo-in-sandbox + (with "a valid and complete commit-graph file" + git commit-graph write --reachable + (with "statistics" + it "generates the correct output" && { + WITH_SNAPSHOT="$snapshot/statistics-success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free commit-graph verify -s .git/objects/info + } + ) + if test "$kind" = "max"; then + (with "statistics --format json" + it "generates the correct output" && { + WITH_SNAPSHOT="$snapshot/statistics-json-success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" --format json free commit-graph verify -s .git/objects/info + } + ) + fi ) - fi ) ) ) diff --git a/tests/snapshots/plumbing/commit-graph/verify/statistics-json-success b/tests/snapshots/plumbing/no-repo/commit-graph/verify/statistics-json-success similarity index 100% rename from tests/snapshots/plumbing/commit-graph/verify/statistics-json-success rename to tests/snapshots/plumbing/no-repo/commit-graph/verify/statistics-json-success diff --git a/tests/snapshots/plumbing/commit-graph/verify/statistics-success b/tests/snapshots/plumbing/no-repo/commit-graph/verify/statistics-success similarity index 100% rename from tests/snapshots/plumbing/commit-graph/verify/statistics-success rename to tests/snapshots/plumbing/no-repo/commit-graph/verify/statistics-success diff --git a/tests/snapshots/plumbing/pack/explode/broken-delete-pack-to-sink-failure b/tests/snapshots/plumbing/no-repo/pack/explode/broken-delete-pack-to-sink-failure similarity index 100% rename from tests/snapshots/plumbing/pack/explode/broken-delete-pack-to-sink-failure rename to tests/snapshots/plumbing/no-repo/pack/explode/broken-delete-pack-to-sink-failure diff --git a/tests/snapshots/plumbing/pack/explode/broken-delete-pack-to-sink-skip-checks-success b/tests/snapshots/plumbing/no-repo/pack/explode/broken-delete-pack-to-sink-skip-checks-success similarity index 100% rename from tests/snapshots/plumbing/pack/explode/broken-delete-pack-to-sink-skip-checks-success rename to tests/snapshots/plumbing/no-repo/pack/explode/broken-delete-pack-to-sink-skip-checks-success diff --git a/tests/snapshots/plumbing/pack/explode/broken-with-objects-dir-skip-checks-success-tree-zlib-ng b/tests/snapshots/plumbing/no-repo/pack/explode/broken-with-objects-dir-skip-checks-success-tree-zlib-ng similarity index 100% rename from tests/snapshots/plumbing/pack/explode/broken-with-objects-dir-skip-checks-success-tree-zlib-ng rename to tests/snapshots/plumbing/no-repo/pack/explode/broken-with-objects-dir-skip-checks-success-tree-zlib-ng diff --git a/tests/snapshots/plumbing/pack/explode/missing-objects-dir-fail b/tests/snapshots/plumbing/no-repo/pack/explode/missing-objects-dir-fail similarity index 100% rename from tests/snapshots/plumbing/pack/explode/missing-objects-dir-fail rename to tests/snapshots/plumbing/no-repo/pack/explode/missing-objects-dir-fail diff --git a/tests/snapshots/plumbing/pack/explode/to-sink-delete-pack-success b/tests/snapshots/plumbing/no-repo/pack/explode/to-sink-delete-pack-success similarity index 100% rename from tests/snapshots/plumbing/pack/explode/to-sink-delete-pack-success rename to tests/snapshots/plumbing/no-repo/pack/explode/to-sink-delete-pack-success diff --git a/tests/snapshots/plumbing/pack/explode/to-sink-success b/tests/snapshots/plumbing/no-repo/pack/explode/to-sink-success similarity index 100% rename from tests/snapshots/plumbing/pack/explode/to-sink-success rename to tests/snapshots/plumbing/no-repo/pack/explode/to-sink-success diff --git a/tests/snapshots/plumbing/pack/explode/with-objects-dir-success b/tests/snapshots/plumbing/no-repo/pack/explode/with-objects-dir-success similarity index 100% rename from tests/snapshots/plumbing/pack/explode/with-objects-dir-success rename to tests/snapshots/plumbing/no-repo/pack/explode/with-objects-dir-success diff --git a/tests/snapshots/plumbing/pack/explode/with-objects-dir-success-tree b/tests/snapshots/plumbing/no-repo/pack/explode/with-objects-dir-success-tree similarity index 100% rename from tests/snapshots/plumbing/pack/explode/with-objects-dir-success-tree rename to tests/snapshots/plumbing/no-repo/pack/explode/with-objects-dir-success-tree diff --git a/tests/snapshots/plumbing/pack/index/create/no-output-dir-as-json-success b/tests/snapshots/plumbing/no-repo/pack/index/create/no-output-dir-as-json-success similarity index 100% rename from tests/snapshots/plumbing/pack/index/create/no-output-dir-as-json-success rename to tests/snapshots/plumbing/no-repo/pack/index/create/no-output-dir-as-json-success diff --git a/tests/snapshots/plumbing/pack/index/create/no-output-dir-success b/tests/snapshots/plumbing/no-repo/pack/index/create/no-output-dir-success similarity index 100% rename from tests/snapshots/plumbing/pack/index/create/no-output-dir-success rename to tests/snapshots/plumbing/no-repo/pack/index/create/no-output-dir-success diff --git a/tests/snapshots/plumbing/pack/index/create/output-dir-content b/tests/snapshots/plumbing/no-repo/pack/index/create/output-dir-content similarity index 100% rename from tests/snapshots/plumbing/pack/index/create/output-dir-content rename to tests/snapshots/plumbing/no-repo/pack/index/create/output-dir-content diff --git a/tests/snapshots/plumbing/pack/index/create/output-dir-restore-as-json-success b/tests/snapshots/plumbing/no-repo/pack/index/create/output-dir-restore-as-json-success similarity index 100% rename from tests/snapshots/plumbing/pack/index/create/output-dir-restore-as-json-success rename to tests/snapshots/plumbing/no-repo/pack/index/create/output-dir-restore-as-json-success diff --git a/tests/snapshots/plumbing/pack/index/create/output-dir-restore-success b/tests/snapshots/plumbing/no-repo/pack/index/create/output-dir-restore-success similarity index 100% rename from tests/snapshots/plumbing/pack/index/create/output-dir-restore-success rename to tests/snapshots/plumbing/no-repo/pack/index/create/output-dir-restore-success diff --git a/tests/snapshots/plumbing/pack/index/create/output-dir-success b/tests/snapshots/plumbing/no-repo/pack/index/create/output-dir-success similarity index 100% rename from tests/snapshots/plumbing/pack/index/create/output-dir-success rename to tests/snapshots/plumbing/no-repo/pack/index/create/output-dir-success diff --git a/tests/snapshots/plumbing/pack/receive/file-v-any-no-output b/tests/snapshots/plumbing/no-repo/pack/receive/file-v-any-no-output similarity index 100% rename from tests/snapshots/plumbing/pack/receive/file-v-any-no-output rename to tests/snapshots/plumbing/no-repo/pack/receive/file-v-any-no-output diff --git a/tests/snapshots/plumbing/pack/receive/file-v-any-no-output-json b/tests/snapshots/plumbing/no-repo/pack/receive/file-v-any-no-output-json similarity index 100% rename from tests/snapshots/plumbing/pack/receive/file-v-any-no-output-json rename to tests/snapshots/plumbing/no-repo/pack/receive/file-v-any-no-output-json diff --git a/tests/snapshots/plumbing/pack/receive/file-v-any-no-output-non-existing-single-ref b/tests/snapshots/plumbing/no-repo/pack/receive/file-v-any-no-output-non-existing-single-ref similarity index 100% rename from tests/snapshots/plumbing/pack/receive/file-v-any-no-output-non-existing-single-ref rename to tests/snapshots/plumbing/no-repo/pack/receive/file-v-any-no-output-non-existing-single-ref diff --git a/tests/snapshots/plumbing/pack/receive/file-v-any-no-output-single-ref b/tests/snapshots/plumbing/no-repo/pack/receive/file-v-any-no-output-single-ref similarity index 100% rename from tests/snapshots/plumbing/pack/receive/file-v-any-no-output-single-ref rename to tests/snapshots/plumbing/no-repo/pack/receive/file-v-any-no-output-single-ref diff --git a/tests/snapshots/plumbing/pack/receive/file-v-any-no-output-wanted-ref-p1 b/tests/snapshots/plumbing/no-repo/pack/receive/file-v-any-no-output-wanted-ref-p1 similarity index 100% rename from tests/snapshots/plumbing/pack/receive/file-v-any-no-output-wanted-ref-p1 rename to tests/snapshots/plumbing/no-repo/pack/receive/file-v-any-no-output-wanted-ref-p1 diff --git a/tests/snapshots/plumbing/pack/receive/file-v-any-with-output b/tests/snapshots/plumbing/no-repo/pack/receive/file-v-any-with-output similarity index 100% rename from tests/snapshots/plumbing/pack/receive/file-v-any-with-output rename to tests/snapshots/plumbing/no-repo/pack/receive/file-v-any-with-output diff --git a/tests/snapshots/plumbing/pack/receive/ls-in-output-dir b/tests/snapshots/plumbing/no-repo/pack/receive/ls-in-output-dir similarity index 100% rename from tests/snapshots/plumbing/pack/receive/ls-in-output-dir rename to tests/snapshots/plumbing/no-repo/pack/receive/ls-in-output-dir diff --git a/tests/snapshots/plumbing/pack/receive/repo-refs/HEAD b/tests/snapshots/plumbing/no-repo/pack/receive/repo-refs/HEAD similarity index 100% rename from tests/snapshots/plumbing/pack/receive/repo-refs/HEAD rename to tests/snapshots/plumbing/no-repo/pack/receive/repo-refs/HEAD diff --git a/tests/snapshots/plumbing/pack/receive/repo-refs/refs/heads/dev b/tests/snapshots/plumbing/no-repo/pack/receive/repo-refs/refs/heads/dev similarity index 100% rename from tests/snapshots/plumbing/pack/receive/repo-refs/refs/heads/dev rename to tests/snapshots/plumbing/no-repo/pack/receive/repo-refs/refs/heads/dev diff --git a/tests/snapshots/plumbing/pack/receive/repo-refs/refs/heads/main b/tests/snapshots/plumbing/no-repo/pack/receive/repo-refs/refs/heads/main similarity index 100% rename from tests/snapshots/plumbing/pack/receive/repo-refs/refs/heads/main rename to tests/snapshots/plumbing/no-repo/pack/receive/repo-refs/refs/heads/main diff --git a/tests/snapshots/plumbing/pack/receive/repo-refs/refs/tags/annotated b/tests/snapshots/plumbing/no-repo/pack/receive/repo-refs/refs/tags/annotated similarity index 100% rename from tests/snapshots/plumbing/pack/receive/repo-refs/refs/tags/annotated rename to tests/snapshots/plumbing/no-repo/pack/receive/repo-refs/refs/tags/annotated diff --git a/tests/snapshots/plumbing/pack/receive/repo-refs/refs/tags/unannotated b/tests/snapshots/plumbing/no-repo/pack/receive/repo-refs/refs/tags/unannotated similarity index 100% rename from tests/snapshots/plumbing/pack/receive/repo-refs/refs/tags/unannotated rename to tests/snapshots/plumbing/no-repo/pack/receive/repo-refs/refs/tags/unannotated diff --git a/tests/snapshots/plumbing/pack/verify/index-failure b/tests/snapshots/plumbing/no-repo/pack/verify/index-failure similarity index 100% rename from tests/snapshots/plumbing/pack/verify/index-failure rename to tests/snapshots/plumbing/no-repo/pack/verify/index-failure diff --git a/tests/snapshots/plumbing/pack/verify/index-success b/tests/snapshots/plumbing/no-repo/pack/verify/index-success similarity index 100% rename from tests/snapshots/plumbing/pack/verify/index-success rename to tests/snapshots/plumbing/no-repo/pack/verify/index-success diff --git a/tests/snapshots/plumbing/pack/verify/index-with-statistics-json-success b/tests/snapshots/plumbing/no-repo/pack/verify/index-with-statistics-json-success similarity index 100% rename from tests/snapshots/plumbing/pack/verify/index-with-statistics-json-success rename to tests/snapshots/plumbing/no-repo/pack/verify/index-with-statistics-json-success diff --git a/tests/snapshots/plumbing/pack/verify/index-with-statistics-success b/tests/snapshots/plumbing/no-repo/pack/verify/index-with-statistics-success similarity index 100% rename from tests/snapshots/plumbing/pack/verify/index-with-statistics-success rename to tests/snapshots/plumbing/no-repo/pack/verify/index-with-statistics-success diff --git a/tests/snapshots/plumbing/pack/verify/multi-index/fast-index-success b/tests/snapshots/plumbing/no-repo/pack/verify/multi-index/fast-index-success similarity index 100% rename from tests/snapshots/plumbing/pack/verify/multi-index/fast-index-success rename to tests/snapshots/plumbing/no-repo/pack/verify/multi-index/fast-index-success diff --git a/tests/snapshots/plumbing/pack/verify/multi-index/index-success b/tests/snapshots/plumbing/no-repo/pack/verify/multi-index/index-success similarity index 100% rename from tests/snapshots/plumbing/pack/verify/multi-index/index-success rename to tests/snapshots/plumbing/no-repo/pack/verify/multi-index/index-success diff --git a/tests/snapshots/plumbing/pack/verify/multi-index/index-with-statistics-json-success b/tests/snapshots/plumbing/no-repo/pack/verify/multi-index/index-with-statistics-json-success similarity index 100% rename from tests/snapshots/plumbing/pack/verify/multi-index/index-with-statistics-json-success rename to tests/snapshots/plumbing/no-repo/pack/verify/multi-index/index-with-statistics-json-success diff --git a/tests/snapshots/plumbing/pack/verify/multi-index/index-with-statistics-success b/tests/snapshots/plumbing/no-repo/pack/verify/multi-index/index-with-statistics-success similarity index 100% rename from tests/snapshots/plumbing/pack/verify/multi-index/index-with-statistics-success rename to tests/snapshots/plumbing/no-repo/pack/verify/multi-index/index-with-statistics-success diff --git a/tests/snapshots/plumbing/pack/verify/success b/tests/snapshots/plumbing/no-repo/pack/verify/success similarity index 100% rename from tests/snapshots/plumbing/pack/verify/success rename to tests/snapshots/plumbing/no-repo/pack/verify/success diff --git a/tests/snapshots/plumbing/plumbing/remote-ref-list/file-v-any b/tests/snapshots/plumbing/no-repo/remote/ref-list/file-v-any similarity index 100% rename from tests/snapshots/plumbing/plumbing/remote-ref-list/file-v-any rename to tests/snapshots/plumbing/no-repo/remote/ref-list/file-v-any diff --git a/tests/snapshots/plumbing/plumbing/remote-ref-list/file-v-any-json b/tests/snapshots/plumbing/no-repo/remote/ref-list/file-v-any-json similarity index 100% rename from tests/snapshots/plumbing/plumbing/remote-ref-list/file-v-any-json rename to tests/snapshots/plumbing/no-repo/remote/ref-list/file-v-any-json diff --git a/tests/snapshots/plumbing/pack/explode/broken-with-objects-dir-skip-checks-success-tree-miniz-oxide b/tests/snapshots/plumbing/pack/explode/broken-with-objects-dir-skip-checks-success-tree-miniz-oxide deleted file mode 100644 index 44c9b820241..00000000000 --- a/tests/snapshots/plumbing/pack/explode/broken-with-objects-dir-skip-checks-success-tree-miniz-oxide +++ /dev/null @@ -1,54 +0,0 @@ -. -├── 0e -│   └── ad45fc727edcf5cadca25ef922284f32bb6fc1 -├── 15 -│   └── 926d8d6d17d1cbdf7f03c457e8ff983270f363 -├── 18 -│   └── bd3fc20b0565f94bce0a3e94b6a83b26b88627 -├── 1d -│   └── fd336d2290794b0b1f80d98af33f725da6f42d -├── 2b -│   └── 621c1a3aac23b8258885a9b4658d9ac993742f -├── 2c -│   └── 1e59ee54facb7d72c0061d06b9fe3889f357a9 -├── 2d -│   └── ad8b277db3a95919bd904133d7e7cc3e323cb9 -├── 3a -│   └── b660ad62dd7c8c8bd637aa9bc1c2843a8439fe -├── 3d -│   └── 650a1c41a4529863818fd613b95e83668bbfc1 -├── 41 -│   └── 97ce3c6d943759e1088a0298b64571b4bc725a -├── 50 -│   └── 1b297447a8255d3533c6858bb692575cdefaa0 -├── 5d -│   └── e2eda652f29103c0d160f8c05d7e83b653a157 -├── 66 -│   └── 74d310d179400358d581f9725cbd4a2c32e3bf -├── 68 -│   └── b95733c796b12571fb1f656062a15a78e7dcf4 -├── 83 -│   └── d9602eccfc733a550812ce492d4caa0af625c8 -├── 84 -│   ├── 26f672fc65239135b1f1580bb79ecb16fd05f0 -│   └── 81dbefa2fb9398a673fe1f48dc480c1f558890 -├── 85 -│   └── 48234cfc7b4f0c9475d24d4c386783533a8034 -├── 88 -│   └── 58983d81b0eef76eb55d21a0d96b7b16846eca -├── af -│   └── 4f6405296dec699321ca59d48583ffa0323b0e -├── b2 -│   └── 025146d0718d953036352f8435cfa392b1d799 -├── bb -│   └── a287531b3a845faa032a8fef3e6d70d185c89b -├── bd -│   └── 91890c62d85ec16aadd3fb991b3ad7a365adde -├── cb -│   └── 572206d9dac4ba52878e7e1a4a7028d85707ab -├── e2 -│   └── 34c232ce0b8acef3f43fa34c036e68522b5612 -└── e8 - └── 00b9c207e17f9b11e321cc1fba5dfe08af4222 - -25 directories, 26 files \ No newline at end of file diff --git a/tests/snapshots/plumbing/pack/receive/pack receive-no-networking-in-small-failure b/tests/snapshots/plumbing/pack/receive/pack receive-no-networking-in-small-failure deleted file mode 100644 index 04aa399b4c1..00000000000 --- a/tests/snapshots/plumbing/pack/receive/pack receive-no-networking-in-small-failure +++ /dev/null @@ -1,6 +0,0 @@ -error: Found argument 'receive' which wasn't expected, or isn't valid in this context - -USAGE: - gix pack - -For more information try --help \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/commit-graph-verify/statistics-json-success b/tests/snapshots/plumbing/plumbing/commit-graph-verify/statistics-json-success deleted file mode 100644 index 301cf39a105..00000000000 --- a/tests/snapshots/plumbing/plumbing/commit-graph-verify/statistics-json-success +++ /dev/null @@ -1,8 +0,0 @@ -{ - "longest_path_length": 2, - "num_commits": 3, - "parent_counts": { - "0": 1, - "1": 2 - } -} \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/commit-graph-verify/statistics-success b/tests/snapshots/plumbing/plumbing/commit-graph-verify/statistics-success deleted file mode 100644 index b0bf9808c4a..00000000000 --- a/tests/snapshots/plumbing/plumbing/commit-graph-verify/statistics-success +++ /dev/null @@ -1,6 +0,0 @@ -number of commits with the given number of parents - 0: 1 - 1: 2 - ->: 3 - -longest path length between two commits: 2 \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-explode/broken-delete-pack-to-sink-failure b/tests/snapshots/plumbing/plumbing/pack-explode/broken-delete-pack-to-sink-failure deleted file mode 100644 index 188504fa7b4..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-explode/broken-delete-pack-to-sink-failure +++ /dev/null @@ -1,5 +0,0 @@ -Error: Failed to explode the entire pack - some loose objects may have been created nonetheless - -Caused by: - 0: The pack of this index file failed to verify its checksums - 1: pack checksum mismatch: expected f1cd3cc7bc63a4a2b357a475a58ad49b40355470, got 337fe3b886fc5041a35313887d68feefeae52519 \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-explode/broken-delete-pack-to-sink-skip-checks-success b/tests/snapshots/plumbing/plumbing/pack-explode/broken-delete-pack-to-sink-skip-checks-success deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/snapshots/plumbing/plumbing/pack-explode/broken-with-objects-dir-skip-checks-success-tree-miniz-oxide b/tests/snapshots/plumbing/plumbing/pack-explode/broken-with-objects-dir-skip-checks-success-tree-miniz-oxide deleted file mode 100644 index 44c9b820241..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-explode/broken-with-objects-dir-skip-checks-success-tree-miniz-oxide +++ /dev/null @@ -1,54 +0,0 @@ -. -├── 0e -│   └── ad45fc727edcf5cadca25ef922284f32bb6fc1 -├── 15 -│   └── 926d8d6d17d1cbdf7f03c457e8ff983270f363 -├── 18 -│   └── bd3fc20b0565f94bce0a3e94b6a83b26b88627 -├── 1d -│   └── fd336d2290794b0b1f80d98af33f725da6f42d -├── 2b -│   └── 621c1a3aac23b8258885a9b4658d9ac993742f -├── 2c -│   └── 1e59ee54facb7d72c0061d06b9fe3889f357a9 -├── 2d -│   └── ad8b277db3a95919bd904133d7e7cc3e323cb9 -├── 3a -│   └── b660ad62dd7c8c8bd637aa9bc1c2843a8439fe -├── 3d -│   └── 650a1c41a4529863818fd613b95e83668bbfc1 -├── 41 -│   └── 97ce3c6d943759e1088a0298b64571b4bc725a -├── 50 -│   └── 1b297447a8255d3533c6858bb692575cdefaa0 -├── 5d -│   └── e2eda652f29103c0d160f8c05d7e83b653a157 -├── 66 -│   └── 74d310d179400358d581f9725cbd4a2c32e3bf -├── 68 -│   └── b95733c796b12571fb1f656062a15a78e7dcf4 -├── 83 -│   └── d9602eccfc733a550812ce492d4caa0af625c8 -├── 84 -│   ├── 26f672fc65239135b1f1580bb79ecb16fd05f0 -│   └── 81dbefa2fb9398a673fe1f48dc480c1f558890 -├── 85 -│   └── 48234cfc7b4f0c9475d24d4c386783533a8034 -├── 88 -│   └── 58983d81b0eef76eb55d21a0d96b7b16846eca -├── af -│   └── 4f6405296dec699321ca59d48583ffa0323b0e -├── b2 -│   └── 025146d0718d953036352f8435cfa392b1d799 -├── bb -│   └── a287531b3a845faa032a8fef3e6d70d185c89b -├── bd -│   └── 91890c62d85ec16aadd3fb991b3ad7a365adde -├── cb -│   └── 572206d9dac4ba52878e7e1a4a7028d85707ab -├── e2 -│   └── 34c232ce0b8acef3f43fa34c036e68522b5612 -└── e8 - └── 00b9c207e17f9b11e321cc1fba5dfe08af4222 - -25 directories, 26 files \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-explode/broken-with-objects-dir-skip-checks-success-tree-zlib-ng b/tests/snapshots/plumbing/plumbing/pack-explode/broken-with-objects-dir-skip-checks-success-tree-zlib-ng deleted file mode 100644 index 3b1ec68144d..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-explode/broken-with-objects-dir-skip-checks-success-tree-zlib-ng +++ /dev/null @@ -1,56 +0,0 @@ -. -├── 0e -│   └── ad45fc727edcf5cadca25ef922284f32bb6fc1 -├── 15 -│   └── 926d8d6d17d1cbdf7f03c457e8ff983270f363 -├── 18 -│   └── bd3fc20b0565f94bce0a3e94b6a83b26b88627 -├── 1d -│   └── fd336d2290794b0b1f80d98af33f725da6f42d -├── 2b -│   └── 621c1a3aac23b8258885a9b4658d9ac993742f -├── 2c -│   └── 1e59ee54facb7d72c0061d06b9fe3889f357a9 -├── 2d -│   └── ad8b277db3a95919bd904133d7e7cc3e323cb9 -├── 3a -│   └── b660ad62dd7c8c8bd637aa9bc1c2843a8439fe -├── 3d -│   └── 650a1c41a4529863818fd613b95e83668bbfc1 -├── 41 -│   └── 97ce3c6d943759e1088a0298b64571b4bc725a -├── 50 -│   └── 1b297447a8255d3533c6858bb692575cdefaa0 -├── 5d -│   └── e2eda652f29103c0d160f8c05d7e83b653a157 -├── 66 -│   └── 74d310d179400358d581f9725cbd4a2c32e3bf -├── 68 -│   └── b95733c796b12571fb1f656062a15a78e7dcf4 -├── 83 -│   └── d9602eccfc733a550812ce492d4caa0af625c8 -├── 84 -│   ├── 26f672fc65239135b1f1580bb79ecb16fd05f0 -│   └── 81dbefa2fb9398a673fe1f48dc480c1f558890 -├── 85 -│   └── 48234cfc7b4f0c9475d24d4c386783533a8034 -├── 88 -│   └── 58983d81b0eef76eb55d21a0d96b7b16846eca -├── a2 -│   └── 9ebd0e0fcbcd2a0842dd44cc7c22a90a310a3a -├── af -│   └── 4f6405296dec699321ca59d48583ffa0323b0e -├── b2 -│   └── 025146d0718d953036352f8435cfa392b1d799 -├── bb -│   └── a287531b3a845faa032a8fef3e6d70d185c89b -├── bd -│   └── 91890c62d85ec16aadd3fb991b3ad7a365adde -├── cb -│   └── 572206d9dac4ba52878e7e1a4a7028d85707ab -├── e2 -│   └── 34c232ce0b8acef3f43fa34c036e68522b5612 -└── e8 - └── 00b9c207e17f9b11e321cc1fba5dfe08af4222 - -26 directories, 27 files \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-explode/missing-objects-dir-fail b/tests/snapshots/plumbing/plumbing/pack-explode/missing-objects-dir-fail deleted file mode 100644 index 1993202aeed..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-explode/missing-objects-dir-fail +++ /dev/null @@ -1 +0,0 @@ -Error: The object directory at 'does-not-exist' is inaccessible \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-explode/to-sink-delete-pack-success b/tests/snapshots/plumbing/plumbing/pack-explode/to-sink-delete-pack-success deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/snapshots/plumbing/plumbing/pack-explode/to-sink-success b/tests/snapshots/plumbing/plumbing/pack-explode/to-sink-success deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/snapshots/plumbing/plumbing/pack-explode/with-objects-dir-success b/tests/snapshots/plumbing/plumbing/pack-explode/with-objects-dir-success deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/snapshots/plumbing/plumbing/pack-explode/with-objects-dir-success-tree b/tests/snapshots/plumbing/plumbing/pack-explode/with-objects-dir-success-tree deleted file mode 100644 index 3632ea6c75e..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-explode/with-objects-dir-success-tree +++ /dev/null @@ -1,61 +0,0 @@ -. -├── 0e -│   └── ad45fc727edcf5cadca25ef922284f32bb6fc1 -├── 15 -│   └── 926d8d6d17d1cbdf7f03c457e8ff983270f363 -├── 18 -│   └── bd3fc20b0565f94bce0a3e94b6a83b26b88627 -├── 1a -│   └── 480b442042edd4a6bacae41bf4113727e7a130 -├── 1d -│   └── fd336d2290794b0b1f80d98af33f725da6f42d -├── 2b -│   └── 621c1a3aac23b8258885a9b4658d9ac993742f -├── 2c -│   └── 1e59ee54facb7d72c0061d06b9fe3889f357a9 -├── 2d -│   └── ad8b277db3a95919bd904133d7e7cc3e323cb9 -├── 3a -│   └── b660ad62dd7c8c8bd637aa9bc1c2843a8439fe -├── 3d -│   └── 650a1c41a4529863818fd613b95e83668bbfc1 -├── 41 -│   └── 97ce3c6d943759e1088a0298b64571b4bc725a -├── 4c -│   ├── 35f641dbedaed230b5588fdc106c4538b4d09b -│   └── 97a057e41159f9767cf8704ed5ae181adf4d8d -├── 50 -│   └── 1b297447a8255d3533c6858bb692575cdefaa0 -├── 5d -│   └── e2eda652f29103c0d160f8c05d7e83b653a157 -├── 66 -│   └── 74d310d179400358d581f9725cbd4a2c32e3bf -├── 68 -│   └── b95733c796b12571fb1f656062a15a78e7dcf4 -├── 83 -│   └── d9602eccfc733a550812ce492d4caa0af625c8 -├── 84 -│   ├── 26f672fc65239135b1f1580bb79ecb16fd05f0 -│   └── 81dbefa2fb9398a673fe1f48dc480c1f558890 -├── 85 -│   └── 48234cfc7b4f0c9475d24d4c386783533a8034 -├── 88 -│   └── 58983d81b0eef76eb55d21a0d96b7b16846eca -├── ac -│   └── f86bca46d2b53d19a5a382e10def38d3e224da -├── af -│   └── 4f6405296dec699321ca59d48583ffa0323b0e -├── b2 -│   └── 025146d0718d953036352f8435cfa392b1d799 -├── bb -│   └── a287531b3a845faa032a8fef3e6d70d185c89b -├── bd -│   └── 91890c62d85ec16aadd3fb991b3ad7a365adde -├── cb -│   └── 572206d9dac4ba52878e7e1a4a7028d85707ab -├── e2 -│   └── 34c232ce0b8acef3f43fa34c036e68522b5612 -└── e8 - └── 00b9c207e17f9b11e321cc1fba5dfe08af4222 - -28 directories, 30 files \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-index-from-data/no-output-dir-as-json-success b/tests/snapshots/plumbing/plumbing/pack-index-from-data/no-output-dir-as-json-success deleted file mode 100644 index f4fa3f420b9..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-index-from-data/no-output-dir-as-json-success +++ /dev/null @@ -1,57 +0,0 @@ -{ - "index": { - "index_kind": "V2", - "index_hash": { - "Sha1": [ - 86, - 14, - 186, - 102, - 230, - 179, - 145, - 235, - 131, - 239, - 195, - 236, - 159, - 200, - 163, - 8, - 119, - 136, - 145, - 28 - ] - }, - "data_hash": { - "Sha1": [ - 241, - 205, - 60, - 199, - 188, - 99, - 164, - 162, - 179, - 87, - 164, - 117, - 165, - 138, - 212, - 155, - 64, - 53, - 84, - 112 - ] - }, - "num_objects": 30 - }, - "pack_kind": "V2", - "index_path": null, - "data_path": null -} \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-index-from-data/no-output-dir-success b/tests/snapshots/plumbing/plumbing/pack-index-from-data/no-output-dir-success deleted file mode 100644 index d781ca1ed7f..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-index-from-data/no-output-dir-success +++ /dev/null @@ -1,2 +0,0 @@ -index: 560eba66e6b391eb83efc3ec9fc8a3087788911c -pack: f1cd3cc7bc63a4a2b357a475a58ad49b40355470 \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-index-from-data/output-dir-content b/tests/snapshots/plumbing/plumbing/pack-index-from-data/output-dir-content deleted file mode 100644 index a649eb341f0..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-index-from-data/output-dir-content +++ /dev/null @@ -1,2 +0,0 @@ -f1cd3cc7bc63a4a2b357a475a58ad49b40355470.idx -f1cd3cc7bc63a4a2b357a475a58ad49b40355470.pack \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-index-from-data/output-dir-restore-as-json-success b/tests/snapshots/plumbing/plumbing/pack-index-from-data/output-dir-restore-as-json-success deleted file mode 100644 index 7027faa9fe6..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-index-from-data/output-dir-restore-as-json-success +++ /dev/null @@ -1,57 +0,0 @@ -{ - "index": { - "index_kind": "V2", - "index_hash": { - "Sha1": [ - 44, - 185, - 97, - 229, - 91, - 122, - 124, - 171, - 95, - 21, - 242, - 34, - 7, - 36, - 229, - 221, - 122, - 222, - 249, - 244 - ] - }, - "data_hash": { - "Sha1": [ - 1, - 186, - 104, - 186, - 85, - 239, - 94, - 145, - 116, - 131, - 212, - 206, - 70, - 190, - 40, - 132, - 168, - 158, - 81, - 175 - ] - }, - "num_objects": 13 - }, - "pack_kind": "V2", - "index_path": "" - "data_path": "" -} \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-index-from-data/output-dir-restore-success b/tests/snapshots/plumbing/plumbing/pack-index-from-data/output-dir-restore-success deleted file mode 100644 index 2c5237a4c0e..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-index-from-data/output-dir-restore-success +++ /dev/null @@ -1,2 +0,0 @@ -index: 2cb961e55b7a7cab5f15f2220724e5dd7adef9f4 -pack: 01ba68ba55ef5e917483d4ce46be2884a89e51af \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-index-from-data/output-dir-success b/tests/snapshots/plumbing/plumbing/pack-index-from-data/output-dir-success deleted file mode 100644 index d781ca1ed7f..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-index-from-data/output-dir-success +++ /dev/null @@ -1,2 +0,0 @@ -index: 560eba66e6b391eb83efc3ec9fc8a3087788911c -pack: f1cd3cc7bc63a4a2b357a475a58ad49b40355470 \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-no-output b/tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-no-output deleted file mode 100644 index 57c0617c306..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-no-output +++ /dev/null @@ -1,8 +0,0 @@ -index: c787de2aafb897417ca8167baeb146eabd18bc5f -pack: 346574b7331dc3a1724da218d622c6e1b6c66a57 - -3f72b39ad1600e6dac63430c15e0d875e9d3f9d6 HEAD symref-target:refs/heads/main -ee3c97678e89db4eab7420b04aef51758359f152 refs/heads/dev -3f72b39ad1600e6dac63430c15e0d875e9d3f9d6 refs/heads/main -ee3c97678e89db4eab7420b04aef51758359f152 refs/tags/annotated tag:feae03400632392a7f38e5b2775f98a439f5eaf5 -efa596d621559707b2d221f10490959b2decbc6c refs/tags/unannotated \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-no-output-json b/tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-no-output-json deleted file mode 100644 index 590a10265e2..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-no-output-json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "index": { - "index_kind": "V2", - "index_hash": "c787de2aafb897417ca8167baeb146eabd18bc5f", - "data_hash": "346574b7331dc3a1724da218d622c6e1b6c66a57", - "num_objects": 9 - }, - "pack_kind": "V2", - "index_path": null, - "data_path": null, - "refs": [ - { - "Symbolic": { - "path": "HEAD", - "target": "refs/heads/main", - "object": "3f72b39ad1600e6dac63430c15e0d875e9d3f9d6" - } - }, - { - "Direct": { - "path": "refs/heads/dev", - "object": "ee3c97678e89db4eab7420b04aef51758359f152" - } - }, - { - "Direct": { - "path": "refs/heads/main", - "object": "3f72b39ad1600e6dac63430c15e0d875e9d3f9d6" - } - }, - { - "Peeled": { - "path": "refs/tags/annotated", - "tag": "feae03400632392a7f38e5b2775f98a439f5eaf5", - "object": "ee3c97678e89db4eab7420b04aef51758359f152" - } - }, - { - "Direct": { - "path": "refs/tags/unannotated", - "object": "efa596d621559707b2d221f10490959b2decbc6c" - } - } - ] -} \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-no-output-non-existing-single-ref b/tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-no-output-non-existing-single-ref deleted file mode 100644 index 6bed155babf..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-no-output-non-existing-single-ref +++ /dev/null @@ -1,5 +0,0 @@ -Error: The server response could not be parsed - -Caused by: - 0: Upload pack reported an error - 1: unknown ref refs/heads/does-not-exist \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-no-output-single-ref b/tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-no-output-single-ref deleted file mode 100644 index 84260bc4021..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-no-output-single-ref +++ /dev/null @@ -1,2 +0,0 @@ -index: c787de2aafb897417ca8167baeb146eabd18bc5f -pack: 346574b7331dc3a1724da218d622c6e1b6c66a57 \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-no-output-wanted-ref-p1 b/tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-no-output-wanted-ref-p1 deleted file mode 100644 index c5203d08e6e..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-no-output-wanted-ref-p1 +++ /dev/null @@ -1,4 +0,0 @@ -Error: Could not access repository or failed to read streaming pack file - -Caused by: - Want to get specific refs, but remote doesn't support this capability \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-with-output b/tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-with-output deleted file mode 100644 index 43ac53bfe11..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-with-output +++ /dev/null @@ -1,8 +0,0 @@ -index: c787de2aafb897417ca8167baeb146eabd18bc5f (out/346574b7331dc3a1724da218d622c6e1b6c66a57.idx) -pack: 346574b7331dc3a1724da218d622c6e1b6c66a57 (out/346574b7331dc3a1724da218d622c6e1b6c66a57.pack) - -3f72b39ad1600e6dac63430c15e0d875e9d3f9d6 HEAD symref-target:refs/heads/main -ee3c97678e89db4eab7420b04aef51758359f152 refs/heads/dev -3f72b39ad1600e6dac63430c15e0d875e9d3f9d6 refs/heads/main -ee3c97678e89db4eab7420b04aef51758359f152 refs/tags/annotated tag:feae03400632392a7f38e5b2775f98a439f5eaf5 -efa596d621559707b2d221f10490959b2decbc6c refs/tags/unannotated \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-receive/ls-in-output-dir b/tests/snapshots/plumbing/plumbing/pack-receive/ls-in-output-dir deleted file mode 100644 index 9c73e822fe2..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-receive/ls-in-output-dir +++ /dev/null @@ -1,2 +0,0 @@ -346574b7331dc3a1724da218d622c6e1b6c66a57.idx -346574b7331dc3a1724da218d622c6e1b6c66a57.pack \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-receive/pack-receive-no-networking-in-small-failure b/tests/snapshots/plumbing/plumbing/pack-receive/pack-receive-no-networking-in-small-failure deleted file mode 100644 index 74d6602a909..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-receive/pack-receive-no-networking-in-small-failure +++ /dev/null @@ -1 +0,0 @@ -Unrecognized argument: pack-receive \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-receive/repo-refs/HEAD b/tests/snapshots/plumbing/plumbing/pack-receive/repo-refs/HEAD deleted file mode 100644 index bddcc0b83e6..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-receive/repo-refs/HEAD +++ /dev/null @@ -1 +0,0 @@ -ref: refs/heads/main \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-receive/repo-refs/refs/heads/dev b/tests/snapshots/plumbing/plumbing/pack-receive/repo-refs/refs/heads/dev deleted file mode 100644 index 1a3950e2e0c..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-receive/repo-refs/refs/heads/dev +++ /dev/null @@ -1 +0,0 @@ -ee3c97678e89db4eab7420b04aef51758359f152 \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-receive/repo-refs/refs/heads/main b/tests/snapshots/plumbing/plumbing/pack-receive/repo-refs/refs/heads/main deleted file mode 100644 index 6f4a76ab3b8..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-receive/repo-refs/refs/heads/main +++ /dev/null @@ -1 +0,0 @@ -3f72b39ad1600e6dac63430c15e0d875e9d3f9d6 \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-receive/repo-refs/refs/tags/annotated b/tests/snapshots/plumbing/plumbing/pack-receive/repo-refs/refs/tags/annotated deleted file mode 100644 index cd887a5aedb..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-receive/repo-refs/refs/tags/annotated +++ /dev/null @@ -1 +0,0 @@ -feae03400632392a7f38e5b2775f98a439f5eaf5 \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-receive/repo-refs/refs/tags/unannotated b/tests/snapshots/plumbing/plumbing/pack-receive/repo-refs/refs/tags/unannotated deleted file mode 100644 index cbc7b39d75c..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-receive/repo-refs/refs/tags/unannotated +++ /dev/null @@ -1 +0,0 @@ -efa596d621559707b2d221f10490959b2decbc6c \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-verify/index-failure b/tests/snapshots/plumbing/plumbing/pack-verify/index-failure deleted file mode 100644 index b93e9895bb1..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-verify/index-failure +++ /dev/null @@ -1,6 +0,0 @@ -Could not find matching pack file at 'index.pack' - only index file will be verified, error was: Could not open pack file at 'index.pack' -Error: Verification failure - -Caused by: - 0: Index file, pack file or object verification failed - 1: index checksum mismatch: expected 0eba66e6b391eb83efc3ec9fc8a3087788911c0a, got fa9a8a630eacc2d3df00aff604bec2451ccbc8ff \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-verify/index-success b/tests/snapshots/plumbing/plumbing/pack-verify/index-success deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/snapshots/plumbing/plumbing/pack-verify/index-with-statistics-json-success b/tests/snapshots/plumbing/plumbing/pack-verify/index-with-statistics-json-success deleted file mode 100644 index 9a42741b0ba..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-verify/index-with-statistics-json-success +++ /dev/null @@ -1,26 +0,0 @@ -{ - "average": { - "kind": "Tree", - "num_deltas": 1, - "decompressed_size": 3456, - "compressed_size": 1725, - "object_size": 9621 - }, - "objects_per_chain_length": { - "0": 18, - "1": 4, - "2": 3, - "3": 1, - "4": 2, - "5": 1, - "6": 1 - }, - "total_compressed_entries_size": 51753, - "total_decompressed_entries_size": 103701, - "total_object_size": 288658, - "pack_size": 51875, - "num_commits": 10, - "num_trees": 15, - "num_tags": 0, - "num_blobs": 5 -} \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-verify/index-with-statistics-success b/tests/snapshots/plumbing/plumbing/pack-verify/index-with-statistics-success deleted file mode 100644 index 420f9ce588d..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-verify/index-with-statistics-success +++ /dev/null @@ -1,31 +0,0 @@ -objects per delta chain length - 0: 18 - 1: 4 - 2: 3 - 3: 1 - 4: 2 - 5: 1 - 6: 1 - ->: 30 - -averages - delta chain length: 1; - decompressed entry [B]: 3456; - compressed entry [B]: 1725; - decompressed object size [B]: 9621; - -compression - compressed entries size : 51.8 KB - decompressed entries size : 103.7 KB - total object size : 288.7 KB - pack size : 51.9 KB - - num trees : 15 - num blobs : 5 - num commits : 10 - num tags : 0 - - compression ratio : 2.00 - delta compression ratio : 5.58 - delta gain : 2.78 - pack overhead : 0.235% \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-verify/success b/tests/snapshots/plumbing/plumbing/pack-verify/success deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/snapshots/plumbing/plumbing/remote-ref-list/remote-ref-list-no-networking-in-small-failure b/tests/snapshots/plumbing/plumbing/remote-ref-list/remote-ref-list-no-networking-in-small-failure deleted file mode 100644 index aaf27b70548..00000000000 --- a/tests/snapshots/plumbing/plumbing/remote-ref-list/remote-ref-list-no-networking-in-small-failure +++ /dev/null @@ -1 +0,0 @@ -Unrecognized argument: remote-ref-list \ No newline at end of file diff --git a/tests/snapshots/plumbing/remote/ref-list/file-v-any b/tests/snapshots/plumbing/remote/ref-list/file-v-any deleted file mode 100644 index 1ff9b7937dc..00000000000 --- a/tests/snapshots/plumbing/remote/ref-list/file-v-any +++ /dev/null @@ -1,5 +0,0 @@ -3f72b39ad1600e6dac63430c15e0d875e9d3f9d6 HEAD symref-target:refs/heads/main -ee3c97678e89db4eab7420b04aef51758359f152 refs/heads/dev -3f72b39ad1600e6dac63430c15e0d875e9d3f9d6 refs/heads/main -ee3c97678e89db4eab7420b04aef51758359f152 refs/tags/annotated tag:feae03400632392a7f38e5b2775f98a439f5eaf5 -efa596d621559707b2d221f10490959b2decbc6c refs/tags/unannotated \ No newline at end of file diff --git a/tests/snapshots/plumbing/remote/ref-list/file-v-any-json b/tests/snapshots/plumbing/remote/ref-list/file-v-any-json deleted file mode 100644 index e4afb4c6b48..00000000000 --- a/tests/snapshots/plumbing/remote/ref-list/file-v-any-json +++ /dev/null @@ -1,34 +0,0 @@ -[ - { - "Symbolic": { - "path": "HEAD", - "target": "refs/heads/main", - "object": "3f72b39ad1600e6dac63430c15e0d875e9d3f9d6" - } - }, - { - "Direct": { - "path": "refs/heads/dev", - "object": "ee3c97678e89db4eab7420b04aef51758359f152" - } - }, - { - "Direct": { - "path": "refs/heads/main", - "object": "3f72b39ad1600e6dac63430c15e0d875e9d3f9d6" - } - }, - { - "Peeled": { - "path": "refs/tags/annotated", - "tag": "feae03400632392a7f38e5b2775f98a439f5eaf5", - "object": "ee3c97678e89db4eab7420b04aef51758359f152" - } - }, - { - "Direct": { - "path": "refs/tags/unannotated", - "object": "efa596d621559707b2d221f10490959b2decbc6c" - } - } -] \ No newline at end of file diff --git a/tests/snapshots/plumbing/remote/ref-list/remote ref-list-no-networking-in-small-failure b/tests/snapshots/plumbing/remote/ref-list/remote ref-list-no-networking-in-small-failure deleted file mode 100644 index ebb5294e607..00000000000 --- a/tests/snapshots/plumbing/remote/ref-list/remote ref-list-no-networking-in-small-failure +++ /dev/null @@ -1,6 +0,0 @@ -error: Found argument 'remote' which wasn't expected, or isn't valid in this context - -USAGE: - gix [OPTIONS] - -For more information try --help \ No newline at end of file From 48b3f4a5077ba66d47482a80e505feb69e9ac9fc Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 22 Jul 2022 13:18:06 +0800 Subject: [PATCH 351/366] thanks clippy --- src/plumbing/main.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/plumbing/main.rs b/src/plumbing/main.rs index 71e08be61f0..252d046e4ab 100644 --- a/src/plumbing/main.rs +++ b/src/plumbing/main.rs @@ -218,17 +218,17 @@ pub fn main() -> Result<()> { }, ), }, - free::Subcommands::Mailmap { cmd } => match cmd { - free::mailmap::Platform { path, cmd } => match cmd { - free::mailmap::Subcommands::Verify => prepare_and_run( - "mailmap-verify", - verbose, - progress, - progress_keep_open, - core::mailmap::PROGRESS_RANGE, - move |_progress, out, _err| core::mailmap::verify(path, format, out), - ), - }, + free::Subcommands::Mailmap { + cmd: free::mailmap::Platform { path, cmd }, + } => match cmd { + free::mailmap::Subcommands::Verify => prepare_and_run( + "mailmap-verify", + verbose, + progress, + progress_keep_open, + core::mailmap::PROGRESS_RANGE, + move |_progress, out, _err| core::mailmap::verify(path, format, out), + ), }, free::Subcommands::Pack(subcommands) => match subcommands { free::pack::Subcommands::Create { From 06b86e05dd9a712d26456b43c8da0a11870f08df Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 22 Jul 2022 16:00:07 +0800 Subject: [PATCH 352/366] final documentation review + adjustments prior to release candidate (#331) --- git-config/src/file/init/comfort.rs | 8 ++++---- git-config/src/lib.rs | 4 ++-- git-config/src/parse/mod.rs | 5 ++--- git-config/src/types.rs | 9 +++------ git-config/src/values/color.rs | 2 +- git-config/src/values/integer.rs | 4 +--- git-config/src/values/mod.rs | 3 +-- git-config/tests/file/init/comfort.rs | 8 ++++---- 8 files changed, 18 insertions(+), 25 deletions(-) diff --git a/git-config/src/file/init/comfort.rs b/git-config/src/file/init/comfort.rs index db671a82fdb..6288ce37945 100644 --- a/git-config/src/file/init/comfort.rs +++ b/git-config/src/file/init/comfort.rs @@ -21,7 +21,7 @@ impl File<'static> { /// which excludes repository local configuration, as well as override-configuration from environment variables. /// /// Note that the file might [be empty][File::is_void()] in case no configuration file was found. - pub fn new_globals() -> Result, init::from_paths::Error> { + pub fn from_globals() -> Result, init::from_paths::Error> { let metas = [source::Kind::System, source::Kind::Global] .iter() .flat_map(|kind| kind.sources()) @@ -55,7 +55,7 @@ impl File<'static> { /// See [`git-config`'s documentation] for more information on the environment variables in question. /// /// [`git-config`'s documentation]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-GITCONFIGCOUNT - pub fn new_environment_overrides() -> Result, init::from_env::Error> { + pub fn from_environment_overrides() -> Result, init::from_env::Error> { let home = std::env::var("HOME").ok().map(PathBuf::from); let options = init::Options { includes: init::includes::Options::follow_without_conditional(home.as_deref()), @@ -107,11 +107,11 @@ impl File<'static> { lossy: false, }; - let mut globals = Self::new_globals()?; + let mut globals = Self::from_globals()?; globals.resolve_includes(options)?; local.resolve_includes(options)?; - globals.append(local).append(Self::new_environment_overrides()?); + globals.append(local).append(Self::from_environment_overrides()?); Ok(globals) } } diff --git a/git-config/src/lib.rs b/git-config/src/lib.rs index 735b65439d0..91f9c92633c 100644 --- a/git-config/src/lib.rs +++ b/git-config/src/lib.rs @@ -24,7 +24,7 @@ //! - Legacy headers like `[section.subsection]` are supposed to be turned into to lower case and compared //! case-sensitively. We keep its case and compare case-insensitively. //! -//! [^1]: When read values do not need normalization. +//! [^1]: When read values do not need normalization and it wasn't parsed in 'owned' mode. //! //! [`git-config` files]: https://git-scm.com/docs/git-config#_configuration_file //! [`File`]: crate::File @@ -45,7 +45,7 @@ pub mod parse; /// pub mod value; mod values; -pub use values::{boolean, color, integer, path}; +pub use values::{color, integer, path}; mod types; pub use types::{Boolean, Color, File, Integer, Path, Source}; diff --git a/git-config/src/parse/mod.rs b/git-config/src/parse/mod.rs index 33c51b19d57..19dc6535f11 100644 --- a/git-config/src/parse/mod.rs +++ b/git-config/src/parse/mod.rs @@ -5,7 +5,7 @@ //! The workflow for interacting with this is to use //! [`from_bytes()`] to obtain all parse events or tokens of the given input. //! -//! On a higher level, one can use [`Events`] to parse all evnets into a set +//! On a higher level, one can use [`Events`] to parse all events into a set //! of easily interpretable data type, similar to what [`File`] does. //! //! [`File`]: crate::File @@ -16,8 +16,7 @@ use bstr::BStr; mod nom; pub use self::nom::from_bytes; -/// -pub mod event; +mod event; #[path = "events.rs"] mod events_type; pub use events_type::{Events, FrontMatterEvents}; diff --git a/git-config/src/types.rs b/git-config/src/types.rs index 949d57fc384..a84c1798a45 100644 --- a/git-config/src/types.rs +++ b/git-config/src/types.rs @@ -9,8 +9,9 @@ use crate::{ parse::section, }; -/// A list of known sources for configuration files, with the first one being overridden -/// by the second one, and so forth, in order of ascending precedence. +/// A list of known sources for git configuration in order of ascending precedence. +/// +/// This means values from the first one will be overridden by values in the second one, and so forth. /// Note that included files via `include.path` and `includeIf..path` inherit /// their source. #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] @@ -148,10 +149,6 @@ pub struct Integer { } /// Any value that can be interpreted as a boolean. -/// -/// Note that while values can effectively be any byte string, the `git-config` -/// documentation has a strict subset of values that may be interpreted as a -/// boolean value, all of which are ASCII and thus UTF-8 representable. #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] #[allow(missing_docs)] pub struct Boolean(pub bool); diff --git a/git-config/src/values/color.rs b/git-config/src/values/color.rs index 3a37d72d23a..49a63f11c5b 100644 --- a/git-config/src/values/color.rs +++ b/git-config/src/values/color.rs @@ -97,7 +97,7 @@ impl TryFrom> for Color { } } -/// Discriminating enum for [`Color`] values. +/// Discriminating enum for names of [`Color`] values. /// /// `git-config` supports the eight standard colors, their bright variants, an /// ANSI color code, or a 24-bit hex value prefixed with an octothorpe/hash. diff --git a/git-config/src/values/integer.rs b/git-config/src/values/integer.rs index 1734075679e..0a3e729f9b0 100644 --- a/git-config/src/values/integer.rs +++ b/git-config/src/values/integer.rs @@ -63,8 +63,6 @@ impl TryFrom<&BStr> for Integer { return Ok(Self { value, suffix: None }); } - // Assume we have a prefix at this point. - if s.len() <= 1 { return Err(int_err(s)); } @@ -89,7 +87,7 @@ impl TryFrom> for Integer { } } -/// Integer prefixes that are supported by `git-config`. +/// Integer suffixes that are supported by `git-config`. /// /// These values are base-2 unit of measurements, not the base-10 variants. #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] diff --git a/git-config/src/values/mod.rs b/git-config/src/values/mod.rs index 3b65c2dd407..59908866c9b 100644 --- a/git-config/src/values/mod.rs +++ b/git-config/src/values/mod.rs @@ -1,5 +1,4 @@ -/// -pub mod boolean; +mod boolean; /// pub mod color; /// diff --git a/git-config/tests/file/init/comfort.rs b/git-config/tests/file/init/comfort.rs index 0eee42ea067..629bbec7cd6 100644 --- a/git-config/tests/file/init/comfort.rs +++ b/git-config/tests/file/init/comfort.rs @@ -3,8 +3,8 @@ use git_testtools::Env; use serial_test::serial; #[test] -fn new_globals() { - let config = git_config::File::new_globals().unwrap(); +fn from_globals() { + let config = git_config::File::from_globals().unwrap(); assert!(config.sections().all(|section| { let kind = section.meta().source.kind(); kind != source::Kind::Repository && kind != source::Kind::Override @@ -13,8 +13,8 @@ fn new_globals() { #[test] #[serial] -fn new_environment_overrides() { - let config = git_config::File::new_environment_overrides().unwrap(); +fn from_environment_overrides() { + let config = git_config::File::from_environment_overrides().unwrap(); assert!(config.is_void()); } From b0e4da621114d188a73b9f40757f59564da3c079 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 22 Jul 2022 16:13:05 +0800 Subject: [PATCH 353/366] Make lossy-configuration configurable (#331) That way, applications which want to display or work with configuration files can do so. --- git-repository/src/config/cache.rs | 13 ++++++++----- git-repository/src/open.rs | 17 +++++++++++++++-- gitoxide-core/src/repository/config.rs | 1 + 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/git-repository/src/config/cache.rs b/git-repository/src/config/cache.rs index a8b9c490e3c..71e41914084 100644 --- a/git-repository/src/config/cache.rs +++ b/git-repository/src/config/cache.rs @@ -15,13 +15,14 @@ pub(crate) struct StageOne { buf: Vec, is_bare: bool, + lossy: Option, pub object_hash: git_hash::Kind, pub reflog: Option, } /// Initialization impl StageOne { - pub fn new(git_dir: &std::path::Path, git_dir_trust: git_sec::Trust) -> Result { + pub fn new(git_dir: &std::path::Path, git_dir_trust: git_sec::Trust, lossy: Option) -> Result { let mut buf = Vec::with_capacity(512); let config = { let config_path = git_dir.join("config"); @@ -34,7 +35,7 @@ impl StageOne { .with(git_dir_trust), git_config::file::init::Options { includes: git_config::file::includes::Options::no_follow(), - ..base_options() + ..base_options(lossy) }, )? }; @@ -64,6 +65,7 @@ impl StageOne { git_dir_config: config, buf, is_bare, + lossy, object_hash, reflog, }) @@ -77,6 +79,7 @@ impl Cache { StageOne { git_dir_config, mut buf, + lossy, is_bare, object_hash, reflog: _, @@ -111,7 +114,7 @@ impl Cache { } else { git_config::file::includes::Options::no_follow() }, - ..base_options() + ..base_options(lossy) }; let config = { @@ -274,9 +277,9 @@ pub(crate) fn interpolate_context<'a>( } } -fn base_options() -> git_config::file::init::Options<'static> { +fn base_options(lossy: Option) -> git_config::file::init::Options<'static> { git_config::file::init::Options { - lossy: !cfg!(debug_assertions), + lossy: lossy.unwrap_or(!cfg!(debug_assertions)), ..Default::default() } } diff --git a/git-repository/src/open.rs b/git-repository/src/open.rs index a93ed2b2af8..d492fdb4b49 100644 --- a/git-repository/src/open.rs +++ b/git-repository/src/open.rs @@ -69,6 +69,7 @@ pub struct Options { pub(crate) permissions: Permissions, pub(crate) git_dir_trust: Option, pub(crate) filter_config_section: Option bool>, + pub(crate) lossy_config: Option, } #[derive(Default, Clone)] @@ -170,6 +171,15 @@ impl Options { self } + /// By default, in release mode configuration will be read without retaining non-essential information like + /// comments or whitespace to optimize lookup performance. + /// + /// Some application might want to toggle this to false in they want to display or edit configuration losslessly. + pub fn lossy_config(mut self, toggle: bool) -> Self { + self.lossy_config = toggle.into(); + self + } + /// Open a repository at `path` with the options set so far. pub fn open(self, path: impl Into) -> Result { ThreadSafeRepository::open_opts(path, self) @@ -184,7 +194,8 @@ impl git_sec::trust::DefaultForLevel for Options { replacement_objects: Default::default(), permissions: Permissions::all(), git_dir_trust: git_sec::Trust::Full.into(), - filter_config_section: Some(crate::config::section::is_trusted), + filter_config_section: Some(config::section::is_trusted), + lossy_config: None, }, git_sec::Trust::Reduced => Options { object_store_slots: git_odb::store::init::Slots::Given(32), // limit resource usage @@ -192,6 +203,7 @@ impl git_sec::trust::DefaultForLevel for Options { permissions: Default::default(), git_dir_trust: git_sec::Trust::Reduced.into(), filter_config_section: Some(crate::config::section::is_trusted), + lossy_config: None, }, } } @@ -283,6 +295,7 @@ impl ThreadSafeRepository { object_store_slots, filter_config_section, ref replacement_objects, + lossy_config, permissions: Permissions { git_dir: ref git_dir_perm, @@ -301,7 +314,7 @@ impl ThreadSafeRepository { .map(|cd| git_dir.join(cd)); let common_dir_ref = common_dir.as_deref().unwrap_or(&git_dir); - let repo_config = crate::config::cache::StageOne::new(common_dir_ref, git_dir_trust)?; + let repo_config = crate::config::cache::StageOne::new(common_dir_ref, git_dir_trust, lossy_config)?; let mut refs = { let reflog = repo_config.reflog.unwrap_or(git_ref::store::WriteReflog::Disable); let object_hash = repo_config.object_hash; diff --git a/gitoxide-core/src/repository/config.rs b/gitoxide-core/src/repository/config.rs index 48633dfd77d..9797f59d805 100644 --- a/gitoxide-core/src/repository/config.rs +++ b/gitoxide-core/src/repository/config.rs @@ -11,6 +11,7 @@ pub fn list( if format != OutputFormat::Human { bail!("Only human output format is supported at the moment"); } + let repo = git::open_opts(repo.git_dir(), repo.open_options().clone().lossy_config(false))?; let config = repo.config_snapshot(); let config = config.plumbing(); if let Some(frontmatter) = config.frontmatter() { From 4c69541cd7192ebd5bdd696a833992d5a52cd9b6 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 22 Jul 2022 16:24:35 +0800 Subject: [PATCH 354/366] Group similarly named sections together more by not separating them with newline (#331) --- gitoxide-core/src/repository/config.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/gitoxide-core/src/repository/config.rs b/gitoxide-core/src/repository/config.rs index 9797f59d805..74e8e8bd33b 100644 --- a/gitoxide-core/src/repository/config.rs +++ b/gitoxide-core/src/repository/config.rs @@ -21,7 +21,8 @@ pub fn list( } let filters: Vec<_> = filters.into_iter().map(Filter::new).collect(); let mut last_meta = None; - for (section, matter) in config.sections_and_postmatter() { + let mut it = config.sections_and_postmatter().peekable(); + while let Some((section, matter)) = it.next() { if !filters.is_empty() && !filters.iter().any(|filter| filter.matches_section(section)) { continue; } @@ -36,7 +37,11 @@ pub fn list( for event in matter { event.write_to(&mut out)?; } - writeln!(&mut out)?; + if it.peek().map_or(false, |(next_section, _)| { + next_section.header().name() != section.header().name() + }) { + writeln!(&mut out)?; + } } Ok(()) } From d552fb319bb7739ea5aa960084032c285700ad25 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 22 Jul 2022 16:43:52 +0800 Subject: [PATCH 355/366] add strangely appearing journey test files --- ...s-dir-skip-checks-success-tree-miniz-oxide | 54 +++++++++++++++++++ ...ack receive-no-networking-in-small-failure | 6 +++ ...te ref-list-no-networking-in-small-failure | 6 +++ 3 files changed, 66 insertions(+) create mode 100644 tests/snapshots/plumbing/no-repo/pack/explode/broken-with-objects-dir-skip-checks-success-tree-miniz-oxide create mode 100644 tests/snapshots/plumbing/no-repo/pack/receive/pack receive-no-networking-in-small-failure create mode 100644 tests/snapshots/plumbing/no-repo/remote/ref-list/remote ref-list-no-networking-in-small-failure diff --git a/tests/snapshots/plumbing/no-repo/pack/explode/broken-with-objects-dir-skip-checks-success-tree-miniz-oxide b/tests/snapshots/plumbing/no-repo/pack/explode/broken-with-objects-dir-skip-checks-success-tree-miniz-oxide new file mode 100644 index 00000000000..44c9b820241 --- /dev/null +++ b/tests/snapshots/plumbing/no-repo/pack/explode/broken-with-objects-dir-skip-checks-success-tree-miniz-oxide @@ -0,0 +1,54 @@ +. +├── 0e +│   └── ad45fc727edcf5cadca25ef922284f32bb6fc1 +├── 15 +│   └── 926d8d6d17d1cbdf7f03c457e8ff983270f363 +├── 18 +│   └── bd3fc20b0565f94bce0a3e94b6a83b26b88627 +├── 1d +│   └── fd336d2290794b0b1f80d98af33f725da6f42d +├── 2b +│   └── 621c1a3aac23b8258885a9b4658d9ac993742f +├── 2c +│   └── 1e59ee54facb7d72c0061d06b9fe3889f357a9 +├── 2d +│   └── ad8b277db3a95919bd904133d7e7cc3e323cb9 +├── 3a +│   └── b660ad62dd7c8c8bd637aa9bc1c2843a8439fe +├── 3d +│   └── 650a1c41a4529863818fd613b95e83668bbfc1 +├── 41 +│   └── 97ce3c6d943759e1088a0298b64571b4bc725a +├── 50 +│   └── 1b297447a8255d3533c6858bb692575cdefaa0 +├── 5d +│   └── e2eda652f29103c0d160f8c05d7e83b653a157 +├── 66 +│   └── 74d310d179400358d581f9725cbd4a2c32e3bf +├── 68 +│   └── b95733c796b12571fb1f656062a15a78e7dcf4 +├── 83 +│   └── d9602eccfc733a550812ce492d4caa0af625c8 +├── 84 +│   ├── 26f672fc65239135b1f1580bb79ecb16fd05f0 +│   └── 81dbefa2fb9398a673fe1f48dc480c1f558890 +├── 85 +│   └── 48234cfc7b4f0c9475d24d4c386783533a8034 +├── 88 +│   └── 58983d81b0eef76eb55d21a0d96b7b16846eca +├── af +│   └── 4f6405296dec699321ca59d48583ffa0323b0e +├── b2 +│   └── 025146d0718d953036352f8435cfa392b1d799 +├── bb +│   └── a287531b3a845faa032a8fef3e6d70d185c89b +├── bd +│   └── 91890c62d85ec16aadd3fb991b3ad7a365adde +├── cb +│   └── 572206d9dac4ba52878e7e1a4a7028d85707ab +├── e2 +│   └── 34c232ce0b8acef3f43fa34c036e68522b5612 +└── e8 + └── 00b9c207e17f9b11e321cc1fba5dfe08af4222 + +25 directories, 26 files \ No newline at end of file diff --git a/tests/snapshots/plumbing/no-repo/pack/receive/pack receive-no-networking-in-small-failure b/tests/snapshots/plumbing/no-repo/pack/receive/pack receive-no-networking-in-small-failure new file mode 100644 index 00000000000..3ca8355b32e --- /dev/null +++ b/tests/snapshots/plumbing/no-repo/pack/receive/pack receive-no-networking-in-small-failure @@ -0,0 +1,6 @@ +error: Found argument 'receive' which wasn't expected, or isn't valid in this context + +USAGE: + gix free pack + +For more information try --help \ No newline at end of file diff --git a/tests/snapshots/plumbing/no-repo/remote/ref-list/remote ref-list-no-networking-in-small-failure b/tests/snapshots/plumbing/no-repo/remote/ref-list/remote ref-list-no-networking-in-small-failure new file mode 100644 index 00000000000..885fe4f7406 --- /dev/null +++ b/tests/snapshots/plumbing/no-repo/remote/ref-list/remote ref-list-no-networking-in-small-failure @@ -0,0 +1,6 @@ +error: Found argument 'remote' which wasn't expected, or isn't valid in this context + +USAGE: + gix free + +For more information try --help \ No newline at end of file From 3c50625fa51350ec885b0f38ec9e92f9444df0f9 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 22 Jul 2022 16:49:07 +0800 Subject: [PATCH 356/366] prepare changelog prior to release --- CHANGELOG.md | 43 ++- git-actor/CHANGELOG.md | 35 ++- git-attributes/CHANGELOG.md | 27 +- git-commitgraph/CHANGELOG.md | 7 +- git-config/CHANGELOG.md | 589 ++++++++++++++++++++++++++++++++++- git-credentials/CHANGELOG.md | 27 +- git-date/CHANGELOG.md | 34 +- git-diff/CHANGELOG.md | 35 ++- git-discover/CHANGELOG.md | 33 +- git-features/CHANGELOG.md | 47 ++- git-glob/CHANGELOG.md | 73 +++-- git-hash/CHANGELOG.md | 26 +- git-index/CHANGELOG.md | 73 +++-- git-mailmap/CHANGELOG.md | 7 +- git-object/CHANGELOG.md | 74 +++-- git-odb/CHANGELOG.md | 31 +- git-pack/CHANGELOG.md | 27 +- git-path/CHANGELOG.md | 45 ++- git-protocol/CHANGELOG.md | 26 +- git-ref/CHANGELOG.md | 49 ++- git-repository/CHANGELOG.md | 149 ++++++++- git-revision/CHANGELOG.md | 39 ++- git-sec/CHANGELOG.md | 41 ++- git-tempfile/CHANGELOG.md | 33 +- git-transport/CHANGELOG.md | 34 +- git-traverse/CHANGELOG.md | 19 +- git-url/CHANGELOG.md | 32 +- git-worktree/CHANGELOG.md | 27 +- gitoxide-core/CHANGELOG.md | 60 +++- 29 files changed, 1623 insertions(+), 119 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07256bec33d..89c08ecce64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,20 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### New Features + + - `gix config` with section and sub-section filtering. + - `gix config` lists all entries of all configuration files git considers. + Filters allow to narrow down the output. + ### Commit Statistics - - 12 commits contributed to the release over the course of 61 calendar days. - - 67 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - - 1 unique issue was worked on: [#301](https://github.com/Byron/gitoxide/issues/301) + - 37 commits contributed to the release over the course of 101 calendar days. + - 107 days passed between releases. + - 2 commits where understood as [conventional](https://www.conventionalcommits.org). + - 3 unique issues were worked on: [#301](https://github.com/Byron/gitoxide/issues/301), [#331](https://github.com/Byron/gitoxide/issues/331), [#427](https://github.com/Byron/gitoxide/issues/427) ### Thanks Clippy -[Clippy](https://github.com/rust-lang/rust-clippy) helped 2 times to make code idiomatic. +[Clippy](https://github.com/rust-lang/rust-clippy) helped 3 times to make code idiomatic. ### Commit Details @@ -37,7 +43,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - refactor ([`3ff991d`](https://github.com/Byron/gitoxide/commit/3ff991d0ca0d63632fc5710680351840f51c14c3)) - frame for `gix repo exclude query` ([`a331314`](https://github.com/Byron/gitoxide/commit/a331314758629a93ba036245a5dd03cf4109dc52)) - make fmt ([`50ff7aa`](https://github.com/Byron/gitoxide/commit/50ff7aa7fa86e5e2a94fb15aab86470532ac3f51)) + * **[#331](https://github.com/Byron/gitoxide/issues/331)** + - fix journey tests after `gix` restructuring ([`59b95c9`](https://github.com/Byron/gitoxide/commit/59b95c94aacac174e374048b7d11d2c0984a19e0)) + - `gix config` with section and sub-section filtering. ([`eda39ec`](https://github.com/Byron/gitoxide/commit/eda39ec7d736d49af1ad9e2ad775e4aa12b264b7)) + - `gix config` lists all entries of all configuration files git considers. ([`d99453e`](https://github.com/Byron/gitoxide/commit/d99453ebeb970ed493be236def299d1e82b01f83)) + - refactor ([`a437abe`](https://github.com/Byron/gitoxide/commit/a437abe8e77ad07bf25a16f19ca046ebdaef42d6)) + - move 'exclude' up one level and dissolve 'repo' subcommand ([`8e5b796`](https://github.com/Byron/gitoxide/commit/8e5b796ea3fd760839f3c29a4f65bb42b1f3e893)) + - move 'mailmap' up one level ([`5cf08ce`](https://github.com/Byron/gitoxide/commit/5cf08ce3d04d635bbfee169cb77ce259efbf6bc3)) + - move 'odb' up one level ([`0ed65da`](https://github.com/Byron/gitoxide/commit/0ed65da9b66d4cc3c85d3b70fa4bc383c7a0d1a3)) + - move 'tree' up one level ([`38a8350`](https://github.com/Byron/gitoxide/commit/38a8350d75720a8455e9c55d12f7cdf4b1742e56)) + - move 'commit' up one level ([`72876f1`](https://github.com/Byron/gitoxide/commit/72876f1fd65efc816b704db6880ab881c89cff01)) + - move 'verify' up one level ([`ac7d99a`](https://github.com/Byron/gitoxide/commit/ac7d99ac42ff8561e81f476856d0bbe86b5fa627)) + - move 'revision' one level up ([`c9c78e8`](https://github.com/Byron/gitoxide/commit/c9c78e86c387c09838404c90de420892f41f4356)) + - move 'remote' to 'free' ([`8967fcd`](https://github.com/Byron/gitoxide/commit/8967fcd009260c2d32881866244ba673894775f2)) + - move commitgraph to 'free' ([`f99c3b2`](https://github.com/Byron/gitoxide/commit/f99c3b29cea30f1cbbea7e5855abfec3de6ca630)) + - move index to 'free' ([`83585bd`](https://github.com/Byron/gitoxide/commit/83585bdfccdc42b5307255b2d56d8cb12d4136cb)) + - move 'pack' to 'free' ([`1cdecbc`](https://github.com/Byron/gitoxide/commit/1cdecbc583ae412e7f25cade73b46e00a182125f)) + - migrate mailmap to the new 'free' section ([`141c5f1`](https://github.com/Byron/gitoxide/commit/141c5f1145f9d3864e2d879089c66c62f38a2b5d)) + - first step towards moving all repository-commands one level up. ([`f4e1810`](https://github.com/Byron/gitoxide/commit/f4e1810fb711d57778be79c88f49aa583821abab)) + - make obvious what plumbing and porcelain really are ([`faaf791`](https://github.com/Byron/gitoxide/commit/faaf791cc960c37b180ddef9792dfabc7d106138)) + - adjustments due to breaking changes in `git_path` ([`4420ae9`](https://github.com/Byron/gitoxide/commit/4420ae932d5b20a9662a6d36353a27111b5cd672)) + * **[#427](https://github.com/Byron/gitoxide/issues/427)** + - basic infrastructure for delegate implementation ([`d3c0bc6`](https://github.com/Byron/gitoxide/commit/d3c0bc6e8d7764728f4e10500bb895152ccd0b0b)) + - Hookup explain command ([`1049b00`](https://github.com/Byron/gitoxide/commit/1049b00eaa261a67f060eaca4eb50dcda831eafd)) + - frame for `gix repo rev explain` ([`12e6277`](https://github.com/Byron/gitoxide/commit/12e6277a65a6572a0e43e8324d2d1dfb23d0bb40)) * **Uncategorized** + - thanks clippy ([`48b3f4a`](https://github.com/Byron/gitoxide/commit/48b3f4a5077ba66d47482a80e505feb69e9ac9fc)) + - make fmt ([`0700b09`](https://github.com/Byron/gitoxide/commit/0700b09d6828849fa2470df89af1f75a67bfb27d)) + - Use git_path::realpath in all places that allow it right now ([`229dc91`](https://github.com/Byron/gitoxide/commit/229dc917fc7d9241b85e5818260a6fbdd3a5daaa)) - make fmt ([`251b6df`](https://github.com/Byron/gitoxide/commit/251b6df5dbdda24b7bdc452085f808f3acef69d8)) - Merge branch 'git_includeif' of https://github.com/svetli-n/gitoxide into svetli-n-git_includeif ([`0e01da7`](https://github.com/Byron/gitoxide/commit/0e01da74dffedaa46190db6a7b60a2aaff190d81)) - thanks clippy ([`056e8d2`](https://github.com/Byron/gitoxide/commit/056e8d26dc511fe7939ec87c62ef16aafd34fa9c)) diff --git a/git-actor/CHANGELOG.md b/git-actor/CHANGELOG.md index 04810103cb3..afc26412f15 100644 --- a/git-actor/CHANGELOG.md +++ b/git-actor/CHANGELOG.md @@ -5,6 +5,38 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Changed (BREAKING) + + - remove local-time-support feature toggle. + We treat local time as default feature without a lot of fuzz, and + will eventually document that definitive support needs a compile + time switch in the compiler (`--cfg unsound_local_offset` or something). + + One day it will perish. Failure is possible anyway and we will write + code to deal with it while minimizing the amount of system time + fetches when asking for the current local time. + +### Commit Statistics + + + + - 1 commit contributed to the release. + - 39 days passed between releases. + - 1 commit where understood as [conventional](https://www.conventionalcommits.org). + - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) + +### Commit Details + + + +
view details + + * **[#331](https://github.com/Byron/gitoxide/issues/331)** + - remove local-time-support feature toggle. ([`89a41bf`](https://github.com/Byron/gitoxide/commit/89a41bf2b37db29b9983b4e5492cfd67ed490b23)) +
+ ## 0.10.1 (2022-06-13) A maintenance release without user-facing changes. @@ -13,7 +45,7 @@ A maintenance release without user-facing changes. - - 3 commits contributed to the release over the course of 5 calendar days. + - 4 commits contributed to the release over the course of 5 calendar days. - 25 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#427](https://github.com/Byron/gitoxide/issues/427) @@ -27,6 +59,7 @@ A maintenance release without user-facing changes. * **[#427](https://github.com/Byron/gitoxide/issues/427)** - Replace `Time` with `git-date::Time`. ([`59b3ff8`](https://github.com/Byron/gitoxide/commit/59b3ff8a7e028962917cf3b2930b5b7e5156c302)) * **Uncategorized** + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980)) - make fmt ([`c665aef`](https://github.com/Byron/gitoxide/commit/c665aef4270c5ee54da89ee015cc0affd6337608)) diff --git a/git-attributes/CHANGELOG.md b/git-attributes/CHANGELOG.md index 64cdb52faf9..1463123d819 100644 --- a/git-attributes/CHANGELOG.md +++ b/git-attributes/CHANGELOG.md @@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +This is a maintenance release with no functional changes. + +### Commit Statistics + + + + - 2 commits contributed to the release over the course of 33 calendar days. + - 39 days passed between releases. + - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) + - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) +
+ ## 0.2.0 (2022-06-13) A maintenance release without user-facing changes. @@ -13,7 +37,7 @@ A maintenance release without user-facing changes. - - 2 commits contributed to the release over the course of 16 calendar days. + - 3 commits contributed to the release over the course of 16 calendar days. - 25 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +49,7 @@ A maintenance release without user-facing changes.
view details * **Uncategorized** + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980)) - branch start, upgrade to compact_str v0.4 ([`b2f56d5`](https://github.com/Byron/gitoxide/commit/b2f56d5a279dae745d9c2c80ebe599c00e72c0d7))
diff --git a/git-commitgraph/CHANGELOG.md b/git-commitgraph/CHANGELOG.md index f707d9c999b..5b5e2797188 100644 --- a/git-commitgraph/CHANGELOG.md +++ b/git-commitgraph/CHANGELOG.md @@ -13,8 +13,8 @@ A maintenance release without user-facing changes. - - 4 commits contributed to the release over the course of 59 calendar days. - - 70 days passed between releases. + - 7 commits contributed to the release over the course of 99 calendar days. + - 110 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#384](https://github.com/Byron/gitoxide/issues/384) @@ -29,6 +29,9 @@ A maintenance release without user-facing changes. - add archive files via git-lfs ([`7202a1c`](https://github.com/Byron/gitoxide/commit/7202a1c4734ad904c026ee3e4e2143c0461d51a2)) - auto-set commit.gpgsign=false when executing git ([`c23feb6`](https://github.com/Byron/gitoxide/commit/c23feb64ad157180cfba8a11c882b829733ea8f6)) * **Uncategorized** + - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) + - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980)) - Release git-hash v0.9.4, git-features v0.21.0, git-actor v0.10.0, git-glob v0.3.0, git-path v0.1.1, git-attributes v0.1.0, git-sec v0.1.0, git-config v0.3.0, git-credentials v0.1.0, git-validate v0.5.4, git-object v0.19.0, git-diff v0.16.0, git-lock v2.1.0, git-ref v0.13.0, git-discover v0.1.0, git-index v0.3.0, git-mailmap v0.2.0, git-traverse v0.15.0, git-pack v0.19.0, git-odb v0.29.0, git-packetline v0.12.5, git-url v0.5.0, git-transport v0.17.0, git-protocol v0.16.0, git-revision v0.2.0, git-worktree v0.2.0, git-repository v0.17.0, safety bump 20 crates ([`654cf39`](https://github.com/Byron/gitoxide/commit/654cf39c92d5aa4c8d542a6cadf13d4acef6a78e)) diff --git a/git-config/CHANGELOG.md b/git-config/CHANGELOG.md index 1bce4477ff0..bb852217ae3 100644 --- a/git-config/CHANGELOG.md +++ b/git-config/CHANGELOG.md @@ -5,6 +5,559 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### New Features + + - following includes is now non-fatal by default + Otherwise it would be relatively easy to fail gitoxide startup, + and we want to be closer to the behaviour in git which ignores + most of the errors. + - `File::from_git_dir()` as comfortable way to instantiate most complete git configuration. + - `File` now compares actual content, ignoring whitespace and comments. + - `File::new_environment_overrides()` to easily instantiate overrides from the environment. + - `File::new_globals()` can instantiate non-local configuration with zero-configuration. + - `Source::storage_location()` to know where files should be located. + - `file::ValueMut::(section|into_section_mut)()` to go from value to the owning section. + This can be useful if the value was obtained using `raw_value_mut()`. + - `Source::is_in_repository()` to find out if a source is in the repository. + - `parse::key` to parse a `remote.origin.url`-like key to identify a value + - Add `File::detect_newline_style()`, which does at it says. + - `File::frontmatter()` and `File::sections_and_postmatter()`. + - `parse::Event::to_bstr_lossy()` to get a glimpse at event content. + - `File::append()` can append one file to another rather losslessly. + The loss happens as we, maybe for the wrong reasons, automatically + insert newlines where needed which can only be done while we still know + the file boundaries. + - `file::Section::meta()` to access a section's metadata. + - `File::sections()` to obtain an iterator over all sections, in order. + - place spaces around `key = value` pairs, or whatever is used in the source configuration. + - proper escaping of value bytes to allow round-tripping after mutation + - whitespace in newly pushed keys is derived from first section value. + That way, newly added key-value pairs look like they should assuming + all keys have the same indentation as the first key in the section. + + If there is no key, then the default whitespace will be double-tabs + like what's commmon in git. + - `File::from_str()` implementation, to support `let config: File = "[core]".parse()?` + - whitespace in mutable sections can be finely controlled, and is derived from existing sections + - `parse::Header::new(…)` with sub-section name validation + - Add `parse::(Event|section::Header|Comment)::write_to(…)`. + Now it's possible to serialize these types in a streaming fashion and + without arbitrarily enforcing UTF-8 on it + - `serde1` feature to add limited serde support + +### Bug Fixes + + - maintain insertion order of includes on per-section basis at least. + Note that git inserts values right after the include directive, + 'splitting' the section, but we don't do that and insert new values + after the section. Probably no issue in practice while keeping + our implementation simple. + - maintain newline format depending on what's present or use platform default. + Previously implicit newlines when adding new sections or keys to + sections was always `\n` which isn't correct on windows. + + Now the newline style is detected and used according to what's present, + or in the lack of content, defaults to what's correct for the platform. + - validate incoming conifguration keys when interpreting envirnoment variables. + - `Boolean` can use numbers to indicate true or false, drops support for `one` and `zero`. + - `file::MutableSection::remove()` now actually removes keys _and_ values. + - `file::MutableMultiValue` escapes input values and maintains key separator specific whitespace. + - value normalization (via `value::normalize()` handles escape sequences. + The latter ones are `\n`, `\t` and `\b` which are the only supported + ones in values of git-config files. + - stable sort order for `File::sections_by_name_with_header()` + - count newlines (for error display) in multi-line values as well + - auto-normalize string values to support quote removal in case of strings. + Related to https://github.com/starship/starship/pull/3883 . + +### Other + + - :Events::from_bytes()` with `filter` support. + +### Changed (BREAKING) + + - add `File::resolve_includes()` and move its error type to `file::includes`. + - add `File::from_bytes_owned()` and remove `File::from_path_with_buf()` + - remove `File::from_env_paths()`. + It's replaced by its more comfortable `new_globals()`. + - untangle `file::init::…` `Option` and `Error` types. + This moves types to where they belong which is more specific instead + of having a catch-all `Error` and `Options` type. + - rename `parse::Comment::(comment_tag|comment)` to `::tag|text` and `parse::Section::section_header` to `::header`. + - Associate `file::Metadata` with each `File`. + This is the first step towards knowing more about the source of each + value to filter them based on some properties. + + This breaks various methods handling the instantiation of configuration + files as `file::Metadata` typically has to be provided by the caller + now or be associated with each path to read configuration from. + - rename `file::SectionBody` to `file::section::Body`. + - Remove `File::sections_by_name_with_header()` as `::sections_by_name()` now returns entire sections. + - create `resolve_includes` options to make space for more options when loading paths. + - rename `path::Options` into `path::Context`. + It's not an option if it's required context to perform a certain + operation. + - All accessors in `File` are now using `impl AsRef` where possible for added comfort. + - Much more comfortable API `file::*Mut` types thanks to `impl Into/AsRef`. + - Rename `Mutable*` into `$1Mut` for consistency. + - conform APIs of `file::MutableValue` and `file::MutableMultiValue`. + There are more renames and removals than worth mentioning here given the + current adoption of the crate. + - rename `file::MutableSection::set_leading_space()` to `set_leading_whitespace()`. + The corresponding getter was renamed as well to `leading_whitespace()`. + - Enforce `parse::section::Header::new()` by making its fields private. + - Add `File::write_to()` and `File::to_bstring()`; remove some `TryFrom` impls. + Now `File` can be serialized in a streaming fashion and without the + possibility for UTF8 conversion issues. + + Note that `Display` is still imlpemented with the usual caveats. + - remove `Integer::to_bstring()` as well as some `TryFrom` impls. + Note that it can still display itself like before via + `std::fmt::Display`. + - remove `Boolean::to_bstring()` along with a few `From` impls. + These were superfluous and aren't useful in practice. + Note that serialization is still implemented via `Display`. + - allocation free `File::sections_by_name()` and `File::sections_by_name_with_header()`. + - `Path::interpolate()` now takes `path::interpolate::Options` instead of three parameters. + - remove `String` type in favor of referring to the `File::string()` method. + The wrapper had no effect whatsoever except for adding complexity. + - Simplify `Boolean` to be a wrapper around `bool`. + Previously it tried hard not to degenerate information, making it a + complicated type. + + However, in practice nobody cares about the exact makeup of the boolean, + and there is no need to serialize a boolean faithfully either. + + Instead, those who want to set a value just set any value as a string, + no need for type safety there, and we take care of escaping values + properly on write. + - Use bitflags for `color::Attribute` instead of `Vec` of enums. + This is less wasteful and sufficient for git, so it should be sufficient + for us, especially since attributes are indeed a set and declaring + one twice has no effect. + - simplify `Color` API. + For now we only parse and serialize for display, but more uses are + enabled when needed and trivially. + - remove `parse::Events::from_path` and `File::at` + The latter has been replaced with `File::from_path_with_buf(…)` and + is a low-level way to load just a single config file, purposefully + uncomfortable as it will not resolve includes. + + The initialization API will need some time to stabilize. + - Slim down API surface of `parse::Events`. + It's more of a 'dumb' structure now than before, merely present + to facilitate typical parsing than something special on its own. + - remove `File::new()` method in favor of `File::default()`. + + - rename `parse::event::List` to `parse::Events` + - rename `parse::State` to `parse::event::List` + - move `value::*` into the crate root, except for `Error` and `normalize_*()`. + - rename `value::parse::Error` to `value::Error`. + - rename `value::TrueVariant` to `value::boolean::True` + - rename `IntegerSuffix` to `integer::Suffix` + - rename `value::Color(Attribute|Value)` to `value::color::Attribute` and `value::color::Name`. + - Turn `parse::ParseOrIoError` into `parse::state::from_path::Error` + - rename `parse::ParsedComment` into `parse::Comment` + - rename `parse::Section*` related types. + These are now located in `section::*`. + - rename `parse::Parser` to `parse::State`. + Furthermore, make `State` the entry point for all parsing, removing + all free-standing functions that returned a `State`. + - rename `parser` module to `parse` + - rename `normalize_cow()` to `normalize()` and move all `normalize*` functions from `values` to the `value` module + - move `Path` from `values` to `value` module + - Move `Boolean` and `String` from `values` into `value` module + - move `values::Integer` into `value` module + - move `Color` to own `value` module + - remove `values::Bytes` - use `values::String` instead. + Note that these values are always normalized and it's only possible + to get a raw values using the `raw_value()` API. + +### New Features (BREAKING) + + - Support for `lossy` load mode. + There is a lot of breaking changes as `file::from_paths::Options` now + became `file::init::Options`, and the same goes for the error type. + - add `_filter()` versions to most access methods. + That way it's possible to filter values by their origin. + + Note that the `remove_section()` methods now return the entire + removed section, not just the body, which yields more information + than before including section metadata. + - section names are now validated. + - filtering supportort for `parse::Events`. + That way it's possible to construct Files which are not destined to be + written back as they only keep events necessary for value access, + greatly reducing allocations. + - change mostily internal uses of [u8] to BString/BStr + - Path-interpolation makes `home-dir` configurable. + That way the caller has full control over how the environment is used, + which also allows more fine-grained control over which config files + can be included. + +### Bug Fixes (BREAKING) + + - Simplify specifying keys when mutating config values. + - `File::rename_section()` with validation of input arguments. + - improve normalization; assure no extra copies are made on query. + We now return our own content, rather than the originals with their + lifetimes, meaning we bind lifetimes of returned values to our own + `File` instance. This allows them to be referenced more often, and + smarter normalization assures we don't copy in the simple cases + either. + + More tests were added as well. + This is breaking as lifetime changes can cause distruptions, and + `values?_as()` was removed as well as it's somewhat duplicate + to higher-level APIs and it wasn't tested at all. + - Remove `git-config` test utilities from `git-path`. + +### Other (BREAKING) + + - `File::raw_multi_value()` to `File::raw_values()` + - `File::raw_multi_value_mut()` to `File::raw_values_mut()` + - `File::multi_value()` to `File::values()`. + The latter is better in line with `string()/strings()` + +### Commit Statistics + + + + - 312 commits contributed to the release over the course of 33 calendar days. + - 39 days passed between releases. + - 93 commits where understood as [conventional](https://www.conventionalcommits.org). + - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) + +### Thanks Clippy + + + +[Clippy](https://github.com/rust-lang/rust-clippy) helped 19 times to make code idiomatic. + +### Commit Details + + + +
view details + + * **[#331](https://github.com/Byron/gitoxide/issues/331)** + - final documentation review + adjustments prior to release candidate ([`06b86e0`](https://github.com/Byron/gitoxide/commit/06b86e05dd9a712d26456b43c8da0a11870f08df)) + - refactor ([`4dc6594`](https://github.com/Byron/gitoxide/commit/4dc6594686478d9d6cd09e2ba02048624c3577e7)) + - exclude particular assertion which fails on the linux CI. ([`5e0f889`](https://github.com/Byron/gitoxide/commit/5e0f889c1edb862d698a2d344a61f12ab3b6ade7)) + - first sketch of using configuration and environment variables for author/committer ([`330d0a1`](https://github.com/Byron/gitoxide/commit/330d0a19d54aabac868b76ef6281fffdbdcde53c)) + - remove `Permissions` as there is no need for that here. ([`1954ef0`](https://github.com/Byron/gitoxide/commit/1954ef096a58aedb9f568a01e439d5a5cb46c40d)) + - following includes is now non-fatal by default ([`1bc96bf`](https://github.com/Byron/gitoxide/commit/1bc96bf378d198b012efce9ec9e5b244a91f62bc)) + - Allow to skip non-existing input paths without error ([`989603e`](https://github.com/Byron/gitoxide/commit/989603efcdf0064e2bb7d48100391cabc810204d)) + - `File::from_git_dir()` as comfortable way to instantiate most complete git configuration. ([`f9ce1b5`](https://github.com/Byron/gitoxide/commit/f9ce1b5411f1ac788f71060ecf785dda9dfd87bf)) + - Add a way to load multiple configuration files without allocating a read buffer ([`acb4520`](https://github.com/Byron/gitoxide/commit/acb4520a88ab083640c80a7f23a56a2ca3cda335)) + - refactor ([`ec21e95`](https://github.com/Byron/gitoxide/commit/ec21e95f4d9ffac771410947923f27187e88321a)) + - move `Env` test utility into `git-testtools` ([`bd3f4d0`](https://github.com/Byron/gitoxide/commit/bd3f4d014dd7df7a1e25defa8eea7253eec1560a)) + - refactor ([`b073e29`](https://github.com/Byron/gitoxide/commit/b073e2930bed60ccedadd1709cfaa8889e02ffe3)) + - another failing tests that can't be fixed without a refactor ([`e4d8fd7`](https://github.com/Byron/gitoxide/commit/e4d8fd72f1f648a29e56e487827f2328bfc08d03)) + - an attempt to hack newline handling into place for windows newlines ([`dac1463`](https://github.com/Byron/gitoxide/commit/dac146343a0fbe96b6c0990f4fd4e976e0359a7e)) + - Serialize lossily-read configuration files correctly anyway. ([`cfda0c3`](https://github.com/Byron/gitoxide/commit/cfda0c335d759cae0b23cef51f7b85a5f4b11e82)) + - multi-path include test ([`3d89a46`](https://github.com/Byron/gitoxide/commit/3d89a46bf88b1fb5b4aa5da9fd12c7e310be3f9d)) + - refactor ([`8a7fb15`](https://github.com/Byron/gitoxide/commit/8a7fb15f78ce16d5caedd7656e8aa98e72f248a6)) + - fix windows tests ([`fbcf40e`](https://github.com/Byron/gitoxide/commit/fbcf40e16b8fc1ff97dbed2bc22b64bd44a8b99d)) + - finally proper whitespace handling in all the right places for perfect roundtripping to/from string ([`97e5ede`](https://github.com/Byron/gitoxide/commit/97e5ededb0390c1b4f296a35903433de9c519821)) + - serializations maintains some invariants about whitespace where possible. ([`ee10dd5`](https://github.com/Byron/gitoxide/commit/ee10dd5a8ae0dabfee21c1ce146e92c3c9635e8a)) + - refactor ([`9c248ee`](https://github.com/Byron/gitoxide/commit/9c248eeb015495f910f48ce5df3c8fcce905dba7)) + - `File` now compares actual content, ignoring whitespace and comments. ([`14a68a6`](https://github.com/Byron/gitoxide/commit/14a68a6a78a09f8ae56e30e3b7501de66ef31fdc)) + - maintain insertion order of includes on per-section basis at least. ([`6c1588f`](https://github.com/Byron/gitoxide/commit/6c1588fd1a2fa80fd866787cbf4bcc6e5b51abe6)) + - allow insertion of sections while preserving order ([`f5580a3`](https://github.com/Byron/gitoxide/commit/f5580a3635289d96e662aab00e60d801c4e34e1c)) + - a test showing that include ordering isn't correct compared to the including config. ([`4e47df5`](https://github.com/Byron/gitoxide/commit/4e47df5332810f6e46ab682a68e870220ba3a6fb)) + - add `File::resolve_includes()` and move its error type to `file::includes`. ([`17c83d5`](https://github.com/Byron/gitoxide/commit/17c83d55f8942788aac5eb1bea22a48daa045bf4)) + - add `File::from_bytes_owned()` and remove `File::from_path_with_buf()` ([`5221676`](https://github.com/Byron/gitoxide/commit/5221676e28f2b6cc1a7ef1bdd5654b880965f38c)) + - make it necessary to deal with the possibility of no-input in `from_paths_metadata()` . ([`612645f`](https://github.com/Byron/gitoxide/commit/612645f74ffc49229ccd783361b4d455e2284ac0)) + - Don't fail on empty input on the comfort level ([`61ecaca`](https://github.com/Byron/gitoxide/commit/61ecaca43fb871eaff5cf94a8e7f9cc9413a5a77)) + - `File::new_environment_overrides()` to easily instantiate overrides from the environment. ([`7dadfd8`](https://github.com/Byron/gitoxide/commit/7dadfd82494d47e36d3f570988eaf3c6b628977f)) + - prepare for supporting comfortable version of environment overrides ([`45c964a`](https://github.com/Byron/gitoxide/commit/45c964a3f581dc7d3090bbbe26f188d553783fb3)) + - remove `File::from_env_paths()`. ([`98d45c2`](https://github.com/Byron/gitoxide/commit/98d45c2f59863fdee033b38e757cec09593f6892)) + - `File::new_globals()` can instantiate non-local configuration with zero-configuration. ([`146eeb0`](https://github.com/Byron/gitoxide/commit/146eeb064822839bc46fd37a247a1b9a84f64e40)) + - Classify `Source` in accordance for what git actually does. ([`97374e4`](https://github.com/Byron/gitoxide/commit/97374e4d867e82d7be04da2eaa6ef553e0d9a7ff)) + - `Source::storage_location()` to know where files should be located. ([`e701e05`](https://github.com/Byron/gitoxide/commit/e701e053fd05850973930be0cefe73e8f3604d40)) + - `file::ValueMut::(section|into_section_mut)()` to go from value to the owning section. ([`fff0884`](https://github.com/Byron/gitoxide/commit/fff088485dd5067976cc93d525903b39aafea76a)) + - `Source::is_in_repository()` to find out if a source is in the repository. ([`f5f2d9b`](https://github.com/Byron/gitoxide/commit/f5f2d9b3fef98d9100d713f9291510fa4aa27867)) + - `parse::key` to parse a `remote.origin.url`-like key to identify a value ([`91e718f`](https://github.com/Byron/gitoxide/commit/91e718f0e116052b64ca436d7c74cea79529e696)) + - maintain newline format depending on what's present or use platform default. ([`f7bd2ca`](https://github.com/Byron/gitoxide/commit/f7bd2caceb87a179288030e0771da2e4ed6bd1e4)) + - prepare for passing through newline ([`3c06f88`](https://github.com/Byron/gitoxide/commit/3c06f8889854860b731735a8ce2bf532366003ef)) + - Add `File::detect_newline_style()`, which does at it says. ([`26147a7`](https://github.com/Byron/gitoxide/commit/26147a7a61a695eda680808ee4aab44a890b2964)) + - fix docs ([`78e85d9`](https://github.com/Byron/gitoxide/commit/78e85d9786a541aa43ad7266e85dc1da5e71a412)) + - a test for lossy File parsing ([`5e8127b`](https://github.com/Byron/gitoxide/commit/5e8127b395bd564129b20a1db2d59d39307a2857)) + - 'lossy' is now inherited by includes processing ([`88c6b18`](https://github.com/Byron/gitoxide/commit/88c6b185b2e51858b140e4378a5b5730b5cb4075)) + - untangle `file::init::…` `Option` and `Error` types. ([`230a523`](https://github.com/Byron/gitoxide/commit/230a523593afcfb8720db965ff56265aaceea772)) + - Support for `lossy` load mode. ([`d003c0f`](https://github.com/Byron/gitoxide/commit/d003c0f139d61e3bd998a0283a9c7af25a60db02)) + - :Events::from_bytes()` with `filter` support. ([`32d5b3c`](https://github.com/Byron/gitoxide/commit/32d5b3c695d868ba93755123a25b276bfbe55e0a)) + - try to fix attributes, once more ([`a50a396`](https://github.com/Byron/gitoxide/commit/a50a3964dbf01982b5a2c9a8ccd469332b6f9ca1)) + - `File::frontmatter()` and `File::sections_and_postmatter()`. ([`0ad1c9a`](https://github.com/Byron/gitoxide/commit/0ad1c9a5280cc172432b5258e0f79898721bac68)) + - add `_filter()` versions to most access methods. ([`1ea26d8`](https://github.com/Byron/gitoxide/commit/1ea26d80f392114349d25ebf88a7b260ee822aa1)) + - even better handling of newlines ([`50c1753`](https://github.com/Byron/gitoxide/commit/50c1753c6389f29279d278fbab1afbd9ded34a76)) + - refactor ([`df94c67`](https://github.com/Byron/gitoxide/commit/df94c6737ba642fff40623f406df0764d5bd3c43)) + - rename `parse::Comment::(comment_tag|comment)` to `::tag|text` and `parse::Section::section_header` to `::header`. ([`3f3ff11`](https://github.com/Byron/gitoxide/commit/3f3ff11a6ebe9775ee5ae7fc0ec18a94b5b46d61)) + - `parse::Event::to_bstr_lossy()` to get a glimpse at event content. ([`fc7e311`](https://github.com/Byron/gitoxide/commit/fc7e311b423c5fffb8240d9d0f917ae7139a6133)) + - finally fix newline behaviour ([`c70e135`](https://github.com/Byron/gitoxide/commit/c70e135ecbbce8c696a6ab542ae20f5b5981dfdf)) + - Be smarter about which newline style to use by guessing it based onprior events ([`25ed92e`](https://github.com/Byron/gitoxide/commit/25ed92e66bf4345f852e7e84741079c61ae896c8)) + - `File::append()` can append one file to another rather losslessly. ([`09966a8`](https://github.com/Byron/gitoxide/commit/09966a8ea4eaa3e0805e04188de86dd1bac9f388)) + - A test to validate frontmatter isn't currently handled correctly when appending ([`4665e87`](https://github.com/Byron/gitoxide/commit/4665e876df4ac6ab9135c10ee69b5408b89b5313)) + - `file::Section::meta()` to access a section's metadata. ([`56ae574`](https://github.com/Byron/gitoxide/commit/56ae5744e8957e617f3a0ebc4d725846b18d93f8)) + - refactor ([`d60025e`](https://github.com/Byron/gitoxide/commit/d60025e317d2b5f34f3569f321845bbb557ba2e7)) + - `File::sections()` to obtain an iterator over all sections, in order. ([`6f97bf0`](https://github.com/Byron/gitoxide/commit/6f97bf0c3e7164855cf5aa53462dbc39c430e03f)) + - Associate `file::Metadata` with each `File`. ([`6f4eea9`](https://github.com/Byron/gitoxide/commit/6f4eea936d64fb9827277c160f989168e7b1dba2)) + - rename `file::SectionBody` to `file::section::Body`. ([`b672ed7`](https://github.com/Byron/gitoxide/commit/b672ed7667a334be3d45c59f4727f12797b340da)) + - Remove `File::sections_by_name_with_header()` as `::sections_by_name()` now returns entire sections. ([`3bea26d`](https://github.com/Byron/gitoxide/commit/3bea26d7d2a9b5751c6c15e1fa9a924b67e0159e)) + - A way to more easily set interpolation even without following includes. ([`9aa5acd`](https://github.com/Byron/gitoxide/commit/9aa5acdec12a0721543c6bcc39ffe6bd734f9a69)) + - create `resolve_includes` options to make space for more options when loading paths. ([`41b3e62`](https://github.com/Byron/gitoxide/commit/41b3e622ee71943c285eadc518150fc7b6c92361)) + - rename `path::Options` into `path::Context`. ([`cabc8ef`](https://github.com/Byron/gitoxide/commit/cabc8ef0e31c954642525e7693009a7fe4b4c465)) + - try to fix attributes, once more ([`207e483`](https://github.com/Byron/gitoxide/commit/207e483620b29efb029c6ee742c0bb48d54be020)) + - validate incoming conifguration keys when interpreting envirnoment variables. ([`0d07ef1`](https://github.com/Byron/gitoxide/commit/0d07ef1aa4a9e238c20249d4ae2ed19e6740308a)) + - try to fix filter settings, but it doesn't seem to work ([`9750b7a`](https://github.com/Byron/gitoxide/commit/9750b7a1f01d6f0690221c6091b16c51784df0a3)) + - sketch new section and metadata ([`9cb9acb`](https://github.com/Byron/gitoxide/commit/9cb9acb7b7ebada4d6bb3eef199337912ceeaa36)) + - add `Source` type to allow knowing where a particular value is from. ([`c92d5c6`](https://github.com/Byron/gitoxide/commit/c92d5c6a223e377c10c2ca6b822e7eeb9070e12c)) + - `Boolean` can use numbers to indicate true or false, drops support for `one` and `zero`. ([`6b90184`](https://github.com/Byron/gitoxide/commit/6b901843cb18b3d31f8b0b84bb9ebbae279aff19)) + - All accessors in `File` are now using `impl AsRef` where possible for added comfort. ([`3de0cfd`](https://github.com/Byron/gitoxide/commit/3de0cfd81523e4ba7cc362d8625f85ebf8fd9172)) + - Much more comfortable API `file::*Mut` types thanks to `impl Into/AsRef`. ([`3d25fe6`](https://github.com/Byron/gitoxide/commit/3d25fe6c7a52529488fab19c927d64a1bc75838f)) + - Rename `Mutable*` into `$1Mut` for consistency. ([`393b392`](https://github.com/Byron/gitoxide/commit/393b392d515661e5c3e60629319fdab771c3d3f0)) + - `file::MutableSection::remove()` now actually removes keys _and_ values. ([`94dde44`](https://github.com/Byron/gitoxide/commit/94dde44e8dd1a0b8d4e11f2627a3f6b345a15989)) + - many more tests for MutableSection ([`ac843cb`](https://github.com/Byron/gitoxide/commit/ac843cbef4a6322be706b978e6691bc36c5e458f)) + - refactor ([`701266e`](https://github.com/Byron/gitoxide/commit/701266e6e52456c0c1938732c260be19ec8029c9)) + - conform APIs of `file::MutableValue` and `file::MutableMultiValue`. ([`0a7391a`](https://github.com/Byron/gitoxide/commit/0a7391a6575f4035c51a46d34fa20c69e9d078e9)) + - `file::MutableMultiValue` escapes input values and maintains key separator specific whitespace. ([`048b925`](https://github.com/Byron/gitoxide/commit/048b92531eb877a5a128e702504891bf1e31becf)) + - place spaces around `key = value` pairs, or whatever is used in the source configuration. ([`5418bc7`](https://github.com/Byron/gitoxide/commit/5418bc70e67476f8778656f2d577f1f9aa65ffbe)) + - avoid extra copies when setting values and escaping them ([`a7eff01`](https://github.com/Byron/gitoxide/commit/a7eff0166f200a403d4dba320280f20a70e9afc7)) + - refactor ([`15cd1d2`](https://github.com/Byron/gitoxide/commit/15cd1d2ba447ff27819f6cf398d31e96ff11b213)) + - more empty-value tests ([`511985a`](https://github.com/Byron/gitoxide/commit/511985a8084f2a00e0550e5f2a85c93779385a1b)) + - default space is just a single tab, not two ones ([`7e03b83`](https://github.com/Byron/gitoxide/commit/7e03b835bd6f0f5b3f00dbc63e7960ce6364eaef)) + - proper escaping of value bytes to allow round-tripping after mutation ([`8118644`](https://github.com/Byron/gitoxide/commit/8118644625dc25b616e5f33c85f5100d600766e4)) + - refactor ([`afa736a`](https://github.com/Byron/gitoxide/commit/afa736aba385bd52e7f11fd89538aea99787ac9d)) + - a few tests for `MutableValue` showing that it's too buggy right now ([`5e6f9d9`](https://github.com/Byron/gitoxide/commit/5e6f9d909db41926e829e464abc53ef05fbf620b)) + - rename `file::MutableSection::set_leading_space()` to `set_leading_whitespace()`. ([`83a0922`](https://github.com/Byron/gitoxide/commit/83a0922f06081312b79908835dac2b7f4e849bb3)) + - whitespace in newly pushed keys is derived from first section value. ([`9f59356`](https://github.com/Byron/gitoxide/commit/9f59356b4f6a1f5f7f35a62c9fbe4859bf8e8e5f)) + - `File::from_str()` implementation, to support `let config: File = "[core]".parse()?` ([`db1f34d`](https://github.com/Byron/gitoxide/commit/db1f34dfb855058ac08e97d4715876b5db712f61)) + - whitespace in mutable sections can be finely controlled, and is derived from existing sections ([`9157717`](https://github.com/Byron/gitoxide/commit/9157717c2fb143b5decbdf60d18cc2bd99dde775)) + - refactor ([`c88eea8`](https://github.com/Byron/gitoxide/commit/c88eea87d7ece807ca5b1753b47ce89d3ad6a502)) + - refactor ([`a0d6caa`](https://github.com/Byron/gitoxide/commit/a0d6caa243aa293386d4ad164e1604f0e71c2cf3)) + - auto-compute whitespace for sections, even though it probably needs to be better than that ([`ee9ac95`](https://github.com/Byron/gitoxide/commit/ee9ac953180886cc483e1125b7f4e172af92c3ce)) + - validation for Keys and header names ([`59ec7f7`](https://github.com/Byron/gitoxide/commit/59ec7f7bf019d269573f8cc69f6d34b9458b1f1a)) + - Simplify specifying keys when mutating config values. ([`a93a156`](https://github.com/Byron/gitoxide/commit/a93a156655d640ae63ff7c35b0a1f5d67a5ca20f)) + - `File::rename_section()` with validation of input arguments. ([`895ce40`](https://github.com/Byron/gitoxide/commit/895ce40aabbe6d6af5b681a0d0942303fd6549a2)) + - re-add newlines after multi-line values ([`9a2f597`](https://github.com/Byron/gitoxide/commit/9a2f59742cf94643c5b9967b76042bcc7a4e1a71)) + - more header escaping tests ([`12cf005`](https://github.com/Byron/gitoxide/commit/12cf0052d92ee5bee1926f50c879526b5903c175)) + - Enforce `parse::section::Header::new()` by making its fields private. ([`219cf7a`](https://github.com/Byron/gitoxide/commit/219cf7ae0b35b3ac92f97974be52cd022698e01f)) + - `parse::Header::new(…)` with sub-section name validation ([`ae3895c`](https://github.com/Byron/gitoxide/commit/ae3895c7882e0a543a44693faee5f760b49b54d7)) + - section names are now validated. ([`cfd974f`](https://github.com/Byron/gitoxide/commit/cfd974f46d2cbb99e7784a05f5e358fed0d4bcab)) + - prepare for validation of `parse::section::Header` ([`00592f6`](https://github.com/Byron/gitoxide/commit/00592f6b80abe15a32a890ddc2b1fbf6701798d8)) + - basic escaping of subsection names during serialization ([`00d1a9b`](https://github.com/Byron/gitoxide/commit/00d1a9b741845b49d8691262bef6e5c21876567e)) + - refactor ([`9fac8e0`](https://github.com/Byron/gitoxide/commit/9fac8e0066c9b1845d9e06fb30b61ca9e9d64555)) + - new roundtrip test on file level ([`78bb93c`](https://github.com/Byron/gitoxide/commit/78bb93cf35b6a990bac64bbfc56144799ad36243)) + - Add `File::write_to()` and `File::to_bstring()`; remove some `TryFrom` impls. ([`4f6cd8c`](https://github.com/Byron/gitoxide/commit/4f6cd8cf65c2d8698bffe327a19031c342b229a6)) + - remove `Integer::to_bstring()` as well as some `TryFrom` impls. ([`0e392f8`](https://github.com/Byron/gitoxide/commit/0e392f81e99c8c0ff29f41b9b86afd57cd99c245)) + - remove `Boolean::to_bstring()` along with a few `From` impls. ([`b22732a`](https://github.com/Byron/gitoxide/commit/b22732a2ab17213c4a1020859ec41f25ccabfbfc)) + - Add `parse::(Event|section::Header|Comment)::write_to(…)`. ([`d087f12`](https://github.com/Byron/gitoxide/commit/d087f12eec73626eb327eaacef8ebb3836b02381)) + - fix tests on windows ([`3d7fc18`](https://github.com/Byron/gitoxide/commit/3d7fc188914337074775863acc1d6c15f47e913c)) + - value normalization (via `value::normalize()` handles escape sequences. ([`f911707`](https://github.com/Byron/gitoxide/commit/f911707b455ba6f3800b85f667f91e4d56027b91)) + - refactor normalization and more tests ([`cf3bf4a`](https://github.com/Byron/gitoxide/commit/cf3bf4a3bde6cdf20c63ffee1a5ae55a1f4e1742)) + - more escape characters for normalization ([`b92bd58`](https://github.com/Byron/gitoxide/commit/b92bd580de45cb58cd2b3c4af430273e96139c79)) + - review docs of `file::mutating` ([`2d5703e`](https://github.com/Byron/gitoxide/commit/2d5703e5909946e4327e0372097273facaeca759)) + - stable sort order for `File::sections_by_name_with_header()` ([`44dfec0`](https://github.com/Byron/gitoxide/commit/44dfec07480cc2ac6fd01674b748cc03af51fed1)) + - review `file::raw` module ([`6acf4a4`](https://github.com/Byron/gitoxide/commit/6acf4a43fd63c1c5e24b2e21702dc79827e3d11e)) + - don't over-normalize in comfort layer - all values are normalized now ([`b979a3b`](https://github.com/Byron/gitoxide/commit/b979a3b318faada23a6cf073953b13f7828398af)) + - docs for comfort level File API ([`eafc6ce`](https://github.com/Byron/gitoxide/commit/eafc6ce14a9f3d3dbc585e34e465609385f07f69)) + - review and refactor 'File::value' module ([`7aa8a0b`](https://github.com/Byron/gitoxide/commit/7aa8a0b66f3508336e8c20a1a0d2b481e7b9bde8)) + - allocation free `File::sections_by_name()` and `File::sections_by_name_with_header()`. ([`65c520c`](https://github.com/Byron/gitoxide/commit/65c520c4de8187884f87059adf5cef9cbdcd90a2)) + - refactor ([`2abffd6`](https://github.com/Byron/gitoxide/commit/2abffd6f2224edd98f806b5dbd4fc0e1c60019c5)) + - refactor ([`539c2f6`](https://github.com/Byron/gitoxide/commit/539c2f67bede1247478ce75429690c2904915a89)) + - refactor ([`f1668e9`](https://github.com/Byron/gitoxide/commit/f1668e9d9e94f166fa05164612eab9ee26357d12)) + - refactor ([`2599680`](https://github.com/Byron/gitoxide/commit/2599680f7479e18612b4379efbe918139dde2345)) + - refactor ([`879fad5`](https://github.com/Byron/gitoxide/commit/879fad5afdcd90e248934e9c3b973d7bd438d1f9)) + - fix docs ([`b2b82da`](https://github.com/Byron/gitoxide/commit/b2b82da6c6d3c71b249c9ff2055cd98a58f1d988)) + - once again zero-allocation for SectionBodyIter ([`ba69124`](https://github.com/Byron/gitoxide/commit/ba691243778b3eb89452fd1277c50dfe83d0075f)) + - refactor ([`33efef6`](https://github.com/Byron/gitoxide/commit/33efef6de375e399fe33a02e7b6dace1a679ac7e)) + - docs and refactor ([`700d6aa`](https://github.com/Byron/gitoxide/commit/700d6aa34f2604ee72e619afb15c1bb6ce1697f2)) + - `Path::interpolate()` now takes `path::interpolate::Options` instead of three parameters. ([`ac57c44`](https://github.com/Byron/gitoxide/commit/ac57c4479e7b6867e8b8e71f7cf76de759dc64a2)) + - refactor `from_env` ([`c8693f9`](https://github.com/Byron/gitoxide/commit/c8693f9058765671804c93ead1eea1175a94f87c)) + - make fmt ([`a7d7751`](https://github.com/Byron/gitoxide/commit/a7d7751822a1a8ac89930031707af57ad95d9cbd)) + - more doc adjustments ([`95fc20a`](https://github.com/Byron/gitoxide/commit/95fc20a377aeb914d6b527c1d1b8e75d8c42c608)) + - review docs of 'parse' module; refactor ([`a361c7f`](https://github.com/Byron/gitoxide/commit/a361c7ff290cdae071a12351330013ad0043b517)) + - refactor ([`8e84fda`](https://github.com/Byron/gitoxide/commit/8e84fdadfc49ba61f258286acb0a707bfb2a396b)) + - `File::raw_multi_value()` to `File::raw_values()` ([`9cd9933`](https://github.com/Byron/gitoxide/commit/9cd99337333f5ef4b30e0ec9461fc087699576e6)) + - `File::raw_multi_value_mut()` to `File::raw_values_mut()` ([`0076dcf`](https://github.com/Byron/gitoxide/commit/0076dcf9b37f1d633bdad5573b40d34a9fbaba90)) + - `File::multi_value()` to `File::values()`. ([`a8604a2`](https://github.com/Byron/gitoxide/commit/a8604a237782f8d60a185d4730db57bad81424a6)) + - remove `String` type in favor of referring to the `File::string()` method. ([`0915051`](https://github.com/Byron/gitoxide/commit/0915051798dd782b40617a1aa16abd71f6db1175)) + - fix docs ([`8fa7600`](https://github.com/Byron/gitoxide/commit/8fa7600847da6946784466213cea4c32ff9f7f92)) + - refactor ([`b78e3fa`](https://github.com/Byron/gitoxide/commit/b78e3fa792fad4f3e3f9d5c668afccd75bc551e0)) + - change! Add `home_for_user` in `Path::interpolate(…)`. ([`f9e0ef3`](https://github.com/Byron/gitoxide/commit/f9e0ef38e97fbc1e123d310dc696270d496438b6)) + - Simplify `Boolean` to be a wrapper around `bool`. ([`9cadc6f`](https://github.com/Byron/gitoxide/commit/9cadc6f0cbaad0ac23f5469db2f040aecfbfb82c)) + - Use bitflags for `color::Attribute` instead of `Vec` of enums. ([`703922d`](https://github.com/Byron/gitoxide/commit/703922dd4e1e5b27835298217ff4eb8ef1dc57ce)) + - A bitflag version of color attributes ([`23ec673`](https://github.com/Byron/gitoxide/commit/23ec673baaf666fc38fda2f3b1ace9a8cf6816b8)) + - refactor ([`4f21d1e`](https://github.com/Byron/gitoxide/commit/4f21d1ed145bfd0d56d31be73fade25b104bab53)) + - simplify `Color` API. ([`3fc4ac0`](https://github.com/Byron/gitoxide/commit/3fc4ac04f46f869c6e3a94ce4bb8a5737aa0c524)) + - deduplicate ([`c1b9cd4`](https://github.com/Byron/gitoxide/commit/c1b9cd443ec103a01daee8b8226a53f560d62498)) + - first tests for colors specifically; fix space between tokens ([`e2bd055`](https://github.com/Byron/gitoxide/commit/e2bd0557d9ab68a02216c252ab20aaec2e4efd4e)) + - count newlines (for error display) in multi-line values as well ([`1ea919d`](https://github.com/Byron/gitoxide/commit/1ea919d5ff81ab7b01b8201386ef63c7e081b537)) + - zero-copy for section names ([`25b9760`](https://github.com/Byron/gitoxide/commit/25b9760f9a6a79c6e28393f032150e37d5ae831e)) + - prepare for copy-on-write subsections ([`7474997`](https://github.com/Byron/gitoxide/commit/7474997216df2616a034fb9adc0938590f3ab7ed)) + - another normalization case ([`637fe8f`](https://github.com/Byron/gitoxide/commit/637fe8fca2ce36e07ad671a4454da512b709045c)) + - allow backspaces in value parser ([`199e546`](https://github.com/Byron/gitoxide/commit/199e5461cb85b11ce0b9a0e727fab40a49b78456)) + - another failing test pointing at issues with normalization/escaping in parser ([`3c29321`](https://github.com/Byron/gitoxide/commit/3c2932167aa45a89974be79123932bc964fe3ea9)) + - found failing test with complex multi-line value ([`117401d`](https://github.com/Byron/gitoxide/commit/117401ddb9dea1d78b867ddbafe57c2b37ec10f4)) + - review `git-config::File` docs and rename some internal symbols ([`5a8b111`](https://github.com/Byron/gitoxide/commit/5a8b111b9a3bba2c01d7d5e32fc58fd8a64b81ad)) + - more correctness for sub-section parsing ([`910af94`](https://github.com/Byron/gitoxide/commit/910af94fe11bc6e1c270c5512af9124f8a2e0049)) + - reduce top-level docs ([`cdfb13f`](https://github.com/Byron/gitoxide/commit/cdfb13f5984c92c8e7f234e7751b66930291b461)) + - refactor; remove unnecessary docs ([`c95e0b9`](https://github.com/Byron/gitoxide/commit/c95e0b9331282e029ef6188880d11a892ed1b4bf)) + - assure no important docs are missed ([`f5026fb`](https://github.com/Byron/gitoxide/commit/f5026fb3b64bccf26bc8d5a74dbc5e89b98d9959)) + - filtering supportort for `parse::Events`. ([`6ba2f80`](https://github.com/Byron/gitoxide/commit/6ba2f8060768978ad7204e162fb2253ca8843879)) + - deduplicate events instantiation ([`ead757c`](https://github.com/Byron/gitoxide/commit/ead757c2a4b737d2f617cf23c370e2ca5c46b08b)) + - unclutter lifetime declarations ([`e571fdb`](https://github.com/Byron/gitoxide/commit/e571fdb4630ff373ece02efcd963724c05978ede)) + - remove redundant documentation about errors ([`183c7ae`](https://github.com/Byron/gitoxide/commit/183c7ae0d5f44bb468954a7ad18cc02a01d717bc)) + - adjust to changes in `git-config` ([`c52cb95`](https://github.com/Byron/gitoxide/commit/c52cb958f85b533e791ec6b38166a9d819f12dd4)) + - remove `parse::Events::from_path` and `File::at` ([`14149ee`](https://github.com/Byron/gitoxide/commit/14149eea54e2e8a25ac0ccdb2f6efe624f6eaa22)) + - try to strike a balance between allocations and memory footprint ([`52bd1e7`](https://github.com/Byron/gitoxide/commit/52bd1e7455d2b09811ea0ac5140c3693d3c1e1f7)) + - allocation-free parsing as callback is passed through ([`ed00e22`](https://github.com/Byron/gitoxide/commit/ed00e22cbdfea1d69d1d4c2b829effc26b493185)) + - foundation for allocation free (and smallvec free) parsing ([`307c1af`](https://github.com/Byron/gitoxide/commit/307c1afebfba952a4931a69796686b8a998c4cd9)) + - Slim down API surface of `parse::Events`. ([`73adcee`](https://github.com/Byron/gitoxide/commit/73adceeae12270c0d470d4b7271c1fd6089d5c2d)) + - remove `File::new()` method in favor of `File::default()`. ([`2e47167`](https://github.com/Byron/gitoxide/commit/2e47167e4a963743494b2df6b0c15800cb876dd0)) + - a greatly simplified Events->File conversion ([`c5c4398`](https://github.com/Byron/gitoxide/commit/c5c4398a56d4300c83c5be2ba66664bd11f49d5e)) + - fix docs ([`5022be3`](https://github.com/Byron/gitoxide/commit/5022be3bb7fa54c97e5110f74aaded9e2f1b6ca5)) + - about 30% faster parsing due to doing no less allocations for section events ([`050d0f0`](https://github.com/Byron/gitoxide/commit/050d0f0dee9a64597855e85417460f6e84672b02)) + - allocation-free fuzzing, with optimized footprints ([`2e149b9`](https://github.com/Byron/gitoxide/commit/2e149b982ec57689c161924dd1d0b22c4fcb681f)) + - allocation-free sections ([`d3a0c53`](https://github.com/Byron/gitoxide/commit/d3a0c53864ccc9f8d2851d06f0154b9e8f9bcda7)) + - allocation-free frontmatter ([`6c3f326`](https://github.com/Byron/gitoxide/commit/6c3f3264911042e88afa0819414eb543a3626d11)) + - remove last duplicate of top-level parse function ([`cd7a21f`](https://github.com/Byron/gitoxide/commit/cd7a21f8381385833f5353925dc57c05c07e718d)) + - workaround lack of GAT! ([`4fb327c`](https://github.com/Byron/gitoxide/commit/4fb327c247f1c0260cb3a3443d81063b71e87fe4)) + - remove duplication of top-level parser ([`0f5c99b`](https://github.com/Byron/gitoxide/commit/0f5c99bffdb61e4665e83472275c5c8b0383650b)) + - a minimally invasive sketch of a parse Delegate ([`5958ffb`](https://github.com/Byron/gitoxide/commit/5958ffbfec7724c1a47be8db210df03cf54c9374)) + - fix docs ([`2186456`](https://github.com/Byron/gitoxide/commit/218645618429258e48cb0fdb2bbfba3daa32ee2d)) + - fix fuzz crash in parser ([`86e1a76`](https://github.com/Byron/gitoxide/commit/86e1a76484be50f83d06d6c8a176107f8cb3dea6)) + - rename `parse::event::List` to `parse::Events` ([`ea67650`](https://github.com/Byron/gitoxide/commit/ea6765093b5475912ba1aa81d4440cbf5dd49fb6)) + - rename `parse::State` to `parse::event::List` ([`89f5fca`](https://github.com/Byron/gitoxide/commit/89f5fca843d999c5bea35fb3fe2a03dc3588f74e)) + - update fuzz instructions and make it work ([`19300d5`](https://github.com/Byron/gitoxide/commit/19300d5f37c201aba921a6bff9760996fec2108e)) + - improve normalization; assure no extra copies are made on query. ([`4a01d98`](https://github.com/Byron/gitoxide/commit/4a01d983f54a7713dea523f6032cbf5bb2b9dde8)) + - refactor; assure `normalize` doesn't copy unnecessarily ([`ce069ca`](https://github.com/Byron/gitoxide/commit/ce069ca0b6b44cd734f4d8b4525916d1ddb0de0b)) + - normalize values in all the right places ([`91ba2dd`](https://github.com/Byron/gitoxide/commit/91ba2ddcd3de63aa22dc6e863b26ce1893a36995)) + - avoid unnecessary clones ([`e684488`](https://github.com/Byron/gitoxide/commit/e68448831a94574ee3ca2fa36788f603c91d57a0)) + - adapt to changes in `git-config` ([`363a826`](https://github.com/Byron/gitoxide/commit/363a826144ad59518b5c1a3dbbc82d04e4fc062d)) + - move `value::*` into the crate root, except for `Error` and `normalize_*()`. ([`3cdb089`](https://github.com/Byron/gitoxide/commit/3cdb0890b71e62cfa92b1ed1760c88cb547ec729)) + - rename `value::parse::Error` to `value::Error`. ([`748d921`](https://github.com/Byron/gitoxide/commit/748d921efd7469d5c19e40ddcb9099e2462e3bbc)) + - rename `value::TrueVariant` to `value::boolean::True` ([`7e8a225`](https://github.com/Byron/gitoxide/commit/7e8a22590297f2f4aab76b53be512353637fb651)) + - rename `IntegerSuffix` to `integer::Suffix` ([`8bcaec0`](https://github.com/Byron/gitoxide/commit/8bcaec0599cf085a73b344f4f53fc023f6e31430)) + - rename `value::Color(Attribute|Value)` to `value::color::Attribute` and `value::color::Name`. ([`d085037`](https://github.com/Byron/gitoxide/commit/d085037ad9c067af7ce3ba3ab6e5d5ddb45b4057)) + - refactor ([`a0f7f44`](https://github.com/Byron/gitoxide/commit/a0f7f44c4fca20d3c9b95a3fafe65cef84c760e7)) + - refactor ([`0845c84`](https://github.com/Byron/gitoxide/commit/0845c84b6f694d97519d5f86a97bca49739df8bf)) + - keep str in value API ([`ef5b48c`](https://github.com/Byron/gitoxide/commit/ef5b48c71e0e78fa602699a2f8ca8563c10455c4)) + - Keep BStr even though str could be used. ([`aeca6cc`](https://github.com/Byron/gitoxide/commit/aeca6cce7b4cfe67b18cd80abb600f1271ad6057)) + - Turn `parse::ParseOrIoError` into `parse::state::from_path::Error` ([`a0f6252`](https://github.com/Byron/gitoxide/commit/a0f6252343a62b0b55eef02888ac00c09100687a)) + - rename `parse::ParsedComment` into `parse::Comment` ([`b6b31e9`](https://github.com/Byron/gitoxide/commit/b6b31e9c8dd8b3dc4860431069bb1cf5eacd1702)) + - Allocation-free hashing for section keys and names ([`44d0061`](https://github.com/Byron/gitoxide/commit/44d00611178a4e2f6a080574c41355a50b79b181)) + - allocation-free case-inequality tests for section keys and names ([`94608db`](https://github.com/Byron/gitoxide/commit/94608db648cd717af43a97785ea842bc75361b7e)) + - rename `parse::Section*` related types. ([`239cbfb`](https://github.com/Byron/gitoxide/commit/239cbfb450a8cddfc5bec1de21f3dc54fab914ce)) + - adjustments required due to changed in `git-config` ([`41bfd3b`](https://github.com/Byron/gitoxide/commit/41bfd3b4122e37370d268608b60cb00a671a8879)) + - rename `parse::Parser` to `parse::State`. ([`60af4c9`](https://github.com/Byron/gitoxide/commit/60af4c9ecb1b99f21df0e8facc33e5f6fc70c424)) + - rename `parser` module to `parse` ([`3724850`](https://github.com/Byron/gitoxide/commit/3724850e0411f1f76e52c6c767fd8cebe8aea0f6)) + - fix docs ([`b05aed1`](https://github.com/Byron/gitoxide/commit/b05aed1cfc15a2e29d7796bad4c9a6d4019f4353)) + - refactor ([`8bd9cd6`](https://github.com/Byron/gitoxide/commit/8bd9cd695d608d05859d8bff4033883e71ce7caa)) + - refactor ([`90dd2ce`](https://github.com/Byron/gitoxide/commit/90dd2cec8ea88980365bfd08a16614d145e87095)) + - fix docs ([`0d1be2b`](https://github.com/Byron/gitoxide/commit/0d1be2b893574f2a9d4ba35ac4f2b3da710d4b03)) + - rename `normalize_cow()` to `normalize()` and move all `normalize*` functions from `values` to the `value` module ([`58b2215`](https://github.com/Byron/gitoxide/commit/58b22152a0295998935abb43563e9096589ef53e)) + - Documentation for feature flags ([`26e4a9c`](https://github.com/Byron/gitoxide/commit/26e4a9c83af7550eab1acaf0256099774be97965)) + - `serde1` feature to add limited serde support ([`5a8f242`](https://github.com/Byron/gitoxide/commit/5a8f242ee98793e2467e7bc9806f8780b9d320ce)) + - remove unused serde feature ([`66a8237`](https://github.com/Byron/gitoxide/commit/66a8237ff284c2cf7f80cc909c7b613b599e1358)) + - move `Path` from `values` to `value` module ([`767bedc`](https://github.com/Byron/gitoxide/commit/767bedccdae1f3e6faf853d59ecf884a06cc3827)) + - Move `Boolean` and `String` from `values` into `value` module ([`6033f3f`](https://github.com/Byron/gitoxide/commit/6033f3f93d2356399a661567353a83a044662699)) + - move `values::Integer` into `value` module ([`d4444e1`](https://github.com/Byron/gitoxide/commit/d4444e18042891b0fe5b9c6e6813fed26df6c560)) + - move `Color` to own `value` module ([`38f3117`](https://github.com/Byron/gitoxide/commit/38f31174e8c117af675cdfbc21926133b821ec38)) + - Make symlink tests so that they test real-path conversion ([`d4fbf2e`](https://github.com/Byron/gitoxide/commit/d4fbf2ea71ee1f285c195dd00bfa4e21bf429922)) + - adjustments due to breaking changes in `git_path` ([`4420ae9`](https://github.com/Byron/gitoxide/commit/4420ae932d5b20a9662a6d36353a27111b5cd672)) + - a test to validate relative includepaths aren't valid for includeIf ([`7d27dd5`](https://github.com/Byron/gitoxide/commit/7d27dd5e3558a22865e0c9159d269577431097f3)) + - reuse the initialized environment for a little speed ([`6001613`](https://github.com/Byron/gitoxide/commit/600161324edc370707613841ce9228320c700bf6)) + - Also test against git baseline ([`adcddb0`](https://github.com/Byron/gitoxide/commit/adcddb0056c14302f0133de251fa07e877b6f509)) + - refactor ([`0229e25`](https://github.com/Byron/gitoxide/commit/0229e2583ed7beccaf59dc0c82893c5b67c285dd)) + - prevent race when calling `git` around `GIT_CONFIG_*` env vars ([`53efbf5`](https://github.com/Byron/gitoxide/commit/53efbf54364c373426a7790c28c74c787670877a)) + - remove duplicate gitdir tests that don't have a baseline ([`5c71394`](https://github.com/Byron/gitoxide/commit/5c713946b1f35675bacb27bd5392addf25010942)) + - remove unmotivated forward-slash conversion ([`3af09e5`](https://github.com/Byron/gitoxide/commit/3af09e5800648df87cdaf22191dd4d1dc4b278a3)) + - improved slash/backslash handling on windows ([`a3b7828`](https://github.com/Byron/gitoxide/commit/a3b7828e8bf9d90775f10b0d996fc7ad82f92466)) + - fix build warnings on windows ([`9d48b2f`](https://github.com/Byron/gitoxide/commit/9d48b2f51777de37cc996ad54261f2d20f417901)) + - fix windows test ([`a922f0a`](https://github.com/Byron/gitoxide/commit/a922f0a817d290ef4a539bbf99238a4f96d443f9)) + - refactor ([`d76aee2`](https://github.com/Byron/gitoxide/commit/d76aee22498cb980ab0b53295a2e51af04a8cb7c)) + - conforming subsection parsing handling backslashes like git ([`6366148`](https://github.com/Byron/gitoxide/commit/6366148f538ee03314dd866e083157de810d4ad4)) + - Only copy pattern if required ([`b3a752a`](https://github.com/Byron/gitoxide/commit/b3a752a0a873cf9d685e1893c8d35255d7f7323a)) + * **Uncategorized** + - thanks clippy ([`fddc720`](https://github.com/Byron/gitoxide/commit/fddc7206476423a6964d61acd060305572ecd02b)) + - thanks fuzzy ([`15a379a`](https://github.com/Byron/gitoxide/commit/15a379a85d59d83f3a0512b9e9fbff1774c9f561)) + - thanks clippy ([`15fee74`](https://github.com/Byron/gitoxide/commit/15fee74fdfb5fc84349ac103cd5727332f3d2230)) + - thanks clippy ([`0b05be8`](https://github.com/Byron/gitoxide/commit/0b05be850d629124f027af993e316b9018912337)) + - thanks clippy ([`693e304`](https://github.com/Byron/gitoxide/commit/693e304a2c38130ed936d5e4544faaa858665872)) + - fix git-config/tests/.gitattributes ([`a741766`](https://github.com/Byron/gitoxide/commit/a7417664ca1e41936f9de8cf066e13aeaf9b0d75)) + - Merge branch 'config-metadata' ([`453e9bc`](https://github.com/Byron/gitoxide/commit/453e9bca8f4af12e49222c7e3a46d6222580c7b2)) + - forced checkin to fix strange crlf issue ([`5d0a5c0`](https://github.com/Byron/gitoxide/commit/5d0a5c0712fbd8fcc00aff54563c83281afc9476)) + - thanks clippy ([`e5ba0f5`](https://github.com/Byron/gitoxide/commit/e5ba0f532bf9bfee46d2dab24e6a6503df4d239d)) + - thanks clippy ([`00bfbca`](https://github.com/Byron/gitoxide/commit/00bfbca21e2361008c2e81b54424a9c6f09e76e9)) + - thanks clippy ([`09e2374`](https://github.com/Byron/gitoxide/commit/09e23743035b9d4463f438378aed54677c03311f)) + - thanks clippy ([`e842633`](https://github.com/Byron/gitoxide/commit/e84263362fe0631935379a0b4e8d8b1fcf6ac81b)) + - thanks clippy ([`3ca8027`](https://github.com/Byron/gitoxide/commit/3ca8027e07a835e84a704688778cfb82c956643b)) + - make fmt ([`aa9fdb0`](https://github.com/Byron/gitoxide/commit/aa9fdb0febfb29f906eb81e4378f07ef01b03e05)) + - thanks clippy ([`c9a2390`](https://github.com/Byron/gitoxide/commit/c9a239095511ae95fb5efbbc9207293641b623f7)) + - thanks clippy ([`badd00c`](https://github.com/Byron/gitoxide/commit/badd00c402b59994614e653b28bb3e6c5b70d9d1)) + - make fmt ([`0700b09`](https://github.com/Byron/gitoxide/commit/0700b09d6828849fa2470df89af1f75a67bfb27d)) + - thanks clippy ([`b246f0a`](https://github.com/Byron/gitoxide/commit/b246f0ade5aa42413cc387470b35df357b1136bc)) + - thanks clippy ([`08441de`](https://github.com/Byron/gitoxide/commit/08441def5d1738bbf13b68979f2d1ff7ff3b4153)) + - thanks clippy ([`8b29dda`](https://github.com/Byron/gitoxide/commit/8b29ddaa627048b9ca130b52221709a575f50d3a)) + - thanks clippy ([`cff6e01`](https://github.com/Byron/gitoxide/commit/cff6e018a8f0c3b6c78425f99a204d29d72a65aa)) + - thanks clippy ([`f7be3b0`](https://github.com/Byron/gitoxide/commit/f7be3b0f79bf19faf5a3b68032f764c0b7a12d7e)) + - thanks clippy ([`7a2a31e`](https://github.com/Byron/gitoxide/commit/7a2a31e5758a2be8434f22cd9401ac00539f2bd9)) + - Allow backslashes in subsections ([`6f4f325`](https://github.com/Byron/gitoxide/commit/6f4f325a42656800c8c76c8eae075508c31657be)) + - fix build after changes to `git-url` and `git-config` ([`1f02420`](https://github.com/Byron/gitoxide/commit/1f0242034071ce317743df75cc685e7428b604b0)) + - thanks clippy ([`9b6a67b`](https://github.com/Byron/gitoxide/commit/9b6a67bf369fcf51c6a3289784c3ef8ab366bee7)) + - remove `values::Bytes` - use `values::String` instead. ([`aa630ad`](https://github.com/Byron/gitoxide/commit/aa630ad6ec2c6306d3307d5c77e272cb24b00ddd)) + - change mostily internal uses of [u8] to BString/BStr ([`311d4b4`](https://github.com/Byron/gitoxide/commit/311d4b447daf8d4364670382a20901468748d34d)) + - Definitely don't unconditionally convert to forward slashes ([`146eb0c`](https://github.com/Byron/gitoxide/commit/146eb0c831ce0a96e215b1ec6499a86bbf5902c9)) + - avoid panics and provide errors instead of just not matching ([`a0f842c`](https://github.com/Byron/gitoxide/commit/a0f842c7f449a6a7aedc2742f7fc4f74a12fdd17)) + - try to fix git-config tests on windows even harder ([`16778d4`](https://github.com/Byron/gitoxide/commit/16778d478d6941ab86571de0bd99aaab816ffe67)) + - try once more to get failing tests under control on windows ([`c26c2e9`](https://github.com/Byron/gitoxide/commit/c26c2e962aa6a93c0e06b900dc719f9cd92f6137)) + - thanks clippy ([`27b2dde`](https://github.com/Byron/gitoxide/commit/27b2dde9a299aca112347f988fa21d797f64552b)) + - fix test with brute force; take some notes for later ([`2eda529`](https://github.com/Byron/gitoxide/commit/2eda5296ad9ee58756d564225e98e64a800f46d7)) + - Merge branch 'main' into cont_include_if ([`daa71c3`](https://github.com/Byron/gitoxide/commit/daa71c3b753c6d76a3d652c29237906b3e28728f)) + - Take GitEnv by ref. ([`937d7ee`](https://github.com/Byron/gitoxide/commit/937d7eea84e92467fcc8a6a72c78fe6c060dd95d)) + - remove leftover debug printing ([`7d1cf34`](https://github.com/Byron/gitoxide/commit/7d1cf34e4535721db97f566734f68014ebc7c3e8)) + - auto-normalize string values to support quote removal in case of strings. ([`1e71e71`](https://github.com/Byron/gitoxide/commit/1e71e71c984289f0d7e0a39379ee6728918b7dc5)) + - refactor ([`1d6ba9b`](https://github.com/Byron/gitoxide/commit/1d6ba9bd719ad01ce22573cabd8022ddb675c5fc)) + - avoid unwrap() more as the test code matures ([`c2d7e80`](https://github.com/Byron/gitoxide/commit/c2d7e800abe022f5a2663176f0f6b3ac90eacf0e)) + - refactor ([`b5c0b30`](https://github.com/Byron/gitoxide/commit/b5c0b3011d2c0e63c933be42753aea65b88ca569)) + - Merge branch 'main' into cont_include_if ([`0e9df36`](https://github.com/Byron/gitoxide/commit/0e9df364c4cddf006b1de18b8d167319b7cc1186)) + - make '..' related tests work ([`5f11318`](https://github.com/Byron/gitoxide/commit/5f11318dc55b8dd8da016a4053cc4ad34b13fa97)) + - find a few cases that aren't according to spec by failing (and ignored) tests ([`f0e6ea9`](https://github.com/Byron/gitoxide/commit/f0e6ea9086ebfa134044568114bb578120eb5da9)) + - refactor ([`62e5396`](https://github.com/Byron/gitoxide/commit/62e5396ac9221f13437c87f06715c98989981785)) + - generally avoid using `target_os = "windows"` in favor of `cfg(windows)` and negations ([`91d5402`](https://github.com/Byron/gitoxide/commit/91d54026a61c2aae5e3e1341d271acf16478cd83)) + - Invoke git only when necessary ([`556c7cf`](https://github.com/Byron/gitoxide/commit/556c7cff5f813e885598b4bd858c6c22cedf688b)) + - Also use git_path::realpath() in other places that used canonicalize before ([`08af648`](https://github.com/Byron/gitoxide/commit/08af648923c226a0330f0025784c42914d4fea7f)) + - Our own git_path::realpath doesn't have the questionmark? issue on windows ([`cfe196b`](https://github.com/Byron/gitoxide/commit/cfe196b23051e639cb1332f88f1ec917608fbbe1)) + - fix windows tests ([`47f10fe`](https://github.com/Byron/gitoxide/commit/47f10feb2b143b9b429237cf6a4a7424c2b9ab13)) + - more debugging for windows failures ([`e0a72e6`](https://github.com/Byron/gitoxide/commit/e0a72e65e4bbe76755aea1a905d69d74d01f543a)) + - no need for serial anymore ([`34bb715`](https://github.com/Byron/gitoxide/commit/34bb7152ca5992fc35be5f51016565a568916e7c)) + - Make a note to be sure we use the home-dir correctly in git-repository; avoid `dirs` crate ([`0e8cf19`](https://github.com/Byron/gitoxide/commit/0e8cf19d7f742f9400afa4863d302ba18a452adc)) + - finally all tests work without the need for dirs::home_dir() ([`180ce99`](https://github.com/Byron/gitoxide/commit/180ce99a016c17641990eb41b6bbe3b2407ab271)) + - refactor ([`00ba5d8`](https://github.com/Byron/gitoxide/commit/00ba5d8a53aae1c4adbb379c076651756e1af68d)) + - refactor ([`0eb7ced`](https://github.com/Byron/gitoxide/commit/0eb7ced6ec49fe6303659bdcab29952c5cea41bd)) + - Path-interpolation makes `home-dir` configurable. ([`edd2267`](https://github.com/Byron/gitoxide/commit/edd226719cd04a480274cb7d983b6d5d8bfdbb13)) + - refactor ([`aab9865`](https://github.com/Byron/gitoxide/commit/aab98656ee5c4abf65f79d403c1f0cb36fd0ee88)) + - Change last test to new simplified symlink setup ([`a40e3c9`](https://github.com/Byron/gitoxide/commit/a40e3c999baf203c92d0e5e53ee61c0032e32e51)) + - refactor ([`67677b0`](https://github.com/Byron/gitoxide/commit/67677b0edfa1faa0c011a225d41d78dbde3c5f15)) + - assure the IDE doesn't confuse a module with a test ([`7be0b05`](https://github.com/Byron/gitoxide/commit/7be0b05ff3a5bbea9d9712e4d13ee08cf9979861)) + - refactor ([`1203a14`](https://github.com/Byron/gitoxide/commit/1203a14eba79d335137c96d4ee573739df30b067)) + - refactor ([`a721efe`](https://github.com/Byron/gitoxide/commit/a721efecd36984064b4b31c715bbe011df2538ad)) + - refactor ([`2c8c6e5`](https://github.com/Byron/gitoxide/commit/2c8c6e53fd4681289c9fa2308735c779ed4eace5)) + - refactor ([`eb0ace1`](https://github.com/Byron/gitoxide/commit/eb0ace14a92899002749d6dbd99dac3a35d73c25)) + - refactor ([`8f8f873`](https://github.com/Byron/gitoxide/commit/8f8f873ae711eb5ae62f192f6731653f2bb7ff4b)) + - Merge branch 'main' into cont_include_if ([`41ea8ba`](https://github.com/Byron/gitoxide/commit/41ea8ba78e74f5c988148367386a1f4f304cb951)) + - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) + - Remove `git-config` test utilities from `git-path`. ([`c9933c0`](https://github.com/Byron/gitoxide/commit/c9933c0b0f51d21dc8244b2acc33d7dc8a33f6ce)) + - Add repo_dir to EnvOverwrite. ([`ed5c442`](https://github.com/Byron/gitoxide/commit/ed5c442cc4f0c546834f2e0e9dc553a221b6985d)) + - Use EnvOverwrite struct. ([`f2e124f`](https://github.com/Byron/gitoxide/commit/f2e124f60f8f9a0d517fddb029d795fa91bcda5a)) + - tempdir lives long enough for sure. ([`a41002f`](https://github.com/Byron/gitoxide/commit/a41002fe4004485fac429d904bc4e8b6842eaf3c)) + - Disable symlink tests on windows. ([`8de6b3d`](https://github.com/Byron/gitoxide/commit/8de6b3d42c89c741195e4add273a2d1e7b48fad9)) +
+ ## 0.5.0 (2022-06-13) ### New Features (BREAKING) @@ -16,7 +569,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 7 commits contributed to the release over the course of 22 calendar days. + - 41 commits contributed to the release over the course of 22 calendar days. - 22 days passed between releases. - 1 commit where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#436](https://github.com/Byron/gitoxide/issues/436) @@ -30,11 +583,45 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * **[#436](https://github.com/Byron/gitoxide/issues/436)** - Remove outdated examples ([`cb9529e`](https://github.com/Byron/gitoxide/commit/cb9529e18b222b9fd9f8c1bb0dba8038a6ea1d4b)) * **Uncategorized** + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980)) + - make fmt ([`cd4f727`](https://github.com/Byron/gitoxide/commit/cd4f7279678678fa6f2e55d4e7681a2075f1d6cf)) + - Temp ignore symlink tests. ([`ec40b94`](https://github.com/Byron/gitoxide/commit/ec40b94bffda14b7b991dd57cd36d893f1f6962b)) + - fmt. ([`82ea726`](https://github.com/Byron/gitoxide/commit/82ea7261cfb75a01992489aa7631e2e6d807be06)) + - Use `dirs::home_dir()` ([`5767a50`](https://github.com/Byron/gitoxide/commit/5767a505f2f2cc3515eb604e39da673fa2e09454)) + - Try fix windows home. ([`393758e`](https://github.com/Byron/gitoxide/commit/393758e14a1b5ff14301f153807fe45623d9f973)) + - Add more tests. ([`db1204d`](https://github.com/Byron/gitoxide/commit/db1204d74b16ff7e905fb5b2d91d9ecb109bca07)) + - Add debug output. ([`52db5e8`](https://github.com/Byron/gitoxide/commit/52db5e8894c5033ec3d58894a7cf17b4f29e03f4)) + - Tests like git: https://github.com/git/git/blob/master/t/t1305-config-include.sh ([`c3a0454`](https://github.com/Byron/gitoxide/commit/c3a04548b08b6972ea0999b0030017d1a6002de2)) + - Start extracting gitdir tests cont. ([`22e5cbe`](https://github.com/Byron/gitoxide/commit/22e5cbece0206da6cf8890a831fd82847526396a)) - remove `pwd` crate dependency in favor of using libc directly ([`4adfa11`](https://github.com/Byron/gitoxide/commit/4adfa11d70cf78bed541fa59707e8a5082dda245)) - Drop non-existent config paths before parsing ([`475d6fa`](https://github.com/Byron/gitoxide/commit/475d6fab2467ad0499db7df2d4c99f74e43682fc)) + - Start extracting gitdir tests. ([`5aaf7ba`](https://github.com/Byron/gitoxide/commit/5aaf7ba93857f1e5570f64f4a9539cd3d547b81d)) + - thanks clippy ([`cfa577f`](https://github.com/Byron/gitoxide/commit/cfa577f84c45c7fbed27e6d59ef361f9ac5c2614)) + - refactor ([`da23958`](https://github.com/Byron/gitoxide/commit/da239580fca76011f91a45ae502af88c67d429a4)) + - Finalize onbranch tests; remove mixed ones in favor of specific cases ([`26680c4`](https://github.com/Byron/gitoxide/commit/26680c48951a82d5119f54c57b4e7045d2c20649)) + - refactor ([`11c417f`](https://github.com/Byron/gitoxide/commit/11c417fdc03331db2c4a778bc3e8038ffd0aff89)) + - More tests for branch matching prior to making tests clearer ([`31e6db8`](https://github.com/Byron/gitoxide/commit/31e6db8cdc959549a6c2754692d2471103ada64f)) + - Basic test-setup for more specialized tests ([`b4374d2`](https://github.com/Byron/gitoxide/commit/b4374d21882eca637ddbb80cdde1dac7bc68560e)) + - refactor ([`04da720`](https://github.com/Byron/gitoxide/commit/04da7207a7e44175dc96e4ea850274b2cc5a6d84)) + - Fix including .. path. ([`8891fea`](https://github.com/Byron/gitoxide/commit/8891feac0341960a6339ee86c671fc80c3133b4e)) + - Fix case-insensitive. ([`ca05802`](https://github.com/Byron/gitoxide/commit/ca058024e1e19818261fea39099c893d666928dc)) + - Fix \\ test. ([`ab555b5`](https://github.com/Byron/gitoxide/commit/ab555b557f4bd68b491a552a14cd4549c6a625bc)) + - fix tests on windows ([`bb3b4f0`](https://github.com/Byron/gitoxide/commit/bb3b4f013c862a4c017c65075919e1df59cc1986)) + - refactor ([`e1ba36f`](https://github.com/Byron/gitoxide/commit/e1ba36fab772417d9b60bf89cc49b45fbb7252f9)) + - Merge branch 'main' into svetli-n-cont_include_if ([`315c87e`](https://github.com/Byron/gitoxide/commit/315c87e18c6cac0fafa7b4e59fdd3c076a58a45a)) + - refactor ([`e47fb41`](https://github.com/Byron/gitoxide/commit/e47fb412a136d087c79710e7490d3e1c97d1f955)) + - refactor ([`56eadc8`](https://github.com/Byron/gitoxide/commit/56eadc8b565b2f8a272080bc8814d6665b3f1205)) + - refactor ([`0ccd8ae`](https://github.com/Byron/gitoxide/commit/0ccd8ae0ab01cdb5ae33dd79f486edfcee2b176a)) + - Try fix windows test. ([`e2e94db`](https://github.com/Byron/gitoxide/commit/e2e94db2cee237168d5c56db5c5e94a8b4317991)) + - Refactor include sequence test. ([`b4e657e`](https://github.com/Byron/gitoxide/commit/b4e657ed02cf062b1c2cb1f6c15abdf5d777c177)) + - Extract include_paths. ([`c078671`](https://github.com/Byron/gitoxide/commit/c0786717c4979810002365a68d31abbf21d90f2d)) - Merge branch 'main' into davidkna-envopen ([`bc0abc6`](https://github.com/Byron/gitoxide/commit/bc0abc643d3329f885f250b6880560dec861150f)) - Make `realpath()` easier to use by introducing `realpath_opt()`. ([`266d437`](https://github.com/Byron/gitoxide/commit/266d4379e9132fd7dd21e6c8fccb36e125069d6e)) + - Adjust test structure to mirror the new code structure. ([`984b58e`](https://github.com/Byron/gitoxide/commit/984b58ee1dac58fe0dfd0b80f990ca37d323cad7)) + - Refact. ([`d5d81bc`](https://github.com/Byron/gitoxide/commit/d5d81bc16116b4c58f628e0e5c66d5d0a59b7816)) + - Read include and incideIf sections in correct order. ([`a4a7ebd`](https://github.com/Byron/gitoxide/commit/a4a7ebdb6fcb5f6183917719d6c93f54eea72e85)) + - Refact. ([`a342e53`](https://github.com/Byron/gitoxide/commit/a342e53dac58cea1787a94eaa1a9d24fb1389df2)) - Merge branch 'davidkna-discover-x-fs' ([`9abaeda`](https://github.com/Byron/gitoxide/commit/9abaeda2d22e2dbb1db1632c6eb637f1458d06e1)) diff --git a/git-credentials/CHANGELOG.md b/git-credentials/CHANGELOG.md index af0d80ffe7f..4add543252b 100644 --- a/git-credentials/CHANGELOG.md +++ b/git-credentials/CHANGELOG.md @@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +This is a maintenance release with no functional changes. + +### Commit Statistics + + + + - 2 commits contributed to the release over the course of 33 calendar days. + - 39 days passed between releases. + - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) + - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) +
+ ## 0.2.0 (2022-06-13) A maintenance release without user-facing changes. @@ -13,7 +37,7 @@ A maintenance release without user-facing changes. - - 1 commit contributed to the release. + - 2 commits contributed to the release. - 25 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +49,7 @@ A maintenance release without user-facing changes.
view details * **Uncategorized** + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980))
diff --git a/git-date/CHANGELOG.md b/git-date/CHANGELOG.md index a170e201216..3bf540c600c 100644 --- a/git-date/CHANGELOG.md +++ b/git-date/CHANGELOG.md @@ -5,6 +5,37 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### New Features + + - initialize `Time` from `now_utc` and `now_local` + Localtime support depends on some other factors now, but that + will only get better over time. + + We might have to document `unsound_local_time` at some point. + - `Time::is_set()` to see if the time is more than just the default. + +### Commit Statistics + + + + - 2 commits contributed to the release. + - 39 days passed between releases. + - 2 commits where understood as [conventional](https://www.conventionalcommits.org). + - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) + +### Commit Details + + + +
view details + + * **[#331](https://github.com/Byron/gitoxide/issues/331)** + - initialize `Time` from `now_utc` and `now_local` ([`c76fde7`](https://github.com/Byron/gitoxide/commit/c76fde7de278b49ded13b655d5345e4eb8c1b134)) + - `Time::is_set()` to see if the time is more than just the default. ([`aeda76e`](https://github.com/Byron/gitoxide/commit/aeda76ed500d2edba62747d667227f2664edd267)) +
+ ## 0.0.1 (2022-06-13) ### New Features @@ -16,7 +47,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 4 commits contributed to the release over the course of 58 calendar days. + - 5 commits contributed to the release over the course of 58 calendar days. - 59 days passed between releases. - 1 commit where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#427](https://github.com/Byron/gitoxide/issues/427) @@ -31,6 +62,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - reflog lookup by date is complete ([`b3d009e`](https://github.com/Byron/gitoxide/commit/b3d009e80e3e81afd3d095fa2d7b5fc737d585c7)) - Add `Time` type. ([`cfb6a72`](https://github.com/Byron/gitoxide/commit/cfb6a726ddb763f7c22688f8ef309e719c2dfce4)) * **Uncategorized** + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980)) - Merge branch 'test-archive-support' ([`350df01`](https://github.com/Byron/gitoxide/commit/350df01042d6ca8b93f8737fa101e69b50535a0f)) diff --git a/git-diff/CHANGELOG.md b/git-diff/CHANGELOG.md index 413e125595e..da53509948e 100644 --- a/git-diff/CHANGELOG.md +++ b/git-diff/CHANGELOG.md @@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +This is a maintenance release with no functional changes. + +### Commit Statistics + + + + - 3 commits contributed to the release over the course of 30 calendar days. + - 64 days passed between releases. + - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Merge branch 'main' into cont_include_if ([`daa71c3`](https://github.com/Byron/gitoxide/commit/daa71c3b753c6d76a3d652c29237906b3e28728f)) + - Merge branch 'main' into cont_include_if ([`0e9df36`](https://github.com/Byron/gitoxide/commit/0e9df364c4cddf006b1de18b8d167319b7cc1186)) + - generally avoid using `target_os = "windows"` in favor of `cfg(windows)` and negations ([`91d5402`](https://github.com/Byron/gitoxide/commit/91d54026a61c2aae5e3e1341d271acf16478cd83)) +
+ ## 0.16.0 (2022-05-18) A maintenance release without user-facing changes. @@ -13,7 +38,7 @@ A maintenance release without user-facing changes. - - 5 commits contributed to the release over the course of 34 calendar days. + - 6 commits contributed to the release over the course of 34 calendar days. - 43 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 2 unique issues were worked on: [#301](https://github.com/Byron/gitoxide/issues/301), [#384](https://github.com/Byron/gitoxide/issues/384) @@ -31,6 +56,8 @@ A maintenance release without user-facing changes. - No need to isolate archives by crate name ([`19d46f3`](https://github.com/Byron/gitoxide/commit/19d46f35440419b9911b6e2bca2cfc975865dce9)) - add archive files via git-lfs ([`7202a1c`](https://github.com/Byron/gitoxide/commit/7202a1c4734ad904c026ee3e4e2143c0461d51a2)) - auto-set commit.gpgsign=false when executing git ([`c23feb6`](https://github.com/Byron/gitoxide/commit/c23feb64ad157180cfba8a11c882b829733ea8f6)) + * **Uncategorized** + - Release git-hash v0.9.4, git-features v0.21.0, git-actor v0.10.0, git-glob v0.3.0, git-path v0.1.1, git-attributes v0.1.0, git-sec v0.1.0, git-config v0.3.0, git-credentials v0.1.0, git-validate v0.5.4, git-object v0.19.0, git-diff v0.16.0, git-lock v2.1.0, git-ref v0.13.0, git-discover v0.1.0, git-index v0.3.0, git-mailmap v0.2.0, git-traverse v0.15.0, git-pack v0.19.0, git-odb v0.29.0, git-packetline v0.12.5, git-url v0.5.0, git-transport v0.17.0, git-protocol v0.16.0, git-revision v0.2.0, git-worktree v0.2.0, git-repository v0.17.0, safety bump 20 crates ([`654cf39`](https://github.com/Byron/gitoxide/commit/654cf39c92d5aa4c8d542a6cadf13d4acef6a78e)) ## 0.15.0 (2022-04-05) @@ -73,7 +100,7 @@ A maintenance release primarily to adapt to dependent crates. - - 8 commits contributed to the release over the course of 68 calendar days. + - 7 commits contributed to the release over the course of 68 calendar days. - 69 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#364](https://github.com/Byron/gitoxide/issues/364) @@ -92,7 +119,6 @@ A maintenance release primarily to adapt to dependent crates. - Merge branch 'for-onefetch' ([`8e5cb65`](https://github.com/Byron/gitoxide/commit/8e5cb65da75036a13ed469334e7ae6c527d9fff6)) - Release git-hash v0.9.3, git-features v0.20.0, git-config v0.2.0, safety bump 12 crates ([`f0cbb24`](https://github.com/Byron/gitoxide/commit/f0cbb24b2e3d8f028be0e773f9da530da2656257)) - Merge branch 'AP2008-implement-worktree' ([`f32c669`](https://github.com/Byron/gitoxide/commit/f32c669bc519d59a1f1d90d61cc48a422c86aede)) - - Release git-bitmap v0.0.1, git-hash v0.9.0, git-features v0.19.0, git-index v0.1.0, safety bump 9 crates ([`4624725`](https://github.com/Byron/gitoxide/commit/4624725f54a34dd6b35d3632fb3516965922f60a)) - Merge branch 'index-information' ([`025f157`](https://github.com/Byron/gitoxide/commit/025f157de10a509a4b36a9aed41de80487e8c15c)) @@ -114,7 +140,7 @@ A maintenance release primarily to adapt to dependent crates. - - 10 commits contributed to the release over the course of 51 calendar days. + - 11 commits contributed to the release over the course of 51 calendar days. - 55 days passed between releases. - 2 commits where understood as [conventional](https://www.conventionalcommits.org). - 2 unique issues were worked on: [#266](https://github.com/Byron/gitoxide/issues/266), [#279](https://github.com/Byron/gitoxide/issues/279) @@ -143,6 +169,7 @@ A maintenance release primarily to adapt to dependent crates. - Release git-features v0.19.1, git-actor v0.8.0, git-config v0.1.10, git-object v0.17.0, git-diff v0.13.0, git-tempfile v1.0.4, git-chunk v0.3.0, git-traverse v0.12.0, git-pack v0.16.0, git-odb v0.26.0, git-packetline v0.12.3, git-url v0.3.5, git-transport v0.15.0, git-protocol v0.14.0, git-ref v0.11.0, git-repository v0.14.0, cargo-smart-release v0.8.0 ([`d78aab7`](https://github.com/Byron/gitoxide/commit/d78aab7b9c4b431d437ac70a0ef96263acb64e46)) - Release git-hash v0.9.1, git-features v0.19.1, git-actor v0.8.0, git-config v0.1.10, git-object v0.17.0, git-diff v0.13.0, git-tempfile v1.0.4, git-chunk v0.3.0, git-traverse v0.12.0, git-pack v0.16.0, git-odb v0.26.0, git-packetline v0.12.3, git-url v0.3.5, git-transport v0.15.0, git-protocol v0.14.0, git-ref v0.11.0, git-repository v0.14.0, cargo-smart-release v0.8.0, safety bump 4 crates ([`373cbc8`](https://github.com/Byron/gitoxide/commit/373cbc877f7ad60dac682e57c52a7b90f108ebe3)) - prepar changelogs for cargo-smart-release release ([`8900d69`](https://github.com/Byron/gitoxide/commit/8900d699226eb0995be70d66249827ce348261df)) + - Release git-bitmap v0.0.1, git-hash v0.9.0, git-features v0.19.0, git-index v0.1.0, safety bump 9 crates ([`4624725`](https://github.com/Byron/gitoxide/commit/4624725f54a34dd6b35d3632fb3516965922f60a)) - thanks clippy ([`7dd2313`](https://github.com/Byron/gitoxide/commit/7dd2313d980fe7c058319ae66d313b3097e3ae5f)) diff --git a/git-discover/CHANGELOG.md b/git-discover/CHANGELOG.md index d7c5338fc1e..46a5f193ef5 100644 --- a/git-discover/CHANGELOG.md +++ b/git-discover/CHANGELOG.md @@ -5,6 +5,36 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### New Features + + - add `DOT_GIT_DIR` constant, containing the name ".git". + +### Commit Statistics + + + + - 5 commits contributed to the release over the course of 33 calendar days. + - 39 days passed between releases. + - 1 commit where understood as [conventional](https://www.conventionalcommits.org). + - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) + +### Commit Details + + + +
view details + + * **[#331](https://github.com/Byron/gitoxide/issues/331)** + - add `DOT_GIT_DIR` constant, containing the name ".git". ([`0103501`](https://github.com/Byron/gitoxide/commit/010350180459aec41132c960ddafc7b81dd9c04d)) + - adjustments due to breaking changes in `git_path` ([`4420ae9`](https://github.com/Byron/gitoxide/commit/4420ae932d5b20a9662a6d36353a27111b5cd672)) + * **Uncategorized** + - Remove another special case on windows due to canonicalize() ([`61abb0b`](https://github.com/Byron/gitoxide/commit/61abb0b006292d2122784b032e198cc716fb7b92)) + - Use git_path::realpath in all places that allow it right now ([`229dc91`](https://github.com/Byron/gitoxide/commit/229dc917fc7d9241b85e5818260a6fbdd3a5daaa)) + - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) +
+ ## 0.2.0 (2022-06-13) @@ -22,7 +52,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 12 commits contributed to the release over the course of 16 calendar days. + - 13 commits contributed to the release over the course of 16 calendar days. - 16 days passed between releases. - 2 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -34,6 +64,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
view details * **Uncategorized** + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980)) - make fmt ([`c665aef`](https://github.com/Byron/gitoxide/commit/c665aef4270c5ee54da89ee015cc0affd6337608)) - refactor ([`ec37cb8`](https://github.com/Byron/gitoxide/commit/ec37cb8005fa272aed2e23e65adc291875b1fd68)) diff --git a/git-features/CHANGELOG.md b/git-features/CHANGELOG.md index 2ef78770a66..95649510624 100644 --- a/git-features/CHANGELOG.md +++ b/git-features/CHANGELOG.md @@ -5,6 +5,50 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### New Features + + - initialize `Time` from `now_utc` and `now_local` + Localtime support depends on some other factors now, but that + will only get better over time. + + We might have to document `unsound_local_time` at some point. + +### Changed (BREAKING) + + - remove local-time-support feature toggle. + We treat local time as default feature without a lot of fuzz, and + will eventually document that definitive support needs a compile + time switch in the compiler (`--cfg unsound_local_offset` or something). + + One day it will perish. Failure is possible anyway and we will write + code to deal with it while minimizing the amount of system time + fetches when asking for the current local time. + +### Commit Statistics + + + + - 4 commits contributed to the release over the course of 17 calendar days. + - 39 days passed between releases. + - 2 commits where understood as [conventional](https://www.conventionalcommits.org). + - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) + +### Commit Details + + + +
view details + + * **[#331](https://github.com/Byron/gitoxide/issues/331)** + - remove local-time-support feature toggle. ([`89a41bf`](https://github.com/Byron/gitoxide/commit/89a41bf2b37db29b9983b4e5492cfd67ed490b23)) + - initialize `Time` from `now_utc` and `now_local` ([`c76fde7`](https://github.com/Byron/gitoxide/commit/c76fde7de278b49ded13b655d5345e4eb8c1b134)) + - a first sketch on how identity management could look like. ([`780f14f`](https://github.com/Byron/gitoxide/commit/780f14f5c270802e51cf039639c2fbdb5ac5a85e)) + * **Uncategorized** + - git-features' walkdir: 2.3.1 -> 2.3.2 ([`41dd754`](https://github.com/Byron/gitoxide/commit/41dd7545234e6d2637d2bca5bb6d4f6d8bfc8f57)) +
+ ## 0.21.1 (2022-06-13) A maintenance release without user-facing changes. @@ -13,7 +57,7 @@ A maintenance release without user-facing changes. - - 2 commits contributed to the release over the course of 6 calendar days. + - 3 commits contributed to the release over the course of 6 calendar days. - 25 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +69,7 @@ A maintenance release without user-facing changes.
view details * **Uncategorized** + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980)) - Assure we used most recent version of crossbeam-utils ([`033f0d3`](https://github.com/Byron/gitoxide/commit/033f0d3e0015b7eead6408c775d2101eb413ffbf))
diff --git a/git-glob/CHANGELOG.md b/git-glob/CHANGELOG.md index 3bf45a8f3f1..697f66a8276 100644 --- a/git-glob/CHANGELOG.md +++ b/git-glob/CHANGELOG.md @@ -5,6 +5,29 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +This is a maintenance release with no functional changes. + +### Commit Statistics + + + + - 1 commit contributed to the release over the course of 12 calendar days. + - 64 days passed between releases. + - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) +
+ ## 0.3.0 (2022-05-18) ### New Features @@ -12,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `fmt::Display` impl for `Pattern`. This way the original pattern can be reproduced on the fly without actually storing it, saving one allocation. + - add `Default` impl for `pattern::Case` ### Changed (BREAKING) @@ -23,14 +47,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Even though it's convenient to have a base path per pattern, it's quite some duplication. + - `Pattern::matches()` is now private + It doesn't work as one would expect due to it wanting to match relative + paths only. Thus it's better to spare folks the surprise and instead + use `wildmatch()` directly. It works the same, but doesn't + have certain shortcuts which aren't needed for standard matches + anyway. ### Commit Statistics - - 17 commits contributed to the release over the course of 35 calendar days. + - 31 commits contributed to the release over the course of 35 calendar days. - 35 days passed between releases. - - 3 commits where understood as [conventional](https://www.conventionalcommits.org). + - 5 commits where understood as [conventional](https://www.conventionalcommits.org). - 2 unique issues were worked on: [#301](https://github.com/Byron/gitoxide/issues/301), [#384](https://github.com/Byron/gitoxide/issues/384) ### Thanks Clippy @@ -47,23 +77,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * **[#301](https://github.com/Byron/gitoxide/issues/301)** - update changelogs prior to release ([`84cb256`](https://github.com/Byron/gitoxide/commit/84cb25614a5fcddff297c1713eba4efbb6ff1596)) + - `fmt::Display` impl for `Pattern`. ([`455a72e`](https://github.com/Byron/gitoxide/commit/455a72eb0c01c158f43d9b9a1180886f677bad00)) + - adapt to changes in git-path ([`cc2d810`](https://github.com/Byron/gitoxide/commit/cc2d81012d107da7a61bf4de5b28342dea5083b7)) + - add `Default` impl for `pattern::Case` ([`2c88b57`](https://github.com/Byron/gitoxide/commit/2c88b575630e1b179955dad578e779aad8dd58d8)) + - cleanup ([`1ab4705`](https://github.com/Byron/gitoxide/commit/1ab470589450ecda45826c38417616f227e3031b)) + - Allow basename matches to work like before ([`4f6cefc`](https://github.com/Byron/gitoxide/commit/4f6cefc96bea5f116eb26a9de8095271fd0f58e2)) + - adjust baseline to only handle patterns that work without a dir stack ([`fb65a39`](https://github.com/Byron/gitoxide/commit/fb65a39e1826c331545b7141c0741904ed5bb1a4)) + - discover an entirely new class of exclude matches… ([`f8dd5ce`](https://github.com/Byron/gitoxide/commit/f8dd5ce8ce27cd24b9d81795dcf01ce03efe802d)) + - Basic match group pattern matching ([`cc1312d`](https://github.com/Byron/gitoxide/commit/cc1312dc06d1dccfa2e3cf0ae134affa9a3fa947)) + - `Pattern::matches()` is now private ([`568f013`](https://github.com/Byron/gitoxide/commit/568f013e762423fc54a8fb1daed1e7b59c1dc0f0)) - push base path handling to the caller ([`e4b57b1`](https://github.com/Byron/gitoxide/commit/e4b57b197884bc981b8e3c9ee8c7b5349afa594b)) - A slightly ugly way of not adjusting input patterns too much ([`3912ee6`](https://github.com/Byron/gitoxide/commit/3912ee66b6117681331df5e6e0f8345335728bde)) - Adjustments to support lower MSRV ([`16a0973`](https://github.com/Byron/gitoxide/commit/16a09737f0e81654cc7a5bbc9043385528524ca5)) - a failing test to show that the absolute pattern handling isn't quite there yet ([`74c89eb`](https://github.com/Byron/gitoxide/commit/74c89ebbd235e8f5464e0665cc7bc7a930a8eb76)) + - remove `base_path` field from `Pattern` ([`f76a426`](https://github.com/Byron/gitoxide/commit/f76a426833530c7a7e787487cfceaba2c80b21ac)) + - make fmt ([`5fc5459`](https://github.com/Byron/gitoxide/commit/5fc5459b17b623726f99846c432a70106464e970)) - cleanup tests ([`16570ef`](https://github.com/Byron/gitoxide/commit/16570ef96785c62eb813d4613df097aca3aa0d8f)) - case-insensitive tests for baseline path matching ([`bc928f9`](https://github.com/Byron/gitoxide/commit/bc928f9c00b5f00527a122c8bf847278e90ffb04)) - invert meaning of `wildcard::Mode::SLASH_IS_LITERAL` ([`8fd9f24`](https://github.com/Byron/gitoxide/commit/8fd9f24e2f751292a99b4f92cc47df67e17ab537)) - - `fmt::Display` impl for `Pattern`. ([`455a72e`](https://github.com/Byron/gitoxide/commit/455a72eb0c01c158f43d9b9a1180886f677bad00)) - - remove `base_path` field from `Pattern` ([`f76a426`](https://github.com/Byron/gitoxide/commit/f76a426833530c7a7e787487cfceaba2c80b21ac)) - make glob tests work on windows for now… ([`29738ed`](https://github.com/Byron/gitoxide/commit/29738edc56da6dbb9b853ac8f7482672eafd5050)) - See if being less pedantic yields the correct results ([`18953e4`](https://github.com/Byron/gitoxide/commit/18953e4c367ef1d3c2b28a0b027acc715af6372f)) * **[#384](https://github.com/Byron/gitoxide/issues/384)** - No need to isolate archives by crate name ([`19d46f3`](https://github.com/Byron/gitoxide/commit/19d46f35440419b9911b6e2bca2cfc975865dce9)) - add archive files via git-lfs ([`7202a1c`](https://github.com/Byron/gitoxide/commit/7202a1c4734ad904c026ee3e4e2143c0461d51a2)) + - make sure existing files aren't written into ([`9b5a8a2`](https://github.com/Byron/gitoxide/commit/9b5a8a243d49b6567d1db31050d3bf3123dd54d3)) + - auto-set commit.gpgsign=false when executing git ([`c23feb6`](https://github.com/Byron/gitoxide/commit/c23feb64ad157180cfba8a11c882b829733ea8f6)) * **Uncategorized** + - Release git-hash v0.9.4, git-features v0.21.0, git-actor v0.10.0, git-glob v0.3.0, git-path v0.1.1, git-attributes v0.1.0, git-sec v0.1.0, git-config v0.3.0, git-credentials v0.1.0, git-validate v0.5.4, git-object v0.19.0, git-diff v0.16.0, git-lock v2.1.0, git-ref v0.13.0, git-discover v0.1.0, git-index v0.3.0, git-mailmap v0.2.0, git-traverse v0.15.0, git-pack v0.19.0, git-odb v0.29.0, git-packetline v0.12.5, git-url v0.5.0, git-transport v0.17.0, git-protocol v0.16.0, git-revision v0.2.0, git-worktree v0.2.0, git-repository v0.17.0, safety bump 20 crates ([`654cf39`](https://github.com/Byron/gitoxide/commit/654cf39c92d5aa4c8d542a6cadf13d4acef6a78e)) - make fmt ([`251b6df`](https://github.com/Byron/gitoxide/commit/251b6df5dbdda24b7bdc452085f808f3acef69d8)) + - Merge branch 'git_includeif' of https://github.com/svetli-n/gitoxide into svetli-n-git_includeif ([`0e01da7`](https://github.com/Byron/gitoxide/commit/0e01da74dffedaa46190db6a7b60a2aaff190d81)) - thanks clippy ([`5bf6b52`](https://github.com/Byron/gitoxide/commit/5bf6b52cd51bef19079e87230e5ac463f8f881c0)) + - Merge branch 'main' into worktree-stack ([`8674c11`](https://github.com/Byron/gitoxide/commit/8674c11973e5282d087e35a71c70e418b6cc75be)) - thanks clippy ([`74f6420`](https://github.com/Byron/gitoxide/commit/74f64202dfc6d9b34228595e260014708ec388e3))
@@ -80,10 +124,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 55 commits contributed to the release over the course of 6 calendar days. + - 51 commits contributed to the release over the course of 6 calendar days. - 6 days passed between releases. - 1 commit where understood as [conventional](https://www.conventionalcommits.org). - - 2 unique issues were worked on: [#301](https://github.com/Byron/gitoxide/issues/301), [#384](https://github.com/Byron/gitoxide/issues/384) + - 1 unique issue was worked on: [#301](https://github.com/Byron/gitoxide/issues/301) ### Thanks Clippy @@ -99,7 +143,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * **[#301](https://github.com/Byron/gitoxide/issues/301)** - `parse()` returns a `Pattern`. ([`6ce3611`](https://github.com/Byron/gitoxide/commit/6ce3611891d4b60c86055bf749a1b4060ee2c3e1)) - - make fmt ([`5fc5459`](https://github.com/Byron/gitoxide/commit/5fc5459b17b623726f99846c432a70106464e970)) - docs for git-glob ([`8f4969f`](https://github.com/Byron/gitoxide/commit/8f4969fe7c2e3f3bb38275d5e4ccb08d0bde02bb)) - all wildmatch tests succeed ([`d3a7349`](https://github.com/Byron/gitoxide/commit/d3a7349b707911670f17a92a0f82681544ebc769)) - add all character classes sans some of the more obscure ones ([`538d41d`](https://github.com/Byron/gitoxide/commit/538d41d51d7cdc472b2a712823a5a69810f75015)) @@ -122,7 +165,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - question mark support ([`e83c8df`](https://github.com/Byron/gitoxide/commit/e83c8df03e801e00571f5934331e004af9774c7f)) - very basic beginnings of wildmatch ([`334c624`](https://github.com/Byron/gitoxide/commit/334c62459dbb6763a46647a64129f89e27b5781b)) - fix logic in wildmatch tests; validate feasibility of all test cases ([`1336bc9`](https://github.com/Byron/gitoxide/commit/1336bc938cc43e3a2f9e47af64f2c9933c9fc961)) - - adapt to changes in git-path ([`cc2d810`](https://github.com/Byron/gitoxide/commit/cc2d81012d107da7a61bf4de5b28342dea5083b7)) - test corpus for wildcard matches ([`bd8f95f`](https://github.com/Byron/gitoxide/commit/bd8f95f757e45b3cf8523d3e11503f4571461abf)) - frame for wildmatch function and its tests ([`04ca834`](https://github.com/Byron/gitoxide/commit/04ca8349e326f7b7505a9ea49a401565259f21dc)) - more tests for early exit in case no-wildcard prefix doesn't match ([`1ff348c`](https://github.com/Byron/gitoxide/commit/1ff348c4f09839569dcd8bb93699e7004fa59d4a)) @@ -147,9 +189,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - bring in all ~140 tests for git pattern matching, git-ignore styile ([`f9ab830`](https://github.com/Byron/gitoxide/commit/f9ab830df2920387c1cffec048be3a4089f4aa40)) - refactor ([`dbe7305`](https://github.com/Byron/gitoxide/commit/dbe7305d371c7dad02d8888492b60b882b418a46)) - refactor ([`8a54341`](https://github.com/Byron/gitoxide/commit/8a543410e10326ce506b8a7ba65e662641835849)) - * **[#384](https://github.com/Byron/gitoxide/issues/384)** - - make sure existing files aren't written into ([`9b5a8a2`](https://github.com/Byron/gitoxide/commit/9b5a8a243d49b6567d1db31050d3bf3123dd54d3)) - - auto-set commit.gpgsign=false when executing git ([`c23feb6`](https://github.com/Byron/gitoxide/commit/c23feb64ad157180cfba8a11c882b829733ea8f6)) * **Uncategorized** - Release git-glob v0.2.0, safety bump 3 crates ([`ab6bed7`](https://github.com/Byron/gitoxide/commit/ab6bed7e2aa19eeb9990441741008c430f373708)) - thanks clippy ([`b1a6100`](https://github.com/Byron/gitoxide/commit/b1a610029e1b40600f90194ce986155238f58101)) @@ -178,8 +217,8 @@ Initial release with pattern parsing functionality. - - 11 commits contributed to the release. - - 2 commits where understood as [conventional](https://www.conventionalcommits.org). + - 3 commits contributed to the release. + - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#301](https://github.com/Byron/gitoxide/issues/301) ### Commit Details @@ -191,15 +230,7 @@ Initial release with pattern parsing functionality. * **[#301](https://github.com/Byron/gitoxide/issues/301)** - prepare changelog prior to release ([`2794bb2`](https://github.com/Byron/gitoxide/commit/2794bb2f6bd80cccba508fa9f251609499167646)) - Add git-glob crate with pattern matching parsing from git-attributes::ignore ([`b3efc94`](https://github.com/Byron/gitoxide/commit/b3efc94134a32018db1d6a2d7f8cc397c4371999)) - - add `Default` impl for `pattern::Case` ([`2c88b57`](https://github.com/Byron/gitoxide/commit/2c88b575630e1b179955dad578e779aad8dd58d8)) - - cleanup ([`1ab4705`](https://github.com/Byron/gitoxide/commit/1ab470589450ecda45826c38417616f227e3031b)) - - Allow basename matches to work like before ([`4f6cefc`](https://github.com/Byron/gitoxide/commit/4f6cefc96bea5f116eb26a9de8095271fd0f58e2)) - - adjust baseline to only handle patterns that work without a dir stack ([`fb65a39`](https://github.com/Byron/gitoxide/commit/fb65a39e1826c331545b7141c0741904ed5bb1a4)) - - discover an entirely new class of exclude matches… ([`f8dd5ce`](https://github.com/Byron/gitoxide/commit/f8dd5ce8ce27cd24b9d81795dcf01ce03efe802d)) - - Basic match group pattern matching ([`cc1312d`](https://github.com/Byron/gitoxide/commit/cc1312dc06d1dccfa2e3cf0ae134affa9a3fa947)) - - `Pattern::matches()` is now private ([`568f013`](https://github.com/Byron/gitoxide/commit/568f013e762423fc54a8fb1daed1e7b59c1dc0f0)) * **Uncategorized** - Release git-glob v0.1.0 ([`0f66c5d`](https://github.com/Byron/gitoxide/commit/0f66c5d56bd3f0febff881065911638f22e71158)) - - Merge branch 'main' into worktree-stack ([`8674c11`](https://github.com/Byron/gitoxide/commit/8674c11973e5282d087e35a71c70e418b6cc75be)) diff --git a/git-hash/CHANGELOG.md b/git-hash/CHANGELOG.md index af33a0dec45..8c63b5387f6 100644 --- a/git-hash/CHANGELOG.md +++ b/git-hash/CHANGELOG.md @@ -5,6 +5,29 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +This is a maintenance release with no functional changes. + +### Commit Statistics + + + + - 1 commit contributed to the release over the course of 12 calendar days. + - 39 days passed between releases. + - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) +
+ ## 0.9.5 (2022-06-13) ### New Features @@ -17,7 +40,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 2 commits contributed to the release over the course of 5 calendar days. + - 3 commits contributed to the release over the course of 5 calendar days. - 25 days passed between releases. - 1 commit where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#427](https://github.com/Byron/gitoxide/issues/427) @@ -31,6 +54,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * **[#427](https://github.com/Byron/gitoxide/issues/427)** - expose `Prefix::MIN_HEX_LEN`. ([`652f228`](https://github.com/Byron/gitoxide/commit/652f228bb7ec880856d4e6ee1c171b0b85a735e2)) * **Uncategorized** + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980)) diff --git a/git-index/CHANGELOG.md b/git-index/CHANGELOG.md index 597fbc2ff85..21fe3dc6bba 100644 --- a/git-index/CHANGELOG.md +++ b/git-index/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +This is a maintenance release with no functional changes. + ## 0.3.0 (2022-05-18) ### New Features @@ -21,7 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 15 commits contributed to the release over the course of 23 calendar days. + - 17 commits contributed to the release over the course of 34 calendar days. - 45 days passed between releases. - 2 commits where understood as [conventional](https://www.conventionalcommits.org). - 3 unique issues were worked on: [#298](https://github.com/Byron/gitoxide/issues/298), [#301](https://github.com/Byron/gitoxide/issues/301), [#384](https://github.com/Byron/gitoxide/issues/384) @@ -36,13 +40,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - upgrade git-index->atoi to 1.0 ([`728dd65`](https://github.com/Byron/gitoxide/commit/728dd6501b86b12e1d0237256f94059a7ead14a9)) * **[#301](https://github.com/Byron/gitoxide/issues/301)** - update changelogs prior to release ([`84cb256`](https://github.com/Byron/gitoxide/commit/84cb25614a5fcddff297c1713eba4efbb6ff1596)) - - Adjustments to support lower MSRV ([`16a0973`](https://github.com/Byron/gitoxide/commit/16a09737f0e81654cc7a5bbc9043385528524ca5)) + - Differentiate between owned and ref'ed path storage ([`c71b2bb`](https://github.com/Byron/gitoxide/commit/c71b2bb944c3066e7e44fbdd8a2e511a5a5d944a)) - `State::path_backing()`. ([`8ab219a`](https://github.com/Byron/gitoxide/commit/8ab219ac47ca67f2478b8715d7820fd6171c0db2)) - sketch `open_index()` on `Worktree`, but… ([`ff76261`](https://github.com/Byron/gitoxide/commit/ff76261f568f6b717a93b1f2dcf5d8e8b63acfca)) - - Differentiate between owned and ref'ed path storage ([`c71b2bb`](https://github.com/Byron/gitoxide/commit/c71b2bb944c3066e7e44fbdd8a2e511a5a5d944a)) - support for separating lifetimes of entries and path-backing ([`645ed50`](https://github.com/Byron/gitoxide/commit/645ed50dc2ae5ded2df0c09daf4fe366b90cf47e)) - An attempt to build a lookup table of attribute files, but… ([`9841efb`](https://github.com/Byron/gitoxide/commit/9841efb566748dae6c79c5990c4fd1ecbc468aef)) - refactor ([`475aa6a`](https://github.com/Byron/gitoxide/commit/475aa6a3e08f63df627a0988cd16c20494960c79)) + - Adjustments to support lower MSRV ([`16a0973`](https://github.com/Byron/gitoxide/commit/16a09737f0e81654cc7a5bbc9043385528524ca5)) * **[#384](https://github.com/Byron/gitoxide/issues/384)** - prevent line-ending conversions for shell scripts on windows ([`96bb4d4`](https://github.com/Byron/gitoxide/commit/96bb4d460db420e18dfd0f925109c740e971820d)) - No need to isolate archives by crate name ([`19d46f3`](https://github.com/Byron/gitoxide/commit/19d46f35440419b9911b6e2bca2cfc975865dce9)) @@ -50,7 +54,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Assure we don't pick up unnecessary files during publishing ([`545b2d5`](https://github.com/Byron/gitoxide/commit/545b2d5121ba64efaee7564d5191cec37661efd7)) - auto-set commit.gpgsign=false when executing git ([`c23feb6`](https://github.com/Byron/gitoxide/commit/c23feb64ad157180cfba8a11c882b829733ea8f6)) * **Uncategorized** + - Release git-ref v0.13.0, git-discover v0.1.0, git-index v0.3.0, git-mailmap v0.2.0, git-traverse v0.15.0, git-pack v0.19.0, git-odb v0.29.0, git-packetline v0.12.5, git-url v0.5.0, git-transport v0.17.0, git-protocol v0.16.0, git-revision v0.2.0, git-worktree v0.2.0, git-repository v0.17.0 ([`349c590`](https://github.com/Byron/gitoxide/commit/349c5904b0dac350838a896759d51576b66880a7)) - Release git-hash v0.9.4, git-features v0.21.0, git-actor v0.10.0, git-glob v0.3.0, git-path v0.1.1, git-attributes v0.1.0, git-sec v0.1.0, git-config v0.3.0, git-credentials v0.1.0, git-validate v0.5.4, git-object v0.19.0, git-diff v0.16.0, git-lock v2.1.0, git-ref v0.13.0, git-discover v0.1.0, git-index v0.3.0, git-mailmap v0.2.0, git-traverse v0.15.0, git-pack v0.19.0, git-odb v0.29.0, git-packetline v0.12.5, git-url v0.5.0, git-transport v0.17.0, git-protocol v0.16.0, git-revision v0.2.0, git-worktree v0.2.0, git-repository v0.17.0, safety bump 20 crates ([`654cf39`](https://github.com/Byron/gitoxide/commit/654cf39c92d5aa4c8d542a6cadf13d4acef6a78e)) + - Merge branch 'git_includeif' of https://github.com/svetli-n/gitoxide into svetli-n-git_includeif ([`0e01da7`](https://github.com/Byron/gitoxide/commit/0e01da74dffedaa46190db6a7b60a2aaff190d81)) ## 0.2.0 (2022-04-03) @@ -67,7 +73,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 17 commits contributed to the release over the course of 73 calendar days. + - 43 commits contributed to the release over the course of 73 calendar days. - 73 days passed between releases. - 2 commits where understood as [conventional](https://www.conventionalcommits.org). - 5 unique issues were worked on: [#293](https://github.com/Byron/gitoxide/issues/293), [#298](https://github.com/Byron/gitoxide/issues/298), [#301](https://github.com/Byron/gitoxide/issues/301), [#329](https://github.com/Byron/gitoxide/issues/329), [#333](https://github.com/Byron/gitoxide/issues/333) @@ -80,10 +86,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * **[#293](https://github.com/Byron/gitoxide/issues/293)** - Remove performance bottleneck when reading TREE extension ([`50411c8`](https://github.com/Byron/gitoxide/commit/50411c8031e3103bb84da46b94b8faf92c597df9)) + - Assert store tree cache matches actual source objects ([`b062bec`](https://github.com/Byron/gitoxide/commit/b062becd01058f5c519538f89d9d8fec8342114d)) + - Sketch a surprisingly difficult way of loading objects in verify_extension() ([`3baeab4`](https://github.com/Byron/gitoxide/commit/3baeab4ab216132536d5c182b3e316ce65095085)) + - Properly sort cache tree entries upon load ([`421d1ba`](https://github.com/Byron/gitoxide/commit/421d1ba853a75560f59cb0ce2b353087db0dff56)) + - tree-ordering validation shows something wrong ([`5fb2857`](https://github.com/Byron/gitoxide/commit/5fb2857e9f970c150f5221ca721506e7bc046ef4)) + - First stab at tree verification ([`f928350`](https://github.com/Byron/gitoxide/commit/f9283500e8316ab949fc0ff9c2fc13a498380873)) + - Verify entry order ([`2d101eb`](https://github.com/Byron/gitoxide/commit/2d101ebbd36e000ffec0e965012081fec2e234f7)) + - refactor ([`017e915`](https://github.com/Byron/gitoxide/commit/017e9153aaaa1cdd6788d9f61ff1ffbd61bc1b30)) + - basic index file checksum verification ([`c644565`](https://github.com/Byron/gitoxide/commit/c644565d5b8d9ae3991ee82a7ffa5e21a2705f91)) + - At least check for the presence of extensions ([`28c056c`](https://github.com/Byron/gitoxide/commit/28c056c6d2bbfb16a826238fd6879adecbeb1171)) + - thorough checking of Tree extension ([`d1063aa`](https://github.com/Byron/gitoxide/commit/d1063aa20bfcefb064ba08089f095baef1299dcd)) + - refactor ([`d0725bd`](https://github.com/Byron/gitoxide/commit/d0725bd40f0b9af0e0af34ffe77c2d8406c6d24c)) + - Fix tree-extension loading for empty trees ([`2e13989`](https://github.com/Byron/gitoxide/commit/2e1398985edfaf9e62ff5643cf4756d9d9717862)) + - Now we are able to load indices correctly ([`762efa3`](https://github.com/Byron/gitoxide/commit/762efa3f5e4ebda4d3bcc6a9bba43c6cdb407937)) + - Add breaking test with conflicting file in index ([`791a9f8`](https://github.com/Byron/gitoxide/commit/791a9f84ff8871c7beb0e2100a4dcba0e9384737)) + - Print extension names instead of count ([`1cc07e0`](https://github.com/Byron/gitoxide/commit/1cc07e0cfdae74e388abb29d7acb9c6f643278b4)) + - Print basic index information, including the tree extension ([`9277cf8`](https://github.com/Byron/gitoxide/commit/9277cf877e1f2276dcad1efdeb97e0e3d96ed3f0)) - lower rust edition to 2018 ([`0b1543d`](https://github.com/Byron/gitoxide/commit/0b1543d481337ed51dcfdeb907af21f0bc98dcb9)) - lower MSRV to 1.52 ([`c2cc939`](https://github.com/Byron/gitoxide/commit/c2cc939d131a278c177c5f44d3b26127c65bd352)) * **[#298](https://github.com/Byron/gitoxide/issues/298)** - Use hash_hasher based hash state for better keys/less collisions ([`814de07`](https://github.com/Byron/gitoxide/commit/814de079f4226f42efa49ad334a348bce67184e4)) + - Also print stage of entries ([`003515f`](https://github.com/Byron/gitoxide/commit/003515f3c90a49fbe9db9b84987233486711beb8)) + - simple printing of basic entry information ([`329538b`](https://github.com/Byron/gitoxide/commit/329538b9c3f44bb8e70a4567ba90dc3b594c2dfc)) * **[#301](https://github.com/Byron/gitoxide/issues/301)** - basic version of index checkout via command-line ([`f23b8d2`](https://github.com/Byron/gitoxide/commit/f23b8d2f1c4b767d337ec51888afaa8b3719798c)) - document-features support for git-index and git-worktree ([`1367cf5`](https://github.com/Byron/gitoxide/commit/1367cf5bc5908639e67e12f78f57835c5fd68a90)) @@ -101,6 +125,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Merge branch 'short-id' ([`5849d5b`](https://github.com/Byron/gitoxide/commit/5849d5b326b83f98a16cf1d956c720c7f0fd4445)) - Merge branch 'AP2008-implement-worktree' ([`f32c669`](https://github.com/Byron/gitoxide/commit/f32c669bc519d59a1f1d90d61cc48a422c86aede)) - Implemented git-worktree ([`4177d72`](https://github.com/Byron/gitoxide/commit/4177d72c95bd94cf6a49e917dc21918044e8250b)) + - Release git-hash v0.9.2, git-object v0.17.1, git-pack v0.16.1 ([`0db19b8`](https://github.com/Byron/gitoxide/commit/0db19b8deaf11a4d4cbc03fa3ae40eea104bc302)) + - refactor ([`afdeca1`](https://github.com/Byron/gitoxide/commit/afdeca1e5ec119607c5d1f5ccec5d216fc7d5261)) + - thanks clippy ([`2f25bf1`](https://github.com/Byron/gitoxide/commit/2f25bf1ebf44aef8c4886eaefb3e87836d535f61)) + - thanks clippy ([`d721019`](https://github.com/Byron/gitoxide/commit/d721019aebe5b01ddb15c9b1aab279647069452a)) + - Merge branch 'index-information' ([`025f157`](https://github.com/Byron/gitoxide/commit/025f157de10a509a4b36a9aed41de80487e8c15c)) + - Release git-hash v0.9.1, git-features v0.19.1, git-actor v0.8.0, git-config v0.1.10, git-object v0.17.0, git-diff v0.13.0, git-tempfile v1.0.4, git-chunk v0.3.0, git-traverse v0.12.0, git-pack v0.16.0, git-odb v0.26.0, git-packetline v0.12.3, git-url v0.3.5, git-transport v0.15.0, git-protocol v0.14.0, git-ref v0.11.0, git-repository v0.14.0, cargo-smart-release v0.8.0, safety bump 4 crates ([`373cbc8`](https://github.com/Byron/gitoxide/commit/373cbc877f7ad60dac682e57c52a7b90f108ebe3)) + - upgrade to tui 0.17 and prodash 18 ([`eba101a`](https://github.com/Byron/gitoxide/commit/eba101a576ecb7bc0f63357d0dd81eb817b94be4)) + - dependency update ([`ca59e44`](https://github.com/Byron/gitoxide/commit/ca59e448061698dd559db43123fe676ae61129a0)) ## 0.1.0 (2022-01-19) @@ -113,16 +145,16 @@ certain extensions are present. - - 98 commits contributed to the release over the course of 490 calendar days. + - 72 commits contributed to the release over the course of 490 calendar days. - 509 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - - 2 unique issues were worked on: [#293](https://github.com/Byron/gitoxide/issues/293), [#298](https://github.com/Byron/gitoxide/issues/298) + - 1 unique issue was worked on: [#293](https://github.com/Byron/gitoxide/issues/293) ### Thanks Clippy -[Clippy](https://github.com/rust-lang/rust-clippy) helped 7 times to make code idiomatic. +[Clippy](https://github.com/rust-lang/rust-clippy) helped 5 times to make code idiomatic. ### Commit Details @@ -139,34 +171,18 @@ certain extensions are present. - FSMN V2 decoding ([`04279bf`](https://github.com/Byron/gitoxide/commit/04279bffc8bd43abe85f559634436be789782829)) - Failing test for fs-monitor V1 ([`625b89a`](https://github.com/Byron/gitoxide/commit/625b89a7786fe9de29c9ad2ca41a734174f53128)) - Validate UNTR with exclude-file oids ([`20ebb81`](https://github.com/Byron/gitoxide/commit/20ebb81c9ece6c2d693edd6243eaa730fa7cf44c)) - - Assert store tree cache matches actual source objects ([`b062bec`](https://github.com/Byron/gitoxide/commit/b062becd01058f5c519538f89d9d8fec8342114d)) - read remaining pieces of UNTR ([`9d9cc95`](https://github.com/Byron/gitoxide/commit/9d9cc95a24d86cf5f66f1746c09ece032640a892)) - - Sketch a surprisingly difficult way of loading objects in verify_extension() ([`3baeab4`](https://github.com/Byron/gitoxide/commit/3baeab4ab216132536d5c182b3e316ce65095085)) - Make stat parsing more general/reusable ([`c41b933`](https://github.com/Byron/gitoxide/commit/c41b933d14f2e4538928e4fbd682e1017702e69c)) - refactor ([`a1dc8de`](https://github.com/Byron/gitoxide/commit/a1dc8dedc5d9e1712295131d2332c21f3df4ac45)) - simplify UNTR directory indexing ([`7857d08`](https://github.com/Byron/gitoxide/commit/7857d08a25eac1c7d4a91f04eb80a83ec5677d1b)) - - Properly sort cache tree entries upon load ([`421d1ba`](https://github.com/Byron/gitoxide/commit/421d1ba853a75560f59cb0ce2b353087db0dff56)) - flatten UNTR directory list for later access via bitmaps ([`2e39184`](https://github.com/Byron/gitoxide/commit/2e391841af52f88b7a0472179e5dda89cc6c9808)) - - tree-ordering validation shows something wrong ([`5fb2857`](https://github.com/Byron/gitoxide/commit/5fb2857e9f970c150f5221ca721506e7bc046ef4)) - read UNTR directory blocks and bitmaps ([`59f46fe`](https://github.com/Byron/gitoxide/commit/59f46fe134e8619f501c79da4290cadd5548751c)) - - First stab at tree verification ([`f928350`](https://github.com/Byron/gitoxide/commit/f9283500e8316ab949fc0ff9c2fc13a498380873)) - First portion of reading the untracked cache ([`ed2fe5d`](https://github.com/Byron/gitoxide/commit/ed2fe5dbfbcf79ffdcdceed90f6321792cff076d)) - failing test for UNTR extension ([`223f2cc`](https://github.com/Byron/gitoxide/commit/223f2cc1c88f76dc75ca6706f1f61514ab52e496)) - - Verify entry order ([`2d101eb`](https://github.com/Byron/gitoxide/commit/2d101ebbd36e000ffec0e965012081fec2e234f7)) - - Now we are able to load indices correctly ([`762efa3`](https://github.com/Byron/gitoxide/commit/762efa3f5e4ebda4d3bcc6a9bba43c6cdb407937)) - Add UNTR extension fixture ([`3c7ba24`](https://github.com/Byron/gitoxide/commit/3c7ba247a3fdab114d9d549de50d6143c7fcce0a)) - - refactor ([`017e915`](https://github.com/Byron/gitoxide/commit/017e9153aaaa1cdd6788d9f61ff1ffbd61bc1b30)) - - Add breaking test with conflicting file in index ([`791a9f8`](https://github.com/Byron/gitoxide/commit/791a9f84ff8871c7beb0e2100a4dcba0e9384737)) - - basic index file checksum verification ([`c644565`](https://github.com/Byron/gitoxide/commit/c644565d5b8d9ae3991ee82a7ffa5e21a2705f91)) - REUC reading works ([`29c1af9`](https://github.com/Byron/gitoxide/commit/29c1af9b2d7b9879a806fc84cfc89ed6c0d7f083)) - - At least check for the presence of extensions ([`28c056c`](https://github.com/Byron/gitoxide/commit/28c056c6d2bbfb16a826238fd6879adecbeb1171)) - - Print extension names instead of count ([`1cc07e0`](https://github.com/Byron/gitoxide/commit/1cc07e0cfdae74e388abb29d7acb9c6f643278b4)) - frame and test for REUC exstension ([`229cabe`](https://github.com/Byron/gitoxide/commit/229cabe8de9e1bd244d56d24327b05e3d80dfb6e)) - - thorough checking of Tree extension ([`d1063aa`](https://github.com/Byron/gitoxide/commit/d1063aa20bfcefb064ba08089f095baef1299dcd)) - add git index with REUC exstension ([`8359fdb`](https://github.com/Byron/gitoxide/commit/8359fdb6c49b263bc7ac2f3105254b83eac47638)) - - refactor ([`d0725bd`](https://github.com/Byron/gitoxide/commit/d0725bd40f0b9af0e0af34ffe77c2d8406c6d24c)) - - Fix tree-extension loading for empty trees ([`2e13989`](https://github.com/Byron/gitoxide/commit/2e1398985edfaf9e62ff5643cf4756d9d9717862)) - - Print basic index information, including the tree extension ([`9277cf8`](https://github.com/Byron/gitoxide/commit/9277cf877e1f2276dcad1efdeb97e0e3d96ed3f0)) - Support for 'sdir' extension ([`a38c3b8`](https://github.com/Byron/gitoxide/commit/a38c3b889cfbf1447c87d489d3eb9902e757aa4b)) - Turn git-bitmap Array into Vec, as it will be able to adjust its size ([`9e99e01`](https://github.com/Byron/gitoxide/commit/9e99e016c17f0d5bcd2ab645261dfac58cb48be5)) - first stab at decoding ewah bitmaps ([`353a53c`](https://github.com/Byron/gitoxide/commit/353a53ccab5af990e7c384b74b38e5429417d449)) @@ -210,21 +226,10 @@ certain extensions are present. - The realization that FileBuffer really shouldn't be used anymore ([`b481f13`](https://github.com/Byron/gitoxide/commit/b481f136c4084b8839ebecb604dea5aa30d3a44e)) - base setup for index testing ([`aa60fdf`](https://github.com/Byron/gitoxide/commit/aa60fdf3d86e08877c88f9e4973f546642ed1370)) - notes on how test indices have been created ([`3040857`](https://github.com/Byron/gitoxide/commit/3040857ec4d2e0557b4920eaa77ddc4292d9adae)) - * **[#298](https://github.com/Byron/gitoxide/issues/298)** - - Also print stage of entries ([`003515f`](https://github.com/Byron/gitoxide/commit/003515f3c90a49fbe9db9b84987233486711beb8)) - - simple printing of basic entry information ([`329538b`](https://github.com/Byron/gitoxide/commit/329538b9c3f44bb8e70a4567ba90dc3b594c2dfc)) * **Uncategorized** - Release git-bitmap v0.0.1, git-hash v0.9.0, git-features v0.19.0, git-index v0.1.0, safety bump 9 crates ([`4624725`](https://github.com/Byron/gitoxide/commit/4624725f54a34dd6b35d3632fb3516965922f60a)) - - Release git-hash v0.9.2, git-object v0.17.1, git-pack v0.16.1 ([`0db19b8`](https://github.com/Byron/gitoxide/commit/0db19b8deaf11a4d4cbc03fa3ae40eea104bc302)) - thanks clippy ([`09df2bc`](https://github.com/Byron/gitoxide/commit/09df2bcb4b45f72742d139530907be8aa4dc36f8)) - - refactor ([`afdeca1`](https://github.com/Byron/gitoxide/commit/afdeca1e5ec119607c5d1f5ccec5d216fc7d5261)) - - thanks clippy ([`2f25bf1`](https://github.com/Byron/gitoxide/commit/2f25bf1ebf44aef8c4886eaefb3e87836d535f61)) - thanks clippy ([`93c3d23`](https://github.com/Byron/gitoxide/commit/93c3d23d255a02d65b5404c2f62f96d94e36f33d)) - - thanks clippy ([`d721019`](https://github.com/Byron/gitoxide/commit/d721019aebe5b01ddb15c9b1aab279647069452a)) - - Merge branch 'index-information' ([`025f157`](https://github.com/Byron/gitoxide/commit/025f157de10a509a4b36a9aed41de80487e8c15c)) - - Release git-hash v0.9.1, git-features v0.19.1, git-actor v0.8.0, git-config v0.1.10, git-object v0.17.0, git-diff v0.13.0, git-tempfile v1.0.4, git-chunk v0.3.0, git-traverse v0.12.0, git-pack v0.16.0, git-odb v0.26.0, git-packetline v0.12.3, git-url v0.3.5, git-transport v0.15.0, git-protocol v0.14.0, git-ref v0.11.0, git-repository v0.14.0, cargo-smart-release v0.8.0, safety bump 4 crates ([`373cbc8`](https://github.com/Byron/gitoxide/commit/373cbc877f7ad60dac682e57c52a7b90f108ebe3)) - - upgrade to tui 0.17 and prodash 18 ([`eba101a`](https://github.com/Byron/gitoxide/commit/eba101a576ecb7bc0f63357d0dd81eb817b94be4)) - - dependency update ([`ca59e44`](https://github.com/Byron/gitoxide/commit/ca59e448061698dd559db43123fe676ae61129a0)) - Fix index without extension test & thanks clippy ([`066464d`](https://github.com/Byron/gitoxide/commit/066464d2ad2833012fa196fe41e93a54ab05457f)) - thanks clippy ([`f477032`](https://github.com/Byron/gitoxide/commit/f47703256fe6a5c68ed3af6705bcdf01262500d6)) - thanks clippy ([`5526020`](https://github.com/Byron/gitoxide/commit/552602074a99dc536624f0c6295e807caf32f58b)) diff --git a/git-mailmap/CHANGELOG.md b/git-mailmap/CHANGELOG.md index 7d9b0e85208..78b60c16a9e 100644 --- a/git-mailmap/CHANGELOG.md +++ b/git-mailmap/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +This is a maintenance release with no functional changes. + ## 0.2.0 (2022-05-18) A maintenance release without user-facing changes. @@ -13,7 +17,7 @@ A maintenance release without user-facing changes. - - 2 commits contributed to the release. + - 3 commits contributed to the release. - 45 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#301](https://github.com/Byron/gitoxide/issues/301) @@ -27,6 +31,7 @@ A maintenance release without user-facing changes. * **[#301](https://github.com/Byron/gitoxide/issues/301)** - update changelogs prior to release ([`84cb256`](https://github.com/Byron/gitoxide/commit/84cb25614a5fcddff297c1713eba4efbb6ff1596)) * **Uncategorized** + - Release git-ref v0.13.0, git-discover v0.1.0, git-index v0.3.0, git-mailmap v0.2.0, git-traverse v0.15.0, git-pack v0.19.0, git-odb v0.29.0, git-packetline v0.12.5, git-url v0.5.0, git-transport v0.17.0, git-protocol v0.16.0, git-revision v0.2.0, git-worktree v0.2.0, git-repository v0.17.0 ([`349c590`](https://github.com/Byron/gitoxide/commit/349c5904b0dac350838a896759d51576b66880a7)) - Release git-hash v0.9.4, git-features v0.21.0, git-actor v0.10.0, git-glob v0.3.0, git-path v0.1.1, git-attributes v0.1.0, git-sec v0.1.0, git-config v0.3.0, git-credentials v0.1.0, git-validate v0.5.4, git-object v0.19.0, git-diff v0.16.0, git-lock v2.1.0, git-ref v0.13.0, git-discover v0.1.0, git-index v0.3.0, git-mailmap v0.2.0, git-traverse v0.15.0, git-pack v0.19.0, git-odb v0.29.0, git-packetline v0.12.5, git-url v0.5.0, git-transport v0.17.0, git-protocol v0.16.0, git-revision v0.2.0, git-worktree v0.2.0, git-repository v0.17.0, safety bump 20 crates ([`654cf39`](https://github.com/Byron/gitoxide/commit/654cf39c92d5aa4c8d542a6cadf13d4acef6a78e)) diff --git a/git-object/CHANGELOG.md b/git-object/CHANGELOG.md index 9882c056dd3..aa5435fb6af 100644 --- a/git-object/CHANGELOG.md +++ b/git-object/CHANGELOG.md @@ -5,6 +5,36 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +This is a maintenance release with no functional changes. + +### Commit Statistics + + + + - 2 commits contributed to the release over the course of 21 calendar days. + - 64 days passed between releases. + - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Thanks Clippy + + + +[Clippy](https://github.com/rust-lang/rust-clippy) helped 1 time to make code idiomatic. + +### Commit Details + + + +
view details + + * **Uncategorized** + - Merge branch 'normalize-values' ([`4e8cc7a`](https://github.com/Byron/gitoxide/commit/4e8cc7a5b447656c744cd84e6521e620d0479acb)) + - thanks clippy ([`e1003d5`](https://github.com/Byron/gitoxide/commit/e1003d5fdee5d4439c0cf0286c67dec9b5e34f53)) +
+ ## 0.19.0 (2022-05-18) ### New Features @@ -17,7 +47,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 2 commits contributed to the release over the course of 30 calendar days. + - 3 commits contributed to the release over the course of 30 calendar days. - 45 days passed between releases. - 1 commit where understood as [conventional](https://www.conventionalcommits.org). - 2 unique issues were worked on: [#301](https://github.com/Byron/gitoxide/issues/301), [#389](https://github.com/Byron/gitoxide/issues/389) @@ -32,6 +62,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - update changelogs prior to release ([`84cb256`](https://github.com/Byron/gitoxide/commit/84cb25614a5fcddff297c1713eba4efbb6ff1596)) * **[#389](https://github.com/Byron/gitoxide/issues/389)** - `TagRefIter::tagger()`. ([`0d22ab4`](https://github.com/Byron/gitoxide/commit/0d22ab459ce14bc57549270142595d8ebd98ea41)) + * **Uncategorized** + - Release git-hash v0.9.4, git-features v0.21.0, git-actor v0.10.0, git-glob v0.3.0, git-path v0.1.1, git-attributes v0.1.0, git-sec v0.1.0, git-config v0.3.0, git-credentials v0.1.0, git-validate v0.5.4, git-object v0.19.0, git-diff v0.16.0, git-lock v2.1.0, git-ref v0.13.0, git-discover v0.1.0, git-index v0.3.0, git-mailmap v0.2.0, git-traverse v0.15.0, git-pack v0.19.0, git-odb v0.29.0, git-packetline v0.12.5, git-url v0.5.0, git-transport v0.17.0, git-protocol v0.16.0, git-revision v0.2.0, git-worktree v0.2.0, git-repository v0.17.0, safety bump 20 crates ([`654cf39`](https://github.com/Byron/gitoxide/commit/654cf39c92d5aa4c8d542a6cadf13d4acef6a78e)) ## 0.18.0 (2022-04-03) @@ -56,7 +88,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 18 commits contributed to the release over the course of 73 calendar days. + - 17 commits contributed to the release over the course of 55 calendar days. - 60 days passed between releases. - 5 commits where understood as [conventional](https://www.conventionalcommits.org). - 2 unique issues were worked on: [#329](https://github.com/Byron/gitoxide/issues/329), [#364](https://github.com/Byron/gitoxide/issues/364) @@ -93,7 +125,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - upgrade document-features ([`c35e62e`](https://github.com/Byron/gitoxide/commit/c35e62e0da9ac1f7dcb863f5f9c69108c728d32e)) - Merge branch 'AP2008-implement-worktree' ([`f32c669`](https://github.com/Byron/gitoxide/commit/f32c669bc519d59a1f1d90d61cc48a422c86aede)) - Release git-actor v0.8.1 ([`08fe550`](https://github.com/Byron/gitoxide/commit/08fe5508472f2eb209db8a5fc4e4942a9d7db93d)) - - Release git-bitmap v0.0.1, git-hash v0.9.0, git-features v0.19.0, git-index v0.1.0, safety bump 9 crates ([`4624725`](https://github.com/Byron/gitoxide/commit/4624725f54a34dd6b35d3632fb3516965922f60a)) ## 0.17.1 (2022-02-01) @@ -108,7 +139,7 @@ A automated maintenance release without impact to the public API. - - 5 commits contributed to the release over the course of 4 calendar days. + - 5 commits contributed to the release over the course of 7 calendar days. - 8 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#293](https://github.com/Byron/gitoxide/issues/293) @@ -158,7 +189,7 @@ A automated maintenance release without impact to the public API. - - 13 commits contributed to the release over the course of 51 calendar days. + - 14 commits contributed to the release over the course of 51 calendar days. - 55 days passed between releases. - 5 commits where understood as [conventional](https://www.conventionalcommits.org). - 2 unique issues were worked on: [#266](https://github.com/Byron/gitoxide/issues/266), [#279](https://github.com/Byron/gitoxide/issues/279) @@ -187,9 +218,10 @@ A automated maintenance release without impact to the public API. - Release git-features v0.19.1, git-actor v0.8.0, git-config v0.1.10, git-object v0.17.0, git-diff v0.13.0, git-tempfile v1.0.4, git-chunk v0.3.0, git-traverse v0.12.0, git-pack v0.16.0, git-odb v0.26.0, git-packetline v0.12.3, git-url v0.3.5, git-transport v0.15.0, git-protocol v0.14.0, git-ref v0.11.0, git-repository v0.14.0, cargo-smart-release v0.8.0 ([`d78aab7`](https://github.com/Byron/gitoxide/commit/d78aab7b9c4b431d437ac70a0ef96263acb64e46)) - Release git-hash v0.9.1, git-features v0.19.1, git-actor v0.8.0, git-config v0.1.10, git-object v0.17.0, git-diff v0.13.0, git-tempfile v1.0.4, git-chunk v0.3.0, git-traverse v0.12.0, git-pack v0.16.0, git-odb v0.26.0, git-packetline v0.12.3, git-url v0.3.5, git-transport v0.15.0, git-protocol v0.14.0, git-ref v0.11.0, git-repository v0.14.0, cargo-smart-release v0.8.0, safety bump 4 crates ([`373cbc8`](https://github.com/Byron/gitoxide/commit/373cbc877f7ad60dac682e57c52a7b90f108ebe3)) - prepar changelogs for cargo-smart-release release ([`8900d69`](https://github.com/Byron/gitoxide/commit/8900d699226eb0995be70d66249827ce348261df)) + - Release git-bitmap v0.0.1, git-hash v0.9.0, git-features v0.19.0, git-index v0.1.0, safety bump 9 crates ([`4624725`](https://github.com/Byron/gitoxide/commit/4624725f54a34dd6b35d3632fb3516965922f60a)) - Merge branch 'oknozor-feat/traversal-sort-by-committer-date' ([`6add377`](https://github.com/Byron/gitoxide/commit/6add3773c64a9155c236a36bd002099c218882eb)) - - rename `commit::ref_iter::Token::into_id()` to `*::try_into_id()` ([`fda2a8d`](https://github.com/Byron/gitoxide/commit/fda2a8d2f5f8b7d80b4cc0177d08d6a061f1b8f1)) - Add sorting mode to ancestor traversal #270 ([`eb36a3d`](https://github.com/Byron/gitoxide/commit/eb36a3dda83a46ad59078a904f4e277f298a24e1)) + - rename `commit::ref_iter::Token::into_id()` to `*::try_into_id()` ([`fda2a8d`](https://github.com/Byron/gitoxide/commit/fda2a8d2f5f8b7d80b4cc0177d08d6a061f1b8f1)) - thanks clippy ([`7dd2313`](https://github.com/Byron/gitoxide/commit/7dd2313d980fe7c058319ae66d313b3097e3ae5f)) @@ -203,7 +235,7 @@ Maintenance release due, which isn't really required but one now has to be caref - - 7 commits contributed to the release over the course of 25 calendar days. + - 6 commits contributed to the release over the course of 11 calendar days. - 12 days passed between releases. - 1 commit where understood as [conventional](https://www.conventionalcommits.org). - 2 unique issues were worked on: [#250](https://github.com/Byron/gitoxide/issues/250), [#259](https://github.com/Byron/gitoxide/issues/259) @@ -223,7 +255,6 @@ Maintenance release due, which isn't really required but one now has to be caref - Release git-features v0.18.0, git-actor v0.7.0, git-config v0.1.9, git-object v0.16.0, git-diff v0.12.0, git-traverse v0.11.0, git-pack v0.15.0, git-odb v0.25.0, git-packetline v0.12.2, git-transport v0.14.0, git-protocol v0.13.0, git-ref v0.10.0, git-repository v0.13.0, cargo-smart-release v0.7.0, safety bump 12 crates ([`acd3737`](https://github.com/Byron/gitoxide/commit/acd37371dcd92ebac3d1f039224d02f2b4e9fa0b)) - Adjust changelogs prior to release ([`ec38950`](https://github.com/Byron/gitoxide/commit/ec3895005d141abe79764eaff7c0f04153e38d73)) - Merge branch 'git-loose-objects' of https://github.com/xmo-odoo/gitoxide into xmo-odoo-git-loose-objects ([`ee737cd`](https://github.com/Byron/gitoxide/commit/ee737cd237ad70bf9f2c5e0d3e4557909e495bca)) - - Move "loose object header" ser/de to git-object ([`3d1565a`](https://github.com/Byron/gitoxide/commit/3d1565acfc336baf6487edccefd72d0226141a08)) ## 0.15.1 (2021-11-16) @@ -234,7 +265,7 @@ A maintenance release triggered by changes to git-pack and changelog rewrites. - - 6 commits contributed to the release. + - 7 commits contributed to the release over the course of 15 calendar days. - 27 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#254](https://github.com/Byron/gitoxide/issues/254) @@ -251,8 +282,9 @@ A maintenance release triggered by changes to git-pack and changelog rewrites. - Release git-config v0.1.8, git-object v0.15.1, git-diff v0.11.1, git-traverse v0.10.1, git-pack v0.14.0, git-odb v0.24.0, git-packetline v0.12.1, git-transport v0.13.1, git-protocol v0.12.1, git-ref v0.9.1, git-repository v0.12.0, cargo-smart-release v0.6.0 ([`f606fa9`](https://github.com/Byron/gitoxide/commit/f606fa9a0ca338534252df8921cd5e9d3875bf94)) - better changelog descriptions. ([`f69b2d6`](https://github.com/Byron/gitoxide/commit/f69b2d627099639bc144fd94fde678d84a10d6f7)) - Adjusting changelogs prior to release of git-config v0.1.8, git-object v0.15.1, git-diff v0.11.1, git-traverse v0.10.1, git-pack v0.14.0, git-odb v0.24.0, git-packetline v0.12.1, git-transport v0.13.1, git-protocol v0.12.1, git-ref v0.9.1, git-repository v0.12.0, cargo-smart-release v0.6.0, safety bump 5 crates ([`39b40c8`](https://github.com/Byron/gitoxide/commit/39b40c8c3691029cc146b893fa0d8d25d56d0819)) - - Improve error handling of encode::header_field_multi_line & simplify ([`bab9fb5`](https://github.com/Byron/gitoxide/commit/bab9fb567e47bb88d27b36f6ffa95c62c14ed80a)) - Adjust changelogs prior to git-pack release ([`ac8015d`](https://github.com/Byron/gitoxide/commit/ac8015de710142c2bedd0e4188e872e0cf1ceccc)) + - Move "loose object header" ser/de to git-object ([`3d1565a`](https://github.com/Byron/gitoxide/commit/3d1565acfc336baf6487edccefd72d0226141a08)) + - Improve error handling of encode::header_field_multi_line & simplify ([`bab9fb5`](https://github.com/Byron/gitoxide/commit/bab9fb567e47bb88d27b36f6ffa95c62c14ed80a)) ## v0.15.0 (2021-10-19) @@ -412,7 +444,7 @@ or generally trying to figure out what changed between commits. - - 2 commits contributed to the release over the course of 1 calendar day. + - 5 commits contributed to the release over the course of 5 calendar days. - 10 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -426,6 +458,9 @@ or generally trying to figure out what changed between commits. * **Uncategorized** - Release git-object v0.13.1 ([`2c55ea7`](https://github.com/Byron/gitoxide/commit/2c55ea759caa1d317f008966ae388b3cf0ce0f6d)) - Bump git-hash v0.6.0 ([`6efd90d`](https://github.com/Byron/gitoxide/commit/6efd90db54f7f7441b76159dba3be80c15657a3d)) + - [object #190] consistent method naming ([`c5de433`](https://github.com/Byron/gitoxide/commit/c5de433e569c2cc8e78f3f84e368a11fe95f522a)) + - [object #190] More conversion methods for Object ([`78bacf9`](https://github.com/Byron/gitoxide/commit/78bacf97d669f3adfebdb093054c162cfd5214fa)) + - [repository #190] A way to write objects and the empty tree specifically ([`7c559d6`](https://github.com/Byron/gitoxide/commit/7c559d6e1b68bc89220bca426257f383bce586ae)) ## v0.13.0 (2021-08-27) @@ -434,7 +469,7 @@ or generally trying to figure out what changed between commits. - - 31 commits contributed to the release over the course of 8 calendar days. + - 28 commits contributed to the release over the course of 8 calendar days. - 10 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -450,15 +485,12 @@ or generally trying to figure out what changed between commits. - [object #177] dissolve 'immutable' module ([`70e11c2`](https://github.com/Byron/gitoxide/commit/70e11c21b0637cd250f54381d5490e9976880ad9)) - [object #177] fix docs ([`2fd23ed`](https://github.com/Byron/gitoxide/commit/2fd23ed9ad556b8e46cf650e23f0c6726e304708)) - [object #177] resolve 'mutable' module ([`b201b32`](https://github.com/Byron/gitoxide/commit/b201b3260e3eec98ed71716c1aab1ba4a06ab829)) - - [object #190] consistent method naming ([`c5de433`](https://github.com/Byron/gitoxide/commit/c5de433e569c2cc8e78f3f84e368a11fe95f522a)) - [object #177] refactor ([`216dd0f`](https://github.com/Byron/gitoxide/commit/216dd0f10add7a11ebdf96732ed7649d74815d64)) - [object #177] refactor ([`472e13b`](https://github.com/Byron/gitoxide/commit/472e13b27e97a196c644d716cad1801bd62fac71)) - [object #177] Commit::write_to migration ([`60b9365`](https://github.com/Byron/gitoxide/commit/60b936553bef3c9126d46ece9779f08b5eef9a95)) - - [object #190] More conversion methods for Object ([`78bacf9`](https://github.com/Byron/gitoxide/commit/78bacf97d669f3adfebdb093054c162cfd5214fa)) - [object #177] commit::RefIter -> CommitRefIter ([`e603306`](https://github.com/Byron/gitoxide/commit/e603306e81f392af97aa5afd232653de56bf3ce9)) - [object #177] migrate immutable::commit into crate::commit ([`45d3934`](https://github.com/Byron/gitoxide/commit/45d393438eac2c7ecd47670922437dd0de4cd69b)) - [object #177] refactor tag write_to ([`7f19559`](https://github.com/Byron/gitoxide/commit/7f1955916ae9d7e17be971170c853487e3755169)) - - [repository #190] A way to write objects and the empty tree specifically ([`7c559d6`](https://github.com/Byron/gitoxide/commit/7c559d6e1b68bc89220bca426257f383bce586ae)) - [object #177] tag::RefIter -> TagRefIter ([`28587c6`](https://github.com/Byron/gitoxide/commit/28587c691eb74e5cb097afb2b63f9d9e2561c45d)) - [object #177] into_mutable() -> into_owned() ([`7e701ce`](https://github.com/Byron/gitoxide/commit/7e701ce49efe5d40327770a988aae88692d88219)) - [object #177] fix docs ([`25d8e7b`](https://github.com/Byron/gitoxide/commit/25d8e7b1862bd05489359b162a32c6ad45ecdf9a)) @@ -469,12 +501,12 @@ or generally trying to figure out what changed between commits. - [object #177] rename immutable::* to immutable::*Ref ([`6deb012`](https://github.com/Byron/gitoxide/commit/6deb01291fb382b7fb9206682e319afa81bacc05)) - Release git-object v0.13.0 ([`708fc5a`](https://github.com/Byron/gitoxide/commit/708fc5abd8af4dd7459f388c7092bf35915c6662)) - Merge pull request #172 from mellowagain/main ([`61aebbf`](https://github.com/Byron/gitoxide/commit/61aebbfff02eb87e0e8c49438a093a21b1134baf)) - - Release git-actor v0.4.0 ([`16358c9`](https://github.com/Byron/gitoxide/commit/16358c9bf03604857d51bfa4dbfd2fc8c5210da7)) - - [actor #173] fix docs ([`2d7956a`](https://github.com/Byron/gitoxide/commit/2d7956a22511d73b767e443dac21b60e93f286dd)) - Release git-actor v0.5.0 ([`a684b0f`](https://github.com/Byron/gitoxide/commit/a684b0ff96ebfc5e4b3ce78452dc21ce856a6869)) - - Upgrade to nom-7 ([`f0aa3e1`](https://github.com/Byron/gitoxide/commit/f0aa3e1b5b407b2afd187c9cb622676fcddaf706)) - [actor #175] refactor ([`ec88c59`](https://github.com/Byron/gitoxide/commit/ec88c5905194150cc94db4d4c20e9f4e2f6595c3)) + - Release git-actor v0.4.0 ([`16358c9`](https://github.com/Byron/gitoxide/commit/16358c9bf03604857d51bfa4dbfd2fc8c5210da7)) + - [actor #173] fix docs ([`2d7956a`](https://github.com/Byron/gitoxide/commit/2d7956a22511d73b767e443dac21b60e93f286dd)) - [actor #173] rename immutable::Signature to SignatureRef! ([`96461ac`](https://github.com/Byron/gitoxide/commit/96461ace776d6b351b313d4f2697f2d95b9e196e)) + - Upgrade to nom-7 ([`f0aa3e1`](https://github.com/Byron/gitoxide/commit/f0aa3e1b5b407b2afd187c9cb622676fcddaf706)) - [smart-release #162] use TreeRef capabilities to lookup path ([`51d1943`](https://github.com/Byron/gitoxide/commit/51d19433e6704fabb6547a0ba1b5c32afce43d8b)) - [repository #162] what could be a correct implementation of a tree path lookup ([`1f638ee`](https://github.com/Byron/gitoxide/commit/1f638eee0aa5f6e1cc34c5bc59a18b5f22af4cbc)) @@ -799,7 +831,7 @@ or generally trying to figure out what changed between commits. - - 16 commits contributed to the release over the course of 90 calendar days. + - 17 commits contributed to the release over the course of 90 calendar days. - 94 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -827,6 +859,7 @@ or generally trying to figure out what changed between commits. - Document borrowed odb objects ([`7626f7f`](https://github.com/Byron/gitoxide/commit/7626f7f3af885f1b95801f9dbc71bee0bc77400e)) - remove dash in all repository links ([`98c1360`](https://github.com/Byron/gitoxide/commit/98c1360ba4d2fb3443602b7da8775906224feb1d)) - Finish removal of rust 2018 idioms ([`0d1699e`](https://github.com/Byron/gitoxide/commit/0d1699e0e0bc9052be0a74b1b3f3d3eeeec39e3e)) + - refactor ([`e4bcfe6`](https://github.com/Byron/gitoxide/commit/e4bcfe6406b14feffa63598c7cdcc8ecc73222bd)) ### Thanks Clippy @@ -841,7 +874,7 @@ or generally trying to figure out what changed between commits. - - 7 commits contributed to the release over the course of 29 calendar days. + - 6 commits contributed to the release over the course of 29 calendar days. - 30 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -854,7 +887,6 @@ or generally trying to figure out what changed between commits. * **Uncategorized** - (cargo-release) version 0.4.0 ([`0d7b60e`](https://github.com/Byron/gitoxide/commit/0d7b60e856325009431172e1df742a1cd2165575)) - - refactor ([`e4bcfe6`](https://github.com/Byron/gitoxide/commit/e4bcfe6406b14feffa63598c7cdcc8ecc73222bd)) - (cargo-release) version 0.4.0 ([`f9dd225`](https://github.com/Byron/gitoxide/commit/f9dd225afc4aafde1a8b8148943f56f2c547a9ea)) - [clone] proper parsing of V1 refs ([`d262307`](https://github.com/Byron/gitoxide/commit/d26230727ef795a819852bc82d6c2e9956809d8c)) - [clone] Don't expose hex-error in public interfaces anymore ([`92dab30`](https://github.com/Byron/gitoxide/commit/92dab3033890fe26fe2b901d87abe16abd065cce)) diff --git a/git-odb/CHANGELOG.md b/git-odb/CHANGELOG.md index be2294d5dea..fd62a0bf4ef 100644 --- a/git-odb/CHANGELOG.md +++ b/git-odb/CHANGELOG.md @@ -5,6 +5,34 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +This is a maintenance release with no functional changes. + +### Commit Statistics + + + + - 5 commits contributed to the release over the course of 33 calendar days. + - 39 days passed between releases. + - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) + +### Commit Details + + + +
view details + + * **[#331](https://github.com/Byron/gitoxide/issues/331)** + - adjustments due to breaking changes in `git_path` ([`4420ae9`](https://github.com/Byron/gitoxide/commit/4420ae932d5b20a9662a6d36353a27111b5cd672)) + * **Uncategorized** + - make fmt ([`0700b09`](https://github.com/Byron/gitoxide/commit/0700b09d6828849fa2470df89af1f75a67bfb27d)) + - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) + - Use git_path::realpath in all places that allow it right now ([`229dc91`](https://github.com/Byron/gitoxide/commit/229dc917fc7d9241b85e5818260a6fbdd3a5daaa)) + - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) +
+ ## 0.30.0 (2022-06-13) A maintenance release without user-facing changes. @@ -13,7 +41,7 @@ A maintenance release without user-facing changes. - - 1 commit contributed to the release. + - 2 commits contributed to the release. - 25 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +53,7 @@ A maintenance release without user-facing changes.
view details * **Uncategorized** + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980))
diff --git a/git-pack/CHANGELOG.md b/git-pack/CHANGELOG.md index b3647b8bc0e..21b2e1d2fd5 100644 --- a/git-pack/CHANGELOG.md +++ b/git-pack/CHANGELOG.md @@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +This is a maintenance release with no functional changes. + +### Commit Statistics + + + + - 2 commits contributed to the release over the course of 33 calendar days. + - 39 days passed between releases. + - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) + - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) +
+ ## 0.20.0 (2022-06-13) A maintenance release without user-facing changes. @@ -13,7 +37,7 @@ A maintenance release without user-facing changes. - - 2 commits contributed to the release over the course of 22 calendar days. + - 3 commits contributed to the release over the course of 22 calendar days. - 22 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +49,7 @@ A maintenance release without user-facing changes.
view details * **Uncategorized** + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980)) - Merge branch 'davidkna-discover-x-fs' ([`9abaeda`](https://github.com/Byron/gitoxide/commit/9abaeda2d22e2dbb1db1632c6eb637f1458d06e1))
diff --git a/git-path/CHANGELOG.md b/git-path/CHANGELOG.md index 20b808ba443..d6e9cb35264 100644 --- a/git-path/CHANGELOG.md +++ b/git-path/CHANGELOG.md @@ -5,6 +5,48 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Changed (BREAKING) + + - `realpath()` handles `cwd` internally + This makes for more convenient usage in the common case. + +### Commit Statistics + + + + - 10 commits contributed to the release over the course of 32 calendar days. + - 33 days passed between releases. + - 1 commit where understood as [conventional](https://www.conventionalcommits.org). + - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) + +### Thanks Clippy + + + +[Clippy](https://github.com/rust-lang/rust-clippy) helped 1 time to make code idiomatic. + +### Commit Details + + + +
view details + + * **[#331](https://github.com/Byron/gitoxide/issues/331)** + - `realpath()` handles `cwd` internally ([`dfa1e05`](https://github.com/Byron/gitoxide/commit/dfa1e05d3c983f1e8b1cb3b80d03608341187883)) + * **Uncategorized** + - make fmt ([`0700b09`](https://github.com/Byron/gitoxide/commit/0700b09d6828849fa2470df89af1f75a67bfb27d)) + - fix docs ([`4f8e3b1`](https://github.com/Byron/gitoxide/commit/4f8e3b169e57d599439c7abc861c82c08bcd92e3)) + - thanks clippy ([`7a2a31e`](https://github.com/Byron/gitoxide/commit/7a2a31e5758a2be8434f22cd9401ac00539f2bd9)) + - Merge branch 'main' into cont_include_if ([`daa71c3`](https://github.com/Byron/gitoxide/commit/daa71c3b753c6d76a3d652c29237906b3e28728f)) + - Merge branch 'main' into cont_include_if ([`0e9df36`](https://github.com/Byron/gitoxide/commit/0e9df364c4cddf006b1de18b8d167319b7cc1186)) + - generally avoid using `target_os = "windows"` in favor of `cfg(windows)` and negations ([`91d5402`](https://github.com/Byron/gitoxide/commit/91d54026a61c2aae5e3e1341d271acf16478cd83)) + - Use git_path::realpath in all places that allow it right now ([`229dc91`](https://github.com/Byron/gitoxide/commit/229dc917fc7d9241b85e5818260a6fbdd3a5daaa)) + - avoid unwraps in tests as they are now stable ([`efa1423`](https://github.com/Byron/gitoxide/commit/efa14234c352b6b8417f0a42fc946e88f2eb52d3)) + - remove canonicalized-path abstraction ([`9496e55`](https://github.com/Byron/gitoxide/commit/9496e5512975825efebe0db86335d0d2dc8c9095)) +
+ ## 0.3.0 (2022-06-19) ### Bug Fixes (BREAKING) @@ -15,7 +57,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 3 commits contributed to the release. + - 4 commits contributed to the release. - 6 days passed between releases. - 1 commit where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -27,6 +69,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
view details * **Uncategorized** + - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) - Fix git-paths tests; improve error handling. ([`9c00504`](https://github.com/Byron/gitoxide/commit/9c0050451f634a54e610c86199b5d7d393378878)) - docs for git-path ([`a520092`](https://github.com/Byron/gitoxide/commit/a52009244c9b1059ebb3d5dd472c25f9c49691f3)) - Remove `git-config` test utilities from `git-path`. ([`c9933c0`](https://github.com/Byron/gitoxide/commit/c9933c0b0f51d21dc8244b2acc33d7dc8a33f6ce)) diff --git a/git-protocol/CHANGELOG.md b/git-protocol/CHANGELOG.md index 4498a1869dd..eb8d5ff302f 100644 --- a/git-protocol/CHANGELOG.md +++ b/git-protocol/CHANGELOG.md @@ -5,6 +5,29 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +This is a maintenance release with no functional changes. + +### Commit Statistics + + + + - 1 commit contributed to the release over the course of 33 calendar days. + - 39 days passed between releases. + - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) +
+ ## 0.17.0 (2022-06-13) A maintenance release without user-facing changes. @@ -13,7 +36,7 @@ A maintenance release without user-facing changes. - - 1 commit contributed to the release. + - 2 commits contributed to the release. - 25 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +48,7 @@ A maintenance release without user-facing changes.
view details * **Uncategorized** + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980))
diff --git a/git-ref/CHANGELOG.md b/git-ref/CHANGELOG.md index 30053692158..be8e5c343f4 100644 --- a/git-ref/CHANGELOG.md +++ b/git-ref/CHANGELOG.md @@ -5,6 +5,52 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### New Features + + - Add `store::WriteRefLog::Always` to unconditionally write reflogs. + +### Changed (BREAKING) + + - `Target(Ref)?::try_name()` now returns `Option<&FullNameRef>`. + That way, the name is actually directly usable in most methods that + require a validated name as input. + +### Commit Statistics + + + + - 9 commits contributed to the release over the course of 33 calendar days. + - 39 days passed between releases. + - 2 commits where understood as [conventional](https://www.conventionalcommits.org). + - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) + +### Thanks Clippy + + + +[Clippy](https://github.com/rust-lang/rust-clippy) helped 1 time to make code idiomatic. + +### Commit Details + + + +
view details + + * **[#331](https://github.com/Byron/gitoxide/issues/331)** + - `Target(Ref)?::try_name()` now returns `Option<&FullNameRef>`. ([`0f753e9`](https://github.com/Byron/gitoxide/commit/0f753e922e313f735ed267f913366771e9de1111)) + - Add `store::WriteRefLog::Always` to unconditionally write reflogs. ([`4607a18`](https://github.com/Byron/gitoxide/commit/4607a18e24b8270c182663a434b79dff8761db0e)) + * **Uncategorized** + - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) + - Merge branch 'main' into cont_include_if ([`daa71c3`](https://github.com/Byron/gitoxide/commit/daa71c3b753c6d76a3d652c29237906b3e28728f)) + - Merge branch 'normalize-values' ([`4e8cc7a`](https://github.com/Byron/gitoxide/commit/4e8cc7a5b447656c744cd84e6521e620d0479acb)) + - thanks clippy ([`e1003d5`](https://github.com/Byron/gitoxide/commit/e1003d5fdee5d4439c0cf0286c67dec9b5e34f53)) + - Merge branch 'main' into cont_include_if ([`0e9df36`](https://github.com/Byron/gitoxide/commit/0e9df364c4cddf006b1de18b8d167319b7cc1186)) + - generally avoid using `target_os = "windows"` in favor of `cfg(windows)` and negations ([`91d5402`](https://github.com/Byron/gitoxide/commit/91d54026a61c2aae5e3e1341d271acf16478cd83)) + - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) +
+ ## 0.14.0 (2022-06-13) A maintenance release without user-facing changes. @@ -13,7 +59,7 @@ A maintenance release without user-facing changes. - - 2 commits contributed to the release. + - 3 commits contributed to the release. - 25 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +71,7 @@ A maintenance release without user-facing changes.
view details * **Uncategorized** + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980)) - make fmt ([`c665aef`](https://github.com/Byron/gitoxide/commit/c665aef4270c5ee54da89ee015cc0affd6337608))
diff --git a/git-repository/CHANGELOG.md b/git-repository/CHANGELOG.md index 4b72c184571..b89f503ed1e 100644 --- a/git-repository/CHANGELOG.md +++ b/git-repository/CHANGELOG.md @@ -5,6 +5,150 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### New Features + + - respect `safe.directory`. + In practice, this code will rarely be hit as it would require very + strict settings that forbid any operation within a non-owned git + directory. + - permissions for configuration. + It provides fine-grained control over what sources to load. + - `git-config` is now accessible in `git-repository::config`. + - `gix config` lists all entries of all configuration files git considers. + Filters allow to narrow down the output. + - repository now initializes global configuration files and resolves includes + - resolve includes in local repository configuration + - `config::Snapshot::trusted_path()` to obtain trustworthy paths. + We also apply trust-based config query during initialization to assure + we don't use paths which aren't owned by the current user. + - `Repository::config_snapshot()` to access configuration values. + - Use `git-config` to write config file on initialization, including `logallrefupdates` and `precomposeunicode`. + - respect `core.logallrefupdates` configuration setting. + +### Changed (BREAKING) + + - Make `SignatureRef<'_>` mandatory for editing reference changelogs. + If defaults are desired, these can be set by the caller. + - `Repository::committer()` now returns an `Option`, see `::committer_or_default()` for a method that doesn't. + - remove local-time-support feature toggle. + We treat local time as default feature without a lot of fuzz, and + will eventually document that definitive support needs a compile + time switch in the compiler (`--cfg unsound_local_offset` or something). + + One day it will perish. Failure is possible anyway and we will write + code to deal with it while minimizing the amount of system time + fetches when asking for the current local time. + - Associate `file::Metadata` with each `File`. + This is the first step towards knowing more about the source of each + value to filter them based on some properties. + + This breaks various methods handling the instantiation of configuration + files as `file::Metadata` typically has to be provided by the caller + now or be associated with each path to read configuration from. + +### New Features (BREAKING) + + - Support for `lossy` load mode. + There is a lot of breaking changes as `file::from_paths::Options` now + became `file::init::Options`, and the same goes for the error type. + - change mostily internal uses of [u8] to BString/BStr + +### Commit Statistics + + + + - 69 commits contributed to the release over the course of 33 calendar days. + - 39 days passed between releases. + - 16 commits where understood as [conventional](https://www.conventionalcommits.org). + - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) + +### Thanks Clippy + + + +[Clippy](https://github.com/rust-lang/rust-clippy) helped 5 times to make code idiomatic. + +### Commit Details + + + +
view details + + * **[#331](https://github.com/Byron/gitoxide/issues/331)** + - Make lossy-configuration configurable ([`b0e4da6`](https://github.com/Byron/gitoxide/commit/b0e4da621114d188a73b9f40757f59564da3c079)) + - tests for author/committer/user ([`6d2e53c`](https://github.com/Byron/gitoxide/commit/6d2e53c32145770e8314f0879d6d769090667f90)) + - refactor ([`4dc6594`](https://github.com/Byron/gitoxide/commit/4dc6594686478d9d6cd09e2ba02048624c3577e7)) + - default user signature now with 'now' time, like advertised. ([`ad40202`](https://github.com/Byron/gitoxide/commit/ad4020224114127612eaf5d1e732baf81818812d)) + - Make `SignatureRef<'_>` mandatory for editing reference changelogs. ([`68f4bc2`](https://github.com/Byron/gitoxide/commit/68f4bc2570d455c762da7e3d675b9b507cec69bb)) + - `Repository::committer()` now returns an `Option`, see `::committer_or_default()` for a method that doesn't. ([`f932cea`](https://github.com/Byron/gitoxide/commit/f932cea68ece997f711add3368db53aeb8cdf064)) + - first sketch of using configuration and environment variables for author/committer ([`330d0a1`](https://github.com/Byron/gitoxide/commit/330d0a19d54aabac868b76ef6281fffdbdcde53c)) + - remove local-time-support feature toggle. ([`89a41bf`](https://github.com/Byron/gitoxide/commit/89a41bf2b37db29b9983b4e5492cfd67ed490b23)) + - a first sketch on how identity management could look like. ([`780f14f`](https://github.com/Byron/gitoxide/commit/780f14f5c270802e51cf039639c2fbdb5ac5a85e)) + - refactor ([`4f61312`](https://github.com/Byron/gitoxide/commit/4f613120f9f761b86fc7eb16227d08fc5b9828d8)) + - respect `safe.directory`. ([`1b765ec`](https://github.com/Byron/gitoxide/commit/1b765ec6ae70d1f4cc5a885b3c68d6f3335ba827)) + - permissions for configuration. ([`840d9a3`](https://github.com/Byron/gitoxide/commit/840d9a3018d11146bb8e80fc92693c65eb534d91)) + - `git-config` is now accessible in `git-repository::config`. ([`6570808`](https://github.com/Byron/gitoxide/commit/657080829867d9dcb0c9b9cb6c1c8126c4df3783)) + - `gix config` lists all entries of all configuration files git considers. ([`d99453e`](https://github.com/Byron/gitoxide/commit/d99453ebeb970ed493be236def299d1e82b01f83)) + - adapt to changes in `git-config` ([`b52b540`](https://github.com/Byron/gitoxide/commit/b52b5407638adef2216aeb4215a7c0437d6ee2d5)) + - adapt to changes in `git-config` ([`3c57344`](https://github.com/Byron/gitoxide/commit/3c57344325ad20ae891824cd8791d2d17f4148e5)) + - adjust to changes in `git-config` for greater efficiency ([`e9afede`](https://github.com/Byron/gitoxide/commit/e9afedeebafb70d81a8fa2e6dc320b387e6ee926)) + - adapt to changes in git-config ([`14ba883`](https://github.com/Byron/gitoxide/commit/14ba8834b8738817d2bfb0ca66d1fb86fc8f3075)) + - refactor ([`95ed219`](https://github.com/Byron/gitoxide/commit/95ed219c5f414b6fa96d80eacf297f24d823a4fe)) + - repository now initializes global configuration files and resolves includes ([`ebedd03`](https://github.com/Byron/gitoxide/commit/ebedd03e119aa5d46da07e577bfccad621eaecb5)) + - adapt to changes in git-config ([`627a0e1`](https://github.com/Byron/gitoxide/commit/627a0e1e12e15a060a70d880ffdfb05f1f7db36c)) + - only a select few early config attributes must be repo-local ([`be0971c`](https://github.com/Byron/gitoxide/commit/be0971c5191f7866063ebcc0407331e683cf7d68)) + - resolve includes in local repository configuration ([`de8572f`](https://github.com/Byron/gitoxide/commit/de8572ff2ced9422832e1ba433955c33f0994675)) + - Adjust to changes in `git-config` ([`30cbe29`](https://github.com/Byron/gitoxide/commit/30cbe299860d84b5aeffced54839529dc068a8c7)) + - solve cycle between config and ref-store ([`1679d56`](https://github.com/Byron/gitoxide/commit/1679d5684cec852b39a0d51d5001fbcecafc6748)) + - adapt to changes in `git-config` ([`7f41f1e`](https://github.com/Byron/gitoxide/commit/7f41f1e267c9cbf87061821dd2f0edb6b0984226)) + - prepare for resolving a complete config… ([`9be1dd6`](https://github.com/Byron/gitoxide/commit/9be1dd6f7cdb9aea7c85df896e370b3c40f5e4ec)) + - Allow to configure a different filter for configuration section. ([`e512ab0`](https://github.com/Byron/gitoxide/commit/e512ab09477629957e469719f05e7de65955f3db)) + - adjust to changes in `git-config` ([`ca89d0d`](https://github.com/Byron/gitoxide/commit/ca89d0d4785ec4d66a0a4316fbc74be63dcc0f48)) + - refactor ([`5723730`](https://github.com/Byron/gitoxide/commit/57237303d9ae8a746c64d05ecedf3d43a0d041f6)) + - load configuration with trust information, needs cleanup ([`d8e41e2`](https://github.com/Byron/gitoxide/commit/d8e41e20de741c3d4701d862033cf50582a0d015)) + - Add remaining config access, and an escape hatch. ([`81715ff`](https://github.com/Byron/gitoxide/commit/81715ffca33e40cb6e37fff25baa68fca70c4844)) + - `config::Snapshot::trusted_path()` to obtain trustworthy paths. ([`d5a48b8`](https://github.com/Byron/gitoxide/commit/d5a48b82230b047434610550aacd2dc741b4b5f0)) + - `Debug` for `config::Snapshot`. ([`2c21956`](https://github.com/Byron/gitoxide/commit/2c2195640818319795a93e73bed79174fa358f55)) + - `Repository::config_snapshot()` to access configuration values. ([`5f9bfa8`](https://github.com/Byron/gitoxide/commit/5f9bfa89ceb61f484be80575b0379bbf9d7a36b3)) + - adapt to changes in `git-config` ([`c9423db`](https://github.com/Byron/gitoxide/commit/c9423db5381064296d22f48b532f29d3e8162ce9)) + - Support for `lossy` load mode. ([`d003c0f`](https://github.com/Byron/gitoxide/commit/d003c0f139d61e3bd998a0283a9c7af25a60db02)) + - Associate `file::Metadata` with each `File`. ([`6f4eea9`](https://github.com/Byron/gitoxide/commit/6f4eea936d64fb9827277c160f989168e7b1dba2)) + - adjust to changes in `git-config` ([`81e63cc`](https://github.com/Byron/gitoxide/commit/81e63cc3590301ca32c1172b358ffb45a13b6a8f)) + - Use `git-config` to write config file on initialization, including `logallrefupdates` and `precomposeunicode`. ([`7f67b23`](https://github.com/Byron/gitoxide/commit/7f67b23b9462b805591b1fe5a8406f8d7404f372)) + - respect `core.logallrefupdates` configuration setting. ([`e263e13`](https://github.com/Byron/gitoxide/commit/e263e13d312e41aa1481d104fa79ede509fbe1c5)) + - adapt to breaking changes in `git-config` ([`a02d575`](https://github.com/Byron/gitoxide/commit/a02d5759c14eb1d42fe24e61afc32a4cd463d1b7)) + - adapt to changes in `git-config` ([`858dc8b`](https://github.com/Byron/gitoxide/commit/858dc8b1b721ce5a45a76d9a97935cb0daf61e1a)) + - adjustments due to breaking changes in `git-config` ([`924f148`](https://github.com/Byron/gitoxide/commit/924f14879bd14ca1ff13fdd6ccafe43d6de01b68)) + - adjustments for breaking changes in `git-config` ([`d3841ee`](https://github.com/Byron/gitoxide/commit/d3841ee752e426bf58130cde1e4e40215ccb8f33)) + - adjust to changes in `git-config` ([`c52cb95`](https://github.com/Byron/gitoxide/commit/c52cb958f85b533e791ec6b38166a9d819f12dd4)) + - adjustments due to breaking changes in `git-config` ([`07bf647`](https://github.com/Byron/gitoxide/commit/07bf647c788afbe5a595ed3091744459e3623f13)) + - adapt to changes in `git-config` ([`363a826`](https://github.com/Byron/gitoxide/commit/363a826144ad59518b5c1a3dbbc82d04e4fc062d)) + - adjust to changes in `git-config` ([`920d56e`](https://github.com/Byron/gitoxide/commit/920d56e4f5141eeb536956cdc5fac042ddee3525)) + - adjustments required due to changed in `git-config` ([`41bfd3b`](https://github.com/Byron/gitoxide/commit/41bfd3b4122e37370d268608b60cb00a671a8879)) + - adjust to breaking changes in `git-config` ([`5b66202`](https://github.com/Byron/gitoxide/commit/5b66202d96bf664ed84755afc3ec49c301ecd62c)) + - adjustments due to breaking changes in `git_path` ([`4420ae9`](https://github.com/Byron/gitoxide/commit/4420ae932d5b20a9662a6d36353a27111b5cd672)) + * **Uncategorized** + - thanks clippy ([`fddc720`](https://github.com/Byron/gitoxide/commit/fddc7206476423a6964d61acd060305572ecd02b)) + - thanks clippy ([`0346aaa`](https://github.com/Byron/gitoxide/commit/0346aaaeccfe18a443410652cada7b14eb34d8b9)) + - thanks clippy ([`b630543`](https://github.com/Byron/gitoxide/commit/b630543669af5289508ce066bd026e2b9a9d5044)) + - thanks clippy ([`d9eb34c`](https://github.com/Byron/gitoxide/commit/d9eb34cad7a69b56f10eec5b88b86ebd6a9a74af)) + - avoid extra copies of paths using `PathCursor` tool during repo init ([`5771721`](https://github.com/Byron/gitoxide/commit/5771721ff5f86dd808d9961126c9c4a61867507c)) + - make fmt ([`0700b09`](https://github.com/Byron/gitoxide/commit/0700b09d6828849fa2470df89af1f75a67bfb27d)) + - change mostily internal uses of [u8] to BString/BStr ([`311d4b4`](https://github.com/Byron/gitoxide/commit/311d4b447daf8d4364670382a20901468748d34d)) + - Merge branch 'main' into cont_include_if ([`daa71c3`](https://github.com/Byron/gitoxide/commit/daa71c3b753c6d76a3d652c29237906b3e28728f)) + - Merge branch 'normalize-values' ([`4e8cc7a`](https://github.com/Byron/gitoxide/commit/4e8cc7a5b447656c744cd84e6521e620d0479acb)) + - thanks clippy ([`e1003d5`](https://github.com/Byron/gitoxide/commit/e1003d5fdee5d4439c0cf0286c67dec9b5e34f53)) + - Merge branch 'main' into cont_include_if ([`0e9df36`](https://github.com/Byron/gitoxide/commit/0e9df364c4cddf006b1de18b8d167319b7cc1186)) + - Use git_path::realpath in all places that allow it right now ([`229dc91`](https://github.com/Byron/gitoxide/commit/229dc917fc7d9241b85e5818260a6fbdd3a5daaa)) + - fix build warnings ([`84109f5`](https://github.com/Byron/gitoxide/commit/84109f54877d045f8ccc7a380c012802708c2f1e)) + - Make a note to be sure we use the home-dir correctly in git-repository; avoid `dirs` crate ([`0e8cf19`](https://github.com/Byron/gitoxide/commit/0e8cf19d7f742f9400afa4863d302ba18a452adc)) + - adjust to changes in git-config ([`7a1678d`](https://github.com/Byron/gitoxide/commit/7a1678d8da0c361e0a0cc4380a04ebfb3ce5035d)) + - Merge branch 'main' into cont_include_if ([`41ea8ba`](https://github.com/Byron/gitoxide/commit/41ea8ba78e74f5c988148367386a1f4f304cb951)) + - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) +
+ ## 0.19.0 (2022-06-13) ### New Features (BREAKING) @@ -16,7 +160,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 10 commits contributed to the release over the course of 20 calendar days. + - 13 commits contributed to the release over the course of 20 calendar days. - 20 days passed between releases. - 1 commit where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -28,14 +172,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
view details * **Uncategorized** + - Release git-worktree v0.3.0, git-repository v0.19.0 ([`0d8e856`](https://github.com/Byron/gitoxide/commit/0d8e8566dc5c6955487d67e235f86fbc75a3a88a)) - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980)) - make fmt ([`c665aef`](https://github.com/Byron/gitoxide/commit/c665aef4270c5ee54da89ee015cc0affd6337608)) + - Merge branch 'main' into svetli-n-cont_include_if ([`315c87e`](https://github.com/Byron/gitoxide/commit/315c87e18c6cac0fafa7b4e59fdd3c076a58a45a)) - fix docs ([`daef221`](https://github.com/Byron/gitoxide/commit/daef2215cc6c4fddded5229951e8ac71c395468d)) - refactor ([`b27a8c2`](https://github.com/Byron/gitoxide/commit/b27a8c243cdc14730478c2a94cafdc8ccf5c60d3)) - refactor ([`06e96a4`](https://github.com/Byron/gitoxide/commit/06e96a435d820a1ef1e567bf93e7b9ca5fa74829)) - Merge branch 'main' into davidkna-envopen ([`bc0abc6`](https://github.com/Byron/gitoxide/commit/bc0abc643d3329f885f250b6880560dec861150f)) - Make `realpath()` easier to use by introducing `realpath_opt()`. ([`266d437`](https://github.com/Byron/gitoxide/commit/266d4379e9132fd7dd21e6c8fccb36e125069d6e)) + - Refact. ([`a342e53`](https://github.com/Byron/gitoxide/commit/a342e53dac58cea1787a94eaa1a9d24fb1389df2)) - Add discovery opt env-overrides & env discovery helpers ([`e521d39`](https://github.com/Byron/gitoxide/commit/e521d39e1b0f4849280bae1527bf28977eec5093)) - Merge branch 'davidkna-admin-sec' ([`3d0e2c2`](https://github.com/Byron/gitoxide/commit/3d0e2c2d4ebdbe3dff01846aac3375128353a2e1))
diff --git a/git-revision/CHANGELOG.md b/git-revision/CHANGELOG.md index 7f776d51865..331ee135def 100644 --- a/git-revision/CHANGELOG.md +++ b/git-revision/CHANGELOG.md @@ -5,6 +5,42 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +This is a maintenance release with no functional changes. + +### Commit Statistics + + + + - 7 commits contributed to the release over the course of 38 calendar days. + - 39 days passed between releases. + - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 1 unique issue was worked on: [#427](https://github.com/Byron/gitoxide/issues/427) + +### Thanks Clippy + + + +[Clippy](https://github.com/rust-lang/rust-clippy) helped 1 time to make code idiomatic. + +### Commit Details + + + +
view details + + * **[#427](https://github.com/Byron/gitoxide/issues/427)** + - Support for explaining all navitation ([`ace9c89`](https://github.com/Byron/gitoxide/commit/ace9c8953bebc4a808c639e365010ed53c031622)) + - Handle lonely tilde gracefully ([`6fb834e`](https://github.com/Byron/gitoxide/commit/6fb834e06639febbe67a46e702cd523c4e7bd2a7)) + - refactor ([`1a15e12`](https://github.com/Byron/gitoxide/commit/1a15e120a75d29b3d3f7615af1a66a033dfd3c8b)) + * **Uncategorized** + - make fmt ([`0700b09`](https://github.com/Byron/gitoxide/commit/0700b09d6828849fa2470df89af1f75a67bfb27d)) + - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) + - Merge branch 'normalize-values' ([`4e8cc7a`](https://github.com/Byron/gitoxide/commit/4e8cc7a5b447656c744cd84e6521e620d0479acb)) + - thanks clippy ([`e1003d5`](https://github.com/Byron/gitoxide/commit/e1003d5fdee5d4439c0cf0286c67dec9b5e34f53)) +
+ ## 0.2.1 (2022-06-13) ### New Features @@ -15,7 +51,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 75 commits contributed to the release over the course of 5 calendar days. + - 76 commits contributed to the release over the course of 5 calendar days. - 25 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#427](https://github.com/Byron/gitoxide/issues/427) @@ -99,6 +135,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - refactor ([`efc05e1`](https://github.com/Byron/gitoxide/commit/efc05e11fa2ec11952b06080ba76387a4c11c3b4)) - A basis for 'pure' parsing of rev-specs ([`29ab704`](https://github.com/Byron/gitoxide/commit/29ab7049fd180fac2e443a99908db066c67938db)) * **Uncategorized** + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980)) - make fmt ([`c665aef`](https://github.com/Byron/gitoxide/commit/c665aef4270c5ee54da89ee015cc0affd6337608)) - thanks clippy ([`1bbd3f4`](https://github.com/Byron/gitoxide/commit/1bbd3f471d78e53a76b3e708c755fc9d72fc28fe)) diff --git a/git-sec/CHANGELOG.md b/git-sec/CHANGELOG.md index 0c6766a3163..2654270090e 100644 --- a/git-sec/CHANGELOG.md +++ b/git-sec/CHANGELOG.md @@ -5,6 +5,44 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### New Features + + - Support for SUDO_UID as fallback for ownership check on unix. + +### Bug Fixes + + - on windows, emit a `NotFound` io error, similar to what happens on unix. + That way code relying on this behaviour will work the same on both + platforms. + + On windows, this costs at an additional metadata check. + +### Commit Statistics + + + + - 5 commits contributed to the release over the course of 33 calendar days. + - 39 days passed between releases. + - 2 commits where understood as [conventional](https://www.conventionalcommits.org). + - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) + +### Commit Details + + + +
view details + + * **[#331](https://github.com/Byron/gitoxide/issues/331)** + - on windows, emit a `NotFound` io error, similar to what happens on unix. ([`9a1e982`](https://github.com/Byron/gitoxide/commit/9a1e9828e813ec1de68ac2e83a986c49c71c5dbe)) + - fix build after breaking changes in `git-path` ([`34aed2f`](https://github.com/Byron/gitoxide/commit/34aed2fb608df79bdc56b244f7ac216f46322e5f)) + * **Uncategorized** + - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) + - Support for SUDO_UID as fallback for ownership check on unix. ([`3d16c36`](https://github.com/Byron/gitoxide/commit/3d16c36d7288d9a5fae5b9d23715e043d4d8ce76)) + - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) +
+ ## 0.2.0 (2022-06-13) ### New Features (BREAKING) @@ -16,7 +54,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 4 commits contributed to the release over the course of 15 calendar days. + - 5 commits contributed to the release over the course of 15 calendar days. - 16 days passed between releases. - 1 commit where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -28,6 +66,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
view details * **Uncategorized** + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980)) - dependency upgrades ([`a1981d4`](https://github.com/Byron/gitoxide/commit/a1981d48e98e51445d8413c615c6eccfb91cf05a)) - Merge branch 'main' into davidkna-envopen ([`bc0abc6`](https://github.com/Byron/gitoxide/commit/bc0abc643d3329f885f250b6880560dec861150f)) diff --git a/git-tempfile/CHANGELOG.md b/git-tempfile/CHANGELOG.md index bc17e2ca4b0..c57faa3042d 100644 --- a/git-tempfile/CHANGELOG.md +++ b/git-tempfile/CHANGELOG.md @@ -5,6 +5,36 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +This is a maintenance release with no functional changes. + +### Commit Statistics + + + + - 2 commits contributed to the release over the course of 21 calendar days. + - 110 days passed between releases. + - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Thanks Clippy + + + +[Clippy](https://github.com/rust-lang/rust-clippy) helped 1 time to make code idiomatic. + +### Commit Details + + + +
view details + + * **Uncategorized** + - Merge branch 'normalize-values' ([`4e8cc7a`](https://github.com/Byron/gitoxide/commit/4e8cc7a5b447656c744cd84e6521e620d0479acb)) + - thanks clippy ([`e1003d5`](https://github.com/Byron/gitoxide/commit/e1003d5fdee5d4439c0cf0286c67dec9b5e34f53)) +
+ ## 2.0.1 (2022-04-03) A maintenance release without any changes on the surface. @@ -13,7 +43,7 @@ A maintenance release without any changes on the surface. - - 4 commits contributed to the release over the course of 42 calendar days. + - 5 commits contributed to the release over the course of 42 calendar days. - 44 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 3 unique issues were worked on: [#298](https://github.com/Byron/gitoxide/issues/298), [#328](https://github.com/Byron/gitoxide/issues/328), [#364](https://github.com/Byron/gitoxide/issues/364) @@ -31,6 +61,7 @@ A maintenance release without any changes on the surface. * **[#364](https://github.com/Byron/gitoxide/issues/364)** - update changelogs prior to release ([`746a676`](https://github.com/Byron/gitoxide/commit/746a676056cd4907da7137a00798344b5bdb4419)) * **Uncategorized** + - Release git-diff v0.14.0, git-bitmap v0.1.0, git-index v0.2.0, git-tempfile v2.0.1, git-lock v2.0.0, git-mailmap v0.1.0, git-traverse v0.13.0, git-pack v0.17.0, git-quote v0.2.0, git-odb v0.27.0, git-packetline v0.12.4, git-url v0.4.0, git-transport v0.16.0, git-protocol v0.15.0, git-ref v0.12.0, git-worktree v0.1.0, git-repository v0.15.0, cargo-smart-release v0.9.0, safety bump 5 crates ([`e58dc30`](https://github.com/Byron/gitoxide/commit/e58dc3084cf17a9f618ae3a6554a7323e44428bf)) - make fmt ([`7cf3545`](https://github.com/Byron/gitoxide/commit/7cf354509b545f7e7c99e159b5989ddfbe86273d))
diff --git a/git-transport/CHANGELOG.md b/git-transport/CHANGELOG.md index 5940e2e5ba2..10564f263da 100644 --- a/git-transport/CHANGELOG.md +++ b/git-transport/CHANGELOG.md @@ -5,6 +5,37 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +This is a maintenance release with no functional changes. + +### Commit Statistics + + + + - 3 commits contributed to the release over the course of 33 calendar days. + - 39 days passed between releases. + - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Thanks Clippy + + + +[Clippy](https://github.com/rust-lang/rust-clippy) helped 1 time to make code idiomatic. + +### Commit Details + + + +
view details + + * **Uncategorized** + - Merge branch 'normalize-values' ([`4e8cc7a`](https://github.com/Byron/gitoxide/commit/4e8cc7a5b447656c744cd84e6521e620d0479acb)) + - thanks clippy ([`e1003d5`](https://github.com/Byron/gitoxide/commit/e1003d5fdee5d4439c0cf0286c67dec9b5e34f53)) + - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) +
+ ## 0.18.0 (2022-06-13) A maintenance release without user-facing changes. @@ -13,7 +44,7 @@ A maintenance release without user-facing changes. - - 1 commit contributed to the release. + - 2 commits contributed to the release. - 25 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +56,7 @@ A maintenance release without user-facing changes.
view details * **Uncategorized** + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980))
diff --git a/git-traverse/CHANGELOG.md b/git-traverse/CHANGELOG.md index c181e035002..640dfdc2f1e 100644 --- a/git-traverse/CHANGELOG.md +++ b/git-traverse/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +This is a maintenance release with no functional changes. + ## 0.15.0 (2022-05-18) A maintenance release without user-facing changes. @@ -13,7 +17,7 @@ A maintenance release without user-facing changes. - - 5 commits contributed to the release over the course of 34 calendar days. + - 6 commits contributed to the release over the course of 34 calendar days. - 43 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 2 unique issues were worked on: [#301](https://github.com/Byron/gitoxide/issues/301), [#384](https://github.com/Byron/gitoxide/issues/384) @@ -31,6 +35,7 @@ A maintenance release without user-facing changes. - add archive files via git-lfs ([`7202a1c`](https://github.com/Byron/gitoxide/commit/7202a1c4734ad904c026ee3e4e2143c0461d51a2)) - auto-set commit.gpgsign=false when executing git ([`c23feb6`](https://github.com/Byron/gitoxide/commit/c23feb64ad157180cfba8a11c882b829733ea8f6)) * **Uncategorized** + - Release git-ref v0.13.0, git-discover v0.1.0, git-index v0.3.0, git-mailmap v0.2.0, git-traverse v0.15.0, git-pack v0.19.0, git-odb v0.29.0, git-packetline v0.12.5, git-url v0.5.0, git-transport v0.17.0, git-protocol v0.16.0, git-revision v0.2.0, git-worktree v0.2.0, git-repository v0.17.0 ([`349c590`](https://github.com/Byron/gitoxide/commit/349c5904b0dac350838a896759d51576b66880a7)) - Release git-hash v0.9.4, git-features v0.21.0, git-actor v0.10.0, git-glob v0.3.0, git-path v0.1.1, git-attributes v0.1.0, git-sec v0.1.0, git-config v0.3.0, git-credentials v0.1.0, git-validate v0.5.4, git-object v0.19.0, git-diff v0.16.0, git-lock v2.1.0, git-ref v0.13.0, git-discover v0.1.0, git-index v0.3.0, git-mailmap v0.2.0, git-traverse v0.15.0, git-pack v0.19.0, git-odb v0.29.0, git-packetline v0.12.5, git-url v0.5.0, git-transport v0.17.0, git-protocol v0.16.0, git-revision v0.2.0, git-worktree v0.2.0, git-repository v0.17.0, safety bump 20 crates ([`654cf39`](https://github.com/Byron/gitoxide/commit/654cf39c92d5aa4c8d542a6cadf13d4acef6a78e))
@@ -86,7 +91,7 @@ A maintenance release without user-facing changes. - - 12 commits contributed to the release over the course of 68 calendar days. + - 11 commits contributed to the release over the course of 68 calendar days. - 69 days passed between releases. - 1 commit where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#364](https://github.com/Byron/gitoxide/issues/364) @@ -98,10 +103,10 @@ A maintenance release without user-facing changes.
view details * **[#364](https://github.com/Byron/gitoxide/issues/364)** - - fix docs ([`0a1caeb`](https://github.com/Byron/gitoxide/commit/0a1caebfcb5fe019fc8db3b51d16908da37be59f)) - - require `Ancestors` traversal `find()` to return `Result` ([`83746f6`](https://github.com/Byron/gitoxide/commit/83746f613a559a86a0ea81370fca3f094bc81e35)) - Full error handling for CommitRefIter ([`b94471a`](https://github.com/Byron/gitoxide/commit/b94471a0ced50204156cf5d4126c676f0258a5eb)) - More speedy access to author/committer ([`6129607`](https://github.com/Byron/gitoxide/commit/61296077cebaaf2eb939fa6082121304bc6cf39b)) + - fix docs ([`0a1caeb`](https://github.com/Byron/gitoxide/commit/0a1caebfcb5fe019fc8db3b51d16908da37be59f)) + - require `Ancestors` traversal `find()` to return `Result` ([`83746f6`](https://github.com/Byron/gitoxide/commit/83746f613a559a86a0ea81370fca3f094bc81e35)) * **Uncategorized** - Release git-diff v0.14.0, git-bitmap v0.1.0, git-index v0.2.0, git-tempfile v2.0.1, git-lock v2.0.0, git-mailmap v0.1.0, git-traverse v0.13.0, git-pack v0.17.0, git-quote v0.2.0, git-odb v0.27.0, git-packetline v0.12.4, git-url v0.4.0, git-transport v0.16.0, git-protocol v0.15.0, git-ref v0.12.0, git-worktree v0.1.0, git-repository v0.15.0, cargo-smart-release v0.9.0, safety bump 5 crates ([`e58dc30`](https://github.com/Byron/gitoxide/commit/e58dc3084cf17a9f618ae3a6554a7323e44428bf)) - Merge branch 'for-onefetch' ([`8e5cb65`](https://github.com/Byron/gitoxide/commit/8e5cb65da75036a13ed469334e7ae6c527d9fff6)) @@ -109,7 +114,6 @@ A maintenance release without user-facing changes. - Merge branch 'svetli-n-refactor_git_config_tests' ([`babaa9f`](https://github.com/Byron/gitoxide/commit/babaa9f5725ab8cdf14e0c7e002c2e1de09de103)) - adapt to breaking changes in git-actor ([`40c48c3`](https://github.com/Byron/gitoxide/commit/40c48c390eb796b427ebd516dde92e9538ce5fb7)) - Merge branch 'AP2008-implement-worktree' ([`f32c669`](https://github.com/Byron/gitoxide/commit/f32c669bc519d59a1f1d90d61cc48a422c86aede)) - - Release git-bitmap v0.0.1, git-hash v0.9.0, git-features v0.19.0, git-index v0.1.0, safety bump 9 crates ([`4624725`](https://github.com/Byron/gitoxide/commit/4624725f54a34dd6b35d3632fb3516965922f60a)) - Merge branch 'index-information' ([`025f157`](https://github.com/Byron/gitoxide/commit/025f157de10a509a4b36a9aed41de80487e8c15c))
@@ -142,7 +146,7 @@ A maintenance release without user-facing changes. - - 15 commits contributed to the release over the course of 51 calendar days. + - 16 commits contributed to the release over the course of 51 calendar days. - 55 days passed between releases. - 5 commits where understood as [conventional](https://www.conventionalcommits.org). - 3 unique issues were worked on: [#215](https://github.com/Byron/gitoxide/issues/215), [#266](https://github.com/Byron/gitoxide/issues/266), [#270](https://github.com/Byron/gitoxide/issues/270) @@ -175,6 +179,7 @@ A maintenance release without user-facing changes. - Release git-features v0.19.1, git-actor v0.8.0, git-config v0.1.10, git-object v0.17.0, git-diff v0.13.0, git-tempfile v1.0.4, git-chunk v0.3.0, git-traverse v0.12.0, git-pack v0.16.0, git-odb v0.26.0, git-packetline v0.12.3, git-url v0.3.5, git-transport v0.15.0, git-protocol v0.14.0, git-ref v0.11.0, git-repository v0.14.0, cargo-smart-release v0.8.0 ([`d78aab7`](https://github.com/Byron/gitoxide/commit/d78aab7b9c4b431d437ac70a0ef96263acb64e46)) - Release git-hash v0.9.1, git-features v0.19.1, git-actor v0.8.0, git-config v0.1.10, git-object v0.17.0, git-diff v0.13.0, git-tempfile v1.0.4, git-chunk v0.3.0, git-traverse v0.12.0, git-pack v0.16.0, git-odb v0.26.0, git-packetline v0.12.3, git-url v0.3.5, git-transport v0.15.0, git-protocol v0.14.0, git-ref v0.11.0, git-repository v0.14.0, cargo-smart-release v0.8.0, safety bump 4 crates ([`373cbc8`](https://github.com/Byron/gitoxide/commit/373cbc877f7ad60dac682e57c52a7b90f108ebe3)) - prepar changelogs for cargo-smart-release release ([`8900d69`](https://github.com/Byron/gitoxide/commit/8900d699226eb0995be70d66249827ce348261df)) + - Release git-bitmap v0.0.1, git-hash v0.9.0, git-features v0.19.0, git-index v0.1.0, safety bump 9 crates ([`4624725`](https://github.com/Byron/gitoxide/commit/4624725f54a34dd6b35d3632fb3516965922f60a)) - thanks clippy ([`03d0660`](https://github.com/Byron/gitoxide/commit/03d06609002933f23abe37a7208841cd152bd63d)) - Add sorting mode to ancestor traversal #270 ([`eb36a3d`](https://github.com/Byron/gitoxide/commit/eb36a3dda83a46ad59078a904f4e277f298a24e1)) - ensure tests use 'merge.ff false' and recreate fixtures on each run ([`1d5ab44`](https://github.com/Byron/gitoxide/commit/1d5ab44145ccbc2064ee8cc7acebb62db82c45aa)) @@ -294,7 +299,7 @@ Some module paths have been removed to avoid path duplication, possibly leading - - 24 commits contributed to the release over the course of 30 calendar days. + - 24 commits contributed to the release over the course of 32 calendar days. - 36 days passed between releases. - 4 commits where understood as [conventional](https://www.conventionalcommits.org). - 3 unique issues were worked on: [#164](https://github.com/Byron/gitoxide/issues/164), [#196](https://github.com/Byron/gitoxide/issues/196), [#198](https://github.com/Byron/gitoxide/issues/198) diff --git a/git-url/CHANGELOG.md b/git-url/CHANGELOG.md index c081a4901f4..ae674272444 100644 --- a/git-url/CHANGELOG.md +++ b/git-url/CHANGELOG.md @@ -5,6 +5,35 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Changed (BREAKING) + + - `From<&[u8]>` is now `From<&BStr>` + This better represents the meaning of the input, and simplifies + interactions with `git-config`. + +### Commit Statistics + + + + - 3 commits contributed to the release over the course of 33 calendar days. + - 39 days passed between releases. + - 1 commit where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) + - `From<&[u8]>` is now `From<&BStr>` ([`ffc4a85`](https://github.com/Byron/gitoxide/commit/ffc4a85b9a914b685d7ab528b30f2a3eefb44094)) + - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) +
+ ## 0.6.0 (2022-06-13) A maintenance release without user-facing changes. @@ -13,7 +42,7 @@ A maintenance release without user-facing changes. - - 1 commit contributed to the release. + - 2 commits contributed to the release. - 25 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +54,7 @@ A maintenance release without user-facing changes.
view details * **Uncategorized** + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980))
diff --git a/git-worktree/CHANGELOG.md b/git-worktree/CHANGELOG.md index 61b0fc61fb3..fabca2640bd 100644 --- a/git-worktree/CHANGELOG.md +++ b/git-worktree/CHANGELOG.md @@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +This is a maintenance release with no functional changes. + +### Commit Statistics + + + + - 2 commits contributed to the release over the course of 33 calendar days. + - 39 days passed between releases. + - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - make it harder to forget documentation in git-worktree ([`15d87ee`](https://github.com/Byron/gitoxide/commit/15d87ee99ef269985e8f378bb2ab9c8931e8fd7d)) + - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) +
+ ## 0.3.0 (2022-06-13) A maintenance release without user-facing changes. @@ -13,7 +37,7 @@ A maintenance release without user-facing changes. - - 2 commits contributed to the release. + - 3 commits contributed to the release. - 25 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +49,7 @@ A maintenance release without user-facing changes.
view details * **Uncategorized** + - Release git-worktree v0.3.0, git-repository v0.19.0 ([`0d8e856`](https://github.com/Byron/gitoxide/commit/0d8e8566dc5c6955487d67e235f86fbc75a3a88a)) - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980))
diff --git a/gitoxide-core/CHANGELOG.md b/gitoxide-core/CHANGELOG.md index 8d10a08c40d..2b0d07966e6 100644 --- a/gitoxide-core/CHANGELOG.md +++ b/gitoxide-core/CHANGELOG.md @@ -7,6 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Changed (BREAKING) + + - remove local-time-support feature toggle. + We treat local time as default feature without a lot of fuzz, and + will eventually document that definitive support needs a compile + time switch in the compiler (`--cfg unsound_local_offset` or something). + + One day it will perish. Failure is possible anyway and we will write + code to deal with it while minimizing the amount of system time + fetches when asking for the current local time. + - Associate `file::Metadata` with each `File`. + This is the first step towards knowing more about the source of each + value to filter them based on some properties. + + This breaks various methods handling the instantiation of configuration + files as `file::Metadata` typically has to be provided by the caller + now or be associated with each path to read configuration from. + +### New Features + + - `gix config` with section and sub-section filtering. + - `gix config` lists all entries of all configuration files git considers. + Filters allow to narrow down the output. + - Use `git-config` to write config file on initialization, including `logallrefupdates` and `precomposeunicode`. + ### Bug Fixes - `ein tool organize` now ignores worktrees. @@ -19,10 +44,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 35 commits contributed to the release over the course of 61 calendar days. - - 67 days passed between releases. - - 1 commit where understood as [conventional](https://www.conventionalcommits.org). - - 1 unique issue was worked on: [#301](https://github.com/Byron/gitoxide/issues/301) + - 60 commits contributed to the release over the course of 101 calendar days. + - 107 days passed between releases. + - 6 commits where understood as [conventional](https://www.conventionalcommits.org). + - 3 unique issues were worked on: [#301](https://github.com/Byron/gitoxide/issues/301), [#331](https://github.com/Byron/gitoxide/issues/331), [#427](https://github.com/Byron/gitoxide/issues/427) ### Thanks Clippy @@ -56,7 +81,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - fix build ([`ffe92ca`](https://github.com/Byron/gitoxide/commit/ffe92ca3cf066c09020bf6fa875bea06552cbd0d)) - Make attributes and ignore configuration possible, but… ([`8a75fd7`](https://github.com/Byron/gitoxide/commit/8a75fd745a194786f0da7c1fd660211446ea51f7)) - make fmt ([`50ff7aa`](https://github.com/Byron/gitoxide/commit/50ff7aa7fa86e5e2a94fb15aab86470532ac3f51)) + * **[#331](https://github.com/Byron/gitoxide/issues/331)** + - Group similarly named sections together more by not separating them with newline ([`4c69541`](https://github.com/Byron/gitoxide/commit/4c69541cd7192ebd5bdd696a833992d5a52cd9b6)) + - Make lossy-configuration configurable ([`b0e4da6`](https://github.com/Byron/gitoxide/commit/b0e4da621114d188a73b9f40757f59564da3c079)) + - update README with `gix config` information ([`c19d9fd`](https://github.com/Byron/gitoxide/commit/c19d9fdc569528972f7f6255760ae86ba99848cc)) + - remove local-time-support feature toggle. ([`89a41bf`](https://github.com/Byron/gitoxide/commit/89a41bf2b37db29b9983b4e5492cfd67ed490b23)) + - `gix config` with section and sub-section filtering. ([`eda39ec`](https://github.com/Byron/gitoxide/commit/eda39ec7d736d49af1ad9e2ad775e4aa12b264b7)) + - `gix config` lists all entries of all configuration files git considers. ([`d99453e`](https://github.com/Byron/gitoxide/commit/d99453ebeb970ed493be236def299d1e82b01f83)) + - Associate `file::Metadata` with each `File`. ([`6f4eea9`](https://github.com/Byron/gitoxide/commit/6f4eea936d64fb9827277c160f989168e7b1dba2)) + - Use `git-config` to write config file on initialization, including `logallrefupdates` and `precomposeunicode`. ([`7f67b23`](https://github.com/Byron/gitoxide/commit/7f67b23b9462b805591b1fe5a8406f8d7404f372)) + - adjust to changes in `git-config` ([`c52cb95`](https://github.com/Byron/gitoxide/commit/c52cb958f85b533e791ec6b38166a9d819f12dd4)) + * **[#427](https://github.com/Byron/gitoxide/issues/427)** + - Handle 'kind' changes which completes 'explain' ([`45022a0`](https://github.com/Byron/gitoxide/commit/45022a0efe6e71404868a7ba816c6972050098b9)) + - Support for explaining all navitation ([`ace9c89`](https://github.com/Byron/gitoxide/commit/ace9c8953bebc4a808c639e365010ed53c031622)) + - start navigation implementation ([`ea1c009`](https://github.com/Byron/gitoxide/commit/ea1c009e1b064deccf242fc60876a8535f4814b5)) + - Implement `Revision` anchors ([`a1f0e3d`](https://github.com/Byron/gitoxide/commit/a1f0e3d463397be201f4df40184ce38b830f3bde)) + - basic infrastructure for delegate implementation ([`d3c0bc6`](https://github.com/Byron/gitoxide/commit/d3c0bc6e8d7764728f4e10500bb895152ccd0b0b)) + - Hookup explain command ([`1049b00`](https://github.com/Byron/gitoxide/commit/1049b00eaa261a67f060eaca4eb50dcda831eafd)) * **Uncategorized** + - make fmt ([`0700b09`](https://github.com/Byron/gitoxide/commit/0700b09d6828849fa2470df89af1f75a67bfb27d)) + - fix build after changes to `git-url` and `git-config` ([`1f02420`](https://github.com/Byron/gitoxide/commit/1f0242034071ce317743df75cc685e7428b604b0)) + - Merge branch 'main' into cont_include_if ([`daa71c3`](https://github.com/Byron/gitoxide/commit/daa71c3b753c6d76a3d652c29237906b3e28728f)) + - Merge branch 'main' into cont_include_if ([`0e9df36`](https://github.com/Byron/gitoxide/commit/0e9df364c4cddf006b1de18b8d167319b7cc1186)) + - Merge branch 'main' into cont_include_if ([`41ea8ba`](https://github.com/Byron/gitoxide/commit/41ea8ba78e74f5c988148367386a1f4f304cb951)) + - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) + - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980)) + - Merge branch 'main' into svetli-n-cont_include_if ([`315c87e`](https://github.com/Byron/gitoxide/commit/315c87e18c6cac0fafa7b4e59fdd3c076a58a45a)) + - Refact. ([`a342e53`](https://github.com/Byron/gitoxide/commit/a342e53dac58cea1787a94eaa1a9d24fb1389df2)) - `ein tool organize` now ignores worktrees. ([`5667a7c`](https://github.com/Byron/gitoxide/commit/5667a7c1bafcfdff1a278b3ad0e1198cd0cc4653)) - Revert "ignore worktrees in 'organize', but…" ([`f59471f`](https://github.com/Byron/gitoxide/commit/f59471f0cf883176594ab4635248b4029bcb6caf)) - ignore worktrees in 'organize', but… ([`e501c9e`](https://github.com/Byron/gitoxide/commit/e501c9e6348e1595fee4a5e0bd712fc2433b10df)) From 4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 22 Jul 2022 16:50:21 +0800 Subject: [PATCH 357/366] Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates SAFETY BUMP: git-actor v0.11.0, git-attributes v0.3.0, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0 --- CHANGELOG.md | 2 +- Cargo.lock | 26 +++++++++++++------------- Cargo.toml | 2 +- experiments/diffing/Cargo.toml | 2 +- git-actor/CHANGELOG.md | 6 ++++-- git-actor/Cargo.toml | 6 +++--- git-attributes/CHANGELOG.md | 5 +++-- git-attributes/Cargo.toml | 6 +++--- git-commitgraph/CHANGELOG.md | 5 +++-- git-commitgraph/Cargo.toml | 4 ++-- git-config/CHANGELOG.md | 11 ++++++++--- git-config/Cargo.toml | 6 +++--- git-credentials/CHANGELOG.md | 5 +++-- git-date/CHANGELOG.md | 6 ++++-- git-date/Cargo.toml | 2 +- git-diff/CHANGELOG.md | 5 +++-- git-diff/Cargo.toml | 6 +++--- git-discover/CHANGELOG.md | 5 +++-- git-discover/Cargo.toml | 4 ++-- git-features/CHANGELOG.md | 5 +++-- git-features/Cargo.toml | 4 ++-- git-glob/CHANGELOG.md | 5 +++-- git-glob/Cargo.toml | 2 +- git-hash/CHANGELOG.md | 5 +++-- git-hash/Cargo.toml | 2 +- git-index/CHANGELOG.md | 21 ++++++++++++++++++++- git-index/Cargo.toml | 8 ++++---- git-mailmap/CHANGELOG.md | 21 ++++++++++++++++++++- git-mailmap/Cargo.toml | 4 ++-- git-object/CHANGELOG.md | 5 +++-- git-object/Cargo.toml | 8 ++++---- git-odb/CHANGELOG.md | 5 +++-- git-odb/Cargo.toml | 8 ++++---- git-pack/CHANGELOG.md | 5 +++-- git-pack/Cargo.toml | 12 ++++++------ git-path/CHANGELOG.md | 5 +++-- git-path/Cargo.toml | 2 +- git-protocol/CHANGELOG.md | 5 +++-- git-protocol/Cargo.toml | 4 ++-- git-ref/CHANGELOG.md | 5 +++-- git-ref/Cargo.toml | 10 +++++----- git-repository/CHANGELOG.md | 5 +++-- git-repository/Cargo.toml | 24 ++++++++++++------------ git-revision/CHANGELOG.md | 5 +++-- git-revision/Cargo.toml | 8 ++++---- git-sec/CHANGELOG.md | 5 +++-- git-sec/Cargo.toml | 2 +- git-tempfile/CHANGELOG.md | 5 +++-- git-tempfile/Cargo.toml | 2 +- git-transport/CHANGELOG.md | 5 +++-- git-transport/Cargo.toml | 2 +- git-traverse/CHANGELOG.md | 21 ++++++++++++++++++++- git-traverse/Cargo.toml | 6 +++--- git-url/CHANGELOG.md | 5 +++-- git-url/Cargo.toml | 4 ++-- git-worktree/CHANGELOG.md | 5 +++-- git-worktree/Cargo.toml | 12 ++++++------ gitoxide-core/CHANGELOG.md | 5 +++-- gitoxide-core/Cargo.toml | 2 +- tests/tools/Cargo.toml | 2 +- 60 files changed, 239 insertions(+), 151 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89c08ecce64..c497f0f1085 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 0.13.0 (2022-07-22) ### New Features diff --git a/Cargo.lock b/Cargo.lock index 4a5a48b1629..e8cc7b680db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1067,7 +1067,7 @@ dependencies = [ [[package]] name = "git-actor" -version = "0.10.1" +version = "0.11.0" dependencies = [ "bstr", "btoi", @@ -1180,7 +1180,7 @@ dependencies = [ [[package]] name = "git-date" -version = "0.0.1" +version = "0.0.2" dependencies = [ "bstr", "document-features", @@ -1192,7 +1192,7 @@ dependencies = [ [[package]] name = "git-diff" -version = "0.16.0" +version = "0.17.0" dependencies = [ "git-hash", "git-object", @@ -1220,7 +1220,7 @@ dependencies = [ [[package]] name = "git-features" -version = "0.21.1" +version = "0.22.0" dependencies = [ "bstr", "bytes", @@ -1248,7 +1248,7 @@ version = "0.0.0" [[package]] name = "git-glob" -version = "0.3.0" +version = "0.3.1" dependencies = [ "bitflags", "bstr", @@ -1259,7 +1259,7 @@ dependencies = [ [[package]] name = "git-hash" -version = "0.9.5" +version = "0.9.6" dependencies = [ "document-features", "git-testtools", @@ -1270,7 +1270,7 @@ dependencies = [ [[package]] name = "git-index" -version = "0.3.0" +version = "0.4.0" dependencies = [ "atoi", "bitflags", @@ -1304,7 +1304,7 @@ dependencies = [ [[package]] name = "git-mailmap" -version = "0.2.0" +version = "0.3.0" dependencies = [ "bstr", "git-actor", @@ -1319,7 +1319,7 @@ version = "0.0.0" [[package]] name = "git-object" -version = "0.19.0" +version = "0.20.0" dependencies = [ "bstr", "btoi", @@ -1410,7 +1410,7 @@ dependencies = [ [[package]] name = "git-path" -version = "0.3.0" +version = "0.4.0" dependencies = [ "bstr", "tempfile", @@ -1527,7 +1527,7 @@ dependencies = [ [[package]] name = "git-revision" -version = "0.2.1" +version = "0.3.0" dependencies = [ "bstr", "document-features", @@ -1566,7 +1566,7 @@ version = "0.0.0" [[package]] name = "git-tempfile" -version = "2.0.1" +version = "2.0.2" dependencies = [ "dashmap", "libc", @@ -1630,7 +1630,7 @@ dependencies = [ [[package]] name = "git-traverse" -version = "0.15.0" +version = "0.16.0" dependencies = [ "git-hash", "git-object", diff --git a/Cargo.toml b/Cargo.toml index 25c5780d593..6b1be5acf11 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,7 +83,7 @@ cache-efficiency-debug = ["git-features/cache-efficiency-debug"] anyhow = "1.0.42" gitoxide-core = { version = "^0.15.0", path = "gitoxide-core" } -git-features = { version = "^0.21.1", path = "git-features" } +git-features = { version = "^0.22.0", path = "git-features" } git-repository = { version = "^0.20.0", path = "git-repository", default-features = false } git-transport-for-configuration-only = { package = "git-transport", optional = true, version = "^0.19.0", path = "git-transport" } diff --git a/experiments/diffing/Cargo.toml b/experiments/diffing/Cargo.toml index 477ff1704ae..fede7fca83d 100644 --- a/experiments/diffing/Cargo.toml +++ b/experiments/diffing/Cargo.toml @@ -10,6 +10,6 @@ publish = false [dependencies] anyhow = "1" git-repository = { version = "^0.20.0", path = "../../git-repository", features = ["unstable"] } -git-features-for-config = { package = "git-features", version = "^0.21.0", path = "../../git-features", features = ["cache-efficiency-debug"] } +git-features-for-config = { package = "git-features", version = "^0.22.0", path = "../../git-features", features = ["cache-efficiency-debug"] } git2 = "0.14" rayon = "1.5.0" diff --git a/git-actor/CHANGELOG.md b/git-actor/CHANGELOG.md index afc26412f15..b08ea0cd7ee 100644 --- a/git-actor/CHANGELOG.md +++ b/git-actor/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 0.11.0 (2022-07-22) ### Changed (BREAKING) @@ -22,7 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 1 commit contributed to the release. + - 2 commits contributed to the release. - 39 days passed between releases. - 1 commit where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) @@ -35,6 +35,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * **[#331](https://github.com/Byron/gitoxide/issues/331)** - remove local-time-support feature toggle. ([`89a41bf`](https://github.com/Byron/gitoxide/commit/89a41bf2b37db29b9983b4e5492cfd67ed490b23)) + * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) ## 0.10.1 (2022-06-13) diff --git a/git-actor/Cargo.toml b/git-actor/Cargo.toml index 91c638e49fe..4458f663c36 100644 --- a/git-actor/Cargo.toml +++ b/git-actor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "git-actor" -version = "0.10.1" +version = "0.11.0" description = "A way to identify git actors" authors = ["Sebastian Thiel "] repository = "https://github.com/Byron/gitoxide" @@ -16,8 +16,8 @@ doctest = false serde1 = ["serde", "bstr/serde1", "git-date/serde1"] [dependencies] -git-features = { version = "^0.21.1", path = "../git-features", optional = true } -git-date = { version = "^0.0.1", path = "../git-date" } +git-features = { version = "^0.22.0", path = "../git-features", optional = true } +git-date = { version = "^0.0.2", path = "../git-date" } quick-error = "2.0.0" btoi = "0.4.2" diff --git a/git-attributes/CHANGELOG.md b/git-attributes/CHANGELOG.md index 1463123d819..3cc3d280df2 100644 --- a/git-attributes/CHANGELOG.md +++ b/git-attributes/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 0.3.0 (2022-07-22) This is a maintenance release with no functional changes. @@ -13,7 +13,7 @@ This is a maintenance release with no functional changes. - - 2 commits contributed to the release over the course of 33 calendar days. + - 3 commits contributed to the release over the course of 33 calendar days. - 39 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +25,7 @@ This is a maintenance release with no functional changes.
view details * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35))
diff --git a/git-attributes/Cargo.toml b/git-attributes/Cargo.toml index 4c5c2d64f71..4683ead10d9 100644 --- a/git-attributes/Cargo.toml +++ b/git-attributes/Cargo.toml @@ -18,10 +18,10 @@ serde1 = ["serde", "bstr/serde1", "git-glob/serde1", "compact_str/serde"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -git-features = { version = "^0.21.1", path = "../git-features" } -git-path = { version = "^0.3.0", path = "../git-path" } +git-features = { version = "^0.22.0", path = "../git-features" } +git-path = { version = "^0.4.0", path = "../git-path" } git-quote = { version = "^0.2.0", path = "../git-quote" } -git-glob = { version = "^0.3.0", path = "../git-glob" } +git-glob = { version = "^0.3.1", path = "../git-glob" } bstr = { version = "0.2.13", default-features = false, features = ["std"]} unicode-bom = "1.1.4" diff --git a/git-commitgraph/CHANGELOG.md b/git-commitgraph/CHANGELOG.md index 5b5e2797188..15b30223580 100644 --- a/git-commitgraph/CHANGELOG.md +++ b/git-commitgraph/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 0.8.0 (2022-07-22) A maintenance release without user-facing changes. @@ -13,7 +13,7 @@ A maintenance release without user-facing changes. - - 7 commits contributed to the release over the course of 99 calendar days. + - 8 commits contributed to the release over the course of 99 calendar days. - 110 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#384](https://github.com/Byron/gitoxide/issues/384) @@ -29,6 +29,7 @@ A maintenance release without user-facing changes. - add archive files via git-lfs ([`7202a1c`](https://github.com/Byron/gitoxide/commit/7202a1c4734ad904c026ee3e4e2143c0461d51a2)) - auto-set commit.gpgsign=false when executing git ([`c23feb6`](https://github.com/Byron/gitoxide/commit/c23feb64ad157180cfba8a11c882b829733ea8f6)) * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980)) diff --git a/git-commitgraph/Cargo.toml b/git-commitgraph/Cargo.toml index cb7f7d80b43..c457a182d97 100644 --- a/git-commitgraph/Cargo.toml +++ b/git-commitgraph/Cargo.toml @@ -17,8 +17,8 @@ doctest = false serde1 = ["serde", "git-hash/serde1", "bstr/serde1"] [dependencies] -git-features = { version = "^0.21.1", path = "../git-features", features = ["rustsha1"] } -git-hash = { version = "^0.9.5", path = "../git-hash" } +git-features = { version = "^0.22.0", path = "../git-features", features = ["rustsha1"] } +git-hash = { version = "^0.9.6", path = "../git-hash" } git-chunk = { version = "^0.3.0", path = "../git-chunk" } bstr = { version = "0.2.13", default-features = false, features = ["std"] } diff --git a/git-config/CHANGELOG.md b/git-config/CHANGELOG.md index bb852217ae3..95d07f62748 100644 --- a/git-config/CHANGELOG.md +++ b/git-config/CHANGELOG.md @@ -5,7 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 0.6.0 (2022-07-22) + + + + + ### New Features @@ -150,7 +155,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 It's more of a 'dumb' structure now than before, merely present to facilitate typical parsing than something special on its own. - remove `File::new()` method in favor of `File::default()`. - - rename `parse::event::List` to `parse::Events` - rename `parse::State` to `parse::event::List` - move `value::*` into the crate root, except for `Error` and `normalize_*()`. @@ -225,7 +229,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 312 commits contributed to the release over the course of 33 calendar days. + - 313 commits contributed to the release over the course of 33 calendar days. - 39 days passed between releases. - 93 commits where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) @@ -483,6 +487,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - conforming subsection parsing handling backslashes like git ([`6366148`](https://github.com/Byron/gitoxide/commit/6366148f538ee03314dd866e083157de810d4ad4)) - Only copy pattern if required ([`b3a752a`](https://github.com/Byron/gitoxide/commit/b3a752a0a873cf9d685e1893c8d35255d7f7323a)) * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - thanks clippy ([`fddc720`](https://github.com/Byron/gitoxide/commit/fddc7206476423a6964d61acd060305572ecd02b)) - thanks fuzzy ([`15a379a`](https://github.com/Byron/gitoxide/commit/15a379a85d59d83f3a0512b9e9fbff1774c9f561)) - thanks clippy ([`15fee74`](https://github.com/Byron/gitoxide/commit/15fee74fdfb5fc84349ac103cd5727332f3d2230)) diff --git a/git-config/Cargo.toml b/git-config/Cargo.toml index 6a918a43991..8bc71da6630 100644 --- a/git-config/Cargo.toml +++ b/git-config/Cargo.toml @@ -15,11 +15,11 @@ include = ["src/**/*", "LICENSE-*", "README.md", "CHANGELOG.md"] serde1 = ["serde", "bstr/serde1", "git-sec/serde1", "git-ref/serde1", "git-glob/serde1"] [dependencies] -git-features = { version = "^0.21.1", path = "../git-features"} -git-path = { version = "^0.3.0", path = "../git-path" } +git-features = { version = "^0.22.0", path = "../git-features"} +git-path = { version = "^0.4.0", path = "../git-path" } git-sec = { version = "^0.3.0", path = "../git-sec" } git-ref = { version = "^0.15.0", path = "../git-ref" } -git-glob = { version = "0.3.0", path = "../git-glob" } +git-glob = { version = "^0.3.1", path = "../git-glob" } nom = { version = "7", default_features = false, features = [ "std" ] } memchr = "2" diff --git a/git-credentials/CHANGELOG.md b/git-credentials/CHANGELOG.md index 4add543252b..a7ba72a512a 100644 --- a/git-credentials/CHANGELOG.md +++ b/git-credentials/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 0.3.0 (2022-07-22) This is a maintenance release with no functional changes. @@ -13,7 +13,7 @@ This is a maintenance release with no functional changes. - - 2 commits contributed to the release over the course of 33 calendar days. + - 3 commits contributed to the release over the course of 33 calendar days. - 39 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +25,7 @@ This is a maintenance release with no functional changes.
view details * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35))
diff --git a/git-date/CHANGELOG.md b/git-date/CHANGELOG.md index 3bf540c600c..b981067a844 100644 --- a/git-date/CHANGELOG.md +++ b/git-date/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 0.0.2 (2022-07-22) ### New Features @@ -20,7 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 2 commits contributed to the release. + - 3 commits contributed to the release. - 39 days passed between releases. - 2 commits where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) @@ -34,6 +34,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * **[#331](https://github.com/Byron/gitoxide/issues/331)** - initialize `Time` from `now_utc` and `now_local` ([`c76fde7`](https://github.com/Byron/gitoxide/commit/c76fde7de278b49ded13b655d5345e4eb8c1b134)) - `Time::is_set()` to see if the time is more than just the default. ([`aeda76e`](https://github.com/Byron/gitoxide/commit/aeda76ed500d2edba62747d667227f2664edd267)) + * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) ## 0.0.1 (2022-06-13) diff --git a/git-date/Cargo.toml b/git-date/Cargo.toml index d3e5a9e934f..babccb1fc69 100644 --- a/git-date/Cargo.toml +++ b/git-date/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "git-date" -version = "0.0.1" +version = "0.0.2" repository = "https://github.com/Byron/gitoxide" license = "MIT/Apache-2.0" description = "A WIP crate of the gitoxide project parsing dates the way git does" diff --git a/git-diff/CHANGELOG.md b/git-diff/CHANGELOG.md index da53509948e..d6b59677def 100644 --- a/git-diff/CHANGELOG.md +++ b/git-diff/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 0.17.0 (2022-07-22) This is a maintenance release with no functional changes. @@ -13,7 +13,7 @@ This is a maintenance release with no functional changes. - - 3 commits contributed to the release over the course of 30 calendar days. + - 4 commits contributed to the release over the course of 30 calendar days. - 64 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +25,7 @@ This is a maintenance release with no functional changes.
view details * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - Merge branch 'main' into cont_include_if ([`daa71c3`](https://github.com/Byron/gitoxide/commit/daa71c3b753c6d76a3d652c29237906b3e28728f)) - Merge branch 'main' into cont_include_if ([`0e9df36`](https://github.com/Byron/gitoxide/commit/0e9df364c4cddf006b1de18b8d167319b7cc1186)) - generally avoid using `target_os = "windows"` in favor of `cfg(windows)` and negations ([`91d5402`](https://github.com/Byron/gitoxide/commit/91d54026a61c2aae5e3e1341d271acf16478cd83)) diff --git a/git-diff/Cargo.toml b/git-diff/Cargo.toml index 0a3b489e686..be52d6aa112 100644 --- a/git-diff/Cargo.toml +++ b/git-diff/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "git-diff" -version = "0.16.0" +version = "0.17.0" repository = "https://github.com/Byron/gitoxide" license = "MIT/Apache-2.0" description = "Calculate differences between various git objects" @@ -14,8 +14,8 @@ doctest = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -git-hash = { version = "^0.9.4", path = "../git-hash" } -git-object = { version = "^0.19.0", path = "../git-object" } +git-hash = { version = "^0.9.6", path = "../git-hash" } +git-object = { version = "^0.20.0", path = "../git-object" } quick-error = "2.0.0" [dev-dependencies] diff --git a/git-discover/CHANGELOG.md b/git-discover/CHANGELOG.md index 46a5f193ef5..52bc2bc5aab 100644 --- a/git-discover/CHANGELOG.md +++ b/git-discover/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 0.3.0 (2022-07-22) ### New Features @@ -15,7 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 5 commits contributed to the release over the course of 33 calendar days. + - 6 commits contributed to the release over the course of 33 calendar days. - 39 days passed between releases. - 1 commit where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) @@ -30,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - add `DOT_GIT_DIR` constant, containing the name ".git". ([`0103501`](https://github.com/Byron/gitoxide/commit/010350180459aec41132c960ddafc7b81dd9c04d)) - adjustments due to breaking changes in `git_path` ([`4420ae9`](https://github.com/Byron/gitoxide/commit/4420ae932d5b20a9662a6d36353a27111b5cd672)) * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - Remove another special case on windows due to canonicalize() ([`61abb0b`](https://github.com/Byron/gitoxide/commit/61abb0b006292d2122784b032e198cc716fb7b92)) - Use git_path::realpath in all places that allow it right now ([`229dc91`](https://github.com/Byron/gitoxide/commit/229dc917fc7d9241b85e5818260a6fbdd3a5daaa)) - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) diff --git a/git-discover/Cargo.toml b/git-discover/Cargo.toml index a4a85aa2f4f..744b6e52f45 100644 --- a/git-discover/Cargo.toml +++ b/git-discover/Cargo.toml @@ -15,9 +15,9 @@ doctest = false [dependencies] git-sec = { version = "^0.3.0", path = "../git-sec", features = ["thiserror"] } -git-path = { version = "^0.3.0", path = "../git-path" } +git-path = { version = "^0.4.0", path = "../git-path" } git-ref = { version = "^0.15.0", path = "../git-ref" } -git-hash = { version = "^0.9.5", path = "../git-hash" } +git-hash = { version = "^0.9.6", path = "../git-hash" } bstr = { version = "0.2.13", default-features = false, features = ["std", "unicode"] } thiserror = "1.0.26" diff --git a/git-features/CHANGELOG.md b/git-features/CHANGELOG.md index 95649510624..7c94a266a43 100644 --- a/git-features/CHANGELOG.md +++ b/git-features/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 0.22.0 (2022-07-22) ### New Features @@ -30,7 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 4 commits contributed to the release over the course of 17 calendar days. + - 5 commits contributed to the release over the course of 17 calendar days. - 39 days passed between releases. - 2 commits where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) @@ -46,6 +46,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - initialize `Time` from `now_utc` and `now_local` ([`c76fde7`](https://github.com/Byron/gitoxide/commit/c76fde7de278b49ded13b655d5345e4eb8c1b134)) - a first sketch on how identity management could look like. ([`780f14f`](https://github.com/Byron/gitoxide/commit/780f14f5c270802e51cf039639c2fbdb5ac5a85e)) * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - git-features' walkdir: 2.3.1 -> 2.3.2 ([`41dd754`](https://github.com/Byron/gitoxide/commit/41dd7545234e6d2637d2bca5bb6d4f6d8bfc8f57))
diff --git a/git-features/Cargo.toml b/git-features/Cargo.toml index 8d797448568..8438d2e257d 100644 --- a/git-features/Cargo.toml +++ b/git-features/Cargo.toml @@ -2,7 +2,7 @@ name = "git-features" description = "A crate to integrate various capabilities using compile-time feature flags" repository = "https://github.com/Byron/gitoxide" -version = "0.21.1" +version = "0.22.0" authors = ["Sebastian Thiel "] license = "MIT/Apache-2.0" edition = "2018" @@ -84,7 +84,7 @@ required-features = ["io-pipe"] [dependencies] #! ### Optional Dependencies -git-hash = { version = "^0.9.5", path = "../git-hash" } +git-hash = { version = "^0.9.6", path = "../git-hash" } diff --git a/git-glob/CHANGELOG.md b/git-glob/CHANGELOG.md index 697f66a8276..f99b3508fba 100644 --- a/git-glob/CHANGELOG.md +++ b/git-glob/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 0.3.1 (2022-07-22) This is a maintenance release with no functional changes. @@ -13,7 +13,7 @@ This is a maintenance release with no functional changes. - - 1 commit contributed to the release over the course of 12 calendar days. + - 2 commits contributed to the release over the course of 12 calendar days. - 64 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +25,7 @@ This is a maintenance release with no functional changes.
view details * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721))
diff --git a/git-glob/Cargo.toml b/git-glob/Cargo.toml index 32459266332..f4aba3a7d3c 100644 --- a/git-glob/Cargo.toml +++ b/git-glob/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "git-glob" -version = "0.3.0" +version = "0.3.1" repository = "https://github.com/Byron/gitoxide" license = "MIT/Apache-2.0" description = "A WIP crate of the gitoxide project dealing with pattern matching" diff --git a/git-hash/CHANGELOG.md b/git-hash/CHANGELOG.md index 8c63b5387f6..e1eac4bd7bd 100644 --- a/git-hash/CHANGELOG.md +++ b/git-hash/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 0.9.6 (2022-07-22) This is a maintenance release with no functional changes. @@ -13,7 +13,7 @@ This is a maintenance release with no functional changes. - - 1 commit contributed to the release over the course of 12 calendar days. + - 2 commits contributed to the release over the course of 12 calendar days. - 39 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +25,7 @@ This is a maintenance release with no functional changes.
view details * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721))
diff --git a/git-hash/Cargo.toml b/git-hash/Cargo.toml index a50ca1542f6..23fdaea0a55 100644 --- a/git-hash/Cargo.toml +++ b/git-hash/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "git-hash" -version = "0.9.5" +version = "0.9.6" description = "Borrowed and owned git hash digests used to identify git objects" authors = ["Sebastian Thiel "] repository = "https://github.com/Byron/gitoxide" diff --git a/git-index/CHANGELOG.md b/git-index/CHANGELOG.md index 21fe3dc6bba..a30688b59a1 100644 --- a/git-index/CHANGELOG.md +++ b/git-index/CHANGELOG.md @@ -5,10 +5,29 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 0.4.0 (2022-07-22) This is a maintenance release with no functional changes. +### Commit Statistics + + + + - 1 commit contributed to the release. + - 64 days passed between releases. + - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) +
+ ## 0.3.0 (2022-05-18) ### New Features diff --git a/git-index/Cargo.toml b/git-index/Cargo.toml index 8b65d72e914..921fafb8ad4 100644 --- a/git-index/Cargo.toml +++ b/git-index/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "git-index" -version = "0.3.0" +version = "0.4.0" repository = "https://github.com/Byron/gitoxide" license = "MIT/Apache-2.0" description = "A work-in-progress crate of the gitoxide project dedicated implementing the git index file" @@ -32,10 +32,10 @@ internal-testing-to-avoid-being-run-by-cargo-test-all = [] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -git-features = { version = "^0.21.0", path = "../git-features", features = ["rustsha1", "progress"] } -git-hash = { version = "^0.9.4", path = "../git-hash" } +git-features = { version = "^0.22.0", path = "../git-features", features = ["rustsha1", "progress"] } +git-hash = { version = "^0.9.6", path = "../git-hash" } git-bitmap = { version = "^0.1.0", path = "../git-bitmap" } -git-object = { version = "^0.19.0", path = "../git-object" } +git-object = { version = "^0.20.0", path = "../git-object" } quick-error = "2.0.0" memmap2 = "0.5.0" diff --git a/git-mailmap/CHANGELOG.md b/git-mailmap/CHANGELOG.md index 78b60c16a9e..d7e4a7a5383 100644 --- a/git-mailmap/CHANGELOG.md +++ b/git-mailmap/CHANGELOG.md @@ -5,10 +5,29 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 0.3.0 (2022-07-22) This is a maintenance release with no functional changes. +### Commit Statistics + + + + - 1 commit contributed to the release. + - 64 days passed between releases. + - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) +
+ ## 0.2.0 (2022-05-18) A maintenance release without user-facing changes. diff --git a/git-mailmap/Cargo.toml b/git-mailmap/Cargo.toml index 762520f430c..78285f0541d 100644 --- a/git-mailmap/Cargo.toml +++ b/git-mailmap/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "git-mailmap" -version = "0.2.0" +version = "0.3.0" repository = "https://github.com/Byron/gitoxide" license = "MIT/Apache-2.0" description = "A WIP crate of the gitoxide project for parsing mailmap files" @@ -17,7 +17,7 @@ serde1 = ["serde", "bstr/serde1", "git-actor/serde1"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -git-actor = { version = "^0.10.0", path = "../git-actor" } +git-actor = { version = "^0.11.0", path = "../git-actor" } bstr = { version = "0.2.13", default-features = false, features = ["std", "unicode"]} quick-error = "2.0.0" serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]} diff --git a/git-object/CHANGELOG.md b/git-object/CHANGELOG.md index aa5435fb6af..ff0f2102286 100644 --- a/git-object/CHANGELOG.md +++ b/git-object/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 0.20.0 (2022-07-22) This is a maintenance release with no functional changes. @@ -13,7 +13,7 @@ This is a maintenance release with no functional changes. - - 2 commits contributed to the release over the course of 21 calendar days. + - 3 commits contributed to the release over the course of 21 calendar days. - 64 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -31,6 +31,7 @@ This is a maintenance release with no functional changes.
view details * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - Merge branch 'normalize-values' ([`4e8cc7a`](https://github.com/Byron/gitoxide/commit/4e8cc7a5b447656c744cd84e6521e620d0479acb)) - thanks clippy ([`e1003d5`](https://github.com/Byron/gitoxide/commit/e1003d5fdee5d4439c0cf0286c67dec9b5e34f53))
diff --git a/git-object/Cargo.toml b/git-object/Cargo.toml index caad1b9af50..248cd1fa296 100644 --- a/git-object/Cargo.toml +++ b/git-object/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "git-object" -version = "0.19.0" +version = "0.20.0" description = "Immutable and mutable git objects with decoding and encoding support" authors = ["Sebastian Thiel "] repository = "https://github.com/Byron/gitoxide" @@ -21,10 +21,10 @@ serde1 = ["serde", "bstr/serde1", "smallvec/serde", "git-hash/serde1", "git-acto verbose-object-parsing-errors = ["nom/std"] [dependencies] -git-features = { version = "^0.21.0", path = "../git-features", features = ["rustsha1"] } -git-hash = { version = "^0.9.4", path = "../git-hash" } +git-features = { version = "^0.22.0", path = "../git-features", features = ["rustsha1"] } +git-hash = { version = "^0.9.6", path = "../git-hash" } git-validate = { version = "^0.5.4", path = "../git-validate" } -git-actor = { version = "^0.10.0", path = "../git-actor" } +git-actor = { version = "^0.11.0", path = "../git-actor" } btoi = "0.4.2" itoa = "1.0.1" diff --git a/git-odb/CHANGELOG.md b/git-odb/CHANGELOG.md index fd62a0bf4ef..1547aca67ce 100644 --- a/git-odb/CHANGELOG.md +++ b/git-odb/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 0.31.0 (2022-07-22) This is a maintenance release with no functional changes. @@ -13,7 +13,7 @@ This is a maintenance release with no functional changes. - - 5 commits contributed to the release over the course of 33 calendar days. + - 6 commits contributed to the release over the course of 33 calendar days. - 39 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) @@ -27,6 +27,7 @@ This is a maintenance release with no functional changes. * **[#331](https://github.com/Byron/gitoxide/issues/331)** - adjustments due to breaking changes in `git_path` ([`4420ae9`](https://github.com/Byron/gitoxide/commit/4420ae932d5b20a9662a6d36353a27111b5cd672)) * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - make fmt ([`0700b09`](https://github.com/Byron/gitoxide/commit/0700b09d6828849fa2470df89af1f75a67bfb27d)) - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) - Use git_path::realpath in all places that allow it right now ([`229dc91`](https://github.com/Byron/gitoxide/commit/229dc917fc7d9241b85e5818260a6fbdd3a5daaa)) diff --git a/git-odb/Cargo.toml b/git-odb/Cargo.toml index c873fcc650c..1d0ef65a716 100644 --- a/git-odb/Cargo.toml +++ b/git-odb/Cargo.toml @@ -27,11 +27,11 @@ path = "tests/odb-single-threaded.rs" required-features = [] [dependencies] -git-features = { version = "^0.21.1", path = "../git-features", features = ["rustsha1", "walkdir", "zlib", "crc32" ] } -git-path = { version = "^0.3.0", path = "../git-path" } -git-hash = { version = "^0.9.5", path = "../git-hash" } +git-features = { version = "^0.22.0", path = "../git-features", features = ["rustsha1", "walkdir", "zlib", "crc32" ] } +git-path = { version = "^0.4.0", path = "../git-path" } +git-hash = { version = "^0.9.6", path = "../git-hash" } git-quote = { version = "^0.2.0", path = "../git-quote" } -git-object = { version = "^0.19.0", path = "../git-object" } +git-object = { version = "^0.20.0", path = "../git-object" } git-pack = { version = "^0.21.0", path = "../git-pack" } serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]} diff --git a/git-pack/CHANGELOG.md b/git-pack/CHANGELOG.md index 21b2e1d2fd5..8d2c9d9c370 100644 --- a/git-pack/CHANGELOG.md +++ b/git-pack/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 0.21.0 (2022-07-22) This is a maintenance release with no functional changes. @@ -13,7 +13,7 @@ This is a maintenance release with no functional changes. - - 2 commits contributed to the release over the course of 33 calendar days. + - 3 commits contributed to the release over the course of 33 calendar days. - 39 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +25,7 @@ This is a maintenance release with no functional changes.
view details * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35))
diff --git a/git-pack/Cargo.toml b/git-pack/Cargo.toml index 0c56ec907f9..ed441f6b5cc 100644 --- a/git-pack/Cargo.toml +++ b/git-pack/Cargo.toml @@ -37,13 +37,13 @@ path = "tests/pack-single-threaded.rs" required-features = ["internal-testing-to-avoid-being-run-by-cargo-test-all"] [dependencies] -git-features = { version = "^0.21.1", path = "../git-features", features = ["crc32", "rustsha1", "progress", "zlib"] } -git-hash = { version = "^0.9.5", path = "../git-hash" } -git-path = { version = "^0.3.0", path = "../git-path" } +git-features = { version = "^0.22.0", path = "../git-features", features = ["crc32", "rustsha1", "progress", "zlib"] } +git-hash = { version = "^0.9.6", path = "../git-hash" } +git-path = { version = "^0.4.0", path = "../git-path" } git-chunk = { version = "^0.3.0", path = "../git-chunk" } -git-object = { version = "^0.19.0", path = "../git-object" } -git-traverse = { version = "^0.15.0", path = "../git-traverse" } -git-diff = { version = "^0.16.0", path = "../git-diff" } +git-object = { version = "^0.20.0", path = "../git-object" } +git-traverse = { version = "^0.16.0", path = "../git-traverse" } +git-diff = { version = "^0.17.0", path = "../git-diff" } git-tempfile = { version = "^2.0.0", path = "../git-tempfile" } smallvec = "1.3.0" diff --git a/git-path/CHANGELOG.md b/git-path/CHANGELOG.md index d6e9cb35264..1c29bdd6e26 100644 --- a/git-path/CHANGELOG.md +++ b/git-path/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 0.4.0 (2022-07-22) ### Changed (BREAKING) @@ -16,7 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 10 commits contributed to the release over the course of 32 calendar days. + - 11 commits contributed to the release over the course of 32 calendar days. - 33 days passed between releases. - 1 commit where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) @@ -36,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * **[#331](https://github.com/Byron/gitoxide/issues/331)** - `realpath()` handles `cwd` internally ([`dfa1e05`](https://github.com/Byron/gitoxide/commit/dfa1e05d3c983f1e8b1cb3b80d03608341187883)) * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - make fmt ([`0700b09`](https://github.com/Byron/gitoxide/commit/0700b09d6828849fa2470df89af1f75a67bfb27d)) - fix docs ([`4f8e3b1`](https://github.com/Byron/gitoxide/commit/4f8e3b169e57d599439c7abc861c82c08bcd92e3)) - thanks clippy ([`7a2a31e`](https://github.com/Byron/gitoxide/commit/7a2a31e5758a2be8434f22cd9401ac00539f2bd9)) diff --git a/git-path/Cargo.toml b/git-path/Cargo.toml index 7ea12582b5d..bc63239df8e 100644 --- a/git-path/Cargo.toml +++ b/git-path/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "git-path" -version = "0.3.0" +version = "0.4.0" repository = "https://github.com/Byron/gitoxide" license = "MIT/Apache-2.0" description = "A WIP crate of the gitoxide project dealing paths and their conversions" diff --git a/git-protocol/CHANGELOG.md b/git-protocol/CHANGELOG.md index eb8d5ff302f..c00e030df42 100644 --- a/git-protocol/CHANGELOG.md +++ b/git-protocol/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 0.18.0 (2022-07-22) This is a maintenance release with no functional changes. @@ -13,7 +13,7 @@ This is a maintenance release with no functional changes. - - 1 commit contributed to the release over the course of 33 calendar days. + - 2 commits contributed to the release over the course of 33 calendar days. - 39 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +25,7 @@ This is a maintenance release with no functional changes.
view details * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35))
diff --git a/git-protocol/Cargo.toml b/git-protocol/Cargo.toml index 43ab77525f4..5d8874e3616 100644 --- a/git-protocol/Cargo.toml +++ b/git-protocol/Cargo.toml @@ -39,9 +39,9 @@ path = "tests/async-protocol.rs" required-features = ["async-client"] [dependencies] -git-features = { version = "^0.21.1", path = "../git-features", features = ["progress"] } +git-features = { version = "^0.22.0", path = "../git-features", features = ["progress"] } git-transport = { version = "^0.19.0", path = "../git-transport" } -git-hash = { version = "^0.9.5", path = "../git-hash" } +git-hash = { version = "^0.9.6", path = "../git-hash" } git-credentials = { version = "^0.3.0", path = "../git-credentials" } quick-error = "2.0.0" diff --git a/git-ref/CHANGELOG.md b/git-ref/CHANGELOG.md index be8e5c343f4..6f541f5f00e 100644 --- a/git-ref/CHANGELOG.md +++ b/git-ref/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 0.15.0 (2022-07-22) ### New Features @@ -21,7 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 9 commits contributed to the release over the course of 33 calendar days. + - 10 commits contributed to the release over the course of 33 calendar days. - 39 days passed between releases. - 2 commits where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) @@ -42,6 +42,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Target(Ref)?::try_name()` now returns `Option<&FullNameRef>`. ([`0f753e9`](https://github.com/Byron/gitoxide/commit/0f753e922e313f735ed267f913366771e9de1111)) - Add `store::WriteRefLog::Always` to unconditionally write reflogs. ([`4607a18`](https://github.com/Byron/gitoxide/commit/4607a18e24b8270c182663a434b79dff8761db0e)) * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) - Merge branch 'main' into cont_include_if ([`daa71c3`](https://github.com/Byron/gitoxide/commit/daa71c3b753c6d76a3d652c29237906b3e28728f)) - Merge branch 'normalize-values' ([`4e8cc7a`](https://github.com/Byron/gitoxide/commit/4e8cc7a5b447656c744cd84e6521e620d0479acb)) diff --git a/git-ref/Cargo.toml b/git-ref/Cargo.toml index 5c53df38570..96519173520 100644 --- a/git-ref/Cargo.toml +++ b/git-ref/Cargo.toml @@ -25,12 +25,12 @@ required-features = ["internal-testing-git-features-parallel"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -git-features = { version = "^0.21.1", path = "../git-features", features = ["walkdir"]} -git-path = { version = "^0.3.0", path = "../git-path" } -git-hash = { version = "^0.9.5", path = "../git-hash" } -git-object = { version = "^0.19.0", path = "../git-object" } +git-features = { version = "^0.22.0", path = "../git-features", features = ["walkdir"]} +git-path = { version = "^0.4.0", path = "../git-path" } +git-hash = { version = "^0.9.6", path = "../git-hash" } +git-object = { version = "^0.20.0", path = "../git-object" } git-validate = { version = "^0.5.4", path = "../git-validate" } -git-actor = { version = "^0.10.1", path = "../git-actor" } +git-actor = { version = "^0.11.0", path = "../git-actor" } git-lock = { version = "^2.0.0", path = "../git-lock" } git-tempfile = { version = "^2.0.0", path = "../git-tempfile" } diff --git a/git-repository/CHANGELOG.md b/git-repository/CHANGELOG.md index b89f503ed1e..96c6c1dbc83 100644 --- a/git-repository/CHANGELOG.md +++ b/git-repository/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 0.20.0 (2022-07-22) ### New Features @@ -59,7 +59,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 69 commits contributed to the release over the course of 33 calendar days. + - 70 commits contributed to the release over the course of 33 calendar days. - 39 days passed between releases. - 16 commits where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) @@ -130,6 +130,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - adjust to breaking changes in `git-config` ([`5b66202`](https://github.com/Byron/gitoxide/commit/5b66202d96bf664ed84755afc3ec49c301ecd62c)) - adjustments due to breaking changes in `git_path` ([`4420ae9`](https://github.com/Byron/gitoxide/commit/4420ae932d5b20a9662a6d36353a27111b5cd672)) * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - thanks clippy ([`fddc720`](https://github.com/Byron/gitoxide/commit/fddc7206476423a6964d61acd060305572ecd02b)) - thanks clippy ([`0346aaa`](https://github.com/Byron/gitoxide/commit/0346aaaeccfe18a443410652cada7b14eb34d8b9)) - thanks clippy ([`b630543`](https://github.com/Byron/gitoxide/commit/b630543669af5289508ce066bd026e2b9a9d5044)) diff --git a/git-repository/Cargo.toml b/git-repository/Cargo.toml index 4c9dfcb8391..3b8fa8fab84 100644 --- a/git-repository/Cargo.toml +++ b/git-repository/Cargo.toml @@ -60,30 +60,30 @@ git-tempfile = { version = "^2.0.0", path = "../git-tempfile" } git-lock = { version = "^2.0.0", path = "../git-lock" } git-validate = { version = "^0.5.4", path = "../git-validate" } git-sec = { version = "^0.3.0", path = "../git-sec", features = ["thiserror"] } -git-date = { version = "^0.0.1", path = "../git-date" } +git-date = { version = "^0.0.2", path = "../git-date" } git-config = { version = "^0.6.0", path = "../git-config" } git-odb = { version = "^0.31.0", path = "../git-odb" } -git-hash = { version = "^0.9.5", path = "../git-hash" } -git-object = { version = "^0.19.0", path = "../git-object" } -git-actor = { version = "^0.10.1", path = "../git-actor" } +git-hash = { version = "^0.9.6", path = "../git-hash" } +git-object = { version = "^0.20.0", path = "../git-object" } +git-actor = { version = "^0.11.0", path = "../git-actor" } git-pack = { version = "^0.21.0", path = "../git-pack", features = ["object-cache-dynamic"] } -git-revision = { version = "^0.2.1", path = "../git-revision" } +git-revision = { version = "^0.3.0", path = "../git-revision" } -git-path = { version = "^0.3.0", path = "../git-path" } +git-path = { version = "^0.4.0", path = "../git-path" } git-url = { version = "^0.7.0", path = "../git-url", optional = true } -git-traverse = { version = "^0.15.0", path = "../git-traverse" } +git-traverse = { version = "^0.16.0", path = "../git-traverse" } git-protocol = { version = "^0.18.0", path = "../git-protocol", optional = true } git-transport = { version = "^0.19.0", path = "../git-transport", optional = true } -git-diff = { version = "^0.16.0", path = "../git-diff", optional = true } -git-mailmap = { version = "^0.2.0", path = "../git-mailmap", optional = true } -git-features = { version = "^0.21.1", path = "../git-features", features = ["progress", "once_cell"] } +git-diff = { version = "^0.17.0", path = "../git-diff", optional = true } +git-mailmap = { version = "^0.3.0", path = "../git-mailmap", optional = true } +git-features = { version = "^0.22.0", path = "../git-features", features = ["progress", "once_cell"] } # unstable only git-attributes = { version = "^0.3.0", path = "../git-attributes", optional = true } -git-glob = { version = "^0.3.0", path = "../git-glob", optional = true } +git-glob = { version = "^0.3.1", path = "../git-glob", optional = true } git-credentials = { version = "^0.3.0", path = "../git-credentials", optional = true } -git-index = { version = "^0.3.0", path = "../git-index", optional = true } +git-index = { version = "^0.4.0", path = "../git-index", optional = true } git-worktree = { version = "^0.4.0", path = "../git-worktree" } signal-hook = { version = "0.3.9", default-features = false } diff --git a/git-revision/CHANGELOG.md b/git-revision/CHANGELOG.md index 331ee135def..9d90e70f56a 100644 --- a/git-revision/CHANGELOG.md +++ b/git-revision/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 0.3.0 (2022-07-22) This is a maintenance release with no functional changes. @@ -13,7 +13,7 @@ This is a maintenance release with no functional changes. - - 7 commits contributed to the release over the course of 38 calendar days. + - 8 commits contributed to the release over the course of 38 calendar days. - 39 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#427](https://github.com/Byron/gitoxide/issues/427) @@ -35,6 +35,7 @@ This is a maintenance release with no functional changes. - Handle lonely tilde gracefully ([`6fb834e`](https://github.com/Byron/gitoxide/commit/6fb834e06639febbe67a46e702cd523c4e7bd2a7)) - refactor ([`1a15e12`](https://github.com/Byron/gitoxide/commit/1a15e120a75d29b3d3f7615af1a66a033dfd3c8b)) * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - make fmt ([`0700b09`](https://github.com/Byron/gitoxide/commit/0700b09d6828849fa2470df89af1f75a67bfb27d)) - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) - Merge branch 'normalize-values' ([`4e8cc7a`](https://github.com/Byron/gitoxide/commit/4e8cc7a5b447656c744cd84e6521e620d0479acb)) diff --git a/git-revision/Cargo.toml b/git-revision/Cargo.toml index 2d2e11636ef..f1490626e65 100644 --- a/git-revision/Cargo.toml +++ b/git-revision/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "git-revision" -version = "0.2.1" +version = "0.3.0" repository = "https://github.com/Byron/gitoxide" license = "MIT/Apache-2.0" description = "A WIP crate of the gitoxide project dealing with finding names for revisions and parsing specifications" @@ -17,9 +17,9 @@ serde1 = [ "serde", "git-hash/serde1", "git-object/serde1" ] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -git-hash = { version = "^0.9.5", path = "../git-hash" } -git-object = { version = "^0.19.0", path = "../git-object" } -git-date = { version = "^0.0.1", path = "../git-date" } +git-hash = { version = "^0.9.6", path = "../git-hash" } +git-object = { version = "^0.20.0", path = "../git-object" } +git-date = { version = "^0.0.2", path = "../git-date" } bstr = { version = "0.2.13", default-features = false, features = ["std"]} hash_hasher = "2.0.3" diff --git a/git-sec/CHANGELOG.md b/git-sec/CHANGELOG.md index 2654270090e..1f888e2cacc 100644 --- a/git-sec/CHANGELOG.md +++ b/git-sec/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 0.3.0 (2022-07-22) ### New Features @@ -23,7 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 5 commits contributed to the release over the course of 33 calendar days. + - 6 commits contributed to the release over the course of 33 calendar days. - 39 days passed between releases. - 2 commits where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) @@ -38,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - on windows, emit a `NotFound` io error, similar to what happens on unix. ([`9a1e982`](https://github.com/Byron/gitoxide/commit/9a1e9828e813ec1de68ac2e83a986c49c71c5dbe)) - fix build after breaking changes in `git-path` ([`34aed2f`](https://github.com/Byron/gitoxide/commit/34aed2fb608df79bdc56b244f7ac216f46322e5f)) * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) - Support for SUDO_UID as fallback for ownership check on unix. ([`3d16c36`](https://github.com/Byron/gitoxide/commit/3d16c36d7288d9a5fae5b9d23715e043d4d8ce76)) - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) diff --git a/git-sec/Cargo.toml b/git-sec/Cargo.toml index 17ee4c48a44..c9eae44d451 100644 --- a/git-sec/Cargo.toml +++ b/git-sec/Cargo.toml @@ -28,7 +28,7 @@ document-features = { version = "0.2.1", optional = true } libc = "0.2.123" [target.'cfg(windows)'.dependencies] -git-path = { version = "^0.3.0", path = "../git-path" } +git-path = { version = "^0.4.0", path = "../git-path" } dirs = "4" windows = { version = "0.37.0", features = [ "alloc", "Win32_Foundation", diff --git a/git-tempfile/CHANGELOG.md b/git-tempfile/CHANGELOG.md index c57faa3042d..701dbf85139 100644 --- a/git-tempfile/CHANGELOG.md +++ b/git-tempfile/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 2.0.2 (2022-07-22) This is a maintenance release with no functional changes. @@ -13,7 +13,7 @@ This is a maintenance release with no functional changes. - - 2 commits contributed to the release over the course of 21 calendar days. + - 3 commits contributed to the release over the course of 21 calendar days. - 110 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -31,6 +31,7 @@ This is a maintenance release with no functional changes.
view details * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - Merge branch 'normalize-values' ([`4e8cc7a`](https://github.com/Byron/gitoxide/commit/4e8cc7a5b447656c744cd84e6521e620d0479acb)) - thanks clippy ([`e1003d5`](https://github.com/Byron/gitoxide/commit/e1003d5fdee5d4439c0cf0286c67dec9b5e34f53))
diff --git a/git-tempfile/Cargo.toml b/git-tempfile/Cargo.toml index f2b4f4344fd..f3956b0eb4f 100644 --- a/git-tempfile/Cargo.toml +++ b/git-tempfile/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "git-tempfile" -version = "2.0.1" +version = "2.0.2" repository = "https://github.com/Byron/gitoxide" license = "MIT/Apache-2.0" description = "A tempfile implementation with a global registry to assure cleanup" diff --git a/git-transport/CHANGELOG.md b/git-transport/CHANGELOG.md index 10564f263da..2f4971d750d 100644 --- a/git-transport/CHANGELOG.md +++ b/git-transport/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 0.19.0 (2022-07-22) This is a maintenance release with no functional changes. @@ -13,7 +13,7 @@ This is a maintenance release with no functional changes. - - 3 commits contributed to the release over the course of 33 calendar days. + - 4 commits contributed to the release over the course of 33 calendar days. - 39 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -31,6 +31,7 @@ This is a maintenance release with no functional changes.
view details * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - Merge branch 'normalize-values' ([`4e8cc7a`](https://github.com/Byron/gitoxide/commit/4e8cc7a5b447656c744cd84e6521e620d0479acb)) - thanks clippy ([`e1003d5`](https://github.com/Byron/gitoxide/commit/e1003d5fdee5d4439c0cf0286c67dec9b5e34f53)) - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) diff --git a/git-transport/Cargo.toml b/git-transport/Cargo.toml index 084313fd2cb..8c380ebc31b 100644 --- a/git-transport/Cargo.toml +++ b/git-transport/Cargo.toml @@ -50,7 +50,7 @@ required-features = ["async-client"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -git-features = { version = "^0.21.1", path = "../git-features" } +git-features = { version = "^0.22.0", path = "../git-features" } git-url = { version = "^0.7.0", path = "../git-url" } git-sec = { version = "^0.3.0", path = "../git-sec" } git-packetline = { version = "^0.12.5", path = "../git-packetline" } diff --git a/git-traverse/CHANGELOG.md b/git-traverse/CHANGELOG.md index 640dfdc2f1e..c94cc710def 100644 --- a/git-traverse/CHANGELOG.md +++ b/git-traverse/CHANGELOG.md @@ -5,10 +5,29 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 0.16.0 (2022-07-22) This is a maintenance release with no functional changes. +### Commit Statistics + + + + - 1 commit contributed to the release. + - 64 days passed between releases. + - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) +
+ ## 0.15.0 (2022-05-18) A maintenance release without user-facing changes. diff --git a/git-traverse/Cargo.toml b/git-traverse/Cargo.toml index d8a3bf4d630..2dfbded17c9 100644 --- a/git-traverse/Cargo.toml +++ b/git-traverse/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "git-traverse" -version = "0.15.0" +version = "0.16.0" repository = "https://github.com/Byron/gitoxide" license = "MIT/Apache-2.0" description = "A WIP crate of the gitoxide project" @@ -14,8 +14,8 @@ doctest = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -git-hash = { version = "^0.9.4", path = "../git-hash" } -git-object = { version = "^0.19.0", path = "../git-object" } +git-hash = { version = "^0.9.6", path = "../git-hash" } +git-object = { version = "^0.20.0", path = "../git-object" } quick-error = "2.0.0" hash_hasher = "2.0.3" diff --git a/git-url/CHANGELOG.md b/git-url/CHANGELOG.md index ae674272444..f85ee131c7e 100644 --- a/git-url/CHANGELOG.md +++ b/git-url/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 0.7.0 (2022-07-22) ### Changed (BREAKING) @@ -17,7 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 3 commits contributed to the release over the course of 33 calendar days. + - 4 commits contributed to the release over the course of 33 calendar days. - 39 days passed between releases. - 1 commit where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
view details * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) - `From<&[u8]>` is now `From<&BStr>` ([`ffc4a85`](https://github.com/Byron/gitoxide/commit/ffc4a85b9a914b685d7ab528b30f2a3eefb44094)) - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) diff --git a/git-url/Cargo.toml b/git-url/Cargo.toml index 47883dbc7fb..fc5cd573c68 100644 --- a/git-url/Cargo.toml +++ b/git-url/Cargo.toml @@ -19,8 +19,8 @@ serde1 = ["serde", "bstr/serde1"] [dependencies] serde = { version = "1.0.114", optional = true, default-features = false, features = ["std", "derive"]} -git-features = { version = "^0.21.1", path = "../git-features" } -git-path = { version = "^0.3.0", path = "../git-path" } +git-features = { version = "^0.22.0", path = "../git-features" } +git-path = { version = "^0.4.0", path = "../git-path" } quick-error = "2.0.0" url = "2.1.1" bstr = { version = "0.2.13", default-features = false, features = ["std"] } diff --git a/git-worktree/CHANGELOG.md b/git-worktree/CHANGELOG.md index fabca2640bd..30732db7c93 100644 --- a/git-worktree/CHANGELOG.md +++ b/git-worktree/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 0.4.0 (2022-07-22) This is a maintenance release with no functional changes. @@ -13,7 +13,7 @@ This is a maintenance release with no functional changes. - - 2 commits contributed to the release over the course of 33 calendar days. + - 3 commits contributed to the release over the course of 33 calendar days. - 39 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +25,7 @@ This is a maintenance release with no functional changes.
view details * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - make it harder to forget documentation in git-worktree ([`15d87ee`](https://github.com/Byron/gitoxide/commit/15d87ee99ef269985e8f378bb2ab9c8931e8fd7d)) - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35))
diff --git a/git-worktree/Cargo.toml b/git-worktree/Cargo.toml index 84dc5f543d3..0cc78de51e9 100644 --- a/git-worktree/Cargo.toml +++ b/git-worktree/Cargo.toml @@ -30,13 +30,13 @@ internal-testing-git-features-parallel = ["git-features/parallel"] internal-testing-to-avoid-being-run-by-cargo-test-all = [] [dependencies] -git-index = { version = "^0.3.0", path = "../git-index" } -git-hash = { version = "^0.9.5", path = "../git-hash" } -git-object = { version = "^0.19.0", path = "../git-object" } -git-glob = { version = "^0.3.0", path = "../git-glob" } -git-path = { version = "^0.3.0", path = "../git-path" } +git-index = { version = "^0.4.0", path = "../git-index" } +git-hash = { version = "^0.9.6", path = "../git-hash" } +git-object = { version = "^0.20.0", path = "../git-object" } +git-glob = { version = "^0.3.1", path = "../git-glob" } +git-path = { version = "^0.4.0", path = "../git-path" } git-attributes = { version = "^0.3.0", path = "../git-attributes" } -git-features = { version = "^0.21.1", path = "../git-features" } +git-features = { version = "^0.22.0", path = "../git-features" } serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]} diff --git a/gitoxide-core/CHANGELOG.md b/gitoxide-core/CHANGELOG.md index 2b0d07966e6..bc0878459e3 100644 --- a/gitoxide-core/CHANGELOG.md +++ b/gitoxide-core/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 0.15.0 (2022-07-22) ### Changed (BREAKING) @@ -44,7 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 60 commits contributed to the release over the course of 101 calendar days. + - 61 commits contributed to the release over the course of 101 calendar days. - 107 days passed between releases. - 6 commits where understood as [conventional](https://www.conventionalcommits.org). - 3 unique issues were worked on: [#301](https://github.com/Byron/gitoxide/issues/301), [#331](https://github.com/Byron/gitoxide/issues/331), [#427](https://github.com/Byron/gitoxide/issues/427) @@ -99,6 +99,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - basic infrastructure for delegate implementation ([`d3c0bc6`](https://github.com/Byron/gitoxide/commit/d3c0bc6e8d7764728f4e10500bb895152ccd0b0b)) - Hookup explain command ([`1049b00`](https://github.com/Byron/gitoxide/commit/1049b00eaa261a67f060eaca4eb50dcda831eafd)) * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - make fmt ([`0700b09`](https://github.com/Byron/gitoxide/commit/0700b09d6828849fa2470df89af1f75a67bfb27d)) - fix build after changes to `git-url` and `git-config` ([`1f02420`](https://github.com/Byron/gitoxide/commit/1f0242034071ce317743df75cc685e7428b604b0)) - Merge branch 'main' into cont_include_if ([`daa71c3`](https://github.com/Byron/gitoxide/commit/daa71c3b753c6d76a3d652c29237906b3e28728f)) diff --git a/gitoxide-core/Cargo.toml b/gitoxide-core/Cargo.toml index f08f3d72901..0a767fb0562 100644 --- a/gitoxide-core/Cargo.toml +++ b/gitoxide-core/Cargo.toml @@ -40,7 +40,7 @@ git-repository = { version = "^0.20.0", path = "../git-repository", default-feat git-pack-for-configuration-only = { package = "git-pack", version = "^0.21.0", path = "../git-pack", default-features = false, features = ["pack-cache-lru-dynamic", "pack-cache-lru-static"] } git-commitgraph = { version = "^0.8.0", path = "../git-commitgraph" } git-config = { version = "^0.6.0", path = "../git-config" } -git-features = { version = "^0.21.1", path = "../git-features" } +git-features = { version = "^0.22.0", path = "../git-features" } serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"] } anyhow = "1.0.42" quick-error = "2.0.0" diff --git a/tests/tools/Cargo.toml b/tests/tools/Cargo.toml index 2e1600f441b..98e9e048f92 100644 --- a/tests/tools/Cargo.toml +++ b/tests/tools/Cargo.toml @@ -13,7 +13,7 @@ path = "src/main.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -git-hash = { version = "^0.9.5", path = "../../git-hash" } +git-hash = { version = "^0.9.6", path = "../../git-hash" } git-lock = { version = "^2.0.0", path = "../../git-lock" } git-discover = { version = "^0.3.0", path = "../../git-discover" } git-attributes = { version = "^0.3.0", path = "../../git-attributes" } From aa639d8c43f3098cc4a5b50614c5ae94a8156928 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 22 Jul 2022 17:03:32 +0800 Subject: [PATCH 358/366] Release git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0 --- git-commitgraph/CHANGELOG.md | 3 ++- git-config/CHANGELOG.md | 3 ++- git-credentials/CHANGELOG.md | 3 ++- git-diff/CHANGELOG.md | 3 ++- git-discover/CHANGELOG.md | 3 ++- git-index/CHANGELOG.md | 3 ++- git-mailmap/CHANGELOG.md | 3 ++- git-odb/CHANGELOG.md | 3 ++- git-pack/CHANGELOG.md | 3 ++- git-protocol/CHANGELOG.md | 3 ++- git-repository/CHANGELOG.md | 3 ++- git-revision/CHANGELOG.md | 3 ++- git-transport/CHANGELOG.md | 3 ++- git-traverse/CHANGELOG.md | 3 ++- git-url/CHANGELOG.md | 3 ++- git-worktree/CHANGELOG.md | 3 ++- gitoxide-core/CHANGELOG.md | 3 ++- 17 files changed, 34 insertions(+), 17 deletions(-) diff --git a/git-commitgraph/CHANGELOG.md b/git-commitgraph/CHANGELOG.md index 15b30223580..4059a765645 100644 --- a/git-commitgraph/CHANGELOG.md +++ b/git-commitgraph/CHANGELOG.md @@ -13,7 +13,7 @@ A maintenance release without user-facing changes. - - 8 commits contributed to the release over the course of 99 calendar days. + - 9 commits contributed to the release over the course of 99 calendar days. - 110 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#384](https://github.com/Byron/gitoxide/issues/384) @@ -29,6 +29,7 @@ A maintenance release without user-facing changes. - add archive files via git-lfs ([`7202a1c`](https://github.com/Byron/gitoxide/commit/7202a1c4734ad904c026ee3e4e2143c0461d51a2)) - auto-set commit.gpgsign=false when executing git ([`c23feb6`](https://github.com/Byron/gitoxide/commit/c23feb64ad157180cfba8a11c882b829733ea8f6)) * **Uncategorized** + - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) diff --git a/git-config/CHANGELOG.md b/git-config/CHANGELOG.md index 95d07f62748..1fe15e2ddba 100644 --- a/git-config/CHANGELOG.md +++ b/git-config/CHANGELOG.md @@ -229,7 +229,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 313 commits contributed to the release over the course of 33 calendar days. + - 314 commits contributed to the release over the course of 33 calendar days. - 39 days passed between releases. - 93 commits where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) @@ -487,6 +487,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - conforming subsection parsing handling backslashes like git ([`6366148`](https://github.com/Byron/gitoxide/commit/6366148f538ee03314dd866e083157de810d4ad4)) - Only copy pattern if required ([`b3a752a`](https://github.com/Byron/gitoxide/commit/b3a752a0a873cf9d685e1893c8d35255d7f7323a)) * **Uncategorized** + - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - thanks clippy ([`fddc720`](https://github.com/Byron/gitoxide/commit/fddc7206476423a6964d61acd060305572ecd02b)) - thanks fuzzy ([`15a379a`](https://github.com/Byron/gitoxide/commit/15a379a85d59d83f3a0512b9e9fbff1774c9f561)) diff --git a/git-credentials/CHANGELOG.md b/git-credentials/CHANGELOG.md index a7ba72a512a..4c0eeb30824 100644 --- a/git-credentials/CHANGELOG.md +++ b/git-credentials/CHANGELOG.md @@ -13,7 +13,7 @@ This is a maintenance release with no functional changes. - - 3 commits contributed to the release over the course of 33 calendar days. + - 4 commits contributed to the release over the course of 33 calendar days. - 39 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +25,7 @@ This is a maintenance release with no functional changes.
view details * **Uncategorized** + - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) diff --git a/git-diff/CHANGELOG.md b/git-diff/CHANGELOG.md index d6b59677def..d4a86f3b239 100644 --- a/git-diff/CHANGELOG.md +++ b/git-diff/CHANGELOG.md @@ -13,7 +13,7 @@ This is a maintenance release with no functional changes. - - 4 commits contributed to the release over the course of 30 calendar days. + - 5 commits contributed to the release over the course of 30 calendar days. - 64 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +25,7 @@ This is a maintenance release with no functional changes.
view details * **Uncategorized** + - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - Merge branch 'main' into cont_include_if ([`daa71c3`](https://github.com/Byron/gitoxide/commit/daa71c3b753c6d76a3d652c29237906b3e28728f)) - Merge branch 'main' into cont_include_if ([`0e9df36`](https://github.com/Byron/gitoxide/commit/0e9df364c4cddf006b1de18b8d167319b7cc1186)) diff --git a/git-discover/CHANGELOG.md b/git-discover/CHANGELOG.md index 52bc2bc5aab..9b96e4e9fb4 100644 --- a/git-discover/CHANGELOG.md +++ b/git-discover/CHANGELOG.md @@ -15,7 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 6 commits contributed to the release over the course of 33 calendar days. + - 7 commits contributed to the release over the course of 33 calendar days. - 39 days passed between releases. - 1 commit where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) @@ -30,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - add `DOT_GIT_DIR` constant, containing the name ".git". ([`0103501`](https://github.com/Byron/gitoxide/commit/010350180459aec41132c960ddafc7b81dd9c04d)) - adjustments due to breaking changes in `git_path` ([`4420ae9`](https://github.com/Byron/gitoxide/commit/4420ae932d5b20a9662a6d36353a27111b5cd672)) * **Uncategorized** + - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - Remove another special case on windows due to canonicalize() ([`61abb0b`](https://github.com/Byron/gitoxide/commit/61abb0b006292d2122784b032e198cc716fb7b92)) - Use git_path::realpath in all places that allow it right now ([`229dc91`](https://github.com/Byron/gitoxide/commit/229dc917fc7d9241b85e5818260a6fbdd3a5daaa)) diff --git a/git-index/CHANGELOG.md b/git-index/CHANGELOG.md index a30688b59a1..4d3da1c26eb 100644 --- a/git-index/CHANGELOG.md +++ b/git-index/CHANGELOG.md @@ -13,7 +13,7 @@ This is a maintenance release with no functional changes. - - 1 commit contributed to the release. + - 2 commits contributed to the release. - 64 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +25,7 @@ This is a maintenance release with no functional changes.
view details * **Uncategorized** + - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9))
diff --git a/git-mailmap/CHANGELOG.md b/git-mailmap/CHANGELOG.md index d7e4a7a5383..0e8eb169986 100644 --- a/git-mailmap/CHANGELOG.md +++ b/git-mailmap/CHANGELOG.md @@ -13,7 +13,7 @@ This is a maintenance release with no functional changes. - - 1 commit contributed to the release. + - 2 commits contributed to the release. - 64 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +25,7 @@ This is a maintenance release with no functional changes.
view details * **Uncategorized** + - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9))
diff --git a/git-odb/CHANGELOG.md b/git-odb/CHANGELOG.md index 1547aca67ce..07497f117f0 100644 --- a/git-odb/CHANGELOG.md +++ b/git-odb/CHANGELOG.md @@ -13,7 +13,7 @@ This is a maintenance release with no functional changes. - - 6 commits contributed to the release over the course of 33 calendar days. + - 7 commits contributed to the release over the course of 33 calendar days. - 39 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) @@ -27,6 +27,7 @@ This is a maintenance release with no functional changes. * **[#331](https://github.com/Byron/gitoxide/issues/331)** - adjustments due to breaking changes in `git_path` ([`4420ae9`](https://github.com/Byron/gitoxide/commit/4420ae932d5b20a9662a6d36353a27111b5cd672)) * **Uncategorized** + - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - make fmt ([`0700b09`](https://github.com/Byron/gitoxide/commit/0700b09d6828849fa2470df89af1f75a67bfb27d)) - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) diff --git a/git-pack/CHANGELOG.md b/git-pack/CHANGELOG.md index 8d2c9d9c370..c58ec88ce70 100644 --- a/git-pack/CHANGELOG.md +++ b/git-pack/CHANGELOG.md @@ -13,7 +13,7 @@ This is a maintenance release with no functional changes. - - 3 commits contributed to the release over the course of 33 calendar days. + - 4 commits contributed to the release over the course of 33 calendar days. - 39 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +25,7 @@ This is a maintenance release with no functional changes.
view details * **Uncategorized** + - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) diff --git a/git-protocol/CHANGELOG.md b/git-protocol/CHANGELOG.md index c00e030df42..69195434b25 100644 --- a/git-protocol/CHANGELOG.md +++ b/git-protocol/CHANGELOG.md @@ -13,7 +13,7 @@ This is a maintenance release with no functional changes. - - 2 commits contributed to the release over the course of 33 calendar days. + - 3 commits contributed to the release over the course of 33 calendar days. - 39 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +25,7 @@ This is a maintenance release with no functional changes.
view details * **Uncategorized** + - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35))
diff --git a/git-repository/CHANGELOG.md b/git-repository/CHANGELOG.md index 96c6c1dbc83..7f1465abfd4 100644 --- a/git-repository/CHANGELOG.md +++ b/git-repository/CHANGELOG.md @@ -59,7 +59,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 70 commits contributed to the release over the course of 33 calendar days. + - 71 commits contributed to the release over the course of 33 calendar days. - 39 days passed between releases. - 16 commits where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) @@ -130,6 +130,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - adjust to breaking changes in `git-config` ([`5b66202`](https://github.com/Byron/gitoxide/commit/5b66202d96bf664ed84755afc3ec49c301ecd62c)) - adjustments due to breaking changes in `git_path` ([`4420ae9`](https://github.com/Byron/gitoxide/commit/4420ae932d5b20a9662a6d36353a27111b5cd672)) * **Uncategorized** + - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - thanks clippy ([`fddc720`](https://github.com/Byron/gitoxide/commit/fddc7206476423a6964d61acd060305572ecd02b)) - thanks clippy ([`0346aaa`](https://github.com/Byron/gitoxide/commit/0346aaaeccfe18a443410652cada7b14eb34d8b9)) diff --git a/git-revision/CHANGELOG.md b/git-revision/CHANGELOG.md index 9d90e70f56a..99bf3f9dac1 100644 --- a/git-revision/CHANGELOG.md +++ b/git-revision/CHANGELOG.md @@ -13,7 +13,7 @@ This is a maintenance release with no functional changes. - - 8 commits contributed to the release over the course of 38 calendar days. + - 9 commits contributed to the release over the course of 38 calendar days. - 39 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#427](https://github.com/Byron/gitoxide/issues/427) @@ -35,6 +35,7 @@ This is a maintenance release with no functional changes. - Handle lonely tilde gracefully ([`6fb834e`](https://github.com/Byron/gitoxide/commit/6fb834e06639febbe67a46e702cd523c4e7bd2a7)) - refactor ([`1a15e12`](https://github.com/Byron/gitoxide/commit/1a15e120a75d29b3d3f7615af1a66a033dfd3c8b)) * **Uncategorized** + - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - make fmt ([`0700b09`](https://github.com/Byron/gitoxide/commit/0700b09d6828849fa2470df89af1f75a67bfb27d)) - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) diff --git a/git-transport/CHANGELOG.md b/git-transport/CHANGELOG.md index 2f4971d750d..149ad91f1e3 100644 --- a/git-transport/CHANGELOG.md +++ b/git-transport/CHANGELOG.md @@ -13,7 +13,7 @@ This is a maintenance release with no functional changes. - - 4 commits contributed to the release over the course of 33 calendar days. + - 5 commits contributed to the release over the course of 33 calendar days. - 39 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -31,6 +31,7 @@ This is a maintenance release with no functional changes.
view details * **Uncategorized** + - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - Merge branch 'normalize-values' ([`4e8cc7a`](https://github.com/Byron/gitoxide/commit/4e8cc7a5b447656c744cd84e6521e620d0479acb)) - thanks clippy ([`e1003d5`](https://github.com/Byron/gitoxide/commit/e1003d5fdee5d4439c0cf0286c67dec9b5e34f53)) diff --git a/git-traverse/CHANGELOG.md b/git-traverse/CHANGELOG.md index c94cc710def..9ab6470cf56 100644 --- a/git-traverse/CHANGELOG.md +++ b/git-traverse/CHANGELOG.md @@ -13,7 +13,7 @@ This is a maintenance release with no functional changes. - - 1 commit contributed to the release. + - 2 commits contributed to the release. - 64 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +25,7 @@ This is a maintenance release with no functional changes.
view details * **Uncategorized** + - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9))
diff --git a/git-url/CHANGELOG.md b/git-url/CHANGELOG.md index f85ee131c7e..b0f0c698874 100644 --- a/git-url/CHANGELOG.md +++ b/git-url/CHANGELOG.md @@ -17,7 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 4 commits contributed to the release over the course of 33 calendar days. + - 5 commits contributed to the release over the course of 33 calendar days. - 39 days passed between releases. - 1 commit where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
view details * **Uncategorized** + - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) - `From<&[u8]>` is now `From<&BStr>` ([`ffc4a85`](https://github.com/Byron/gitoxide/commit/ffc4a85b9a914b685d7ab528b30f2a3eefb44094)) diff --git a/git-worktree/CHANGELOG.md b/git-worktree/CHANGELOG.md index 30732db7c93..a46aab2adde 100644 --- a/git-worktree/CHANGELOG.md +++ b/git-worktree/CHANGELOG.md @@ -13,7 +13,7 @@ This is a maintenance release with no functional changes. - - 3 commits contributed to the release over the course of 33 calendar days. + - 4 commits contributed to the release over the course of 33 calendar days. - 39 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +25,7 @@ This is a maintenance release with no functional changes.
view details * **Uncategorized** + - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - make it harder to forget documentation in git-worktree ([`15d87ee`](https://github.com/Byron/gitoxide/commit/15d87ee99ef269985e8f378bb2ab9c8931e8fd7d)) - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) diff --git a/gitoxide-core/CHANGELOG.md b/gitoxide-core/CHANGELOG.md index bc0878459e3..53228115f6c 100644 --- a/gitoxide-core/CHANGELOG.md +++ b/gitoxide-core/CHANGELOG.md @@ -44,7 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 61 commits contributed to the release over the course of 101 calendar days. + - 62 commits contributed to the release over the course of 101 calendar days. - 107 days passed between releases. - 6 commits where understood as [conventional](https://www.conventionalcommits.org). - 3 unique issues were worked on: [#301](https://github.com/Byron/gitoxide/issues/301), [#331](https://github.com/Byron/gitoxide/issues/331), [#427](https://github.com/Byron/gitoxide/issues/427) @@ -99,6 +99,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - basic infrastructure for delegate implementation ([`d3c0bc6`](https://github.com/Byron/gitoxide/commit/d3c0bc6e8d7764728f4e10500bb895152ccd0b0b)) - Hookup explain command ([`1049b00`](https://github.com/Byron/gitoxide/commit/1049b00eaa261a67f060eaca4eb50dcda831eafd)) * **Uncategorized** + - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - make fmt ([`0700b09`](https://github.com/Byron/gitoxide/commit/0700b09d6828849fa2470df89af1f75a67bfb27d)) - fix build after changes to `git-url` and `git-config` ([`1f02420`](https://github.com/Byron/gitoxide/commit/1f0242034071ce317743df75cc685e7428b604b0)) From d4df661dbf60dad75d07002ef9979cabe8a86935 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 22 Jul 2022 21:16:19 +0800 Subject: [PATCH 359/366] Release git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0 --- git-commitgraph/CHANGELOG.md | 3 ++- git-repository/CHANGELOG.md | 3 ++- git-worktree/CHANGELOG.md | 3 ++- gitoxide-core/CHANGELOG.md | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/git-commitgraph/CHANGELOG.md b/git-commitgraph/CHANGELOG.md index 4059a765645..afc356a7b66 100644 --- a/git-commitgraph/CHANGELOG.md +++ b/git-commitgraph/CHANGELOG.md @@ -13,7 +13,7 @@ A maintenance release without user-facing changes. - - 9 commits contributed to the release over the course of 99 calendar days. + - 10 commits contributed to the release over the course of 99 calendar days. - 110 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#384](https://github.com/Byron/gitoxide/issues/384) @@ -29,6 +29,7 @@ A maintenance release without user-facing changes. - add archive files via git-lfs ([`7202a1c`](https://github.com/Byron/gitoxide/commit/7202a1c4734ad904c026ee3e4e2143c0461d51a2)) - auto-set commit.gpgsign=false when executing git ([`c23feb6`](https://github.com/Byron/gitoxide/commit/c23feb64ad157180cfba8a11c882b829733ea8f6)) * **Uncategorized** + - Release git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0 ([`aa639d8`](https://github.com/Byron/gitoxide/commit/aa639d8c43f3098cc4a5b50614c5ae94a8156928)) - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) diff --git a/git-repository/CHANGELOG.md b/git-repository/CHANGELOG.md index 7f1465abfd4..073bc13b9cd 100644 --- a/git-repository/CHANGELOG.md +++ b/git-repository/CHANGELOG.md @@ -59,7 +59,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 71 commits contributed to the release over the course of 33 calendar days. + - 72 commits contributed to the release over the course of 33 calendar days. - 39 days passed between releases. - 16 commits where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) @@ -130,6 +130,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - adjust to breaking changes in `git-config` ([`5b66202`](https://github.com/Byron/gitoxide/commit/5b66202d96bf664ed84755afc3ec49c301ecd62c)) - adjustments due to breaking changes in `git_path` ([`4420ae9`](https://github.com/Byron/gitoxide/commit/4420ae932d5b20a9662a6d36353a27111b5cd672)) * **Uncategorized** + - Release git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0 ([`aa639d8`](https://github.com/Byron/gitoxide/commit/aa639d8c43f3098cc4a5b50614c5ae94a8156928)) - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - thanks clippy ([`fddc720`](https://github.com/Byron/gitoxide/commit/fddc7206476423a6964d61acd060305572ecd02b)) diff --git a/git-worktree/CHANGELOG.md b/git-worktree/CHANGELOG.md index a46aab2adde..ca4936d0072 100644 --- a/git-worktree/CHANGELOG.md +++ b/git-worktree/CHANGELOG.md @@ -13,7 +13,7 @@ This is a maintenance release with no functional changes. - - 4 commits contributed to the release over the course of 33 calendar days. + - 5 commits contributed to the release over the course of 33 calendar days. - 39 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +25,7 @@ This is a maintenance release with no functional changes.
view details * **Uncategorized** + - Release git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0 ([`aa639d8`](https://github.com/Byron/gitoxide/commit/aa639d8c43f3098cc4a5b50614c5ae94a8156928)) - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - make it harder to forget documentation in git-worktree ([`15d87ee`](https://github.com/Byron/gitoxide/commit/15d87ee99ef269985e8f378bb2ab9c8931e8fd7d)) diff --git a/gitoxide-core/CHANGELOG.md b/gitoxide-core/CHANGELOG.md index 53228115f6c..33b267910e0 100644 --- a/gitoxide-core/CHANGELOG.md +++ b/gitoxide-core/CHANGELOG.md @@ -44,7 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 62 commits contributed to the release over the course of 101 calendar days. + - 63 commits contributed to the release over the course of 101 calendar days. - 107 days passed between releases. - 6 commits where understood as [conventional](https://www.conventionalcommits.org). - 3 unique issues were worked on: [#301](https://github.com/Byron/gitoxide/issues/301), [#331](https://github.com/Byron/gitoxide/issues/331), [#427](https://github.com/Byron/gitoxide/issues/427) @@ -99,6 +99,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - basic infrastructure for delegate implementation ([`d3c0bc6`](https://github.com/Byron/gitoxide/commit/d3c0bc6e8d7764728f4e10500bb895152ccd0b0b)) - Hookup explain command ([`1049b00`](https://github.com/Byron/gitoxide/commit/1049b00eaa261a67f060eaca4eb50dcda831eafd)) * **Uncategorized** + - Release git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0 ([`aa639d8`](https://github.com/Byron/gitoxide/commit/aa639d8c43f3098cc4a5b50614c5ae94a8156928)) - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) - make fmt ([`0700b09`](https://github.com/Byron/gitoxide/commit/0700b09d6828849fa2470df89af1f75a67bfb27d)) From e9fcb70e429edb2974afa3f58d181f3ef14c3da3 Mon Sep 17 00:00:00 2001 From: Kian-Meng Ang Date: Fri, 22 Jul 2022 21:14:12 +0800 Subject: [PATCH 360/366] Fix typos --- Cargo.toml | 2 +- DEVELOPMENT.md | 2 +- cargo-smart-release/src/context.rs | 2 +- cargo-smart-release/tests/changelog/parse.rs | 2 +- .../parse/unknown-known-unknown-known-unsorted.md | 2 +- deny.toml | 2 +- etc/discovery/odb.md | 8 ++++---- git-bitmap/src/ewah.rs | 2 +- git-config/src/file/access/mutate.rs | 4 ++-- git-config/src/file/includes/mod.rs | 2 +- git-config/src/file/mod.rs | 4 ++-- git-config/src/parse/events.rs | 2 +- git-diff/src/tree/visit.rs | 2 +- .../fixtures/generated-archives/make_diff_repo.tar.xz | 4 ++-- git-features/src/parallel/in_parallel.rs | 4 ++-- git-features/src/parallel/serial.rs | 2 +- git-mailmap/src/entry.rs | 2 +- git-mailmap/src/lib.rs | 2 +- git-mailmap/src/snapshot.rs | 2 +- git-odb/src/store_impls/dynamic/load_index.rs | 2 +- git-odb/src/store_impls/dynamic/mod.rs | 2 +- git-odb/src/store_impls/dynamic/types.rs | 2 +- git-odb/tests/odb/store/dynamic.rs | 2 +- git-pack/src/cache/delta/mod.rs | 2 +- git-pack/src/data/file/decode_entry.rs | 6 +++--- git-pack/src/data/input/entries_to_bytes.rs | 4 ++-- git-pack/src/data/output/bytes.rs | 2 +- git-pack/src/data/output/count/mod.rs | 2 +- git-pack/src/data/output/count/objects/types.rs | 2 +- git-pack/src/data/output/entry/iter_from_counts.rs | 2 +- git-pack/src/multi_index/access.rs | 2 +- git-pack/tests/pack/data/file.rs | 4 ++-- git-pack/tests/pack/data/input.rs | 2 +- git-packetline/tests/read/mod.rs | 2 +- git-packetline/tests/read/sideband.rs | 2 +- git-protocol/src/fetch/response/async_io.rs | 2 +- git-protocol/src/fetch/response/blocking_io.rs | 2 +- git-protocol/src/fetch/tests/arguments.rs | 2 +- git-ref/src/peel.rs | 2 +- git-ref/src/store/file/find.rs | 2 +- .../src/store/file/loose/reflog/create_or_update/tests.rs | 2 +- git-ref/src/store/file/transaction/prepare.rs | 4 ++-- git-ref/src/store/packed/transaction.rs | 2 +- git-ref/tests/file/store/iter.rs | 2 +- git-repository/src/config/snapshot.rs | 2 +- git-repository/src/create.rs | 2 +- git-repository/src/id.rs | 4 ++-- git-repository/src/repository/worktree.rs | 2 +- git-tempfile/README.md | 2 +- git-tempfile/examples/try-deadlock-on-cleanup.rs | 2 +- git-tempfile/src/handler.rs | 2 +- git-transport/tests/client/git.rs | 4 ++-- git-traverse/src/commit.rs | 2 +- git-worktree/src/fs/cache/state.rs | 4 ++-- gitoxide-core/Cargo.toml | 2 +- gitoxide-core/src/net.rs | 2 +- gitoxide-core/src/pack/create.rs | 2 +- src/plumbing/options.rs | 2 +- 58 files changed, 73 insertions(+), 73 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6b1be5acf11..abbfcd7b70b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -118,7 +118,7 @@ codegen-units = 1 incremental = false build-override = { opt-level = 0 } -# It's not quite worth building depencies with more optimizations yet. Let's keep it here for later. +# It's not quite worth building dependencies with more optimizations yet. Let's keep it here for later. #[profile.dev.package."*"] #opt-level = 2 diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index e2000c8002a..66b00b0f37a 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -150,7 +150,7 @@ by humans. * **Experiments** * quick, potentially one-off programs to learn about an aspect of gitoxide potentially in comparison to other implementations like `libgit2`. * No need for tests of any kind, but it must compile and be idiomatic Rust and `gitoxide`. - * Manual commmand-line parsing is OK + * Manual command-line parsing is OK * no polish * make it compile quickly, so no extras * **Examples** diff --git a/cargo-smart-release/src/context.rs b/cargo-smart-release/src/context.rs index 9cdf5c1de27..ec5569e64ee 100644 --- a/cargo-smart-release/src/context.rs +++ b/cargo-smart-release/src/context.rs @@ -52,7 +52,7 @@ impl Context { .parent() .expect("parent of a file is always present") .strip_prefix(&self.root) - .expect("workspace members are releative to the root directory"); + .expect("workspace members are relative to the root directory"); if dir.as_os_str().is_empty() { None diff --git a/cargo-smart-release/tests/changelog/parse.rs b/cargo-smart-release/tests/changelog/parse.rs index a5d5425e049..39c3535a2ba 100644 --- a/cargo-smart-release/tests/changelog/parse.rs +++ b/cargo-smart-release/tests/changelog/parse.rs @@ -111,7 +111,7 @@ fn known_and_unknown_sections_are_sorted() { markdown: "- initial release\n\n".into() }, Segment::User { - markdown: "### Something inbetween\n\nintermezzo\n".into() + markdown: "### Something in between\n\nintermezzo\n".into() }, ] }, diff --git a/cargo-smart-release/tests/fixtures/changelog/parse/unknown-known-unknown-known-unsorted.md b/cargo-smart-release/tests/fixtures/changelog/parse/unknown-known-unknown-known-unsorted.md index 7b8dfa06a3f..f491aa53cc9 100644 --- a/cargo-smart-release/tests/fixtures/changelog/parse/unknown-known-unknown-known-unsorted.md +++ b/cargo-smart-release/tests/fixtures/changelog/parse/unknown-known-unknown-known-unsorted.md @@ -4,7 +4,7 @@ Hello, this is a changelog. - initial release -### Something inbetween +### Something in between intermezzo diff --git a/deny.toml b/deny.toml index 7e1f78a1534..e07e6200dfc 100644 --- a/deny.toml +++ b/deny.toml @@ -32,7 +32,7 @@ ignore = [ ] [licenses] # The lint level for crates which do not have a detectable license unlicensed = "deny" -# List of explictly allowed licenses +# List of explicitly allowed licenses # See https://spdx.org/licenses/ for list of possible licenses # [possible values: any SPDX 3.11 short identifier (+ optional exception)]. allow = [ diff --git a/etc/discovery/odb.md b/etc/discovery/odb.md index bab727505f8..bcaaeea9491 100644 --- a/etc/discovery/odb.md +++ b/etc/discovery/odb.md @@ -153,7 +153,7 @@ Solutions aren't always mutually exclusive despite the form of presentation sugg | | | 3. catch error, force a pack refresh, repeat | can work in conjunction with similar shortcomings of loose reference database | needs mutability, burden on the API user; | | | | | 4. writers force an update of the process-wide pool of packs after creating new packs and before updating references with the new objects | | high implementation complexity; assumes complete control of one process over git repository, excluding running git-maintenance; new readers aren't allowed for a while until the new pack is placed causing some moments of unresponsiveness/waiting | | | **pack** | ~~5. race when creating/altering more than a pack at a time~~ | 1. ignore | | a chance for occasional object misses | all of them | -| | | 2. retry more than one time | greatly reduced likelyhood of object misses | | | +| | | 2. retry more than one time | greatly reduced likelihood of object misses | | | | **pack** | **6.too many (small) packs (i.e. due to pack-receive) reduce lookup performance** | 1. explode pack into loose objects (and deal with them separately) | can run in parallel (but is typically bound by max IOP/s) | might take a while if many objects are contained in the pack due to file IOP/s; needs recompresssion and looses delta compression; risk of too many small objects | | | | | 2. combine multiple packs into one | keep all benefits of packs; very fast if pack-to-pack copy is used; can run in parallel (but is typically bound by max IOP/s) | combining with big packs takes has to write a lot of data; can be costly if pack delta compression is used | | | | | 3. Just-in-time maintenance after writes | tuned to run just at the right time to run just as much as needed | an implementation isn't trivial as there must only be one maintenance operation per repository at a time, so some queue should be made available to not skip maintenance just because one is running already. | | @@ -238,7 +238,7 @@ for applications that don't need it, like CLIs. #### Loose References Writing loose references isn't actually atomic, so readers may observe some references in an old and some in a new state. This isn't always a breaking issue like it is -the case for packs, the progam can still operate and is likely to produce correct (enough) outcomes. +the case for packs, the program can still operate and is likely to produce correct (enough) outcomes. Mitigations are possible with careful programming on the API user's side or by using the `ref-table` database instead. @@ -273,7 +273,7 @@ refresh to the user in case they fetched or pulled in the meantime, to refresh t **Drawbacks** The program could benefit of using 1.2 instead of 1.1 which could cause exhaustion of file handles despite the user having no interest in evaluating all available objects, -but ideally that is possible without loosing performance during multi-threading. +but ideally that is possible without losing performance during multi-threading. ### Professional git-hosting mono-repo server with git-maintenance tasks and just-in-time replication @@ -381,7 +381,7 @@ The default favors speed and using all available cores, but savvy users can run - not an issue as there isn't enough traffic here * **9.2** loose object database - too many loose objects reduce overall performance - just-in-time maintenance * **10** - disk full - display early warnings in the front-end to every user to get it fixed - - This solution is implemented on application side (and not in `gitoxide`), it's intersting enough to mention though for systems that operate themselves. + - This solution is implemented on application side (and not in `gitoxide`), it's interesting enough to mention though for systems that operate themselves. - One could also imagine that it tries to spend the nights aggressively compression repositories, some low-hanging fruits there. * **10** - write failure - fail connection - write failures aren't specifically handled but result in typical Rust error behaviour probably alongside error reporting on the respective channels of the git-transport sideband. diff --git a/git-bitmap/src/ewah.rs b/git-bitmap/src/ewah.rs index 9b42ffda2c5..3cd4502e4ab 100644 --- a/git-bitmap/src/ewah.rs +++ b/git-bitmap/src/ewah.rs @@ -21,7 +21,7 @@ pub fn decode(data: &[u8]) -> Result<(Vec, &[u8]), decode::Error> { let (len, data) = decode::u32(data).ok_or(Error::Corrupt("eof reading chunk length"))?; let len = len as usize; - // NOTE: git does this by copying all bytes first, and then it will change the endianess in a separate loop. + // NOTE: git does this by copying all bytes first, and then it will change the endianness in a separate loop. // Maybe it's faster, but we can't do it without unsafe. Let's leave it to the optimizer and maybe // one day somebody will find out that it's worth it to use unsafe here. let (mut bits, data) = decode::split_at_pos(data, len * std::mem::size_of::()) diff --git a/git-config/src/file/access/mutate.rs b/git-config/src/file/access/mutate.rs index 100d5fb285a..2976e85b355 100644 --- a/git-config/src/file/access/mutate.rs +++ b/git-config/src/file/access/mutate.rs @@ -234,12 +234,12 @@ impl<'event> File<'event> { Ok(()) } - /// Append another File to the end of ourselves, without loosing any information. + /// Append another File to the end of ourselves, without losing any information. pub fn append(&mut self, other: Self) -> &mut Self { self.append_or_insert(other, None) } - /// Append another File to the end of ourselves, without loosing any information. + /// Append another File to the end of ourselves, without losing any information. pub(crate) fn append_or_insert(&mut self, mut other: Self, mut insert_after: Option) -> &mut Self { let nl = self.detect_newline_style_smallvec(); fn extend_and_assure_newline<'a>( diff --git a/git-config/src/file/includes/mod.rs b/git-config/src/file/includes/mod.rs index 696ffbe1be3..85680351b38 100644 --- a/git-config/src/file/includes/mod.rs +++ b/git-config/src/file/includes/mod.rs @@ -24,7 +24,7 @@ impl File<'static> { /// a deviation from how git does it, as it technically adds new value right after the include path itself, /// technically 'splitting' the section. This can only make a difference if the `include` section also has values /// which later overwrite portions of the included file, which seems unusual as these would be related to `includes`. - /// We can fix this by 'splitting' the inlcude section if needed so the included sections are put into the right place. + /// We can fix this by 'splitting' the include section if needed so the included sections are put into the right place. pub fn resolve_includes(&mut self, options: init::Options<'_>) -> Result<(), Error> { if options.includes.max_depth == 0 { return Ok(()); diff --git a/git-config/src/file/mod.rs b/git-config/src/file/mod.rs index cb8528edc12..6a16dc16c12 100644 --- a/git-config/src/file/mod.rs +++ b/git-config/src/file/mod.rs @@ -61,7 +61,7 @@ pub struct Section<'a> { meta: OwnShared, } -/// A function to filter metadata, returning `true` if the corresponding but ommitted value can be used. +/// A function to filter metadata, returning `true` if the corresponding but omitted value can be used. pub type MetadataFilter = dyn FnMut(&'_ Metadata) -> bool; /// A strongly typed index into some range. @@ -76,7 +76,7 @@ impl Add for Index { } } -/// A stronlgy typed a size. +/// A strongly typed a size. #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Clone, Copy)] pub(crate) struct Size(pub(crate) usize); diff --git a/git-config/src/parse/events.rs b/git-config/src/parse/events.rs index 2295a583a48..a68c181f7b1 100644 --- a/git-config/src/parse/events.rs +++ b/git-config/src/parse/events.rs @@ -7,7 +7,7 @@ use crate::{ parse::{section, Event, Section}, }; -/// A type store without allocation all events that are typicaly preceeding the first section. +/// A type store without allocation all events that are typically preceding the first section. pub type FrontMatterEvents<'a> = SmallVec<[Event<'a>; 8]>; /// A zero-copy `git-config` file parser. diff --git a/git-diff/src/tree/visit.rs b/git-diff/src/tree/visit.rs index ef791d7aaf0..eee7803b3ff 100644 --- a/git-diff/src/tree/visit.rs +++ b/git-diff/src/tree/visit.rs @@ -37,7 +37,7 @@ pub enum Change { pub enum Action { /// Continue the traversal of changes. Continue, - /// Stop the traversal of changes, making this te last call to [visit(…)][Visit::visit()]. + /// Stop the traversal of changes, making this the last call to [visit(…)][Visit::visit()]. Cancel, } diff --git a/git-diff/tests/fixtures/generated-archives/make_diff_repo.tar.xz b/git-diff/tests/fixtures/generated-archives/make_diff_repo.tar.xz index 46a0c2cc6ee..e4d7f8d089c 100644 --- a/git-diff/tests/fixtures/generated-archives/make_diff_repo.tar.xz +++ b/git-diff/tests/fixtures/generated-archives/make_diff_repo.tar.xz @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f9ebb33f4370bf3b2a2c39957b586b3ee003b8f8539aa98fb89d4009f8f99cef -size 17144 +oid sha256:a121ee880e909e430504c730c3d3d14079f380c19811e65512ec836597c192b9 +size 15996 diff --git a/git-features/src/parallel/in_parallel.rs b/git-features/src/parallel/in_parallel.rs index 52ad15545f9..50df0d1f4ce 100644 --- a/git-features/src/parallel/in_parallel.rs +++ b/git-features/src/parallel/in_parallel.rs @@ -16,7 +16,7 @@ pub fn join(left: impl FnOnce() -> O1 + Send, right: impl Fn /// That way it's possible to handle threads without needing the 'static lifetime for data they interact with. /// /// Note that the threads should not rely on actual parallelism as threading might be turned off entirely, hence should not -/// connect each other with channels as deadlock would occour in single-threaded mode. +/// connect each other with channels as deadlock would occur in single-threaded mode. pub fn threads<'env, F, R>(f: F) -> std::thread::Result where F: FnOnce(&crossbeam_utils::thread::Scope<'env>) -> R, @@ -85,7 +85,7 @@ where } /// An experiment to have fine-grained per-item parallelization with built-in aggregation via thread state. -/// This is only good for operations where near-random access isn't detremental, so it's not usually great +/// This is only good for operations where near-random access isn't detrimental, so it's not usually great /// for file-io as it won't make use of sorted inputs well. /// Note that `periodic` is not guaranteed to be called in case other threads come up first and finish too fast. // TODO: better docs diff --git a/git-features/src/parallel/serial.rs b/git-features/src/parallel/serial.rs index da61709bb3c..c0bf564b82d 100644 --- a/git-features/src/parallel/serial.rs +++ b/git-features/src/parallel/serial.rs @@ -57,7 +57,7 @@ mod not_parallel { } /// An experiment to have fine-grained per-item parallelization with built-in aggregation via thread state. - /// This is only good for operations where near-random access isn't detremental, so it's not usually great + /// This is only good for operations where near-random access isn't detrimental, so it's not usually great /// for file-io as it won't make use of sorted inputs well. // TODO: better docs pub fn in_parallel_with_slice( diff --git a/git-mailmap/src/entry.rs b/git-mailmap/src/entry.rs index e157428145b..69a0c53ee57 100644 --- a/git-mailmap/src/entry.rs +++ b/git-mailmap/src/entry.rs @@ -2,7 +2,7 @@ use bstr::BStr; use crate::Entry; -/// Acccess +/// Access impl<'a> Entry<'a> { /// The name to map to. pub fn new_name(&self) -> Option<&'a BStr> { diff --git a/git-mailmap/src/lib.rs b/git-mailmap/src/lib.rs index c471b5b10bf..2248db26d75 100644 --- a/git-mailmap/src/lib.rs +++ b/git-mailmap/src/lib.rs @@ -10,7 +10,7 @@ pub mod parse; /// Parse the given `buf` of bytes line by line into mapping [Entries][Entry]. /// -/// Errors may occour per line, but it's up to the caller to stop iteration when +/// Errors may occur per line, but it's up to the caller to stop iteration when /// one is encountered. pub fn parse(buf: &[u8]) -> parse::Lines<'_> { parse::Lines::new(buf) diff --git a/git-mailmap/src/snapshot.rs b/git-mailmap/src/snapshot.rs index e61ea56ce80..c6adb243e65 100644 --- a/git-mailmap/src/snapshot.rs +++ b/git-mailmap/src/snapshot.rs @@ -183,7 +183,7 @@ impl<'a> From> for EmailEntry { } impl Snapshot { - /// Create a new snapshot from the given bytes buffer, ignoring all parse errors that may occour on a line-by-line basis. + /// Create a new snapshot from the given bytes buffer, ignoring all parse errors that may occur on a line-by-line basis. /// /// This is similar to what git does. pub fn from_bytes(buf: &[u8]) -> Self { diff --git a/git-odb/src/store_impls/dynamic/load_index.rs b/git-odb/src/store_impls/dynamic/load_index.rs index a1c455d0bc9..9075274c11b 100644 --- a/git-odb/src/store_impls/dynamic/load_index.rs +++ b/git-odb/src/store_impls/dynamic/load_index.rs @@ -531,7 +531,7 @@ impl super::Store { let mut files = slot.files.load_full(); let files_mut = Arc::make_mut(&mut files); // set the generation before we actually change the value, otherwise readers of old generations could observe the new one. - // We rather want them to turn around here and update their index, which, by that time, migth actually already be available. + // We rather want them to turn around here and update their index, which, by that time, might actually already be available. // If not, they would fail unable to load a pack or index they need, but that's preferred over returning wrong objects. // Safety: can't race as we hold the lock, have to set the generation beforehand to help avoid others to observe the value. slot.generation.store(generation, Ordering::SeqCst); diff --git a/git-odb/src/store_impls/dynamic/mod.rs b/git-odb/src/store_impls/dynamic/mod.rs index 50bb93de8bd..94548551ab8 100644 --- a/git-odb/src/store_impls/dynamic/mod.rs +++ b/git-odb/src/store_impls/dynamic/mod.rs @@ -134,7 +134,7 @@ pub mod structure { /// /// Note that this call is expensive as it gathers additional information about loose object databases. /// Note that it may change as we collect information due to the highly volatile nature of the - /// implementation. The likelyhood of actual changes is low though as these still depend on something + /// implementation. The likelihood of actual changes is low though as these still depend on something /// changing on disk and somebody reading at the same time. pub fn structure(&self) -> Result, load_index::Error> { let index = self.index.load(); diff --git a/git-odb/src/store_impls/dynamic/types.rs b/git-odb/src/store_impls/dynamic/types.rs index a055cdc961e..e902514a11b 100644 --- a/git-odb/src/store_impls/dynamic/types.rs +++ b/git-odb/src/store_impls/dynamic/types.rs @@ -259,7 +259,7 @@ impl IndexAndPacks { } } - /// If we are garbaged, put ourselve into the loaded state. Otherwise put ourselves back to unloaded. + /// If we are garbaged, put ourselves into the loaded state. Otherwise put ourselves back to unloaded. pub(crate) fn put_back(&mut self) { match self { IndexAndPacks::Index(bundle) => { diff --git a/git-odb/tests/odb/store/dynamic.rs b/git-odb/tests/odb/store/dynamic.rs index 6d6b6df9a27..4d84e123910 100644 --- a/git-odb/tests/odb/store/dynamic.rs +++ b/git-odb/tests/odb/store/dynamic.rs @@ -325,7 +325,7 @@ fn contains() { unreachable_indices: 0, unreachable_packs: 0 }, - "when asking for an object in the smallest pack, all inbetween packs are also loaded." + "when asking for an object in the smallest pack, all in between packs are also loaded." ); assert!(!new_handle.contains(hex_to_id("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"))); diff --git a/git-pack/src/cache/delta/mod.rs b/git-pack/src/cache/delta/mod.rs index c308d97f13c..d99a3d5510f 100644 --- a/git-pack/src/cache/delta/mod.rs +++ b/git-pack/src/cache/delta/mod.rs @@ -44,7 +44,7 @@ enum NodeKind { pub struct Tree { /// The root nodes, i.e. base objects root_items: Vec>, - /// The child nodes, i.e. those that rely a base object, like ref and ofs delta objets + /// The child nodes, i.e. those that rely a base object, like ref and ofs delta objects child_items: Vec>, /// The last encountered node was either a root or a child. last_seen: Option, diff --git a/git-pack/src/data/file/decode_entry.rs b/git-pack/src/data/file/decode_entry.rs index 14e54b2fdf3..b3a7920deb0 100644 --- a/git-pack/src/data/file/decode_entry.rs +++ b/git-pack/src/data/file/decode_entry.rs @@ -135,7 +135,7 @@ impl File { /// a base object, instead of an in-pack offset. /// /// `delta_cache` is a mechanism to avoid looking up base objects multiple times when decompressing multiple objects in a row. - /// Use a [Noop-Cache][cache::Never] to disable caching alltogether at the cost of repeating work. + /// Use a [Noop-Cache][cache::Never] to disable caching all together at the cost of repeating work. pub fn decode_entry( &self, entry: crate::data::Entry, @@ -165,7 +165,7 @@ impl File { } } - /// resolve: technically, this shoudln't ever be required as stored local packs don't refer to objects by id + /// resolve: technically, this shouldn't ever be required as stored local packs don't refer to objects by id /// that are outside of the pack. Unless, of course, the ref refers to an object within this pack, which means /// it's very, very large as 20bytes are smaller than the corresponding MSB encoded number fn resolve_deltas( @@ -298,7 +298,7 @@ impl File { let end = first_buffer_size + second_buffer_size; if delta_range.start < end { // …this means that the delta size is even larger than two uncompressed worst-case - // intermediate results combined. It would already be undesireable to have it bigger + // intermediate results combined. It would already be undesirable to have it bigger // then the target size (as you could just store the object in whole). // However, this just means that it reuses existing deltas smartly, which as we rightfully // remember stand for an object each. However, this means a lot of data is read to restore diff --git a/git-pack/src/data/input/entries_to_bytes.rs b/git-pack/src/data/input/entries_to_bytes.rs index 8843774b41e..7082387ce5a 100644 --- a/git-pack/src/data/input/entries_to_bytes.rs +++ b/git-pack/src/data/input/entries_to_bytes.rs @@ -21,7 +21,7 @@ pub struct EntriesToBytesIter { data_version: crate::data::Version, /// The amount of entries seen so far num_entries: u32, - /// If we are done, no additional writes will occour + /// If we are done, no additional writes will occur is_done: bool, /// The kind of hash to use for the digest object_hash: git_hash::Kind, @@ -33,7 +33,7 @@ where W: std::io::Read + std::io::Write + std::io::Seek, { /// Create a new instance reading [entries][input::Entry] from an `input` iterator and write pack data bytes to - /// `output` writer, resembling a pack of `version`. The amonut of entries will be dynaimcally determined and + /// `output` writer, resembling a pack of `version`. The amount of entries will be dynaimcally determined and /// the pack is completed once the last entry was written. /// `object_hash` is the kind of hash to use for the pack checksum and maybe other places, depending on the version. /// diff --git a/git-pack/src/data/output/bytes.rs b/git-pack/src/data/output/bytes.rs index 03ed7ec4fa1..ba854d3713d 100644 --- a/git-pack/src/data/output/bytes.rs +++ b/git-pack/src/data/output/bytes.rs @@ -37,7 +37,7 @@ pub struct FromEntriesIter { /// It stores the pack offsets at which objects begin. /// Additionally we store if an object was invalid, and if so we will not write it nor will we allow delta objects to it. pack_offsets_and_validity: Vec<(u64, bool)>, - /// If we are done, no additional writes will occour + /// If we are done, no additional writes will occur is_done: bool, } diff --git a/git-pack/src/data/output/count/mod.rs b/git-pack/src/data/output/count/mod.rs index 7a567ea0fd9..e9a423c836e 100644 --- a/git-pack/src/data/output/count/mod.rs +++ b/git-pack/src/data/output/count/mod.rs @@ -8,7 +8,7 @@ use crate::data::output::Count; pub enum PackLocation { /// We did not lookup this object NotLookedUp, - /// The object was looked up and there may be a location in a pack, along with enty information + /// The object was looked up and there may be a location in a pack, along with entry information LookedUp(Option), } diff --git a/git-pack/src/data/output/count/objects/types.rs b/git-pack/src/data/output/count/objects/types.rs index 3dd63dfd12b..8294e1318c6 100644 --- a/git-pack/src/data/output/count/objects/types.rs +++ b/git-pack/src/data/output/count/objects/types.rs @@ -58,7 +58,7 @@ impl Default for ObjectExpansion { } } -/// Configuration options for the pack generation functions provied in [this module][crate::data::output]. +/// Configuration options for the pack generation functions provided in [this module][crate::data::output]. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Options { diff --git a/git-pack/src/data/output/entry/iter_from_counts.rs b/git-pack/src/data/output/entry/iter_from_counts.rs index d5f6176236a..37725b99d7c 100644 --- a/git-pack/src/data/output/entry/iter_from_counts.rs +++ b/git-pack/src/data/output/entry/iter_from_counts.rs @@ -351,7 +351,7 @@ mod types { PackCopyAndBaseObjects, } - /// Configuration options for the pack generation functions provied in [this module][crate::data::output]. + /// Configuration options for the pack generation functions provided in [this module][crate::data::output]. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Options { diff --git a/git-pack/src/multi_index/access.rs b/git-pack/src/multi_index/access.rs index dc8338943eb..1bb79fb709f 100644 --- a/git-pack/src/multi_index/access.rs +++ b/git-pack/src/multi_index/access.rs @@ -24,7 +24,7 @@ pub struct Entry { /// Access methods impl File { - /// Returns the verion of the multi-index file. + /// Returns the version of the multi-index file. pub fn version(&self) -> Version { self.version } diff --git a/git-pack/tests/pack/data/file.rs b/git-pack/tests/pack/data/file.rs index bc8b5d4418d..5be6a86f0ed 100644 --- a/git-pack/tests/pack/data/file.rs +++ b/git-pack/tests/pack/data/file.rs @@ -67,7 +67,7 @@ mod decode_entry { #[test] fn blob_ofs_delta_two_links() { let buf = decode_entry_at_offset(3033); - assert_eq!(buf.len(), 173, "buffer length is the acutal object size"); + assert_eq!(buf.len(), 173, "buffer length is the actual object size"); assert_eq!( buf.capacity(), 2381, @@ -82,7 +82,7 @@ mod decode_entry { #[test] fn blob_ofs_delta_single_link() { let buf = decode_entry_at_offset(3569); - assert_eq!(buf.len(), 1163, "buffer length is the acutal object size"); + assert_eq!(buf.len(), 1163, "buffer length is the actual object size"); assert_eq!( buf.capacity(), 2398, diff --git a/git-pack/tests/pack/data/input.rs b/git-pack/tests/pack/data/input.rs index bf80d74a778..5bedd196bbf 100644 --- a/git-pack/tests/pack/data/input.rs +++ b/git-pack/tests/pack/data/input.rs @@ -182,7 +182,7 @@ mod lookup_ref_delta_objects { }), Ok(entry(base(), D_B)), ]; - let actual = LookupRefDeltaObjectsIter::new(input.into_iter(), |_, _| unreachable!("wont be called")) + let actual = LookupRefDeltaObjectsIter::new(input.into_iter(), |_, _| unreachable!("won't be called")) .collect::>(); for (actual, expected) in actual.into_iter().zip(expected.into_iter()) { assert_eq!(format!("{:?}", actual), format!("{:?}", expected)); diff --git a/git-packetline/tests/read/mod.rs b/git-packetline/tests/read/mod.rs index ca3d51f956b..f4e5b91e164 100644 --- a/git-packetline/tests/read/mod.rs +++ b/git-packetline/tests/read/mod.rs @@ -187,7 +187,7 @@ pub mod streaming_peek_iter { "it should read the second part of the identical file from the previously advanced reader" ); - // this reset is will cause actual io::Errors to occour + // this reset is will cause actual io::Errors to occur rd.reset(); let res = rd.read_line().await; assert_eq!( diff --git a/git-packetline/tests/read/sideband.rs b/git-packetline/tests/read/sideband.rs index ff364b5e0fc..5484b598216 100644 --- a/git-packetline/tests/read/sideband.rs +++ b/git-packetline/tests/read/sideband.rs @@ -183,7 +183,7 @@ async fn peek_past_a_delimiter_is_no_error() -> crate::Result { let res = reader.peek_data_line().await; assert!( res.is_none(), - "peeking past a flush packet is a 'natural' event that shold not cause an error" + "peeking past a flush packet is a 'natural' event that should not cause an error" ); Ok(()) } diff --git a/git-protocol/src/fetch/response/async_io.rs b/git-protocol/src/fetch/response/async_io.rs index b68e8a160d5..6472b5b67a0 100644 --- a/git-protocol/src/fetch/response/async_io.rs +++ b/git-protocol/src/fetch/response/async_io.rs @@ -46,7 +46,7 @@ impl Response { line.clear(); let peeked_line = match reader.peek_data_line().await { Some(Ok(Ok(line))) => String::from_utf8_lossy(line), - // This special case (hang/block forver) deals with a single NAK being a legitimate EOF sometimes + // This special case (hang/block forever) deals with a single NAK being a legitimate EOF sometimes // Note that this might block forever in stateful connections as there it's not really clear // if something will be following or not by just looking at the response. Instead you have to know // the arguments sent to the server and count response lines based on intricate knowledge on how the diff --git a/git-protocol/src/fetch/response/blocking_io.rs b/git-protocol/src/fetch/response/blocking_io.rs index 8befc72e64b..f63143b3794 100644 --- a/git-protocol/src/fetch/response/blocking_io.rs +++ b/git-protocol/src/fetch/response/blocking_io.rs @@ -45,7 +45,7 @@ impl Response { line.clear(); let peeked_line = match reader.peek_data_line() { Some(Ok(Ok(line))) => String::from_utf8_lossy(line), - // This special case (hang/block forver) deals with a single NAK being a legitimate EOF sometimes + // This special case (hang/block forever) deals with a single NAK being a legitimate EOF sometimes // Note that this might block forever in stateful connections as there it's not really clear // if something will be following or not by just looking at the response. Instead you have to know // the arguments sent to the server and count response lines based on intricate knowledge on how the diff --git a/git-protocol/src/fetch/tests/arguments.rs b/git-protocol/src/fetch/tests/arguments.rs index c2c102246b7..76c8bd0acaf 100644 --- a/git-protocol/src/fetch/tests/arguments.rs +++ b/git-protocol/src/fetch/tests/arguments.rs @@ -239,7 +239,7 @@ mod v2 { 0009done 0000" .as_bstr(), - "we filter features/capabilities without value as these apparently sholdn't be listed (remote dies otherwise)" + "we filter features/capabilities without value as these apparently shouldn't be listed (remote dies otherwise)" ); } diff --git a/git-ref/src/peel.rs b/git-ref/src/peel.rs index 503b94f1044..361c7d4d7b6 100644 --- a/git-ref/src/peel.rs +++ b/git-ref/src/peel.rs @@ -32,7 +32,7 @@ pub mod to_id { display("Refusing to follow more than {} levels of indirection", max_depth) } Find(err: Box) { - display("An error occurred when trying to resolve an object a refererence points to") + display("An error occurred when trying to resolve an object a reference points to") from() source(&**err) } diff --git a/git-ref/src/store/file/find.rs b/git-ref/src/store/file/find.rs index 27ee7c4b695..c672cc72941 100644 --- a/git-ref/src/store/file/find.rs +++ b/git-ref/src/store/file/find.rs @@ -315,7 +315,7 @@ pub mod existing { #[allow(missing_docs)] pub enum Error { Find(err: find::Error) { - display("An error occured while trying to find a reference") + display("An error occurred while trying to find a reference") from() source(err) } diff --git a/git-ref/src/store/file/loose/reflog/create_or_update/tests.rs b/git-ref/src/store/file/loose/reflog/create_or_update/tests.rs index e5ff078607f..410523db504 100644 --- a/git-ref/src/store/file/loose/reflog/create_or_update/tests.rs +++ b/git-ref/src/store/file/loose/reflog/create_or_update/tests.rs @@ -60,7 +60,7 @@ fn missing_reflog_creates_it_even_if_similarly_named_empty_dir_exists_and_append let new = hex_to_id("28ce6a8b26aa170e1de65536fe8abe1832bd3242"); let committer = Signature { name: "committer".into(), - email: "commiter@example.com".into(), + email: "committer@example.com".into(), time: Time { seconds_since_unix_epoch: 1234, offset_in_seconds: 1800, diff --git a/git-ref/src/store/file/transaction/prepare.rs b/git-ref/src/store/file/transaction/prepare.rs index d83f66ff820..a2c4f57f663 100644 --- a/git-ref/src/store/file/transaction/prepare.rs +++ b/git-ref/src/store/file/transaction/prepare.rs @@ -59,7 +59,7 @@ impl<'s> Transaction<'s> { ) .map_err(|err| Error::LockAcquire { err, - full_name: "borrowchk wont allow change.name()".into(), + full_name: "borrowchk won't allow change.name()".into(), })?; let existing_ref = existing_ref?; match (&expected, &existing_ref) { @@ -105,7 +105,7 @@ impl<'s> Transaction<'s> { ) .map_err(|err| Error::LockAcquire { err, - full_name: "borrowchk wont allow change.name() and this will be corrected by caller".into(), + full_name: "borrowchk won't allow change.name() and this will be corrected by caller".into(), })?; let existing_ref = existing_ref?; diff --git a/git-ref/src/store/packed/transaction.rs b/git-ref/src/store/packed/transaction.rs index a53efaf1428..45f88debd6c 100644 --- a/git-ref/src/store/packed/transaction.rs +++ b/git-ref/src/store/packed/transaction.rs @@ -260,7 +260,7 @@ pub mod commit { #[allow(missing_docs)] pub enum Error { Commit(err: git_lock::commit::Error) { - display("Changes to the resource could not be comitted") + display("Changes to the resource could not be committed") from() source(err) } diff --git a/git-ref/tests/file/store/iter.rs b/git-ref/tests/file/store/iter.rs index 5323b4ce697..5566d6aebb2 100644 --- a/git-ref/tests/file/store/iter.rs +++ b/git-ref/tests/file/store/iter.rs @@ -265,7 +265,7 @@ fn loose_iter_with_broken_refs() -> crate::Result { #[cfg(windows)] let msg = "The reference at 'refs\\broken' could not be instantiated"; assert_eq!( - actual[first_error].as_ref().expect_err("unparseable ref").to_string(), + actual[first_error].as_ref().expect_err("unparsable ref").to_string(), msg ); let ref_paths: Vec<_> = actual diff --git a/git-repository/src/config/snapshot.rs b/git-repository/src/config/snapshot.rs index 485532e2a63..b2653b78df9 100644 --- a/git-repository/src/config/snapshot.rs +++ b/git-repository/src/config/snapshot.rs @@ -61,7 +61,7 @@ impl<'repo> Snapshot<'repo> { /// Return the trusted and fully interpolated path at `key`, or `None` if there is no such value /// or if no value was found in a trusted file. - /// An error occours if the path could not be interpolated to its final value. + /// An error occurs if the path could not be interpolated to its final value. pub fn trusted_path( &self, key: &str, diff --git a/git-repository/src/create.rs b/git-repository/src/create.rs index 8d53336d54d..3f6e42094bf 100644 --- a/git-repository/src/create.rs +++ b/git-repository/src/create.rs @@ -102,7 +102,7 @@ pub struct Options { /// If true, the repository will be a bare repository without a worktree. pub bare: bool, - /// If set, use these filesytem capabilities to populate the respective git-config fields. + /// If set, use these filesystem capabilities to populate the respective git-config fields. /// If `None`, the directory will be probed. pub fs_capabilities: Option, } diff --git a/git-repository/src/id.rs b/git-repository/src/id.rs index 34e74d35ab1..db10f534c3d 100644 --- a/git-repository/src/id.rs +++ b/git-repository/src/id.rs @@ -147,7 +147,7 @@ pub mod ancestors { error_on_missing_commit: bool, // TODO: tests /// After iteration this flag is true if the iteration was stopped prematurely due to missing parent commits. - /// Note that this flag won't be `Some` if any iteration error occours, which is the case if + /// Note that this flag won't be `Some` if any iteration error occurs, which is the case if /// [`error_on_missing_commit()`][Iter::error_on_missing_commit()] was called. /// /// This happens if a repository is a shallow clone. @@ -157,7 +157,7 @@ pub mod ancestors { impl<'repo> Iter<'repo> { // TODO: tests - /// Once invoked, the iteration will return an error if a commit cannot be found in the object database. This typicall happens + /// Once invoked, the iteration will return an error if a commit cannot be found in the object database. This typically happens /// when operating on a shallow clone and thus is non-critical by default. /// /// Check the [`is_shallow`][Iter::is_shallow] field once the iteration ended otherwise to learn if a shallow commit graph diff --git a/git-repository/src/repository/worktree.rs b/git-repository/src/repository/worktree.rs index 41d0f26aa3e..12e953a49bc 100644 --- a/git-repository/src/repository/worktree.rs +++ b/git-repository/src/repository/worktree.rs @@ -41,7 +41,7 @@ impl crate::Repository { impl crate::Repository { /// Return the repository owning the main worktree. /// - /// Note that it might be the one that is currently open if this repository dosn't point to a linked worktree. + /// Note that it might be the one that is currently open if this repository doesn't point to a linked worktree. /// Also note that the main repo might be bare. pub fn main_repo(&self) -> Result { crate::ThreadSafeRepository::open_opts(self.common_dir(), self.options.clone()).map(Into::into) diff --git a/git-tempfile/README.md b/git-tempfile/README.md index 83a7cc64905..45821724e36 100644 --- a/git-tempfile/README.md +++ b/git-tempfile/README.md @@ -4,7 +4,7 @@ in a signal-safe way, making the change atomic. Tempfiles can also be used as locks as only one tempfile can exist at a given path at a time. * [x] registered temporary files which are deleted automatically as the process terminates or on drop - * [x] write to temorary file and persist it under new name + * [x] write to temporary file and persist it under new name * [x] close temporary files to convert them into a marker while saving system resources * [x] mark paths with a closed temporary file * [x] persist temporary files to prevent them from perishing. diff --git a/git-tempfile/examples/try-deadlock-on-cleanup.rs b/git-tempfile/examples/try-deadlock-on-cleanup.rs index c6fb6222061..30a38ca20e4 100644 --- a/git-tempfile/examples/try-deadlock-on-cleanup.rs +++ b/git-tempfile/examples/try-deadlock-on-cleanup.rs @@ -52,7 +52,7 @@ fn main() -> Result<(), Box> { let signal_raised = Arc::clone(&signal_raised); move || { eprintln!( - "If a deadlock occours tempfiles will be left in '{}'", + "If a deadlock occurs tempfiles will be left in '{}'", tmp.path().display() ); for ttl in (1..=secs_to_run).rev() { diff --git a/git-tempfile/src/handler.rs b/git-tempfile/src/handler.rs index 5081285a92d..ebccbe5a760 100644 --- a/git-tempfile/src/handler.rs +++ b/git-tempfile/src/handler.rs @@ -7,7 +7,7 @@ use crate::{SignalHandlerMode, NEXT_MAP_INDEX, REGISTER, SIGNAL_HANDLER_MODE}; /// /// # Safety /// Note that Mutexes of any kind are not allowed, and so aren't allocation or deallocation of memory. -/// We are usign lock-free datastructures and sprinkle in `std::mem::forget` to avoid deallocating. +/// We are using lock-free datastructures and sprinkle in `std::mem::forget` to avoid deallocating. pub fn cleanup_tempfiles() { let current_pid = std::process::id(); let one_past_last_index = NEXT_MAP_INDEX.load(Ordering::SeqCst); diff --git a/git-transport/tests/client/git.rs b/git-transport/tests/client/git.rs index da0c89215ee..10e4ebbd005 100644 --- a/git-transport/tests/client/git.rs +++ b/git-transport/tests/client/git.rs @@ -199,9 +199,9 @@ async fn handshake_v2_and_request() -> crate::Result { // This monstrosity simulates how one can process a pack received in async-io by transforming it into // blocking io::BufRead, while still handling the whole operation in a way that won't block the executor. // It's a way of `spawn_blocking()` in other executors. Currently this can only be done on a per-command basis. - // Thinking about it, it's most certainly fine to do `fetch' commands on another thread and move the entire conenction + // Thinking about it, it's most certainly fine to do `fetch' commands on another thread and move the entire connection // there as it's always the end of an operation and a lot of IO is required that is blocking anyway, like accessing - // commit graph information for fetch negotiations, and of cource processing a received pack. + // commit graph information for fetch negotiations, and of course processing a received pack. #[cfg(feature = "async-client")] Ok( blocking::unblock(|| futures_lite::future::block_on(handshake_v2_and_request_inner()).expect("no failure")) diff --git a/git-traverse/src/commit.rs b/git-traverse/src/commit.rs index bad52722a05..c13ec37467e 100644 --- a/git-traverse/src/commit.rs +++ b/git-traverse/src/commit.rs @@ -27,7 +27,7 @@ impl Default for Parents { pub enum Sorting { /// Commits are sorted as they are mentioned in the commit graph. Topological, - /// Commits are sorted by their commit time in decending order, that is newest first. + /// Commits are sorted by their commit time in descending order, that is newest first. /// /// The sorting applies to all currently queued commit ids and thus is full. ByCommitTimeNewestFirst, diff --git a/git-worktree/src/fs/cache/state.rs b/git-worktree/src/fs/cache/state.rs index 47d094b8044..5505b1ba49c 100644 --- a/git-worktree/src/fs/cache/state.rs +++ b/git-worktree/src/fs/cache/state.rs @@ -102,10 +102,10 @@ impl Ignore { if mapping.pattern.is_negative() { dir_match = Some(match_); } else { - // Note that returning here is wrong if this pattern _was_ preceeded by a negative pattern that + // Note that returning here is wrong if this pattern _was_ preceded by a negative pattern that // didn't match the directory, but would match now. // Git does it similarly so we do too even though it's incorrect. - // To fix this, one would probably keep track of whether there was a preceeding negative pattern, and + // To fix this, one would probably keep track of whether there was a preceding negative pattern, and // if so we check the path in full and only use the dir match if there was no match, similar to the negative // case above whose fix fortunately won't change the overall result. return match_.into(); diff --git a/gitoxide-core/Cargo.toml b/gitoxide-core/Cargo.toml index 0a767fb0562..388c5096708 100644 --- a/gitoxide-core/Cargo.toml +++ b/gitoxide-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gitoxide-core" -description = "The library implementating all capabilities of the gitoxide CLI" +description = "The library implementing all capabilities of the gitoxide CLI" repository = "https://github.com/Byron/gitoxide" version = "0.15.0" authors = ["Sebastian Thiel "] diff --git a/gitoxide-core/src/net.rs b/gitoxide-core/src/net.rs index 19d4122b102..96ed0137396 100644 --- a/gitoxide-core/src/net.rs +++ b/gitoxide-core/src/net.rs @@ -36,7 +36,7 @@ mod impls { impl Default for Protocol { fn default() -> Self { - // Note that it's very important this remains V2, as V1 may block forver in stateful (i.e. non-http) connections when fetching + // Note that it's very important this remains V2, as V1 may block forever in stateful (i.e. non-http) connections when fetching // as we chose not to complicate matters by counting which arguments where sent (just yet). Protocol::V2 } diff --git a/gitoxide-core/src/pack/create.rs b/gitoxide-core/src/pack/create.rs index 76658024aff..ddb6b4a5b4c 100644 --- a/gitoxide-core/src/pack/create.rs +++ b/gitoxide-core/src/pack/create.rs @@ -66,7 +66,7 @@ impl From for pack::data::output::count::objects::ObjectExpansi pub struct Context { /// The way input objects should be handled pub expansion: ObjectExpansion, - /// If `Some(threads)`, use this amount of `threads` to accelerate the counting phase at the cost of loosing + /// If `Some(threads)`, use this amount of `threads` to accelerate the counting phase at the cost of losing /// determinism as the order of objects during expansion changes with multiple threads unless no expansion is performed. /// In the latter case, this flag has no effect. /// If `None`, counting will only use one thread and thus yield the same sequence of objects in any case. diff --git a/src/plumbing/options.rs b/src/plumbing/options.rs index 58dde701425..58d0d916262 100644 --- a/src/plumbing/options.rs +++ b/src/plumbing/options.rs @@ -484,7 +484,7 @@ pub mod free { /// This flag is primarily to test the implementation of encoding, and requires to decode the object first. /// Encoding an object after decoding it should yield exactly the same bytes. /// This will reduce overall performance even more, as re-encoding requires to transform zero-copy objects into - /// owned objects, causing plenty of allocation to occour. + /// owned objects, causing plenty of allocation to occur. pub re_encode: bool, } From b7a5eb2e4bf677a1b64adf8ad646bdfd802b30ac Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 22 Jul 2022 21:33:08 +0800 Subject: [PATCH 361/366] fix stress test (adjust to changed `gix` command). --- Makefile | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index f8e6a689844..950ddaa6f90 100644 --- a/Makefile +++ b/Makefile @@ -241,20 +241,20 @@ commit_graphs = \ stress: ## Run various algorithms on big repositories $(MAKE) -j3 $(linux_repo) $(rust_repo) release-lean - time ./target/release/gix --verbose pack verify --re-encode $(linux_repo)/objects/pack/*.idx - time ./target/release/gix --verbose pack multi-index -i $(linux_repo)/objects/pack/multi-pack-index create $(linux_repo)/objects/pack/*.idx - time ./target/release/gix --verbose pack verify $(linux_repo)/objects/pack/multi-pack-index + time ./target/release/gix --verbose no-repo pack verify --re-encode $(linux_repo)/objects/pack/*.idx + time ./target/release/gix --verbose no-repo pack multi-index -i $(linux_repo)/objects/pack/multi-pack-index create $(linux_repo)/objects/pack/*.idx + time ./target/release/gix --verbose no-repo pack verify $(linux_repo)/objects/pack/multi-pack-index rm -Rf out; mkdir out && time ./target/release/gix --verbose pack index create -p $(linux_repo)/objects/pack/*.pack out/ - time ./target/release/gix --verbose pack verify out/*.idx + time ./target/release/gix --verbose no-repo pack verify out/*.idx - time ./target/release/gix --verbose pack verify --statistics $(rust_repo)/objects/pack/*.idx - time ./target/release/gix --verbose pack verify --algorithm less-memory $(rust_repo)/objects/pack/*.idx - time ./target/release/gix --verbose pack verify --re-encode $(rust_repo)/objects/pack/*.idx + time ./target/release/gix --verbose no-repo pack verify --statistics $(rust_repo)/objects/pack/*.idx + time ./target/release/gix --verbose no-repo pack verify --algorithm less-memory $(rust_repo)/objects/pack/*.idx + time ./target/release/gix --verbose no-repo pack verify --re-encode $(rust_repo)/objects/pack/*.idx # We must ensure there is exactly one pack file for the pack-explode *.idx globs to work. git repack -Ad - time ./target/release/gix --verbose pack explode .git/objects/pack/*.idx + time ./target/release/gix --verbose no-repo pack explode .git/objects/pack/*.idx - rm -Rf delme; mkdir delme && time ./target/release/gix --verbose pack explode .git/objects/pack/*.idx delme/ + rm -Rf delme; mkdir delme && time ./target/release/gix --verbose no-repo pack explode .git/objects/pack/*.idx delme/ $(MAKE) stress-commitgraph $(MAKE) bench-git-config @@ -262,7 +262,7 @@ stress: ## Run various algorithms on big repositories .PHONY: stress-commitgraph stress-commitgraph: release-lean $(commit_graphs) set -x; for path in $(wordlist 2, 999, $^); do \ - time ./target/release/gix --verbose commit-graph verify $$path; \ + time ./target/release/gix --verbose no-repo commit-graph verify $$path; \ done .PHONY: bench-git-config From 330b5fbef2151729957cbbadae1515cfc8373b32 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 22 Jul 2022 21:44:38 +0800 Subject: [PATCH 362/366] update crates listing in README --- README.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 66de18fd25d..f990be9f508 100644 --- a/README.md +++ b/README.md @@ -94,18 +94,25 @@ Follow linked crate name for detailed status. Please note that all crates follow ### Stabilization Candidates Crates that seem feature complete and need to see some more use before they can be released as 1.0. +Documentation is complete and was reviewed at least once. * [git-mailmap](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-mailmap) * [git-chunk](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-chunk) +* [git-ref](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-ref) +* [git-config](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-config) +* [git-glob](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-glob) ### Initial Development -* **usable** + +These crates may be missing some features and thus are somewhat incomplete, but what's there +is usable to some extend. + +* **usable** _(with rough but complete docs)_ * [git-actor](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-actor) * [git-hash](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-hash) * [git-object](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-object) * [git-validate](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-validate) * [git-url](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-url) - * [git-glob](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-glob) * [git-packetline](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-packetline) * [git-transport](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-transport) * [git-protocol](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-protocol) @@ -114,24 +121,22 @@ Crates that seem feature complete and need to see some more use before they can * [git-commitgraph](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-commitgraph) * [git-diff](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-diff) * [git-traverse](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-traverse) - * [git-config](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-config) * [git-features](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-features) * [git-credentials](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-credentials) * [git-sec](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-sec) * [git-quote](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-quote) - * [git-ref](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-ref) * [git-discover](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-discover) * [git-path](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-path) * [git-repository](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-repository) * `gitoxide-core` -* **very early** +* **very early** _(possibly without any documentation and many rough edges)_ * [git-index](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-index) * [git-worktree](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-worktree) * [git-bitmap](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-bitmap) * [git-attributes](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-attributes) * [git-revision](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-revision) * [git-date](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-date) -* **idea** +* **idea** _(just a name placeholder)_ * [git-note](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-note) * [git-filter](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-filter) * [git-lfs](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-lfs) From ef905d41053e3f08c9eca9eeaac078d2d2650271 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 23 Jul 2022 08:20:37 +0800 Subject: [PATCH 363/366] revert archive back to original --- .../tests/fixtures/generated-archives/make_diff_repo.tar.xz | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git-diff/tests/fixtures/generated-archives/make_diff_repo.tar.xz b/git-diff/tests/fixtures/generated-archives/make_diff_repo.tar.xz index e4d7f8d089c..46a0c2cc6ee 100644 --- a/git-diff/tests/fixtures/generated-archives/make_diff_repo.tar.xz +++ b/git-diff/tests/fixtures/generated-archives/make_diff_repo.tar.xz @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a121ee880e909e430504c730c3d3d14079f380c19811e65512ec836597c192b9 -size 15996 +oid sha256:f9ebb33f4370bf3b2a2c39957b586b3ee003b8f8539aa98fb89d4009f8f99cef +size 17144 From 47724c0edb382c036a3fc99884becfd2b0740d4b Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 22 Jul 2022 00:33:23 +0800 Subject: [PATCH 364/366] make fmt --- etc/check-package-size.sh | 12 +++++------ git-config/src/file/access/comfort.rs | 3 +-- git-config/src/file/access/mutate.rs | 10 ++++----- git-config/src/file/access/raw.rs | 3 +-- git-config/src/file/access/read_only.rs | 17 +++++++++------ git-config/src/file/impls.rs | 14 +++++++------ git-config/src/file/includes/mod.rs | 7 +++++-- git-config/src/file/includes/types.rs | 3 +-- git-config/src/file/init/comfort.rs | 7 +++++-- git-config/src/file/init/from_env.rs | 6 ++---- git-config/src/file/init/from_paths.rs | 8 ++++--- git-config/src/file/init/mod.rs | 7 +++++-- git-config/src/file/init/types.rs | 5 +---- git-config/src/file/meta.rs | 3 +-- git-config/src/file/mod.rs | 2 +- git-config/src/file/mutable/multi_value.rs | 21 ++++++++++++------- git-config/src/file/mutable/section.rs | 4 ++-- git-config/src/file/section/body.rs | 13 ++++++------ git-config/src/file/section/mod.rs | 17 +++++++++------ git-config/src/file/tests.rs | 9 +++++--- git-config/src/file/utils.rs | 3 +-- git-config/src/file/write.rs | 3 +-- git-config/src/parse/nom/tests.rs | 9 ++++---- git-config/src/source.rs | 9 +++++--- git-config/src/types.rs | 6 +++--- git-config/tests/file/access/read_only.rs | 7 +++++-- git-config/tests/file/init/from_env.rs | 7 ++++--- .../from_paths/includes/conditional/mod.rs | 10 ++++----- .../includes/conditional/onbranch.rs | 8 ++++--- .../init/from_paths/includes/unconditional.rs | 13 +++++++----- git-config/tests/file/init/from_paths/mod.rs | 3 +-- git-config/tests/file/mutable/section.rs | 8 +++---- git-config/tests/file/resolve_includes.rs | 3 +-- git-config/tests/file/write.rs | 3 ++- git-date/src/time.rs | 4 +--- git-discover/src/is.rs | 3 ++- git-discover/src/path.rs | 3 ++- git-repository/src/config/cache.rs | 4 +--- git-repository/src/config/mod.rs | 5 ++--- git-repository/src/config/snapshot.rs | 14 ++++++++----- git-repository/src/create.rs | 7 ++++--- git-repository/src/open.rs | 4 +--- git-repository/src/repository/identity.rs | 7 ++++--- git-repository/tests/repository/config.rs | 6 ++++-- gitoxide-core/src/repository/config.rs | 3 ++- src/plumbing/main.rs | 3 +-- src/plumbing/options.rs | 3 ++- tests/tools/src/lib.rs | 3 +-- 48 files changed, 182 insertions(+), 150 deletions(-) diff --git a/etc/check-package-size.sh b/etc/check-package-size.sh index 6dbc8126e38..fe8cf936f4d 100755 --- a/etc/check-package-size.sh +++ b/etc/check-package-size.sh @@ -20,15 +20,15 @@ echo "in root: gitoxide CLI" (enter git-pathspec && indent cargo diet -n --package-size-limit 5KB) (enter git-path && indent cargo diet -n --package-size-limit 15KB) (enter git-attributes && indent cargo diet -n --package-size-limit 15KB) -(enter git-discover && indent cargo diet -n --package-size-limit 15KB) +(enter git-discover && indent cargo diet -n --package-size-limit 20KB) (enter git-index && indent cargo diet -n --package-size-limit 30KB) (enter git-worktree && indent cargo diet -n --package-size-limit 30KB) (enter git-quote && indent cargo diet -n --package-size-limit 5KB) -(enter git-revision && indent cargo diet -n --package-size-limit 20KB) +(enter git-revision && indent cargo diet -n --package-size-limit 25KB) (enter git-bitmap && indent cargo diet -n --package-size-limit 5KB) (enter git-tempfile && indent cargo diet -n --package-size-limit 25KB) (enter git-lock && indent cargo diet -n --package-size-limit 15KB) -(enter git-config && indent cargo diet -n --package-size-limit 90KB) +(enter git-config && indent cargo diet -n --package-size-limit 110KB) (enter git-hash && indent cargo diet -n --package-size-limit 20KB) (enter git-chunk && indent cargo diet -n --package-size-limit 10KB) (enter git-rebase && indent cargo diet -n --package-size-limit 5KB) @@ -45,13 +45,13 @@ echo "in root: gitoxide CLI" (enter git-note && indent cargo diet -n --package-size-limit 5KB) (enter git-sec && indent cargo diet -n --package-size-limit 10KB) (enter git-tix && indent cargo diet -n --package-size-limit 5KB) -(enter git-credentials && indent cargo diet -n --package-size-limit 5KB) +(enter git-credentials && indent cargo diet -n --package-size-limit 10KB) (enter git-object && indent cargo diet -n --package-size-limit 25KB) (enter git-commitgraph && indent cargo diet -n --package-size-limit 25KB) (enter git-pack && indent cargo diet -n --package-size-limit 115KB) (enter git-odb && indent cargo diet -n --package-size-limit 120KB) (enter git-protocol && indent cargo diet -n --package-size-limit 50KB) (enter git-packetline && indent cargo diet -n --package-size-limit 35KB) -(enter git-repository && indent cargo diet -n --package-size-limit 110KB) +(enter git-repository && indent cargo diet -n --package-size-limit 120KB) (enter git-transport && indent cargo diet -n --package-size-limit 50KB) -(enter gitoxide-core && indent cargo diet -n --package-size-limit 70KB) +(enter gitoxide-core && indent cargo diet -n --package-size-limit 80KB) diff --git a/git-config/src/file/access/comfort.rs b/git-config/src/file/access/comfort.rs index 57fedf56125..0f15288a724 100644 --- a/git-config/src/file/access/comfort.rs +++ b/git-config/src/file/access/comfort.rs @@ -2,8 +2,7 @@ use std::{borrow::Cow, convert::TryFrom}; use bstr::BStr; -use crate::file::MetadataFilter; -use crate::{value, File}; +use crate::{file::MetadataFilter, value, File}; /// Comfortable API for accessing values impl<'event> File<'event> { diff --git a/git-config/src/file/access/mutate.rs b/git-config/src/file/access/mutate.rs index 2976e85b355..7810f7c32a3 100644 --- a/git-config/src/file/access/mutate.rs +++ b/git-config/src/file/access/mutate.rs @@ -1,13 +1,11 @@ -use git_features::threading::OwnShared; use std::borrow::Cow; -use crate::file::write::ends_with_newline; -use crate::file::{MetadataFilter, SectionId}; -use crate::parse::{Event, FrontMatterEvents}; +use git_features::threading::OwnShared; + use crate::{ - file::{self, rename_section, SectionMut}, + file::{self, rename_section, write::ends_with_newline, MetadataFilter, SectionId, SectionMut}, lookup, - parse::section, + parse::{section, Event, FrontMatterEvents}, File, }; diff --git a/git-config/src/file/access/raw.rs b/git-config/src/file/access/raw.rs index 60c4aae2ade..6a6d90a3838 100644 --- a/git-config/src/file/access/raw.rs +++ b/git-config/src/file/access/raw.rs @@ -3,9 +3,8 @@ use std::{borrow::Cow, collections::HashMap}; use bstr::BStr; use smallvec::ToSmallVec; -use crate::file::MetadataFilter; use crate::{ - file::{mutable::multi_value::EntryData, Index, MultiValueMut, Size, ValueMut}, + file::{mutable::multi_value::EntryData, Index, MetadataFilter, MultiValueMut, Size, ValueMut}, lookup, parse::{section, Event}, File, diff --git a/git-config/src/file/access/read_only.rs b/git-config/src/file/access/read_only.rs index 7891d2a0b76..4915cb3f8f1 100644 --- a/git-config/src/file/access/read_only.rs +++ b/git-config/src/file/access/read_only.rs @@ -1,14 +1,19 @@ -use std::iter::FromIterator; -use std::{borrow::Cow, convert::TryFrom}; +use std::{borrow::Cow, convert::TryFrom, iter::FromIterator}; use bstr::BStr; use git_features::threading::OwnShared; use smallvec::SmallVec; -use crate::file::write::{extract_newline, platform_newline}; -use crate::file::{Metadata, MetadataFilter}; -use crate::parse::Event; -use crate::{file, lookup, File}; +use crate::{ + file, + file::{ + write::{extract_newline, platform_newline}, + Metadata, MetadataFilter, + }, + lookup, + parse::Event, + File, +}; /// Read-only low-level access methods, as it requires generics for converting into /// custom values defined in this crate like [`Integer`][crate::Integer] and diff --git a/git-config/src/file/impls.rs b/git-config/src/file/impls.rs index 21e4f788c9c..c26df5fb81a 100644 --- a/git-config/src/file/impls.rs +++ b/git-config/src/file/impls.rs @@ -1,12 +1,14 @@ -use std::borrow::Cow; -use std::{convert::TryFrom, fmt::Display, str::FromStr}; +use std::{borrow::Cow, convert::TryFrom, fmt::Display, str::FromStr}; use bstr::{BStr, BString, ByteVec}; -use crate::file::Metadata; -use crate::parse::{section, Event}; -use crate::value::normalize; -use crate::{parse, File}; +use crate::{ + file::Metadata, + parse, + parse::{section, Event}, + value::normalize, + File, +}; impl FromStr for File<'static> { type Err = parse::Error; diff --git a/git-config/src/file/includes/mod.rs b/git-config/src/file/includes/mod.rs index 85680351b38..e95327f02ba 100644 --- a/git-config/src/file/includes/mod.rs +++ b/git-config/src/file/includes/mod.rs @@ -7,8 +7,11 @@ use bstr::{BStr, BString, ByteSlice, ByteVec}; use git_features::threading::OwnShared; use git_ref::Category; -use crate::file::{includes, init, Metadata, SectionId}; -use crate::{file, path, File}; +use crate::{ + file, + file::{includes, init, Metadata, SectionId}, + path, File, +}; impl File<'static> { /// Traverse all `include` and `includeIf` directives found in this instance and follow them, loading the diff --git a/git-config/src/file/includes/types.rs b/git-config/src/file/includes/types.rs index b97b28e8e99..9f342255093 100644 --- a/git-config/src/file/includes/types.rs +++ b/git-config/src/file/includes/types.rs @@ -1,5 +1,4 @@ -use crate::parse; -use crate::path::interpolate; +use crate::{parse, path::interpolate}; /// The error returned when following includes. #[derive(Debug, thiserror::Error)] diff --git a/git-config/src/file/init/comfort.rs b/git-config/src/file/init/comfort.rs index 6288ce37945..d9352fb615f 100644 --- a/git-config/src/file/init/comfort.rs +++ b/git-config/src/file/init/comfort.rs @@ -1,7 +1,10 @@ -use crate::file::{init, Metadata}; -use crate::{path, source, File, Source}; use std::path::PathBuf; +use crate::{ + file::{init, Metadata}, + path, source, File, Source, +}; + /// Easy-instantiation of typical non-repository git configuration files with all configuration defaulting to typical values. /// /// ### Limitations diff --git a/git-config/src/file/init/from_env.rs b/git-config/src/file/init/from_env.rs index 0fc923bd639..b721758b55a 100644 --- a/git-config/src/file/init/from_env.rs +++ b/git-config/src/file/init/from_env.rs @@ -1,8 +1,6 @@ -use std::borrow::Cow; -use std::convert::TryFrom; +use std::{borrow::Cow, convert::TryFrom}; -use crate::file::init; -use crate::{file, parse, parse::section, path::interpolate, File}; +use crate::{file, file::init, parse, parse::section, path::interpolate, File}; /// Represents the errors that may occur when calling [`File::from_env()`]. #[derive(Debug, thiserror::Error)] diff --git a/git-config/src/file/init/from_paths.rs b/git-config/src/file/init/from_paths.rs index 1cc44abffe9..2b4c840989d 100644 --- a/git-config/src/file/init/from_paths.rs +++ b/git-config/src/file/init/from_paths.rs @@ -1,8 +1,10 @@ -use crate::file::init::Options; -use crate::file::{init, Metadata}; -use crate::File; use std::collections::BTreeSet; +use crate::{ + file::{init, init::Options, Metadata}, + File, +}; + /// The error returned by [`File::from_paths_metadata()`] and [`File::from_path_no_includes()`]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] diff --git a/git-config/src/file/init/mod.rs b/git-config/src/file/init/mod.rs index b49a8f89d62..d44dece91cd 100644 --- a/git-config/src/file/init/mod.rs +++ b/git-config/src/file/init/mod.rs @@ -1,7 +1,10 @@ -use crate::file::{includes, section, Metadata}; -use crate::{parse, File}; use git_features::threading::OwnShared; +use crate::{ + file::{includes, section, Metadata}, + parse, File, +}; + mod types; pub use types::{Error, Options}; diff --git a/git-config/src/file/init/types.rs b/git-config/src/file/init/types.rs index 779931f5432..fcb17c0cabb 100644 --- a/git-config/src/file/init/types.rs +++ b/git-config/src/file/init/types.rs @@ -1,7 +1,4 @@ -use crate::file::init; -use crate::parse; -use crate::parse::Event; -use crate::path::interpolate; +use crate::{file::init, parse, parse::Event, path::interpolate}; /// The error returned by [`File::from_bytes_no_includes()`][crate::File::from_bytes_no_includes()]. #[derive(Debug, thiserror::Error)] diff --git a/git-config/src/file/meta.rs b/git-config/src/file/meta.rs index 9f8c02ffbcb..5bb653b846b 100644 --- a/git-config/src/file/meta.rs +++ b/git-config/src/file/meta.rs @@ -1,7 +1,6 @@ use std::path::PathBuf; -use crate::file::Metadata; -use crate::{file, Source}; +use crate::{file, file::Metadata, Source}; /// Instantiation impl Metadata { diff --git a/git-config/src/file/mod.rs b/git-config/src/file/mod.rs index 6a16dc16c12..540c1637761 100644 --- a/git-config/src/file/mod.rs +++ b/git-config/src/file/mod.rs @@ -1,9 +1,9 @@ //! A high level wrapper around a single or multiple `git-config` file, for reading and mutation. -use std::path::PathBuf; use std::{ borrow::Cow, collections::HashMap, ops::{Add, AddAssign}, + path::PathBuf, }; use bstr::BStr; diff --git a/git-config/src/file/mutable/multi_value.rs b/git-config/src/file/mutable/multi_value.rs index 2ddbff34b7b..396b49b6a47 100644 --- a/git-config/src/file/mutable/multi_value.rs +++ b/git-config/src/file/mutable/multi_value.rs @@ -1,12 +1,17 @@ -use crate::file::mutable::{escape_value, Whitespace}; -use crate::file::{self, Section, SectionId}; -use crate::lookup; -use crate::parse::{section, Event}; -use crate::value::{normalize_bstr, normalize_bstring}; +use std::{borrow::Cow, collections::HashMap, ops::DerefMut}; + use bstr::{BStr, BString, ByteVec}; -use std::borrow::Cow; -use std::collections::HashMap; -use std::ops::DerefMut; + +use crate::{ + file::{ + self, + mutable::{escape_value, Whitespace}, + Section, SectionId, + }, + lookup, + parse::{section, Event}, + value::{normalize_bstr, normalize_bstring}, +}; /// Internal data structure for [`MutableMultiValue`] #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] diff --git a/git-config/src/file/mutable/section.rs b/git-config/src/file/mutable/section.rs index 276faff7148..fac6f04febd 100644 --- a/git-config/src/file/mutable/section.rs +++ b/git-config/src/file/mutable/section.rs @@ -6,11 +6,11 @@ use std::{ use bstr::{BStr, BString, ByteSlice, ByteVec}; use smallvec::SmallVec; -use crate::file::{self, Section}; use crate::{ file::{ + self, mutable::{escape_value, Whitespace}, - Index, Size, + Index, Section, Size, }, lookup, parse, parse::{section::Key, Event}, diff --git a/git-config/src/file/section/body.rs b/git-config/src/file/section/body.rs index fa8460685d0..875f1c45ed5 100644 --- a/git-config/src/file/section/body.rs +++ b/git-config/src/file/section/body.rs @@ -1,10 +1,11 @@ -use crate::parse::section::Key; -use crate::parse::Event; -use crate::value::{normalize, normalize_bstr, normalize_bstring}; +use std::{borrow::Cow, iter::FusedIterator, ops::Range}; + use bstr::{BStr, BString, ByteVec}; -use std::borrow::Cow; -use std::iter::FusedIterator; -use std::ops::Range; + +use crate::{ + parse::{section::Key, Event}, + value::{normalize, normalize_bstr, normalize_bstring}, +}; /// A opaque type that represents a section body. #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Clone, Debug, Default)] diff --git a/git-config/src/file/section/mod.rs b/git-config/src/file/section/mod.rs index cbdd21bebba..10a5233c51a 100644 --- a/git-config/src/file/section/mod.rs +++ b/git-config/src/file/section/mod.rs @@ -1,16 +1,21 @@ -use crate::file::{Metadata, Section, SectionMut}; -use crate::parse::{section, Event}; -use crate::{file, parse}; +use std::{borrow::Cow, ops::Deref}; + use bstr::{BString, ByteSlice}; use smallvec::SmallVec; -use std::borrow::Cow; -use std::ops::Deref; + +use crate::{ + file, + file::{Metadata, Section, SectionMut}, + parse, + parse::{section, Event}, +}; pub(crate) mod body; -use crate::file::write::{extract_newline, platform_newline}; pub use body::{Body, BodyIter}; use git_features::threading::OwnShared; +use crate::file::write::{extract_newline, platform_newline}; + impl<'a> Deref for Section<'a> { type Target = Body<'a>; diff --git a/git-config/src/file/tests.rs b/git-config/src/file/tests.rs index 0901786f69f..c218dbaacb8 100644 --- a/git-config/src/file/tests.rs +++ b/git-config/src/file/tests.rs @@ -1,11 +1,14 @@ -use crate::file::{self, Section, SectionId}; -use crate::parse::section; use std::collections::HashMap; +use crate::{ + file::{self, Section, SectionId}, + parse::section, +}; + mod try_from { - use super::{bodies, headers}; use std::{borrow::Cow, collections::HashMap, convert::TryFrom}; + use super::{bodies, headers}; use crate::{ file::{self, SectionBodyIdsLut, SectionId}, parse::{ diff --git a/git-config/src/file/utils.rs b/git-config/src/file/utils.rs index 2c05a918d89..9b8405b3cae 100644 --- a/git-config/src/file/utils.rs +++ b/git-config/src/file/utils.rs @@ -1,5 +1,4 @@ -use std::cmp::Ordering; -use std::collections::HashMap; +use std::{cmp::Ordering, collections::HashMap}; use bstr::BStr; diff --git a/git-config/src/file/write.rs b/git-config/src/file/write.rs index 305fbd3bce8..71e3590b7f8 100644 --- a/git-config/src/file/write.rs +++ b/git-config/src/file/write.rs @@ -1,7 +1,6 @@ use bstr::{BStr, BString, ByteSlice}; -use crate::parse::Event; -use crate::File; +use crate::{parse::Event, File}; impl File<'_> { /// Serialize this type into a `BString` for convenience. diff --git a/git-config/src/parse/nom/tests.rs b/git-config/src/parse/nom/tests.rs index 2aa27aea8ae..e8d6a6a2fbb 100644 --- a/git-config/src/parse/nom/tests.rs +++ b/git-config/src/parse/nom/tests.rs @@ -163,13 +163,13 @@ mod config_name { } mod section { - use crate::parse::tests::util::newline_custom_event; use crate::parse::{ error::ParseNode, section, tests::util::{ - comment_event, fully_consumed, name_event, newline_event, section_header as parsed_section_header, - value_done_event, value_event, value_not_done_event, whitespace_event, + comment_event, fully_consumed, name_event, newline_custom_event, newline_event, + section_header as parsed_section_header, value_done_event, value_event, value_not_done_event, + whitespace_event, }, Event, Section, }; @@ -562,10 +562,9 @@ mod section { mod value_continuation { use bstr::ByteSlice; - use crate::parse::tests::util::newline_custom_event; use crate::parse::{ section, - tests::util::{into_events, newline_event, value_done_event, value_not_done_event}, + tests::util::{into_events, newline_custom_event, newline_event, value_done_event, value_not_done_event}, }; pub fn value_impl<'a>(i: &'a [u8], events: &mut section::Events<'a>) -> nom::IResult<&'a [u8], ()> { diff --git a/git-config/src/source.rs b/git-config/src/source.rs index 651dc0c27d0..1943e455679 100644 --- a/git-config/src/source.rs +++ b/git-config/src/source.rs @@ -1,7 +1,10 @@ +use std::{ + borrow::Cow, + ffi::OsString, + path::{Path, PathBuf}, +}; + use crate::Source; -use std::borrow::Cow; -use std::ffi::OsString; -use std::path::{Path, PathBuf}; /// The category of a [`Source`], in order of ascending precedence. #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] diff --git a/git-config/src/types.rs b/git-config/src/types.rs index a84c1798a45..246a7d44ea5 100644 --- a/git-config/src/types.rs +++ b/git-config/src/types.rs @@ -1,10 +1,10 @@ -use git_features::threading::OwnShared; use std::collections::{HashMap, VecDeque}; -use crate::file::Metadata; +use git_features::threading::OwnShared; + use crate::{ color, file, - file::{SectionBodyIdsLut, SectionId}, + file::{Metadata, SectionBodyIdsLut, SectionId}, integer, parse::section, }; diff --git a/git-config/tests/file/access/read_only.rs b/git-config/tests/file/access/read_only.rs index 4e583b9dc55..acc4e728256 100644 --- a/git-config/tests/file/access/read_only.rs +++ b/git-config/tests/file/access/read_only.rs @@ -1,8 +1,11 @@ use std::{borrow::Cow, convert::TryFrom, error::Error}; use bstr::BStr; -use git_config::file::{init, Metadata}; -use git_config::{color, integer, path, Boolean, Color, File, Integer}; +use git_config::{ + color, + file::{init, Metadata}, + integer, path, Boolean, Color, File, Integer, +}; use crate::file::cow_str; diff --git a/git-config/tests/file/init/from_env.rs b/git-config/tests/file/init/from_env.rs index f8dca3efebc..0cb9109b596 100644 --- a/git-config/tests/file/init/from_env.rs +++ b/git-config/tests/file/init/from_env.rs @@ -1,8 +1,9 @@ use std::{borrow::Cow, fs}; -use git_config::file::includes; -use git_config::file::init; -use git_config::{file::init::from_env, File}; +use git_config::{ + file::{includes, init, init::from_env}, + File, +}; use git_testtools::Env; use serial_test::serial; use tempfile::tempdir; diff --git a/git-config/tests/file/init/from_paths/includes/conditional/mod.rs b/git-config/tests/file/init/from_paths/includes/conditional/mod.rs index 9775d824d92..772cb4c4f21 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/mod.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/mod.rs @@ -1,9 +1,9 @@ -use std::str::FromStr; -use std::{fs, path::Path}; +use std::{fs, path::Path, str::FromStr}; -use git_config::file::includes; -use git_config::file::init; -use git_config::{path, File}; +use git_config::{ + file::{includes, init}, + path, File, +}; use tempfile::tempdir; use crate::file::{cow_str, init::from_paths::escape_backslashes}; diff --git a/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs b/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs index a2abcc01e82..f9255841587 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs @@ -4,9 +4,11 @@ use std::{ }; use bstr::{BString, ByteSlice}; -use git_config::file::includes; -use git_config::file::includes::conditional; -use git_config::file::init::{self}; +use git_config::file::{ + includes, + includes::conditional, + init::{self}, +}; use git_ref::{ transaction::{Change, PreviousValue, RefEdit}, FullName, Target, diff --git a/git-config/tests/file/init/from_paths/includes/unconditional.rs b/git-config/tests/file/init/from_paths/includes/unconditional.rs index 23184b087af..6e586a240e4 100644 --- a/git-config/tests/file/init/from_paths/includes/unconditional.rs +++ b/git-config/tests/file/init/from_paths/includes/unconditional.rs @@ -1,12 +1,15 @@ use std::fs; -use git_config::file::includes; -use git_config::file::init; -use git_config::{file::init::from_paths, File}; +use git_config::{ + file::{includes, init, init::from_paths}, + File, +}; use tempfile::tempdir; -use crate::file::init::from_paths::into_meta; -use crate::file::{cow_str, init::from_paths::escape_backslashes}; +use crate::file::{ + cow_str, + init::from_paths::{escape_backslashes, into_meta}, +}; fn follow_options() -> init::Options<'static> { init::Options { diff --git a/git-config/tests/file/init/from_paths/mod.rs b/git-config/tests/file/init/from_paths/mod.rs index a602745fe47..8c807e687b8 100644 --- a/git-config/tests/file/init/from_paths/mod.rs +++ b/git-config/tests/file/init/from_paths/mod.rs @@ -1,5 +1,4 @@ -use std::fs; -use std::path::PathBuf; +use std::{fs, path::PathBuf}; use git_config::{File, Source}; use tempfile::tempdir; diff --git a/git-config/tests/file/mutable/section.rs b/git-config/tests/file/mutable/section.rs index 5738aa989b7..2add8166b84 100644 --- a/git-config/tests/file/mutable/section.rs +++ b/git-config/tests/file/mutable/section.rs @@ -54,9 +54,10 @@ mod pop { } mod set { - use super::multi_value_section; use std::convert::TryInto; + use super::multi_value_section; + #[test] fn various_escapes_onto_various_kinds_of_values() -> crate::Result { let mut config = multi_value_section(); @@ -148,10 +149,9 @@ mod push { } mod set_leading_whitespace { - use bstr::BString; - use std::borrow::Cow; - use std::convert::TryFrom; + use std::{borrow::Cow, convert::TryFrom}; + use bstr::BString; use git_config::parse::section::Key; use crate::file::cow_str; diff --git a/git-config/tests/file/resolve_includes.rs b/git-config/tests/file/resolve_includes.rs index 2ec3b9a53b2..25c0697544e 100644 --- a/git-config/tests/file/resolve_includes.rs +++ b/git-config/tests/file/resolve_includes.rs @@ -1,5 +1,4 @@ -use git_config::file; -use git_config::file::init; +use git_config::{file, file::init}; #[test] fn missing_includes_are_ignored_by_default() -> crate::Result { diff --git a/git-config/tests/file/write.rs b/git-config/tests/file/write.rs index 584ecb95285..00929a88c2a 100644 --- a/git-config/tests/file/write.rs +++ b/git-config/tests/file/write.rs @@ -1,6 +1,7 @@ +use std::convert::TryFrom; + use bstr::ByteVec; use git_config::file::{init, Metadata}; -use std::convert::TryFrom; #[test] fn empty_sections_roundtrip() { diff --git a/git-date/src/time.rs b/git-date/src/time.rs index d94614b2f4e..c7b711e13b6 100644 --- a/git-date/src/time.rs +++ b/git-date/src/time.rs @@ -1,6 +1,4 @@ -use std::convert::TryInto; -use std::io; -use std::ops::Sub; +use std::{convert::TryInto, io, ops::Sub}; use crate::Time; diff --git a/git-discover/src/is.rs b/git-discover/src/is.rs index 1dd221674c7..7951d660606 100644 --- a/git-discover/src/is.rs +++ b/git-discover/src/is.rs @@ -1,6 +1,7 @@ -use crate::DOT_GIT_DIR; use std::{borrow::Cow, ffi::OsStr, path::Path}; +use crate::DOT_GIT_DIR; + /// Returns true if the given `git_dir` seems to be a bare repository. /// /// Please note that repositories without an index generally _look_ bare, even though they might also be uninitialized. diff --git a/git-discover/src/path.rs b/git-discover/src/path.rs index 8c882185b3a..6269d68cc32 100644 --- a/git-discover/src/path.rs +++ b/git-discover/src/path.rs @@ -1,6 +1,7 @@ -use crate::DOT_GIT_DIR; use std::{io::Read, path::PathBuf}; +use crate::DOT_GIT_DIR; + /// pub mod from_gitdir_file { /// The error returned by [`from_gitdir_file()`][crate::path::from_gitdir_file()]. diff --git a/git-repository/src/config/cache.rs b/git-repository/src/config/cache.rs index 71e41914084..c38d0835793 100644 --- a/git-repository/src/config/cache.rs +++ b/git-repository/src/config/cache.rs @@ -1,11 +1,9 @@ use std::{convert::TryFrom, path::PathBuf}; -use crate::repository; use git_config::{Boolean, Integer}; use super::{Cache, Error}; -use crate::bstr::ByteSlice; -use crate::repository::identity; +use crate::{bstr::ByteSlice, repository, repository::identity}; /// A utility to deal with the cyclic dependency between the ref store and the configuration. The ref-store needs the /// object hash kind, and the configuration needs the current branch name to resolve conditional includes with `onbranch`. diff --git a/git-repository/src/config/mod.rs b/git-repository/src/config/mod.rs index ce547f800fb..627a558ff02 100644 --- a/git-repository/src/config/mod.rs +++ b/git-repository/src/config/mod.rs @@ -1,8 +1,7 @@ -use crate::repository::identity; -use crate::{bstr::BString, permission, Repository}; +pub use git_config::*; use git_features::threading::OnceCell; -pub use git_config::*; +use crate::{bstr::BString, permission, repository::identity, Repository}; pub(crate) mod cache; mod snapshot; diff --git a/git-repository/src/config/snapshot.rs b/git-repository/src/config/snapshot.rs index b2653b78df9..7bbcb31a88d 100644 --- a/git-repository/src/config/snapshot.rs +++ b/git-repository/src/config/snapshot.rs @@ -1,8 +1,12 @@ -use crate::bstr::BStr; -use crate::config::cache::interpolate_context; -use crate::config::Snapshot; -use std::borrow::Cow; -use std::fmt::{Debug, Formatter}; +use std::{ + borrow::Cow, + fmt::{Debug, Formatter}, +}; + +use crate::{ + bstr::BStr, + config::{cache::interpolate_context, Snapshot}, +}; /// Access configuration values, frozen in time, using a `key` which is a `.` separated string of up to /// three tokens, namely `section_name.[subsection_name.]value_name`, like `core.bare` or `remote.origin.url`. diff --git a/git-repository/src/create.rs b/git-repository/src/create.rs index 3f6e42094bf..8fb1c2ed5f0 100644 --- a/git-repository/src/create.rs +++ b/git-repository/src/create.rs @@ -1,12 +1,13 @@ -use git_config::parse::section; -use git_discover::DOT_GIT_DIR; -use std::convert::TryFrom; use std::{ + convert::TryFrom, fs::{self, OpenOptions}, io::Write, path::{Path, PathBuf}, }; +use git_config::parse::section; +use git_discover::DOT_GIT_DIR; + /// The error used in [`into()`]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] diff --git a/git-repository/src/open.rs b/git-repository/src/open.rs index d492fdb4b49..1d9abccfb5a 100644 --- a/git-repository/src/open.rs +++ b/git-repository/src/open.rs @@ -2,9 +2,7 @@ use std::path::PathBuf; use git_features::threading::OwnShared; -use crate::config::cache::interpolate_context; -use crate::{config, permission, permissions}; -use crate::{Permissions, ThreadSafeRepository}; +use crate::{config, config::cache::interpolate_context, permission, permissions, Permissions, ThreadSafeRepository}; /// A way to configure the usage of replacement objects, see `git replace`. #[derive(Debug, Clone)] diff --git a/git-repository/src/repository/identity.rs b/git-repository/src/repository/identity.rs index d3d28081e68..a5f9207fe7f 100644 --- a/git-repository/src/repository/identity.rs +++ b/git-repository/src/repository/identity.rs @@ -1,8 +1,9 @@ -use crate::bstr::BString; -use crate::permission; +use std::borrow::Cow; + use git_actor::SignatureRef; use git_config::File; -use std::borrow::Cow; + +use crate::{bstr::BString, permission}; /// Identity handling. impl crate::Repository { diff --git a/git-repository/tests/repository/config.rs b/git-repository/tests/repository/config.rs index 7f0c8f0d10b..1a3c1954c51 100644 --- a/git-repository/tests/repository/config.rs +++ b/git-repository/tests/repository/config.rs @@ -1,9 +1,11 @@ -use crate::named_repo; +use std::path::Path; + use git_repository as git; use git_sec::{Access, Permission}; use git_testtools::Env; use serial_test::serial; -use std::path::Path; + +use crate::named_repo; #[test] #[serial] diff --git a/gitoxide-core/src/repository/config.rs b/gitoxide-core/src/repository/config.rs index 74e8e8bd33b..a179e2c1db2 100644 --- a/gitoxide-core/src/repository/config.rs +++ b/gitoxide-core/src/repository/config.rs @@ -1,7 +1,8 @@ -use crate::OutputFormat; use anyhow::{bail, Result}; use git_repository as git; +use crate::OutputFormat; + pub fn list( repo: git::Repository, filters: Vec, diff --git a/src/plumbing/main.rs b/src/plumbing/main.rs index 252d046e4ab..e3343b278bc 100644 --- a/src/plumbing/main.rs +++ b/src/plumbing/main.rs @@ -13,9 +13,8 @@ use git_repository::bstr::io::BufReadExt; use gitoxide_core as core; use gitoxide_core::pack::verify; -use crate::plumbing::options::{commit, config, exclude, mailmap, odb, revision, tree}; use crate::{ - plumbing::options::{free, Args, Subcommands}, + plumbing::options::{commit, config, exclude, free, mailmap, odb, revision, tree, Args, Subcommands}, shared::pretty::prepare_and_run, }; diff --git a/src/plumbing/options.rs b/src/plumbing/options.rs index 58d0d916262..489133e7cd9 100644 --- a/src/plumbing/options.rs +++ b/src/plumbing/options.rs @@ -1,6 +1,7 @@ -use gitoxide_core as core; use std::path::PathBuf; +use gitoxide_core as core; + #[derive(Debug, clap::Parser)] #[clap(name = "gix-plumbing", about = "The git underworld", version = clap::crate_version!())] #[clap(subcommand_required = true)] diff --git a/tests/tools/src/lib.rs b/tests/tools/src/lib.rs index 77fdbc0a818..4aab07ddbd5 100644 --- a/tests/tools/src/lib.rs +++ b/tests/tools/src/lib.rs @@ -9,11 +9,10 @@ use std::{ pub use bstr; use bstr::{BStr, ByteSlice}; use io_close::Close; +pub use is_ci; use nom::error::VerboseError; use once_cell::sync::Lazy; use parking_lot::Mutex; - -pub use is_ci; pub use tempfile; pub type Result = std::result::Result>; From 8652b15c48b4e812b2f6668f29738433af296116 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 23 Jul 2022 13:35:50 +0800 Subject: [PATCH 365/366] fix cron-jobbed stress test for good --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 950ddaa6f90..3987ee4dba4 100644 --- a/Makefile +++ b/Makefile @@ -244,7 +244,7 @@ stress: ## Run various algorithms on big repositories time ./target/release/gix --verbose no-repo pack verify --re-encode $(linux_repo)/objects/pack/*.idx time ./target/release/gix --verbose no-repo pack multi-index -i $(linux_repo)/objects/pack/multi-pack-index create $(linux_repo)/objects/pack/*.idx time ./target/release/gix --verbose no-repo pack verify $(linux_repo)/objects/pack/multi-pack-index - rm -Rf out; mkdir out && time ./target/release/gix --verbose pack index create -p $(linux_repo)/objects/pack/*.pack out/ + rm -Rf out; mkdir out && time ./target/release/gix --verbose no-repo pack index create -p $(linux_repo)/objects/pack/*.pack out/ time ./target/release/gix --verbose no-repo pack verify out/*.idx time ./target/release/gix --verbose no-repo pack verify --statistics $(rust_repo)/objects/pack/*.idx From 270242c707bd086b7746ee45b55791587f1484b1 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 23 Jul 2022 21:56:09 +0800 Subject: [PATCH 366/366] fix: provide additional explanation about when to use `open::Options::with()` --- git-repository/src/lib.rs | 6 ++++++ git-repository/src/open.rs | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/git-repository/src/lib.rs b/git-repository/src/lib.rs index f0ece8c1221..76da8548b3e 100644 --- a/git-repository/src/lib.rs +++ b/git-repository/src/lib.rs @@ -426,6 +426,12 @@ pub mod discover { /// Try to open a git repository in `directory` and search upwards through its parents until one is found, /// while applying `options`. Then use the `trust_map` to determine which of our own repository options to use /// for instantiations. + /// + /// Note that [trust overrides](crate::open::Options::with()) in the `trust_map` are not effective here and we will + /// always override it with the determined trust value. This is a precaution as the API user is unable to actually know + /// if the directory that is discovered can indeed be trusted (or else they'd have to implement the discovery themselves + /// and be sure that no attacker ever gets access to a directory structure. The cost of this is a permission check, which + /// seems acceptable). pub fn discover_opts( directory: impl AsRef, options: upwards::Options, diff --git a/git-repository/src/open.rs b/git-repository/src/open.rs index 1d9abccfb5a..0e29a28c847 100644 --- a/git-repository/src/open.rs +++ b/git-repository/src/open.rs @@ -154,6 +154,13 @@ impl Options { /// /// If not called explicitly, it will be determined by looking at its /// ownership via [`git_sec::Trust::from_path_ownership()`]. + /// + /// # Security Warning + /// + /// Use with extreme care and only if it's absolutely known that the repository + /// is always controlled by the desired user. Using this capability _only_ saves + /// a permission check and only so if the [`open()`][Self::open()] method is used, + /// as opposed to discovery. pub fn with(mut self, trust: git_sec::Trust) -> Self { self.git_dir_trust = trust.into(); self