@@ -652,21 +652,60 @@ fn configure_command<'a, I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
652
652
. env ( "GIT_CONFIG_VALUE_3" , "always" )
653
653
}
654
654
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) {
659
688
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 ( )
670
709
}
671
710
672
711
fn write_failure_marker ( failure_marker : & Path ) {
@@ -1059,4 +1098,32 @@ mod tests {
1059
1098
fn bash_program_ok_for_platform ( ) {
1060
1099
assert_eq ! ( bash_program( ) , Path :: new( "bash" ) ) ;
1061
1100
}
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
+ }
1062
1129
}
0 commit comments