diff --git a/tests/tools/src/lib.rs b/tests/tools/src/lib.rs index 27fb06d5006..ad5c8c46fd4 100644 --- a/tests/tools/src/lib.rs +++ b/tests/tools/src/lib.rs @@ -59,6 +59,7 @@ impl Drop for GitDaemon { } static SCRIPT_IDENTITY: Lazy>> = Lazy::new(|| Mutex::new(BTreeMap::new())); + static EXCLUDE_LUT: Lazy>> = Lazy::new(|| { let cache = (|| { let (repo_path, _) = gix_discover::upwards(Path::new(".")).ok()?; @@ -86,8 +87,31 @@ static EXCLUDE_LUT: Lazy>> = Lazy::new(|| { })(); Mutex::new(cache) }); + +#[cfg(windows)] +const GIT_PROGRAM: &str = "git.exe"; +#[cfg(not(windows))] +const GIT_PROGRAM: &str = "git"; + +static GIT_CORE_DIR: Lazy = Lazy::new(|| { + let output = std::process::Command::new(GIT_PROGRAM) + .arg("--exec-path") + .output() + .expect("can execute `git --exec-path`"); + + assert!(output.status.success(), "`git --exec-path` failed"); + + output + .stdout + .strip_suffix(b"\n") + .expect("`git --exec-path` output to be well-formed") + .to_os_str() + .expect("no invalid UTF-8 in `--exec-path` except as OS allows") + .into() +}); + /// The major, minor and patch level of the git version on the system. -pub static GIT_VERSION: Lazy<(u8, u8, u8)> = Lazy::new(|| parse_gix_version().expect("git version to be parsable")); +pub static GIT_VERSION: Lazy<(u8, u8, u8)> = Lazy::new(|| parse_git_version().expect("git version to be parsable")); /// Define how [`scripted_fixture_writable_with_args()`] uses produces the writable copy. pub enum Creation { @@ -116,10 +140,8 @@ pub fn should_skip_as_git_version_is_smaller_than(major: u8, minor: u8, patch: u *GIT_VERSION < (major, minor, patch) } -fn parse_gix_version() -> Result<(u8, u8, u8)> { - let gix_program = cfg!(windows).then(|| "git.exe").unwrap_or("git"); - let output = std::process::Command::new(gix_program).arg("--version").output()?; - +fn parse_git_version() -> Result<(u8, u8, u8)> { + let output = std::process::Command::new(GIT_PROGRAM).arg("--version").output()?; git_version_from_bytes(&output.stdout) } @@ -173,7 +195,7 @@ impl Drop for AutoRevertToPreviousCWD { /// 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") + std::process::Command::new(GIT_PROGRAM) .current_dir(working_dir) .args(args) .status() @@ -181,17 +203,6 @@ pub fn run_git(working_dir: &Path, args: &[&str]) -> std::io::Result) -> std::io::Result { - static EXEC_PATH: Lazy = Lazy::new(|| { - let path = std::process::Command::new("git") - .arg("--exec-path") - .stderr(std::process::Stdio::null()) - .output() - .expect("can execute `git --exec-path`") - .stdout; - String::from_utf8(path.trim().into()) - .expect("no invalid UTF8 in exec-path") - .into() - }); let mut ports: Vec<_> = (9419u16..9419 + 100).collect(); fastrand::shuffle(&mut ports); let addr_at = |port| std::net::SocketAddr::from(([127, 0, 0, 1], port)); @@ -200,11 +211,12 @@ pub fn spawn_git_daemon(working_dir: impl AsRef) -> std::io::Result { - cmd = std::process::Command::new("bash"); + cmd = std::process::Command::new(bash_program()); configure_command(cmd.arg(script_absolute_path), &args, &script_result_directory).output()? } Err(err) => return Err(err.into()), @@ -632,6 +644,22 @@ fn configure_command<'a, I: IntoIterator, S: AsRef>( .env("GIT_CONFIG_VALUE_3", "always") } +fn bash_program() -> &'static Path { + if cfg!(windows) { + static GIT_BASH: Lazy> = Lazy::new(|| { + GIT_CORE_DIR + .parent()? + .parent()? + .parent() + .map(|installation_dir| installation_dir.join("bin").join("bash.exe")) + .filter(|bash| bash.is_file()) + }); + GIT_BASH.as_deref().unwrap_or(Path::new("bash.exe")) + } else { + Path::new("bash") + } +} + fn write_failure_marker(failure_marker: &Path) { std::fs::write(failure_marker, []).ok(); } @@ -738,7 +766,10 @@ fn populate_meta_dir(destination_dir: &Path, script_identity: u32) -> std::io::R )?; std::fs::write( meta_dir.join(META_GIT_VERSION), - std::process::Command::new("git").arg("--version").output()?.stdout, + std::process::Command::new(GIT_PROGRAM) + .arg("--version") + .output()? + .stdout, )?; Ok(meta_dir) } @@ -951,7 +982,7 @@ mod tests { let temp = tempfile::TempDir::new().expect("can create temp dir"); populate_ad_hoc_config_files(temp.path()); - let mut cmd = std::process::Command::new("git"); + let mut cmd = std::process::Command::new(GIT_PROGRAM); cmd.env("GIT_CONFIG_SYSTEM", SCOPE_ENV_VALUE); cmd.env("GIT_CONFIG_GLOBAL", SCOPE_ENV_VALUE); configure_command(&mut cmd, ["config", "-l", "--show-origin"], temp.path()); @@ -968,4 +999,43 @@ mod tests { assert_eq!(lines, Vec::<&str>::new(), "should be no config variables from files"); assert_eq!(status, 0, "reading the config should succeed"); } + + #[test] + #[cfg(windows)] + fn bash_program_ok_for_platform() { + let path = bash_program(); + assert!(path.is_absolute()); + + let for_version = std::process::Command::new(path) + .arg("--version") + .output() + .expect("can pass it `--version`"); + assert!(for_version.status.success(), "passing `--version` succeeds"); + let version_line = for_version + .stdout + .lines() + .nth(0) + .expect("`--version` output has first line"); + assert!( + version_line.ends_with(b"-pc-msys)"), // On Windows, "-pc-linux-gnu)" would be WSL. + "it is an MSYS bash (such as Git Bash)" + ); + + let for_uname_os = std::process::Command::new(path) + .args(["-c", "uname -o"]) + .output() + .expect("can tell it to run `uname -o`"); + assert!(for_uname_os.status.success(), "telling it to run `uname -o` succeeds"); + assert_eq!( + for_uname_os.stdout.trim_end(), + b"Msys", + "it runs commands in an MSYS environment" + ); + } + + #[test] + #[cfg(not(windows))] + fn bash_program_ok_for_platform() { + assert_eq!(bash_program(), Path::new("bash")); + } }