Skip to content
Merged
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions git-config/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ serde_crate = { version = "1", package = "serde", optional = true }
serial_test = "0.5.1"
serde_derive = "1.0"
criterion = "0.3"
tempfile = "3.2.0"

[[bench]]
name = "large_config_file"
Expand Down
119 changes: 119 additions & 0 deletions git-config/src/file/git_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,33 @@ impl<'event> GitConfig<'event> {
parse_from_path(path).map(Self::from)
}

/// Constructs a `git-config` file from the provided paths in the order provided.
/// This is neither zero-copy nor zero-alloc.
///
/// # Errors
///
/// Returns an error if there was an IO error or if a file wasn't a valid
/// git-config file.
///
/// [`git-config`'s documentation]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-FILES
#[inline]
pub fn from_paths(paths: &[&Path]) -> Result<Self, ParserOrIoError<'static>> {
let mut config = Self::new();

for path in paths {
let other = Self::open(path)?;
for (section_id, section_header) in other.section_headers {
config.push_section(
section_header.name.0.to_owned(),
section_header.subsection_name.to_owned(),
other.sections[&section_id].clone(),
);
}
}

Ok(config)
}

/// 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.
Expand Down Expand Up @@ -1503,6 +1530,98 @@ a"#,
}
}

#[cfg(test)]
mod from_paths {
use super::{Cow, GitConfig, ParserOrIoError};
use std::{fs, io};
use tempfile::tempdir;

#[test]
fn file_not_found() {
let dir = tempdir().unwrap();
let config_path = dir.path().join("config");

let paths = vec![config_path.as_path()];
let error = GitConfig::from_paths(&paths).unwrap_err();
assert!(matches!(error, ParserOrIoError::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").expect("Unable to write config file");

let paths = vec![config_path.as_path()];
let config = GitConfig::from_paths(&paths).unwrap();

assert_eq!(
config.get_raw_value("core", None, "boolean"),
Ok(Cow::<[u8]>::Borrowed(b"true"))
);

assert_eq!(config.len(), 1);
}

#[test]
fn multiple_paths_single_value() {
let dir = tempdir().unwrap();

let a_path = dir.path().join("a");
fs::write(a_path.as_path(), b"[core]\na = true").expect("Unable to write config file");

let b_path = dir.path().join("b");
fs::write(b_path.as_path(), b"[core]\nb = true").expect("Unable to write config file");

let c_path = dir.path().join("c");
fs::write(c_path.as_path(), b"[core]\nc = true").expect("Unable to write config file");

let paths = vec![a_path.as_path(), b_path.as_path(), c_path.as_path()];
let config = GitConfig::from_paths(&paths).unwrap();

assert_eq!(
config.get_raw_value("core", None, "a"),
Ok(Cow::<[u8]>::Borrowed(b"true"))
);

assert_eq!(
config.get_raw_value("core", None, "b"),
Ok(Cow::<[u8]>::Borrowed(b"true"))
);

assert_eq!(
config.get_raw_value("core", None, "c"),
Ok(Cow::<[u8]>::Borrowed(b"true"))
);

assert_eq!(config.len(), 3);
}

#[test]
fn multiple_paths_multi_value() {
let dir = tempdir().unwrap();

let a_path = dir.path().join("a");
fs::write(a_path.as_path(), b"[core]\nkey = a").expect("Unable to write config file");

let b_path = dir.path().join("b");
fs::write(b_path.as_path(), b"[core]\nkey = b").expect("Unable to write config file");

let c_path = dir.path().join("c");
fs::write(c_path.as_path(), b"[core]\nkey = c").expect("Unable to write config file");

let paths = vec![a_path.as_path(), b_path.as_path(), c_path.as_path()];
let config = GitConfig::from_paths(&paths).unwrap();

assert_eq!(
config.get_raw_multi_value("core", None, "key").unwrap(),
vec![Cow::Borrowed(b"a"), Cow::Borrowed(b"b"), Cow::Borrowed(b"c")]
);

assert_eq!(config.len(), 3);
}
}

#[cfg(test)]
mod from_env {
use std::env;
Expand Down