From fece9725d60201b16b67073c185195b88fa1ad20 Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Fri, 13 May 2022 09:25:40 +0200 Subject: [PATCH 001/248] initial setup of pathspec module --- git-pathspec/src/lib.rs | 10 ++++++++++ git-pathspec/tests/pathspec.rs | 11 +++++++++++ 2 files changed, 21 insertions(+) create mode 100644 git-pathspec/tests/pathspec.rs diff --git a/git-pathspec/src/lib.rs b/git-pathspec/src/lib.rs index d7a83e4f525..03b380ca3eb 100644 --- a/git-pathspec/src/lib.rs +++ b/git-pathspec/src/lib.rs @@ -1 +1,11 @@ #![forbid(unsafe_code, rust_2018_idioms)] + +pub struct Pathlist {} + +pub fn matches(_buf: &[u8]) -> Pathlist { + todo!() +} + +pub fn parse(_buf: &[u8]) -> () { + todo!() +} diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs new file mode 100644 index 00000000000..fb6e7143c00 --- /dev/null +++ b/git-pathspec/tests/pathspec.rs @@ -0,0 +1,11 @@ +#[test] +fn can_parse() { + let buf = b""; + git_pathspec::parse(buf); +} + +#[test] +fn can_match() { + let buf = b"git-pathspec/tests/pathspec.rs"; + git_pathspec::matches(buf); +} From 7d95f162a3edb7b2714dfadb9b5cf8311f3da061 Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Fri, 13 May 2022 15:39:47 +0200 Subject: [PATCH 002/248] pathspec parser is functional (#415) --- Cargo.lock | 5 ++ git-pathspec/Cargo.toml | 3 + git-pathspec/src/lib.rs | 20 ++++-- git-pathspec/src/parse.rs | 127 +++++++++++++++++++++++++++++++++ git-pathspec/tests/pathspec.rs | 57 +++++++++++++-- 5 files changed, 203 insertions(+), 9 deletions(-) create mode 100644 git-pathspec/src/parse.rs diff --git a/Cargo.lock b/Cargo.lock index 502df18a787..18c0a56753d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1403,6 +1403,11 @@ dependencies = [ [[package]] name = "git-pathspec" version = "0.0.0" +dependencies = [ + "bitflags", + "bstr", + "git-glob", +] [[package]] name = "git-protocol" diff --git a/git-pathspec/Cargo.toml b/git-pathspec/Cargo.toml index 0224338d4d9..16b92fd4f7a 100644 --- a/git-pathspec/Cargo.toml +++ b/git-pathspec/Cargo.toml @@ -13,3 +13,6 @@ doctest = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +bstr = { version = "0.2.13", default-features = false, features = ["std"]} +bitflags = "1.3.2" +git-glob = { version = "^0.2.0", path = "../git-glob" } diff --git a/git-pathspec/src/lib.rs b/git-pathspec/src/lib.rs index 03b380ca3eb..adba4c9d5c0 100644 --- a/git-pathspec/src/lib.rs +++ b/git-pathspec/src/lib.rs @@ -1,11 +1,21 @@ #![forbid(unsafe_code, rust_2018_idioms)] -pub struct Pathlist {} +use bitflags::bitflags; +pub use parse::Pattern; -pub fn matches(_buf: &[u8]) -> Pathlist { - todo!() +mod parse; + +bitflags! { + pub struct MagicSignature: u32 { + const TOP = 1 << 0; + const LITERAL = 1 << 1; + const ICASE = 1 << 2; + const GLOB = 1 << 3; + const ATTR = 1 << 4; + const EXCLUDE = 1 << 5; + } } -pub fn parse(_buf: &[u8]) -> () { - todo!() +pub fn parse(input: &[u8]) -> Pattern { + Pattern::from_bytes(input) } diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs new file mode 100644 index 00000000000..d87f8c0aa21 --- /dev/null +++ b/git-pathspec/src/parse.rs @@ -0,0 +1,127 @@ +use crate::MagicSignature; +use bstr::BString; +use std::iter::{FromIterator, Peekable}; + +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +pub struct Pattern { + pub path: BString, + pub signature: Option, +} + +impl Pattern { + pub fn from_bytes(input: &[u8]) -> Self { + let mut parser = Parser::new(input); + parser.parse() + } +} + +struct Parser<'a> { + input: Peekable>, +} + +impl<'a> Parser<'a> { + fn new(input: &'a [u8]) -> Self { + Self { + input: input.iter().peekable(), + } + } + + fn parse(&mut self) -> Pattern { + let signature = self.parse_magic_signature(); + let path = self.parse_path(); + + Pattern { path, signature } + } + + fn parse_magic_signature(&mut self) -> Option { + match self.input.peek() { + Some(b':') => { + self.input.next(); + let mut signature = MagicSignature::empty(); + + while let Some(&b) = self.input.peek() { + match b { + b':' if !signature.is_empty() => { + self.input.next(); + break; + } + b':' => { + self.input.next(); + } + b'/' => { + self.input.next(); + signature |= MagicSignature::TOP + } + b'^' | b'!' => { + self.input.next(); + signature |= MagicSignature::EXCLUDE; + } + b'(' => { + self.input.next(); + signature |= self.parse_magic_keywords(); + } + _ => break, + } + } + + if signature.is_empty() { + None + } else { + Some(signature) + } + } + _ => None, + } + } + + fn parse_magic_keywords(&mut self) -> MagicSignature { + let mut buf: Vec<&u8> = vec![]; + let mut keywords: Vec> = vec![]; + + while let Some(b) = self.input.next() { + match b { + b')' => { + if !buf.is_empty() { + keywords.push(buf.drain(..).collect()); + } + break; + } + b',' => { + if !buf.is_empty() { + keywords.push(buf.drain(..).collect()); + } + } + b' ' => { + // TODO: make this non panicing + panic!("space in magic keywords not allowed"); + } + _ => { + buf.push(b); + } + } + } + + let mut signature = MagicSignature::empty(); + + for keyword in keywords.into_iter() { + let keyword = keyword.iter().map(|&x| x.to_owned()).collect::>(); + let keyword = String::from_utf8_lossy(&keyword[..]); + + match &keyword[..] { + "top" => signature |= MagicSignature::TOP, + "literal" => signature |= MagicSignature::LITERAL, + "icase" => signature |= MagicSignature::ICASE, + "glob" => signature |= MagicSignature::GLOB, + "attr" => signature |= MagicSignature::ATTR, + "exclude" => signature |= MagicSignature::EXCLUDE, + _ => panic!("Invalid signature: \"{}\"", keyword), + } + } + + signature + } + + fn parse_path(&mut self) -> BString { + BString::from_iter(self.input.clone().copied()) + } +} diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index fb6e7143c00..a384788fbf6 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -1,11 +1,60 @@ +use git_pathspec::{MagicSignature, Pattern}; + #[test] fn can_parse() { - let buf = b""; - git_pathspec::parse(buf); + let inputs = vec![ + ("some/path", pat("some/path", None)), + ("some/*.path", pat("some/*.path", None)), + (":/", pat("", Some(MagicSignature::TOP))), + (":^", pat("", Some(MagicSignature::EXCLUDE))), + (":!", pat("", Some(MagicSignature::EXCLUDE))), + (":(top)", pat("", Some(MagicSignature::TOP))), + (":(literal)", pat("", Some(MagicSignature::LITERAL))), + (":(icase)", pat("", Some(MagicSignature::ICASE))), + (":(glob)", pat("", Some(MagicSignature::GLOB))), + (":(attr)", pat("", Some(MagicSignature::ATTR))), + (":(exclude)", pat("", Some(MagicSignature::EXCLUDE))), + ( + ":(top,literal,icase,glob,attr,exclude)some/path", + pat( + "some/path", + Some( + MagicSignature::TOP + | MagicSignature::LITERAL + | MagicSignature::ICASE + | MagicSignature::GLOB + | MagicSignature::ATTR + | MagicSignature::EXCLUDE, + ), + ), + ), + (":/:some/path", pat("some/path", Some(MagicSignature::TOP))), + ( + ":!(literal)some/*path", + pat("some/*path", Some(MagicSignature::EXCLUDE | MagicSignature::LITERAL)), + ), + (":", pat("", None)), + (":()", pat("", None)), + (":::::", pat("", None)), + (":!/!/:", pat("", Some(MagicSignature::TOP | MagicSignature::EXCLUDE))), + ]; + + for (input, expected) in inputs { + let pattern = git_pathspec::parse(input.as_bytes()); + assert_eq!(pattern, expected, "while checking input: \"{}\"", input); + } +} + +fn pat(path: &str, signature: Option) -> Pattern { + Pattern { + path: path.into(), + signature, + } } #[test] +#[ignore] fn can_match() { - let buf = b"git-pathspec/tests/pathspec.rs"; - git_pathspec::matches(buf); + // let buf = b"git-pathspec/tests/pathspec.rs"; + // git_pathspec::matches(buf); } From fbed980797057efb22140e8ff371989d49cc2a73 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 16 May 2022 21:10:17 +0800 Subject: [PATCH 003/248] refactor --- README.md | 2 +- git-pathspec/src/lib.rs | 8 +++++++- git-pathspec/src/parse.rs | 24 ++++++------------------ 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index fe05bf5b6c3..fe07fcaa722 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,7 @@ Crates that seem feature complete and need to see some more use before they can * [git-attributes](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-attributes) * [git-path](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-path) * [git-discover](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-discover) + * [git-pathspec](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-pathspec) * **idea** * [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) @@ -133,7 +134,6 @@ Crates that seem feature complete and need to see some more use before they can * [git-lfs](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-lfs) * [git-rebase](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-rebase) * [git-sequencer](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-sequencer) - * [git-pathspec](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-pathspec) * [git-submodule](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-submodule) * [git-tui](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-tui) * [git-tix](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-tix) diff --git a/git-pathspec/src/lib.rs b/git-pathspec/src/lib.rs index adba4c9d5c0..4e2b5c73a5d 100644 --- a/git-pathspec/src/lib.rs +++ b/git-pathspec/src/lib.rs @@ -1,10 +1,16 @@ #![forbid(unsafe_code, rust_2018_idioms)] use bitflags::bitflags; -pub use parse::Pattern; +use bstr::BString; mod parse; +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +pub struct Pattern { + pub path: BString, + pub signature: Option, +} + bitflags! { pub struct MagicSignature: u32 { const TOP = 1 << 0; diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index d87f8c0aa21..c0a9936b834 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -1,17 +1,10 @@ -use crate::MagicSignature; +use crate::{MagicSignature, Pattern}; use bstr::BString; use std::iter::{FromIterator, Peekable}; -#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] -pub struct Pattern { - pub path: BString, - pub signature: Option, -} - impl Pattern { pub fn from_bytes(input: &[u8]) -> Self { - let mut parser = Parser::new(input); - parser.parse() + Parser::new(input).parse() } } @@ -41,12 +34,11 @@ impl<'a> Parser<'a> { while let Some(&b) = self.input.peek() { match b { - b':' if !signature.is_empty() => { - self.input.next(); - break; - } b':' => { self.input.next(); + if !signature.is_empty() { + break; + } } b'/' => { self.input.next(); @@ -64,11 +56,7 @@ impl<'a> Parser<'a> { } } - if signature.is_empty() { - None - } else { - Some(signature) - } + (!signature.is_empty()).then(|| signature) } _ => None, } From d109cfead637b0b2c2866fb411eeccbf6a5bff2c Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 16 May 2022 21:15:13 +0800 Subject: [PATCH 004/248] refactor --- git-pathspec/src/parse.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index c0a9936b834..f6cb535933e 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -63,28 +63,28 @@ impl<'a> Parser<'a> { } fn parse_magic_keywords(&mut self) -> MagicSignature { - let mut buf: Vec<&u8> = vec![]; - let mut keywords: Vec> = vec![]; + let mut buf: Vec = vec![]; + let mut keywords: Vec> = vec![]; while let Some(b) = self.input.next() { match b { b')' => { if !buf.is_empty() { - keywords.push(buf.drain(..).collect()); + keywords.push(buf); } break; } b',' => { if !buf.is_empty() { - keywords.push(buf.drain(..).collect()); + keywords.push(std::mem::take(&mut buf)); } } b' ' => { - // TODO: make this non panicing + // TODO: make this non panicking panic!("space in magic keywords not allowed"); } _ => { - buf.push(b); + buf.push(*b); } } } From a0477e9b1fdf6ef289208a77f0539ea090c84e79 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 16 May 2022 21:26:10 +0800 Subject: [PATCH 005/248] refactor --- git-pathspec/src/parse.rs | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index f6cb535933e..4f5920112f3 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -19,7 +19,7 @@ impl<'a> Parser<'a> { } } - fn parse(&mut self) -> Pattern { + fn parse(mut self) -> Pattern { let signature = self.parse_magic_signature(); let path = self.parse_path(); @@ -63,8 +63,8 @@ impl<'a> Parser<'a> { } fn parse_magic_keywords(&mut self) -> MagicSignature { - let mut buf: Vec = vec![]; - let mut keywords: Vec> = vec![]; + let mut buf = Vec::new(); + let mut keywords = Vec::new(); while let Some(b) = self.input.next() { match b { @@ -92,24 +92,22 @@ impl<'a> Parser<'a> { let mut signature = MagicSignature::empty(); for keyword in keywords.into_iter() { - let keyword = keyword.iter().map(|&x| x.to_owned()).collect::>(); - let keyword = String::from_utf8_lossy(&keyword[..]); - - match &keyword[..] { - "top" => signature |= MagicSignature::TOP, - "literal" => signature |= MagicSignature::LITERAL, - "icase" => signature |= MagicSignature::ICASE, - "glob" => signature |= MagicSignature::GLOB, - "attr" => signature |= MagicSignature::ATTR, - "exclude" => signature |= MagicSignature::EXCLUDE, - _ => panic!("Invalid signature: \"{}\"", keyword), + signature |= match &keyword[..] { + b"top" => MagicSignature::TOP, + b"literal" => MagicSignature::LITERAL, + b"icase" => MagicSignature::ICASE, + b"glob" => MagicSignature::GLOB, + b"attr" => MagicSignature::ATTR, + b"exclude" => MagicSignature::EXCLUDE, + // TODO: make this an error + _ => panic!("Invalid signature"), } } signature } - fn parse_path(&mut self) -> BString { - BString::from_iter(self.input.clone().copied()) + fn parse_path(self) -> BString { + BString::from_iter(self.input.copied()) } } From e8da18663d5110c87200287a1bc0d1b6f86cf0f9 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 16 May 2022 21:34:22 +0800 Subject: [PATCH 006/248] =?UTF-8?q?hint=20for=20how=20to=20make=20a=20func?= =?UTF-8?q?tional=20version=20bearable=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …regargding parameter types. --- git-pathspec/src/parse.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index 4f5920112f3..8d76287b477 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -8,8 +8,10 @@ impl Pattern { } } +type ByteIter<'a> = Peekable>; + struct Parser<'a> { - input: Peekable>, + input: ByteIter<'a>, } impl<'a> Parser<'a> { From 31aba11c953b5b7dd70f14ee904026d12db69d10 Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Thu, 19 May 2022 09:56:18 +0200 Subject: [PATCH 007/248] error handling: parser can return a result now (#415) --- Cargo.lock | 1 + git-pathspec/Cargo.toml | 1 + git-pathspec/src/lib.rs | 4 ++-- git-pathspec/src/parse.rs | 43 +++++++++++++++++++++++----------- git-pathspec/tests/pathspec.rs | 31 +++++++++++++++++------- 5 files changed, 56 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 18c0a56753d..9d36c74d2a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1407,6 +1407,7 @@ dependencies = [ "bitflags", "bstr", "git-glob", + "quick-error", ] [[package]] diff --git a/git-pathspec/Cargo.toml b/git-pathspec/Cargo.toml index 16b92fd4f7a..563a8603f0b 100644 --- a/git-pathspec/Cargo.toml +++ b/git-pathspec/Cargo.toml @@ -16,3 +16,4 @@ doctest = false bstr = { version = "0.2.13", default-features = false, features = ["std"]} bitflags = "1.3.2" git-glob = { version = "^0.2.0", path = "../git-glob" } +quick-error = "2.0.0" diff --git a/git-pathspec/src/lib.rs b/git-pathspec/src/lib.rs index 4e2b5c73a5d..bf69b7d6c84 100644 --- a/git-pathspec/src/lib.rs +++ b/git-pathspec/src/lib.rs @@ -3,7 +3,7 @@ use bitflags::bitflags; use bstr::BString; -mod parse; +pub mod parse; #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] pub struct Pattern { @@ -22,6 +22,6 @@ bitflags! { } } -pub fn parse(input: &[u8]) -> Pattern { +pub fn parse(input: &[u8]) -> Result { Pattern::from_bytes(input) } diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index 8d76287b477..f4be35824ca 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -1,9 +1,22 @@ use crate::{MagicSignature, Pattern}; use bstr::BString; +use quick_error::quick_error; use std::iter::{FromIterator, Peekable}; +quick_error! { + #[derive(Debug, Eq, PartialEq)] + pub enum Error { + InvalidSignature { found_signature: BString } { + display("Found {}, which is not a valid signature", found_signature) + } + WhitespaceInSignature { + display("Whitespace in magic keywords are not allowed") + } + } +} + impl Pattern { - pub fn from_bytes(input: &[u8]) -> Self { + pub fn from_bytes(input: &[u8]) -> Result { Parser::new(input).parse() } } @@ -21,14 +34,14 @@ impl<'a> Parser<'a> { } } - fn parse(mut self) -> Pattern { - let signature = self.parse_magic_signature(); + fn parse(mut self) -> Result { + let signature = self.parse_magic_signature()?; let path = self.parse_path(); - Pattern { path, signature } + Ok(Pattern { path, signature }) } - fn parse_magic_signature(&mut self) -> Option { + fn parse_magic_signature(&mut self) -> Result, Error> { match self.input.peek() { Some(b':') => { self.input.next(); @@ -52,19 +65,19 @@ impl<'a> Parser<'a> { } b'(' => { self.input.next(); - signature |= self.parse_magic_keywords(); + signature |= self.parse_magic_keywords()?; } _ => break, } } - (!signature.is_empty()).then(|| signature) + (!signature.is_empty()).then(|| signature).map(Result::Ok).transpose() } - _ => None, + _ => Ok(None), } } - fn parse_magic_keywords(&mut self) -> MagicSignature { + fn parse_magic_keywords(&mut self) -> Result { let mut buf = Vec::new(); let mut keywords = Vec::new(); @@ -82,8 +95,7 @@ impl<'a> Parser<'a> { } } b' ' => { - // TODO: make this non panicking - panic!("space in magic keywords not allowed"); + return Err(Error::WhitespaceInSignature); } _ => { buf.push(*b); @@ -101,12 +113,15 @@ impl<'a> Parser<'a> { b"glob" => MagicSignature::GLOB, b"attr" => MagicSignature::ATTR, b"exclude" => MagicSignature::EXCLUDE, - // TODO: make this an error - _ => panic!("Invalid signature"), + s => { + return Err(Error::InvalidSignature { + found_signature: BString::from(s), + }) + } } } - signature + Ok(signature) } fn parse_path(self) -> BString { diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index a384788fbf6..4f9b6ff65b5 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -1,3 +1,4 @@ +use bstr::BString; use git_pathspec::{MagicSignature, Pattern}; #[test] @@ -40,21 +41,35 @@ fn can_parse() { ]; for (input, expected) in inputs { - let pattern = git_pathspec::parse(input.as_bytes()); + let pattern = git_pathspec::parse(input.as_bytes()).expect("parsing should not fail"); assert_eq!(pattern, expected, "while checking input: \"{}\"", input); } } +#[test] +fn should_fail_on_whitespace_or_invalid_keywords() { + use git_pathspec::parse::Error; + let inputs = vec![ + (":(top, exclude)some/path", Error::WhitespaceInSignature), + (":( )some/path", Error::WhitespaceInSignature), + ( + ":(tp)some/path", + Error::InvalidSignature { + found_signature: BString::from("tp"), + }, + ), + ]; + + for (input, expected) in inputs { + let output = git_pathspec::parse(input.as_bytes()); + assert!(output.is_err()); + assert_eq!(output.unwrap_err(), expected); + } +} + fn pat(path: &str, signature: Option) -> Pattern { Pattern { path: path.into(), signature, } } - -#[test] -#[ignore] -fn can_match() { - // let buf = b"git-pathspec/tests/pathspec.rs"; - // git_pathspec::matches(buf); -} From 4d20cd91be403d48fd5443202048f4a5bb867a62 Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Thu, 19 May 2022 10:28:46 +0200 Subject: [PATCH 008/248] remove WhitespaceError (#415) --- git-pathspec/src/parse.rs | 12 ++++++------ git-pathspec/tests/pathspec.rs | 14 ++++++++++++-- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index f4be35824ca..0a284bfcea2 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -9,9 +9,9 @@ quick_error! { InvalidSignature { found_signature: BString } { display("Found {}, which is not a valid signature", found_signature) } - WhitespaceInSignature { - display("Whitespace in magic keywords are not allowed") - } + // WhitespaceInSignature { + // display("Whitespace in magic keywords are not allowed") + // } } } @@ -94,9 +94,9 @@ impl<'a> Parser<'a> { keywords.push(std::mem::take(&mut buf)); } } - b' ' => { - return Err(Error::WhitespaceInSignature); - } + // b' ' => { + // return Err(Error::WhitespaceInSignature); + // } _ => { buf.push(*b); } diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index 4f9b6ff65b5..c414a062a7b 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -50,8 +50,18 @@ fn can_parse() { fn should_fail_on_whitespace_or_invalid_keywords() { use git_pathspec::parse::Error; let inputs = vec![ - (":(top, exclude)some/path", Error::WhitespaceInSignature), - (":( )some/path", Error::WhitespaceInSignature), + ( + ":(top, exclude)some/path", + Error::InvalidSignature { + found_signature: BString::from(" exclude"), + }, + ), + ( + ":( )some/path", + Error::InvalidSignature { + found_signature: BString::from(" "), + }, + ), ( ":(tp)some/path", Error::InvalidSignature { From 334659e34b1f99f3bc662fa599dcd8f0d94ad206 Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Thu, 19 May 2022 10:52:26 +0200 Subject: [PATCH 009/248] tests now check if pathspec is valid in git (#415) --- git-pathspec/tests/pathspec.rs | 46 +++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index c414a062a7b..e731abf4f59 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -15,20 +15,23 @@ fn can_parse() { (":(glob)", pat("", Some(MagicSignature::GLOB))), (":(attr)", pat("", Some(MagicSignature::ATTR))), (":(exclude)", pat("", Some(MagicSignature::EXCLUDE))), - ( - ":(top,literal,icase,glob,attr,exclude)some/path", - pat( - "some/path", - Some( - MagicSignature::TOP - | MagicSignature::LITERAL - | MagicSignature::ICASE - | MagicSignature::GLOB - | MagicSignature::ATTR - | MagicSignature::EXCLUDE, - ), - ), - ), + // TODO: + // 'literal' and 'glob' cannot appear in the same pathspec together + // is this the parsers job to handle? + // ( + // ":(top,literal,icase,glob,attr,exclude)some/path", + // pat( + // "some/path", + // Some( + // MagicSignature::TOP + // | MagicSignature::LITERAL + // | MagicSignature::ICASE + // | MagicSignature::GLOB + // | MagicSignature::ATTR + // | MagicSignature::EXCLUDE, + // ), + // ), + // ), (":/:some/path", pat("some/path", Some(MagicSignature::TOP))), ( ":!(literal)some/*path", @@ -41,6 +44,8 @@ fn can_parse() { ]; for (input, expected) in inputs { + assert!(is_valid_in_git(input), "This pathspec is invalid in git: {}", input); + let pattern = git_pathspec::parse(input.as_bytes()).expect("parsing should not fail"); assert_eq!(pattern, expected, "while checking input: \"{}\"", input); } @@ -71,6 +76,8 @@ fn should_fail_on_whitespace_or_invalid_keywords() { ]; for (input, expected) in inputs { + assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); + let output = git_pathspec::parse(input.as_bytes()); assert!(output.is_err()); assert_eq!(output.unwrap_err(), expected); @@ -83,3 +90,14 @@ fn pat(path: &str, signature: Option) -> Pattern { signature, } } + +fn is_valid_in_git(pathspec: &str) -> bool { + use std::process::Command; + + let output = Command::new("git") + .args(["ls-files", pathspec]) + .output() + .expect("failed to execute process"); + + output.status.success() +} From c04d4be29c7a0cf72500d30432215513f6066338 Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Fri, 20 May 2022 14:23:05 +0200 Subject: [PATCH 010/248] added some documentation (#415) --- git-pathspec/src/lib.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/git-pathspec/src/lib.rs b/git-pathspec/src/lib.rs index bf69b7d6c84..32385e6aadd 100644 --- a/git-pathspec/src/lib.rs +++ b/git-pathspec/src/lib.rs @@ -5,6 +5,7 @@ use bstr::BString; pub mod parse; +/// The output of a pathspec parsing operaion. It can be used to matched against a path. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] pub struct Pattern { pub path: BString, @@ -13,15 +14,22 @@ pub struct Pattern { bitflags! { pub struct MagicSignature: u32 { + /// Matches patterns from the root of the repository const TOP = 1 << 0; + /// Special characters in the pattern, like '*' or '?', are treated literally const LITERAL = 1 << 1; + /// Matches patterns in case insensitive mode const ICASE = 1 << 2; + /// A single '*' will not match a '/' in the pattern, but a '**' will const GLOB = 1 << 3; + /// Specifies a list of attribute requirements that the matches should meet const ATTR = 1 << 4; + /// Excludes the matching patterns from the previous results const EXCLUDE = 1 << 5; } } +/// Parse a git-style pathspec into a `Pattern` pub fn parse(input: &[u8]) -> Result { Pattern::from_bytes(input) } From 7c84fb81506cc41fbf68575583129eafbd89139d Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Fri, 20 May 2022 15:57:46 +0200 Subject: [PATCH 011/248] attribute parsing WIP (#415) --- Cargo.lock | 1 + git-pathspec/Cargo.toml | 1 + git-pathspec/src/lib.rs | 1 + git-pathspec/src/parse.rs | 24 +++++++++++++++--------- git-pathspec/tests/pathspec.rs | 19 +++++++++++++++++-- 5 files changed, 35 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9d36c74d2a3..32171352371 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1406,6 +1406,7 @@ version = "0.0.0" dependencies = [ "bitflags", "bstr", + "git-attributes", "git-glob", "quick-error", ] diff --git a/git-pathspec/Cargo.toml b/git-pathspec/Cargo.toml index 563a8603f0b..ed3447d48ee 100644 --- a/git-pathspec/Cargo.toml +++ b/git-pathspec/Cargo.toml @@ -16,4 +16,5 @@ doctest = false bstr = { version = "0.2.13", default-features = false, features = ["std"]} bitflags = "1.3.2" git-glob = { version = "^0.2.0", path = "../git-glob" } +git-attributes = { version = "^0.1.0", path = "../git-attributes" } quick-error = "2.0.0" diff --git a/git-pathspec/src/lib.rs b/git-pathspec/src/lib.rs index 32385e6aadd..1981455d9f6 100644 --- a/git-pathspec/src/lib.rs +++ b/git-pathspec/src/lib.rs @@ -10,6 +10,7 @@ pub mod parse; pub struct Pattern { pub path: BString, pub signature: Option, + // pub attributes: Vec<(BString, git_attributes::State)>, } bitflags! { diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index 0a284bfcea2..805f9a47b61 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -4,14 +4,17 @@ use quick_error::quick_error; use std::iter::{FromIterator, Peekable}; quick_error! { - #[derive(Debug, Eq, PartialEq)] + #[derive(Debug)] pub enum Error { InvalidSignature { found_signature: BString } { - display("Found {}, which is not a valid signature", found_signature) + display("Found \"{}\", which is not a valid signature", found_signature) + } + // TODO: Fix error messages + InvalidAttribute(err: git_attributes::parse::Error) { + display("{}", err) + from() + source(err) } - // WhitespaceInSignature { - // display("Whitespace in magic keywords are not allowed") - // } } } @@ -94,9 +97,6 @@ impl<'a> Parser<'a> { keywords.push(std::mem::take(&mut buf)); } } - // b' ' => { - // return Err(Error::WhitespaceInSignature); - // } _ => { buf.push(*b); } @@ -113,10 +113,16 @@ impl<'a> Parser<'a> { b"glob" => MagicSignature::GLOB, b"attr" => MagicSignature::ATTR, b"exclude" => MagicSignature::EXCLUDE, + s if s.starts_with(b"attr:") => git_attributes::parse::Iter::new(s[5..].into(), 0) + .collect::, _>>() + .map(|v| { + println!("{:?}", v); + MagicSignature::ATTR + })?, s => { return Err(Error::InvalidSignature { found_signature: BString::from(s), - }) + }); } } } diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index e731abf4f59..a624d24dde5 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -14,6 +14,11 @@ fn can_parse() { (":(icase)", pat("", Some(MagicSignature::ICASE))), (":(glob)", pat("", Some(MagicSignature::GLOB))), (":(attr)", pat("", Some(MagicSignature::ATTR))), + (":(attr:someAttr)", pat("", Some(MagicSignature::ATTR))), + (":(attr:!someAttr)", pat("", Some(MagicSignature::ATTR))), + (":(attr:-someAttr)", pat("", Some(MagicSignature::ATTR))), + (":(attr:someAttr=value)", pat("", Some(MagicSignature::ATTR))), + (":(attr:someAttr anotherAttr)", pat("", Some(MagicSignature::ATTR))), (":(exclude)", pat("", Some(MagicSignature::EXCLUDE))), // TODO: // 'literal' and 'glob' cannot appear in the same pathspec together @@ -73,14 +78,23 @@ fn should_fail_on_whitespace_or_invalid_keywords() { found_signature: BString::from("tp"), }, ), + ( + ":(attr:+someAttr)some/path", + Error::InvalidAttribute(git_attributes::parse::Error::AttributeName { + line_number: 0, + attribute: BString::from("+someAttr"), + }), + ), ]; - for (input, expected) in inputs { + for (input, _expected) in inputs { assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); let output = git_pathspec::parse(input.as_bytes()); assert!(output.is_err()); - assert_eq!(output.unwrap_err(), expected); + + // TODO: Find a way to do this without `Eq` trait + // assert_eq!(output.unwrap_err()., expected); } } @@ -91,6 +105,7 @@ fn pat(path: &str, signature: Option) -> Pattern { } } +// TODO: Cache results instead of running them with each test run fn is_valid_in_git(pathspec: &str) -> bool { use std::process::Command; From 8b9570fdcdb426cbde7edb9fcca4b7340944550e Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Wed, 25 May 2022 08:48:33 +0200 Subject: [PATCH 012/248] updated documentation (#415) --- git-pathspec/src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/git-pathspec/src/lib.rs b/git-pathspec/src/lib.rs index 1981455d9f6..eb154f5a66f 100644 --- a/git-pathspec/src/lib.rs +++ b/git-pathspec/src/lib.rs @@ -5,11 +5,14 @@ use bstr::BString; pub mod parse; -/// The output of a pathspec parsing operaion. It can be used to matched against a path. +/// The output of a pathspec parsing operaion. It can be used to matche against a path / multiple paths. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] pub struct Pattern { + /// The path part of a pathspec. pub path: BString, + /// All magig sigantures that were included in the pathspec. pub signature: Option, + // All attributes that were included in the `ATTR` part of the pathspec, if present. // pub attributes: Vec<(BString, git_attributes::State)>, } From 4b2ed7e0f033fd01a7364fc41057e113adf2fbdc Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Wed, 25 May 2022 09:22:32 +0200 Subject: [PATCH 013/248] attribute parsing functional (#415) --- git-pathspec/src/lib.rs | 4 +- git-pathspec/src/parse.rs | 33 ++++++++++---- git-pathspec/tests/pathspec.rs | 82 ++++++++++++++++++++++++---------- 3 files changed, 85 insertions(+), 34 deletions(-) diff --git a/git-pathspec/src/lib.rs b/git-pathspec/src/lib.rs index eb154f5a66f..3cff9d1a55a 100644 --- a/git-pathspec/src/lib.rs +++ b/git-pathspec/src/lib.rs @@ -12,8 +12,8 @@ pub struct Pattern { pub path: BString, /// All magig sigantures that were included in the pathspec. pub signature: Option, - // All attributes that were included in the `ATTR` part of the pathspec, if present. - // pub attributes: Vec<(BString, git_attributes::State)>, + /// All attributes that were included in the `ATTR` part of the pathspec, if present. + pub attributes: Vec<(BString, git_attributes::State)>, } bitflags! { diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index 805f9a47b61..4966badf48e 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -38,17 +38,24 @@ impl<'a> Parser<'a> { } fn parse(mut self) -> Result { - let signature = self.parse_magic_signature()?; + let (signature, attributes) = self.parse_magic_signature()?; let path = self.parse_path(); - Ok(Pattern { path, signature }) + Ok(Pattern { + path, + signature, + attributes, + }) } - fn parse_magic_signature(&mut self) -> Result, Error> { + fn parse_magic_signature( + &mut self, + ) -> Result<(Option, Vec<(BString, git_attributes::State)>), Error> { match self.input.peek() { Some(b':') => { self.input.next(); let mut signature = MagicSignature::empty(); + let mut attributes = Vec::new(); while let Some(&b) = self.input.peek() { match b { @@ -68,21 +75,28 @@ impl<'a> Parser<'a> { } b'(' => { self.input.next(); - signature |= self.parse_magic_keywords()?; + let (signatures, attrs) = self.parse_magic_keywords()?; + signature |= signatures; + attributes = attrs; } _ => break, } } - (!signature.is_empty()).then(|| signature).map(Result::Ok).transpose() + (!signature.is_empty()) + .then(|| signature) + .map(Result::Ok) + .transpose() + .map(|x| (x, attributes)) } - _ => Ok(None), + _ => Ok((None, Vec::new())), } } - fn parse_magic_keywords(&mut self) -> Result { + fn parse_magic_keywords(&mut self) -> Result<(MagicSignature, Vec<(BString, git_attributes::State)>), Error> { let mut buf = Vec::new(); let mut keywords = Vec::new(); + let mut attributes = Vec::new(); while let Some(b) = self.input.next() { match b { @@ -114,9 +128,10 @@ impl<'a> Parser<'a> { b"attr" => MagicSignature::ATTR, b"exclude" => MagicSignature::EXCLUDE, s if s.starts_with(b"attr:") => git_attributes::parse::Iter::new(s[5..].into(), 0) + .map(|res| res.map(|(attr, state)| (BString::from(attr), git_attributes::State::from(state)))) .collect::, _>>() .map(|v| { - println!("{:?}", v); + attributes = v; MagicSignature::ATTR })?, s => { @@ -127,7 +142,7 @@ impl<'a> Parser<'a> { } } - Ok(signature) + Ok((signature, attributes)) } fn parse_path(self) -> BString { diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index a624d24dde5..9b346f4b6c6 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -1,25 +1,49 @@ use bstr::BString; +use git_attributes::State; use git_pathspec::{MagicSignature, Pattern}; #[test] fn can_parse() { let inputs = vec![ - ("some/path", pat("some/path", None)), - ("some/*.path", pat("some/*.path", None)), - (":/", pat("", Some(MagicSignature::TOP))), - (":^", pat("", Some(MagicSignature::EXCLUDE))), - (":!", pat("", Some(MagicSignature::EXCLUDE))), - (":(top)", pat("", Some(MagicSignature::TOP))), - (":(literal)", pat("", Some(MagicSignature::LITERAL))), - (":(icase)", pat("", Some(MagicSignature::ICASE))), - (":(glob)", pat("", Some(MagicSignature::GLOB))), - (":(attr)", pat("", Some(MagicSignature::ATTR))), - (":(attr:someAttr)", pat("", Some(MagicSignature::ATTR))), - (":(attr:!someAttr)", pat("", Some(MagicSignature::ATTR))), - (":(attr:-someAttr)", pat("", Some(MagicSignature::ATTR))), - (":(attr:someAttr=value)", pat("", Some(MagicSignature::ATTR))), - (":(attr:someAttr anotherAttr)", pat("", Some(MagicSignature::ATTR))), - (":(exclude)", pat("", Some(MagicSignature::EXCLUDE))), + ("some/path", pat("some/path", None, vec![])), + ("some/*.path", pat("some/*.path", None, vec![])), + (":/", pat("", Some(MagicSignature::TOP), vec![])), + (":^", pat("", Some(MagicSignature::EXCLUDE), vec![])), + (":!", pat("", Some(MagicSignature::EXCLUDE), vec![])), + (":(top)", pat("", Some(MagicSignature::TOP), vec![])), + (":(literal)", pat("", Some(MagicSignature::LITERAL), vec![])), + (":(icase)", pat("", Some(MagicSignature::ICASE), vec![])), + (":(glob)", pat("", Some(MagicSignature::GLOB), vec![])), + (":(attr)", pat("", Some(MagicSignature::ATTR), vec![])), + ( + ":(attr:someAttr)", + pat("", Some(MagicSignature::ATTR), vec![("someAttr", State::Set)]), + ), + ( + ":(attr:!someAttr)", + pat("", Some(MagicSignature::ATTR), vec![("someAttr", State::Unspecified)]), + ), + ( + ":(attr:-someAttr)", + pat("", Some(MagicSignature::ATTR), vec![("someAttr", State::Unset)]), + ), + ( + ":(attr:someAttr=value)", + pat( + "", + Some(MagicSignature::ATTR), + vec![("someAttr", State::Value("value".into()))], + ), + ), + ( + ":(attr:someAttr anotherAttr)", + pat( + "", + Some(MagicSignature::ATTR), + vec![("someAttr", State::Set), ("anotherAttr", State::Set)], + ), + ), + (":(exclude)", pat("", Some(MagicSignature::EXCLUDE), vec![])), // TODO: // 'literal' and 'glob' cannot appear in the same pathspec together // is this the parsers job to handle? @@ -35,17 +59,25 @@ fn can_parse() { // | MagicSignature::ATTR // | MagicSignature::EXCLUDE, // ), + // vec![] // ), // ), - (":/:some/path", pat("some/path", Some(MagicSignature::TOP))), + (":/:some/path", pat("some/path", Some(MagicSignature::TOP), vec![])), ( ":!(literal)some/*path", - pat("some/*path", Some(MagicSignature::EXCLUDE | MagicSignature::LITERAL)), + pat( + "some/*path", + Some(MagicSignature::EXCLUDE | MagicSignature::LITERAL), + vec![], + ), + ), + (":", pat("", None, vec![])), + (":()", pat("", None, vec![])), + (":::::", pat("", None, vec![])), + ( + ":!/!/:", + pat("", Some(MagicSignature::TOP | MagicSignature::EXCLUDE), vec![]), ), - (":", pat("", None)), - (":()", pat("", None)), - (":::::", pat("", None)), - (":!/!/:", pat("", Some(MagicSignature::TOP | MagicSignature::EXCLUDE))), ]; for (input, expected) in inputs { @@ -98,10 +130,14 @@ fn should_fail_on_whitespace_or_invalid_keywords() { } } -fn pat(path: &str, signature: Option) -> Pattern { +fn pat(path: &str, signature: Option, attributes: Vec<(&str, State)>) -> Pattern { Pattern { path: path.into(), signature, + attributes: attributes + .into_iter() + .map(|(attr, state)| (attr.into(), state)) + .collect(), } } From eb2dec0777bfe894f4d02bdc03a3e507302b41b4 Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Wed, 25 May 2022 11:30:03 +0200 Subject: [PATCH 014/248] added alternative parsing module (#415) --- git-pathspec/src/lib.rs | 3 +- git-pathspec/src/parse.rs | 3 + git-pathspec/src/parse_functional.rs | 94 ++++++++++++++++++++++++++++ git-pathspec/tests/pathspec.rs | 6 ++ 4 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 git-pathspec/src/parse_functional.rs diff --git a/git-pathspec/src/lib.rs b/git-pathspec/src/lib.rs index 3cff9d1a55a..f6bcd98bbe9 100644 --- a/git-pathspec/src/lib.rs +++ b/git-pathspec/src/lib.rs @@ -4,6 +4,7 @@ use bitflags::bitflags; use bstr::BString; pub mod parse; +pub mod parse_functional; /// The output of a pathspec parsing operaion. It can be used to matche against a path / multiple paths. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] @@ -35,5 +36,5 @@ bitflags! { /// Parse a git-style pathspec into a `Pattern` pub fn parse(input: &[u8]) -> Result { - Pattern::from_bytes(input) + Pattern::from_bytes_functional(input) } diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index 4966badf48e..90dceb50877 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -9,6 +9,9 @@ quick_error! { InvalidSignature { found_signature: BString } { display("Found \"{}\", which is not a valid signature", found_signature) } + MissingClosingParenthesis { pathspec: BString } { + display("Missing ')' at the end of pathspec magic in '{}'", pathspec) + } // TODO: Fix error messages InvalidAttribute(err: git_attributes::parse::Error) { display("{}", err) diff --git a/git-pathspec/src/parse_functional.rs b/git-pathspec/src/parse_functional.rs new file mode 100644 index 00000000000..1e90e8c7f71 --- /dev/null +++ b/git-pathspec/src/parse_functional.rs @@ -0,0 +1,94 @@ +use crate::parse::Error; +use bstr::{BString, ByteSlice}; +use git_attributes::{parse::Iter, State}; + +use crate::{MagicSignature, Pattern}; + +impl Pattern { + pub fn empty() -> Self { + Pattern { + path: BString::default(), + signature: None, + attributes: Vec::new(), + } + } + + pub fn from_bytes_functional(input: &[u8]) -> Result { + if input.is_empty() { + return Ok(Pattern::empty()); + } + + let mut cursor = 0; + let mut signature = MagicSignature::empty(); + let mut attributes = Vec::new(); + + if input.first() == Some(&b':') { + while let Some(&b) = input.get(cursor) { + cursor += 1; + match b { + b':' => { + if !signature.is_empty() { + break; + } + } + b'/' => signature |= MagicSignature::TOP, + b'^' | b'!' => signature |= MagicSignature::EXCLUDE, + b'(' => { + let end = input.find(")").ok_or(Error::MissingClosingParenthesis { + pathspec: BString::from(input), + })?; + let (signatures, attrs) = parse_keywords(&input[cursor..end])?; + + cursor = end + 1; + signature |= signatures; + attributes = attrs; + } + _ => { + cursor -= 1; + break; + } + } + } + } + + Ok(Pattern { + path: BString::from(&input[cursor..]), + signature: (!signature.is_empty()).then(|| signature), + attributes, + }) + } +} + +fn parse_keywords(input: &[u8]) -> Result<(MagicSignature, Vec<(BString, State)>), Error> { + let mut signature = MagicSignature::empty(); + let mut attributes = Vec::new(); + + if input.is_empty() { + return Ok((signature, attributes)); + } + + for keyword in input.split(|&c| c == b',') { + signature |= match keyword { + b"top" => MagicSignature::TOP, + b"literal" => MagicSignature::LITERAL, + b"icase" => MagicSignature::ICASE, + b"glob" => MagicSignature::GLOB, + b"attr" => MagicSignature::ATTR, + b"exclude" => MagicSignature::EXCLUDE, + s if s.starts_with(b"attr:") => Iter::new(s[5..].into(), 0) + .map(|res| res.map(|(attr, state)| (BString::from(attr), State::from(state)))) + .collect::, _>>() + .map(|v| { + attributes = v; + MagicSignature::ATTR + })?, + s => { + return Err(Error::InvalidSignature { + found_signature: BString::from(s), + }); + } + } + } + + Ok((signature, attributes)) +} diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index 9b346f4b6c6..9acdb50d3ad 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -117,6 +117,12 @@ fn should_fail_on_whitespace_or_invalid_keywords() { attribute: BString::from("+someAttr"), }), ), + ( + ":(top", + Error::MissingClosingParenthesis { + pathspec: BString::from(":(top"), + }, + ), ]; for (input, _expected) in inputs { From 2690b8a73c39175ccfddd098c1f72f2cdee048cf Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 25 May 2022 21:08:16 +0800 Subject: [PATCH 015/248] refactor - consolidate parsers by removing the old one - minor renames maybe --- git-pathspec/Cargo.toml | 5 +- git-pathspec/src/lib.rs | 5 +- git-pathspec/src/parse.rs | 186 +++++++++++---------------- git-pathspec/src/parse_functional.rs | 94 -------------- 4 files changed, 77 insertions(+), 213 deletions(-) delete mode 100644 git-pathspec/src/parse_functional.rs diff --git a/git-pathspec/Cargo.toml b/git-pathspec/Cargo.toml index 9268bc52993..e734ab1ee17 100644 --- a/git-pathspec/Cargo.toml +++ b/git-pathspec/Cargo.toml @@ -13,8 +13,9 @@ doctest = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bstr = { version = "0.2.13", default-features = false, features = ["std"]} -bitflags = "1.3.2" git-glob = { version = "^0.3.0", path = "../git-glob" } git-attributes = { version = "^0.1.0", path = "../git-attributes" } + +bstr = { version = "0.2.13", default-features = false, features = ["std"]} +bitflags = "1.3.2" quick-error = "2.0.0" diff --git a/git-pathspec/src/lib.rs b/git-pathspec/src/lib.rs index f6bcd98bbe9..89afdabded3 100644 --- a/git-pathspec/src/lib.rs +++ b/git-pathspec/src/lib.rs @@ -4,14 +4,13 @@ use bitflags::bitflags; use bstr::BString; pub mod parse; -pub mod parse_functional; /// The output of a pathspec parsing operaion. It can be used to matche against a path / multiple paths. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] pub struct Pattern { /// The path part of a pathspec. pub path: BString, - /// All magig sigantures that were included in the pathspec. + /// All magic signatures that were included in the pathspec. pub signature: Option, /// All attributes that were included in the `ATTR` part of the pathspec, if present. pub attributes: Vec<(BString, git_attributes::State)>, @@ -36,5 +35,5 @@ bitflags! { /// Parse a git-style pathspec into a `Pattern` pub fn parse(input: &[u8]) -> Result { - Pattern::from_bytes_functional(input) + Pattern::from_bytes(input) } diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index 90dceb50877..9bd850103ad 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -1,7 +1,8 @@ -use crate::{MagicSignature, Pattern}; -use bstr::BString; +use bstr::{BString, ByteSlice}; +use git_attributes::{parse::Iter, State}; use quick_error::quick_error; -use std::iter::{FromIterator, Peekable}; + +use crate::{MagicSignature, Pattern}; quick_error! { #[derive(Debug)] @@ -22,133 +23,90 @@ quick_error! { } impl Pattern { - pub fn from_bytes(input: &[u8]) -> Result { - Parser::new(input).parse() - } -} - -type ByteIter<'a> = Peekable>; - -struct Parser<'a> { - input: ByteIter<'a>, -} - -impl<'a> Parser<'a> { - fn new(input: &'a [u8]) -> Self { - Self { - input: input.iter().peekable(), + pub fn empty() -> Self { + Pattern { + path: BString::default(), + signature: None, + attributes: Vec::new(), } } - fn parse(mut self) -> Result { - let (signature, attributes) = self.parse_magic_signature()?; - let path = self.parse_path(); - - Ok(Pattern { - path, - signature, - attributes, - }) - } - - fn parse_magic_signature( - &mut self, - ) -> Result<(Option, Vec<(BString, git_attributes::State)>), Error> { - match self.input.peek() { - Some(b':') => { - self.input.next(); - let mut signature = MagicSignature::empty(); - let mut attributes = Vec::new(); - - while let Some(&b) = self.input.peek() { - match b { - b':' => { - self.input.next(); - if !signature.is_empty() { - break; - } - } - b'/' => { - self.input.next(); - signature |= MagicSignature::TOP - } - b'^' | b'!' => { - self.input.next(); - signature |= MagicSignature::EXCLUDE; - } - b'(' => { - self.input.next(); - let (signatures, attrs) = self.parse_magic_keywords()?; - signature |= signatures; - attributes = attrs; - } - _ => break, - } - } - - (!signature.is_empty()) - .then(|| signature) - .map(Result::Ok) - .transpose() - .map(|x| (x, attributes)) - } - _ => Ok((None, Vec::new())), + pub fn from_bytes(input: &[u8]) -> Result { + if input.is_empty() { + return Ok(Pattern::empty()); } - } - fn parse_magic_keywords(&mut self) -> Result<(MagicSignature, Vec<(BString, git_attributes::State)>), Error> { - let mut buf = Vec::new(); - let mut keywords = Vec::new(); + let mut cursor = 0; + let mut signature = MagicSignature::empty(); let mut attributes = Vec::new(); - while let Some(b) = self.input.next() { - match b { - b')' => { - if !buf.is_empty() { - keywords.push(buf); + if input.first() == Some(&b':') { + while let Some(&b) = input.get(cursor) { + cursor += 1; + match b { + b':' => { + if !signature.is_empty() { + break; + } } - break; - } - b',' => { - if !buf.is_empty() { - keywords.push(std::mem::take(&mut buf)); + b'/' => signature |= MagicSignature::TOP, + b'^' | b'!' => signature |= MagicSignature::EXCLUDE, + b'(' => { + let end = input.find(")").ok_or(Error::MissingClosingParenthesis { + pathspec: BString::from(input), + })?; + let (signatures, attrs) = parse_keywords(&input[cursor..end])?; + + cursor = end + 1; + signature |= signatures; + attributes = attrs; + } + _ => { + cursor -= 1; + break; } - } - _ => { - buf.push(*b); } } } - let mut signature = MagicSignature::empty(); + Ok(Pattern { + path: BString::from(&input[cursor..]), + signature: (!signature.is_empty()).then(|| signature), + attributes, + }) + } +} - for keyword in keywords.into_iter() { - signature |= match &keyword[..] { - b"top" => MagicSignature::TOP, - b"literal" => MagicSignature::LITERAL, - b"icase" => MagicSignature::ICASE, - b"glob" => MagicSignature::GLOB, - b"attr" => MagicSignature::ATTR, - b"exclude" => MagicSignature::EXCLUDE, - s if s.starts_with(b"attr:") => git_attributes::parse::Iter::new(s[5..].into(), 0) - .map(|res| res.map(|(attr, state)| (BString::from(attr), git_attributes::State::from(state)))) - .collect::, _>>() - .map(|v| { - attributes = v; - MagicSignature::ATTR - })?, - s => { - return Err(Error::InvalidSignature { - found_signature: BString::from(s), - }); - } - } - } +fn parse_keywords(input: &[u8]) -> Result<(MagicSignature, Vec<(BString, State)>), Error> { + let mut signature = MagicSignature::empty(); + let mut attributes = Vec::new(); - Ok((signature, attributes)) + if input.is_empty() { + return Ok((signature, attributes)); } - fn parse_path(self) -> BString { - BString::from_iter(self.input.copied()) + for keyword in input.split(|&c| c == b',') { + signature |= match keyword { + b"top" => MagicSignature::TOP, + b"literal" => MagicSignature::LITERAL, + b"icase" => MagicSignature::ICASE, + b"glob" => MagicSignature::GLOB, + b"attr" => MagicSignature::ATTR, + b"exclude" => MagicSignature::EXCLUDE, + s if s.starts_with(b"attr:") => Iter::new(s[5..].into(), 0) + .map(|res| res.map(|(attr, state)| (BString::from(attr), State::from(state)))) + .collect::, _>>() + .map(|v| { + attributes = v; + MagicSignature::ATTR + })?, + s => { + return Err(Error::InvalidSignature { + found_signature: BString::from(s), + }); + } + } } + + Ok((signature, attributes)) } diff --git a/git-pathspec/src/parse_functional.rs b/git-pathspec/src/parse_functional.rs deleted file mode 100644 index 1e90e8c7f71..00000000000 --- a/git-pathspec/src/parse_functional.rs +++ /dev/null @@ -1,94 +0,0 @@ -use crate::parse::Error; -use bstr::{BString, ByteSlice}; -use git_attributes::{parse::Iter, State}; - -use crate::{MagicSignature, Pattern}; - -impl Pattern { - pub fn empty() -> Self { - Pattern { - path: BString::default(), - signature: None, - attributes: Vec::new(), - } - } - - pub fn from_bytes_functional(input: &[u8]) -> Result { - if input.is_empty() { - return Ok(Pattern::empty()); - } - - let mut cursor = 0; - let mut signature = MagicSignature::empty(); - let mut attributes = Vec::new(); - - if input.first() == Some(&b':') { - while let Some(&b) = input.get(cursor) { - cursor += 1; - match b { - b':' => { - if !signature.is_empty() { - break; - } - } - b'/' => signature |= MagicSignature::TOP, - b'^' | b'!' => signature |= MagicSignature::EXCLUDE, - b'(' => { - let end = input.find(")").ok_or(Error::MissingClosingParenthesis { - pathspec: BString::from(input), - })?; - let (signatures, attrs) = parse_keywords(&input[cursor..end])?; - - cursor = end + 1; - signature |= signatures; - attributes = attrs; - } - _ => { - cursor -= 1; - break; - } - } - } - } - - Ok(Pattern { - path: BString::from(&input[cursor..]), - signature: (!signature.is_empty()).then(|| signature), - attributes, - }) - } -} - -fn parse_keywords(input: &[u8]) -> Result<(MagicSignature, Vec<(BString, State)>), Error> { - let mut signature = MagicSignature::empty(); - let mut attributes = Vec::new(); - - if input.is_empty() { - return Ok((signature, attributes)); - } - - for keyword in input.split(|&c| c == b',') { - signature |= match keyword { - b"top" => MagicSignature::TOP, - b"literal" => MagicSignature::LITERAL, - b"icase" => MagicSignature::ICASE, - b"glob" => MagicSignature::GLOB, - b"attr" => MagicSignature::ATTR, - b"exclude" => MagicSignature::EXCLUDE, - s if s.starts_with(b"attr:") => Iter::new(s[5..].into(), 0) - .map(|res| res.map(|(attr, state)| (BString::from(attr), State::from(state)))) - .collect::, _>>() - .map(|v| { - attributes = v; - MagicSignature::ATTR - })?, - s => { - return Err(Error::InvalidSignature { - found_signature: BString::from(s), - }); - } - } - } - - Ok((signature, attributes)) -} From 13b7db526c0a56360998a731ba10a7b4990d9529 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 25 May 2022 21:15:43 +0800 Subject: [PATCH 016/248] refactor --- git-pathspec/src/parse.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index 9bd850103ad..04af8b0c6bc 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -93,17 +93,17 @@ fn parse_keywords(input: &[u8]) -> Result<(MagicSignature, Vec<(BString, State)> b"glob" => MagicSignature::GLOB, b"attr" => MagicSignature::ATTR, b"exclude" => MagicSignature::EXCLUDE, - s if s.starts_with(b"attr:") => Iter::new(s[5..].into(), 0) - .map(|res| res.map(|(attr, state)| (BString::from(attr), State::from(state)))) - .collect::, _>>() - .map(|v| { - attributes = v; - MagicSignature::ATTR - })?, s => { - return Err(Error::InvalidSignature { - found_signature: BString::from(s), - }); + if let Some(attrs) = s.strip_prefix(b"attr:") { + attributes = Iter::new(attrs.into(), 0) + .map(|res| res.map(|(attr, state)| (attr.into(), state.into()))) + .collect::, _>>()?; + MagicSignature::ATTR + } else { + return Err(Error::InvalidSignature { + found_signature: BString::from(s), + }); + } } } } From 49fcab749ea3260f3333976567fcc4ab8a072fe3 Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Sun, 29 May 2022 12:21:53 +0200 Subject: [PATCH 017/248] changed quickerror to thiserror (#415) --- Cargo.lock | 2 +- git-pathspec/Cargo.toml | 2 +- git-pathspec/src/parse.rs | 36 +++++++++++++++++----------------- git-pathspec/tests/pathspec.rs | 5 ++--- 4 files changed, 22 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index de5538a80ab..977c2382ceb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1421,7 +1421,7 @@ dependencies = [ "bstr", "git-attributes", "git-glob", - "quick-error", + "thiserror", ] [[package]] diff --git a/git-pathspec/Cargo.toml b/git-pathspec/Cargo.toml index e734ab1ee17..a9a949ca2bd 100644 --- a/git-pathspec/Cargo.toml +++ b/git-pathspec/Cargo.toml @@ -18,4 +18,4 @@ git-attributes = { version = "^0.1.0", path = "../git-attributes" } bstr = { version = "0.2.13", default-features = false, features = ["std"]} bitflags = "1.3.2" -quick-error = "2.0.0" +thiserror = "1.0.26" diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index 04af8b0c6bc..8beba93271c 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -1,25 +1,18 @@ use bstr::{BString, ByteSlice}; use git_attributes::{parse::Iter, State}; -use quick_error::quick_error; use crate::{MagicSignature, Pattern}; -quick_error! { - #[derive(Debug)] - pub enum Error { - InvalidSignature { found_signature: BString } { - display("Found \"{}\", which is not a valid signature", found_signature) - } - MissingClosingParenthesis { pathspec: BString } { - display("Missing ')' at the end of pathspec magic in '{}'", pathspec) - } - // TODO: Fix error messages - InvalidAttribute(err: git_attributes::parse::Error) { - display("{}", err) - from() - source(err) - } - } +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("Found \"{}\", which is not a valid signature", found_signature)] + InvalidSignature { found_signature: BString }, + + #[error("Missing ')' at the end of pathspec magic in '{}'", pathspec)] + MissingClosingParenthesis { pathspec: BString }, + + #[error("Attribute has non-ascii characters or starts with '-': {}", attribute)] + InvalidAttribute { attribute: BString }, } impl Pattern { @@ -97,7 +90,14 @@ fn parse_keywords(input: &[u8]) -> Result<(MagicSignature, Vec<(BString, State)> if let Some(attrs) = s.strip_prefix(b"attr:") { attributes = Iter::new(attrs.into(), 0) .map(|res| res.map(|(attr, state)| (attr.into(), state.into()))) - .collect::, _>>()?; + .collect::, _>>() + .map_err(|e| match e { + git_attributes::parse::Error::AttributeName { + line_number: _, + attribute, + } => Error::InvalidAttribute { attribute }, + _ => unreachable!("expecting only 'Error::AttributeName' but got {}", e), + })?; MagicSignature::ATTR } else { return Err(Error::InvalidSignature { diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index 9acdb50d3ad..f1463f8e67e 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -112,10 +112,9 @@ fn should_fail_on_whitespace_or_invalid_keywords() { ), ( ":(attr:+someAttr)some/path", - Error::InvalidAttribute(git_attributes::parse::Error::AttributeName { - line_number: 0, + Error::InvalidAttribute { attribute: BString::from("+someAttr"), - }), + }, ), ( ":(top", From 6a569a70d15c416f91ab20083747e25f867f7446 Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Tue, 31 May 2022 09:45:55 +0200 Subject: [PATCH 018/248] error tests now use matches! --- git-pathspec/src/parse.rs | 8 ++--- git-pathspec/tests/pathspec.rs | 66 ++++++++++++++++------------------ 2 files changed, 35 insertions(+), 39 deletions(-) diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index 8beba93271c..735f8e708f3 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -5,8 +5,8 @@ use crate::{MagicSignature, Pattern}; #[derive(thiserror::Error, Debug)] pub enum Error { - #[error("Found \"{}\", which is not a valid signature", found_signature)] - InvalidSignature { found_signature: BString }, + #[error("Found \"{}\", which is not a valid keyword", found_keyword)] + InvalidKeyword { found_keyword: BString }, #[error("Missing ')' at the end of pathspec magic in '{}'", pathspec)] MissingClosingParenthesis { pathspec: BString }, @@ -100,8 +100,8 @@ fn parse_keywords(input: &[u8]) -> Result<(MagicSignature, Vec<(BString, State)> })?; MagicSignature::ATTR } else { - return Err(Error::InvalidSignature { - found_signature: BString::from(s), + return Err(Error::InvalidKeyword { + found_keyword: BString::from(s), }); } } diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index f1463f8e67e..9aaa3535fc6 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -1,5 +1,6 @@ use bstr::BString; use git_attributes::State; +use git_pathspec::parse::Error; use git_pathspec::{MagicSignature, Pattern}; #[test] @@ -89,49 +90,44 @@ fn can_parse() { } #[test] -fn should_fail_on_whitespace_or_invalid_keywords() { - use git_pathspec::parse::Error; +fn should_fail_on_invalid_keywords() { + let inputs = vec![":( )some/path", ":(tp)some/path", ":(top, exclude)some/path"]; + + for input in inputs { + assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); + + let output = git_pathspec::parse(input.as_bytes()); + assert!(output.is_err()); + assert!(matches!(output.unwrap_err(), Error::InvalidKeyword { .. })); + } +} + +#[test] +fn should_fail_on_invalid_attributes() { let inputs = vec![ - ( - ":(top, exclude)some/path", - Error::InvalidSignature { - found_signature: BString::from(" exclude"), - }, - ), - ( - ":( )some/path", - Error::InvalidSignature { - found_signature: BString::from(" "), - }, - ), - ( - ":(tp)some/path", - Error::InvalidSignature { - found_signature: BString::from("tp"), - }, - ), - ( - ":(attr:+someAttr)some/path", - Error::InvalidAttribute { - attribute: BString::from("+someAttr"), - }, - ), - ( - ":(top", - Error::MissingClosingParenthesis { - pathspec: BString::from(":(top"), - }, - ), + ":(attr:+invalidAttr)some/path", + ":(attr:validAttr +invalidAttr)some/path", ]; - for (input, _expected) in inputs { + for input in inputs { assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); let output = git_pathspec::parse(input.as_bytes()); assert!(output.is_err()); + assert!(matches!(output.unwrap_err(), Error::InvalidAttribute { .. })); + } +} - // TODO: Find a way to do this without `Eq` trait - // assert_eq!(output.unwrap_err()., expected); +#[test] +fn should_fail_on_missing_parentheses() { + let inputs = vec![":(top"]; + + for input in inputs { + assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); + + let output = git_pathspec::parse(input.as_bytes()); + assert!(output.is_err()); + assert!(matches!(output.unwrap_err(), Error::MissingClosingParenthesis { .. })); } } From f1f4ab3e3f50d00db4756ea724e5fd1b8ee75a04 Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Tue, 31 May 2022 09:52:55 +0200 Subject: [PATCH 019/248] Pattern uses MagigSignature without Option --- git-pathspec/src/lib.rs | 2 +- git-pathspec/src/parse.rs | 4 +-- git-pathspec/tests/pathspec.rs | 54 +++++++++++++++------------------- 3 files changed, 26 insertions(+), 34 deletions(-) diff --git a/git-pathspec/src/lib.rs b/git-pathspec/src/lib.rs index 89afdabded3..16aeb428eea 100644 --- a/git-pathspec/src/lib.rs +++ b/git-pathspec/src/lib.rs @@ -11,7 +11,7 @@ pub struct Pattern { /// The path part of a pathspec. pub path: BString, /// All magic signatures that were included in the pathspec. - pub signature: Option, + pub signature: MagicSignature, /// All attributes that were included in the `ATTR` part of the pathspec, if present. pub attributes: Vec<(BString, git_attributes::State)>, } diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index 735f8e708f3..a93e913d757 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -19,7 +19,7 @@ impl Pattern { pub fn empty() -> Self { Pattern { path: BString::default(), - signature: None, + signature: MagicSignature::empty(), attributes: Vec::new(), } } @@ -64,7 +64,7 @@ impl Pattern { Ok(Pattern { path: BString::from(&input[cursor..]), - signature: (!signature.is_empty()).then(|| signature), + signature, attributes, }) } diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index 9aaa3535fc6..777a5458de3 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -1,4 +1,3 @@ -use bstr::BString; use git_attributes::State; use git_pathspec::parse::Error; use git_pathspec::{MagicSignature, Pattern}; @@ -6,33 +5,33 @@ use git_pathspec::{MagicSignature, Pattern}; #[test] fn can_parse() { let inputs = vec![ - ("some/path", pat("some/path", None, vec![])), - ("some/*.path", pat("some/*.path", None, vec![])), - (":/", pat("", Some(MagicSignature::TOP), vec![])), - (":^", pat("", Some(MagicSignature::EXCLUDE), vec![])), - (":!", pat("", Some(MagicSignature::EXCLUDE), vec![])), - (":(top)", pat("", Some(MagicSignature::TOP), vec![])), - (":(literal)", pat("", Some(MagicSignature::LITERAL), vec![])), - (":(icase)", pat("", Some(MagicSignature::ICASE), vec![])), - (":(glob)", pat("", Some(MagicSignature::GLOB), vec![])), - (":(attr)", pat("", Some(MagicSignature::ATTR), vec![])), + ("some/path", pat("some/path", MagicSignature::empty(), vec![])), + ("some/*.path", pat("some/*.path", MagicSignature::empty(), vec![])), + (":/", pat("", MagicSignature::TOP, vec![])), + (":^", pat("", MagicSignature::EXCLUDE, vec![])), + (":!", pat("", MagicSignature::EXCLUDE, vec![])), + (":(top)", pat("", MagicSignature::TOP, vec![])), + (":(literal)", pat("", MagicSignature::LITERAL, vec![])), + (":(icase)", pat("", MagicSignature::ICASE, vec![])), + (":(glob)", pat("", MagicSignature::GLOB, vec![])), + (":(attr)", pat("", MagicSignature::ATTR, vec![])), ( ":(attr:someAttr)", - pat("", Some(MagicSignature::ATTR), vec![("someAttr", State::Set)]), + pat("", MagicSignature::ATTR, vec![("someAttr", State::Set)]), ), ( ":(attr:!someAttr)", - pat("", Some(MagicSignature::ATTR), vec![("someAttr", State::Unspecified)]), + pat("", MagicSignature::ATTR, vec![("someAttr", State::Unspecified)]), ), ( ":(attr:-someAttr)", - pat("", Some(MagicSignature::ATTR), vec![("someAttr", State::Unset)]), + pat("", MagicSignature::ATTR, vec![("someAttr", State::Unset)]), ), ( ":(attr:someAttr=value)", pat( "", - Some(MagicSignature::ATTR), + MagicSignature::ATTR, vec![("someAttr", State::Value("value".into()))], ), ), @@ -40,11 +39,11 @@ fn can_parse() { ":(attr:someAttr anotherAttr)", pat( "", - Some(MagicSignature::ATTR), + MagicSignature::ATTR, vec![("someAttr", State::Set), ("anotherAttr", State::Set)], ), ), - (":(exclude)", pat("", Some(MagicSignature::EXCLUDE), vec![])), + (":(exclude)", pat("", MagicSignature::EXCLUDE, vec![])), // TODO: // 'literal' and 'glob' cannot appear in the same pathspec together // is this the parsers job to handle? @@ -63,22 +62,15 @@ fn can_parse() { // vec![] // ), // ), - (":/:some/path", pat("some/path", Some(MagicSignature::TOP), vec![])), + (":/:some/path", pat("some/path", MagicSignature::TOP, vec![])), ( ":!(literal)some/*path", - pat( - "some/*path", - Some(MagicSignature::EXCLUDE | MagicSignature::LITERAL), - vec![], - ), - ), - (":", pat("", None, vec![])), - (":()", pat("", None, vec![])), - (":::::", pat("", None, vec![])), - ( - ":!/!/:", - pat("", Some(MagicSignature::TOP | MagicSignature::EXCLUDE), vec![]), + pat("some/*path", MagicSignature::EXCLUDE | MagicSignature::LITERAL, vec![]), ), + (":", pat("", MagicSignature::empty(), vec![])), + (":()", pat("", MagicSignature::empty(), vec![])), + (":::::", pat("", MagicSignature::empty(), vec![])), + (":!/!/:", pat("", MagicSignature::TOP | MagicSignature::EXCLUDE, vec![])), ]; for (input, expected) in inputs { @@ -131,7 +123,7 @@ fn should_fail_on_missing_parentheses() { } } -fn pat(path: &str, signature: Option, attributes: Vec<(&str, State)>) -> Pattern { +fn pat(path: &str, signature: MagicSignature, attributes: Vec<(&str, State)>) -> Pattern { Pattern { path: path.into(), signature, From 57d8d90d246226fc9119612d10d31808f8fa3053 Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Thu, 2 Jun 2022 00:06:50 +0200 Subject: [PATCH 020/248] test refactor and bug fixes --- git-pathspec/src/parse.rs | 12 +-- git-pathspec/tests/pathspec.rs | 149 ++++++++++++++++++++++----------- 2 files changed, 107 insertions(+), 54 deletions(-) diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index a93e913d757..4ab180ef346 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -5,6 +5,9 @@ use crate::{MagicSignature, Pattern}; #[derive(thiserror::Error, Debug)] pub enum Error { + #[error("Empty string is not a valid pathspec")] + EmptyString, + #[error("Found \"{}\", which is not a valid keyword", found_keyword)] InvalidKeyword { found_keyword: BString }, @@ -26,7 +29,7 @@ impl Pattern { pub fn from_bytes(input: &[u8]) -> Result { if input.is_empty() { - return Ok(Pattern::empty()); + return Err(Error::EmptyString); } let mut cursor = 0; @@ -34,14 +37,11 @@ impl Pattern { let mut attributes = Vec::new(); if input.first() == Some(&b':') { + cursor += 1; while let Some(&b) = input.get(cursor) { cursor += 1; match b { - b':' => { - if !signature.is_empty() { - break; - } - } + b':' => break, b'/' => signature |= MagicSignature::TOP, b'^' | b'!' => signature |= MagicSignature::EXCLUDE, b'(' => { diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index 777a5458de3..46e28aa6a7b 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -3,18 +3,81 @@ use git_pathspec::parse::Error; use git_pathspec::{MagicSignature, Pattern}; #[test] -fn can_parse() { +fn can_parse_empty_signatures() { let inputs = vec![ ("some/path", pat("some/path", MagicSignature::empty(), vec![])), - ("some/*.path", pat("some/*.path", MagicSignature::empty(), vec![])), - (":/", pat("", MagicSignature::TOP, vec![])), - (":^", pat("", MagicSignature::EXCLUDE, vec![])), - (":!", pat("", MagicSignature::EXCLUDE, vec![])), + (":some/path", pat("some/path", MagicSignature::empty(), vec![])), + (":()some/path", pat("some/path", MagicSignature::empty(), vec![])), + ("::some/path", pat("some/path", MagicSignature::empty(), vec![])), + (":::some/path", pat(":some/path", MagicSignature::empty(), vec![])), + ]; + + check_valid_inputs(inputs) +} + +#[test] +fn can_parse_short_signatures() { + let inputs = vec![ + (":/some/path", pat("some/path", MagicSignature::TOP, vec![])), + (":^some/path", pat("some/path", MagicSignature::EXCLUDE, vec![])), + (":!some/path", pat("some/path", MagicSignature::EXCLUDE, vec![])), + ( + ":/!some/path", + pat("some/path", MagicSignature::TOP | MagicSignature::EXCLUDE, vec![]), + ), + (":!/^/:", pat("", MagicSignature::TOP | MagicSignature::EXCLUDE, vec![])), + ]; + + check_valid_inputs(inputs) +} + +#[test] +fn can_parse_signatures() { + let inputs = vec![ (":(top)", pat("", MagicSignature::TOP, vec![])), (":(literal)", pat("", MagicSignature::LITERAL, vec![])), (":(icase)", pat("", MagicSignature::ICASE, vec![])), (":(glob)", pat("", MagicSignature::GLOB, vec![])), (":(attr)", pat("", MagicSignature::ATTR, vec![])), + (":(exclude)", pat("", MagicSignature::EXCLUDE, vec![])), + ( + ":(top,exclude)", + pat("", MagicSignature::TOP | MagicSignature::EXCLUDE, vec![]), + ), + ( + ":(icase,literal)", + pat("", MagicSignature::ICASE | MagicSignature::LITERAL, vec![]), + ), + ( + ":!(literal)some/*path", + pat("some/*path", MagicSignature::EXCLUDE | MagicSignature::LITERAL, vec![]), + ), + // TODO: + // 'literal' and 'glob' cannot appear in the same pathspec together + // adjust Pattern struct to properly represent this case + // ( + // ":(top,literal,icase,glob,attr,exclude)some/path", + // pat( + // "some/path", + // Some( + // MagicSignature::TOP + // | MagicSignature::LITERAL + // | MagicSignature::ICASE + // | MagicSignature::GLOB + // | MagicSignature::ATTR + // | MagicSignature::EXCLUDE, + // ), + // vec![] + // ), + // ), + ]; + + check_valid_inputs(inputs); +} + +#[test] +fn can_parse_attributes_in_signature() { + let inputs = vec![ ( ":(attr:someAttr)", pat("", MagicSignature::ATTR, vec![("someAttr", State::Set)]), @@ -43,55 +106,38 @@ fn can_parse() { vec![("someAttr", State::Set), ("anotherAttr", State::Set)], ), ), - (":(exclude)", pat("", MagicSignature::EXCLUDE, vec![])), - // TODO: - // 'literal' and 'glob' cannot appear in the same pathspec together - // is this the parsers job to handle? - // ( - // ":(top,literal,icase,glob,attr,exclude)some/path", - // pat( - // "some/path", - // Some( - // MagicSignature::TOP - // | MagicSignature::LITERAL - // | MagicSignature::ICASE - // | MagicSignature::GLOB - // | MagicSignature::ATTR - // | MagicSignature::EXCLUDE, - // ), - // vec![] - // ), - // ), - (":/:some/path", pat("some/path", MagicSignature::TOP, vec![])), - ( - ":!(literal)some/*path", - pat("some/*path", MagicSignature::EXCLUDE | MagicSignature::LITERAL, vec![]), - ), - (":", pat("", MagicSignature::empty(), vec![])), - (":()", pat("", MagicSignature::empty(), vec![])), - (":::::", pat("", MagicSignature::empty(), vec![])), - (":!/!/:", pat("", MagicSignature::TOP | MagicSignature::EXCLUDE, vec![])), ]; - for (input, expected) in inputs { - assert!(is_valid_in_git(input), "This pathspec is invalid in git: {}", input); + check_valid_inputs(inputs) +} - let pattern = git_pathspec::parse(input.as_bytes()).expect("parsing should not fail"); - assert_eq!(pattern, expected, "while checking input: \"{}\"", input); - } +#[test] +fn should_fail_on_empty_input() { + let input = ""; + + assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); + + let output = git_pathspec::parse(input.as_bytes()); + assert!(output.is_err()); + assert!(matches!(output.unwrap_err(), Error::EmptyString { .. })); } #[test] fn should_fail_on_invalid_keywords() { - let inputs = vec![":( )some/path", ":(tp)some/path", ":(top, exclude)some/path"]; + let inputs = vec![ + ":( )some/path", + ":(tp)some/path", + ":(top, exclude)some/path", + ":(top,exclude,icse)some/path", + ]; - for input in inputs { + inputs.into_iter().for_each(|input| { assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); let output = git_pathspec::parse(input.as_bytes()); assert!(output.is_err()); assert!(matches!(output.unwrap_err(), Error::InvalidKeyword { .. })); - } + }); } #[test] @@ -112,15 +158,22 @@ fn should_fail_on_invalid_attributes() { #[test] fn should_fail_on_missing_parentheses() { - let inputs = vec![":(top"]; + let input = ":(top"; - for input in inputs { - assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); + assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); - let output = git_pathspec::parse(input.as_bytes()); - assert!(output.is_err()); - assert!(matches!(output.unwrap_err(), Error::MissingClosingParenthesis { .. })); - } + let output = git_pathspec::parse(input.as_bytes()); + assert!(output.is_err()); + assert!(matches!(output.unwrap_err(), Error::MissingClosingParenthesis { .. })); +} + +fn check_valid_inputs(inputs: Vec<(&str, Pattern)>) { + inputs.into_iter().for_each(|(input, expected)| { + assert!(is_valid_in_git(input), "This pathspec is invalid in git: {}", input); + + let pattern = git_pathspec::parse(input.as_bytes()).expect("parsing should not fail"); + assert_eq!(pattern, expected, "while checking input: \"{}\"", input); + }); } fn pat(path: &str, signature: MagicSignature, attributes: Vec<(&str, State)>) -> Pattern { From 0bed9382930486af144876d97b97479e03e0f1c1 Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Thu, 2 Jun 2022 00:57:48 +0200 Subject: [PATCH 021/248] pattern now has searchmode... ...to properly represent valid and invalid cases like git does --- git-pathspec/src/lib.rs | 22 ++++-- git-pathspec/src/parse.rs | 42 +++++++---- git-pathspec/tests/pathspec.rs | 127 ++++++++++++++++++++++----------- 3 files changed, 127 insertions(+), 64 deletions(-) diff --git a/git-pathspec/src/lib.rs b/git-pathspec/src/lib.rs index 16aeb428eea..c9248b00077 100644 --- a/git-pathspec/src/lib.rs +++ b/git-pathspec/src/lib.rs @@ -12,6 +12,8 @@ pub struct Pattern { pub path: BString, /// All magic signatures that were included in the pathspec. pub signature: MagicSignature, + /// The search mode of the pathspec. + pub searchmode: SearchMode, /// All attributes that were included in the `ATTR` part of the pathspec, if present. pub attributes: Vec<(BString, git_attributes::State)>, } @@ -20,19 +22,25 @@ bitflags! { pub struct MagicSignature: u32 { /// Matches patterns from the root of the repository const TOP = 1 << 0; - /// Special characters in the pattern, like '*' or '?', are treated literally - const LITERAL = 1 << 1; /// Matches patterns in case insensitive mode - const ICASE = 1 << 2; - /// A single '*' will not match a '/' in the pattern, but a '**' will - const GLOB = 1 << 3; + const ICASE = 1 << 1; /// Specifies a list of attribute requirements that the matches should meet - const ATTR = 1 << 4; + const ATTR = 1 << 2; /// Excludes the matching patterns from the previous results - const EXCLUDE = 1 << 5; + const EXCLUDE = 1 << 3; } } +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +pub enum SearchMode { + /// Default search mode + Default, + /// Special characters in the pattern, like '*' or '?', are treated literally + Literal, + /// A single '*' will not match a '/' in the pattern, but a '**' will + Glob, +} + /// Parse a git-style pathspec into a `Pattern` pub fn parse(input: &[u8]) -> Result { Pattern::from_bytes(input) diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index 4ab180ef346..6055ca497fb 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -1,7 +1,7 @@ use bstr::{BString, ByteSlice}; use git_attributes::{parse::Iter, State}; -use crate::{MagicSignature, Pattern}; +use crate::{MagicSignature, Pattern, SearchMode}; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -16,6 +16,9 @@ pub enum Error { #[error("Attribute has non-ascii characters or starts with '-': {}", attribute)] InvalidAttribute { attribute: BString }, + + #[error("'literal' and 'globl' keywords can not be used together in the same pathspec")] + IncompatibleSearchmodes, } impl Pattern { @@ -23,6 +26,7 @@ impl Pattern { Pattern { path: BString::default(), signature: MagicSignature::empty(), + searchmode: SearchMode::Default, attributes: Vec::new(), } } @@ -34,6 +38,7 @@ impl Pattern { let mut cursor = 0; let mut signature = MagicSignature::empty(); + let mut searchmode = SearchMode::Default; let mut attributes = Vec::new(); if input.first() == Some(&b':') { @@ -48,10 +53,11 @@ impl Pattern { let end = input.find(")").ok_or(Error::MissingClosingParenthesis { pathspec: BString::from(input), })?; - let (signatures, attrs) = parse_keywords(&input[cursor..end])?; + let (sig, sm, attrs) = parse_keywords(&input[cursor..end])?; cursor = end + 1; - signature |= signatures; + signature |= sig; + searchmode = sm; attributes = attrs; } _ => { @@ -65,27 +71,35 @@ impl Pattern { Ok(Pattern { path: BString::from(&input[cursor..]), signature, + searchmode, attributes, }) } } -fn parse_keywords(input: &[u8]) -> Result<(MagicSignature, Vec<(BString, State)>), Error> { +fn parse_keywords(input: &[u8]) -> Result<(MagicSignature, SearchMode, Vec<(BString, State)>), Error> { let mut signature = MagicSignature::empty(); + let mut searchmode = SearchMode::Default; let mut attributes = Vec::new(); if input.is_empty() { - return Ok((signature, attributes)); + return Ok((signature, searchmode, attributes)); } for keyword in input.split(|&c| c == b',') { - signature |= match keyword { - b"top" => MagicSignature::TOP, - b"literal" => MagicSignature::LITERAL, - b"icase" => MagicSignature::ICASE, - b"glob" => MagicSignature::GLOB, - b"attr" => MagicSignature::ATTR, - b"exclude" => MagicSignature::EXCLUDE, + match keyword { + b"top" => signature |= MagicSignature::TOP, + b"icase" => signature |= MagicSignature::ICASE, + b"exclude" => signature |= MagicSignature::EXCLUDE, + b"attr" => signature |= MagicSignature::ATTR, + b"literal" => match searchmode { + SearchMode::Default => searchmode = SearchMode::Literal, + _ => return Err(Error::IncompatibleSearchmodes), + }, + b"glob" => match searchmode { + SearchMode::Default => searchmode = SearchMode::Glob, + _ => return Err(Error::IncompatibleSearchmodes), + }, s => { if let Some(attrs) = s.strip_prefix(b"attr:") { attributes = Iter::new(attrs.into(), 0) @@ -98,7 +112,7 @@ fn parse_keywords(input: &[u8]) -> Result<(MagicSignature, Vec<(BString, State)> } => Error::InvalidAttribute { attribute }, _ => unreachable!("expecting only 'Error::AttributeName' but got {}", e), })?; - MagicSignature::ATTR + signature |= MagicSignature::ATTR } else { return Err(Error::InvalidKeyword { found_keyword: BString::from(s), @@ -108,5 +122,5 @@ fn parse_keywords(input: &[u8]) -> Result<(MagicSignature, Vec<(BString, State)> } } - Ok((signature, attributes)) + Ok((signature, searchmode, attributes)) } diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index 46e28aa6a7b..046d62a4b90 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -1,15 +1,15 @@ use git_attributes::State; use git_pathspec::parse::Error; -use git_pathspec::{MagicSignature, Pattern}; +use git_pathspec::{MagicSignature, Pattern, SearchMode}; #[test] fn can_parse_empty_signatures() { let inputs = vec![ - ("some/path", pat("some/path", MagicSignature::empty(), vec![])), - (":some/path", pat("some/path", MagicSignature::empty(), vec![])), - (":()some/path", pat("some/path", MagicSignature::empty(), vec![])), - ("::some/path", pat("some/path", MagicSignature::empty(), vec![])), - (":::some/path", pat(":some/path", MagicSignature::empty(), vec![])), + ("some/path", pat_with_path("some/path")), + (":some/path", pat_with_path("some/path")), + (":()some/path", pat_with_path("some/path")), + ("::some/path", pat_with_path("some/path")), + (":::some/path", pat_with_path(":some/path")), ]; check_valid_inputs(inputs) @@ -18,58 +18,60 @@ fn can_parse_empty_signatures() { #[test] fn can_parse_short_signatures() { let inputs = vec![ - (":/some/path", pat("some/path", MagicSignature::TOP, vec![])), - (":^some/path", pat("some/path", MagicSignature::EXCLUDE, vec![])), - (":!some/path", pat("some/path", MagicSignature::EXCLUDE, vec![])), + (":/some/path", pat_with_path_and_sig("some/path", MagicSignature::TOP)), + ( + ":^some/path", + pat_with_path_and_sig("some/path", MagicSignature::EXCLUDE), + ), + ( + ":!some/path", + pat_with_path_and_sig("some/path", MagicSignature::EXCLUDE), + ), ( ":/!some/path", - pat("some/path", MagicSignature::TOP | MagicSignature::EXCLUDE, vec![]), + pat_with_path_and_sig("some/path", MagicSignature::TOP | MagicSignature::EXCLUDE), + ), + ( + ":!/^/:", + pat_with_path_and_sig("", MagicSignature::TOP | MagicSignature::EXCLUDE), ), - (":!/^/:", pat("", MagicSignature::TOP | MagicSignature::EXCLUDE, vec![])), ]; check_valid_inputs(inputs) } #[test] -fn can_parse_signatures() { +fn can_parse_signatures_and_searchmodes() { let inputs = vec![ - (":(top)", pat("", MagicSignature::TOP, vec![])), - (":(literal)", pat("", MagicSignature::LITERAL, vec![])), - (":(icase)", pat("", MagicSignature::ICASE, vec![])), - (":(glob)", pat("", MagicSignature::GLOB, vec![])), - (":(attr)", pat("", MagicSignature::ATTR, vec![])), - (":(exclude)", pat("", MagicSignature::EXCLUDE, vec![])), + (":(top)", pat_with_path_and_sig("", MagicSignature::TOP)), + (":(icase)", pat_with_path_and_sig("", MagicSignature::ICASE)), + (":(attr)", pat_with_path_and_sig("", MagicSignature::ATTR)), + (":(exclude)", pat_with_path_and_sig("", MagicSignature::EXCLUDE)), + ( + ":(literal)", + pat("", MagicSignature::empty(), SearchMode::Literal, vec![]), + ), + (":(glob)", pat("", MagicSignature::empty(), SearchMode::Glob, vec![])), ( ":(top,exclude)", - pat("", MagicSignature::TOP | MagicSignature::EXCLUDE, vec![]), + pat_with_path_and_sig("", MagicSignature::TOP | MagicSignature::EXCLUDE), ), ( ":(icase,literal)", - pat("", MagicSignature::ICASE | MagicSignature::LITERAL, vec![]), + pat("", MagicSignature::ICASE, SearchMode::Literal, vec![]), ), ( ":!(literal)some/*path", - pat("some/*path", MagicSignature::EXCLUDE | MagicSignature::LITERAL, vec![]), + pat("some/*path", MagicSignature::EXCLUDE, SearchMode::Literal, vec![]), + ), + ( + ":(top,literal,icase,attr,exclude)some/path", + pat("some/path", MagicSignature::all(), SearchMode::Literal, vec![]), + ), + ( + ":(top,glob,icase,attr,exclude)some/path", + pat("some/path", MagicSignature::all(), SearchMode::Glob, vec![]), ), - // TODO: - // 'literal' and 'glob' cannot appear in the same pathspec together - // adjust Pattern struct to properly represent this case - // ( - // ":(top,literal,icase,glob,attr,exclude)some/path", - // pat( - // "some/path", - // Some( - // MagicSignature::TOP - // | MagicSignature::LITERAL - // | MagicSignature::ICASE - // | MagicSignature::GLOB - // | MagicSignature::ATTR - // | MagicSignature::EXCLUDE, - // ), - // vec![] - // ), - // ), ]; check_valid_inputs(inputs); @@ -80,21 +82,37 @@ fn can_parse_attributes_in_signature() { let inputs = vec![ ( ":(attr:someAttr)", - pat("", MagicSignature::ATTR, vec![("someAttr", State::Set)]), + pat( + "", + MagicSignature::ATTR, + SearchMode::Default, + vec![("someAttr", State::Set)], + ), ), ( ":(attr:!someAttr)", - pat("", MagicSignature::ATTR, vec![("someAttr", State::Unspecified)]), + pat( + "", + MagicSignature::ATTR, + SearchMode::Default, + vec![("someAttr", State::Unspecified)], + ), ), ( ":(attr:-someAttr)", - pat("", MagicSignature::ATTR, vec![("someAttr", State::Unset)]), + pat( + "", + MagicSignature::ATTR, + SearchMode::Default, + vec![("someAttr", State::Unset)], + ), ), ( ":(attr:someAttr=value)", pat( "", MagicSignature::ATTR, + SearchMode::Default, vec![("someAttr", State::Value("value".into()))], ), ), @@ -103,6 +121,7 @@ fn can_parse_attributes_in_signature() { pat( "", MagicSignature::ATTR, + SearchMode::Default, vec![("someAttr", State::Set), ("anotherAttr", State::Set)], ), ), @@ -167,6 +186,19 @@ fn should_fail_on_missing_parentheses() { assert!(matches!(output.unwrap_err(), Error::MissingClosingParenthesis { .. })); } +#[test] +fn should_fail_on_glob_and_literal_present() { + let input = ":(glob,literal)some/path"; + + assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); + + let output = git_pathspec::parse(input.as_bytes()); + assert!(output.is_err()); + assert!(matches!(output.unwrap_err(), Error::IncompatibleSearchmodes)); +} + +// --- Helpers --- + fn check_valid_inputs(inputs: Vec<(&str, Pattern)>) { inputs.into_iter().for_each(|(input, expected)| { assert!(is_valid_in_git(input), "This pathspec is invalid in git: {}", input); @@ -176,10 +208,19 @@ fn check_valid_inputs(inputs: Vec<(&str, Pattern)>) { }); } -fn pat(path: &str, signature: MagicSignature, attributes: Vec<(&str, State)>) -> Pattern { +fn pat_with_path(path: &str) -> Pattern { + pat_with_path_and_sig(path, MagicSignature::empty()) +} + +fn pat_with_path_and_sig(path: &str, signature: MagicSignature) -> Pattern { + pat(path, signature, SearchMode::Default, vec![]) +} + +fn pat(path: &str, signature: MagicSignature, searchmode: SearchMode, attributes: Vec<(&str, State)>) -> Pattern { Pattern { path: path.into(), signature, + searchmode, attributes: attributes .into_iter() .map(|(attr, state)| (attr.into(), state)) From 162f9a06860fac69e6db6d76dc5051ec2e6ed2db Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 2 Jun 2022 20:13:47 +0800 Subject: [PATCH 022/248] refactor --- git-pathspec/src/parse.rs | 18 +- git-pathspec/tests/pathspec.rs | 344 +++++++++++++++++---------------- 2 files changed, 183 insertions(+), 179 deletions(-) diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index 6055ca497fb..1371ac3f9cf 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -7,18 +7,14 @@ use crate::{MagicSignature, Pattern, SearchMode}; pub enum Error { #[error("Empty string is not a valid pathspec")] EmptyString, - - #[error("Found \"{}\", which is not a valid keyword", found_keyword)] + #[error("Found {:?}, which is not a valid keyword", found_keyword)] InvalidKeyword { found_keyword: BString }, - - #[error("Missing ')' at the end of pathspec magic in '{}'", pathspec)] + #[error("Missing ')' at the end of pathspec magic in {:?}", pathspec)] MissingClosingParenthesis { pathspec: BString }, - - #[error("Attribute has non-ascii characters or starts with '-': {}", attribute)] + #[error("Attribute has non-ascii characters or starts with '-': {:?}", attribute)] InvalidAttribute { attribute: BString }, - - #[error("'literal' and 'globl' keywords can not be used together in the same pathspec")] - IncompatibleSearchmodes, + #[error("'literal' and 'glob' keywords cannot be used together in the same pathspec")] + IncompatibleSearchModes, } impl Pattern { @@ -94,11 +90,11 @@ fn parse_keywords(input: &[u8]) -> Result<(MagicSignature, SearchMode, Vec<(BStr b"attr" => signature |= MagicSignature::ATTR, b"literal" => match searchmode { SearchMode::Default => searchmode = SearchMode::Literal, - _ => return Err(Error::IncompatibleSearchmodes), + _ => return Err(Error::IncompatibleSearchModes), }, b"glob" => match searchmode { SearchMode::Default => searchmode = SearchMode::Glob, - _ => return Err(Error::IncompatibleSearchmodes), + _ => return Err(Error::IncompatibleSearchModes), }, s => { if let Some(attrs) = s.strip_prefix(b"attr:") { diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index 046d62a4b90..f997d63d9fd 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -1,204 +1,212 @@ use git_attributes::State; -use git_pathspec::parse::Error; use git_pathspec::{MagicSignature, Pattern, SearchMode}; -#[test] -fn can_parse_empty_signatures() { - let inputs = vec![ - ("some/path", pat_with_path("some/path")), - (":some/path", pat_with_path("some/path")), - (":()some/path", pat_with_path("some/path")), - ("::some/path", pat_with_path("some/path")), - (":::some/path", pat_with_path(":some/path")), - ]; - - check_valid_inputs(inputs) -} +mod succeed { + use crate::{check_valid_inputs, pat, pat_with_path, pat_with_path_and_sig}; + use git_attributes::State; + use git_pathspec::{MagicSignature, SearchMode}; + + #[test] + fn empty_signatures() { + let inputs = vec![ + ("some/path", pat_with_path("some/path")), + (":some/path", pat_with_path("some/path")), + (":()some/path", pat_with_path("some/path")), + ("::some/path", pat_with_path("some/path")), + (":::some/path", pat_with_path(":some/path")), + ]; + + check_valid_inputs(inputs) + } -#[test] -fn can_parse_short_signatures() { - let inputs = vec![ - (":/some/path", pat_with_path_and_sig("some/path", MagicSignature::TOP)), - ( - ":^some/path", - pat_with_path_and_sig("some/path", MagicSignature::EXCLUDE), - ), - ( - ":!some/path", - pat_with_path_and_sig("some/path", MagicSignature::EXCLUDE), - ), - ( - ":/!some/path", - pat_with_path_and_sig("some/path", MagicSignature::TOP | MagicSignature::EXCLUDE), - ), - ( - ":!/^/:", - pat_with_path_and_sig("", MagicSignature::TOP | MagicSignature::EXCLUDE), - ), - ]; - - check_valid_inputs(inputs) -} + #[test] + fn short_signatures() { + let inputs = vec![ + (":/some/path", pat_with_path_and_sig("some/path", MagicSignature::TOP)), + ( + ":^some/path", + pat_with_path_and_sig("some/path", MagicSignature::EXCLUDE), + ), + ( + ":!some/path", + pat_with_path_and_sig("some/path", MagicSignature::EXCLUDE), + ), + ( + ":/!some/path", + pat_with_path_and_sig("some/path", MagicSignature::TOP | MagicSignature::EXCLUDE), + ), + ( + ":!/^/:", + pat_with_path_and_sig("", MagicSignature::TOP | MagicSignature::EXCLUDE), + ), + ]; -#[test] -fn can_parse_signatures_and_searchmodes() { - let inputs = vec![ - (":(top)", pat_with_path_and_sig("", MagicSignature::TOP)), - (":(icase)", pat_with_path_and_sig("", MagicSignature::ICASE)), - (":(attr)", pat_with_path_and_sig("", MagicSignature::ATTR)), - (":(exclude)", pat_with_path_and_sig("", MagicSignature::EXCLUDE)), - ( - ":(literal)", - pat("", MagicSignature::empty(), SearchMode::Literal, vec![]), - ), - (":(glob)", pat("", MagicSignature::empty(), SearchMode::Glob, vec![])), - ( - ":(top,exclude)", - pat_with_path_and_sig("", MagicSignature::TOP | MagicSignature::EXCLUDE), - ), - ( - ":(icase,literal)", - pat("", MagicSignature::ICASE, SearchMode::Literal, vec![]), - ), - ( - ":!(literal)some/*path", - pat("some/*path", MagicSignature::EXCLUDE, SearchMode::Literal, vec![]), - ), - ( - ":(top,literal,icase,attr,exclude)some/path", - pat("some/path", MagicSignature::all(), SearchMode::Literal, vec![]), - ), - ( - ":(top,glob,icase,attr,exclude)some/path", - pat("some/path", MagicSignature::all(), SearchMode::Glob, vec![]), - ), - ]; - - check_valid_inputs(inputs); -} + check_valid_inputs(inputs) + } -#[test] -fn can_parse_attributes_in_signature() { - let inputs = vec![ - ( - ":(attr:someAttr)", - pat( - "", - MagicSignature::ATTR, - SearchMode::Default, - vec![("someAttr", State::Set)], + #[test] + fn signatures_and_searchmodes() { + let inputs = vec![ + (":(top)", pat_with_path_and_sig("", MagicSignature::TOP)), + (":(icase)", pat_with_path_and_sig("", MagicSignature::ICASE)), + (":(attr)", pat_with_path_and_sig("", MagicSignature::ATTR)), + (":(exclude)", pat_with_path_and_sig("", MagicSignature::EXCLUDE)), + ( + ":(literal)", + pat("", MagicSignature::empty(), SearchMode::Literal, vec![]), ), - ), - ( - ":(attr:!someAttr)", - pat( - "", - MagicSignature::ATTR, - SearchMode::Default, - vec![("someAttr", State::Unspecified)], + (":(glob)", pat("", MagicSignature::empty(), SearchMode::Glob, vec![])), + ( + ":(top,exclude)", + pat_with_path_and_sig("", MagicSignature::TOP | MagicSignature::EXCLUDE), ), - ), - ( - ":(attr:-someAttr)", - pat( - "", - MagicSignature::ATTR, - SearchMode::Default, - vec![("someAttr", State::Unset)], + ( + ":(icase,literal)", + pat("", MagicSignature::ICASE, SearchMode::Literal, vec![]), ), - ), - ( - ":(attr:someAttr=value)", - pat( - "", - MagicSignature::ATTR, - SearchMode::Default, - vec![("someAttr", State::Value("value".into()))], + ( + ":!(literal)some/*path", + pat("some/*path", MagicSignature::EXCLUDE, SearchMode::Literal, vec![]), ), - ), - ( - ":(attr:someAttr anotherAttr)", - pat( - "", - MagicSignature::ATTR, - SearchMode::Default, - vec![("someAttr", State::Set), ("anotherAttr", State::Set)], + ( + ":(top,literal,icase,attr,exclude)some/path", + pat("some/path", MagicSignature::all(), SearchMode::Literal, vec![]), ), - ), - ]; - - check_valid_inputs(inputs) -} + ( + ":(top,glob,icase,attr,exclude)some/path", + pat("some/path", MagicSignature::all(), SearchMode::Glob, vec![]), + ), + ]; -#[test] -fn should_fail_on_empty_input() { - let input = ""; + check_valid_inputs(inputs); + } - assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); + #[test] + fn attributes_in_signature() { + let inputs = vec![ + ( + ":(attr:someAttr)", + pat( + "", + MagicSignature::ATTR, + SearchMode::Default, + vec![("someAttr", State::Set)], + ), + ), + ( + ":(attr:!someAttr)", + pat( + "", + MagicSignature::ATTR, + SearchMode::Default, + vec![("someAttr", State::Unspecified)], + ), + ), + ( + ":(attr:-someAttr)", + pat( + "", + MagicSignature::ATTR, + SearchMode::Default, + vec![("someAttr", State::Unset)], + ), + ), + ( + ":(attr:someAttr=value)", + pat( + "", + MagicSignature::ATTR, + SearchMode::Default, + vec![("someAttr", State::Value("value".into()))], + ), + ), + ( + ":(attr:someAttr anotherAttr)", + pat( + "", + MagicSignature::ATTR, + SearchMode::Default, + vec![("someAttr", State::Set), ("anotherAttr", State::Set)], + ), + ), + ]; - let output = git_pathspec::parse(input.as_bytes()); - assert!(output.is_err()); - assert!(matches!(output.unwrap_err(), Error::EmptyString { .. })); + check_valid_inputs(inputs) + } } -#[test] -fn should_fail_on_invalid_keywords() { - let inputs = vec![ - ":( )some/path", - ":(tp)some/path", - ":(top, exclude)some/path", - ":(top,exclude,icse)some/path", - ]; +mod fail { + use crate::is_valid_in_git; + use git_pathspec::parse::Error; + + #[test] + fn empty_input() { + let input = ""; - inputs.into_iter().for_each(|input| { assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); let output = git_pathspec::parse(input.as_bytes()); assert!(output.is_err()); - assert!(matches!(output.unwrap_err(), Error::InvalidKeyword { .. })); - }); -} + assert!(matches!(output.unwrap_err(), Error::EmptyString { .. })); + } -#[test] -fn should_fail_on_invalid_attributes() { - let inputs = vec![ - ":(attr:+invalidAttr)some/path", - ":(attr:validAttr +invalidAttr)some/path", - ]; + #[test] + fn invalid_keywords() { + let inputs = vec![ + ":( )some/path", + ":(tp)some/path", + ":(top, exclude)some/path", + ":(top,exclude,icse)some/path", + ]; + + inputs.into_iter().for_each(|input| { + assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); + + let output = git_pathspec::parse(input.as_bytes()); + assert!(output.is_err()); + assert!(matches!(output.unwrap_err(), Error::InvalidKeyword { .. })); + }); + } - for input in inputs { - assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); + #[test] + fn invalid_attributes() { + let inputs = vec![ + ":(attr:+invalidAttr)some/path", + ":(attr:validAttr +invalidAttr)some/path", + ]; - let output = git_pathspec::parse(input.as_bytes()); - assert!(output.is_err()); - assert!(matches!(output.unwrap_err(), Error::InvalidAttribute { .. })); + for input in inputs { + assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); + + let output = git_pathspec::parse(input.as_bytes()); + assert!(output.is_err()); + assert!(matches!(output.unwrap_err(), Error::InvalidAttribute { .. })); + } } -} -#[test] -fn should_fail_on_missing_parentheses() { - let input = ":(top"; + #[test] + fn missing_parentheses() { + let input = ":(top"; - assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); + assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); - let output = git_pathspec::parse(input.as_bytes()); - assert!(output.is_err()); - assert!(matches!(output.unwrap_err(), Error::MissingClosingParenthesis { .. })); -} + let output = git_pathspec::parse(input.as_bytes()); + assert!(output.is_err()); + assert!(matches!(output.unwrap_err(), Error::MissingClosingParenthesis { .. })); + } -#[test] -fn should_fail_on_glob_and_literal_present() { - let input = ":(glob,literal)some/path"; + #[test] + fn glob_and_literal_keywords_present() { + let input = ":(glob,literal)some/path"; - assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); + assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); - let output = git_pathspec::parse(input.as_bytes()); - assert!(output.is_err()); - assert!(matches!(output.unwrap_err(), Error::IncompatibleSearchmodes)); + let output = git_pathspec::parse(input.as_bytes()); + assert!(output.is_err()); + assert!(matches!(output.unwrap_err(), Error::IncompatibleSearchModes)); + } } -// --- Helpers --- - fn check_valid_inputs(inputs: Vec<(&str, Pattern)>) { inputs.into_iter().for_each(|(input, expected)| { assert!(is_valid_in_git(input), "This pathspec is invalid in git: {}", input); From 5a3c0fe8d56e9bc28eda77d3d64ef5338365622c Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 2 Jun 2022 20:53:17 +0800 Subject: [PATCH 023/248] refactor - make SearchMode default more clear - bail out early when possible - more tests --- git-pathspec/src/lib.rs | 18 +++++++--- git-pathspec/src/parse.rs | 61 ++++++++++++++-------------------- git-pathspec/tests/pathspec.rs | 36 +++++++++++++++----- 3 files changed, 66 insertions(+), 49 deletions(-) diff --git a/git-pathspec/src/lib.rs b/git-pathspec/src/lib.rs index c9248b00077..c35e27f82f6 100644 --- a/git-pathspec/src/lib.rs +++ b/git-pathspec/src/lib.rs @@ -33,12 +33,20 @@ bitflags! { #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] pub enum SearchMode { - /// Default search mode - Default, - /// Special characters in the pattern, like '*' or '?', are treated literally + /// Expand special characters like `*` similar to how the shell would do it. + /// + /// See [`PathAwareGlob`][SearchMode::PathAwareGlob] for the alternative. + ShellGlob, + /// Special characters in the pattern, like `*` or `?`, are treated literally Literal, - /// A single '*' will not match a '/' in the pattern, but a '**' will - Glob, + /// A single `*` will not match a `/` in the pattern, but a `**` will + PathAwareGlob, +} + +impl Default for SearchMode { + fn default() -> Self { + SearchMode::ShellGlob + } } /// Parse a git-style pathspec into a `Pattern` diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index 1371ac3f9cf..175fdca2f6b 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -18,15 +18,6 @@ pub enum Error { } impl Pattern { - pub fn empty() -> Self { - Pattern { - path: BString::default(), - signature: MagicSignature::empty(), - searchmode: SearchMode::Default, - attributes: Vec::new(), - } - } - pub fn from_bytes(input: &[u8]) -> Result { if input.is_empty() { return Err(Error::EmptyString); @@ -34,7 +25,7 @@ impl Pattern { let mut cursor = 0; let mut signature = MagicSignature::empty(); - let mut searchmode = SearchMode::Default; + let mut search_mode = SearchMode::ShellGlob; let mut attributes = Vec::new(); if input.first() == Some(&b':') { @@ -53,7 +44,7 @@ impl Pattern { cursor = end + 1; signature |= sig; - searchmode = sm; + search_mode = sm; attributes = attrs; } _ => { @@ -67,7 +58,7 @@ impl Pattern { Ok(Pattern { path: BString::from(&input[cursor..]), signature, - searchmode, + searchmode: search_mode, attributes, }) } @@ -75,11 +66,12 @@ impl Pattern { fn parse_keywords(input: &[u8]) -> Result<(MagicSignature, SearchMode, Vec<(BString, State)>), Error> { let mut signature = MagicSignature::empty(); - let mut searchmode = SearchMode::Default; + let mut search_mode = SearchMode::ShellGlob; + debug_assert_eq!(search_mode, SearchMode::default()); let mut attributes = Vec::new(); if input.is_empty() { - return Ok((signature, searchmode, attributes)); + return Ok((signature, search_mode, attributes)); } for keyword in input.split(|&c| c == b',') { @@ -88,35 +80,32 @@ fn parse_keywords(input: &[u8]) -> Result<(MagicSignature, SearchMode, Vec<(BStr b"icase" => signature |= MagicSignature::ICASE, b"exclude" => signature |= MagicSignature::EXCLUDE, b"attr" => signature |= MagicSignature::ATTR, - b"literal" => match searchmode { - SearchMode::Default => searchmode = SearchMode::Literal, + b"literal" => match search_mode { + SearchMode::ShellGlob => search_mode = SearchMode::Literal, _ => return Err(Error::IncompatibleSearchModes), }, - b"glob" => match searchmode { - SearchMode::Default => searchmode = SearchMode::Glob, + b"glob" => match search_mode { + SearchMode::ShellGlob => search_mode = SearchMode::PathAwareGlob, _ => return Err(Error::IncompatibleSearchModes), }, s => { - if let Some(attrs) = s.strip_prefix(b"attr:") { - attributes = Iter::new(attrs.into(), 0) - .map(|res| res.map(|(attr, state)| (attr.into(), state.into()))) - .collect::, _>>() - .map_err(|e| match e { - git_attributes::parse::Error::AttributeName { - line_number: _, - attribute, - } => Error::InvalidAttribute { attribute }, - _ => unreachable!("expecting only 'Error::AttributeName' but got {}", e), - })?; - signature |= MagicSignature::ATTR - } else { - return Err(Error::InvalidKeyword { - found_keyword: BString::from(s), - }); - } + let attrs = s.strip_prefix(b"attr:").ok_or_else(|| Error::InvalidKeyword { + found_keyword: BString::from(s), + })?; + attributes = Iter::new(attrs.into(), 0) + .map(|res| res.map(|(attr, state)| (attr.into(), state.into()))) + .collect::, _>>() + .map_err(|e| match e { + git_attributes::parse::Error::AttributeName { + line_number: _, + attribute, + } => Error::InvalidAttribute { attribute }, + _ => unreachable!("expecting only 'Error::AttributeName' but got {}", e), + })?; + signature |= MagicSignature::ATTR } } } - Ok((signature, searchmode, attributes)) + Ok((signature, search_mode, attributes)) } diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index f997d63d9fd..6538ba6baad 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -6,6 +6,23 @@ mod succeed { use git_attributes::State; use git_pathspec::{MagicSignature, SearchMode}; + #[test] + #[ignore] + fn repeated_matcher_keywords() { + let input = vec![ + ( + ":(glob,glob)", + pat("", MagicSignature::empty(), SearchMode::PathAwareGlob, vec![]), + ), + ( + ":(literal,literal)", + pat("", MagicSignature::empty(), SearchMode::Literal, vec![]), + ), + ]; + + check_valid_inputs(input); + } + #[test] fn empty_signatures() { let inputs = vec![ @@ -55,7 +72,10 @@ mod succeed { ":(literal)", pat("", MagicSignature::empty(), SearchMode::Literal, vec![]), ), - (":(glob)", pat("", MagicSignature::empty(), SearchMode::Glob, vec![])), + ( + ":(glob)", + pat("", MagicSignature::empty(), SearchMode::PathAwareGlob, vec![]), + ), ( ":(top,exclude)", pat_with_path_and_sig("", MagicSignature::TOP | MagicSignature::EXCLUDE), @@ -74,7 +94,7 @@ mod succeed { ), ( ":(top,glob,icase,attr,exclude)some/path", - pat("some/path", MagicSignature::all(), SearchMode::Glob, vec![]), + pat("some/path", MagicSignature::all(), SearchMode::PathAwareGlob, vec![]), ), ]; @@ -89,7 +109,7 @@ mod succeed { pat( "", MagicSignature::ATTR, - SearchMode::Default, + SearchMode::ShellGlob, vec![("someAttr", State::Set)], ), ), @@ -98,7 +118,7 @@ mod succeed { pat( "", MagicSignature::ATTR, - SearchMode::Default, + SearchMode::ShellGlob, vec![("someAttr", State::Unspecified)], ), ), @@ -107,7 +127,7 @@ mod succeed { pat( "", MagicSignature::ATTR, - SearchMode::Default, + SearchMode::ShellGlob, vec![("someAttr", State::Unset)], ), ), @@ -116,7 +136,7 @@ mod succeed { pat( "", MagicSignature::ATTR, - SearchMode::Default, + SearchMode::ShellGlob, vec![("someAttr", State::Value("value".into()))], ), ), @@ -125,7 +145,7 @@ mod succeed { pat( "", MagicSignature::ATTR, - SearchMode::Default, + SearchMode::ShellGlob, vec![("someAttr", State::Set), ("anotherAttr", State::Set)], ), ), @@ -221,7 +241,7 @@ fn pat_with_path(path: &str) -> Pattern { } fn pat_with_path_and_sig(path: &str, signature: MagicSignature) -> Pattern { - pat(path, signature, SearchMode::Default, vec![]) + pat(path, signature, SearchMode::ShellGlob, vec![]) } fn pat(path: &str, signature: MagicSignature, searchmode: SearchMode, attributes: Vec<(&str, State)>) -> Pattern { From d3ec61a2fcc6d8269cb952def86a198a7ac9492e Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Mon, 13 Jun 2022 15:18:50 +0200 Subject: [PATCH 024/248] refactor --- git-pathspec/src/lib.rs | 2 +- git-pathspec/src/parse.rs | 70 +++++++++++++++++----------------- git-pathspec/tests/pathspec.rs | 4 +- 3 files changed, 39 insertions(+), 37 deletions(-) diff --git a/git-pathspec/src/lib.rs b/git-pathspec/src/lib.rs index c35e27f82f6..bfe6152c50d 100644 --- a/git-pathspec/src/lib.rs +++ b/git-pathspec/src/lib.rs @@ -13,7 +13,7 @@ pub struct Pattern { /// All magic signatures that were included in the pathspec. pub signature: MagicSignature, /// The search mode of the pathspec. - pub searchmode: SearchMode, + pub search_mode: SearchMode, /// All attributes that were included in the `ATTR` part of the pathspec, if present. pub attributes: Vec<(BString, git_attributes::State)>, } diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index 175fdca2f6b..388d37501e2 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -1,5 +1,5 @@ use bstr::{BString, ByteSlice}; -use git_attributes::{parse::Iter, State}; +use git_attributes::parse::Iter; use crate::{MagicSignature, Pattern, SearchMode}; @@ -23,29 +23,31 @@ impl Pattern { return Err(Error::EmptyString); } + let mut p = Pattern { + path: BString::default(), + signature: MagicSignature::empty(), + search_mode: SearchMode::ShellGlob, + attributes: Vec::new(), + }; let mut cursor = 0; - let mut signature = MagicSignature::empty(); - let mut search_mode = SearchMode::ShellGlob; - let mut attributes = Vec::new(); - if input.first() == Some(&b':') { cursor += 1; while let Some(&b) = input.get(cursor) { cursor += 1; match b { b':' => break, - b'/' => signature |= MagicSignature::TOP, - b'^' | b'!' => signature |= MagicSignature::EXCLUDE, + b'/' => p.signature |= MagicSignature::TOP, + b'^' | b'!' => p.signature |= MagicSignature::EXCLUDE, b'(' => { let end = input.find(")").ok_or(Error::MissingClosingParenthesis { pathspec: BString::from(input), })?; - let (sig, sm, attrs) = parse_keywords(&input[cursor..end])?; + let pat = parse_keywords(&input[cursor..end])?; cursor = end + 1; - signature |= sig; - search_mode = sm; - attributes = attrs; + p.search_mode = pat.search_mode; + p.attributes = pat.attributes; + p.signature |= pat.signature; } _ => { cursor -= 1; @@ -55,44 +57,44 @@ impl Pattern { } } - Ok(Pattern { - path: BString::from(&input[cursor..]), - signature, - searchmode: search_mode, - attributes, - }) + p.path = BString::from(&input[cursor..]); + Ok(p) } } -fn parse_keywords(input: &[u8]) -> Result<(MagicSignature, SearchMode, Vec<(BString, State)>), Error> { - let mut signature = MagicSignature::empty(); - let mut search_mode = SearchMode::ShellGlob; - debug_assert_eq!(search_mode, SearchMode::default()); - let mut attributes = Vec::new(); +fn parse_keywords(input: &[u8]) -> Result { + let mut p = Pattern { + path: BString::default(), + signature: MagicSignature::empty(), + search_mode: SearchMode::ShellGlob, + attributes: Vec::new(), + }; + + debug_assert_eq!(p.search_mode, SearchMode::default()); if input.is_empty() { - return Ok((signature, search_mode, attributes)); + return Ok(p); } for keyword in input.split(|&c| c == b',') { match keyword { - b"top" => signature |= MagicSignature::TOP, - b"icase" => signature |= MagicSignature::ICASE, - b"exclude" => signature |= MagicSignature::EXCLUDE, - b"attr" => signature |= MagicSignature::ATTR, - b"literal" => match search_mode { - SearchMode::ShellGlob => search_mode = SearchMode::Literal, + b"top" => p.signature |= MagicSignature::TOP, + b"icase" => p.signature |= MagicSignature::ICASE, + b"exclude" => p.signature |= MagicSignature::EXCLUDE, + b"attr" => p.signature |= MagicSignature::ATTR, + b"literal" => match p.search_mode { + SearchMode::ShellGlob => p.search_mode = SearchMode::Literal, _ => return Err(Error::IncompatibleSearchModes), }, - b"glob" => match search_mode { - SearchMode::ShellGlob => search_mode = SearchMode::PathAwareGlob, + b"glob" => match p.search_mode { + SearchMode::ShellGlob => p.search_mode = SearchMode::PathAwareGlob, _ => return Err(Error::IncompatibleSearchModes), }, s => { let attrs = s.strip_prefix(b"attr:").ok_or_else(|| Error::InvalidKeyword { found_keyword: BString::from(s), })?; - attributes = Iter::new(attrs.into(), 0) + p.attributes = Iter::new(attrs.into(), 0) .map(|res| res.map(|(attr, state)| (attr.into(), state.into()))) .collect::, _>>() .map_err(|e| match e { @@ -102,10 +104,10 @@ fn parse_keywords(input: &[u8]) -> Result<(MagicSignature, SearchMode, Vec<(BStr } => Error::InvalidAttribute { attribute }, _ => unreachable!("expecting only 'Error::AttributeName' but got {}", e), })?; - signature |= MagicSignature::ATTR + p.signature |= MagicSignature::ATTR } } } - Ok((signature, search_mode, attributes)) + Ok(p) } diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index 6538ba6baad..f8fd0da7363 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -244,11 +244,11 @@ fn pat_with_path_and_sig(path: &str, signature: MagicSignature) -> Pattern { pat(path, signature, SearchMode::ShellGlob, vec![]) } -fn pat(path: &str, signature: MagicSignature, searchmode: SearchMode, attributes: Vec<(&str, State)>) -> Pattern { +fn pat(path: &str, signature: MagicSignature, search_mode: SearchMode, attributes: Vec<(&str, State)>) -> Pattern { Pattern { path: path.into(), signature, - searchmode, + search_mode, attributes: attributes .into_iter() .map(|(attr, state)| (attr.into(), state)) From 476f31c0f0fc7b29d02110e3a9b9a542defce63e Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Mon, 13 Jun 2022 15:36:46 +0200 Subject: [PATCH 025/248] added more tests --- git-pathspec/src/parse.rs | 13 +++++++++---- git-pathspec/tests/pathspec.rs | 29 ++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index 388d37501e2..17ef9784ab2 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -15,6 +15,8 @@ pub enum Error { InvalidAttribute { attribute: BString }, #[error("'literal' and 'glob' keywords cannot be used together in the same pathspec")] IncompatibleSearchModes, + #[error("Only one attribute specification is allowed in the same pathspec")] + MultipleAttributeSpecifications, } impl Pattern { @@ -83,17 +85,20 @@ fn parse_keywords(input: &[u8]) -> Result { b"exclude" => p.signature |= MagicSignature::EXCLUDE, b"attr" => p.signature |= MagicSignature::ATTR, b"literal" => match p.search_mode { - SearchMode::ShellGlob => p.search_mode = SearchMode::Literal, - _ => return Err(Error::IncompatibleSearchModes), + SearchMode::PathAwareGlob => return Err(Error::IncompatibleSearchModes), + _ => p.search_mode = SearchMode::Literal, }, b"glob" => match p.search_mode { - SearchMode::ShellGlob => p.search_mode = SearchMode::PathAwareGlob, - _ => return Err(Error::IncompatibleSearchModes), + SearchMode::Literal => return Err(Error::IncompatibleSearchModes), + _ => p.search_mode = SearchMode::PathAwareGlob, }, s => { let attrs = s.strip_prefix(b"attr:").ok_or_else(|| Error::InvalidKeyword { found_keyword: BString::from(s), })?; + if p.attributes.len() > 0 { + return Err(Error::MultipleAttributeSpecifications); + } p.attributes = Iter::new(attrs.into(), 0) .map(|res| res.map(|(attr, state)| (attr.into(), state.into()))) .collect::, _>>() diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index f8fd0da7363..c32cd62ca38 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -7,7 +7,6 @@ mod succeed { use git_pathspec::{MagicSignature, SearchMode}; #[test] - #[ignore] fn repeated_matcher_keywords() { let input = vec![ ( @@ -18,6 +17,22 @@ mod succeed { ":(literal,literal)", pat("", MagicSignature::empty(), SearchMode::Literal, vec![]), ), + ( + ":(top,top)", + pat("", MagicSignature::TOP, SearchMode::ShellGlob, vec![]), + ), + ( + ":(icase,icase)", + pat("", MagicSignature::ICASE, SearchMode::ShellGlob, vec![]), + ), + ( + ":(attr,attr)", + pat("", MagicSignature::ATTR, SearchMode::ShellGlob, vec![]), + ), + ( + ":!^(exclude,exclude)", + pat("", MagicSignature::EXCLUDE, SearchMode::ShellGlob, vec![]), + ), ]; check_valid_inputs(input); @@ -193,6 +208,7 @@ mod fail { let inputs = vec![ ":(attr:+invalidAttr)some/path", ":(attr:validAttr +invalidAttr)some/path", + ":(attr:+invalidAttr,attr:valid)some/path", ]; for input in inputs { @@ -225,6 +241,17 @@ mod fail { assert!(output.is_err()); assert!(matches!(output.unwrap_err(), Error::IncompatibleSearchModes)); } + + #[test] + fn multiple_attribute_specifications() { + let input = ":(attr:one,attr:two)some/path"; + + assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); + + let output = git_pathspec::parse(input.as_bytes()); + assert!(output.is_err()); + assert!(matches!(output.unwrap_err(), Error::MultipleAttributeSpecifications)); + } } fn check_valid_inputs(inputs: Vec<(&str, Pattern)>) { From f7a3b69e43e82471047091008355d180e646773d Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Mon, 13 Jun 2022 15:37:37 +0200 Subject: [PATCH 026/248] thanks clippy --- git-pathspec/src/parse.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index 17ef9784ab2..171dfc862ef 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -96,7 +96,7 @@ fn parse_keywords(input: &[u8]) -> Result { let attrs = s.strip_prefix(b"attr:").ok_or_else(|| Error::InvalidKeyword { found_keyword: BString::from(s), })?; - if p.attributes.len() > 0 { + if !p.attributes.is_empty() { return Err(Error::MultipleAttributeSpecifications); } p.attributes = Iter::new(attrs.into(), 0) From 9ceea2718a63bdd55ea8b99c2f1656cc09850145 Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Tue, 14 Jun 2022 10:17:38 +0200 Subject: [PATCH 027/248] add more test cases --- git-pathspec/src/lib.rs | 4 +-- git-pathspec/src/parse.rs | 5 +++ git-pathspec/tests/pathspec.rs | 61 +++++++++++++++++++++++++++++++++- 3 files changed, 67 insertions(+), 3 deletions(-) diff --git a/git-pathspec/src/lib.rs b/git-pathspec/src/lib.rs index bfe6152c50d..7d77133ebbe 100644 --- a/git-pathspec/src/lib.rs +++ b/git-pathspec/src/lib.rs @@ -5,7 +5,7 @@ use bstr::BString; pub mod parse; -/// The output of a pathspec parsing operaion. It can be used to matche against a path / multiple paths. +/// The output of a pathspec parsing operation. It can be used to match against a path / multiple paths. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] pub struct Pattern { /// The path part of a pathspec. @@ -49,7 +49,7 @@ impl Default for SearchMode { } } -/// Parse a git-style pathspec into a `Pattern` +/// Parse a git-style pathspec into a [`Pattern`][Pattern].` pub fn parse(input: &[u8]) -> Result { Pattern::from_bytes(input) } diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index 171dfc862ef..637469665ce 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -13,6 +13,8 @@ pub enum Error { MissingClosingParenthesis { pathspec: BString }, #[error("Attribute has non-ascii characters or starts with '-': {:?}", attribute)] InvalidAttribute { attribute: BString }, + #[error("Attribute specification cannot be empty")] + EmptyAttribute, #[error("'literal' and 'glob' keywords cannot be used together in the same pathspec")] IncompatibleSearchModes, #[error("Only one attribute specification is allowed in the same pathspec")] @@ -96,6 +98,9 @@ fn parse_keywords(input: &[u8]) -> Result { let attrs = s.strip_prefix(b"attr:").ok_or_else(|| Error::InvalidKeyword { found_keyword: BString::from(s), })?; + if attrs.is_empty() { + return Err(Error::EmptyAttribute); + } if !p.attributes.is_empty() { return Err(Error::MultipleAttributeSpecifications); } diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index c32cd62ca38..76fddf6dcd3 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -168,6 +168,39 @@ mod succeed { check_valid_inputs(inputs) } + + #[test] + #[ignore] + fn attributes_with_escaped_values() { + let inputs = vec![( + r":(attr:value=one\,two\,three)", + pat( + "", + MagicSignature::ATTR, + SearchMode::ShellGlob, + vec![("value", State::Value("one,two,three".into()))], + ), + )]; + + check_valid_inputs(inputs) + } + + #[test] + #[ignore] + // TODO: Needs research - what does 'prefix:' do + fn prefix() { + let inputs = vec![( + r":(prefix:)", + pat( + "", + MagicSignature::ATTR, + SearchMode::ShellGlob, + vec![("value", State::Value("one,two,three".into()))], + ), + )]; + + check_valid_inputs(inputs) + } } mod fail { @@ -182,7 +215,21 @@ mod fail { let output = git_pathspec::parse(input.as_bytes()); assert!(output.is_err()); - assert!(matches!(output.unwrap_err(), Error::EmptyString { .. })); + assert!(matches!(output.unwrap_err(), Error::EmptyString)); + } + + #[test] + #[ignore] + fn invalid_short_signatures() { + let inputs = vec![":=()"]; + + inputs.into_iter().for_each(|input| { + assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); + + let output = git_pathspec::parse(input.as_bytes()); + assert!(output.is_err()); + // assert!(matches!(output.unwrap_err(), Error::InvalidShortSignature { .. })); + }); } #[test] @@ -209,6 +256,7 @@ mod fail { ":(attr:+invalidAttr)some/path", ":(attr:validAttr +invalidAttr)some/path", ":(attr:+invalidAttr,attr:valid)some/path", + ":(attr:inva\\lid)some/path", ]; for input in inputs { @@ -220,6 +268,17 @@ mod fail { } } + #[test] + fn empty_attribute() { + let input = ":(attr:)"; + + assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); + + let output = git_pathspec::parse(input.as_bytes()); + assert!(output.is_err()); + assert!(matches!(output.unwrap_err(), Error::EmptyAttribute)); + } + #[test] fn missing_parentheses() { let input = ":(top"; From 476fa56993391410fc0bafeccfcb8d4da8168bfc Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Wed, 15 Jun 2022 15:08:44 +0200 Subject: [PATCH 028/248] refactor --- git-pathspec/src/parse.rs | 66 ++++++++++++++++++++++------------ git-pathspec/tests/pathspec.rs | 14 +++++--- 2 files changed, 53 insertions(+), 27 deletions(-) diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index 637469665ce..94317f9aedd 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -9,6 +9,8 @@ pub enum Error { EmptyString, #[error("Found {:?}, which is not a valid keyword", found_keyword)] InvalidKeyword { found_keyword: BString }, + #[error("Unimplemented pathspec magic {:?}", found_short_keyword)] + Unimplemented { found_short_keyword: BString }, #[error("Missing ')' at the end of pathspec magic in {:?}", pathspec)] MissingClosingParenthesis { pathspec: BString }, #[error("Attribute has non-ascii characters or starts with '-': {:?}", attribute)] @@ -33,31 +35,17 @@ impl Pattern { search_mode: SearchMode::ShellGlob, attributes: Vec::new(), }; + let mut cursor = 0; if input.first() == Some(&b':') { cursor += 1; - while let Some(&b) = input.get(cursor) { + p.signature |= parse_short_keywords(&input, &mut cursor)?; + if let Some(b'(') = input.get(cursor) { cursor += 1; - match b { - b':' => break, - b'/' => p.signature |= MagicSignature::TOP, - b'^' | b'!' => p.signature |= MagicSignature::EXCLUDE, - b'(' => { - let end = input.find(")").ok_or(Error::MissingClosingParenthesis { - pathspec: BString::from(input), - })?; - let pat = parse_keywords(&input[cursor..end])?; - - cursor = end + 1; - p.search_mode = pat.search_mode; - p.attributes = pat.attributes; - p.signature |= pat.signature; - } - _ => { - cursor -= 1; - break; - } - } + let pat = parse_long_keywords(&input, &mut cursor)?; + p.search_mode = pat.search_mode; + p.attributes = pat.attributes; + p.signature |= pat.signature; } } @@ -66,7 +54,41 @@ impl Pattern { } } -fn parse_keywords(input: &[u8]) -> Result { +fn parse_short_keywords(input: &[u8], cursor: &mut usize) -> Result { + let unimplemented_chars = vec![ + b'"', b'#', b'%', b'&', b'\'', b',', b'-', b';', b'<', b'=', b'>', b'@', b'_', b'`', b'~', + ]; + let mut signature = MagicSignature::empty(); + + while let Some(&b) = input.get(*cursor) { + *cursor += 1; + signature |= match b { + b'/' => MagicSignature::TOP, + b'^' | b'!' => MagicSignature::EXCLUDE, + b':' => break, + _ if unimplemented_chars.contains(&b) => { + return Err(Error::Unimplemented { + found_short_keyword: vec![b].into(), + }); + } + _ => { + *cursor -= 1; + break; + } + } + } + + return Ok(signature); +} + +fn parse_long_keywords(input: &[u8], cursor: &mut usize) -> Result { + let end = input.find(")").ok_or(Error::MissingClosingParenthesis { + pathspec: BString::from(input), + })?; + + let input = &input[*cursor..end]; + *cursor = end + 1; + let mut p = Pattern { path: BString::default(), signature: MagicSignature::empty(), diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index 76fddf6dcd3..fd0d25f39c9 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -41,11 +41,13 @@ mod succeed { #[test] fn empty_signatures() { let inputs = vec![ + (".", pat_with_path(".")), ("some/path", pat_with_path("some/path")), (":some/path", pat_with_path("some/path")), (":()some/path", pat_with_path("some/path")), ("::some/path", pat_with_path("some/path")), (":::some/path", pat_with_path(":some/path")), + (":():some/path", pat_with_path(":some/path")), ]; check_valid_inputs(inputs) @@ -68,8 +70,8 @@ mod succeed { pat_with_path_and_sig("some/path", MagicSignature::TOP | MagicSignature::EXCLUDE), ), ( - ":!/^/:", - pat_with_path_and_sig("", MagicSignature::TOP | MagicSignature::EXCLUDE), + ":!/^/:some/path", + pat_with_path_and_sig("some/path", MagicSignature::TOP | MagicSignature::EXCLUDE), ), ]; @@ -219,16 +221,18 @@ mod fail { } #[test] - #[ignore] fn invalid_short_signatures() { - let inputs = vec![":=()"]; + let inputs = vec![ + ":\"()", ":#()", ":%()", ":&()", ":'()", ":,()", ":-()", ":;()", ":<()", ":=()", ":>()", ":@()", ":_()", + ":`()", ":~()", + ]; inputs.into_iter().for_each(|input| { assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); let output = git_pathspec::parse(input.as_bytes()); assert!(output.is_err()); - // assert!(matches!(output.unwrap_err(), Error::InvalidShortSignature { .. })); + assert!(matches!(output.unwrap_err(), Error::Unimplemented { .. })); }); } From eecd388708017414fee9077066f24124d83b70ba Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Wed, 15 Jun 2022 16:35:34 +0200 Subject: [PATCH 029/248] whitespace test added --- git-pathspec/tests/pathspec.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index fd0d25f39c9..f263974fca5 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -53,6 +53,24 @@ mod succeed { check_valid_inputs(inputs) } + #[test] + fn whitespace_in_pathspec() { + let inputs = vec![ + (" some/path", pat_with_path(" some/path")), + ("some/ path", pat_with_path("some/ path")), + ("some/path ", pat_with_path("some/path ")), + (": some/path", pat_with_path(" some/path")), + ( + ":! some/path", + pat_with_path_and_sig(" some/path", MagicSignature::EXCLUDE), + ), + (": :some/path", pat_with_path(" :some/path")), + (": ()some/path", pat_with_path(" ()some/path")), + ]; + + check_valid_inputs(inputs) + } + #[test] fn short_signatures() { let inputs = vec![ From 149d1b36f93d98002175cda362d50bac584c691a Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Wed, 15 Jun 2022 16:51:07 +0200 Subject: [PATCH 030/248] refactor --- git-pathspec/src/parse.rs | 44 ++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index 94317f9aedd..85048377b4f 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -102,6 +102,7 @@ fn parse_long_keywords(input: &[u8], cursor: &mut usize) -> Result p.signature |= MagicSignature::TOP, @@ -116,30 +117,39 @@ fn parse_long_keywords(input: &[u8], cursor: &mut usize) -> Result return Err(Error::IncompatibleSearchModes), _ => p.search_mode = SearchMode::PathAwareGlob, }, - s => { - let attrs = s.strip_prefix(b"attr:").ok_or_else(|| Error::InvalidKeyword { - found_keyword: BString::from(s), - })?; - if attrs.is_empty() { - return Err(Error::EmptyAttribute); - } + _ if keyword.starts_with(b"attr:") => { if !p.attributes.is_empty() { return Err(Error::MultipleAttributeSpecifications); } - p.attributes = Iter::new(attrs.into(), 0) - .map(|res| res.map(|(attr, state)| (attr.into(), state.into()))) - .collect::, _>>() - .map_err(|e| match e { - git_attributes::parse::Error::AttributeName { - line_number: _, - attribute, - } => Error::InvalidAttribute { attribute }, - _ => unreachable!("expecting only 'Error::AttributeName' but got {}", e), - })?; + p.attributes = parse_attributes(keyword.strip_prefix(b"attr:").unwrap())?; p.signature |= MagicSignature::ATTR } + _ if keyword.starts_with(b"prefix:") => { + //TODO: prefix + } + _ => { + return Err(Error::InvalidKeyword { + found_keyword: BString::from(keyword), + }); + } } } Ok(p) } + +fn parse_attributes(attrs: &[u8]) -> Result, Error> { + if attrs.is_empty() { + return Err(Error::EmptyAttribute); + } + Iter::new(attrs.into(), 0) + .map(|res| res.map(|(attr, state)| (attr.into(), state.into()))) + .collect::, _>>() + .map_err(|e| match e { + git_attributes::parse::Error::AttributeName { + line_number: _, + attribute, + } => Error::InvalidAttribute { attribute }, + _ => unreachable!("expecting only 'Error::AttributeName' but got {}", e), + }) +} From 998415d9a3a234787c017bd448410a24ad4965f0 Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Wed, 15 Jun 2022 23:10:11 +0200 Subject: [PATCH 031/248] remove attr from signature bitflag --- git-pathspec/src/lib.rs | 4 +--- git-pathspec/src/parse.rs | 3 +-- git-pathspec/tests/pathspec.rs | 18 +++++++++--------- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/git-pathspec/src/lib.rs b/git-pathspec/src/lib.rs index 7d77133ebbe..bbdc7665362 100644 --- a/git-pathspec/src/lib.rs +++ b/git-pathspec/src/lib.rs @@ -24,10 +24,8 @@ bitflags! { const TOP = 1 << 0; /// Matches patterns in case insensitive mode const ICASE = 1 << 1; - /// Specifies a list of attribute requirements that the matches should meet - const ATTR = 1 << 2; /// Excludes the matching patterns from the previous results - const EXCLUDE = 1 << 3; + const EXCLUDE = 1 << 2; } } diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index 85048377b4f..bf38dbad72a 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -108,7 +108,7 @@ fn parse_long_keywords(input: &[u8], cursor: &mut usize) -> Result p.signature |= MagicSignature::TOP, b"icase" => p.signature |= MagicSignature::ICASE, b"exclude" => p.signature |= MagicSignature::EXCLUDE, - b"attr" => p.signature |= MagicSignature::ATTR, + b"attr" => {} b"literal" => match p.search_mode { SearchMode::PathAwareGlob => return Err(Error::IncompatibleSearchModes), _ => p.search_mode = SearchMode::Literal, @@ -122,7 +122,6 @@ fn parse_long_keywords(input: &[u8], cursor: &mut usize) -> Result { //TODO: prefix diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index f263974fca5..77800ccd4ce 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -27,7 +27,7 @@ mod succeed { ), ( ":(attr,attr)", - pat("", MagicSignature::ATTR, SearchMode::ShellGlob, vec![]), + pat("", MagicSignature::empty(), SearchMode::ShellGlob, vec![]), ), ( ":!^(exclude,exclude)", @@ -101,7 +101,7 @@ mod succeed { let inputs = vec![ (":(top)", pat_with_path_and_sig("", MagicSignature::TOP)), (":(icase)", pat_with_path_and_sig("", MagicSignature::ICASE)), - (":(attr)", pat_with_path_and_sig("", MagicSignature::ATTR)), + (":(attr)", pat_with_path("")), (":(exclude)", pat_with_path_and_sig("", MagicSignature::EXCLUDE)), ( ":(literal)", @@ -143,7 +143,7 @@ mod succeed { ":(attr:someAttr)", pat( "", - MagicSignature::ATTR, + MagicSignature::empty(), SearchMode::ShellGlob, vec![("someAttr", State::Set)], ), @@ -152,7 +152,7 @@ mod succeed { ":(attr:!someAttr)", pat( "", - MagicSignature::ATTR, + MagicSignature::empty(), SearchMode::ShellGlob, vec![("someAttr", State::Unspecified)], ), @@ -161,7 +161,7 @@ mod succeed { ":(attr:-someAttr)", pat( "", - MagicSignature::ATTR, + MagicSignature::empty(), SearchMode::ShellGlob, vec![("someAttr", State::Unset)], ), @@ -170,7 +170,7 @@ mod succeed { ":(attr:someAttr=value)", pat( "", - MagicSignature::ATTR, + MagicSignature::empty(), SearchMode::ShellGlob, vec![("someAttr", State::Value("value".into()))], ), @@ -179,7 +179,7 @@ mod succeed { ":(attr:someAttr anotherAttr)", pat( "", - MagicSignature::ATTR, + MagicSignature::empty(), SearchMode::ShellGlob, vec![("someAttr", State::Set), ("anotherAttr", State::Set)], ), @@ -196,7 +196,7 @@ mod succeed { r":(attr:value=one\,two\,three)", pat( "", - MagicSignature::ATTR, + MagicSignature::empty(), SearchMode::ShellGlob, vec![("value", State::Value("one,two,three".into()))], ), @@ -213,7 +213,7 @@ mod succeed { r":(prefix:)", pat( "", - MagicSignature::ATTR, + MagicSignature::empty(), SearchMode::ShellGlob, vec![("value", State::Value("one,two,three".into()))], ), From f80eb851ab56b4580eb28935978487b9f37fe819 Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Wed, 15 Jun 2022 23:32:25 +0200 Subject: [PATCH 032/248] thanks clippy --- git-pathspec/src/parse.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index bf38dbad72a..ab19277da85 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -39,10 +39,10 @@ impl Pattern { let mut cursor = 0; if input.first() == Some(&b':') { cursor += 1; - p.signature |= parse_short_keywords(&input, &mut cursor)?; + p.signature |= parse_short_keywords(input, &mut cursor)?; if let Some(b'(') = input.get(cursor) { cursor += 1; - let pat = parse_long_keywords(&input, &mut cursor)?; + let pat = parse_long_keywords(input, &mut cursor)?; p.search_mode = pat.search_mode; p.attributes = pat.attributes; p.signature |= pat.signature; @@ -78,7 +78,7 @@ fn parse_short_keywords(input: &[u8], cursor: &mut usize) -> Result Result { @@ -118,10 +118,11 @@ fn parse_long_keywords(input: &[u8], cursor: &mut usize) -> Result p.search_mode = SearchMode::PathAwareGlob, }, _ if keyword.starts_with(b"attr:") => { - if !p.attributes.is_empty() { + if p.attributes.is_empty() { + p.attributes = parse_attributes(keyword.strip_prefix(b"attr:").unwrap())?; + } else { return Err(Error::MultipleAttributeSpecifications); } - p.attributes = parse_attributes(keyword.strip_prefix(b"attr:").unwrap())?; } _ if keyword.starts_with(b"prefix:") => { //TODO: prefix From c0196fa363088426de031e55b982f70573ab738d Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Thu, 16 Jun 2022 01:00:00 +0200 Subject: [PATCH 033/248] no splitting on escaped commas in attribute values --- git-pathspec/src/parse.rs | 31 ++++++++++++++++++++++++------- git-pathspec/tests/pathspec.rs | 30 ++++++++++++++++++++---------- 2 files changed, 44 insertions(+), 17 deletions(-) diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index ab19277da85..c2826f2e6b1 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -102,8 +102,25 @@ fn parse_long_keywords(input: &[u8], cursor: &mut usize) -> Result p.signature |= MagicSignature::TOP, b"icase" => p.signature |= MagicSignature::ICASE, @@ -119,13 +136,13 @@ fn parse_long_keywords(input: &[u8], cursor: &mut usize) -> Result { if p.attributes.is_empty() { - p.attributes = parse_attributes(keyword.strip_prefix(b"attr:").unwrap())?; + p.attributes = parse_attributes(&keyword[5..])?; } else { return Err(Error::MultipleAttributeSpecifications); } } _ if keyword.starts_with(b"prefix:") => { - //TODO: prefix + // TODO: Needs research - what does 'prefix:' do } _ => { return Err(Error::InvalidKeyword { @@ -138,11 +155,11 @@ fn parse_long_keywords(input: &[u8], cursor: &mut usize) -> Result Result, Error> { - if attrs.is_empty() { +fn parse_attributes(input: &[u8]) -> Result, Error> { + if input.is_empty() { return Err(Error::EmptyAttribute); } - Iter::new(attrs.into(), 0) + Iter::new(input.into(), 0) .map(|res| res.map(|(attr, state)| (attr.into(), state.into()))) .collect::, _>>() .map_err(|e| match e { diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index 77800ccd4ce..4a90119f5a3 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -189,25 +189,35 @@ mod succeed { check_valid_inputs(inputs) } + // TODO: unescape attribute values? #[test] - #[ignore] fn attributes_with_escaped_values() { - let inputs = vec![( - r":(attr:value=one\,two\,three)", - pat( - "", - MagicSignature::empty(), - SearchMode::ShellGlob, - vec![("value", State::Value("one,two,three".into()))], + let inputs = vec![ + ( + r":(attr:v=one\-)", + pat( + "", + MagicSignature::empty(), + SearchMode::ShellGlob, + vec![("v", State::Value(r"one\-".into()))], + ), ), - )]; + ( + r":(attr:v=one\,two\,three)", + pat( + "", + MagicSignature::empty(), + SearchMode::ShellGlob, + vec![("v", State::Value(r"one\,two\,three".into()))], + ), + ), + ]; check_valid_inputs(inputs) } #[test] #[ignore] - // TODO: Needs research - what does 'prefix:' do fn prefix() { let inputs = vec![( r":(prefix:)", From f606515dfd89e28c78eaead3cf5023d5064618f5 Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Thu, 16 Jun 2022 01:00:48 +0200 Subject: [PATCH 034/248] more testcases related to escape chars - still todo --- git-pathspec/tests/pathspec.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index 4a90119f5a3..ebff1ed2de8 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -60,6 +60,7 @@ mod succeed { ("some/ path", pat_with_path("some/ path")), ("some/path ", pat_with_path("some/path ")), (": some/path", pat_with_path(" some/path")), + (": !some/path", pat_with_path(" !some/path")), ( ":! some/path", pat_with_path_and_sig(" some/path", MagicSignature::EXCLUDE), @@ -288,7 +289,12 @@ mod fail { ":(attr:+invalidAttr)some/path", ":(attr:validAttr +invalidAttr)some/path", ":(attr:+invalidAttr,attr:valid)some/path", - ":(attr:inva\\lid)some/path", + r":(attr:inva\lid)some/path", + //TODO: handle these case + // r":(attr:v=invalid\ valid)some/path", + // r":(attr:v=invalid\ )some/path", + // r":(attr:v=invalid\)some/path", + // r":(attr:v=invalid\#)some/path", ]; for input in inputs { From b490b4a7be579941c2664fbefb25dd341ed7d1e7 Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Thu, 16 Jun 2022 01:17:01 +0200 Subject: [PATCH 035/248] refactor --- git-pathspec/src/parse.rs | 43 +++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index c2826f2e6b1..a986836971c 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -102,30 +102,12 @@ fn parse_long_keywords(input: &[u8], cursor: &mut usize) -> Result continue, b"top" => p.signature |= MagicSignature::TOP, b"icase" => p.signature |= MagicSignature::ICASE, b"exclude" => p.signature |= MagicSignature::EXCLUDE, - b"attr" => {} b"literal" => match p.search_mode { SearchMode::PathAwareGlob => return Err(Error::IncompatibleSearchModes), _ => p.search_mode = SearchMode::Literal, @@ -155,6 +137,27 @@ fn parse_long_keywords(input: &[u8], cursor: &mut usize) -> Result Vec<&[u8]> { + let mut keywords = Vec::new(); + let mut i = 0; + let mut last = 0; + loop { + if let Some(&b) = input.get(i + 1) { + if b == split_char && input[i] != b'\\' { + i += 1; + keywords.push(&input[last..i]); + last = i + 1; + } + } else { + keywords.push(&input[last..]); + break; + } + + i += 1; + } + keywords +} + fn parse_attributes(input: &[u8]) -> Result, Error> { if input.is_empty() { return Err(Error::EmptyAttribute); From 020bc24973233edc261e05fd9935c5e598bf2922 Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Wed, 22 Jun 2022 12:22:07 +0200 Subject: [PATCH 036/248] refactor --- git-pathspec/src/parse.rs | 73 +++++++++----- git-pathspec/tests/pathspec.rs | 173 ++++++++++++--------------------- 2 files changed, 110 insertions(+), 136 deletions(-) diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index a986836971c..e3cfb640bf6 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -1,5 +1,4 @@ use bstr::{BString, ByteSlice}; -use git_attributes::parse::Iter; use crate::{MagicSignature, Pattern, SearchMode}; @@ -10,7 +9,7 @@ pub enum Error { #[error("Found {:?}, which is not a valid keyword", found_keyword)] InvalidKeyword { found_keyword: BString }, #[error("Unimplemented pathspec magic {:?}", found_short_keyword)] - Unimplemented { found_short_keyword: BString }, + Unimplemented { found_short_keyword: char }, #[error("Missing ')' at the end of pathspec magic in {:?}", pathspec)] MissingClosingParenthesis { pathspec: BString }, #[error("Attribute has non-ascii characters or starts with '-': {:?}", attribute)] @@ -39,13 +38,10 @@ impl Pattern { let mut cursor = 0; if input.first() == Some(&b':') { cursor += 1; - p.signature |= parse_short_keywords(input, &mut cursor)?; + parse_short_keywords(input, &mut p, &mut cursor)?; if let Some(b'(') = input.get(cursor) { cursor += 1; - let pat = parse_long_keywords(input, &mut cursor)?; - p.search_mode = pat.search_mode; - p.attributes = pat.attributes; - p.signature |= pat.signature; + parse_long_keywords(input, &mut p, &mut cursor)?; } } @@ -54,21 +50,20 @@ impl Pattern { } } -fn parse_short_keywords(input: &[u8], cursor: &mut usize) -> Result { +fn parse_short_keywords(input: &[u8], p: &mut Pattern, cursor: &mut usize) -> Result<(), Error> { let unimplemented_chars = vec![ b'"', b'#', b'%', b'&', b'\'', b',', b'-', b';', b'<', b'=', b'>', b'@', b'_', b'`', b'~', ]; - let mut signature = MagicSignature::empty(); while let Some(&b) = input.get(*cursor) { *cursor += 1; - signature |= match b { + p.signature |= match b { b'/' => MagicSignature::TOP, b'^' | b'!' => MagicSignature::EXCLUDE, b':' => break, _ if unimplemented_chars.contains(&b) => { return Err(Error::Unimplemented { - found_short_keyword: vec![b].into(), + found_short_keyword: b.into(), }); } _ => { @@ -78,10 +73,10 @@ fn parse_short_keywords(input: &[u8], cursor: &mut usize) -> Result Result { +fn parse_long_keywords(input: &[u8], p: &mut Pattern, cursor: &mut usize) -> Result<(), Error> { let end = input.find(")").ok_or(Error::MissingClosingParenthesis { pathspec: BString::from(input), })?; @@ -89,17 +84,10 @@ fn parse_long_keywords(input: &[u8], cursor: &mut usize) -> Result Result Vec<&[u8]> { @@ -162,8 +150,11 @@ fn parse_attributes(input: &[u8]) -> Result, _>>() .map_err(|e| match e { git_attributes::parse::Error::AttributeName { @@ -173,3 +164,37 @@ fn parse_attributes(input: &[u8]) -> Result unreachable!("expecting only 'Error::AttributeName' but got {}", e), }) } + +fn _unescape_attribute_values(attrs: Vec<(BString, git_attributes::State)>) -> Vec<(BString, git_attributes::State)> { + attrs + .into_iter() + .map(|(name, state)| { + match &state { + git_attributes::State::Value(_v) => {} + _ => {} + } + (name, state) + }) + .collect::>() +} + +fn _check_attr_value(value: &BString) -> Result<(), Error> { + if value + .bytes() + .all(|b| b.is_ascii_alphanumeric() || b == b'-' || b == b'_' || b == b',') + { + // Invalid character in value + return Err(Error::InvalidAttribute { + attribute: value.clone(), + }); + } + + if value.ends_with(&[b'\\']) { + // escape char '\' not allowed as last character + return Err(Error::InvalidAttribute { + attribute: value.clone(), + }); + } + + Ok(()) +} diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index ebff1ed2de8..87063b8da2b 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -2,37 +2,22 @@ use git_attributes::State; use git_pathspec::{MagicSignature, Pattern, SearchMode}; mod succeed { - use crate::{check_valid_inputs, pat, pat_with_path, pat_with_path_and_sig}; + use crate::{ + check_valid_inputs, pat, pat_with_attrs, pat_with_path, pat_with_path_and_sig, pat_with_search_mode, + pat_with_sig, + }; use git_attributes::State; use git_pathspec::{MagicSignature, SearchMode}; #[test] fn repeated_matcher_keywords() { let input = vec![ - ( - ":(glob,glob)", - pat("", MagicSignature::empty(), SearchMode::PathAwareGlob, vec![]), - ), - ( - ":(literal,literal)", - pat("", MagicSignature::empty(), SearchMode::Literal, vec![]), - ), - ( - ":(top,top)", - pat("", MagicSignature::TOP, SearchMode::ShellGlob, vec![]), - ), - ( - ":(icase,icase)", - pat("", MagicSignature::ICASE, SearchMode::ShellGlob, vec![]), - ), - ( - ":(attr,attr)", - pat("", MagicSignature::empty(), SearchMode::ShellGlob, vec![]), - ), - ( - ":!^(exclude,exclude)", - pat("", MagicSignature::EXCLUDE, SearchMode::ShellGlob, vec![]), - ), + (":(glob,glob)", pat_with_search_mode(SearchMode::PathAwareGlob)), + (":(literal,literal)", pat_with_search_mode(SearchMode::Literal)), + (":(top,top)", pat_with_sig(MagicSignature::TOP)), + (":(icase,icase)", pat_with_sig(MagicSignature::ICASE)), + (":(attr,attr)", pat_with_attrs(vec![])), + (":!^(exclude,exclude)", pat_with_sig(MagicSignature::EXCLUDE)), ]; check_valid_inputs(input); @@ -61,12 +46,12 @@ mod succeed { ("some/path ", pat_with_path("some/path ")), (": some/path", pat_with_path(" some/path")), (": !some/path", pat_with_path(" !some/path")), + (": :some/path", pat_with_path(" :some/path")), + (": ()some/path", pat_with_path(" ()some/path")), ( ":! some/path", pat_with_path_and_sig(" some/path", MagicSignature::EXCLUDE), ), - (": :some/path", pat_with_path(" :some/path")), - (": ()some/path", pat_with_path(" ()some/path")), ]; check_valid_inputs(inputs) @@ -100,21 +85,15 @@ mod succeed { #[test] fn signatures_and_searchmodes() { let inputs = vec![ - (":(top)", pat_with_path_and_sig("", MagicSignature::TOP)), - (":(icase)", pat_with_path_and_sig("", MagicSignature::ICASE)), + (":(top)", pat_with_sig(MagicSignature::TOP)), + (":(icase)", pat_with_sig(MagicSignature::ICASE)), (":(attr)", pat_with_path("")), - (":(exclude)", pat_with_path_and_sig("", MagicSignature::EXCLUDE)), - ( - ":(literal)", - pat("", MagicSignature::empty(), SearchMode::Literal, vec![]), - ), - ( - ":(glob)", - pat("", MagicSignature::empty(), SearchMode::PathAwareGlob, vec![]), - ), + (":(exclude)", pat_with_sig(MagicSignature::EXCLUDE)), + (":(literal)", pat_with_search_mode(SearchMode::Literal)), + (":(glob)", pat_with_search_mode(SearchMode::PathAwareGlob)), ( ":(top,exclude)", - pat_with_path_and_sig("", MagicSignature::TOP | MagicSignature::EXCLUDE), + pat_with_sig(MagicSignature::TOP | MagicSignature::EXCLUDE), ), ( ":(icase,literal)", @@ -140,77 +119,43 @@ mod succeed { #[test] fn attributes_in_signature() { let inputs = vec![ - ( - ":(attr:someAttr)", - pat( - "", - MagicSignature::empty(), - SearchMode::ShellGlob, - vec![("someAttr", State::Set)], - ), - ), + (":(attr:someAttr)", pat_with_attrs(vec![("someAttr", State::Set)])), ( ":(attr:!someAttr)", - pat( - "", - MagicSignature::empty(), - SearchMode::ShellGlob, - vec![("someAttr", State::Unspecified)], - ), - ), - ( - ":(attr:-someAttr)", - pat( - "", - MagicSignature::empty(), - SearchMode::ShellGlob, - vec![("someAttr", State::Unset)], - ), + pat_with_attrs(vec![("someAttr", State::Unspecified)]), ), + (":(attr:-someAttr)", pat_with_attrs(vec![("someAttr", State::Unset)])), ( ":(attr:someAttr=value)", - pat( - "", - MagicSignature::empty(), - SearchMode::ShellGlob, - vec![("someAttr", State::Value("value".into()))], - ), + pat_with_attrs(vec![("someAttr", State::Value("value".into()))]), ), ( ":(attr:someAttr anotherAttr)", - pat( - "", - MagicSignature::empty(), - SearchMode::ShellGlob, - vec![("someAttr", State::Set), ("anotherAttr", State::Set)], - ), + pat_with_attrs(vec![("someAttr", State::Set), ("anotherAttr", State::Set)]), ), ]; check_valid_inputs(inputs) } - // TODO: unescape attribute values? #[test] - fn attributes_with_escaped_values() { + fn attributes_with_escape_chars_in_state_values() { let inputs = vec![ ( r":(attr:v=one\-)", - pat( - "", - MagicSignature::empty(), - SearchMode::ShellGlob, - vec![("v", State::Value(r"one\-".into()))], - ), + pat_with_attrs(vec![("v", State::Value(r"one\-".into()))]), + ), + ( + r":(attr:v=one\_)", + pat_with_attrs(vec![("v", State::Value(r"one\_".into()))]), + ), + ( + r":(attr:v=one\,)", + pat_with_attrs(vec![("v", State::Value(r"one,".into()))]), ), ( r":(attr:v=one\,two\,three)", - pat( - "", - MagicSignature::empty(), - SearchMode::ShellGlob, - vec![("v", State::Value(r"one\,two\,three".into()))], - ), + pat_with_attrs(vec![("v", State::Value(r"one,two,three".into()))]), ), ]; @@ -220,15 +165,7 @@ mod succeed { #[test] #[ignore] fn prefix() { - let inputs = vec![( - r":(prefix:)", - pat( - "", - MagicSignature::empty(), - SearchMode::ShellGlob, - vec![("value", State::Value("one,two,three".into()))], - ), - )]; + let inputs = vec![(r":(prefix:)", pat_with_path(""))]; check_valid_inputs(inputs) } @@ -290,24 +227,24 @@ mod fail { ":(attr:validAttr +invalidAttr)some/path", ":(attr:+invalidAttr,attr:valid)some/path", r":(attr:inva\lid)some/path", - //TODO: handle these case - // r":(attr:v=invalid\ valid)some/path", - // r":(attr:v=invalid\ )some/path", + // TODO: // r":(attr:v=invalid\)some/path", + // r":(attr:v=invalid\ )some/path", // r":(attr:v=invalid\#)some/path", + // r":(attr:v=invalid\ valid)some/path", ]; for input in inputs { assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); let output = git_pathspec::parse(input.as_bytes()); - assert!(output.is_err()); + assert!(output.is_err(), "This pathspec did not produce an error {}", input); assert!(matches!(output.unwrap_err(), Error::InvalidAttribute { .. })); } } #[test] - fn empty_attribute() { + fn empty_attribute_specification() { let input = ":(attr:)"; assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); @@ -318,36 +255,36 @@ mod fail { } #[test] - fn missing_parentheses() { - let input = ":(top"; + fn multiple_attribute_specifications() { + let input = ":(attr:one,attr:two)some/path"; assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); let output = git_pathspec::parse(input.as_bytes()); assert!(output.is_err()); - assert!(matches!(output.unwrap_err(), Error::MissingClosingParenthesis { .. })); + assert!(matches!(output.unwrap_err(), Error::MultipleAttributeSpecifications)); } #[test] - fn glob_and_literal_keywords_present() { - let input = ":(glob,literal)some/path"; + fn missing_parentheses() { + let input = ":(top"; assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); let output = git_pathspec::parse(input.as_bytes()); assert!(output.is_err()); - assert!(matches!(output.unwrap_err(), Error::IncompatibleSearchModes)); + assert!(matches!(output.unwrap_err(), Error::MissingClosingParenthesis { .. })); } #[test] - fn multiple_attribute_specifications() { - let input = ":(attr:one,attr:two)some/path"; + fn glob_and_literal_keywords_present() { + let input = ":(glob,literal)some/path"; assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); let output = git_pathspec::parse(input.as_bytes()); assert!(output.is_err()); - assert!(matches!(output.unwrap_err(), Error::MultipleAttributeSpecifications)); + assert!(matches!(output.unwrap_err(), Error::IncompatibleSearchModes)); } } @@ -368,6 +305,18 @@ fn pat_with_path_and_sig(path: &str, signature: MagicSignature) -> Pattern { pat(path, signature, SearchMode::ShellGlob, vec![]) } +fn pat_with_sig(signature: MagicSignature) -> Pattern { + pat("", signature, SearchMode::ShellGlob, vec![]) +} + +fn pat_with_attrs(attrs: Vec<(&str, State)>) -> Pattern { + pat("", MagicSignature::empty(), SearchMode::ShellGlob, attrs) +} + +fn pat_with_search_mode(search_mode: SearchMode) -> Pattern { + pat("", MagicSignature::empty(), search_mode, vec![]) +} + fn pat(path: &str, signature: MagicSignature, search_mode: SearchMode, attributes: Vec<(&str, State)>) -> Pattern { Pattern { path: path.into(), From 44991d373bd2e2f71ccf27eabe9f074cb5fe7c18 Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Wed, 22 Jun 2022 14:45:53 +0200 Subject: [PATCH 037/248] improved testing against the baseline --- Cargo.lock | 4 + git-pathspec/Cargo.toml | 6 + git-pathspec/src/parse.rs | 61 ++++++--- .../fixtures/generate_pathspec_baseline.sh | 125 ++++++++++++++++++ .../generate_pathspec_baseline.tar.xz | 3 + git-pathspec/tests/pathspec.rs | 124 ++++++++++++----- 6 files changed, 271 insertions(+), 52 deletions(-) create mode 100644 git-pathspec/tests/fixtures/generate_pathspec_baseline.sh create mode 100644 git-pathspec/tests/fixtures/generated-archives/generate_pathspec_baseline.tar.xz diff --git a/Cargo.lock b/Cargo.lock index 977c2382ceb..e5125cad46b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1419,9 +1419,13 @@ version = "0.0.0" dependencies = [ "bitflags", "bstr", + "compact_str", "git-attributes", "git-glob", + "git-testtools", + "once_cell", "thiserror", + "walkdir", ] [[package]] diff --git a/git-pathspec/Cargo.toml b/git-pathspec/Cargo.toml index a9a949ca2bd..814014b0251 100644 --- a/git-pathspec/Cargo.toml +++ b/git-pathspec/Cargo.toml @@ -19,3 +19,9 @@ git-attributes = { version = "^0.1.0", path = "../git-attributes" } bstr = { version = "0.2.13", default-features = false, features = ["std"]} bitflags = "1.3.2" thiserror = "1.0.26" +compact_str = "0.3.2" + +[dev-dependencies] +git-testtools = { path = "../tests/tools" } +once_cell = "1.12.0" +walkdir = "2.3.2" diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index e3cfb640bf6..9cf6920238d 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -1,4 +1,5 @@ use bstr::{BString, ByteSlice}; +use compact_str::CompactStr; use crate::{MagicSignature, Pattern, SearchMode}; @@ -140,7 +141,6 @@ fn split_on_non_escaped_char(input: &[u8], split_char: u8) -> Vec<&[u8]> { keywords.push(&input[last..]); break; } - i += 1; } keywords @@ -153,8 +153,9 @@ fn parse_attributes(input: &[u8]) -> Result, _>>() .map_err(|e| match e { git_attributes::parse::Error::AttributeName { @@ -162,35 +163,57 @@ fn parse_attributes(input: &[u8]) -> Result Error::InvalidAttribute { attribute }, _ => unreachable!("expecting only 'Error::AttributeName' but got {}", e), - }) + })?; + + for (_, state) in parsed_attrs.iter() { + match state { + git_attributes::State::Value(v) => _check_attr_value(&v.to_string().into())?, + _ => {} + } + } + + Ok(parsed_attrs) } -fn _unescape_attribute_values(attrs: Vec<(BString, git_attributes::State)>) -> Vec<(BString, git_attributes::State)> { - attrs - .into_iter() - .map(|(name, state)| { - match &state { - git_attributes::State::Value(_v) => {} - _ => {} +fn _unescape_attribute_value((name, state): (BString, git_attributes::State)) -> (BString, git_attributes::State) { + match &state { + git_attributes::State::Value(v) if v.contains("\\") => { + let mut i = 0; + let v = BString::from(v.to_string()); + let mut new_v = CompactStr::default(); + loop { + if let Some(_) = v.get(i + 1) { + if v[i] == b'\\' { + i += 1; + new_v.push(v[i] as char); + } else { + new_v.push(v[i] as char); + } + } else { + new_v.push(v[i] as char); + break; + } + i += 1; } - (name, state) - }) - .collect::>() + (name, git_attributes::State::Value(new_v)) + } + _ => (name, state), + } } fn _check_attr_value(value: &BString) -> Result<(), Error> { - if value - .bytes() - .all(|b| b.is_ascii_alphanumeric() || b == b'-' || b == b'_' || b == b',') - { - // Invalid character in value + // the only characters allowed in the PATHSPEC attribute value + let is_allowed_char = |c: u8| c.is_ascii_alphanumeric() || c == b'-' || c == b'_' || c == b','; + + if !value.bytes().all(is_allowed_char) { + // TODO: return correct error (invalid character in attribute value) return Err(Error::InvalidAttribute { attribute: value.clone(), }); } if value.ends_with(&[b'\\']) { - // escape char '\' not allowed as last character + // TODO: return correct error (escape char not allowed as last char) return Err(Error::InvalidAttribute { attribute: value.clone(), }); diff --git a/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh b/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh new file mode 100644 index 00000000000..c183a3dd536 --- /dev/null +++ b/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh @@ -0,0 +1,125 @@ +#!/bin/bash +set -eu -o pipefail + +function baseline() { + local pathspec=$1 # first argument is the pathspec to test + + git ls-files "$pathspec" && status=0 || status=$? + { + echo "$pathspec" + echo "$status" + } >> baseline.git +} + +# success + +# repeated_matcher_keywords +baseline ':(glob,glob)' +baseline ':(literal,literal)' +baseline ':(top,top)' +baseline ':(icase,icase)' +baseline ':(attr,attr)' +baseline ':!^(exclude,exclude)' + +# empty_signatures +baseline '.' +baseline 'some/path' +baseline ':some/path' +baseline ':()some/path' +baseline '::some/path' +baseline ':::some/path' +baseline ':():some/path' + +# whitespace_in_pathspec +baseline ' some/path' +baseline 'some/ path' +baseline 'some/path ' +baseline ': some/path' +baseline ': !some/path' +baseline ': :some/path' +baseline ': ()some/path' +baseline ':! some/path' + +# short_signatures +baseline ':/some/path' +baseline ':^some/path' +baseline ':!some/path' +baseline ':/!some/path' +baseline ':!/^/:some/path' + +# signatures_and_searchmodes +baseline ':(top)' +baseline ':(icase)' +baseline ':(attr)' +baseline ':(exclude)' +baseline ':(literal)' +baseline ':(glob)' +baseline ':(top,exclude)' +baseline ':(icase,literal)' +baseline ':!(literal)some/*path' +baseline ':(top,literal,icase,attr,exclude)some/path' +baseline ':(top,glob,icase,attr,exclude)some/path' + +# attributes_in_signature +baseline ':(attr:someAttr)' +baseline ':(attr:!someAttr)' +baseline ':(attr:-someAttr)' +baseline ':(attr:someAttr=value)' +baseline ':(attr:someAttr anotherAttr)' + +# attributes_with_escape_chars_in_state_values +baseline ':(attr:v=one\-)' +baseline ':(attr:v=one\_)' +baseline ':(attr:v=one\,)' +baseline ':(attr:v=one\,two\,three)' + +# failing + +#empty_input +baseline "" + +# invalid_short_signatures +baseline ':"()' +baseline ':#()' +baseline ':%()' +baseline ':&()' +baseline ":'()" +baseline ':,()' +baseline ':-()' +baseline ':;()' +baseline ':<()' +baseline ':=()' +baseline ':>()' +baseline ':@()' +baseline ':_()' +baseline ':`()' +baseline ':~()' + +# invalid_keywords +baseline ':( )some/path' +baseline ':(tp)some/path' +baseline ':(top, exclude)some/path' +baseline ':(top,exclude,icse)some/path' + +# invalid_attributes +baseline ':(attr:+invalidAttr)some/path' +baseline ':(attr:validAttr +invalidAttr)some/path' +baseline ':(attr:+invalidAttr,attr:valid)some/path' +baseline ':(attr:inva\lid)some/path' +baseline ':(attr:v=inva\\lid)some/path' +baseline ':(attr:v=invalid\)some/path' +baseline ':(attr:v=invalid\ )some/path' +baseline ':(attr:v=invalid\#)some/path' +baseline ':(attr:v=invalid\ valid)some/path' + +# empty_attribute_specification +baseline ':(attr:)' + +# multiple_attribute_specifications +baseline ':(attr:one,attr:two)some/path' + +# missing_parentheses +baseline ':(top' + +# glob_and_literal_keywords_present +baseline ':(glob,literal)some/path' \ No newline at end of file diff --git a/git-pathspec/tests/fixtures/generated-archives/generate_pathspec_baseline.tar.xz b/git-pathspec/tests/fixtures/generated-archives/generate_pathspec_baseline.tar.xz new file mode 100644 index 00000000000..b2f006f7c20 --- /dev/null +++ b/git-pathspec/tests/fixtures/generated-archives/generate_pathspec_baseline.tar.xz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9ff4031c2e12d0a3a9f558904180e83a4656e983b76b4e6954a7185ea523d220 +size 652 diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index 87063b8da2b..a6f68cd11b4 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -1,5 +1,30 @@ +use bstr::{BString, ByteSlice}; use git_attributes::State; use git_pathspec::{MagicSignature, Pattern, SearchMode}; +use once_cell::sync::Lazy; +use std::collections::HashMap; + +static BASELINE: Lazy> = Lazy::new(|| { + let mut baseline_map = HashMap::new(); + + let base = git_testtools::scripted_fixture_repo_read_only("generate_pathspec_baseline.sh").unwrap(); + + let dirwalk = walkdir::WalkDir::new(base) + .max_depth(1) + .follow_links(false) + .into_iter() + .filter_map(|e| e.ok().and_then(|e| (e.file_name() == "baseline.git").then(|| e))); + + for baseline_entry in dirwalk { + let baseline = std::fs::read(baseline_entry.path()).unwrap(); + let mut lines = baseline.lines(); + while let Some(spec) = lines.next() { + let exit_code = lines.next().unwrap().into(); + baseline_map.insert(spec.into(), exit_code); + } + } + baseline_map +}); mod succeed { use crate::{ @@ -141,14 +166,14 @@ mod succeed { #[test] fn attributes_with_escape_chars_in_state_values() { let inputs = vec![ - ( - r":(attr:v=one\-)", - pat_with_attrs(vec![("v", State::Value(r"one\-".into()))]), - ), - ( - r":(attr:v=one\_)", - pat_with_attrs(vec![("v", State::Value(r"one\_".into()))]), - ), + // ( + // r":(attr:v=one\-)", + // pat_with_attrs(vec![("v", State::Value(r"one-".into()))]), + // ), + // ( + // r":(attr:v=one\_)", + // pat_with_attrs(vec![("v", State::Value(r"one_".into()))]), + // ), ( r":(attr:v=one\,)", pat_with_attrs(vec![("v", State::Value(r"one,".into()))]), @@ -172,14 +197,18 @@ mod succeed { } mod fail { - use crate::is_valid_in_git; + use crate::check_against_baseline; use git_pathspec::parse::Error; #[test] fn empty_input() { let input = ""; - assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); + assert!( + !check_against_baseline(input), + "This pathspec is valid in git: {}", + input + ); let output = git_pathspec::parse(input.as_bytes()); assert!(output.is_err()); @@ -194,7 +223,11 @@ mod fail { ]; inputs.into_iter().for_each(|input| { - assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); + assert!( + !check_against_baseline(input), + "This pathspec is valid in git: {}", + input + ); let output = git_pathspec::parse(input.as_bytes()); assert!(output.is_err()); @@ -212,7 +245,11 @@ mod fail { ]; inputs.into_iter().for_each(|input| { - assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); + assert!( + !check_against_baseline(input), + "This pathspec is valid in git: {}", + input + ); let output = git_pathspec::parse(input.as_bytes()); assert!(output.is_err()); @@ -227,15 +264,21 @@ mod fail { ":(attr:validAttr +invalidAttr)some/path", ":(attr:+invalidAttr,attr:valid)some/path", r":(attr:inva\lid)some/path", - // TODO: - // r":(attr:v=invalid\)some/path", - // r":(attr:v=invalid\ )some/path", - // r":(attr:v=invalid\#)some/path", - // r":(attr:v=invalid\ valid)some/path", + r":(attr:inva\lid)some/path", + // TODO: Fix error values + r":(attr:v=inva\\lid)some/path", + r":(attr:v=invalid\)some/path", + r":(attr:v=invalid\ )some/path", + r":(attr:v=invalid\#)some/path", + r":(attr:v=invalid\ valid)some/path", ]; for input in inputs { - assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); + assert!( + !check_against_baseline(input), + "This pathspec is valid in git: {}", + input + ); let output = git_pathspec::parse(input.as_bytes()); assert!(output.is_err(), "This pathspec did not produce an error {}", input); @@ -247,7 +290,11 @@ mod fail { fn empty_attribute_specification() { let input = ":(attr:)"; - assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); + assert!( + !check_against_baseline(input), + "This pathspec is valid in git: {}", + input + ); let output = git_pathspec::parse(input.as_bytes()); assert!(output.is_err()); @@ -258,7 +305,11 @@ mod fail { fn multiple_attribute_specifications() { let input = ":(attr:one,attr:two)some/path"; - assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); + assert!( + !check_against_baseline(input), + "This pathspec is valid in git: {}", + input + ); let output = git_pathspec::parse(input.as_bytes()); assert!(output.is_err()); @@ -269,7 +320,11 @@ mod fail { fn missing_parentheses() { let input = ":(top"; - assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); + assert!( + !check_against_baseline(input), + "This pathspec is valid in git: {}", + input + ); let output = git_pathspec::parse(input.as_bytes()); assert!(output.is_err()); @@ -280,7 +335,11 @@ mod fail { fn glob_and_literal_keywords_present() { let input = ":(glob,literal)some/path"; - assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); + assert!( + !check_against_baseline(input), + "This pathspec is valid in git: {}", + input + ); let output = git_pathspec::parse(input.as_bytes()); assert!(output.is_err()); @@ -290,7 +349,11 @@ mod fail { fn check_valid_inputs(inputs: Vec<(&str, Pattern)>) { inputs.into_iter().for_each(|(input, expected)| { - assert!(is_valid_in_git(input), "This pathspec is invalid in git: {}", input); + assert!( + check_against_baseline(input), + "This pathspec is invalid in git: {}", + input + ); let pattern = git_pathspec::parse(input.as_bytes()).expect("parsing should not fail"); assert_eq!(pattern, expected, "while checking input: \"{}\"", input); @@ -329,14 +392,9 @@ fn pat(path: &str, signature: MagicSignature, search_mode: SearchMode, attribute } } -// TODO: Cache results instead of running them with each test run -fn is_valid_in_git(pathspec: &str) -> bool { - use std::process::Command; - - let output = Command::new("git") - .args(["ls-files", pathspec]) - .output() - .expect("failed to execute process"); - - output.status.success() +fn check_against_baseline(pathspec: &str) -> bool { + let base = BASELINE + .get(&BString::from(pathspec)) + .expect(&format!("missing baseline for pathspec: {:?}", pathspec)); + *base == BString::from("0") } From 02fba2c124f3665112102469d41d476b6cf48dcd Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 23 Jun 2022 20:10:55 +0800 Subject: [PATCH 038/248] refactor simplify baseline parsing --- Cargo.lock | 1 - git-config/Cargo.toml | 2 +- git-pathspec/Cargo.toml | 1 - .../fixtures/generate_pathspec_baseline.sh | 2 ++ git-pathspec/tests/pathspec.rs | 31 +++++++++---------- 5 files changed, 17 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e5125cad46b..10e5188d4da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1425,7 +1425,6 @@ dependencies = [ "git-testtools", "once_cell", "thiserror", - "walkdir", ] [[package]] diff --git a/git-config/Cargo.toml b/git-config/Cargo.toml index 4d9dab8fb2f..21960d8452e 100644 --- a/git-config/Cargo.toml +++ b/git-config/Cargo.toml @@ -14,7 +14,7 @@ include = ["src/**/*", "LICENSE-*", "README.md", "CHANGELOG.md"] # serde = ["serde_crate"] [dependencies] -git-features = { version = "^0.21.0", path = "../git-features"} +git-features = { version = "^0.21.0", path = "../git-features" } git-path = { version = "^0.1.2", path = "../git-path" } git-sec = { version = "^0.1.1", path = "../git-sec" } git-ref = { version = "0.13.0", path = "../git-ref" } diff --git a/git-pathspec/Cargo.toml b/git-pathspec/Cargo.toml index 814014b0251..d9602240485 100644 --- a/git-pathspec/Cargo.toml +++ b/git-pathspec/Cargo.toml @@ -24,4 +24,3 @@ compact_str = "0.3.2" [dev-dependencies] git-testtools = { path = "../tests/tools" } once_cell = "1.12.0" -walkdir = "2.3.2" diff --git a/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh b/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh index c183a3dd536..cb6c0fffa2c 100644 --- a/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh +++ b/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh @@ -1,6 +1,8 @@ #!/bin/bash set -eu -o pipefail +git init; + function baseline() { local pathspec=$1 # first argument is the pathspec to test diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index a6f68cd11b4..20cd3dbccc2 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -1,29 +1,25 @@ -use bstr::{BString, ByteSlice}; +use bstr::{BStr, BString, ByteSlice}; use git_attributes::State; use git_pathspec::{MagicSignature, Pattern, SearchMode}; use once_cell::sync::Lazy; use std::collections::HashMap; -static BASELINE: Lazy> = Lazy::new(|| { - let mut baseline_map = HashMap::new(); +pub use git_testtools::Result; +static BASELINE: Lazy> = Lazy::new(|| { let base = git_testtools::scripted_fixture_repo_read_only("generate_pathspec_baseline.sh").unwrap(); - let dirwalk = walkdir::WalkDir::new(base) - .max_depth(1) - .follow_links(false) - .into_iter() - .filter_map(|e| e.ok().and_then(|e| (e.file_name() == "baseline.git").then(|| e))); - - for baseline_entry in dirwalk { - let baseline = std::fs::read(baseline_entry.path()).unwrap(); + (|| -> Result<_> { + let mut map = HashMap::new(); + let baseline = std::fs::read(base.join("baseline.git"))?; let mut lines = baseline.lines(); while let Some(spec) = lines.next() { - let exit_code = lines.next().unwrap().into(); - baseline_map.insert(spec.into(), exit_code); + let exit_code = lines.next().expect("two lines per baseline").to_str()?.parse()?; + map.insert(spec.into(), exit_code); } - } - baseline_map + Ok(map) + })() + .unwrap() }); mod succeed { @@ -393,8 +389,9 @@ fn pat(path: &str, signature: MagicSignature, search_mode: SearchMode, attribute } fn check_against_baseline(pathspec: &str) -> bool { + let key: &BStr = pathspec.into(); let base = BASELINE - .get(&BString::from(pathspec)) + .get(key) .expect(&format!("missing baseline for pathspec: {:?}", pathspec)); - *base == BString::from("0") + *base == 0 } From 7f93231b4983f9ce596cea84ad4525feb3778dd6 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 23 Jun 2022 20:14:14 +0800 Subject: [PATCH 039/248] refactor test structure should match the crate under test, down to function level in this case. --- git-pathspec/tests/pathspec.rs | 668 +++++++++++++++++---------------- 1 file changed, 335 insertions(+), 333 deletions(-) diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index 20cd3dbccc2..1389428f5c2 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -1,224 +1,292 @@ -use bstr::{BStr, BString, ByteSlice}; -use git_attributes::State; -use git_pathspec::{MagicSignature, Pattern, SearchMode}; -use once_cell::sync::Lazy; -use std::collections::HashMap; - pub use git_testtools::Result; -static BASELINE: Lazy> = Lazy::new(|| { - let base = git_testtools::scripted_fixture_repo_read_only("generate_pathspec_baseline.sh").unwrap(); +mod parse { + use bstr::{BStr, BString, ByteSlice}; + use git_attributes::State; + use git_pathspec::{MagicSignature, Pattern, SearchMode}; + use once_cell::sync::Lazy; + use std::collections::HashMap; + + static BASELINE: Lazy> = Lazy::new(|| { + let base = git_testtools::scripted_fixture_repo_read_only("generate_pathspec_baseline.sh").unwrap(); + + (|| -> crate::Result<_> { + let mut map = HashMap::new(); + let baseline = std::fs::read(base.join("baseline.git"))?; + let mut lines = baseline.lines(); + while let Some(spec) = lines.next() { + let exit_code = lines.next().expect("two lines per baseline").to_str()?.parse()?; + map.insert(spec.into(), exit_code); + } + Ok(map) + })() + .unwrap() + }); - (|| -> Result<_> { - let mut map = HashMap::new(); - let baseline = std::fs::read(base.join("baseline.git"))?; - let mut lines = baseline.lines(); - while let Some(spec) = lines.next() { - let exit_code = lines.next().expect("two lines per baseline").to_str()?.parse()?; - map.insert(spec.into(), exit_code); + mod succeed { + use crate::parse::{ + check_valid_inputs, pat, pat_with_attrs, pat_with_path, pat_with_path_and_sig, pat_with_search_mode, + pat_with_sig, + }; + use git_attributes::State; + use git_pathspec::{MagicSignature, SearchMode}; + + #[test] + fn repeated_matcher_keywords() { + let input = vec![ + (":(glob,glob)", pat_with_search_mode(SearchMode::PathAwareGlob)), + (":(literal,literal)", pat_with_search_mode(SearchMode::Literal)), + (":(top,top)", pat_with_sig(MagicSignature::TOP)), + (":(icase,icase)", pat_with_sig(MagicSignature::ICASE)), + (":(attr,attr)", pat_with_attrs(vec![])), + (":!^(exclude,exclude)", pat_with_sig(MagicSignature::EXCLUDE)), + ]; + + check_valid_inputs(input); } - Ok(map) - })() - .unwrap() -}); - -mod succeed { - use crate::{ - check_valid_inputs, pat, pat_with_attrs, pat_with_path, pat_with_path_and_sig, pat_with_search_mode, - pat_with_sig, - }; - use git_attributes::State; - use git_pathspec::{MagicSignature, SearchMode}; - - #[test] - fn repeated_matcher_keywords() { - let input = vec![ - (":(glob,glob)", pat_with_search_mode(SearchMode::PathAwareGlob)), - (":(literal,literal)", pat_with_search_mode(SearchMode::Literal)), - (":(top,top)", pat_with_sig(MagicSignature::TOP)), - (":(icase,icase)", pat_with_sig(MagicSignature::ICASE)), - (":(attr,attr)", pat_with_attrs(vec![])), - (":!^(exclude,exclude)", pat_with_sig(MagicSignature::EXCLUDE)), - ]; - - check_valid_inputs(input); - } - #[test] - fn empty_signatures() { - let inputs = vec![ - (".", pat_with_path(".")), - ("some/path", pat_with_path("some/path")), - (":some/path", pat_with_path("some/path")), - (":()some/path", pat_with_path("some/path")), - ("::some/path", pat_with_path("some/path")), - (":::some/path", pat_with_path(":some/path")), - (":():some/path", pat_with_path(":some/path")), - ]; - - check_valid_inputs(inputs) - } + #[test] + fn empty_signatures() { + let inputs = vec![ + (".", pat_with_path(".")), + ("some/path", pat_with_path("some/path")), + (":some/path", pat_with_path("some/path")), + (":()some/path", pat_with_path("some/path")), + ("::some/path", pat_with_path("some/path")), + (":::some/path", pat_with_path(":some/path")), + (":():some/path", pat_with_path(":some/path")), + ]; + + check_valid_inputs(inputs) + } - #[test] - fn whitespace_in_pathspec() { - let inputs = vec![ - (" some/path", pat_with_path(" some/path")), - ("some/ path", pat_with_path("some/ path")), - ("some/path ", pat_with_path("some/path ")), - (": some/path", pat_with_path(" some/path")), - (": !some/path", pat_with_path(" !some/path")), - (": :some/path", pat_with_path(" :some/path")), - (": ()some/path", pat_with_path(" ()some/path")), - ( - ":! some/path", - pat_with_path_and_sig(" some/path", MagicSignature::EXCLUDE), - ), - ]; - - check_valid_inputs(inputs) - } + #[test] + fn whitespace_in_pathspec() { + let inputs = vec![ + (" some/path", pat_with_path(" some/path")), + ("some/ path", pat_with_path("some/ path")), + ("some/path ", pat_with_path("some/path ")), + (": some/path", pat_with_path(" some/path")), + (": !some/path", pat_with_path(" !some/path")), + (": :some/path", pat_with_path(" :some/path")), + (": ()some/path", pat_with_path(" ()some/path")), + ( + ":! some/path", + pat_with_path_and_sig(" some/path", MagicSignature::EXCLUDE), + ), + ]; + + check_valid_inputs(inputs) + } - #[test] - fn short_signatures() { - let inputs = vec![ - (":/some/path", pat_with_path_and_sig("some/path", MagicSignature::TOP)), - ( - ":^some/path", - pat_with_path_and_sig("some/path", MagicSignature::EXCLUDE), - ), - ( - ":!some/path", - pat_with_path_and_sig("some/path", MagicSignature::EXCLUDE), - ), - ( - ":/!some/path", - pat_with_path_and_sig("some/path", MagicSignature::TOP | MagicSignature::EXCLUDE), - ), - ( - ":!/^/:some/path", - pat_with_path_and_sig("some/path", MagicSignature::TOP | MagicSignature::EXCLUDE), - ), - ]; - - check_valid_inputs(inputs) - } + #[test] + fn short_signatures() { + let inputs = vec![ + (":/some/path", pat_with_path_and_sig("some/path", MagicSignature::TOP)), + ( + ":^some/path", + pat_with_path_and_sig("some/path", MagicSignature::EXCLUDE), + ), + ( + ":!some/path", + pat_with_path_and_sig("some/path", MagicSignature::EXCLUDE), + ), + ( + ":/!some/path", + pat_with_path_and_sig("some/path", MagicSignature::TOP | MagicSignature::EXCLUDE), + ), + ( + ":!/^/:some/path", + pat_with_path_and_sig("some/path", MagicSignature::TOP | MagicSignature::EXCLUDE), + ), + ]; + + check_valid_inputs(inputs) + } - #[test] - fn signatures_and_searchmodes() { - let inputs = vec![ - (":(top)", pat_with_sig(MagicSignature::TOP)), - (":(icase)", pat_with_sig(MagicSignature::ICASE)), - (":(attr)", pat_with_path("")), - (":(exclude)", pat_with_sig(MagicSignature::EXCLUDE)), - (":(literal)", pat_with_search_mode(SearchMode::Literal)), - (":(glob)", pat_with_search_mode(SearchMode::PathAwareGlob)), - ( - ":(top,exclude)", - pat_with_sig(MagicSignature::TOP | MagicSignature::EXCLUDE), - ), - ( - ":(icase,literal)", - pat("", MagicSignature::ICASE, SearchMode::Literal, vec![]), - ), - ( - ":!(literal)some/*path", - pat("some/*path", MagicSignature::EXCLUDE, SearchMode::Literal, vec![]), - ), - ( - ":(top,literal,icase,attr,exclude)some/path", - pat("some/path", MagicSignature::all(), SearchMode::Literal, vec![]), - ), - ( - ":(top,glob,icase,attr,exclude)some/path", - pat("some/path", MagicSignature::all(), SearchMode::PathAwareGlob, vec![]), - ), - ]; - - check_valid_inputs(inputs); - } + #[test] + fn signatures_and_searchmodes() { + let inputs = vec![ + (":(top)", pat_with_sig(MagicSignature::TOP)), + (":(icase)", pat_with_sig(MagicSignature::ICASE)), + (":(attr)", pat_with_path("")), + (":(exclude)", pat_with_sig(MagicSignature::EXCLUDE)), + (":(literal)", pat_with_search_mode(SearchMode::Literal)), + (":(glob)", pat_with_search_mode(SearchMode::PathAwareGlob)), + ( + ":(top,exclude)", + pat_with_sig(MagicSignature::TOP | MagicSignature::EXCLUDE), + ), + ( + ":(icase,literal)", + pat("", MagicSignature::ICASE, SearchMode::Literal, vec![]), + ), + ( + ":!(literal)some/*path", + pat("some/*path", MagicSignature::EXCLUDE, SearchMode::Literal, vec![]), + ), + ( + ":(top,literal,icase,attr,exclude)some/path", + pat("some/path", MagicSignature::all(), SearchMode::Literal, vec![]), + ), + ( + ":(top,glob,icase,attr,exclude)some/path", + pat("some/path", MagicSignature::all(), SearchMode::PathAwareGlob, vec![]), + ), + ]; + + check_valid_inputs(inputs); + } - #[test] - fn attributes_in_signature() { - let inputs = vec![ - (":(attr:someAttr)", pat_with_attrs(vec![("someAttr", State::Set)])), - ( - ":(attr:!someAttr)", - pat_with_attrs(vec![("someAttr", State::Unspecified)]), - ), - (":(attr:-someAttr)", pat_with_attrs(vec![("someAttr", State::Unset)])), - ( - ":(attr:someAttr=value)", - pat_with_attrs(vec![("someAttr", State::Value("value".into()))]), - ), - ( - ":(attr:someAttr anotherAttr)", - pat_with_attrs(vec![("someAttr", State::Set), ("anotherAttr", State::Set)]), - ), - ]; - - check_valid_inputs(inputs) - } + #[test] + fn attributes_in_signature() { + let inputs = vec![ + (":(attr:someAttr)", pat_with_attrs(vec![("someAttr", State::Set)])), + ( + ":(attr:!someAttr)", + pat_with_attrs(vec![("someAttr", State::Unspecified)]), + ), + (":(attr:-someAttr)", pat_with_attrs(vec![("someAttr", State::Unset)])), + ( + ":(attr:someAttr=value)", + pat_with_attrs(vec![("someAttr", State::Value("value".into()))]), + ), + ( + ":(attr:someAttr anotherAttr)", + pat_with_attrs(vec![("someAttr", State::Set), ("anotherAttr", State::Set)]), + ), + ]; + + check_valid_inputs(inputs) + } - #[test] - fn attributes_with_escape_chars_in_state_values() { - let inputs = vec![ - // ( - // r":(attr:v=one\-)", - // pat_with_attrs(vec![("v", State::Value(r"one-".into()))]), - // ), - // ( - // r":(attr:v=one\_)", - // pat_with_attrs(vec![("v", State::Value(r"one_".into()))]), - // ), - ( - r":(attr:v=one\,)", - pat_with_attrs(vec![("v", State::Value(r"one,".into()))]), - ), - ( - r":(attr:v=one\,two\,three)", - pat_with_attrs(vec![("v", State::Value(r"one,two,three".into()))]), - ), - ]; - - check_valid_inputs(inputs) - } + #[test] + fn attributes_with_escape_chars_in_state_values() { + let inputs = vec![ + // ( + // r":(attr:v=one\-)", + // pat_with_attrs(vec![("v", State::Value(r"one-".into()))]), + // ), + // ( + // r":(attr:v=one\_)", + // pat_with_attrs(vec![("v", State::Value(r"one_".into()))]), + // ), + ( + r":(attr:v=one\,)", + pat_with_attrs(vec![("v", State::Value(r"one,".into()))]), + ), + ( + r":(attr:v=one\,two\,three)", + pat_with_attrs(vec![("v", State::Value(r"one,two,three".into()))]), + ), + ]; + + check_valid_inputs(inputs) + } - #[test] - #[ignore] - fn prefix() { - let inputs = vec![(r":(prefix:)", pat_with_path(""))]; + #[test] + #[ignore] + fn prefix() { + let inputs = vec![(r":(prefix:)", pat_with_path(""))]; - check_valid_inputs(inputs) + check_valid_inputs(inputs) + } } -} -mod fail { - use crate::check_against_baseline; - use git_pathspec::parse::Error; + mod fail { + use crate::parse::check_against_baseline; + use git_pathspec::parse::Error; - #[test] - fn empty_input() { - let input = ""; + #[test] + fn empty_input() { + let input = ""; - assert!( - !check_against_baseline(input), - "This pathspec is valid in git: {}", - input - ); + assert!( + !check_against_baseline(input), + "This pathspec is valid in git: {}", + input + ); - let output = git_pathspec::parse(input.as_bytes()); - assert!(output.is_err()); - assert!(matches!(output.unwrap_err(), Error::EmptyString)); - } + let output = git_pathspec::parse(input.as_bytes()); + assert!(output.is_err()); + assert!(matches!(output.unwrap_err(), Error::EmptyString)); + } - #[test] - fn invalid_short_signatures() { - let inputs = vec![ - ":\"()", ":#()", ":%()", ":&()", ":'()", ":,()", ":-()", ":;()", ":<()", ":=()", ":>()", ":@()", ":_()", - ":`()", ":~()", - ]; + #[test] + fn invalid_short_signatures() { + let inputs = vec![ + ":\"()", ":#()", ":%()", ":&()", ":'()", ":,()", ":-()", ":;()", ":<()", ":=()", ":>()", ":@()", + ":_()", ":`()", ":~()", + ]; + + inputs.into_iter().for_each(|input| { + assert!( + !check_against_baseline(input), + "This pathspec is valid in git: {}", + input + ); + + let output = git_pathspec::parse(input.as_bytes()); + assert!(output.is_err()); + assert!(matches!(output.unwrap_err(), Error::Unimplemented { .. })); + }); + } + + #[test] + fn invalid_keywords() { + let inputs = vec![ + ":( )some/path", + ":(tp)some/path", + ":(top, exclude)some/path", + ":(top,exclude,icse)some/path", + ]; + + inputs.into_iter().for_each(|input| { + assert!( + !check_against_baseline(input), + "This pathspec is valid in git: {}", + input + ); + + let output = git_pathspec::parse(input.as_bytes()); + assert!(output.is_err()); + assert!(matches!(output.unwrap_err(), Error::InvalidKeyword { .. })); + }); + } + + #[test] + fn invalid_attributes() { + let inputs = vec![ + ":(attr:+invalidAttr)some/path", + ":(attr:validAttr +invalidAttr)some/path", + ":(attr:+invalidAttr,attr:valid)some/path", + r":(attr:inva\lid)some/path", + r":(attr:inva\lid)some/path", + // TODO: Fix error values + r":(attr:v=inva\\lid)some/path", + r":(attr:v=invalid\)some/path", + r":(attr:v=invalid\ )some/path", + r":(attr:v=invalid\#)some/path", + r":(attr:v=invalid\ valid)some/path", + ]; + + for input in inputs { + assert!( + !check_against_baseline(input), + "This pathspec is valid in git: {}", + input + ); + + let output = git_pathspec::parse(input.as_bytes()); + assert!(output.is_err(), "This pathspec did not produce an error {}", input); + assert!(matches!(output.unwrap_err(), Error::InvalidAttribute { .. })); + } + } + + #[test] + fn empty_attribute_specification() { + let input = ":(attr:)"; - inputs.into_iter().for_each(|input| { assert!( !check_against_baseline(input), "This pathspec is valid in git: {}", @@ -227,20 +295,13 @@ mod fail { let output = git_pathspec::parse(input.as_bytes()); assert!(output.is_err()); - assert!(matches!(output.unwrap_err(), Error::Unimplemented { .. })); - }); - } + assert!(matches!(output.unwrap_err(), Error::EmptyAttribute)); + } - #[test] - fn invalid_keywords() { - let inputs = vec![ - ":( )some/path", - ":(tp)some/path", - ":(top, exclude)some/path", - ":(top,exclude,icse)some/path", - ]; + #[test] + fn multiple_attribute_specifications() { + let input = ":(attr:one,attr:two)some/path"; - inputs.into_iter().for_each(|input| { assert!( !check_against_baseline(input), "This pathspec is valid in git: {}", @@ -249,27 +310,13 @@ mod fail { let output = git_pathspec::parse(input.as_bytes()); assert!(output.is_err()); - assert!(matches!(output.unwrap_err(), Error::InvalidKeyword { .. })); - }); - } + assert!(matches!(output.unwrap_err(), Error::MultipleAttributeSpecifications)); + } + + #[test] + fn missing_parentheses() { + let input = ":(top"; - #[test] - fn invalid_attributes() { - let inputs = vec![ - ":(attr:+invalidAttr)some/path", - ":(attr:validAttr +invalidAttr)some/path", - ":(attr:+invalidAttr,attr:valid)some/path", - r":(attr:inva\lid)some/path", - r":(attr:inva\lid)some/path", - // TODO: Fix error values - r":(attr:v=inva\\lid)some/path", - r":(attr:v=invalid\)some/path", - r":(attr:v=invalid\ )some/path", - r":(attr:v=invalid\#)some/path", - r":(attr:v=invalid\ valid)some/path", - ]; - - for input in inputs { assert!( !check_against_baseline(input), "This pathspec is valid in git: {}", @@ -277,121 +324,76 @@ mod fail { ); let output = git_pathspec::parse(input.as_bytes()); - assert!(output.is_err(), "This pathspec did not produce an error {}", input); - assert!(matches!(output.unwrap_err(), Error::InvalidAttribute { .. })); + assert!(output.is_err()); + assert!(matches!(output.unwrap_err(), Error::MissingClosingParenthesis { .. })); } - } - #[test] - fn empty_attribute_specification() { - let input = ":(attr:)"; + #[test] + fn glob_and_literal_keywords_present() { + let input = ":(glob,literal)some/path"; - assert!( - !check_against_baseline(input), - "This pathspec is valid in git: {}", - input - ); + assert!( + !check_against_baseline(input), + "This pathspec is valid in git: {}", + input + ); - let output = git_pathspec::parse(input.as_bytes()); - assert!(output.is_err()); - assert!(matches!(output.unwrap_err(), Error::EmptyAttribute)); + let output = git_pathspec::parse(input.as_bytes()); + assert!(output.is_err()); + assert!(matches!(output.unwrap_err(), Error::IncompatibleSearchModes)); + } } - #[test] - fn multiple_attribute_specifications() { - let input = ":(attr:one,attr:two)some/path"; - - assert!( - !check_against_baseline(input), - "This pathspec is valid in git: {}", - input - ); + fn check_valid_inputs(inputs: Vec<(&str, Pattern)>) { + inputs.into_iter().for_each(|(input, expected)| { + assert!( + check_against_baseline(input), + "This pathspec is invalid in git: {}", + input + ); - let output = git_pathspec::parse(input.as_bytes()); - assert!(output.is_err()); - assert!(matches!(output.unwrap_err(), Error::MultipleAttributeSpecifications)); + let pattern = git_pathspec::parse(input.as_bytes()).expect("parsing should not fail"); + assert_eq!(pattern, expected, "while checking input: \"{}\"", input); + }); } - #[test] - fn missing_parentheses() { - let input = ":(top"; - - assert!( - !check_against_baseline(input), - "This pathspec is valid in git: {}", - input - ); - - let output = git_pathspec::parse(input.as_bytes()); - assert!(output.is_err()); - assert!(matches!(output.unwrap_err(), Error::MissingClosingParenthesis { .. })); + fn pat_with_path(path: &str) -> Pattern { + pat_with_path_and_sig(path, MagicSignature::empty()) } - #[test] - fn glob_and_literal_keywords_present() { - let input = ":(glob,literal)some/path"; - - assert!( - !check_against_baseline(input), - "This pathspec is valid in git: {}", - input - ); - - let output = git_pathspec::parse(input.as_bytes()); - assert!(output.is_err()); - assert!(matches!(output.unwrap_err(), Error::IncompatibleSearchModes)); + fn pat_with_path_and_sig(path: &str, signature: MagicSignature) -> Pattern { + pat(path, signature, SearchMode::ShellGlob, vec![]) } -} - -fn check_valid_inputs(inputs: Vec<(&str, Pattern)>) { - inputs.into_iter().for_each(|(input, expected)| { - assert!( - check_against_baseline(input), - "This pathspec is invalid in git: {}", - input - ); - - let pattern = git_pathspec::parse(input.as_bytes()).expect("parsing should not fail"); - assert_eq!(pattern, expected, "while checking input: \"{}\"", input); - }); -} - -fn pat_with_path(path: &str) -> Pattern { - pat_with_path_and_sig(path, MagicSignature::empty()) -} - -fn pat_with_path_and_sig(path: &str, signature: MagicSignature) -> Pattern { - pat(path, signature, SearchMode::ShellGlob, vec![]) -} -fn pat_with_sig(signature: MagicSignature) -> Pattern { - pat("", signature, SearchMode::ShellGlob, vec![]) -} + fn pat_with_sig(signature: MagicSignature) -> Pattern { + pat("", signature, SearchMode::ShellGlob, vec![]) + } -fn pat_with_attrs(attrs: Vec<(&str, State)>) -> Pattern { - pat("", MagicSignature::empty(), SearchMode::ShellGlob, attrs) -} + fn pat_with_attrs(attrs: Vec<(&str, State)>) -> Pattern { + pat("", MagicSignature::empty(), SearchMode::ShellGlob, attrs) + } -fn pat_with_search_mode(search_mode: SearchMode) -> Pattern { - pat("", MagicSignature::empty(), search_mode, vec![]) -} + fn pat_with_search_mode(search_mode: SearchMode) -> Pattern { + pat("", MagicSignature::empty(), search_mode, vec![]) + } -fn pat(path: &str, signature: MagicSignature, search_mode: SearchMode, attributes: Vec<(&str, State)>) -> Pattern { - Pattern { - path: path.into(), - signature, - search_mode, - attributes: attributes - .into_iter() - .map(|(attr, state)| (attr.into(), state)) - .collect(), + fn pat(path: &str, signature: MagicSignature, search_mode: SearchMode, attributes: Vec<(&str, State)>) -> Pattern { + Pattern { + path: path.into(), + signature, + search_mode, + attributes: attributes + .into_iter() + .map(|(attr, state)| (attr.into(), state)) + .collect(), + } } -} -fn check_against_baseline(pathspec: &str) -> bool { - let key: &BStr = pathspec.into(); - let base = BASELINE - .get(key) - .expect(&format!("missing baseline for pathspec: {:?}", pathspec)); - *base == 0 + fn check_against_baseline(pathspec: &str) -> bool { + let key: &BStr = pathspec.into(); + let base = BASELINE + .get(key) + .expect(&format!("missing baseline for pathspec: {:?}", pathspec)); + *base == 0 + } } From 699de03f0c981a9f8b5239c66dc425e504de1ec2 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 23 Jun 2022 20:25:28 +0800 Subject: [PATCH 040/248] refactor --- git-pathspec/src/parse.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index 9cf6920238d..c67b16caaca 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -7,10 +7,10 @@ use crate::{MagicSignature, Pattern, SearchMode}; pub enum Error { #[error("Empty string is not a valid pathspec")] EmptyString, - #[error("Found {:?}, which is not a valid keyword", found_keyword)] - InvalidKeyword { found_keyword: BString }, - #[error("Unimplemented pathspec magic {:?}", found_short_keyword)] - Unimplemented { found_short_keyword: char }, + #[error("Found {:?}, which is not a valid keyword", keyword)] + InvalidKeyword { keyword: BString }, + #[error("Unimplemented pathspec magic {:?}", short_keyword)] + Unimplemented { short_keyword: char }, #[error("Missing ')' at the end of pathspec magic in {:?}", pathspec)] MissingClosingParenthesis { pathspec: BString }, #[error("Attribute has non-ascii characters or starts with '-': {:?}", attribute)] @@ -64,7 +64,7 @@ fn parse_short_keywords(input: &[u8], p: &mut Pattern, cursor: &mut usize) -> Re b':' => break, _ if unimplemented_chars.contains(&b) => { return Err(Error::Unimplemented { - found_short_keyword: b.into(), + short_keyword: b.into(), }); } _ => { @@ -117,7 +117,7 @@ fn parse_long_keywords(input: &[u8], p: &mut Pattern, cursor: &mut usize) -> Res } _ => { return Err(Error::InvalidKeyword { - found_keyword: BString::from(keyword), + keyword: BString::from(keyword), }); } } From 2523f9606f0adedb20ac93cf4853298bcd996118 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 23 Jun 2022 20:27:39 +0800 Subject: [PATCH 041/248] refactor --- git-pathspec/src/parse.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index c67b16caaca..5d86f384c79 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -39,7 +39,7 @@ impl Pattern { let mut cursor = 0; if input.first() == Some(&b':') { cursor += 1; - parse_short_keywords(input, &mut p, &mut cursor)?; + p.signature |= parse_short_keywords(input, &mut cursor)?; if let Some(b'(') = input.get(cursor) { cursor += 1; parse_long_keywords(input, &mut p, &mut cursor)?; @@ -51,14 +51,15 @@ impl Pattern { } } -fn parse_short_keywords(input: &[u8], p: &mut Pattern, cursor: &mut usize) -> Result<(), Error> { +fn parse_short_keywords(input: &[u8], cursor: &mut usize) -> Result { let unimplemented_chars = vec![ b'"', b'#', b'%', b'&', b'\'', b',', b'-', b';', b'<', b'=', b'>', b'@', b'_', b'`', b'~', ]; + let mut signature = MagicSignature::empty(); while let Some(&b) = input.get(*cursor) { *cursor += 1; - p.signature |= match b { + signature |= match b { b'/' => MagicSignature::TOP, b'^' | b'!' => MagicSignature::EXCLUDE, b':' => break, @@ -74,7 +75,7 @@ fn parse_short_keywords(input: &[u8], p: &mut Pattern, cursor: &mut usize) -> Re } } - Ok(()) + Ok(signature) } fn parse_long_keywords(input: &[u8], p: &mut Pattern, cursor: &mut usize) -> Result<(), Error> { From 7f00b50070c7b976a030ec836d2660ff5b7b5f72 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 23 Jun 2022 20:48:41 +0800 Subject: [PATCH 042/248] refactor --- git-pathspec/src/parse.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index 5d86f384c79..d3d89e8404c 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -1,4 +1,4 @@ -use bstr::{BString, ByteSlice}; +use bstr::{BStr, BString, ByteSlice}; use compact_str::CompactStr; use crate::{MagicSignature, Pattern, SearchMode}; @@ -168,7 +168,7 @@ fn parse_attributes(input: &[u8]) -> Result _check_attr_value(&v.to_string().into())?, + git_attributes::State::Value(v) => _check_attr_value(v.as_str().into())?, _ => {} } } @@ -202,21 +202,21 @@ fn _unescape_attribute_value((name, state): (BString, git_attributes::State)) -> } } -fn _check_attr_value(value: &BString) -> Result<(), Error> { +fn _check_attr_value(value: &BStr) -> Result<(), Error> { // the only characters allowed in the PATHSPEC attribute value let is_allowed_char = |c: u8| c.is_ascii_alphanumeric() || c == b'-' || c == b'_' || c == b','; if !value.bytes().all(is_allowed_char) { // TODO: return correct error (invalid character in attribute value) return Err(Error::InvalidAttribute { - attribute: value.clone(), + attribute: value.to_owned(), }); } if value.ends_with(&[b'\\']) { // TODO: return correct error (escape char not allowed as last char) return Err(Error::InvalidAttribute { - attribute: value.clone(), + attribute: value.to_owned(), }); } From c22e57f8e4131655595da2b81662e23258bb85c8 Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Mon, 27 Jun 2022 10:42:23 +0200 Subject: [PATCH 043/248] escape attribute values in pathspec crate... ...before passing them to the git-attributes parser --- Cargo.lock | 1 - git-pathspec/Cargo.toml | 1 - git-pathspec/src/parse.rs | 106 +++++++++--------- .../fixtures/generate_pathspec_baseline.sh | 6 + .../generate_pathspec_baseline.tar.xz | 4 +- git-pathspec/tests/pathspec.rs | 54 +++++++-- 6 files changed, 106 insertions(+), 66 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 10e5188d4da..e415865a33c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1419,7 +1419,6 @@ version = "0.0.0" dependencies = [ "bitflags", "bstr", - "compact_str", "git-attributes", "git-glob", "git-testtools", diff --git a/git-pathspec/Cargo.toml b/git-pathspec/Cargo.toml index d9602240485..6b5c81cf7e6 100644 --- a/git-pathspec/Cargo.toml +++ b/git-pathspec/Cargo.toml @@ -19,7 +19,6 @@ git-attributes = { version = "^0.1.0", path = "../git-attributes" } bstr = { version = "0.2.13", default-features = false, features = ["std"]} bitflags = "1.3.2" thiserror = "1.0.26" -compact_str = "0.3.2" [dev-dependencies] git-testtools = { path = "../tests/tools" } diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index d3d89e8404c..0f866f5405e 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -1,5 +1,4 @@ use bstr::{BStr, BString, ByteSlice}; -use compact_str::CompactStr; use crate::{MagicSignature, Pattern, SearchMode}; @@ -15,6 +14,8 @@ pub enum Error { MissingClosingParenthesis { pathspec: BString }, #[error("Attribute has non-ascii characters or starts with '-': {:?}", attribute)] InvalidAttribute { attribute: BString }, + #[error("Invalid character in attribute value: {:?}", character)] + InvalidAttributeValue { character: char }, #[error("Attribute specification cannot be empty")] EmptyAttribute, #[error("'literal' and 'glob' keywords cannot be used together in the same pathspec")] @@ -152,11 +153,10 @@ fn parse_attributes(input: &[u8]) -> Result, _>>() .map_err(|e| match e { git_attributes::parse::Error::AttributeName { @@ -164,60 +164,64 @@ fn parse_attributes(input: &[u8]) -> Result Error::InvalidAttribute { attribute }, _ => unreachable!("expecting only 'Error::AttributeName' but got {}", e), - })?; - - for (_, state) in parsed_attrs.iter() { - match state { - git_attributes::State::Value(v) => _check_attr_value(v.as_str().into())?, - _ => {} - } - } - - Ok(parsed_attrs) + })?) } -fn _unescape_attribute_value((name, state): (BString, git_attributes::State)) -> (BString, git_attributes::State) { - match &state { - git_attributes::State::Value(v) if v.contains("\\") => { - let mut i = 0; - let v = BString::from(v.to_string()); - let mut new_v = CompactStr::default(); - loop { - if let Some(_) = v.get(i + 1) { - if v[i] == b'\\' { - i += 1; - new_v.push(v[i] as char); - } else { - new_v.push(v[i] as char); - } - } else { - new_v.push(v[i] as char); - break; - } - i += 1; +fn unescape_attribute_values(input: &BStr) -> Result { + Ok(input + .split(|&c| c == b' ') + .map(|attr| { + if attr.contains(&b'=') { + let mut s = attr.split(|&c| c == b'='); + let name = s.next().expect("name should be here"); + let value = s.next().expect("value should be here"); + + let value = value + .windows(2) + .filter_map(|window| match (window[0], window[1]) { + (b'\\', b'\\') => Some(&window[1..2]), + (b'\\', _) => { + if value.ends_with(window) { + Some(&window[1..2]) + } else { + None + } + } + (_, _) => { + if value.ends_with(window) { + Some(&window[0..2]) + } else { + Some(&window[0..1]) + } + } + }) + .flat_map(|c| c.to_owned()) + .collect::>(); + + check_attr_value(value.as_bstr())?; + + Ok([Vec::from(name), value].join(&b'=')) + } else { + Ok(Vec::from(attr)) } - (name, git_attributes::State::Value(new_v)) - } - _ => (name, state), - } + }) + .collect::, _>>()? + .join(&b' ') + .into()) } -fn _check_attr_value(value: &BStr) -> Result<(), Error> { - // the only characters allowed in the PATHSPEC attribute value - let is_allowed_char = |c: u8| c.is_ascii_alphanumeric() || c == b'-' || c == b'_' || c == b','; +fn check_attr_value(value: &BStr) -> Result<(), Error> { + let is_invalid_char = |&c: &u8| !c.is_ascii_alphanumeric() && c != b'-' && c != b'_' && c != b','; - if !value.bytes().all(is_allowed_char) { - // TODO: return correct error (invalid character in attribute value) - return Err(Error::InvalidAttribute { - attribute: value.to_owned(), - }); - } + // TODO: Do we actually need this case? + // if value.ends_with(b"\\") { + // return Err(Error::InvalidAttribute { + // attribute: value.to_owned(), + // }); + // } - if value.ends_with(&[b'\\']) { - // TODO: return correct error (escape char not allowed as last char) - return Err(Error::InvalidAttribute { - attribute: value.to_owned(), - }); + if let Some(c) = value.bytes().find(is_invalid_char) { + return Err(Error::InvalidAttributeValue { character: c as char }); } Ok(()) diff --git a/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh b/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh index cb6c0fffa2c..4d01d57f0a6 100644 --- a/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh +++ b/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh @@ -67,6 +67,9 @@ baseline ':(attr:someAttr)' baseline ':(attr:!someAttr)' baseline ':(attr:-someAttr)' baseline ':(attr:someAttr=value)' +baseline ':(attr:a=one b=)' +baseline ':(attr:a= b=two)' +baseline ':(attr:a=one b=two)' baseline ':(attr:someAttr anotherAttr)' # attributes_with_escape_chars_in_state_values @@ -108,7 +111,10 @@ baseline ':(attr:+invalidAttr)some/path' baseline ':(attr:validAttr +invalidAttr)some/path' baseline ':(attr:+invalidAttr,attr:valid)some/path' baseline ':(attr:inva\lid)some/path' + +# invalid_attribute_values baseline ':(attr:v=inva\\lid)some/path' +baseline ':(attr:v=invalid\\)some/path' baseline ':(attr:v=invalid\)some/path' baseline ':(attr:v=invalid\ )some/path' baseline ':(attr:v=invalid\#)some/path' diff --git a/git-pathspec/tests/fixtures/generated-archives/generate_pathspec_baseline.tar.xz b/git-pathspec/tests/fixtures/generated-archives/generate_pathspec_baseline.tar.xz index b2f006f7c20..1ec3c1a61d1 100644 --- a/git-pathspec/tests/fixtures/generated-archives/generate_pathspec_baseline.tar.xz +++ b/git-pathspec/tests/fixtures/generated-archives/generate_pathspec_baseline.tar.xz @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9ff4031c2e12d0a3a9f558904180e83a4656e983b76b4e6954a7185ea523d220 -size 652 +oid sha256:ed6126232a69861531cd182942e8136f44454f18461506532bef9c1f3ea4d3f4 +size 9428 diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index 1389428f5c2..90f07c048a8 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -151,6 +151,21 @@ mod parse { ":(attr:someAttr=value)", pat_with_attrs(vec![("someAttr", State::Value("value".into()))]), ), + ( + ":(attr:a=one b=)", + pat_with_attrs(vec![("a", State::Value("one".into())), ("b", State::Value("".into()))]), + ), + ( + ":(attr:a= b=two)", + pat_with_attrs(vec![("a", State::Value("".into())), ("b", State::Value("two".into()))]), + ), + ( + ":(attr:a=one b=two)", + pat_with_attrs(vec![ + ("a", State::Value("one".into())), + ("b", State::Value("two".into())), + ]), + ), ( ":(attr:someAttr anotherAttr)", pat_with_attrs(vec![("someAttr", State::Set), ("anotherAttr", State::Set)]), @@ -163,14 +178,14 @@ mod parse { #[test] fn attributes_with_escape_chars_in_state_values() { let inputs = vec![ - // ( - // r":(attr:v=one\-)", - // pat_with_attrs(vec![("v", State::Value(r"one-".into()))]), - // ), - // ( - // r":(attr:v=one\_)", - // pat_with_attrs(vec![("v", State::Value(r"one_".into()))]), - // ), + ( + r":(attr:v=one\-)", + pat_with_attrs(vec![("v", State::Value(r"one-".into()))]), + ), + ( + r":(attr:v=one\_)", + pat_with_attrs(vec![("v", State::Value(r"one_".into()))]), + ), ( r":(attr:v=one\,)", pat_with_attrs(vec![("v", State::Value(r"one,".into()))]), @@ -261,9 +276,26 @@ mod parse { ":(attr:validAttr +invalidAttr)some/path", ":(attr:+invalidAttr,attr:valid)some/path", r":(attr:inva\lid)some/path", - r":(attr:inva\lid)some/path", - // TODO: Fix error values + ]; + + for input in inputs { + assert!( + !check_against_baseline(input), + "This pathspec is valid in git: {}", + input + ); + + let output = git_pathspec::parse(input.as_bytes()); + assert!(output.is_err(), "This pathspec did not produce an error {}", input); + assert!(matches!(output.unwrap_err(), Error::InvalidAttribute { .. })); + } + } + + #[test] + fn invalid_attribute_values() { + let inputs = vec![ r":(attr:v=inva\\lid)some/path", + r":(attr:v=invalid\\)some/path", r":(attr:v=invalid\)some/path", r":(attr:v=invalid\ )some/path", r":(attr:v=invalid\#)some/path", @@ -279,7 +311,7 @@ mod parse { let output = git_pathspec::parse(input.as_bytes()); assert!(output.is_err(), "This pathspec did not produce an error {}", input); - assert!(matches!(output.unwrap_err(), Error::InvalidAttribute { .. })); + assert!(matches!(output.unwrap_err(), Error::InvalidAttributeValue { .. })); } } From 1ad98e82f66c5f5eacb15f8ae38d8ccb1bc94e9e Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Mon, 27 Jun 2022 12:23:53 +0200 Subject: [PATCH 044/248] refactor --- git-pathspec/src/parse.rs | 43 +++++++------------ .../fixtures/generate_pathspec_baseline.sh | 1 + .../generate_pathspec_baseline.tar.xz | 4 +- git-pathspec/tests/pathspec.rs | 10 ++++- 4 files changed, 27 insertions(+), 31 deletions(-) diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index 0f866f5405e..5df77baa762 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -1,27 +1,26 @@ -use bstr::{BStr, BString, ByteSlice}; - use crate::{MagicSignature, Pattern, SearchMode}; +use bstr::{BStr, BString, ByteSlice}; #[derive(thiserror::Error, Debug)] pub enum Error { #[error("Empty string is not a valid pathspec")] EmptyString, - #[error("Found {:?}, which is not a valid keyword", keyword)] + #[error("Found {:?} in signature, which is not a valid keyword", keyword)] InvalidKeyword { keyword: BString }, - #[error("Unimplemented pathspec magic {:?}", short_keyword)] + #[error("Unimplemented short keyword: {:?}", short_keyword)] Unimplemented { short_keyword: char }, - #[error("Missing ')' at the end of pathspec magic in {:?}", pathspec)] - MissingClosingParenthesis { pathspec: BString }, + #[error("Missing ')' at the end of pathspec signature")] + MissingClosingParenthesis, #[error("Attribute has non-ascii characters or starts with '-': {:?}", attribute)] InvalidAttribute { attribute: BString }, #[error("Invalid character in attribute value: {:?}", character)] InvalidAttributeValue { character: char }, #[error("Attribute specification cannot be empty")] EmptyAttribute, - #[error("'literal' and 'glob' keywords cannot be used together in the same pathspec")] - IncompatibleSearchModes, #[error("Only one attribute specification is allowed in the same pathspec")] MultipleAttributeSpecifications, + #[error("'literal' and 'glob' keywords cannot be used together in the same pathspec")] + IncompatibleSearchModes, } impl Pattern { @@ -80,9 +79,7 @@ fn parse_short_keywords(input: &[u8], cursor: &mut usize) -> Result Result<(), Error> { - let end = input.find(")").ok_or(Error::MissingClosingParenthesis { - pathspec: BString::from(input), - })?; + let end = input.find(")").ok_or(Error::MissingClosingParenthesis)?; let input = &input[*cursor..end]; *cursor = end + 1; @@ -132,19 +129,16 @@ fn split_on_non_escaped_char(input: &[u8], split_char: u8) -> Vec<&[u8]> { let mut keywords = Vec::new(); let mut i = 0; let mut last = 0; - loop { - if let Some(&b) = input.get(i + 1) { - if b == split_char && input[i] != b'\\' { - i += 1; - keywords.push(&input[last..i]); - last = i + 1; - } + for window in input.windows(2) { + if window[0] != b'\\' && window[1] == split_char { + i += 1; + keywords.push(&input[last..i]); + last = i + 1; } else { - keywords.push(&input[last..]); - break; + i += 1; } - i += 1; } + keywords.push(&input[last..]); keywords } @@ -213,13 +207,6 @@ fn unescape_attribute_values(input: &BStr) -> Result { fn check_attr_value(value: &BStr) -> Result<(), Error> { let is_invalid_char = |&c: &u8| !c.is_ascii_alphanumeric() && c != b'-' && c != b'_' && c != b','; - // TODO: Do we actually need this case? - // if value.ends_with(b"\\") { - // return Err(Error::InvalidAttribute { - // attribute: value.to_owned(), - // }); - // } - if let Some(c) = value.bytes().find(is_invalid_char) { return Err(Error::InvalidAttributeValue { character: c as char }); } diff --git a/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh b/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh index 4d01d57f0a6..5144d673f1d 100644 --- a/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh +++ b/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh @@ -70,6 +70,7 @@ baseline ':(attr:someAttr=value)' baseline ':(attr:a=one b=)' baseline ':(attr:a= b=two)' baseline ':(attr:a=one b=two)' +baseline ':(attr:a=one b=two)' baseline ':(attr:someAttr anotherAttr)' # attributes_with_escape_chars_in_state_values diff --git a/git-pathspec/tests/fixtures/generated-archives/generate_pathspec_baseline.tar.xz b/git-pathspec/tests/fixtures/generated-archives/generate_pathspec_baseline.tar.xz index 1ec3c1a61d1..936caf99671 100644 --- a/git-pathspec/tests/fixtures/generated-archives/generate_pathspec_baseline.tar.xz +++ b/git-pathspec/tests/fixtures/generated-archives/generate_pathspec_baseline.tar.xz @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ed6126232a69861531cd182942e8136f44454f18461506532bef9c1f3ea4d3f4 -size 9428 +oid sha256:4a3e22b67bad9a9afcbf1a0adbca5253ed15cde36be9c295b9e6f3db8805999f +size 9432 diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index 90f07c048a8..e07c9579840 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -166,6 +166,13 @@ mod parse { ("b", State::Value("two".into())), ]), ), + ( + ":(attr:a=one b=two)", + pat_with_attrs(vec![ + ("a", State::Value("one".into())), + ("b", State::Value("two".into())), + ]), + ), ( ":(attr:someAttr anotherAttr)", pat_with_attrs(vec![("someAttr", State::Set), ("anotherAttr", State::Set)]), @@ -384,7 +391,8 @@ mod parse { input ); - let pattern = git_pathspec::parse(input.as_bytes()).expect("parsing should not fail"); + let pattern = git_pathspec::parse(input.as_bytes()) + .expect(&format!("parsing should not fail wtih pathspec {}", input)); assert_eq!(pattern, expected, "while checking input: \"{}\"", input); }); } From 7bb408e631138854a6dff85ce356da96f61367de Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Thu, 30 Jun 2022 10:15:52 +0200 Subject: [PATCH 045/248] protected attribute name via "AttributeName" type --- git-attributes/src/lib.rs | 8 +++++ git-attributes/src/match_group.rs | 14 ++++---- git-attributes/src/parse/attribute.rs | 28 ++++++++++++--- git-attributes/tests/parse/attribute.rs | 4 ++- git-pathspec/src/lib.rs | 3 +- git-pathspec/src/parse.rs | 8 ++--- git-pathspec/tests/pathspec.rs | 45 +++++++++++++++++++------ 7 files changed, 81 insertions(+), 29 deletions(-) diff --git a/git-attributes/src/lib.rs b/git-attributes/src/lib.rs index 691ed4a5463..c1bcf0111df 100644 --- a/git-attributes/src/lib.rs +++ b/git-attributes/src/lib.rs @@ -42,6 +42,14 @@ pub enum State { Unspecified, } +/// Holds and owns data that represent one validated attribute +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +pub struct AttributeName(BString, State); + +/// Holds validated attribute data as a reference +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd)] +pub struct AttributeNameRef<'a>(&'a BStr, StateRef<'a>); + /// Name an attribute and describe it's assigned state. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] diff --git a/git-attributes/src/match_group.rs b/git-attributes/src/match_group.rs index de3b6d56880..257ff867f30 100644 --- a/git-attributes/src/match_group.rs +++ b/git-attributes/src/match_group.rs @@ -1,13 +1,11 @@ +use crate::{Assignment, MatchGroup, PatternList, PatternMapping, State, StateRef}; +use bstr::{BStr, BString, ByteSlice, ByteVec}; use std::{ ffi::OsString, io::Read, path::{Path, PathBuf}, }; -use bstr::{BStr, BString, ByteSlice, ByteVec}; - -use crate::{Assignment, MatchGroup, PatternList, PatternMapping, State, StateRef}; - impl<'a> From> for State { fn from(s: StateRef<'a>) -> Self { match s { @@ -20,13 +18,13 @@ impl<'a> From> for State { } fn attrs_to_assignments<'a>( - attrs: impl Iterator), crate::parse::Error>>, + attrs: impl Iterator, crate::parse::Error>>, ) -> Result, crate::parse::Error> { attrs .map(|res| { - res.map(|(name, state)| Assignment { - name: name.to_str().expect("no illformed unicode").into(), - state: state.into(), + res.map(|attr| Assignment { + name: attr.0.to_str().expect("no illformed unicode").into(), + state: attr.1.into(), }) }) .collect() diff --git a/git-attributes/src/parse/attribute.rs b/git-attributes/src/parse/attribute.rs index 064e78a4a17..09a3525325a 100644 --- a/git-attributes/src/parse/attribute.rs +++ b/git-attributes/src/parse/attribute.rs @@ -1,6 +1,6 @@ -use std::borrow::Cow; - +use crate::{AttributeName, AttributeNameRef, State, StateRef}; use bstr::{BStr, BString, ByteSlice}; +use std::borrow::Cow; #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] @@ -56,7 +56,7 @@ impl<'a> Iter<'a> { } } - fn parse_attr(&self, attr: &'a [u8]) -> Result<(&'a BStr, crate::StateRef<'a>), Error> { + fn parse_attr(&self, attr: &'a [u8]) -> Result, Error> { let mut tokens = attr.splitn(2, |b| *b == b'='); let attr = tokens.next().expect("attr itself").as_bstr(); let possibly_value = tokens.next(); @@ -72,7 +72,7 @@ impl<'a> Iter<'a> { .unwrap_or(crate::StateRef::Set), ) }; - Ok((check_attr(attr, self.line_no)?, state)) + Ok(AttributeNameRef(check_attr(attr, self.line_no)?, state)) } } @@ -95,7 +95,7 @@ fn check_attr(attr: &BStr, line_number: usize) -> Result<&BStr, Error> { } impl<'a> Iterator for Iter<'a> { - type Item = Result<(&'a BStr, crate::StateRef<'a>), Error>; + type Item = Result, Error>; fn next(&mut self) -> Option { let attr = self.attrs.next().filter(|a| !a.is_empty())?; @@ -182,3 +182,21 @@ fn parse_line(line: &BStr, line_number: usize) -> Option, } const BLANKS: &[u8] = b" \t\r"; + +impl<'a> From> for AttributeName { + fn from(v: AttributeNameRef<'a>) -> Self { + AttributeName(v.0.to_owned(), v.1.into()) + } +} + +impl<'a> From> for (&'a BStr, StateRef<'a>) { + fn from(v: AttributeNameRef<'a>) -> Self { + (v.0, v.1) + } +} + +impl From for (BString, State) { + fn from(v: AttributeName) -> Self { + (v.0, v.1) + } +} diff --git a/git-attributes/tests/parse/attribute.rs b/git-attributes/tests/parse/attribute.rs index c4306c70cd0..2cd127393f7 100644 --- a/git-attributes/tests/parse/attribute.rs +++ b/git-attributes/tests/parse/attribute.rs @@ -298,6 +298,8 @@ fn expand( input: Result<(parse::Kind, parse::Iter<'_>, usize), parse::Error>, ) -> Result, parse::Error> { let (pattern, attrs, line_no) = input?; - let attrs = attrs.collect::, _>>()?; + let attrs = attrs + .map(|r| r.map(|attr| attr.into())) + .collect::, _>>()?; Ok((pattern, attrs, line_no)) } diff --git a/git-pathspec/src/lib.rs b/git-pathspec/src/lib.rs index bbdc7665362..c60d9025cd9 100644 --- a/git-pathspec/src/lib.rs +++ b/git-pathspec/src/lib.rs @@ -2,6 +2,7 @@ use bitflags::bitflags; use bstr::BString; +use git_attributes::AttributeName; pub mod parse; @@ -15,7 +16,7 @@ pub struct Pattern { /// The search mode of the pathspec. pub search_mode: SearchMode, /// All attributes that were included in the `ATTR` part of the pathspec, if present. - pub attributes: Vec<(BString, git_attributes::State)>, + pub attributes: Vec, } bitflags! { diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index 5df77baa762..7d8124c5ced 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -142,15 +142,15 @@ fn split_on_non_escaped_char(input: &[u8], split_char: u8) -> Vec<&[u8]> { keywords } -fn parse_attributes(input: &[u8]) -> Result, Error> { +fn parse_attributes(input: &[u8]) -> Result, Error> { if input.is_empty() { return Err(Error::EmptyAttribute); } let unescaped = unescape_attribute_values(input.into())?; - Ok(git_attributes::parse::Iter::new(unescaped.as_bstr(), 0) - .map(|res| res.map(|(name, state)| (name.into(), state.into()))) + git_attributes::parse::Iter::new(unescaped.as_bstr(), 0) + .map(|res| res.map(|v| v.into())) .collect::, _>>() .map_err(|e| match e { git_attributes::parse::Error::AttributeName { @@ -158,7 +158,7 @@ fn parse_attributes(input: &[u8]) -> Result Error::InvalidAttribute { attribute }, _ => unreachable!("expecting only 'Error::AttributeName' but got {}", e), - })?) + }) } fn unescape_attribute_values(input: &BStr) -> Result { diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index e07c9579840..2bf8b388f27 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -7,6 +7,25 @@ mod parse { use once_cell::sync::Lazy; use std::collections::HashMap; + #[derive(Debug, Clone, PartialEq, Eq)] + struct PatternForTesting { + path: BString, + signature: MagicSignature, + search_mode: SearchMode, + attributes: Vec<(BString, State)>, + } + + impl From for PatternForTesting { + fn from(p: Pattern) -> Self { + PatternForTesting { + path: p.path, + signature: p.signature, + search_mode: p.search_mode, + attributes: p.attributes.into_iter().map(|v| v.into()).collect(), + } + } + } + static BASELINE: Lazy> = Lazy::new(|| { let base = git_testtools::scripted_fixture_repo_read_only("generate_pathspec_baseline.sh").unwrap(); @@ -383,7 +402,7 @@ mod parse { } } - fn check_valid_inputs(inputs: Vec<(&str, Pattern)>) { + fn check_valid_inputs(inputs: Vec<(&str, PatternForTesting)>) { inputs.into_iter().for_each(|(input, expected)| { assert!( check_against_baseline(input), @@ -391,34 +410,40 @@ mod parse { input ); - let pattern = git_pathspec::parse(input.as_bytes()) - .expect(&format!("parsing should not fail wtih pathspec {}", input)); + let pattern: PatternForTesting = git_pathspec::parse(input.as_bytes()) + .expect(&format!("parsing should not fail wtih pathspec {}", input)) + .into(); assert_eq!(pattern, expected, "while checking input: \"{}\"", input); }); } - fn pat_with_path(path: &str) -> Pattern { + fn pat_with_path(path: &str) -> PatternForTesting { pat_with_path_and_sig(path, MagicSignature::empty()) } - fn pat_with_path_and_sig(path: &str, signature: MagicSignature) -> Pattern { + fn pat_with_path_and_sig(path: &str, signature: MagicSignature) -> PatternForTesting { pat(path, signature, SearchMode::ShellGlob, vec![]) } - fn pat_with_sig(signature: MagicSignature) -> Pattern { + fn pat_with_sig(signature: MagicSignature) -> PatternForTesting { pat("", signature, SearchMode::ShellGlob, vec![]) } - fn pat_with_attrs(attrs: Vec<(&str, State)>) -> Pattern { + fn pat_with_attrs(attrs: Vec<(&str, State)>) -> PatternForTesting { pat("", MagicSignature::empty(), SearchMode::ShellGlob, attrs) } - fn pat_with_search_mode(search_mode: SearchMode) -> Pattern { + fn pat_with_search_mode(search_mode: SearchMode) -> PatternForTesting { pat("", MagicSignature::empty(), search_mode, vec![]) } - fn pat(path: &str, signature: MagicSignature, search_mode: SearchMode, attributes: Vec<(&str, State)>) -> Pattern { - Pattern { + fn pat( + path: &str, + signature: MagicSignature, + search_mode: SearchMode, + attributes: Vec<(&str, State)>, + ) -> PatternForTesting { + PatternForTesting { path: path.into(), signature, search_mode, From da84b675d3e825d2f815957fbed9928a0480ea4a Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Thu, 30 Jun 2022 10:32:34 +0200 Subject: [PATCH 046/248] quickerror to thiserror --- Cargo.lock | 2 +- git-attributes/Cargo.toml | 2 +- git-attributes/src/parse/attribute.rs | 30 +++++++++------------------ 3 files changed, 12 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e415865a33c..5ddc03fbd15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1106,8 +1106,8 @@ dependencies = [ "git-path", "git-quote", "git-testtools", - "quick-error", "serde", + "thiserror", "unicode-bom", ] diff --git a/git-attributes/Cargo.toml b/git-attributes/Cargo.toml index 2209ccb468a..1789821df54 100644 --- a/git-attributes/Cargo.toml +++ b/git-attributes/Cargo.toml @@ -25,7 +25,7 @@ git-glob = { version = "^0.3.0", path = "../git-glob" } bstr = { version = "0.2.13", default-features = false, features = ["std"]} unicode-bom = "1.1.4" -quick-error = "2.0.0" +thiserror = "1.0.26" serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]} compact_str = "0.3.2" diff --git a/git-attributes/src/parse/attribute.rs b/git-attributes/src/parse/attribute.rs index 09a3525325a..1d1e454178c 100644 --- a/git-attributes/src/parse/attribute.rs +++ b/git-attributes/src/parse/attribute.rs @@ -14,26 +14,16 @@ pub enum Kind { mod error { use bstr::BString; - use quick_error::quick_error; - - quick_error! { - #[derive(Debug)] - pub enum Error { - PatternNegation { line_number: usize, line: BString } { - display("Line {} has a negative pattern, for literal characters use \\!: {}", line_number, line) - } - AttributeName { line_number: usize, attribute: BString } { - display("Attribute in line {} has non-ascii characters or starts with '-': {}", line_number, attribute) - } - MacroName { line_number: usize, macro_name: BString } { - display("Macro in line {} has non-ascii characters or starts with '-': {}", line_number, macro_name) - } - Unquote(err: git_quote::ansi_c::undo::Error) { - display("Could not unquote attributes line") - from() - source(err) - } - } + #[derive(thiserror::Error, Debug)] + pub enum Error { + #[error("Line {} has a negative pattern, for literal characters use \\!: {}", line_number, line)] + PatternNegation { line_number: usize, line: BString }, + #[error("Attribute in line {} has non-ascii characters or starts with '-': {}", line_number, attribute)] + AttributeName { line_number: usize, attribute: BString }, + #[error("Macro in line {} has non-ascii characters or starts with '-': {}", line_number, macro_name)] + MacroName { line_number: usize, macro_name: BString }, + #[error("Could not unquote attributes line")] + Unquote(#[from] git_quote::ansi_c::undo::Error) } } pub use error::Error; From 852bcc316382fce5f5749942ecb43a73738ffe8f Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Thu, 30 Jun 2022 11:52:53 +0200 Subject: [PATCH 047/248] refactor --- git-pathspec/tests/pathspec.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index 2bf8b388f27..44a34c89222 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -411,7 +411,7 @@ mod parse { ); let pattern: PatternForTesting = git_pathspec::parse(input.as_bytes()) - .expect(&format!("parsing should not fail wtih pathspec {}", input)) + .unwrap_or_else(|_| panic!("parsing should not fail with pathspec {}", input)) .into(); assert_eq!(pattern, expected, "while checking input: \"{}\"", input); }); @@ -458,7 +458,7 @@ mod parse { let key: &BStr = pathspec.into(); let base = BASELINE .get(key) - .expect(&format!("missing baseline for pathspec: {:?}", pathspec)); + .unwrap_or_else(|| panic!("missing baseline for pathspec: {:?}", pathspec)); *base == 0 } } From 3b2bab89172b86068bda9704bc9d69690bcfb2ba Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 30 Jun 2022 20:16:19 +0800 Subject: [PATCH 048/248] refactor --- git-attributes/src/parse/attribute.rs | 20 ++++++++++++++++---- git-pathspec/src/parse.rs | 11 +++++------ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/git-attributes/src/parse/attribute.rs b/git-attributes/src/parse/attribute.rs index 1d1e454178c..eb639f06d1e 100644 --- a/git-attributes/src/parse/attribute.rs +++ b/git-attributes/src/parse/attribute.rs @@ -16,14 +16,26 @@ mod error { use bstr::BString; #[derive(thiserror::Error, Debug)] pub enum Error { - #[error("Line {} has a negative pattern, for literal characters use \\!: {}", line_number, line)] + #[error( + "Line {} has a negative pattern, for literal characters use \\!: {}", + line_number, + line + )] PatternNegation { line_number: usize, line: BString }, - #[error("Attribute in line {} has non-ascii characters or starts with '-': {}", line_number, attribute)] + #[error( + "Attribute in line {} has non-ascii characters or starts with '-': {}", + line_number, + attribute + )] AttributeName { line_number: usize, attribute: BString }, - #[error("Macro in line {} has non-ascii characters or starts with '-': {}", line_number, macro_name)] + #[error( + "Macro in line {} has non-ascii characters or starts with '-': {}", + line_number, + macro_name + )] MacroName { line_number: usize, macro_name: BString }, #[error("Could not unquote attributes line")] - Unquote(#[from] git_quote::ansi_c::undo::Error) + Unquote(#[from] git_quote::ansi_c::undo::Error), } } pub use error::Error; diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index 7d8124c5ced..d04dbbb8b4a 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -162,7 +162,7 @@ fn parse_attributes(input: &[u8]) -> Result, } fn unescape_attribute_values(input: &BStr) -> Result { - Ok(input + let unescaped_tokens = input .split(|&c| c == b' ') .map(|attr| { if attr.contains(&b'=') { @@ -199,15 +199,14 @@ fn unescape_attribute_values(input: &BStr) -> Result { Ok(Vec::from(attr)) } }) - .collect::, _>>()? - .join(&b' ') - .into()) + .collect::, _>>()?; + Ok(unescaped_tokens.join(&b' ').into()) } fn check_attr_value(value: &BStr) -> Result<(), Error> { - let is_invalid_char = |&c: &u8| !c.is_ascii_alphanumeric() && c != b'-' && c != b'_' && c != b','; + let is_valid_char = |&c: &u8| c.is_ascii_alphanumeric() || b",-_".contains(&c); - if let Some(c) = value.bytes().find(is_invalid_char) { + if let Some(c) = value.bytes().find(|c| !is_valid_char(c)) { return Err(Error::InvalidAttributeValue { character: c as char }); } From 9945ceb0a99c1343cb6e652e44900b36d3786e22 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 30 Jun 2022 20:47:51 +0800 Subject: [PATCH 049/248] refactor - name changes - sketch of `name::Error` --- git-attributes/src/lib.rs | 14 +++++++-- git-attributes/src/match_group.rs | 2 +- git-attributes/src/parse/attribute.rs | 44 ++++++++++----------------- git-pathspec/src/lib.rs | 4 +-- git-pathspec/src/parse.rs | 2 +- 5 files changed, 32 insertions(+), 34 deletions(-) diff --git a/git-attributes/src/lib.rs b/git-attributes/src/lib.rs index c1bcf0111df..0b3f8f57b1b 100644 --- a/git-attributes/src/lib.rs +++ b/git-attributes/src/lib.rs @@ -44,11 +44,11 @@ pub enum State { /// Holds and owns data that represent one validated attribute #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] -pub struct AttributeName(BString, State); +pub struct Name(BString, State); /// Holds validated attribute data as a reference #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd)] -pub struct AttributeNameRef<'a>(&'a BStr, StateRef<'a>); +pub struct NameRef<'a>(&'a BStr, StateRef<'a>); /// Name an attribute and describe it's assigned state. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] @@ -100,6 +100,16 @@ pub struct PatternMapping { pub sequence_number: usize, } +mod name { + use bstr::BString; + + #[derive(Debug, thiserror::Error)] + #[error("Attribute has non-ascii characters or starts with '-': {attribute}")] + pub struct Error { + pub attribute: BString, + } +} + mod match_group; pub use match_group::{Attributes, Ignore, Match, Pattern}; diff --git a/git-attributes/src/match_group.rs b/git-attributes/src/match_group.rs index 257ff867f30..2f445f021f1 100644 --- a/git-attributes/src/match_group.rs +++ b/git-attributes/src/match_group.rs @@ -18,7 +18,7 @@ impl<'a> From> for State { } fn attrs_to_assignments<'a>( - attrs: impl Iterator, crate::parse::Error>>, + attrs: impl Iterator, crate::parse::Error>>, ) -> Result, crate::parse::Error> { attrs .map(|res| { diff --git a/git-attributes/src/parse/attribute.rs b/git-attributes/src/parse/attribute.rs index eb639f06d1e..f5df007ca76 100644 --- a/git-attributes/src/parse/attribute.rs +++ b/git-attributes/src/parse/attribute.rs @@ -1,4 +1,4 @@ -use crate::{AttributeName, AttributeNameRef, State, StateRef}; +use crate::{Name, NameRef, State, StateRef}; use bstr::{BStr, BString, ByteSlice}; use std::borrow::Cow; @@ -16,23 +16,11 @@ mod error { use bstr::BString; #[derive(thiserror::Error, Debug)] pub enum Error { - #[error( - "Line {} has a negative pattern, for literal characters use \\!: {}", - line_number, - line - )] + #[error("Line {line_number} has a negative pattern, for literal characters use \\!: {line}")] PatternNegation { line_number: usize, line: BString }, - #[error( - "Attribute in line {} has non-ascii characters or starts with '-': {}", - line_number, - attribute - )] + #[error("Attribute in line {line_number} has non-ascii characters or starts with '-': {attribute}")] AttributeName { line_number: usize, attribute: BString }, - #[error( - "Macro in line {} has non-ascii characters or starts with '-': {}", - line_number, - macro_name - )] + #[error("Macro in line {line_number} has non-ascii characters or starts with '-': {macro_name}")] MacroName { line_number: usize, macro_name: BString }, #[error("Could not unquote attributes line")] Unquote(#[from] git_quote::ansi_c::undo::Error), @@ -58,14 +46,14 @@ impl<'a> Iter<'a> { } } - fn parse_attr(&self, attr: &'a [u8]) -> Result, Error> { + fn parse_attr(&self, attr: &'a [u8]) -> Result, Error> { let mut tokens = attr.splitn(2, |b| *b == b'='); let attr = tokens.next().expect("attr itself").as_bstr(); let possibly_value = tokens.next(); let (attr, state) = if attr.first() == Some(&b'-') { - (&attr[1..], crate::StateRef::Unset) + (&attr[1..], StateRef::Unset) } else if attr.first() == Some(&b'!') { - (&attr[1..], crate::StateRef::Unspecified) + (&attr[1..], StateRef::Unspecified) } else { ( attr, @@ -74,7 +62,7 @@ impl<'a> Iter<'a> { .unwrap_or(crate::StateRef::Set), ) }; - Ok(AttributeNameRef(check_attr(attr, self.line_no)?, state)) + Ok(NameRef(check_attr(attr, self.line_no)?, state)) } } @@ -97,7 +85,7 @@ fn check_attr(attr: &BStr, line_number: usize) -> Result<&BStr, Error> { } impl<'a> Iterator for Iter<'a> { - type Item = Result, Error>; + type Item = Result, Error>; fn next(&mut self) -> Option { let attr = self.attrs.next().filter(|a| !a.is_empty())?; @@ -185,20 +173,20 @@ fn parse_line(line: &BStr, line_number: usize) -> Option, const BLANKS: &[u8] = b" \t\r"; -impl<'a> From> for AttributeName { - fn from(v: AttributeNameRef<'a>) -> Self { - AttributeName(v.0.to_owned(), v.1.into()) +impl<'a> From> for Name { + fn from(v: NameRef<'a>) -> Self { + Name(v.0.to_owned(), v.1.into()) } } -impl<'a> From> for (&'a BStr, StateRef<'a>) { - fn from(v: AttributeNameRef<'a>) -> Self { +impl<'a> From> for (&'a BStr, StateRef<'a>) { + fn from(v: NameRef<'a>) -> Self { (v.0, v.1) } } -impl From for (BString, State) { - fn from(v: AttributeName) -> Self { +impl From for (BString, State) { + fn from(v: Name) -> Self { (v.0, v.1) } } diff --git a/git-pathspec/src/lib.rs b/git-pathspec/src/lib.rs index c60d9025cd9..3a81b0700c6 100644 --- a/git-pathspec/src/lib.rs +++ b/git-pathspec/src/lib.rs @@ -2,7 +2,7 @@ use bitflags::bitflags; use bstr::BString; -use git_attributes::AttributeName; +use git_attributes::Name; pub mod parse; @@ -16,7 +16,7 @@ pub struct Pattern { /// The search mode of the pathspec. pub search_mode: SearchMode, /// All attributes that were included in the `ATTR` part of the pathspec, if present. - pub attributes: Vec, + pub attributes: Vec, } bitflags! { diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index d04dbbb8b4a..dc35dc8d381 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -142,7 +142,7 @@ fn split_on_non_escaped_char(input: &[u8], split_char: u8) -> Vec<&[u8]> { keywords } -fn parse_attributes(input: &[u8]) -> Result, Error> { +fn parse_attributes(input: &[u8]) -> Result, Error> { if input.is_empty() { return Err(Error::EmptyAttribute); } From 1f89646fc359f94ee001f5ed01623e5af7934a93 Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Mon, 4 Jul 2022 13:51:19 +0200 Subject: [PATCH 050/248] improved attribute value unescaping --- git-pathspec/src/parse.rs | 83 ++++++++----------- .../fixtures/generate_pathspec_baseline.sh | 4 +- .../generate_pathspec_baseline.tar.xz | 2 +- git-pathspec/tests/pathspec.rs | 26 +++++- 4 files changed, 63 insertions(+), 52 deletions(-) diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index dc35dc8d381..007d020c6ec 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -1,5 +1,6 @@ use crate::{MagicSignature, Pattern, SearchMode}; -use bstr::{BStr, BString, ByteSlice}; +use bstr::{BStr, BString, ByteSlice, ByteVec}; +use std::borrow::Cow; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -15,6 +16,8 @@ pub enum Error { InvalidAttribute { attribute: BString }, #[error("Invalid character in attribute value: {:?}", character)] InvalidAttributeValue { character: char }, + #[error("Escape character '\\' is not allowed as the last character in an attribute value")] + TrailingEscapeCharacter, #[error("Attribute specification cannot be empty")] EmptyAttribute, #[error("Only one attribute specification is allowed in the same pathspec")] @@ -161,54 +164,38 @@ fn parse_attributes(input: &[u8]) -> Result, Error> { }) } -fn unescape_attribute_values(input: &BStr) -> Result { - let unescaped_tokens = input - .split(|&c| c == b' ') - .map(|attr| { - if attr.contains(&b'=') { - let mut s = attr.split(|&c| c == b'='); - let name = s.next().expect("name should be here"); - let value = s.next().expect("value should be here"); - - let value = value - .windows(2) - .filter_map(|window| match (window[0], window[1]) { - (b'\\', b'\\') => Some(&window[1..2]), - (b'\\', _) => { - if value.ends_with(window) { - Some(&window[1..2]) - } else { - None - } - } - (_, _) => { - if value.ends_with(window) { - Some(&window[0..2]) - } else { - Some(&window[0..1]) - } - } - }) - .flat_map(|c| c.to_owned()) - .collect::>(); - - check_attr_value(value.as_bstr())?; - - Ok([Vec::from(name), value].join(&b'=')) - } else { - Ok(Vec::from(attr)) - } - }) - .collect::, _>>()?; - Ok(unescaped_tokens.join(&b' ').into()) -} - -fn check_attr_value(value: &BStr) -> Result<(), Error> { - let is_valid_char = |&c: &u8| c.is_ascii_alphanumeric() || b",-_".contains(&c); +fn unescape_attribute_values(input: &BStr) -> Result, Error> { + if !input.contains(&b'=') { + return Ok(Cow::Borrowed(input)); + } - if let Some(c) = value.bytes().find(|c| !is_valid_char(c)) { - return Err(Error::InvalidAttributeValue { character: c as char }); + let mut ret = BString::from(Vec::with_capacity(input.len())); + + for attr in input.split(|&c| c == b' ') { + if let Some(i) = attr.find_byte(b'=') { + ret.push_str(&attr[0..=i]); + let mut i = i + 1; + while i < attr.len() { + if attr[i] == b'\\' { + i += 1; + if i >= attr.len() { + return Err(Error::TrailingEscapeCharacter); + } + } + if attr[i].is_ascii_alphanumeric() || b",-_".contains(&attr[i]) { + ret.push(attr[i]); + i += 1 + } else { + return Err(Error::InvalidAttributeValue { + character: attr[i] as char, + }); + } + } + } else { + ret.push_str(attr); + } + ret.push(b' '); } - Ok(()) + Ok(Cow::Owned(ret)) } diff --git a/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh b/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh index 5144d673f1d..a74bbd6c910 100644 --- a/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh +++ b/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh @@ -116,9 +116,11 @@ baseline ':(attr:inva\lid)some/path' # invalid_attribute_values baseline ':(attr:v=inva\\lid)some/path' baseline ':(attr:v=invalid\\)some/path' +baseline ':(attr:v=invalid\#)some/path' + +# escape_character_at_end_of_attribute_value baseline ':(attr:v=invalid\)some/path' baseline ':(attr:v=invalid\ )some/path' -baseline ':(attr:v=invalid\#)some/path' baseline ':(attr:v=invalid\ valid)some/path' # empty_attribute_specification diff --git a/git-pathspec/tests/fixtures/generated-archives/generate_pathspec_baseline.tar.xz b/git-pathspec/tests/fixtures/generated-archives/generate_pathspec_baseline.tar.xz index 936caf99671..47e32a273e2 100644 --- a/git-pathspec/tests/fixtures/generated-archives/generate_pathspec_baseline.tar.xz +++ b/git-pathspec/tests/fixtures/generated-archives/generate_pathspec_baseline.tar.xz @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4a3e22b67bad9a9afcbf1a0adbca5253ed15cde36be9c295b9e6f3db8805999f +oid sha256:72f905eac0db4bc51a80f15e297dff04124b5c080f24224b9b15acac08f4e8fa size 9432 diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index 44a34c89222..ff9e004ec93 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -322,9 +322,31 @@ mod parse { let inputs = vec![ r":(attr:v=inva\\lid)some/path", r":(attr:v=invalid\\)some/path", + r":(attr:v=invalid\#)some/path", + ]; + + for input in inputs { + assert!( + !check_against_baseline(input), + "This pathspec is valid in git: {}", + input + ); + + let output = git_pathspec::parse(input.as_bytes()); + assert!(output.is_err(), "This pathspec did not produce an error {}", input); + assert!( + matches!(output.unwrap_err(), Error::InvalidAttributeValue { .. }), + "Errors did not match for pathspec: {}", + input + ); + } + } + + #[test] + fn escape_character_at_end_of_attribute_value() { + let inputs = vec![ r":(attr:v=invalid\)some/path", r":(attr:v=invalid\ )some/path", - r":(attr:v=invalid\#)some/path", r":(attr:v=invalid\ valid)some/path", ]; @@ -337,7 +359,7 @@ mod parse { let output = git_pathspec::parse(input.as_bytes()); assert!(output.is_err(), "This pathspec did not produce an error {}", input); - assert!(matches!(output.unwrap_err(), Error::InvalidAttributeValue { .. })); + assert!(matches!(output.unwrap_err(), Error::TrailingEscapeCharacter)); } } From 0849ebf4bc2052d7886f9425800a547bf530e967 Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Tue, 5 Jul 2022 13:56:47 +0200 Subject: [PATCH 051/248] implement name::error for git-attributes --- git-attributes/src/lib.rs | 2 +- git-attributes/src/match_group.rs | 4 +- git-attributes/src/parse/attribute.rs | 62 +++++++++++++------------ git-attributes/tests/parse/attribute.rs | 8 +++- git-pathspec/src/parse.rs | 10 +--- git-pathspec/tests/pathspec.rs | 8 +++- 6 files changed, 49 insertions(+), 45 deletions(-) diff --git a/git-attributes/src/lib.rs b/git-attributes/src/lib.rs index 4be3ac0a39a..c5a20f5f33b 100644 --- a/git-attributes/src/lib.rs +++ b/git-attributes/src/lib.rs @@ -100,7 +100,7 @@ pub struct PatternMapping { pub sequence_number: usize, } -mod name { +pub mod name { use bstr::BString; #[derive(Debug, thiserror::Error)] diff --git a/git-attributes/src/match_group.rs b/git-attributes/src/match_group.rs index 2f445f021f1..de790a8b2a5 100644 --- a/git-attributes/src/match_group.rs +++ b/git-attributes/src/match_group.rs @@ -18,8 +18,8 @@ impl<'a> From> for State { } fn attrs_to_assignments<'a>( - attrs: impl Iterator, crate::parse::Error>>, -) -> Result, crate::parse::Error> { + attrs: impl Iterator, crate::name::Error>>, +) -> Result, crate::name::Error> { attrs .map(|res| { res.map(|attr| Assignment { diff --git a/git-attributes/src/parse/attribute.rs b/git-attributes/src/parse/attribute.rs index f5df007ca76..14da25b7e68 100644 --- a/git-attributes/src/parse/attribute.rs +++ b/git-attributes/src/parse/attribute.rs @@ -1,4 +1,4 @@ -use crate::{Name, NameRef, State, StateRef}; +use crate::{Name, NameRef, State, StateRef, name}; use bstr::{BStr, BString, ByteSlice}; use std::borrow::Cow; @@ -34,19 +34,17 @@ pub struct Lines<'a> { } pub struct Iter<'a> { - attrs: bstr::Fields<'a>, - line_no: usize, + attrs: bstr::Fields<'a> } impl<'a> Iter<'a> { - pub fn new(attrs: &'a BStr, line_no: usize) -> Self { + pub fn new(attrs: &'a BStr) -> Self { Iter { - attrs: attrs.fields(), - line_no, + attrs: attrs.fields() } } - fn parse_attr(&self, attr: &'a [u8]) -> Result, Error> { + fn parse_attr(&self, attr: &'a [u8]) -> Result, name::Error> { let mut tokens = attr.splitn(2, |b| *b == b'='); let attr = tokens.next().expect("attr itself").as_bstr(); let possibly_value = tokens.next(); @@ -62,11 +60,11 @@ impl<'a> Iter<'a> { .unwrap_or(crate::StateRef::Set), ) }; - Ok(NameRef(check_attr(attr, self.line_no)?, state)) + Ok(NameRef(check_attr(attr)?, state)) } } -fn check_attr(attr: &BStr, line_number: usize) -> Result<&BStr, Error> { +fn check_attr(attr: &BStr) -> Result<&BStr, name::Error> { fn attr_valid(attr: &BStr) -> bool { if attr.first() == Some(&b'-') { return false; @@ -78,14 +76,13 @@ fn check_attr(attr: &BStr, line_number: usize) -> Result<&BStr, Error> { }) } - attr_valid(attr).then(|| attr).ok_or_else(|| Error::AttributeName { - line_number, - attribute: attr.into(), - }) + attr_valid(attr) + .then(|| attr) + .ok_or_else(|| name::Error { attribute: attr.into() }) } impl<'a> Iterator for Iter<'a> { - type Item = Result, Error>; + type Item = Result, name::Error>; fn next(&mut self) -> Option { let attr = self.attrs.next().filter(|a| !a.is_empty())?; @@ -143,14 +140,11 @@ fn parse_line(line: &BStr, line_number: usize) -> Option, }; let kind_res = match line.strip_prefix(b"[attr]") { - Some(macro_name) => check_attr(macro_name.into(), line_number) + Some(macro_name) => check_attr(macro_name.into()) .map(|m| Kind::Macro(m.into())) - .map_err(|err| match err { - Error::AttributeName { line_number, attribute } => Error::MacroName { - line_number, - macro_name: attribute, - }, - _ => unreachable!("BUG: check_attr() must only return attribute errors"), + .map_err(|err| Error::MacroName { + line_number, + macro_name: err.attribute, }), None => { let pattern = git_glob::Pattern::from_bytes(line.as_ref())?; @@ -168,25 +162,33 @@ fn parse_line(line: &BStr, line_number: usize) -> Option, Ok(kind) => kind, Err(err) => return Some(Err(err)), }; - Ok((kind, Iter::new(attrs, line_number), line_number)).into() + Ok((kind, Iter::new(attrs), line_number)).into() } const BLANKS: &[u8] = b" \t\r"; +impl<'a> NameRef<'a> { + pub fn name(&self) -> &'a BStr { + self.0 + } + + pub fn state(&self) -> StateRef<'a> { + self.1.clone() + } +} + impl<'a> From> for Name { fn from(v: NameRef<'a>) -> Self { Name(v.0.to_owned(), v.1.into()) } } -impl<'a> From> for (&'a BStr, StateRef<'a>) { - fn from(v: NameRef<'a>) -> Self { - (v.0, v.1) +impl Name { + pub fn name(&self) -> &BString { + &self.0 } -} -impl From for (BString, State) { - fn from(v: Name) -> Self { - (v.0, v.1) + pub fn state(&self) -> &State { + &self.1 } -} +} \ No newline at end of file diff --git a/git-attributes/tests/parse/attribute.rs b/git-attributes/tests/parse/attribute.rs index 2cd127393f7..3be43de66aa 100644 --- a/git-attributes/tests/parse/attribute.rs +++ b/git-attributes/tests/parse/attribute.rs @@ -299,7 +299,11 @@ fn expand( ) -> Result, parse::Error> { let (pattern, attrs, line_no) = input?; let attrs = attrs - .map(|r| r.map(|attr| attr.into())) - .collect::, _>>()?; + .map(|r| r.map(|attr| (attr.name(), attr.state()))) + .collect::, _>>() + .map_err(|e| parse::Error::AttributeName { + attribute: e.attribute, + line_number: line_no, + })?; Ok((pattern, attrs, line_no)) } diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index 007d020c6ec..f77aa41f264 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -152,16 +152,10 @@ fn parse_attributes(input: &[u8]) -> Result, Error> { let unescaped = unescape_attribute_values(input.into())?; - git_attributes::parse::Iter::new(unescaped.as_bstr(), 0) + git_attributes::parse::Iter::new(unescaped.as_bstr()) .map(|res| res.map(|v| v.into())) .collect::, _>>() - .map_err(|e| match e { - git_attributes::parse::Error::AttributeName { - line_number: _, - attribute, - } => Error::InvalidAttribute { attribute }, - _ => unreachable!("expecting only 'Error::AttributeName' but got {}", e), - }) + .map_err(|e| Error::InvalidAttribute { attribute: e.attribute }) } fn unescape_attribute_values(input: &BStr) -> Result, Error> { diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index ff9e004ec93..0c278232769 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -21,7 +21,11 @@ mod parse { path: p.path, signature: p.signature, search_mode: p.search_mode, - attributes: p.attributes.into_iter().map(|v| v.into()).collect(), + attributes: p + .attributes + .into_iter() + .map(|v| (v.name().clone(), v.state().clone())) + .collect(), } } } @@ -451,7 +455,7 @@ mod parse { pat("", signature, SearchMode::ShellGlob, vec![]) } - fn pat_with_attrs(attrs: Vec<(&str, State)>) -> PatternForTesting { + fn pat_with_attrs(attrs: Vec<(&'static str, State)>) -> PatternForTesting { pat("", MagicSignature::empty(), SearchMode::ShellGlob, attrs) } From 24592f7a4f6188d3bb0042f9e91dffc5fc01e382 Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Wed, 6 Jul 2022 11:47:50 +0200 Subject: [PATCH 052/248] refactor attribute value unnescaping --- git-pathspec/src/parse.rs | 56 +++++++++++-------- .../fixtures/generate_pathspec_baseline.sh | 2 + .../generate_pathspec_baseline.tar.xz | 4 +- git-pathspec/tests/pathspec.rs | 2 + 4 files changed, 39 insertions(+), 25 deletions(-) diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index f77aa41f264..80b8d503ece 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -163,33 +163,43 @@ fn unescape_attribute_values(input: &BStr) -> Result, Error> { return Ok(Cow::Borrowed(input)); } - let mut ret = BString::from(Vec::with_capacity(input.len())); + let mut out = Cow::Borrowed([].as_bstr()); for attr in input.split(|&c| c == b' ') { - if let Some(i) = attr.find_byte(b'=') { - ret.push_str(&attr[0..=i]); - let mut i = i + 1; - while i < attr.len() { - if attr[i] == b'\\' { - i += 1; - if i >= attr.len() { - return Err(Error::TrailingEscapeCharacter); - } - } - if attr[i].is_ascii_alphanumeric() || b",-_".contains(&attr[i]) { - ret.push(attr[i]); - i += 1 - } else { - return Err(Error::InvalidAttributeValue { - character: attr[i] as char, - }); - } - } + let split_point = attr.find_byte(b'=').map(|i| i + 1).unwrap_or(attr.len()); + let (name, value) = attr.split_at(split_point); + + if value.contains(&b'\\') { + out.to_mut().push_str(name); + out.to_mut().push_str(unescape_attribute_value(value.into())?); + out.to_mut().push(b' '); } else { - ret.push_str(attr); + let end = out.len() + attr.len() + 1; + out = Cow::Borrowed(&input[0..end.clamp(0, input.len())]) } - ret.push(b' '); } - Ok(Cow::Owned(ret)) + Ok(out) +} + +fn unescape_attribute_value(value: &BStr) -> Result { + let mut out = BString::from(Vec::with_capacity(value.len())); + let mut i = 0; + while i < value.len() { + if value[i] == b'\\' { + i += 1; + if i >= value.len() { + return Err(Error::TrailingEscapeCharacter); + } + } + if !value[i].is_ascii_alphanumeric() && !b",-_".contains(&value[i]) { + return Err(Error::InvalidAttributeValue { + character: value[i] as char, + }); + } + + out.push(value[i]); + i += 1; + } + Ok(out) } diff --git a/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh b/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh index a74bbd6c910..61c28baf4e4 100644 --- a/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh +++ b/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh @@ -117,6 +117,8 @@ baseline ':(attr:inva\lid)some/path' baseline ':(attr:v=inva\\lid)some/path' baseline ':(attr:v=invalid\\)some/path' baseline ':(attr:v=invalid\#)some/path' +baseline ':(attr:v=inva\=lid)some/path' +baseline ':(attr:a=valid b=inva\#lid)some/path' # escape_character_at_end_of_attribute_value baseline ':(attr:v=invalid\)some/path' diff --git a/git-pathspec/tests/fixtures/generated-archives/generate_pathspec_baseline.tar.xz b/git-pathspec/tests/fixtures/generated-archives/generate_pathspec_baseline.tar.xz index 47e32a273e2..6c00a51a406 100644 --- a/git-pathspec/tests/fixtures/generated-archives/generate_pathspec_baseline.tar.xz +++ b/git-pathspec/tests/fixtures/generated-archives/generate_pathspec_baseline.tar.xz @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:72f905eac0db4bc51a80f15e297dff04124b5c080f24224b9b15acac08f4e8fa -size 9432 +oid sha256:47565551378f8d0a0aa2789697300982b062643da24314603401a0230b0c5690 +size 9448 diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index 0c278232769..a064f0de9f3 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -327,6 +327,8 @@ mod parse { r":(attr:v=inva\\lid)some/path", r":(attr:v=invalid\\)some/path", r":(attr:v=invalid\#)some/path", + r":(attr:v=inva\=lid)some/path", + r":(attr:a=valid b=inva\#lid)some/path", ]; for input in inputs { From a9cb68b5e04e11dc1bd7a4dc152001f26a87a445 Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Thu, 7 Jul 2022 12:58:26 +0200 Subject: [PATCH 053/248] fix rust fmt issue --- git-attributes/src/parse/attribute.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/git-attributes/src/parse/attribute.rs b/git-attributes/src/parse/attribute.rs index 14da25b7e68..2284ad8f070 100644 --- a/git-attributes/src/parse/attribute.rs +++ b/git-attributes/src/parse/attribute.rs @@ -1,4 +1,4 @@ -use crate::{Name, NameRef, State, StateRef, name}; +use crate::{name, Name, NameRef, State, StateRef}; use bstr::{BStr, BString, ByteSlice}; use std::borrow::Cow; @@ -34,14 +34,12 @@ pub struct Lines<'a> { } pub struct Iter<'a> { - attrs: bstr::Fields<'a> + attrs: bstr::Fields<'a>, } impl<'a> Iter<'a> { pub fn new(attrs: &'a BStr) -> Self { - Iter { - attrs: attrs.fields() - } + Iter { attrs: attrs.fields() } } fn parse_attr(&self, attr: &'a [u8]) -> Result, name::Error> { @@ -70,10 +68,8 @@ fn check_attr(attr: &BStr) -> Result<&BStr, name::Error> { return false; } - attr.bytes().all(|b| { - matches!(b, - b'-' | b'.' | b'_' | b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9') - }) + attr.bytes() + .all(|b| matches!(b, b'-' | b'.' | b'_' | b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9')) } attr_valid(attr) @@ -191,4 +187,4 @@ impl Name { pub fn state(&self) -> &State { &self.1 } -} \ No newline at end of file +} From 65c83491281184161ad6d9831adabb8475722e42 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 20:35:44 +0800 Subject: [PATCH 054/248] refactor --- git-pathspec/src/parse.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index 80b8d503ece..b33df14f448 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -186,19 +186,16 @@ fn unescape_attribute_value(value: &BStr) -> Result { let mut out = BString::from(Vec::with_capacity(value.len())); let mut i = 0; while i < value.len() { - if value[i] == b'\\' { + let mut b = value[i]; + if b == b'\\' { i += 1; - if i >= value.len() { - return Err(Error::TrailingEscapeCharacter); - } + b = *value.get(i).ok_or(Error::TrailingEscapeCharacter)?; } - if !value[i].is_ascii_alphanumeric() && !b",-_".contains(&value[i]) { - return Err(Error::InvalidAttributeValue { - character: value[i] as char, - }); + if !b.is_ascii_alphanumeric() && !b",-_".contains(&b) { + return Err(Error::InvalidAttributeValue { character: b as char }); } - out.push(value[i]); + out.push(b); i += 1; } Ok(out) From 850bcc35f0e99ef1d65c6bd888638b9a67cab25b Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 20:38:56 +0800 Subject: [PATCH 055/248] refactor --- git-pathspec/src/parse.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index b33df14f448..bd8bcd0d2d0 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -184,19 +184,16 @@ fn unescape_attribute_values(input: &BStr) -> Result, Error> { fn unescape_attribute_value(value: &BStr) -> Result { let mut out = BString::from(Vec::with_capacity(value.len())); - let mut i = 0; - while i < value.len() { - let mut b = value[i]; + let mut bytes = value.iter(); + while let Some(mut b) = bytes.next().copied() { if b == b'\\' { - i += 1; - b = *value.get(i).ok_or(Error::TrailingEscapeCharacter)?; + b = *bytes.next().ok_or(Error::TrailingEscapeCharacter)?; } if !b.is_ascii_alphanumeric() && !b",-_".contains(&b) { return Err(Error::InvalidAttributeValue { character: b as char }); } out.push(b); - i += 1; } Ok(out) } From 1bdf2e1d1b8f58ed2d6fe21c62edb93fc5473c14 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 21:35:45 +0800 Subject: [PATCH 056/248] refactor --- git-pathspec/src/parse.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index bd8bcd0d2d0..4574e6d8447 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -133,12 +133,10 @@ fn split_on_non_escaped_char(input: &[u8], split_char: u8) -> Vec<&[u8]> { let mut i = 0; let mut last = 0; for window in input.windows(2) { + i += 1; if window[0] != b'\\' && window[1] == split_char { - i += 1; keywords.push(&input[last..i]); last = i + 1; - } else { - i += 1; } } keywords.push(&input[last..]); @@ -166,16 +164,17 @@ fn unescape_attribute_values(input: &BStr) -> Result, Error> { let mut out = Cow::Borrowed([].as_bstr()); for attr in input.split(|&c| c == b' ') { - let split_point = attr.find_byte(b'=').map(|i| i + 1).unwrap_or(attr.len()); + let split_point = attr.find_byte(b'=').map_or_else(|| attr.len(), |i| i + 1); let (name, value) = attr.split_at(split_point); if value.contains(&b'\\') { - out.to_mut().push_str(name); - out.to_mut().push_str(unescape_attribute_value(value.into())?); - out.to_mut().push(b' '); + let out = out.to_mut(); + out.push_str(name); + out.push_str(unescape_attribute_value(value.into())?); + out.push(b' '); } else { let end = out.len() + attr.len() + 1; - out = Cow::Borrowed(&input[0..end.clamp(0, input.len())]) + out = Cow::Borrowed(&input[0..end.min(input.len())]) } } From 957356b4e5ea30ff5fa4390859f9e91093df9feb Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2022 21:35:55 +0800 Subject: [PATCH 057/248] refactor --- git-attributes/src/lib.rs | 20 ++++++++++++++++++-- git-attributes/src/match_group.rs | 11 ----------- git-attributes/src/parse/attribute.rs | 7 ++++--- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/git-attributes/src/lib.rs b/git-attributes/src/lib.rs index c5a20f5f33b..acbc6d79785 100644 --- a/git-attributes/src/lib.rs +++ b/git-attributes/src/lib.rs @@ -10,7 +10,7 @@ pub use git_glob as glob; /// The state an attribute can be in, referencing the value. /// /// Note that this doesn't contain the name. -#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub enum StateRef<'a> { /// The attribute is listed, or has the special value 'true' @@ -37,7 +37,7 @@ pub enum State { Unset, /// The attribute is set to the given value, which followed the `=` sign. /// Note that values can be empty. - Value(CompactString), + Value(CompactString), // TODO: use `kstring`, maybe it gets a binary string soon /// The attribute isn't mentioned with a given path or is explicitly set to `Unspecified` using the `!` sign. Unspecified, } @@ -100,6 +100,22 @@ pub struct PatternMapping { pub sequence_number: usize, } +mod state { + use crate::{State, StateRef}; + use bstr::ByteSlice; + + impl<'a> From> for State { + fn from(s: StateRef<'a>) -> Self { + match s { + StateRef::Value(v) => State::Value(v.to_str().expect("no illformed unicode").into()), + StateRef::Set => State::Set, + StateRef::Unset => State::Unset, + StateRef::Unspecified => State::Unspecified, + } + } + } +} + pub mod name { use bstr::BString; diff --git a/git-attributes/src/match_group.rs b/git-attributes/src/match_group.rs index de790a8b2a5..0503ef2fdd3 100644 --- a/git-attributes/src/match_group.rs +++ b/git-attributes/src/match_group.rs @@ -6,17 +6,6 @@ use std::{ path::{Path, PathBuf}, }; -impl<'a> From> for State { - fn from(s: StateRef<'a>) -> Self { - match s { - StateRef::Value(v) => State::Value(v.to_str().expect("no illformed unicode").into()), - StateRef::Set => State::Set, - StateRef::Unset => State::Unset, - StateRef::Unspecified => State::Unspecified, - } - } -} - fn attrs_to_assignments<'a>( attrs: impl Iterator, crate::name::Error>>, ) -> Result, crate::name::Error> { diff --git a/git-attributes/src/parse/attribute.rs b/git-attributes/src/parse/attribute.rs index 2284ad8f070..88cc659b4ff 100644 --- a/git-attributes/src/parse/attribute.rs +++ b/git-attributes/src/parse/attribute.rs @@ -169,7 +169,7 @@ impl<'a> NameRef<'a> { } pub fn state(&self) -> StateRef<'a> { - self.1.clone() + self.1 } } @@ -180,11 +180,12 @@ impl<'a> From> for Name { } impl Name { - pub fn name(&self) -> &BString { - &self.0 + pub fn name(&self) -> &BStr { + self.0.as_bstr() } pub fn state(&self) -> &State { + // TODO: StateRef<'_> &self.1 } } From 1838f3db13eae6d278264dcdbc48d202de992349 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 8 Jul 2022 06:56:02 +0800 Subject: [PATCH 058/248] fix build --- git-attributes/src/match_group.rs | 2 +- git-pathspec/tests/pathspec.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/git-attributes/src/match_group.rs b/git-attributes/src/match_group.rs index 0503ef2fdd3..172816349bf 100644 --- a/git-attributes/src/match_group.rs +++ b/git-attributes/src/match_group.rs @@ -1,4 +1,4 @@ -use crate::{Assignment, MatchGroup, PatternList, PatternMapping, State, StateRef}; +use crate::{Assignment, MatchGroup, PatternList, PatternMapping}; use bstr::{BStr, BString, ByteSlice, ByteVec}; use std::{ ffi::OsString, diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index a064f0de9f3..f7e528dcf65 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -24,7 +24,7 @@ mod parse { attributes: p .attributes .into_iter() - .map(|v| (v.name().clone(), v.state().clone())) + .map(|v| (v.name().to_owned(), v.state().to_owned())) .collect(), } } From 3be7a2dc3cb8f476184555a1c62e230b7703db54 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 8 Jul 2022 06:58:54 +0800 Subject: [PATCH 059/248] refactor --- git-attributes/src/lib.rs | 30 ++++++++++++++++++++++++++- git-attributes/src/parse/attribute.rs | 29 +------------------------- 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/git-attributes/src/lib.rs b/git-attributes/src/lib.rs index acbc6d79785..3350ef87399 100644 --- a/git-attributes/src/lib.rs +++ b/git-attributes/src/lib.rs @@ -117,7 +117,35 @@ mod state { } pub mod name { - use bstr::BString; + use crate::{Name, NameRef, State, StateRef}; + use bstr::{BStr, BString, ByteSlice}; + + impl<'a> NameRef<'a> { + pub fn name(&self) -> &'a BStr { + self.0 + } + + pub fn state(&self) -> StateRef<'a> { + self.1 + } + } + + impl<'a> From> for Name { + fn from(v: NameRef<'a>) -> Self { + Name(v.0.to_owned(), v.1.into()) + } + } + + impl Name { + pub fn name(&self) -> &BStr { + self.0.as_bstr() + } + + pub fn state(&self) -> &State { + // TODO: StateRef<'_> + &self.1 + } + } #[derive(Debug, thiserror::Error)] #[error("Attribute has non-ascii characters or starts with '-': {attribute}")] diff --git a/git-attributes/src/parse/attribute.rs b/git-attributes/src/parse/attribute.rs index 88cc659b4ff..110fd527221 100644 --- a/git-attributes/src/parse/attribute.rs +++ b/git-attributes/src/parse/attribute.rs @@ -1,4 +1,4 @@ -use crate::{name, Name, NameRef, State, StateRef}; +use crate::{name, NameRef, StateRef}; use bstr::{BStr, BString, ByteSlice}; use std::borrow::Cow; @@ -162,30 +162,3 @@ fn parse_line(line: &BStr, line_number: usize) -> Option, } const BLANKS: &[u8] = b" \t\r"; - -impl<'a> NameRef<'a> { - pub fn name(&self) -> &'a BStr { - self.0 - } - - pub fn state(&self) -> StateRef<'a> { - self.1 - } -} - -impl<'a> From> for Name { - fn from(v: NameRef<'a>) -> Self { - Name(v.0.to_owned(), v.1.into()) - } -} - -impl Name { - pub fn name(&self) -> &BStr { - self.0.as_bstr() - } - - pub fn state(&self) -> &State { - // TODO: StateRef<'_> - &self.1 - } -} From 4d6befdbad72d1d143edd344cc7e9e0cba1d1e8a Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 8 Jul 2022 07:05:14 +0800 Subject: [PATCH 060/248] help type inference --- git-pathspec/src/parse.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index 4574e6d8447..6bdbad8f826 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -161,7 +161,7 @@ fn unescape_attribute_values(input: &BStr) -> Result, Error> { return Ok(Cow::Borrowed(input)); } - let mut out = Cow::Borrowed([].as_bstr()); + let mut out: Cow<'_, BStr> = Cow::Borrowed("".into()); for attr in input.split(|&c| c == b' ') { let split_point = attr.find_byte(b'=').map_or_else(|| attr.len(), |i| i + 1); From 35c6d38088e09d88b30e12538e175a4a286980cd Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Mon, 11 Jul 2022 14:44:55 +0200 Subject: [PATCH 061/248] use "to_owned" instead of "into" --- git-attributes/src/lib.rs | 10 ++++++++++ git-attributes/src/match_group.rs | 2 +- git-pathspec/src/parse.rs | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/git-attributes/src/lib.rs b/git-attributes/src/lib.rs index 3350ef87399..de67ebe55d8 100644 --- a/git-attributes/src/lib.rs +++ b/git-attributes/src/lib.rs @@ -104,6 +104,12 @@ mod state { use crate::{State, StateRef}; use bstr::ByteSlice; + impl<'a> StateRef<'a> { + pub fn to_owned(self) -> State { + self.into() + } + } + impl<'a> From> for State { fn from(s: StateRef<'a>) -> Self { match s { @@ -128,6 +134,10 @@ pub mod name { pub fn state(&self) -> StateRef<'a> { self.1 } + + pub fn to_owned(self) -> Name { + self.into() + } } impl<'a> From> for Name { diff --git a/git-attributes/src/match_group.rs b/git-attributes/src/match_group.rs index 172816349bf..539989b5717 100644 --- a/git-attributes/src/match_group.rs +++ b/git-attributes/src/match_group.rs @@ -13,7 +13,7 @@ fn attrs_to_assignments<'a>( .map(|res| { res.map(|attr| Assignment { name: attr.0.to_str().expect("no illformed unicode").into(), - state: attr.1.into(), + state: attr.1.to_owned(), }) }) .collect() diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index 6bdbad8f826..785ed80f8bd 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -151,7 +151,7 @@ fn parse_attributes(input: &[u8]) -> Result, Error> { let unescaped = unescape_attribute_values(input.into())?; git_attributes::parse::Iter::new(unescaped.as_bstr()) - .map(|res| res.map(|v| v.into())) + .map(|res| res.map(|v| v.to_owned())) .collect::, _>>() .map_err(|e| Error::InvalidAttribute { attribute: e.attribute }) } From 82074d5e026c31b382cf97eade22eeca1bce3390 Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Mon, 11 Jul 2022 15:08:36 +0200 Subject: [PATCH 062/248] impl '.as_ref()' for State --- git-attributes/src/lib.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/git-attributes/src/lib.rs b/git-attributes/src/lib.rs index de67ebe55d8..9fb504d1b96 100644 --- a/git-attributes/src/lib.rs +++ b/git-attributes/src/lib.rs @@ -110,6 +110,17 @@ mod state { } } + impl<'a> State { + pub fn as_ref(&'a self) -> StateRef<'a> { + match self { + State::Value(v) => StateRef::Value(v.as_bytes().as_bstr()), + State::Set => StateRef::Set, + State::Unset => StateRef::Unset, + State::Unspecified => StateRef::Unspecified, + } + } + } + impl<'a> From> for State { fn from(s: StateRef<'a>) -> Self { match s { @@ -123,7 +134,7 @@ mod state { } pub mod name { - use crate::{Name, NameRef, State, StateRef}; + use crate::{Name, NameRef, StateRef}; use bstr::{BStr, BString, ByteSlice}; impl<'a> NameRef<'a> { @@ -151,9 +162,8 @@ pub mod name { self.0.as_bstr() } - pub fn state(&self) -> &State { - // TODO: StateRef<'_> - &self.1 + pub fn state(&self) -> StateRef<'_> { + self.1.as_ref() } } From 6e93144545277cb5efe1dd3fba2ecaf90ea9c726 Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Tue, 12 Jul 2022 00:16:32 +0200 Subject: [PATCH 063/248] check attr values regarless of it being escaped --- git-pathspec/src/parse.rs | 21 ++++++++++++++++--- .../fixtures/generate_pathspec_baseline.sh | 3 +++ .../generate_pathspec_baseline.tar.xz | 4 ++-- git-pathspec/tests/pathspec.rs | 3 +++ 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index 785ed80f8bd..acbe133a88e 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -170,9 +170,10 @@ fn unescape_attribute_values(input: &BStr) -> Result, Error> { if value.contains(&b'\\') { let out = out.to_mut(); out.push_str(name); - out.push_str(unescape_attribute_value(value.into())?); + out.push_str(unescape_and_check_attr_value(value.into())?); out.push(b' '); } else { + check_attribute_value(value.as_bstr())?; let end = out.len() + attr.len() + 1; out = Cow::Borrowed(&input[0..end.min(input.len())]) } @@ -181,14 +182,14 @@ fn unescape_attribute_values(input: &BStr) -> Result, Error> { Ok(out) } -fn unescape_attribute_value(value: &BStr) -> Result { +fn unescape_and_check_attr_value(value: &BStr) -> Result { let mut out = BString::from(Vec::with_capacity(value.len())); let mut bytes = value.iter(); while let Some(mut b) = bytes.next().copied() { if b == b'\\' { b = *bytes.next().ok_or(Error::TrailingEscapeCharacter)?; } - if !b.is_ascii_alphanumeric() && !b",-_".contains(&b) { + if !is_value_char_valid(&b) { return Err(Error::InvalidAttributeValue { character: b as char }); } @@ -196,3 +197,17 @@ fn unescape_attribute_value(value: &BStr) -> Result { } Ok(out) } + +fn check_attribute_value(input: &BStr) -> Result<(), Error> { + let bytes = input.iter(); + for b in bytes { + if !is_value_char_valid(b) { + return Err(Error::InvalidAttributeValue { character: *b as char }); + } + } + Ok(()) +} + +fn is_value_char_valid(byte: &u8) -> bool { + byte.is_ascii_alphanumeric() || b",-_".contains(byte) +} diff --git a/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh b/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh index 61c28baf4e4..5422bb32602 100644 --- a/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh +++ b/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh @@ -114,11 +114,14 @@ baseline ':(attr:+invalidAttr,attr:valid)some/path' baseline ':(attr:inva\lid)some/path' # invalid_attribute_values +baseline ':(attr:v=inva#lid)some/path' baseline ':(attr:v=inva\\lid)some/path' baseline ':(attr:v=invalid\\)some/path' baseline ':(attr:v=invalid\#)some/path' baseline ':(attr:v=inva\=lid)some/path' baseline ':(attr:a=valid b=inva\#lid)some/path' +baseline ':(attr:v=val��)' +baseline ':(attr:pr=pre��x:,)�' # escape_character_at_end_of_attribute_value baseline ':(attr:v=invalid\)some/path' diff --git a/git-pathspec/tests/fixtures/generated-archives/generate_pathspec_baseline.tar.xz b/git-pathspec/tests/fixtures/generated-archives/generate_pathspec_baseline.tar.xz index 6c00a51a406..f90d1870144 100644 --- a/git-pathspec/tests/fixtures/generated-archives/generate_pathspec_baseline.tar.xz +++ b/git-pathspec/tests/fixtures/generated-archives/generate_pathspec_baseline.tar.xz @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:47565551378f8d0a0aa2789697300982b062643da24314603401a0230b0c5690 -size 9448 +oid sha256:ae8dfb9f9cd77a7e390a0fd18c0f9c885936c0c17f84e0d4da26dd2b79b90d65 +size 9472 diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index f7e528dcf65..10362bfc858 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -324,11 +324,14 @@ mod parse { #[test] fn invalid_attribute_values() { let inputs = vec![ + r":(attr:v=inva#lid)some/path", r":(attr:v=inva\\lid)some/path", r":(attr:v=invalid\\)some/path", r":(attr:v=invalid\#)some/path", r":(attr:v=inva\=lid)some/path", r":(attr:a=valid b=inva\#lid)some/path", + ":(attr:v=val��)", + ":(attr:pr=pre��x:,)�", ]; for input in inputs { From 2a775c602da3edf20d84599839ca166bff2457a5 Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Tue, 12 Jul 2022 00:32:04 +0200 Subject: [PATCH 064/248] add fuzzer --- git-pathspec/fuzz/.gitignore | 3 + git-pathspec/fuzz/Cargo.lock | 268 +++++++++++++++++++++++ git-pathspec/fuzz/Cargo.toml | 25 +++ git-pathspec/fuzz/fuzz_targets/parser.rs | 6 + 4 files changed, 302 insertions(+) create mode 100644 git-pathspec/fuzz/.gitignore create mode 100644 git-pathspec/fuzz/Cargo.lock create mode 100644 git-pathspec/fuzz/Cargo.toml create mode 100644 git-pathspec/fuzz/fuzz_targets/parser.rs diff --git a/git-pathspec/fuzz/.gitignore b/git-pathspec/fuzz/.gitignore new file mode 100644 index 00000000000..a0925114d61 --- /dev/null +++ b/git-pathspec/fuzz/.gitignore @@ -0,0 +1,3 @@ +target +corpus +artifacts diff --git a/git-pathspec/fuzz/Cargo.lock b/git-pathspec/fuzz/Cargo.lock new file mode 100644 index 00000000000..50c258223a1 --- /dev/null +++ b/git-pathspec/fuzz/Cargo.lock @@ -0,0 +1,268 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "arbitrary" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a7924531f38b1970ff630f03eb20a2fde69db5c590c93b0f3482e95dcc5fd60" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "memchr", +] + +[[package]] +name = "btoi" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97c0869a9faa81f8bbf8102371105d6d0a7b79167a04c340b04ab16892246a11" +dependencies = [ + "num-traits", +] + +[[package]] +name = "castaway" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "compact_str" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33b5c3ee2b4ffa00ac2b00d1645cd9229ade668139bccf95f15fadcf374127b" +dependencies = [ + "castaway", + "itoa", + "ryu", +] + +[[package]] +name = "git-attributes" +version = "0.3.0" +dependencies = [ + "bstr", + "compact_str", + "git-features", + "git-glob", + "git-path", + "git-quote", + "thiserror", + "unicode-bom", +] + +[[package]] +name = "git-features" +version = "0.21.1" +dependencies = [ + "git-hash", + "libc", +] + +[[package]] +name = "git-glob" +version = "0.3.0" +dependencies = [ + "bitflags", + "bstr", +] + +[[package]] +name = "git-hash" +version = "0.9.5" +dependencies = [ + "hex", + "quick-error", +] + +[[package]] +name = "git-path" +version = "0.3.0" +dependencies = [ + "bstr", + "thiserror", +] + +[[package]] +name = "git-pathspec" +version = "0.0.0" +dependencies = [ + "bitflags", + "bstr", + "git-attributes", + "git-glob", + "thiserror", +] + +[[package]] +name = "git-pathspec-fuzz" +version = "0.0.0" +dependencies = [ + "git-pathspec", + "libfuzzer-sys", +] + +[[package]] +name = "git-quote" +version = "0.2.0" +dependencies = [ + "bstr", + "btoi", + "quick-error", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "336244aaeab6a12df46480dc585802aa743a72d66b11937844c61bbca84c991d" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" + +[[package]] +name = "proc-macro2" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quote" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustversion" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0a5f7c728f5d284929a1cccb5bc19884422bfe6ef4d6c409da2c41838983fcf" + +[[package]] +name = "ryu" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" + +[[package]] +name = "syn" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-bom" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63ec69f541d875b783ca40184d655f2927c95f0bffd486faa83cd3ac3529ec32" + +[[package]] +name = "unicode-ident" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" diff --git a/git-pathspec/fuzz/Cargo.toml b/git-pathspec/fuzz/Cargo.toml new file mode 100644 index 00000000000..ac6f7aac963 --- /dev/null +++ b/git-pathspec/fuzz/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "git-pathspec-fuzz" +version = "0.0.0" +authors = ["Automatically generated"] +publish = false +edition = "2018" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" + +[dependencies.git-pathspec] +path = ".." + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[[bin]] +name = "parser" +path = "fuzz_targets/parser.rs" +test = false +doc = false diff --git a/git-pathspec/fuzz/fuzz_targets/parser.rs b/git-pathspec/fuzz/fuzz_targets/parser.rs new file mode 100644 index 00000000000..738cf24b183 --- /dev/null +++ b/git-pathspec/fuzz/fuzz_targets/parser.rs @@ -0,0 +1,6 @@ +#![no_main] +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + let _a = git_pathspec::parse(data); +}); From 9c6281f903451011407a352e1fca877d79c10466 Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Tue, 12 Jul 2022 10:55:00 +0200 Subject: [PATCH 065/248] fix unescaping logic - thanks fuzzer --- git-pathspec/src/parse.rs | 14 ++++++++-- .../fixtures/generate_pathspec_baseline.sh | 1 + .../generate_pathspec_baseline.tar.xz | 4 +-- git-pathspec/tests/pathspec.rs | 26 +++++++++++++++++++ 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index acbe133a88e..22fc83fc94e 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -174,8 +174,18 @@ fn unescape_attribute_values(input: &BStr) -> Result, Error> { out.push(b' '); } else { check_attribute_value(value.as_bstr())?; - let end = out.len() + attr.len() + 1; - out = Cow::Borrowed(&input[0..end.min(input.len())]) + match out { + Cow::Borrowed(_) => { + let end = out.len() + attr.len() + 1; + out = Cow::Borrowed(&input[0..end.min(input.len())]) + } + Cow::Owned(_) => { + let out = out.to_mut(); + out.push_str(name); + out.push_str(value); + out.push(b' '); + } + } } } diff --git a/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh b/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh index 5422bb32602..134a9e50ba4 100644 --- a/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh +++ b/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh @@ -78,6 +78,7 @@ baseline ':(attr:v=one\-)' baseline ':(attr:v=one\_)' baseline ':(attr:v=one\,)' baseline ':(attr:v=one\,two\,three)' +baseline ':(attr:a=\d b= c=\d)' # failing diff --git a/git-pathspec/tests/fixtures/generated-archives/generate_pathspec_baseline.tar.xz b/git-pathspec/tests/fixtures/generated-archives/generate_pathspec_baseline.tar.xz index f90d1870144..33adf6cf10b 100644 --- a/git-pathspec/tests/fixtures/generated-archives/generate_pathspec_baseline.tar.xz +++ b/git-pathspec/tests/fixtures/generated-archives/generate_pathspec_baseline.tar.xz @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ae8dfb9f9cd77a7e390a0fd18c0f9c885936c0c17f84e0d4da26dd2b79b90d65 -size 9472 +oid sha256:f94976c508e1dd577151cd7d271948ba8bf6583de5d3c31a6aa3e314c13112ce +size 9484 diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index 10362bfc858..23efdd1ad3c 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -224,6 +224,14 @@ mod parse { r":(attr:v=one\,two\,three)", pat_with_attrs(vec![("v", State::Value(r"one,two,three".into()))]), ), + ( + r":(attr:a=\d b= c=\d)", + pat_with_attrs(vec![ + ("a", State::Value(r"d".into())), + ("b", State::Value(r"".into())), + ("c", State::Value(r"d".into())), + ]), + ), ]; check_valid_inputs(inputs) @@ -431,6 +439,24 @@ mod parse { assert!(output.is_err()); assert!(matches!(output.unwrap_err(), Error::IncompatibleSearchModes)); } + + // #[test] + // fn fuzzer() { + // let input = r":(attr:=\d t= �=\d)"; + // let input_raw = vec![ + // 58, 40, 97, 116, 116, 114, 58, 61, 92, 100, 32, 116, 61, 32, 247, 61, 92, 100, 41, + // ]; + + // // assert!( + // // !check_against_baseline(input), + // // "This pathspec is valid in git: {}", + // // input + // // ); + + // let output = git_pathspec::parse(&input_raw); + // assert!(output.is_err()); + // assert!(matches!(output.unwrap_err(), Error::InvalidAttributeValue { .. })); + // } } fn check_valid_inputs(inputs: Vec<(&str, PatternForTesting)>) { From e83879cee1666ba927a95c05c714d132a109eeef Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 12 Jul 2022 20:01:22 +0800 Subject: [PATCH 066/248] refactor --- git-attributes/src/lib.rs | 100 ++++---------------------- git-attributes/src/name.rs | 38 ++++++++++ git-attributes/src/parse/attribute.rs | 2 +- git-attributes/src/state.rs | 30 ++++++++ 4 files changed, 83 insertions(+), 87 deletions(-) create mode 100644 git-attributes/src/name.rs create mode 100644 git-attributes/src/state.rs diff --git a/git-attributes/src/lib.rs b/git-attributes/src/lib.rs index 9fb504d1b96..3b5beb79758 100644 --- a/git-attributes/src/lib.rs +++ b/git-attributes/src/lib.rs @@ -7,6 +7,17 @@ use bstr::{BStr, BString}; use compact_str::CompactString; pub use git_glob as glob; +pub mod name; +mod state; + +mod match_group; +pub use match_group::{Attributes, Ignore, Match, Pattern}; + +pub mod parse; +pub fn parse(buf: &[u8]) -> parse::Lines<'_> { + parse::Lines::new(buf) +} + /// The state an attribute can be in, referencing the value. /// /// Note that this doesn't contain the name. @@ -37,7 +48,7 @@ pub enum State { Unset, /// The attribute is set to the given value, which followed the `=` sign. /// Note that values can be empty. - Value(CompactString), // TODO: use `kstring`, maybe it gets a binary string soon + Value(CompactString), // TODO: use `kstring`, maybe it gets a binary string soon, needs binary, too, no UTF8 is required for attr values /// The attribute isn't mentioned with a given path or is explicitly set to `Unspecified` using the `!` sign. Unspecified, } @@ -64,7 +75,7 @@ pub struct Assignment { /// /// Pattern lists with base path are queryable relative to that base, otherwise they are relative to the repository root. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Default)] -pub struct MatchGroup { +pub struct MatchGroup { /// A list of pattern lists, each representing a patterns from a file or specified by hand, in the order they were /// specified in. /// @@ -77,7 +88,7 @@ pub struct MatchGroup { /// Knowing their base which is relative to a source directory, it will ignore all path to match against /// that don't also start with said base. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Default)] -pub struct PatternList { +pub struct PatternList { /// Patterns and their associated data in the order they were loaded in or specified, /// the line number in its source file or its sequence number (_`(pattern, value, line_number)`_). /// @@ -99,86 +110,3 @@ pub struct PatternMapping { pub value: T, pub sequence_number: usize, } - -mod state { - use crate::{State, StateRef}; - use bstr::ByteSlice; - - impl<'a> StateRef<'a> { - pub fn to_owned(self) -> State { - self.into() - } - } - - impl<'a> State { - pub fn as_ref(&'a self) -> StateRef<'a> { - match self { - State::Value(v) => StateRef::Value(v.as_bytes().as_bstr()), - State::Set => StateRef::Set, - State::Unset => StateRef::Unset, - State::Unspecified => StateRef::Unspecified, - } - } - } - - impl<'a> From> for State { - fn from(s: StateRef<'a>) -> Self { - match s { - StateRef::Value(v) => State::Value(v.to_str().expect("no illformed unicode").into()), - StateRef::Set => State::Set, - StateRef::Unset => State::Unset, - StateRef::Unspecified => State::Unspecified, - } - } - } -} - -pub mod name { - use crate::{Name, NameRef, StateRef}; - use bstr::{BStr, BString, ByteSlice}; - - impl<'a> NameRef<'a> { - pub fn name(&self) -> &'a BStr { - self.0 - } - - pub fn state(&self) -> StateRef<'a> { - self.1 - } - - pub fn to_owned(self) -> Name { - self.into() - } - } - - impl<'a> From> for Name { - fn from(v: NameRef<'a>) -> Self { - Name(v.0.to_owned(), v.1.into()) - } - } - - impl Name { - pub fn name(&self) -> &BStr { - self.0.as_bstr() - } - - pub fn state(&self) -> StateRef<'_> { - self.1.as_ref() - } - } - - #[derive(Debug, thiserror::Error)] - #[error("Attribute has non-ascii characters or starts with '-': {attribute}")] - pub struct Error { - pub attribute: BString, - } -} - -mod match_group; -pub use match_group::{Attributes, Ignore, Match, Pattern}; - -pub mod parse; - -pub fn parse(buf: &[u8]) -> parse::Lines<'_> { - parse::Lines::new(buf) -} diff --git a/git-attributes/src/name.rs b/git-attributes/src/name.rs new file mode 100644 index 00000000000..78d3ad67ce5 --- /dev/null +++ b/git-attributes/src/name.rs @@ -0,0 +1,38 @@ +use crate::{Name, NameRef, StateRef}; +use bstr::{BStr, BString, ByteSlice}; + +impl<'a> NameRef<'a> { + pub fn name(&self) -> &'a BStr { + self.0 + } + + pub fn state(&self) -> StateRef<'a> { + self.1 + } + + pub fn to_owned(self) -> Name { + self.into() + } +} + +impl<'a> From> for Name { + fn from(v: NameRef<'a>) -> Self { + Name(v.0.to_owned(), v.1.into()) + } +} + +impl Name { + pub fn name(&self) -> &BStr { + self.0.as_bstr() + } + + pub fn state(&self) -> StateRef<'_> { + self.1.as_ref() + } +} + +#[derive(Debug, thiserror::Error)] +#[error("Attribute has non-ascii characters or starts with '-': {attribute}")] +pub struct Error { + pub attribute: BString, +} diff --git a/git-attributes/src/parse/attribute.rs b/git-attributes/src/parse/attribute.rs index 110fd527221..91a37231d2e 100644 --- a/git-attributes/src/parse/attribute.rs +++ b/git-attributes/src/parse/attribute.rs @@ -55,7 +55,7 @@ impl<'a> Iter<'a> { attr, possibly_value .map(|v| crate::StateRef::Value(v.as_bstr())) - .unwrap_or(crate::StateRef::Set), + .unwrap_or(StateRef::Set), ) }; Ok(NameRef(check_attr(attr)?, state)) diff --git a/git-attributes/src/state.rs b/git-attributes/src/state.rs new file mode 100644 index 00000000000..363a625dede --- /dev/null +++ b/git-attributes/src/state.rs @@ -0,0 +1,30 @@ +use crate::{State, StateRef}; +use bstr::ByteSlice; + +impl<'a> StateRef<'a> { + pub fn to_owned(self) -> State { + self.into() + } +} + +impl<'a> State { + pub fn as_ref(&'a self) -> StateRef<'a> { + match self { + State::Value(v) => StateRef::Value(v.as_bytes().as_bstr()), + State::Set => StateRef::Set, + State::Unset => StateRef::Unset, + State::Unspecified => StateRef::Unspecified, + } + } +} + +impl<'a> From> for State { + fn from(s: StateRef<'a>) -> Self { + match s { + StateRef::Value(v) => State::Value(v.to_str().expect("no illformed unicode").into()), + StateRef::Set => State::Set, + StateRef::Unset => State::Unset, + StateRef::Unspecified => State::Unspecified, + } + } +} From bffbcee68affce2a829c67bfe71e7df861c15ee5 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 12 Jul 2022 20:01:34 +0800 Subject: [PATCH 067/248] refactor --- git-pathspec/src/parse.rs | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index 22fc83fc94e..089b9e02585 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -199,25 +199,29 @@ fn unescape_and_check_attr_value(value: &BStr) -> Result { if b == b'\\' { b = *bytes.next().ok_or(Error::TrailingEscapeCharacter)?; } - if !is_value_char_valid(&b) { - return Err(Error::InvalidAttributeValue { character: b as char }); - } - out.push(b); + out.push(validated_attr_value_byte(b)?); } Ok(out) } fn check_attribute_value(input: &BStr) -> Result<(), Error> { - let bytes = input.iter(); - for b in bytes { - if !is_value_char_valid(b) { - return Err(Error::InvalidAttributeValue { character: *b as char }); - } + match input.iter().copied().find(|b| !is_valid_attr_value(*b)) { + Some(b) => Err(Error::InvalidAttributeValue { character: b as char }), + None => Ok(()), } - Ok(()) } -fn is_value_char_valid(byte: &u8) -> bool { - byte.is_ascii_alphanumeric() || b",-_".contains(byte) +fn is_valid_attr_value(byte: u8) -> bool { + byte.is_ascii_alphanumeric() || b",-_".contains(&byte) +} + +fn validated_attr_value_byte(byte: u8) -> Result { + if is_valid_attr_value(byte) { + Ok(byte) + } else { + Err(Error::InvalidAttributeValue { + character: byte as char, + }) + } } From c92d5c6a223e377c10c2ca6b822e7eeb9070e12c Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 14 Jul 2022 17:16:37 +0800 Subject: [PATCH 068/248] 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 6449e77e11ef0d25c2990f1c29e9fbea3c97fb0a Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Thu, 14 Jul 2022 13:03:54 +0200 Subject: [PATCH 069/248] refactor of `Name` and `Assignment` --- git-attributes/src/assignment.rs | 26 ++++++++++++ git-attributes/src/lib.rs | 21 +++++++--- git-attributes/src/match_group.rs | 13 ++---- git-attributes/src/name.rs | 28 ++++--------- git-attributes/src/parse/attribute.rs | 19 ++++----- git-attributes/tests/parse/attribute.rs | 56 ++++++++++++++----------- git-pathspec/src/lib.rs | 4 +- git-pathspec/src/parse.rs | 2 +- git-pathspec/tests/pathspec.rs | 20 +-------- 9 files changed, 99 insertions(+), 90 deletions(-) create mode 100644 git-attributes/src/assignment.rs diff --git a/git-attributes/src/assignment.rs b/git-attributes/src/assignment.rs new file mode 100644 index 00000000000..5ae70a4fdbd --- /dev/null +++ b/git-attributes/src/assignment.rs @@ -0,0 +1,26 @@ +use crate::{Assignment, AssignmentRef, NameRef, StateRef}; + +impl<'a> AssignmentRef<'a> { + pub(crate) fn new(name: NameRef<'a>, state: StateRef<'a>) -> AssignmentRef<'a> { + AssignmentRef { name, state } + } + + pub fn to_owned(self) -> Assignment { + self.into() + } +} + +impl<'a> From> for Assignment { + fn from(a: AssignmentRef<'a>) -> Self { + Assignment { + name: a.name.to_owned(), + state: a.state.to_owned(), + } + } +} + +impl<'a> Assignment { + pub fn as_ref(&'a self) -> AssignmentRef<'a> { + AssignmentRef::new(self.name.as_ref(), self.state.as_ref()) + } +} diff --git a/git-attributes/src/lib.rs b/git-attributes/src/lib.rs index 3b5beb79758..fda73a1ced4 100644 --- a/git-attributes/src/lib.rs +++ b/git-attributes/src/lib.rs @@ -7,6 +7,7 @@ use bstr::{BStr, BString}; use compact_str::CompactString; pub use git_glob as glob; +mod assignment; pub mod name; mod state; @@ -53,24 +54,34 @@ pub enum State { Unspecified, } -/// Holds and owns data that represent one validated attribute +/// Represents a validated attribute name #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] -pub struct Name(BString, State); +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +pub struct Name(pub(crate) CompactString); -/// Holds validated attribute data as a reference +/// Holds a validated attribute name as a reference #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd)] -pub struct NameRef<'a>(&'a BStr, StateRef<'a>); +pub struct NameRef<'a>(&'a str); /// Name an attribute and describe it's assigned state. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Assignment { /// The name of the attribute. - pub name: CompactString, + pub name: Name, /// The state of the attribute. pub state: State, } +/// Holds validated attribute data as a reference +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd)] +pub struct AssignmentRef<'a> { + /// The name of the attribute. + pub name: NameRef<'a>, + /// The state of the attribute. + pub state: StateRef<'a>, +} + /// A grouping of lists of patterns while possibly keeping associated to their base path. /// /// Pattern lists with base path are queryable relative to that base, otherwise they are relative to the repository root. diff --git a/git-attributes/src/match_group.rs b/git-attributes/src/match_group.rs index 539989b5717..28d3feca029 100644 --- a/git-attributes/src/match_group.rs +++ b/git-attributes/src/match_group.rs @@ -7,16 +7,9 @@ use std::{ }; fn attrs_to_assignments<'a>( - attrs: impl Iterator, crate::name::Error>>, + attrs: impl Iterator, crate::name::Error>>, ) -> Result, crate::name::Error> { - attrs - .map(|res| { - res.map(|attr| Assignment { - name: attr.0.to_str().expect("no illformed unicode").into(), - state: attr.1.to_owned(), - }) - }) - .collect() + attrs.map(|res| res.map(|attr| attr.to_owned())).collect() } /// A marker trait to identify the type of a description. @@ -73,7 +66,7 @@ impl Pattern for Attributes { let (pattern, value) = match pattern_kind { crate::parse::Kind::Macro(macro_name) => ( git_glob::Pattern { - text: macro_name, + text: macro_name.inner().to_owned(), mode: git_glob::pattern::Mode::all(), first_wildcard_pos: None, }, diff --git a/git-attributes/src/name.rs b/git-attributes/src/name.rs index 78d3ad67ce5..1ce3ec38831 100644 --- a/git-attributes/src/name.rs +++ b/git-attributes/src/name.rs @@ -1,33 +1,23 @@ -use crate::{Name, NameRef, StateRef}; +use crate::{Name, NameRef}; use bstr::{BStr, BString, ByteSlice}; impl<'a> NameRef<'a> { - pub fn name(&self) -> &'a BStr { - self.0 - } - - pub fn state(&self) -> StateRef<'a> { - self.1 - } - pub fn to_owned(self) -> Name { - self.into() + Name(self.0.into()) } -} -impl<'a> From> for Name { - fn from(v: NameRef<'a>) -> Self { - Name(v.0.to_owned(), v.1.into()) + pub fn inner(&self) -> &str { + self.0 } } -impl Name { - pub fn name(&self) -> &BStr { - self.0.as_bstr() +impl<'a> Name { + pub fn as_ref(&'a self) -> NameRef<'a> { + NameRef(self.0.as_ref()) } - pub fn state(&self) -> StateRef<'_> { - self.1.as_ref() + pub fn inner(&'a self) -> &'a BStr { + self.0.as_bytes().as_bstr() } } diff --git a/git-attributes/src/parse/attribute.rs b/git-attributes/src/parse/attribute.rs index 91a37231d2e..89dbba37806 100644 --- a/git-attributes/src/parse/attribute.rs +++ b/git-attributes/src/parse/attribute.rs @@ -1,5 +1,5 @@ -use crate::{name, NameRef, StateRef}; -use bstr::{BStr, BString, ByteSlice}; +use crate::{name, AssignmentRef, Name, NameRef, StateRef}; +use bstr::{BStr, ByteSlice}; use std::borrow::Cow; #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] @@ -8,8 +8,7 @@ pub enum Kind { /// A pattern to match paths against Pattern(git_glob::Pattern), /// The name of the macro to define, always a valid attribute name - // TODO: turn it into its own type for maximum safety - Macro(BString), + Macro(Name), } mod error { @@ -42,7 +41,7 @@ impl<'a> Iter<'a> { Iter { attrs: attrs.fields() } } - fn parse_attr(&self, attr: &'a [u8]) -> Result, name::Error> { + fn parse_attr(&self, attr: &'a [u8]) -> Result, name::Error> { let mut tokens = attr.splitn(2, |b| *b == b'='); let attr = tokens.next().expect("attr itself").as_bstr(); let possibly_value = tokens.next(); @@ -58,11 +57,11 @@ impl<'a> Iter<'a> { .unwrap_or(StateRef::Set), ) }; - Ok(NameRef(check_attr(attr)?, state)) + Ok(AssignmentRef::new(check_attr(attr)?, state)) } } -fn check_attr(attr: &BStr) -> Result<&BStr, name::Error> { +fn check_attr(attr: &BStr) -> Result, name::Error> { fn attr_valid(attr: &BStr) -> bool { if attr.first() == Some(&b'-') { return false; @@ -73,12 +72,12 @@ fn check_attr(attr: &BStr) -> Result<&BStr, name::Error> { } attr_valid(attr) - .then(|| attr) + .then(|| NameRef(attr.to_str().expect("no illformed utf8"))) .ok_or_else(|| name::Error { attribute: attr.into() }) } impl<'a> Iterator for Iter<'a> { - type Item = Result, name::Error>; + type Item = Result, name::Error>; fn next(&mut self) -> Option { let attr = self.attrs.next().filter(|a| !a.is_empty())?; @@ -137,7 +136,7 @@ fn parse_line(line: &BStr, line_number: usize) -> Option, let kind_res = match line.strip_prefix(b"[attr]") { Some(macro_name) => check_attr(macro_name.into()) - .map(|m| Kind::Macro(m.into())) + .map(|m| Kind::Macro(m.to_owned())) .map_err(|err| Error::MacroName { line_number, macro_name: err.attribute, diff --git a/git-attributes/tests/parse/attribute.rs b/git-attributes/tests/parse/attribute.rs index 3be43de66aa..0ac40a6e8e2 100644 --- a/git-attributes/tests/parse/attribute.rs +++ b/git-attributes/tests/parse/attribute.rs @@ -1,4 +1,4 @@ -use bstr::{BStr, ByteSlice}; +use bstr::{BString, ByteSlice}; use git_attributes::{parse, StateRef}; use git_glob::pattern::Mode; use git_testtools::fixture_bytes; @@ -119,16 +119,28 @@ fn invalid_escapes_in_quotes_are_an_error() { #[test] fn custom_macros_can_be_differentiated() { - assert_eq!( - line(r#"[attr]foo bar -baz"#), - (macro_(r"foo"), vec![set("bar"), unset("baz")], 1) - ); + let output = line(r#"[attr]foo bar -baz"#); + match output.0 { + parse::Kind::Pattern(_) => unreachable!(), + parse::Kind::Macro(name) => { + assert_eq!( + (name.inner().to_str().expect("no illformed utf-8"), output.1, output.2), + (r"foo", vec![set("bar"), unset("baz")], 1) + ); + } + } - assert_eq!( - line(r#""[attr]foo" bar -baz"#), - (macro_(r"foo"), vec![set("bar"), unset("baz")], 1), - "it works after unquoting even, making it harder to denote a file name with [attr] prefix" - ); + let output = line(r#""[attr]foo" bar -baz"#); + match output.0 { + parse::Kind::Pattern(_) => unreachable!(), + parse::Kind::Macro(name) => { + assert_eq!( + (name.inner().to_str().expect("no illformed utf-8"), output.1, output.2), + (r"foo", vec![set("bar"), unset("baz")], 1), + "it works after unquoting even, making it harder to denote a file name with [attr] prefix" + ); + } + } } #[test] @@ -249,22 +261,22 @@ fn trailing_whitespace_in_attributes_is_ignored() { ); } -type ExpandedAttribute<'a> = (parse::Kind, Vec<(&'a BStr, git_attributes::StateRef<'a>)>, usize); +type ExpandedAttribute<'a> = (parse::Kind, Vec<(BString, git_attributes::StateRef<'a>)>, usize); -fn set(attr: &str) -> (&BStr, StateRef) { - (attr.as_bytes().as_bstr(), StateRef::Set) +fn set(attr: &str) -> (BString, StateRef) { + (attr.into(), StateRef::Set) } -fn unset(attr: &str) -> (&BStr, StateRef) { - (attr.as_bytes().as_bstr(), StateRef::Unset) +fn unset(attr: &str) -> (BString, StateRef) { + (attr.into(), StateRef::Unset) } -fn unspecified(attr: &str) -> (&BStr, StateRef) { - (attr.as_bytes().as_bstr(), StateRef::Unspecified) +fn unspecified(attr: &str) -> (BString, StateRef) { + (attr.into(), StateRef::Unspecified) } -fn value<'a, 'b>(attr: &'a str, value: &'b str) -> (&'a BStr, StateRef<'b>) { - (attr.as_bytes().as_bstr(), StateRef::Value(value.as_bytes().as_bstr())) +fn value<'a, 'b>(attr: &'a str, value: &'b str) -> (BString, StateRef<'b>) { + (attr.into(), StateRef::Value(value.as_bytes().as_bstr())) } fn pattern(name: &str, flags: git_glob::pattern::Mode, first_wildcard_pos: Option) -> parse::Kind { @@ -275,10 +287,6 @@ fn pattern(name: &str, flags: git_glob::pattern::Mode, first_wildcard_pos: Optio }) } -fn macro_(name: &str) -> parse::Kind { - parse::Kind::Macro(name.into()) -} - fn try_line(input: &str) -> Result { let mut lines = git_attributes::parse(input.as_bytes()); let res = expand(lines.next().unwrap())?; @@ -299,7 +307,7 @@ fn expand( ) -> Result, parse::Error> { let (pattern, attrs, line_no) = input?; let attrs = attrs - .map(|r| r.map(|attr| (attr.name(), attr.state()))) + .map(|r| r.map(|attr| (attr.name.inner().into(), attr.state))) .collect::, _>>() .map_err(|e| parse::Error::AttributeName { attribute: e.attribute, diff --git a/git-pathspec/src/lib.rs b/git-pathspec/src/lib.rs index 3a81b0700c6..c9c677741ff 100644 --- a/git-pathspec/src/lib.rs +++ b/git-pathspec/src/lib.rs @@ -2,7 +2,7 @@ use bitflags::bitflags; use bstr::BString; -use git_attributes::Name; +use git_attributes::Assignment; pub mod parse; @@ -16,7 +16,7 @@ pub struct Pattern { /// The search mode of the pathspec. pub search_mode: SearchMode, /// All attributes that were included in the `ATTR` part of the pathspec, if present. - pub attributes: Vec, + pub attributes: Vec, } bitflags! { diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index 089b9e02585..5bab4910cca 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -143,7 +143,7 @@ fn split_on_non_escaped_char(input: &[u8], split_char: u8) -> Vec<&[u8]> { keywords } -fn parse_attributes(input: &[u8]) -> Result, Error> { +fn parse_attributes(input: &[u8]) -> Result, Error> { if input.is_empty() { return Err(Error::EmptyAttribute); } diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index 23efdd1ad3c..5c07289a083 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -24,7 +24,7 @@ mod parse { attributes: p .attributes .into_iter() - .map(|v| (v.name().to_owned(), v.state().to_owned())) + .map(|v| (v.name.inner().to_owned(), v.state)) .collect(), } } @@ -439,24 +439,6 @@ mod parse { assert!(output.is_err()); assert!(matches!(output.unwrap_err(), Error::IncompatibleSearchModes)); } - - // #[test] - // fn fuzzer() { - // let input = r":(attr:=\d t= �=\d)"; - // let input_raw = vec![ - // 58, 40, 97, 116, 116, 114, 58, 61, 92, 100, 32, 116, 61, 32, 247, 61, 92, 100, 41, - // ]; - - // // assert!( - // // !check_against_baseline(input), - // // "This pathspec is valid in git: {}", - // // input - // // ); - - // let output = git_pathspec::parse(&input_raw); - // assert!(output.is_err()); - // assert!(matches!(output.unwrap_err(), Error::InvalidAttributeValue { .. })); - // } } fn check_valid_inputs(inputs: Vec<(&str, PatternForTesting)>) { From b7baaa5353aa52ca97488993b50cef72248387de Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Thu, 14 Jul 2022 13:05:35 +0200 Subject: [PATCH 070/248] remove prefix stuff --- git-pathspec/src/parse.rs | 3 --- git-pathspec/tests/pathspec.rs | 8 -------- 2 files changed, 11 deletions(-) diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index 5bab4910cca..582875e6aa8 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -114,9 +114,6 @@ fn parse_long_keywords(input: &[u8], p: &mut Pattern, cursor: &mut usize) -> Res return Err(Error::MultipleAttributeSpecifications); } } - _ if keyword.starts_with(b"prefix:") => { - // TODO: Needs research - what does 'prefix:' do - } _ => { return Err(Error::InvalidKeyword { keyword: BString::from(keyword), diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index 5c07289a083..2743b1ba8fe 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -236,14 +236,6 @@ mod parse { check_valid_inputs(inputs) } - - #[test] - #[ignore] - fn prefix() { - let inputs = vec![(r":(prefix:)", pat_with_path(""))]; - - check_valid_inputs(inputs) - } } mod fail { From 9cb9acb7b7ebada4d6bb3eef199337912ceeaa36 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 14 Jul 2022 21:50:47 +0800 Subject: [PATCH 071/248] 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 072/248] 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 073/248] 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 074/248] 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 075/248] 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 076/248] 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 077/248] 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 078/248] 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 079/248] 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 080/248] 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 081/248] 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 082/248] 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 083/248] 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 084/248] 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 085/248] 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 086/248] 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 087/248] 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 088/248] 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 089/248] 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 090/248] 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 091/248] 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 092/248] 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 093/248] 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 094/248] 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 095/248] 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 096/248] 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 097/248] 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 098/248] 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 099/248] 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 100/248] 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 101/248] 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 102/248] 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 103/248] `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 104/248] 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 105/248] =?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 106/248] 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 107/248] '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 108/248] 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 109/248] 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 110/248] 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 111/248] 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 112/248] 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 113/248] 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 114/248] 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 115/248] 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 116/248] `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 117/248] 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 118/248] 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 119/248] 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 120/248] 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 121/248] 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 122/248] 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 123/248] 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 124/248] 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 125/248] 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 126/248] 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 127/248] 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 128/248] 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 129/248] 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 130/248] 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 131/248] 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 913d94c3c226aac321de12eb7ae8bfa3ee3458e9 Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Tue, 19 Jul 2022 11:28:58 +0200 Subject: [PATCH 132/248] add readme file --- git-pathspec/README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 git-pathspec/README.md diff --git a/git-pathspec/README.md b/git-pathspec/README.md new file mode 100644 index 00000000000..a5b3b9ac690 --- /dev/null +++ b/git-pathspec/README.md @@ -0,0 +1,11 @@ +# git-pathspec + +## TODOs + +- [x] parsing +- [ ] matching + +**Note** +- There is one additional keyword that `git` can parse, but that this crate doesn't support yet: the `prefix` keyword + + [Here is a commit](https://github.com/git/git/commit/5be4efbefafcd5b81fe3d97e8395da1887b4902a) in which `prefix` is somewhat explained. \ No newline at end of file From 45c964a3f581dc7d3090bbbe26f188d553783fb3 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 19 Jul 2022 17:31:57 +0800 Subject: [PATCH 133/248] 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 134/248] 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 135/248] 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 136/248] =?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 137/248] 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 138/248] 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 139/248] 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 140/248] 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 141/248] 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 142/248] 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 143/248] 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 144/248] 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 145/248] 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 146/248] 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 147/248] 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 148/248] 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 149/248] 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 150/248] 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 151/248] 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 152/248] 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 153/248] 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 154/248] 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 155/248] 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 156/248] 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 157/248] 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 158/248] 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 159/248] 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 160/248] 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 161/248] 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 162/248] 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 163/248] 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 164/248] 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 165/248] 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 166/248] 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 167/248] 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 168/248] 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 169/248] 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 170/248] 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 171/248] 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 172/248] 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 173/248] 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 174/248] 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 175/248] 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 176/248] 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 177/248] 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 178/248] 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 179/248] 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 180/248] 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 181/248] 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 182/248] 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 183/248] 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 184/248] 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 185/248] 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 186/248] 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 187/248] 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 188/248] 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 189/248] 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 190/248] 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 191/248] 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 192/248] 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 193/248] 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 194/248] 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 195/248] 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 196/248] 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 197/248] 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 198/248] 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 199/248] 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 200/248] 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 201/248] 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 63baa752901388a46a4211c70f3b3a64aa36d4ec Mon Sep 17 00:00:00 2001 From: Sidney Douw Date: Thu, 21 Jul 2022 12:11:12 +0200 Subject: [PATCH 202/248] refactor --- git-attributes/src/match_group.rs | 2 +- git-attributes/src/name.rs | 16 ++++++++++++---- git-attributes/src/parse/attribute.rs | 4 ++-- git-attributes/tests/parse/attribute.rs | 6 +++--- git-pathspec/tests/pathspec.rs | 2 +- 5 files changed, 19 insertions(+), 11 deletions(-) diff --git a/git-attributes/src/match_group.rs b/git-attributes/src/match_group.rs index 28d3feca029..685d1e1784f 100644 --- a/git-attributes/src/match_group.rs +++ b/git-attributes/src/match_group.rs @@ -66,7 +66,7 @@ impl Pattern for Attributes { let (pattern, value) = match pattern_kind { crate::parse::Kind::Macro(macro_name) => ( git_glob::Pattern { - text: macro_name.inner().to_owned(), + text: macro_name.as_bstring(), mode: git_glob::pattern::Mode::all(), first_wildcard_pos: None, }, diff --git a/git-attributes/src/name.rs b/git-attributes/src/name.rs index 1ce3ec38831..a05744b4e07 100644 --- a/git-attributes/src/name.rs +++ b/git-attributes/src/name.rs @@ -1,14 +1,18 @@ use crate::{Name, NameRef}; -use bstr::{BStr, BString, ByteSlice}; +use bstr::BString; impl<'a> NameRef<'a> { pub fn to_owned(self) -> Name { Name(self.0.into()) } - pub fn inner(&self) -> &str { + pub fn as_str(&self) -> &str { self.0 } + + pub fn as_bstring(self) -> BString { + self.0.into() + } } impl<'a> Name { @@ -16,8 +20,12 @@ impl<'a> Name { NameRef(self.0.as_ref()) } - pub fn inner(&'a self) -> &'a BStr { - self.0.as_bytes().as_bstr() + pub fn as_str(&self) -> &str { + self.0.as_str() + } + + pub fn as_bstring(self) -> BString { + self.0.as_str().into() } } diff --git a/git-attributes/src/parse/attribute.rs b/git-attributes/src/parse/attribute.rs index 89dbba37806..a57f4fc23c4 100644 --- a/git-attributes/src/parse/attribute.rs +++ b/git-attributes/src/parse/attribute.rs @@ -53,7 +53,7 @@ impl<'a> Iter<'a> { ( attr, possibly_value - .map(|v| crate::StateRef::Value(v.as_bstr())) + .map(|v| StateRef::Value(v.as_bstr())) .unwrap_or(StateRef::Set), ) }; @@ -136,7 +136,7 @@ fn parse_line(line: &BStr, line_number: usize) -> Option, let kind_res = match line.strip_prefix(b"[attr]") { Some(macro_name) => check_attr(macro_name.into()) - .map(|m| Kind::Macro(m.to_owned())) + .map(|name| Kind::Macro(name.to_owned())) .map_err(|err| Error::MacroName { line_number, macro_name: err.attribute, diff --git a/git-attributes/tests/parse/attribute.rs b/git-attributes/tests/parse/attribute.rs index 0ac40a6e8e2..96156fa8f2f 100644 --- a/git-attributes/tests/parse/attribute.rs +++ b/git-attributes/tests/parse/attribute.rs @@ -124,7 +124,7 @@ fn custom_macros_can_be_differentiated() { parse::Kind::Pattern(_) => unreachable!(), parse::Kind::Macro(name) => { assert_eq!( - (name.inner().to_str().expect("no illformed utf-8"), output.1, output.2), + (name.as_str(), output.1, output.2), (r"foo", vec![set("bar"), unset("baz")], 1) ); } @@ -135,7 +135,7 @@ fn custom_macros_can_be_differentiated() { parse::Kind::Pattern(_) => unreachable!(), parse::Kind::Macro(name) => { assert_eq!( - (name.inner().to_str().expect("no illformed utf-8"), output.1, output.2), + (name.as_str(), output.1, output.2), (r"foo", vec![set("bar"), unset("baz")], 1), "it works after unquoting even, making it harder to denote a file name with [attr] prefix" ); @@ -307,7 +307,7 @@ fn expand( ) -> Result, parse::Error> { let (pattern, attrs, line_no) = input?; let attrs = attrs - .map(|r| r.map(|attr| (attr.name.inner().into(), attr.state))) + .map(|r| r.map(|attr| (attr.name.as_bstring(), attr.state))) .collect::, _>>() .map_err(|e| parse::Error::AttributeName { attribute: e.attribute, diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index 2743b1ba8fe..3573de11811 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -24,7 +24,7 @@ mod parse { attributes: p .attributes .into_iter() - .map(|v| (v.name.inner().to_owned(), v.state)) + .map(|attr| (attr.name.as_bstring(), attr.state)) .collect(), } } From 780f14f5c270802e51cf039639c2fbdb5ac5a85e Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 21 Jul 2022 17:48:37 +0800 Subject: [PATCH 203/248] 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 204/248] 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 205/248] 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 206/248] 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 207/248] 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 208/248] 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 209/248] 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 210/248] 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 211/248] 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 212/248] 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 213/248] 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 214/248] 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 215/248] 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 216/248] 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 217/248] 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 218/248] 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 219/248] 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 220/248] 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 221/248] 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 222/248] 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 223/248] 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 224/248] 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 225/248] 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 226/248] 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 227/248] 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 228/248] 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 229/248] 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 230/248] 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 231/248] 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 232/248] 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 From 1dd9f9a9320813fe5b40578ee4826a1da575c05c Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 24 Jul 2022 08:59:08 +0800 Subject: [PATCH 233/248] commit strange crlf-file that makes everything impossible It's definitely a local issue, too. --- 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 07352ceb942dac5041fbf8584128404880166593 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 24 Jul 2022 09:03:00 +0800 Subject: [PATCH 234/248] update crate status and READMe --- crate-status.md | 4 ++-- git-pathspec/README.md | 9 ++------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/crate-status.md b/crate-status.md index c8bb05938c8..0e0d9f907c1 100644 --- a/crate-status.md +++ b/crate-status.md @@ -231,7 +231,7 @@ Check out the [performance discussion][git-traverse-performance] as well. * [ ] check for match ### git-pathspec -* [ ] parse +* [x] parse * [ ] check for match ### git-note @@ -239,7 +239,7 @@ Check out the [performance discussion][git-traverse-performance] as well. A mechanism to associate metadata with any object, and keep revisions of it using git itself. * [ ] CRUD for git notes -* + ### git-discover * [x] check if a git directory is a git repository diff --git a/git-pathspec/README.md b/git-pathspec/README.md index a5b3b9ac690..be47c97ad71 100644 --- a/git-pathspec/README.md +++ b/git-pathspec/README.md @@ -1,11 +1,6 @@ -# git-pathspec - -## TODOs - -- [x] parsing -- [ ] matching +# `git-pathspec` **Note** - There is one additional keyword that `git` can parse, but that this crate doesn't support yet: the `prefix` keyword - [Here is a commit](https://github.com/git/git/commit/5be4efbefafcd5b81fe3d97e8395da1887b4902a) in which `prefix` is somewhat explained. \ No newline at end of file + [Here is a commit](https://github.com/git/git/commit/5be4efbefafcd5b81fe3d97e8395da1887b4902a) in which `prefix` is somewhat explained. From 1cbc142d37599f4d7bfaf9cb07de41ee4b3f4c24 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 24 Jul 2022 09:10:15 +0800 Subject: [PATCH 235/248] refactor Note that `as_bstring()` cannot happens as `as` implies a cheap and trivial conversion, usually no more than a reference to something. --- git-attributes/src/match_group.rs | 2 +- git-attributes/src/name.rs | 12 ++++++++---- git-attributes/tests/parse/attribute.rs | 2 +- git-pathspec/tests/pathspec.rs | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/git-attributes/src/match_group.rs b/git-attributes/src/match_group.rs index 685d1e1784f..77c10ab85ca 100644 --- a/git-attributes/src/match_group.rs +++ b/git-attributes/src/match_group.rs @@ -66,7 +66,7 @@ impl Pattern for Attributes { let (pattern, value) = match pattern_kind { crate::parse::Kind::Macro(macro_name) => ( git_glob::Pattern { - text: macro_name.as_bstring(), + text: macro_name.as_str().into(), mode: git_glob::pattern::Mode::all(), first_wildcard_pos: None, }, diff --git a/git-attributes/src/name.rs b/git-attributes/src/name.rs index a05744b4e07..fa6ca7b9251 100644 --- a/git-attributes/src/name.rs +++ b/git-attributes/src/name.rs @@ -9,9 +9,11 @@ impl<'a> NameRef<'a> { pub fn as_str(&self) -> &str { self.0 } +} - pub fn as_bstring(self) -> BString { - self.0.into() +impl AsRef for NameRef<'_> { + fn as_ref(&self) -> &str { + self.0 } } @@ -23,9 +25,11 @@ impl<'a> Name { pub fn as_str(&self) -> &str { self.0.as_str() } +} - pub fn as_bstring(self) -> BString { - self.0.as_str().into() +impl AsRef for Name { + fn as_ref(&self) -> &str { + self.0.as_str() } } diff --git a/git-attributes/tests/parse/attribute.rs b/git-attributes/tests/parse/attribute.rs index 96156fa8f2f..1c6b9ca90a3 100644 --- a/git-attributes/tests/parse/attribute.rs +++ b/git-attributes/tests/parse/attribute.rs @@ -307,7 +307,7 @@ fn expand( ) -> Result, parse::Error> { let (pattern, attrs, line_no) = input?; let attrs = attrs - .map(|r| r.map(|attr| (attr.name.as_bstring(), attr.state))) + .map(|r| r.map(|attr| (attr.name.as_str().into(), attr.state))) .collect::, _>>() .map_err(|e| parse::Error::AttributeName { attribute: e.attribute, diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index 3573de11811..6a0b5be1d36 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -24,7 +24,7 @@ mod parse { attributes: p .attributes .into_iter() - .map(|attr| (attr.name.as_bstring(), attr.state)) + .map(|attr| (attr.name.as_str().into(), attr.state)) .collect(), } } From 0eabea9772ce67f70442bc8ded02a7e82f5c17cc Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 24 Jul 2022 09:45:37 +0800 Subject: [PATCH 236/248] Add docs for `git-attributes` --- README.md | 2 +- git-attributes/src/assignment.rs | 2 ++ git-attributes/src/lib.rs | 15 ++++++++-- git-attributes/src/match_group.rs | 40 ++++++++++++++++++--------- git-attributes/src/name.rs | 6 ++++ git-attributes/src/parse/attribute.rs | 17 ++++++++---- git-attributes/src/parse/ignore.rs | 2 ++ git-attributes/src/parse/mod.rs | 6 ++-- git-attributes/src/state.rs | 2 ++ 9 files changed, 68 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 489c9df4fb0..977aa77160a 100644 --- a/README.md +++ b/README.md @@ -128,12 +128,12 @@ is usable to some extend. * [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) + * [git-attributes](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-attributes) * `gitoxide-core` * **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-pathspec](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-pathspec) * [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) diff --git a/git-attributes/src/assignment.rs b/git-attributes/src/assignment.rs index 5ae70a4fdbd..e1d7263f768 100644 --- a/git-attributes/src/assignment.rs +++ b/git-attributes/src/assignment.rs @@ -5,6 +5,7 @@ impl<'a> AssignmentRef<'a> { AssignmentRef { name, state } } + /// Turn this reference into its owned counterpart. pub fn to_owned(self) -> Assignment { self.into() } @@ -20,6 +21,7 @@ impl<'a> From> for Assignment { } impl<'a> Assignment { + /// Provide a ref type to this owned instance. pub fn as_ref(&'a self) -> AssignmentRef<'a> { AssignmentRef::new(self.name.as_ref(), self.state.as_ref()) } diff --git a/git-attributes/src/lib.rs b/git-attributes/src/lib.rs index 2ad92cbe16b..8d472794b38 100644 --- a/git-attributes/src/lib.rs +++ b/git-attributes/src/lib.rs @@ -1,10 +1,12 @@ +//! Parse `.gitattribute` and `.gitignore` files and provide utilities to match against them. +//! //! ## Feature Flags #![cfg_attr( feature = "document-features", cfg_attr(doc, doc = ::document_features::document_features!()) )] #![forbid(unsafe_code)] -#![deny(rust_2018_idioms)] +#![deny(rust_2018_idioms, missing_docs)] use std::path::PathBuf; @@ -13,15 +15,18 @@ use compact_str::CompactString; pub use git_glob as glob; mod assignment; +/// pub mod name; mod state; mod match_group; pub use match_group::{Attributes, Ignore, Match, Pattern}; +/// pub mod parse; -pub fn parse(buf: &[u8]) -> parse::Lines<'_> { - parse::Lines::new(buf) +/// Parse attribute assignments line by line from `bytes`. +pub fn parse(bytes: &[u8]) -> parse::Lines<'_> { + parse::Lines::new(bytes) } /// The state an attribute can be in, referencing the value. @@ -120,9 +125,13 @@ pub struct PatternList { pub base: Option, } +/// An association of a pattern with its value, along with a sequence number providing a sort order in relation to its peers. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] pub struct PatternMapping { + /// The pattern itself, like `/target/*` pub pattern: git_glob::Pattern, + /// The value associated with the pattern. pub value: T, + /// Typically the line number in the file the pattern was parsed from. pub sequence_number: usize, } diff --git a/git-attributes/src/match_group.rs b/git-attributes/src/match_group.rs index 77c10ab85ca..7732762d0af 100644 --- a/git-attributes/src/match_group.rs +++ b/git-attributes/src/match_group.rs @@ -6,13 +6,15 @@ use std::{ path::{Path, PathBuf}, }; -fn attrs_to_assignments<'a>( +fn into_owned_assignments<'a>( attrs: impl Iterator, crate::name::Error>>, ) -> Result, crate::name::Error> { attrs.map(|res| res.map(|attr| attr.to_owned())).collect() } -/// A marker trait to identify the type of a description. +/// A trait to convert bytes into patterns and their associated value. +/// +/// This is used for `gitattributes` which have a value, and `gitignore` which don't. pub trait Pattern: Clone + PartialEq + Eq + std::fmt::Debug + std::hash::Hash + Ord + PartialOrd + Default { /// The value associated with a pattern. type Value: PartialEq + Eq + std::fmt::Debug + std::hash::Hash + Ord + PartialOrd + Clone; @@ -20,10 +22,11 @@ pub trait Pattern: Clone + PartialEq + Eq + std::fmt::Debug + std::hash::Hash + /// Parse all patterns in `bytes` line by line, ignoring lines with errors, and collect them. fn bytes_to_patterns(bytes: &[u8]) -> Vec>; - fn use_pattern(pattern: &git_glob::Pattern) -> bool; + /// Returns true if the given pattern may be used for matching. + fn may_use_glob_pattern(pattern: &git_glob::Pattern) -> bool; } -/// Identify ignore patterns. +/// An implementation of the [`Pattern`] trait for ignore patterns. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Default)] pub struct Ignore; @@ -40,7 +43,7 @@ impl Pattern for Ignore { .collect() } - fn use_pattern(_pattern: &git_glob::Pattern) -> bool { + fn may_use_glob_pattern(_pattern: &git_glob::Pattern) -> bool { true } } @@ -49,10 +52,10 @@ impl Pattern for Ignore { #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] pub enum Value { MacroAttributes(Vec), - Attributes(Vec), + Assignments(Vec), } -/// Identify patterns with attributes. +/// An implementation of the [`Pattern`] trait for attributes. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Default)] pub struct Attributes; @@ -62,7 +65,7 @@ impl Pattern for Attributes { fn bytes_to_patterns(bytes: &[u8]) -> Vec> { crate::parse(bytes) .filter_map(Result::ok) - .filter_map(|(pattern_kind, attrs, line_number)| { + .filter_map(|(pattern_kind, assignments, line_number)| { let (pattern, value) = match pattern_kind { crate::parse::Kind::Macro(macro_name) => ( git_glob::Pattern { @@ -70,11 +73,11 @@ impl Pattern for Attributes { mode: git_glob::pattern::Mode::all(), first_wildcard_pos: None, }, - Value::MacroAttributes(attrs_to_assignments(attrs).ok()?), + Value::MacroAttributes(into_owned_assignments(assignments).ok()?), ), crate::parse::Kind::Pattern(p) => ( (!p.is_negative()).then(|| p)?, - Value::Attributes(attrs_to_assignments(attrs).ok()?), + Value::Assignments(into_owned_assignments(assignments).ok()?), ), }; PatternMapping { @@ -87,7 +90,7 @@ impl Pattern for Attributes { .collect() } - fn use_pattern(pattern: &git_glob::Pattern) -> bool { + fn may_use_glob_pattern(pattern: &git_glob::Pattern) -> bool { pattern.mode != git_glob::pattern::Mode::all() } } @@ -95,6 +98,7 @@ impl Pattern for Attributes { /// Describes a matching value within a [`MatchGroup`]. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] pub struct Match<'a, T> { + /// The glob pattern itself, like `/target/*`. pub pattern: &'a git_glob::Pattern, /// The value associated with the pattern. pub value: &'a T, @@ -179,6 +183,8 @@ impl MatchGroup { Ok(self.patterns.len() != previous_len) } + /// Add patterns as parsed from `bytes`, providing their `source` path and possibly their `root` path, the path they + /// are relative to. This also means that `source` is contained within `root` if `root` is provided. pub fn add_patterns_buffer(&mut self, bytes: &[u8], source: impl Into, root: Option<&Path>) { self.patterns .push(PatternList::::from_bytes(bytes, source.into(), root)); @@ -228,6 +234,9 @@ where base, } } + + /// Create a pattern list from the `source` file, which may be located underneath `root`, while optionally + /// following symlinks with `follow_symlinks`, providing `buf` to temporarily store the data contained in the file. pub fn from_file( source: impl Into, root: Option<&Path>, @@ -243,6 +252,9 @@ impl PatternList where T: Pattern, { + /// Return a match if a pattern matches `relative_path`, providing a pre-computed `basename_pos` which is the + /// starting position of the basename of `relative_path`. `is_dir` is true if `relative_path` is a directory. + /// `case` specifies whether cases should be folded during matching or not. pub fn pattern_matching_relative_path( &self, relative_path: &BStr, @@ -255,7 +267,7 @@ where self.patterns .iter() .rev() - .filter(|pm| T::use_pattern(&pm.pattern)) + .filter(|pm| T::may_use_glob_pattern(&pm.pattern)) .find_map( |PatternMapping { pattern, @@ -274,6 +286,8 @@ where ) } + /// Like [`pattern_matching_relative_path()`][Self::pattern_matching_relative_path()], but returns an index to the pattern + /// that matched `relative_path`, instead of the match itself. pub fn pattern_idx_matching_relative_path( &self, relative_path: &BStr, @@ -287,7 +301,7 @@ where .iter() .enumerate() .rev() - .filter(|(_, pm)| T::use_pattern(&pm.pattern)) + .filter(|(_, pm)| T::may_use_glob_pattern(&pm.pattern)) .find_map(|(idx, pm)| { pm.pattern .matches_repo_relative_path(relative_path, basename_start_pos, is_dir, case) diff --git a/git-attributes/src/name.rs b/git-attributes/src/name.rs index fa6ca7b9251..bc213f88ed9 100644 --- a/git-attributes/src/name.rs +++ b/git-attributes/src/name.rs @@ -2,10 +2,12 @@ use crate::{Name, NameRef}; use bstr::BString; impl<'a> NameRef<'a> { + /// Turn this ref into its owned counterpart. pub fn to_owned(self) -> Name { Name(self.0.into()) } + /// Return the inner `str`. pub fn as_str(&self) -> &str { self.0 } @@ -18,10 +20,12 @@ impl AsRef for NameRef<'_> { } impl<'a> Name { + /// Provide our ref-type. pub fn as_ref(&'a self) -> NameRef<'a> { NameRef(self.0.as_ref()) } + /// Return the inner `str`. pub fn as_str(&self) -> &str { self.0.as_str() } @@ -33,8 +37,10 @@ impl AsRef for Name { } } +/// The error returned by [`parse::Iter`][crate::parse::Iter]. #[derive(Debug, thiserror::Error)] #[error("Attribute has non-ascii characters or starts with '-': {attribute}")] pub struct Error { + /// The attribute that failed to parse. pub attribute: BString, } diff --git a/git-attributes/src/parse/attribute.rs b/git-attributes/src/parse/attribute.rs index a57f4fc23c4..148eb760539 100644 --- a/git-attributes/src/parse/attribute.rs +++ b/git-attributes/src/parse/attribute.rs @@ -2,6 +2,7 @@ use crate::{name, AssignmentRef, Name, NameRef, StateRef}; use bstr::{BStr, ByteSlice}; use std::borrow::Cow; +/// The kind of attribute that was parsed. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub enum Kind { @@ -13,7 +14,9 @@ pub enum Kind { mod error { use bstr::BString; + /// The error returned by [`parse::Lines`][crate::parse::Lines]. #[derive(thiserror::Error, Debug)] + #[allow(missing_docs)] pub enum Error { #[error("Line {line_number} has a negative pattern, for literal characters use \\!: {line}")] PatternNegation { line_number: usize, line: BString }, @@ -27,18 +30,21 @@ mod error { } pub use error::Error; +/// An iterator over attribute assignments, parsed line by line. pub struct Lines<'a> { lines: bstr::Lines<'a>, line_no: usize, } +/// An iterator over attribute assignments in a single line. pub struct Iter<'a> { attrs: bstr::Fields<'a>, } impl<'a> Iter<'a> { - pub fn new(attrs: &'a BStr) -> Self { - Iter { attrs: attrs.fields() } + /// Create a new instance to parse attribute assignments from `input`. + pub fn new(input: &'a BStr) -> Self { + Iter { attrs: input.fields() } } fn parse_attr(&self, attr: &'a [u8]) -> Result, name::Error> { @@ -86,10 +92,11 @@ impl<'a> Iterator for Iter<'a> { } impl<'a> Lines<'a> { - pub fn new(buf: &'a [u8]) -> Self { - let bom = unicode_bom::Bom::from(buf); + /// Create a new instance to parse all attributes in all lines of the input `bytes`. + pub fn new(bytes: &'a [u8]) -> Self { + let bom = unicode_bom::Bom::from(bytes); Lines { - lines: buf[bom.len()..].lines(), + lines: bytes[bom.len()..].lines(), line_no: 0, } } diff --git a/git-attributes/src/parse/ignore.rs b/git-attributes/src/parse/ignore.rs index 71c29904ba2..d9d9e2038c0 100644 --- a/git-attributes/src/parse/ignore.rs +++ b/git-attributes/src/parse/ignore.rs @@ -1,11 +1,13 @@ use bstr::ByteSlice; +/// An iterator over line-wise ignore patterns parsed from a buffer. pub struct Lines<'a> { lines: bstr::Lines<'a>, line_no: usize, } impl<'a> Lines<'a> { + /// Create a new instance from `buf` to parse ignore patterns from. pub fn new(buf: &'a [u8]) -> Self { let bom = unicode_bom::Bom::from(buf); Lines { diff --git a/git-attributes/src/parse/mod.rs b/git-attributes/src/parse/mod.rs index 5bbd86f5bd4..82cacc8ed02 100644 --- a/git-attributes/src/parse/mod.rs +++ b/git-attributes/src/parse/mod.rs @@ -1,8 +1,10 @@ +/// pub mod ignore; mod attribute; pub use attribute::{Error, Iter, Kind, Lines}; -pub fn ignore(buf: &[u8]) -> ignore::Lines<'_> { - ignore::Lines::new(buf) +/// Parse git ignore patterns, line by line, from `bytes`. +pub fn ignore(bytes: &[u8]) -> ignore::Lines<'_> { + ignore::Lines::new(bytes) } diff --git a/git-attributes/src/state.rs b/git-attributes/src/state.rs index 363a625dede..3ae275492e6 100644 --- a/git-attributes/src/state.rs +++ b/git-attributes/src/state.rs @@ -2,12 +2,14 @@ use crate::{State, StateRef}; use bstr::ByteSlice; impl<'a> StateRef<'a> { + /// Turn ourselves into our owned counterpart. pub fn to_owned(self) -> State { self.into() } } impl<'a> State { + /// Turn ourselves into our ref-type. pub fn as_ref(&'a self) -> StateRef<'a> { match self { State::Value(v) => StateRef::Value(v.as_bytes().as_bstr()), From ef03823b407bcdbac16c42e941809dfe7cde850b Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 24 Jul 2022 09:49:03 +0800 Subject: [PATCH 237/248] rename `parser` fuzz target to `parse` Prefer verbs over nouns, for a shorter way of doing the same thing. It's also a small contribution towards a more functional world. --- git-pathspec/fuzz/Cargo.lock | 8 ++++---- git-pathspec/fuzz/Cargo.toml | 4 ++-- git-pathspec/fuzz/fuzz_targets/{parser.rs => parse.rs} | 0 3 files changed, 6 insertions(+), 6 deletions(-) rename git-pathspec/fuzz/fuzz_targets/{parser.rs => parse.rs} (100%) diff --git a/git-pathspec/fuzz/Cargo.lock b/git-pathspec/fuzz/Cargo.lock index 50c258223a1..b4f41b924a5 100644 --- a/git-pathspec/fuzz/Cargo.lock +++ b/git-pathspec/fuzz/Cargo.lock @@ -80,7 +80,7 @@ dependencies = [ [[package]] name = "git-features" -version = "0.21.1" +version = "0.22.0" dependencies = [ "git-hash", "libc", @@ -88,7 +88,7 @@ dependencies = [ [[package]] name = "git-glob" -version = "0.3.0" +version = "0.3.1" dependencies = [ "bitflags", "bstr", @@ -96,7 +96,7 @@ dependencies = [ [[package]] name = "git-hash" -version = "0.9.5" +version = "0.9.6" dependencies = [ "hex", "quick-error", @@ -104,7 +104,7 @@ dependencies = [ [[package]] name = "git-path" -version = "0.3.0" +version = "0.4.0" dependencies = [ "bstr", "thiserror", diff --git a/git-pathspec/fuzz/Cargo.toml b/git-pathspec/fuzz/Cargo.toml index ac6f7aac963..16240362973 100644 --- a/git-pathspec/fuzz/Cargo.toml +++ b/git-pathspec/fuzz/Cargo.toml @@ -19,7 +19,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-pathspec/fuzz/fuzz_targets/parser.rs b/git-pathspec/fuzz/fuzz_targets/parse.rs similarity index 100% rename from git-pathspec/fuzz/fuzz_targets/parser.rs rename to git-pathspec/fuzz/fuzz_targets/parse.rs From 0c9bef47a70a0787b63f9bf8e9b52f2ab9f72738 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 24 Jul 2022 09:52:30 +0800 Subject: [PATCH 238/248] add basic docs for how to run the fuzzer --- git-pathspec/README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/git-pathspec/README.md b/git-pathspec/README.md index be47c97ad71..47bcb5b62bd 100644 --- a/git-pathspec/README.md +++ b/git-pathspec/README.md @@ -1,6 +1,16 @@ # `git-pathspec` -**Note** +### Testing + +#### Fuzzing + +`cargo fuzz` is used for fuzzing, installable with `cargo install cargo-fuzz`. + +Targets can be listed with `cargo fuzz list` and executed via `cargo +nightly fuzz run `, +where `` can be `parse` for example. + +### Notes + - There is one additional keyword that `git` can parse, but that this crate doesn't support yet: the `prefix` keyword [Here is a commit](https://github.com/git/git/commit/5be4efbefafcd5b81fe3d97e8395da1887b4902a) in which `prefix` is somewhat explained. From 4f6fa59d20b4e03663f3d7c3819a7d02b79ab982 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 24 Jul 2022 12:47:45 +0800 Subject: [PATCH 239/248] add documentation; rename `SearchMode` to `MatchMode`; add test --- git-pathspec/src/lib.rs | 17 +++++--- git-pathspec/src/parse.rs | 17 ++++---- .../fixtures/generate_pathspec_baseline.sh | 5 ++- .../generate_pathspec_baseline.tar.xz | 2 +- git-pathspec/tests/pathspec.rs | 39 +++++++++++-------- 5 files changed, 49 insertions(+), 31 deletions(-) diff --git a/git-pathspec/src/lib.rs b/git-pathspec/src/lib.rs index c9c677741ff..cec5a3c62aa 100644 --- a/git-pathspec/src/lib.rs +++ b/git-pathspec/src/lib.rs @@ -1,9 +1,13 @@ +//! Parse [path specifications](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefpathspecapathspec) and +//! see if a path matches. #![forbid(unsafe_code, rust_2018_idioms)] +#![deny(missing_docs)] use bitflags::bitflags; use bstr::BString; use git_attributes::Assignment; +/// pub mod parse; /// The output of a pathspec parsing operation. It can be used to match against a path / multiple paths. @@ -14,12 +18,13 @@ pub struct Pattern { /// All magic signatures that were included in the pathspec. pub signature: MagicSignature, /// The search mode of the pathspec. - pub search_mode: SearchMode, + pub search_mode: MatchMode, /// All attributes that were included in the `ATTR` part of the pathspec, if present. pub attributes: Vec, } bitflags! { + /// Flags to represent 'magic signatures' which are parsed behind colons, like `:top:`. pub struct MagicSignature: u32 { /// Matches patterns from the root of the repository const TOP = 1 << 0; @@ -30,8 +35,10 @@ bitflags! { } } +/// Parts of [magic signatures][MagicSignature] which don't stack as they all configure +/// the way path specs are matched. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] -pub enum SearchMode { +pub enum MatchMode { /// Expand special characters like `*` similar to how the shell would do it. /// /// See [`PathAwareGlob`][SearchMode::PathAwareGlob] for the alternative. @@ -42,13 +49,13 @@ pub enum SearchMode { PathAwareGlob, } -impl Default for SearchMode { +impl Default for MatchMode { fn default() -> Self { - SearchMode::ShellGlob + MatchMode::ShellGlob } } -/// Parse a git-style pathspec into a [`Pattern`][Pattern].` +/// Parse a git-style pathspec into a [`Pattern`][Pattern]. pub fn parse(input: &[u8]) -> Result { Pattern::from_bytes(input) } diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index 582875e6aa8..bf752affe3b 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -1,8 +1,10 @@ -use crate::{MagicSignature, Pattern, SearchMode}; +use crate::{MagicSignature, MatchMode, Pattern}; use bstr::{BStr, BString, ByteSlice, ByteVec}; use std::borrow::Cow; +/// The error returned by [parse()][crate::parse()]. #[derive(thiserror::Error, Debug)] +#[allow(missing_docs)] pub enum Error { #[error("Empty string is not a valid pathspec")] EmptyString, @@ -27,6 +29,7 @@ pub enum Error { } impl Pattern { + /// Try to parse a path-spec pattern from the given `input` bytes. pub fn from_bytes(input: &[u8]) -> Result { if input.is_empty() { return Err(Error::EmptyString); @@ -35,7 +38,7 @@ impl Pattern { let mut p = Pattern { path: BString::default(), signature: MagicSignature::empty(), - search_mode: SearchMode::ShellGlob, + search_mode: MatchMode::ShellGlob, attributes: Vec::new(), }; @@ -87,7 +90,7 @@ fn parse_long_keywords(input: &[u8], p: &mut Pattern, cursor: &mut usize) -> Res let input = &input[*cursor..end]; *cursor = end + 1; - debug_assert_eq!(p.search_mode, SearchMode::default()); + debug_assert_eq!(p.search_mode, MatchMode::default()); if input.is_empty() { return Ok(()); @@ -100,12 +103,12 @@ fn parse_long_keywords(input: &[u8], p: &mut Pattern, cursor: &mut usize) -> Res b"icase" => p.signature |= MagicSignature::ICASE, b"exclude" => p.signature |= MagicSignature::EXCLUDE, b"literal" => match p.search_mode { - SearchMode::PathAwareGlob => return Err(Error::IncompatibleSearchModes), - _ => p.search_mode = SearchMode::Literal, + MatchMode::PathAwareGlob => return Err(Error::IncompatibleSearchModes), + _ => p.search_mode = MatchMode::Literal, }, b"glob" => match p.search_mode { - SearchMode::Literal => return Err(Error::IncompatibleSearchModes), - _ => p.search_mode = SearchMode::PathAwareGlob, + MatchMode::Literal => return Err(Error::IncompatibleSearchModes), + _ => p.search_mode = MatchMode::PathAwareGlob, }, _ if keyword.starts_with(b"attr:") => { if p.attributes.is_empty() { diff --git a/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh b/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh index 134a9e50ba4..b8092dee586 100644 --- a/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh +++ b/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh @@ -15,6 +15,9 @@ function baseline() { # success +# special 'there is no pathspec' spec +baseline ':' + # repeated_matcher_keywords baseline ':(glob,glob)' baseline ':(literal,literal)' @@ -139,4 +142,4 @@ baseline ':(attr:one,attr:two)some/path' baseline ':(top' # glob_and_literal_keywords_present -baseline ':(glob,literal)some/path' \ No newline at end of file +baseline ':(glob,literal)some/path' diff --git a/git-pathspec/tests/fixtures/generated-archives/generate_pathspec_baseline.tar.xz b/git-pathspec/tests/fixtures/generated-archives/generate_pathspec_baseline.tar.xz index 33adf6cf10b..35e00f42ded 100644 --- a/git-pathspec/tests/fixtures/generated-archives/generate_pathspec_baseline.tar.xz +++ b/git-pathspec/tests/fixtures/generated-archives/generate_pathspec_baseline.tar.xz @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f94976c508e1dd577151cd7d271948ba8bf6583de5d3c31a6aa3e314c13112ce +oid sha256:505e0eed20808737e0f3f8b993e54e3b2b9b9d124e1615a003336ce34c60b939 size 9484 diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index 6a0b5be1d36..95fad10d7f2 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -3,7 +3,7 @@ pub use git_testtools::Result; mod parse { use bstr::{BStr, BString, ByteSlice}; use git_attributes::State; - use git_pathspec::{MagicSignature, Pattern, SearchMode}; + use git_pathspec::{MagicSignature, MatchMode, Pattern}; use once_cell::sync::Lazy; use std::collections::HashMap; @@ -11,7 +11,7 @@ mod parse { struct PatternForTesting { path: BString, signature: MagicSignature, - search_mode: SearchMode, + search_mode: MatchMode, attributes: Vec<(BString, State)>, } @@ -52,13 +52,18 @@ mod parse { pat_with_sig, }; use git_attributes::State; - use git_pathspec::{MagicSignature, SearchMode}; + use git_pathspec::{MagicSignature, MatchMode}; + + #[test] + fn there_is_no_pathspec_pathspec() { + check_valid_inputs(Some((":", pat_with_attrs(vec![])))); + } #[test] fn repeated_matcher_keywords() { let input = vec![ - (":(glob,glob)", pat_with_search_mode(SearchMode::PathAwareGlob)), - (":(literal,literal)", pat_with_search_mode(SearchMode::Literal)), + (":(glob,glob)", pat_with_search_mode(MatchMode::PathAwareGlob)), + (":(literal,literal)", pat_with_search_mode(MatchMode::Literal)), (":(top,top)", pat_with_sig(MagicSignature::TOP)), (":(icase,icase)", pat_with_sig(MagicSignature::ICASE)), (":(attr,attr)", pat_with_attrs(vec![])), @@ -134,27 +139,27 @@ mod parse { (":(icase)", pat_with_sig(MagicSignature::ICASE)), (":(attr)", pat_with_path("")), (":(exclude)", pat_with_sig(MagicSignature::EXCLUDE)), - (":(literal)", pat_with_search_mode(SearchMode::Literal)), - (":(glob)", pat_with_search_mode(SearchMode::PathAwareGlob)), + (":(literal)", pat_with_search_mode(MatchMode::Literal)), + (":(glob)", pat_with_search_mode(MatchMode::PathAwareGlob)), ( ":(top,exclude)", pat_with_sig(MagicSignature::TOP | MagicSignature::EXCLUDE), ), ( ":(icase,literal)", - pat("", MagicSignature::ICASE, SearchMode::Literal, vec![]), + pat("", MagicSignature::ICASE, MatchMode::Literal, vec![]), ), ( ":!(literal)some/*path", - pat("some/*path", MagicSignature::EXCLUDE, SearchMode::Literal, vec![]), + pat("some/*path", MagicSignature::EXCLUDE, MatchMode::Literal, vec![]), ), ( ":(top,literal,icase,attr,exclude)some/path", - pat("some/path", MagicSignature::all(), SearchMode::Literal, vec![]), + pat("some/path", MagicSignature::all(), MatchMode::Literal, vec![]), ), ( ":(top,glob,icase,attr,exclude)some/path", - pat("some/path", MagicSignature::all(), SearchMode::PathAwareGlob, vec![]), + pat("some/path", MagicSignature::all(), MatchMode::PathAwareGlob, vec![]), ), ]; @@ -433,7 +438,7 @@ mod parse { } } - fn check_valid_inputs(inputs: Vec<(&str, PatternForTesting)>) { + fn check_valid_inputs<'a>(inputs: impl IntoIterator) { inputs.into_iter().for_each(|(input, expected)| { assert!( check_against_baseline(input), @@ -453,25 +458,25 @@ mod parse { } fn pat_with_path_and_sig(path: &str, signature: MagicSignature) -> PatternForTesting { - pat(path, signature, SearchMode::ShellGlob, vec![]) + pat(path, signature, MatchMode::ShellGlob, vec![]) } fn pat_with_sig(signature: MagicSignature) -> PatternForTesting { - pat("", signature, SearchMode::ShellGlob, vec![]) + pat("", signature, MatchMode::ShellGlob, vec![]) } fn pat_with_attrs(attrs: Vec<(&'static str, State)>) -> PatternForTesting { - pat("", MagicSignature::empty(), SearchMode::ShellGlob, attrs) + pat("", MagicSignature::empty(), MatchMode::ShellGlob, attrs) } - fn pat_with_search_mode(search_mode: SearchMode) -> PatternForTesting { + fn pat_with_search_mode(search_mode: MatchMode) -> PatternForTesting { pat("", MagicSignature::empty(), search_mode, vec![]) } fn pat( path: &str, signature: MagicSignature, - search_mode: SearchMode, + search_mode: MatchMode, attributes: Vec<(&str, State)>, ) -> PatternForTesting { PatternForTesting { From 489972209ee2ead2871ca2410bd10e51019dc9ad Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 24 Jul 2022 16:14:46 +0800 Subject: [PATCH 240/248] assure all baseline samples are validated --- git-pathspec/src/lib.rs | 2 +- git-pathspec/tests/pathspec.rs | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/git-pathspec/src/lib.rs b/git-pathspec/src/lib.rs index cec5a3c62aa..e271b0ffaa9 100644 --- a/git-pathspec/src/lib.rs +++ b/git-pathspec/src/lib.rs @@ -41,7 +41,7 @@ bitflags! { pub enum MatchMode { /// Expand special characters like `*` similar to how the shell would do it. /// - /// See [`PathAwareGlob`][SearchMode::PathAwareGlob] for the alternative. + /// See [`PathAwareGlob`][MatchMode::PathAwareGlob] for the alternative. ShellGlob, /// Special characters in the pattern, like `*` or `?`, are treated literally Literal, diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs index 95fad10d7f2..784562e3576 100644 --- a/git-pathspec/tests/pathspec.rs +++ b/git-pathspec/tests/pathspec.rs @@ -46,6 +46,18 @@ mod parse { .unwrap() }); + #[test] + fn baseline() { + for (pattern, exit_code) in BASELINE.iter() { + let res = git_pathspec::parse(pattern); + assert_eq!( + res.is_ok(), + *exit_code == 0, + "{pattern:?} disagrees with baseline: {res:?}" + ) + } + } + mod succeed { use crate::parse::{ check_valid_inputs, pat, pat_with_attrs, pat_with_path, pat_with_path_and_sig, pat_with_search_mode, From e36d83e62eb7969726e7c8b3d25dbb743a508f8a Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 24 Jul 2022 16:27:39 +0800 Subject: [PATCH 241/248] improve docs and use 'new-style' in error messages. --- git-attributes/src/lib.rs | 2 +- git-pathspec/src/lib.rs | 11 +++++++---- git-pathspec/src/parse.rs | 10 +++++----- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/git-attributes/src/lib.rs b/git-attributes/src/lib.rs index 8d472794b38..6fb14870f67 100644 --- a/git-attributes/src/lib.rs +++ b/git-attributes/src/lib.rs @@ -77,7 +77,7 @@ pub struct NameRef<'a>(&'a str); #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Assignment { - /// The name of the attribute. + /// The validated name of the attribute. pub name: Name, /// The state of the attribute. pub state: State, diff --git a/git-pathspec/src/lib.rs b/git-pathspec/src/lib.rs index e271b0ffaa9..ec3b57dd55b 100644 --- a/git-pathspec/src/lib.rs +++ b/git-pathspec/src/lib.rs @@ -5,22 +5,25 @@ use bitflags::bitflags; use bstr::BString; -use git_attributes::Assignment; /// pub mod parse; -/// The output of a pathspec parsing operation. It can be used to match against a path / multiple paths. +/// The output of a pathspec [parsing][parse()] operation. It can be used to match against a one or more paths. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] pub struct Pattern { /// The path part of a pathspec. + /// + /// `:(top,literal,icase,attr,exclude)some/path` would yield `some/path`. pub path: BString, /// All magic signatures that were included in the pathspec. pub signature: MagicSignature, /// The search mode of the pathspec. pub search_mode: MatchMode, /// All attributes that were included in the `ATTR` part of the pathspec, if present. - pub attributes: Vec, + /// + /// `:(attr:a=one b=):path` would yield attribute `a` and `b`. + pub attributes: Vec, } bitflags! { @@ -43,7 +46,7 @@ pub enum MatchMode { /// /// See [`PathAwareGlob`][MatchMode::PathAwareGlob] for the alternative. ShellGlob, - /// Special characters in the pattern, like `*` or `?`, are treated literally + /// Special characters in the pattern, like `*` or `?`, are treated literally, effectively turning off globbing. Literal, /// A single `*` will not match a `/` in the pattern, but a `**` will PathAwareGlob, diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index bf752affe3b..a021f8fc35c 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -6,17 +6,17 @@ use std::borrow::Cow; #[derive(thiserror::Error, Debug)] #[allow(missing_docs)] pub enum Error { - #[error("Empty string is not a valid pathspec")] + #[error("An empty string is not a valid pathspec")] EmptyString, - #[error("Found {:?} in signature, which is not a valid keyword", keyword)] + #[error("Found {keyword:?} in signature, which is not a valid keyword")] InvalidKeyword { keyword: BString }, - #[error("Unimplemented short keyword: {:?}", short_keyword)] + #[error("Unimplemented short keyword: {short_keyword:?}")] Unimplemented { short_keyword: char }, #[error("Missing ')' at the end of pathspec signature")] MissingClosingParenthesis, - #[error("Attribute has non-ascii characters or starts with '-': {:?}", attribute)] + #[error("Attribute has non-ascii characters or starts with '-': {attribute:?}")] InvalidAttribute { attribute: BString }, - #[error("Invalid character in attribute value: {:?}", character)] + #[error("Invalid character in attribute value: {character:?}")] InvalidAttributeValue { character: char }, #[error("Escape character '\\' is not allowed as the last character in an attribute value")] TrailingEscapeCharacter, From 5a55dbf5fd2ae8c28d95d72d55a30e4e7e2ef9cf Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 24 Jul 2022 16:32:20 +0800 Subject: [PATCH 242/248] avoid temporary vec in favor of a `&'static [u8]`. --- git-pathspec/src/parse.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index a021f8fc35c..029b9356ce9 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -58,9 +58,7 @@ impl Pattern { } fn parse_short_keywords(input: &[u8], cursor: &mut usize) -> Result { - let unimplemented_chars = vec![ - b'"', b'#', b'%', b'&', b'\'', b',', b'-', b';', b'<', b'=', b'>', b'@', b'_', b'`', b'~', - ]; + let unimplemented_chars = b"\"#%&'-',;<=>@_`~"; let mut signature = MagicSignature::empty(); while let Some(&b) = input.get(*cursor) { From d88952a533cf2fa2ebf0f015b10f5983a1c8f144 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 24 Jul 2022 16:43:15 +0800 Subject: [PATCH 243/248] avoid another vec allocation by inlining code via closure. --- git-pathspec/src/parse.rs | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs index 029b9356ce9..469b26ea92a 100644 --- a/git-pathspec/src/parse.rs +++ b/git-pathspec/src/parse.rs @@ -94,9 +94,10 @@ fn parse_long_keywords(input: &[u8], p: &mut Pattern, cursor: &mut usize) -> Res return Ok(()); } - for keyword in split_on_non_escaped_char(input, b',') { + split_on_non_escaped_char(input, b',', |keyword| { + let attr_prefix = b"attr:"; match keyword { - b"attr" => continue, + b"attr" => {} b"top" => p.signature |= MagicSignature::TOP, b"icase" => p.signature |= MagicSignature::ICASE, b"exclude" => p.signature |= MagicSignature::EXCLUDE, @@ -108,9 +109,9 @@ fn parse_long_keywords(input: &[u8], p: &mut Pattern, cursor: &mut usize) -> Res MatchMode::Literal => return Err(Error::IncompatibleSearchModes), _ => p.search_mode = MatchMode::PathAwareGlob, }, - _ if keyword.starts_with(b"attr:") => { + _ if keyword.starts_with(attr_prefix) => { if p.attributes.is_empty() { - p.attributes = parse_attributes(&keyword[5..])?; + p.attributes = parse_attributes(&keyword[attr_prefix.len()..])?; } else { return Err(Error::MultipleAttributeSpecifications); } @@ -120,25 +121,28 @@ fn parse_long_keywords(input: &[u8], p: &mut Pattern, cursor: &mut usize) -> Res keyword: BString::from(keyword), }); } - } - } - - Ok(()) + }; + Ok(()) + }) } -fn split_on_non_escaped_char(input: &[u8], split_char: u8) -> Vec<&[u8]> { - let mut keywords = Vec::new(); +fn split_on_non_escaped_char( + input: &[u8], + split_char: u8, + mut f: impl FnMut(&[u8]) -> Result<(), Error>, +) -> Result<(), Error> { let mut i = 0; let mut last = 0; for window in input.windows(2) { i += 1; if window[0] != b'\\' && window[1] == split_char { - keywords.push(&input[last..i]); + let keyword = &input[last..i]; + f(keyword)?; last = i + 1; } } - keywords.push(&input[last..]); - keywords + let last_keyword = &input[last..]; + f(last_keyword) } fn parse_attributes(input: &[u8]) -> Result, Error> { @@ -175,7 +179,7 @@ fn unescape_attribute_values(input: &BStr) -> Result, Error> { match out { Cow::Borrowed(_) => { let end = out.len() + attr.len() + 1; - out = Cow::Borrowed(&input[0..end.min(input.len())]) + out = Cow::Borrowed(&input[0..end.min(input.len())]); } Cow::Owned(_) => { let out = out.to_mut(); From 5e58eb4b59531ed172400960289088b5c18d774a Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 24 Jul 2022 16:49:01 +0800 Subject: [PATCH 244/248] lift pathspec crate to usable ones --- README.md | 4 ++-- etc/check-package-size.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 977aa77160a..31b2c33aa8f 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ Documentation is complete and was reviewed at least once. 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)_ +* **usable** _(with rough but complete docs, possibly incomplete functionality)_ * [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) @@ -129,12 +129,12 @@ is usable to some extend. * [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) * [git-attributes](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-attributes) + * [git-pathspec](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-pathspec) * `gitoxide-core` * **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-pathspec](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-pathspec) * [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** _(just a name placeholder)_ diff --git a/etc/check-package-size.sh b/etc/check-package-size.sh index fe8cf936f4d..0a84a066738 100755 --- a/etc/check-package-size.sh +++ b/etc/check-package-size.sh @@ -17,7 +17,7 @@ function indent () { echo "in root: gitoxide CLI" (enter cargo-smart-release && indent cargo diet -n --package-size-limit 95KB) (enter git-actor && indent cargo diet -n --package-size-limit 5KB) -(enter git-pathspec && indent cargo diet -n --package-size-limit 5KB) +(enter git-pathspec && indent cargo diet -n --package-size-limit 25KB) (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 20KB) From 074b2833d15c8483bd89e4bde4486c0c7df14637 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 25 Jul 2022 10:06:28 +0800 Subject: [PATCH 245/248] Add documentation to test-tools. --- tests/tools/src/lib.rs | 70 ++++++++++++++++++++++++++++++------------ 1 file changed, 50 insertions(+), 20 deletions(-) diff --git a/tests/tools/src/lib.rs b/tests/tools/src/lib.rs index 4aab07ddbd5..dca509db67f 100644 --- a/tests/tools/src/lib.rs +++ b/tests/tools/src/lib.rs @@ -1,3 +1,6 @@ +//! Utilities for testing `gitoxide` crates, many of which might be useful for testing programs that use `git` in general. +#![deny(missing_docs)] +use std::ffi::OsString; use std::{ collections::BTreeMap, convert::Infallible, @@ -15,6 +18,20 @@ use once_cell::sync::Lazy; use parking_lot::Mutex; pub use tempfile; +/// A result type to allow using the try operator `?` in unit tests. +/// +/// Use it like so: +/// +/// ```norun +/// use git_testtools::Result; +/// +/// #[test] +/// fn this() -> Result { +/// let x: usize = "42".parse()?; +/// Ok(()) +/// +/// } +/// ``` pub type Result = std::result::Result>; static SCRIPT_IDENTITY: Lazy>> = Lazy::new(|| Mutex::new(BTreeMap::new())); @@ -46,11 +63,16 @@ static EXCLUDE_LUT: Lazy>>> = Lazy Mutex::new(cache) }); +/// Define how [scripted_fixture_repo_writable_with_args()] uses produces the writable copy. pub enum Creation { + /// Run the script once and copy the data from its output to the writable location. + /// This is fast but won't work if absolute paths are produced by the script. CopyFromReadOnly, + /// Run the script in the writable location. That way, absolute paths match the location. ExecuteScript, } +/// Run `git` in `working_dir` with all provided `args`. pub fn run_git(working_dir: &Path, args: &[&str]) -> std::io::Result { std::process::Command::new("git") .current_dir(working_dir) @@ -58,37 +80,41 @@ pub fn run_git(working_dir: &Path, args: &[&str]) -> std::io::Result git_hash::ObjectId { git_hash::ObjectId::from_hex(hex.as_bytes()).expect("40 bytes hex") } +/// Return the path to the `/tests/fixtures/` directory. pub fn fixture_path(path: impl AsRef) -> PathBuf { PathBuf::from("tests").join("fixtures").join(path.as_ref()) } -pub fn crate_under_test() -> String { - std::env::current_dir() - .expect("CWD is valid") - .file_name() - .expect("typical cargo invocation") - .to_string_lossy() - .into_owned() -} - +/// Load the fixture from `/tests/fixtures/` and return its data, or _panic_. pub fn fixture_bytes(path: impl AsRef) -> Vec { match std::fs::read(fixture_path(path.as_ref())) { Ok(res) => res, Err(_) => panic!("File at '{}' not found", path.as_ref().display()), } } + +/// Run the executable at `script_name`, like `make_repo.sh` to produce a read-only directory to which +/// the path is returned. +/// Note that it persists and the script at `script_name` will only be executed once if it ran without error. pub fn scripted_fixture_repo_read_only(script_name: impl AsRef) -> Result { scripted_fixture_repo_read_only_with_args(script_name, None) } +/// Run the executable at `script_name`, like `make_repo.sh` to produce a writable directory to which +/// the tempdir is returned. It will be removed automatically, courtesy of [`tempfile::TempDir`]. +/// +/// Note that `script_name` is only executed once, so the data can be copied from its read-only location. pub fn scripted_fixture_repo_writable(script_name: &str) -> Result { scripted_fixture_repo_writable_with_args(script_name, None, Creation::CopyFromReadOnly) } +/// Like [`scripted_fixture_repo_writable()`], but passes `args` to `script_name` while providing control over +/// the way files are created with `mode`. pub fn scripted_fixture_repo_writable_with_args( script_name: &str, args: impl IntoIterator, @@ -108,6 +134,7 @@ pub fn scripted_fixture_repo_writable_with_args( }) } +/// A utility to copy the entire contents of `src_dir` into `dst_dir`. pub fn copy_recursively_into_existing_dir(src_dir: impl AsRef, dst_dir: impl AsRef) -> std::io::Result<()> { fs_extra::copy_items( &std::fs::read_dir(src_dir)? @@ -125,7 +152,8 @@ pub fn copy_recursively_into_existing_dir(src_dir: impl AsRef, dst_dir: im .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?; Ok(()) } -/// Returns the directory at which the data is present + +/// Like `scripted_fixture_repo_read_only()`], but passes `args` to `script_name`. pub fn scripted_fixture_repo_read_only_with_args( script_name: impl AsRef, args: impl IntoIterator, @@ -387,6 +415,7 @@ fn extract_archive( Ok((archive_identity, platform)) } +/// Transform a verbose bom errors from raw bytes into a `BStr` to make printing/debugging human-readable. pub fn to_bstr_err(err: nom::Err>) -> VerboseError<&BStr> { let err = match err { nom::Err::Error(err) | nom::Err::Failure(err) => err, @@ -405,35 +434,36 @@ fn family_name() -> &'static str { } } -pub fn sleep_forever() -> ! { - loop { - std::thread::sleep(std::time::Duration::from_secs(u64::MAX)) - } -} - +/// A utility to set environment variables, while unsetting them (or resetting them to their previous value) on drop. #[derive(Default)] pub struct Env<'a> { - altered_vars: Vec<&'a str>, + altered_vars: Vec<(&'a str, Option)>, } impl<'a> Env<'a> { + /// Create a new instance. pub fn new() -> Self { Env { altered_vars: Vec::new(), } } + /// Set `var` to `value`. pub fn set(mut self, var: &'a str, value: impl Into) -> Self { + let prev = std::env::var_os(var); std::env::set_var(var, value.into()); - self.altered_vars.push(var); + self.altered_vars.push((var, prev)); self } } impl<'a> Drop for Env<'a> { fn drop(&mut self) { - for var in &self.altered_vars { - std::env::remove_var(var); + for (var, prev_value) in &self.altered_vars { + match prev_value { + Some(value) => std::env::set_var(var, value), + None => std::env::remove_var(var), + } } } } From f409a2ae88f2b0d80c7d160563c07935993203a6 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 25 Jul 2022 10:32:23 +0800 Subject: [PATCH 246/248] Add docs related to archives. --- tests/tools/Cargo.toml | 2 +- tests/tools/src/lib.rs | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/tests/tools/Cargo.toml b/tests/tools/Cargo.toml index 98e9e048f92..4c2fae52bd1 100644 --- a/tests/tools/Cargo.toml +++ b/tests/tools/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "git-testtools" description = "Shared code for gitoxide crates to facilitate testing" -version = "0.7.0" +version = "0.7.1" authors = ["Sebastian Thiel "] edition = "2018" license = "MIT OR Apache-2.0" diff --git a/tests/tools/src/lib.rs b/tests/tools/src/lib.rs index dca509db67f..9736bc8eb9b 100644 --- a/tests/tools/src/lib.rs +++ b/tests/tools/src/lib.rs @@ -100,7 +100,26 @@ pub fn fixture_bytes(path: impl AsRef) -> Vec { /// Run the executable at `script_name`, like `make_repo.sh` to produce a read-only directory to which /// the path is returned. +/// /// Note that it persists and the script at `script_name` will only be executed once if it ran without error. +/// +/// ### Automatic Archive Creation +/// +/// In order to speed up CI and even local runs should the cache get purged, the result of each script run +/// is automatically placed into a compressed _tar_ archive. +/// If a script result doesn't exist, these will be checked first and extracted if present, which they are by default. +/// This behaviour can be prohibited by setting the `GITOXIDE_TEST_IGNORE_ARCHIVES` to any value. +/// +/// To speed CI up, one can add these archives to the repository. It's absoutely recommended to use `git-lfs` for that to +/// not bloat the repository size. +/// +/// #### Disable Archive Creation +/// +/// If archives aren't useful, they can be disabled by using `.gitignore` specifications. +/// That way it's trivial to prevent creation of all archives with `generated-archives/*.tar.xz` in the root +/// or more specific `.gitignore` configurations in lower levels of the work tree. +/// +/// The latter is useful if the the script's output is platform specific. pub fn scripted_fixture_repo_read_only(script_name: impl AsRef) -> Result { scripted_fixture_repo_read_only_with_args(script_name, None) } From be6114e7c4ac48467db6acb2180b443dc9f59f32 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 25 Jul 2022 10:46:40 +0800 Subject: [PATCH 247/248] fix: assure permissions per trust level are properly inherited into `open::Options`. --- Cargo.lock | 2 +- git-repository/src/open.rs | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d0242938f62..b6868fd8b7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1587,7 +1587,7 @@ dependencies = [ [[package]] name = "git-testtools" -version = "0.7.0" +version = "0.7.1" dependencies = [ "bstr", "crc", diff --git a/git-repository/src/open.rs b/git-repository/src/open.rs index 0e29a28c847..7a5c945e0e2 100644 --- a/git-repository/src/open.rs +++ b/git-repository/src/open.rs @@ -84,7 +84,7 @@ pub(crate) struct EnvironmentOverrides { } impl EnvironmentOverrides { - fn from_env() -> Result { + fn from_env() -> Result { let mut worktree_dir = None; if let Some(path) = std::env::var_os("GIT_WORK_TREE") { worktree_dir = PathBuf::from(path).into(); @@ -186,7 +186,7 @@ impl Options { } /// Open a repository at `path` with the options set so far. - pub fn open(self, path: impl Into) -> Result { + pub fn open(self, path: impl Into) -> Result { ThreadSafeRepository::open_opts(path, self) } } @@ -197,7 +197,7 @@ impl git_sec::trust::DefaultForLevel for Options { git_sec::Trust::Full => Options { object_store_slots: Default::default(), replacement_objects: Default::default(), - permissions: Permissions::all(), + permissions: Permissions::default_for_level(level), git_dir_trust: git_sec::Trust::Full.into(), filter_config_section: Some(config::section::is_trusted), lossy_config: None, @@ -205,9 +205,9 @@ impl git_sec::trust::DefaultForLevel for Options { 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(), + permissions: Permissions::default_for_level(level), git_dir_trust: git_sec::Trust::Reduced.into(), - filter_config_section: Some(crate::config::section::is_trusted), + filter_config_section: Some(config::section::is_trusted), lossy_config: None, }, } @@ -219,20 +219,20 @@ impl git_sec::trust::DefaultForLevel for Options { #[allow(missing_docs)] pub enum Error { #[error(transparent)] - Config(#[from] crate::config::Error), + Config(#[from] config::Error), #[error(transparent)] NotARepository(#[from] git_discover::is_git::Error), #[error(transparent)] ObjectStoreInitialization(#[from] std::io::Error), #[error("The git directory at '{}' is considered unsafe as it's not owned by the current user.", .path.display())] - UnsafeGitDir { path: std::path::PathBuf }, + UnsafeGitDir { path: PathBuf }, #[error(transparent)] - EnvironmentAccessDenied(#[from] crate::permission::env_var::resource::Error), + EnvironmentAccessDenied(#[from] permission::env_var::resource::Error), } impl ThreadSafeRepository { /// Open a git repository at the given `path`, possibly expanding it to `path/.git` if `path` is a work tree dir. - pub fn open(path: impl Into) -> Result { + pub fn open(path: impl Into) -> Result { Self::open_opts(path, Options::default()) } @@ -240,7 +240,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, mut 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) { @@ -319,7 +319,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, lossy_config)?; + let repo_config = 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; @@ -337,7 +337,7 @@ impl ThreadSafeRepository { 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), + filter_config_section.unwrap_or(config::section::is_trusted), git_install_dir.as_deref(), home.as_deref(), env.clone(), From 1df379ab0046887a330c0a670ad0414e79cfae7b Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 25 Jul 2022 11:00:21 +0800 Subject: [PATCH 248/248] change!: remove `Permissions::git_dir` field entirely. It was meant to help dealing with bailing out if the git dir isn't fully trusted, but the way this was done was over-engineered especially since the read-only permission level wasn't implemented at all. That function is now performed by a new flag, the `bail_on_untrusted` which is off by default. --- git-repository/src/open.rs | 25 +++++++++++++------- git-repository/src/repository/permissions.rs | 9 +------ git-repository/src/repository/snapshots.rs | 2 +- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/git-repository/src/open.rs b/git-repository/src/open.rs index 7a5c945e0e2..1cdf8d58f15 100644 --- a/git-repository/src/open.rs +++ b/git-repository/src/open.rs @@ -68,6 +68,7 @@ pub struct Options { pub(crate) git_dir_trust: Option, pub(crate) filter_config_section: Option bool>, pub(crate) lossy_config: Option, + pub(crate) bail_if_untrusted: bool, } #[derive(Default, Clone)] @@ -118,7 +119,6 @@ impl Options { git_prefix: deny, } }, - ..Permissions::default() }) } } @@ -166,6 +166,17 @@ impl Options { self } + /// If true, default false, and if the repository's trust level is not `Full` + /// (see [`with()`][Self::with()] for more), then the open operation will fail. + /// + /// Use this to mimic `git`s way of handling untrusted repositories. Note that `gitoxide` solves + /// this by not using configuration from untrusted sources and by generally being secured against + /// doctored input files which at worst could cause out-of-memory at the time of writing. + pub fn bail_if_untrusted(mut self, toggle: bool) -> Self { + self.bail_if_untrusted = toggle; + 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. /// @@ -201,6 +212,7 @@ impl git_sec::trust::DefaultForLevel for Options { git_dir_trust: git_sec::Trust::Full.into(), filter_config_section: Some(config::section::is_trusted), lossy_config: None, + bail_if_untrusted: false, }, git_sec::Trust::Reduced => Options { object_store_slots: git_odb::store::init::Slots::Given(32), // limit resource usage @@ -208,6 +220,7 @@ impl git_sec::trust::DefaultForLevel for Options { permissions: Permissions::default_for_level(level), git_dir_trust: git_sec::Trust::Reduced.into(), filter_config_section: Some(config::section::is_trusted), + bail_if_untrusted: false, lossy_config: None, }, } @@ -301,12 +314,8 @@ impl ThreadSafeRepository { filter_config_section, ref replacement_objects, lossy_config, - permissions: - Permissions { - git_dir: ref git_dir_perm, - ref env, - config, - }, + bail_if_untrusted, + permissions: Permissions { ref env, config }, } = options; let git_dir_trust = git_dir_trust.expect("trust must be been determined by now"); @@ -344,7 +353,7 @@ impl ThreadSafeRepository { config, )?; - if **git_dir_perm != git_sec::ReadWrite::all() { + if bail_if_untrusted && git_dir_trust != git_sec::Trust::Full { check_safe_directories(&git_dir, git_install_dir.as_deref(), home.as_deref(), &config)?; } diff --git a/git-repository/src/repository/permissions.rs b/git-repository/src/repository/permissions.rs index 83c5e4b5367..32fab1a1a00 100644 --- a/git-repository/src/repository/permissions.rs +++ b/git-repository/src/repository/permissions.rs @@ -1,14 +1,10 @@ -use git_sec::{permission::Resource, Access, Trust}; +use git_sec::{Access, Trust}; use crate::permission; /// Permissions associated with various resources of a git repository #[derive(Debug, Clone)] pub struct Permissions { - /// Control how a git-dir can be used. - /// - /// Note that a repository won't be usable at all unless read and write permissions are given. - pub git_dir: Access, /// Permissions related to the environment pub env: Environment, /// Permissions related to the handling of git configuration. @@ -90,7 +86,6 @@ impl Permissions { /// thus refusing all operations in it. pub fn strict() -> Self { Permissions { - git_dir: Access::resource(git_sec::ReadWrite::READ), env: Environment::all(), config: Config::all(), } @@ -103,7 +98,6 @@ impl Permissions { /// anything else that could cause us to write into unknown locations or use programs beyond our `PATH`. pub fn secure() -> Self { Permissions { - git_dir: Access::resource(git_sec::ReadWrite::all()), env: Environment::all(), config: Config::all(), } @@ -113,7 +107,6 @@ impl Permissions { /// does with owned repositories. pub fn all() -> Self { Permissions { - git_dir: Access::resource(git_sec::ReadWrite::all()), env: Environment::all(), config: Config::all(), } diff --git a/git-repository/src/repository/snapshots.rs b/git-repository/src/repository/snapshots.rs index 216cd9d5bfb..c2927e3bb63 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.options.permissions.git_dir.is_all() { + home_for_user: if self.options.git_dir_trust.expect("trust is set") == git_sec::Trust::Full { Some(git_config::path::interpolate::home_for_user) } else { None