blob: 864a4d49affe5c9bad780c1153411eced5ad743f [file] [log] [blame]
use std::{
path::Path,
process::{Command, Stdio},
};
use bstr::{BStr, BString, ByteSlice};
/// Returns the file that contains git configuration coming with the installation of the `git` file in the current `PATH`, or `None`
/// if no `git` executable was found or there were other errors during execution.
pub(crate) fn install_config_path() -> Option<&'static BStr> {
let _span = gix_trace::detail!("gix_path::git::install_config_path()");
static PATH: once_cell::sync::Lazy<Option<BString>> = once_cell::sync::Lazy::new(|| {
// Shortcut: in Msys shells this variable is set which allows to deduce the installation directory
// so we can save the `git` invocation.
#[cfg(windows)]
if let Some(mut exec_path) = std::env::var_os("EXEPATH").map(std::path::PathBuf::from) {
exec_path.push("etc");
exec_path.push("gitconfig");
return crate::os_string_into_bstring(exec_path.into()).ok();
}
let mut cmd = Command::new(if cfg!(windows) { "git.exe" } else { "git" });
cmd.args(["config", "-l", "--show-origin"])
.stdin(Stdio::null())
.stderr(Stdio::null());
gix_trace::debug!(cmd = ?cmd, "invoking git for installation config path");
first_file_from_config_with_origin(cmd.output().ok()?.stdout.as_slice().into()).map(ToOwned::to_owned)
});
PATH.as_ref().map(AsRef::as_ref)
}
fn first_file_from_config_with_origin(source: &BStr) -> Option<&BStr> {
let file = source.strip_prefix(b"file:")?;
let end_pos = file.find_byte(b'\t')?;
file[..end_pos].trim_with(|c| c == '"').as_bstr().into()
}
/// Given `config_path` as obtained from `install_config_path()`, return the path of the git installation base.
pub(crate) fn config_to_base_path(config_path: &Path) -> &Path {
config_path
.parent()
.expect("config file paths always have a file name to pop")
}
#[cfg(test)]
mod tests {
use std::path::Path;
#[test]
fn config_to_base_path() {
for (input, expected) in [
(
"/Applications/Xcode.app/Contents/Developer/usr/share/git-core/gitconfig",
"/Applications/Xcode.app/Contents/Developer/usr/share/git-core",
),
("C:/git-sdk-64/etc/gitconfig", "C:/git-sdk-64/etc"),
("C:\\ProgramData/Git/config", "C:\\ProgramData/Git"),
("C:/Program Files/Git/etc/gitconfig", "C:/Program Files/Git/etc"),
] {
assert_eq!(super::config_to_base_path(Path::new(input)), Path::new(expected));
}
}
#[test]
fn first_file_from_config_with_origin() {
let macos = "file:/Applications/Xcode.app/Contents/Developer/usr/share/git-core/gitconfig credential.helper=osxkeychain\nfile:/Users/byron/.gitconfig push.default=simple\n";
let win_msys =
"file:C:/git-sdk-64/etc/gitconfig core.symlinks=false\r\nfile:C:/git-sdk-64/etc/gitconfig core.autocrlf=true";
let win_cmd = "file:C:/Program Files/Git/etc/gitconfig diff.astextplain.textconv=astextplain\r\nfile:C:/Program Files/Git/etc/gitconfig filter.lfs.clean=gix-lfs clean -- %f\r\n";
let win_msys_old = "file:\"C:\\ProgramData/Git/config\" diff.astextplain.textconv=astextplain\r\nfile:\"C:\\ProgramData/Git/config\" filter.lfs.clean=git-lfs clean -- %f\r\n";
let linux = "file:/home/parallels/.gitconfig core.excludesfile=~/.gitignore\n";
let bogus = "something unexpected";
let empty = "";
for (source, expected) in [
(
macos,
Some("/Applications/Xcode.app/Contents/Developer/usr/share/git-core/gitconfig"),
),
(win_msys, Some("C:/git-sdk-64/etc/gitconfig")),
(win_msys_old, Some("C:\\ProgramData/Git/config")),
(win_cmd, Some("C:/Program Files/Git/etc/gitconfig")),
(linux, Some("/home/parallels/.gitconfig")),
(bogus, None),
(empty, None),
] {
assert_eq!(
super::first_file_from_config_with_origin(source.into()),
expected.map(Into::into)
);
}
}
}