11use std:: ffi:: OsString ;
2- use std:: path:: Path ;
2+ use std:: path:: { Path , PathBuf } ;
3+
4+ use once_cell:: sync:: Lazy ;
35
46/// `usr`-like directory component names that MSYS2 may provide, other than for `/usr` itself.
57///
@@ -25,20 +27,48 @@ use std::path::Path;
2527/// Second, we don't recognize `usr` itself here, even though is a plausible prefix. In MSYS2, it
2628/// is the prefix for MSYS2 non-native programs, i.e. those that use `msys-2.0.dll`. But unlike the
2729/// `<platform>` names we recognize, `usr` also has an effectively unbounded range of plausible
28- /// meanings on non-Unix systems, which may occasionally relate to subdirectories whose contents
29- /// are controlled by different user accounts.
30+ /// meanings on non-Unix systems (for example, what should we take `Z:\usr` to mean?), which might
31+ /// occasionally relate to subdirectories with contents controlled by different * user accounts* .
3032///
3133/// If we start with a `libexec/git-core` directory that we already use and trust, and it is in a
3234/// directory with a name like `mingw64`, we infer that this `mingw64` directory has the expected
33- /// meaning and that its `usr` sibling, if present, is acceptable to treat as though it is a
34- /// first-level directory inside an MSYS2-like tree. So we are willing to traverse down to
35- /// `usr/sh.exe` and attempt to use it. But if the `libexec/git-core` we use and trust is inside a
35+ /// meaning and accordingly infer that its `usr` sibling, if present, is acceptable to treat as
36+ /// though it is a first-level directory inside an MSYS2-like tree. So we are willing to traverse
37+ /// down to `usr/sh.exe` and try to use it. But if the `libexec/git-core` we use and trust is in a
3638/// directory named `usr`, that `usr` directory may still not have the meaning we expect of `usr`.
3739///
38- /// The conditions for a privilege escalation attack or other serious malfunction seem unlikely . If
39- /// research indicates the risk is low enough, `usr` may be added. But for now it is omitted.
40+ /// Conditions for a privilege escalation attack or other serious malfunction seem far-fetched . If
41+ /// further research finds the risk is low enough, `usr` may be added. But for now it is omitted.
4042const MSYS_USR_VARIANTS : & [ & str ] = & [ "mingw64" , "mingw32" , "clangarm64" , "clang64" , "clang32" , "ucrt64" ] ;
4143
44+ /// Find a Git for Windows installation directory based on `git --exec-path` output.
45+ ///
46+ /// Currently this is used only for finding the path to an `sh.exe` associated with Git. This is
47+ /// separate from `installation_config()` and `installation_config_prefix()` in `gix_path::env`,
48+ /// which guess where `etc/gitconfig` is based on `EXEPATH` or the location of the highest-scope
49+ /// config file.
50+ ///
51+ /// The techniques might be combined or unified in some way in the future. The techniques those
52+ /// functions currently use shouldn't be used to find `sh.exe`, because `EXEPATH` can take on other
53+ /// values in some environments, and the highest scope config file may be unavailable or in another
54+ /// location if `GIT_CONFIG_SYSTEM` or `GIT_CONFIG_NOSYSTEM` are set or if there are no variables
55+ /// of system scope. Then paths found relative to it could be different. In contrast, the technique
56+ /// used here may be usable for those functions, though may need to cover more directory layouts.
57+ fn git_for_windows_root ( ) -> Option < & ' static Path > {
58+ static GIT_ROOT : Lazy < Option < PathBuf > > = Lazy :: new ( || {
59+ super :: core_dir ( )
60+ . filter ( |core| core. is_absolute ( ) && core. ends_with ( "libexec/git-core" ) )
61+ . and_then ( |core| core. ancestors ( ) . nth ( 2 ) )
62+ . filter ( |prefix| {
63+ // Only use `libexec/git-core` from inside something `usr`-like, such as `mingw64`.
64+ MSYS_USR_VARIANTS . iter ( ) . any ( |name| prefix. ends_with ( name) )
65+ } )
66+ . and_then ( |prefix| prefix. parent ( ) )
67+ . map ( Into :: into)
68+ } ) ;
69+ GIT_ROOT . as_deref ( )
70+ }
71+
4272/// Shell path fragments to concatenate to the root of a Git for Windows or MSYS2 installation.
4373///
4474/// These look like absolute Unix-style paths, but the leading `/` separators are present because
@@ -56,16 +86,9 @@ fn raw_join(path: &Path, raw_suffix: &str) -> OsString {
5686 raw_path
5787}
5888
59- ///
89+ /// Obtain a path to a `sh.exe` on Windows associated with Git, if one can be found.
6090pub ( super ) fn find_sh_on_windows ( ) -> Option < OsString > {
61- super :: core_dir ( )
62- . filter ( |core| core. is_absolute ( ) && core. ends_with ( "libexec/git-core" ) )
63- . and_then ( |core| core. ancestors ( ) . nth ( 2 ) )
64- . filter ( |prefix| {
65- // Only use `libexec/git-core` from inside something `usr`-like, such as `mingw64`.
66- MSYS_USR_VARIANTS . iter ( ) . any ( |name| prefix. ends_with ( name) )
67- } )
68- . and_then ( |prefix| prefix. parent ( ) )
91+ git_for_windows_root ( )
6992 . into_iter ( )
7093 . flat_map ( |git_root| {
7194 // Enumerate locations where `sh.exe` usually is. To avoid breaking scripts that assume the
0 commit comments