@@ -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
672711fn 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}
0 commit comments