Skip to content

Commit 00bd609

Browse files
committed
feat: provide env::executable_invocation() to know how to invoke Git.
That way we can make it easier to rely on Git even if finding it is a bit more involved.
1 parent f71b7a0 commit 00bd609

File tree

2 files changed

+67
-9
lines changed

2 files changed

+67
-9
lines changed

gix-path/src/env/git.rs

+55-9
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,75 @@
1+
use std::path::PathBuf;
12
use std::{
23
path::Path,
34
process::{Command, Stdio},
45
};
56

67
use bstr::{BStr, BString, ByteSlice};
78

9+
/// Other places to find Git in.
10+
#[cfg(windows)]
11+
static ALTERNATIVE_LOCATIONS: &[&str] = &["C:/Program Files/Git/mingw64/bin"];
12+
#[cfg(not(windows))]
13+
static ALTERNATIVE_LOCATIONS: &[&str] = &[];
14+
15+
#[cfg(windows)]
16+
pub(super) static EXE_NAME: &str = "git.exe";
17+
#[cfg(not(windows))]
18+
pub(super) static EXE_NAME: &str = "git";
19+
20+
/// The path to the Git executable as located in the `PATH` or in other locations that it's known to be installed to.
21+
/// It's `None` if environment variables couldn't be read or if no executable could be found.
22+
pub(super) static EXECUTABLE_PATH: once_cell::sync::Lazy<Option<PathBuf>> = once_cell::sync::Lazy::new(|| {
23+
std::env::split_paths(&std::env::var_os("PATH")?)
24+
.chain(ALTERNATIVE_LOCATIONS.into_iter().map(Into::into))
25+
.find_map(|prefix| {
26+
let full_path = prefix.join(EXE_NAME);
27+
full_path.is_file().then_some(full_path)
28+
})
29+
});
30+
31+
/// Invoke the git executable in PATH to obtain the origin configuration, which is cached and returned.
32+
pub(super) static EXE_INFO: once_cell::sync::Lazy<Option<BString>> = once_cell::sync::Lazy::new(|| {
33+
let git_cmd = |executable: PathBuf| {
34+
let mut cmd = Command::new(executable);
35+
cmd.args(["config", "-l", "--show-origin"])
36+
.stdin(Stdio::null())
37+
.stderr(Stdio::null());
38+
cmd
39+
};
40+
let mut cmd = git_cmd(EXE_NAME.into());
41+
gix_trace::debug!(cmd = ?cmd, "invoking git for installation config path");
42+
let cmd_output = match cmd.output() {
43+
Ok(out) => out.stdout,
44+
#[cfg(windows)]
45+
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
46+
let executable = ALTERNATIVE_LOCATIONS.into_iter().find_map(|prefix| {
47+
let candidate = Path::new(prefix).join(EXE_NAME);
48+
candidate.is_file().then_some(candidate)
49+
})?;
50+
gix_trace::debug!(cmd = ?cmd, "invoking git for installation config path in alternate location");
51+
git_cmd(executable).output().ok()?.stdout
52+
}
53+
Err(_) => return None,
54+
};
55+
56+
first_file_from_config_with_origin(cmd_output.as_slice().into()).map(ToOwned::to_owned)
57+
});
58+
859
/// Returns the file that contains git configuration coming with the installation of the `git` file in the current `PATH`, or `None`
960
/// if no `git` executable was found or there were other errors during execution.
10-
pub(crate) fn install_config_path() -> Option<&'static BStr> {
61+
pub(super) fn install_config_path() -> Option<&'static BStr> {
1162
let _span = gix_trace::detail!("gix_path::git::install_config_path()");
1263
static PATH: once_cell::sync::Lazy<Option<BString>> = once_cell::sync::Lazy::new(|| {
13-
// Shortcut: in Msys shells this variable is set which allows to deduce the installation directory
64+
// Shortcut: in Msys shells this variable is set which allows to deduce the installation directory,
1465
// so we can save the `git` invocation.
1566
#[cfg(windows)]
1667
if let Some(mut exec_path) = std::env::var_os("EXEPATH").map(std::path::PathBuf::from) {
1768
exec_path.push("etc");
1869
exec_path.push("gitconfig");
1970
return crate::os_string_into_bstring(exec_path.into()).ok();
2071
}
21-
let mut cmd = Command::new(if cfg!(windows) { "git.exe" } else { "git" });
22-
cmd.args(["config", "-l", "--show-origin"])
23-
.stdin(Stdio::null())
24-
.stderr(Stdio::null());
25-
gix_trace::debug!(cmd = ?cmd, "invoking git for installation config path");
26-
first_file_from_config_with_origin(cmd.output().ok()?.stdout.as_slice().into()).map(ToOwned::to_owned)
72+
first_file_from_config_with_origin((*EXE_INFO).as_ref()?.as_bstr()).map(ToOwned::to_owned)
2773
});
2874
PATH.as_ref().map(AsRef::as_ref)
2975
}
@@ -35,7 +81,7 @@ fn first_file_from_config_with_origin(source: &BStr) -> Option<&BStr> {
3581
}
3682

3783
/// Given `config_path` as obtained from `install_config_path()`, return the path of the git installation base.
38-
pub(crate) fn config_to_base_path(config_path: &Path) -> &Path {
84+
pub(super) fn config_to_base_path(config_path: &Path) -> &Path {
3985
config_path
4086
.parent()
4187
.expect("config file paths always have a file name to pop")

gix-path/src/env/mod.rs

+12
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,18 @@ pub fn installation_config_prefix() -> Option<&'static Path> {
2727
installation_config().map(git::config_to_base_path)
2828
}
2929

30+
/// Return the name of the Git executable to invoke it.
31+
/// Note that on Windows, we will try extra-hard to find the executable possibly by executing it
32+
/// and trying alternate locations, whereas on other platforms it will just return the name of the
33+
/// executable to find in the `PATH` naturally when invoking the command.
34+
pub fn executable_invocation() -> &'static Path {
35+
if cfg!(windows) {
36+
git::EXECUTABLE_PATH.as_deref().unwrap_or(Path::new(git::EXE_NAME))
37+
} else {
38+
Path::new("git")
39+
}
40+
}
41+
3042
/// Returns the fully qualified path in the *xdg-home* directory (or equivalent in the home dir) to `file`,
3143
/// accessing `env_var(<name>)` to learn where these bases are.
3244
///

0 commit comments

Comments
 (0)