Skip to content

Commit 97d50a3

Browse files
authored
Merge pull request #1864 from EliahKagan/run-ci/bash-program
Make the `bash_program()` helper in `gix-testtools` a little more robust
2 parents b10cc6f + 720a23f commit 97d50a3

File tree

2 files changed

+91
-14
lines changed

2 files changed

+91
-14
lines changed

Diff for: tests/tools/src/lib.rs

+81-14
Original file line numberDiff line numberDiff line change
@@ -652,21 +652,60 @@ fn configure_command<'a, I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
652652
.env("GIT_CONFIG_VALUE_3", "always")
653653
}
654654

655-
fn bash_program() -> &'static Path {
656-
if cfg!(windows) {
657-
// TODO(deps): Once `gix_path::env::shell()` is available, maybe do `shell().parent()?.join("bash.exe")`
658-
static GIT_BASH: Lazy<Option<PathBuf>> = Lazy::new(|| {
655+
/// Get the path attempted as a `bash` interpreter, for fixture scripts having no `#!` we can use.
656+
///
657+
/// This is rarely called on Unix-like systems, provided that fixture scripts have usable shebang
658+
/// (`#!`) lines and are marked executable. However, Windows does not recognize `#!` when executing
659+
/// a file. If all fixture scripts that cannot be directly executed are `bash` scripts or can be
660+
/// treated as such, fixture generation still works on Windows, as long as this function manages to
661+
/// find or guess a suitable `bash` interpreter.
662+
///
663+
/// ### Search order
664+
///
665+
/// This function is used internally. It is public to facilitate diagnostic use. The following
666+
/// details are subject to change without warning, and changes are treated as non-breaking.
667+
///
668+
/// The `bash.exe` found in a path search is not always suitable on Windows. This is mainly because
669+
/// `bash.exe` in `System32`, which is associated with WSL, would often be found first. But even
670+
/// where that is not the case, the best `bash.exe` to use to run fixture scripts to set up Git
671+
/// repositories for testing is usually one associated with Git for Windows, even if some other
672+
/// `bash.exe` would be found in a path search. Currently, the search order we use is as follows:
673+
///
674+
/// 1. The shim `bash.exe`, which sets environment variables when run and is, on some systems,
675+
/// needed to find the POSIX utilities that scripts need (or correct versions of them).
676+
///
677+
/// 2. The non-shim `bash.exe`, which is sometimes available even when the shim is not available.
678+
/// This is mainly because the Git for Windows SDK does not come with a `bash.exe` shim.
679+
///
680+
/// 3. As a fallback, the simple name `bash.exe`, which triggers a path search when run.
681+
///
682+
/// On non-Windows systems, the simple name `bash` is used, which triggers a path search when run.
683+
pub fn bash_program() -> &'static Path {
684+
// TODO(deps): Unify with `gix_path::env::shell()` by having both call a more general function
685+
// in `gix-path`. See https://github.com/GitoxideLabs/gitoxide/issues/1886.
686+
static GIT_BASH: Lazy<PathBuf> = Lazy::new(|| {
687+
if cfg!(windows) {
659688
GIT_CORE_DIR
660-
.parent()?
661-
.parent()?
662-
.parent()
663-
.map(|installation_dir| installation_dir.join("bin").join("bash.exe"))
664-
.filter(|bash| bash.is_file())
665-
});
666-
GIT_BASH.as_deref().unwrap_or(Path::new("bash.exe"))
667-
} else {
668-
Path::new("bash")
669-
}
689+
.ancestors()
690+
.nth(3)
691+
.map(OsStr::new)
692+
.iter()
693+
.flat_map(|prefix| {
694+
// Go down to places `bash.exe` usually is. Keep using `/` separators, not `\`.
695+
["/bin/bash.exe", "/usr/bin/bash.exe"].into_iter().map(|suffix| {
696+
let mut raw_path = (*prefix).to_owned();
697+
raw_path.push(suffix);
698+
raw_path
699+
})
700+
})
701+
.map(PathBuf::from)
702+
.find(|bash| bash.is_file())
703+
.unwrap_or_else(|| "bash.exe".into())
704+
} else {
705+
"bash".into()
706+
}
707+
});
708+
GIT_BASH.as_ref()
670709
}
671710

672711
fn write_failure_marker(failure_marker: &Path) {
@@ -1059,4 +1098,32 @@ mod tests {
10591098
fn bash_program_ok_for_platform() {
10601099
assert_eq!(bash_program(), Path::new("bash"));
10611100
}
1101+
1102+
#[test]
1103+
fn bash_program_unix_path() {
1104+
let path = bash_program()
1105+
.to_str()
1106+
.expect("This test depends on the bash path being valid Unicode");
1107+
assert!(
1108+
!path.contains('\\'),
1109+
"The path to bash should have no backslashes, barring very unusual environments"
1110+
);
1111+
}
1112+
1113+
fn is_rooted_relative(path: impl AsRef<Path>) -> bool {
1114+
let p = path.as_ref();
1115+
p.is_relative() && p.has_root()
1116+
}
1117+
1118+
#[test]
1119+
#[cfg(windows)]
1120+
fn unix_style_absolute_is_rooted_relative() {
1121+
assert!(is_rooted_relative("/bin/bash"), "can detect paths like /bin/bash");
1122+
}
1123+
1124+
#[test]
1125+
fn bash_program_absolute_or_unrooted() {
1126+
let bash = bash_program();
1127+
assert!(!is_rooted_relative(bash), "{bash:?}");
1128+
}
10621129
}

Diff for: tests/tools/src/main.rs

+10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
use std::{fs, io, io::prelude::*, path::PathBuf};
22

3+
fn bash_program() -> io::Result<()> {
4+
use std::io::IsTerminal;
5+
if !std::io::stdout().is_terminal() {
6+
eprintln!("warning: `bash-program` subcommand not meant for scripting, format may change");
7+
}
8+
println!("{:?}", gix_testtools::bash_program());
9+
Ok(())
10+
}
11+
312
fn mess_in_the_middle(path: PathBuf) -> io::Result<()> {
413
let mut file = fs::OpenOptions::new().read(false).write(true).open(path)?;
514
file.seek(io::SeekFrom::Start(file.metadata()?.len() / 2))?;
@@ -17,6 +26,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
1726
let mut args = std::env::args().skip(1);
1827
let scmd = args.next().expect("sub command");
1928
match &*scmd {
29+
"bash-program" | "bp" => bash_program()?,
2030
"mess-in-the-middle" => mess_in_the_middle(PathBuf::from(args.next().expect("path to file to mess with")))?,
2131
#[cfg(unix)]
2232
"umask" => umask()?,

0 commit comments

Comments
 (0)