Skip to content

Commit 2ba8978

Browse files
committed
fix: Check prefix and prefer shim in gix_path::env::shell()
This makes a few changes to make `shell()` more robust: 1. Check the last two components of the path `git --exec-path` gave, to make sure they are `libexec/git-core`. (The check is done in such a way that the separator may be `/` or `\`, though a `\` separator here would be unexpected. We permit it because it may plausibly be present due to an overriden `GIT_EXEC_PATH` that breaks with Git's own behavior of using `/` but that is otherwise fully usable.) If the directory is not named `git-core`, or it is a top-level directory (no parent), or its parent is not named `libexec`, then it is not reasonable to guess that this is in a directory where it would be safe to use `sh.exe` in the expected relative location. (Even if safe, such a layout does not suggest that a `sh.exe` found in it would be better choice than the fallback of just doing a `PATH` search.) 2. Check the grandparent component (that `../..` would go to) of the path `git --exec-path` gave, to make sure it is recognized name of a platform-specific `usr`-like directory that has been used in MSYS2. This is to avoid traversing up out of less common directory trees that have some different and shallower structure than found in a typical Git for Windows or MSYS2 installation. 3. Instead of using only the `(git root)/usr/bin/sh.exe` non-shim, prefer the `(git root)/bin/sh.exe` shim. If that is not found, fall back to the `(git root)/usr/bin/sh.exe` non-shim, mainly to support the Git for Windows SDK, which doesn't have the shim. The reason to prefer the shim is that it sets environment variables, including prepending `bin` directories that provide tools one would expect to have when using it. Without this, common POSIX commands may be unavailable, or different and incompatible implementations of them may be found. In particular, if they are found in a different MSYS2 installation whose `msys-2.0.dll` is of a different version or otherwise a different build, then calling them directly may produce strange behavior. See: - https://cygwin.com/faq.html#faq.using.multiple-copies - GitoxideLabs#1862 (comment) This makes things more robust overall than either preferring the non-shim or just doing a path search for `sh` as was done before that. But it exacerbates GitoxideLabs#1868 (as described there), so if the Git for Windows `sh.exe` shim continues to work as it currently does, then further improvements may be called for here.
1 parent 81aba70 commit 2ba8978

File tree

1 file changed

+30
-13
lines changed

1 file changed

+30
-13
lines changed

Diff for: gix-path/src/env/mod.rs

+30-13
Original file line numberDiff line numberDiff line change
@@ -39,25 +39,42 @@ pub fn installation_config_prefix() -> Option<&'static Path> {
3939
pub fn shell() -> &'static OsStr {
4040
static PATH: Lazy<OsString> = Lazy::new(|| {
4141
if cfg!(windows) {
42+
const MSYS_PREFIX_NAMES: &[&str] = &[
43+
"mingw64",
44+
"mingw32",
45+
"clangarm64",
46+
"clang64",
47+
"clang32",
48+
"ucrt64",
49+
"usr",
50+
];
51+
const RAW_SUFFIXES: &[&str] = &[
52+
"/bin/sh.exe", // Usually a shim, which currently we prefer, if available.
53+
"/usr/bin/sh.exe",
54+
];
55+
fn raw_join(path: &Path, raw_suffix: &str) -> OsString {
56+
let mut raw_path = OsString::from(path);
57+
raw_path.push(raw_suffix);
58+
raw_path
59+
}
4260
core_dir()
43-
.and_then(|git_core| {
44-
// Go up above something that is expected to be like mingw64/libexec/git-core.
45-
git_core.ancestors().nth(3)
61+
.filter(|core| core.is_absolute() && core.ends_with("libexec/git-core"))
62+
.and_then(|core| core.ancestors().nth(2))
63+
.filter(|prefix| {
64+
// Only use `libexec/git-core` from inside something `usr`-like, such as `mingw64`.
65+
MSYS_PREFIX_NAMES.iter().any(|name| prefix.ends_with(name))
4666
})
47-
.map(OsString::from)
48-
.map(|mut raw_path| {
49-
// Go down to where `sh.exe` usually is. To avoid breaking shell scripts that
50-
// wrongly assume the shell's own path contains no `\`, as well as to produce
67+
.and_then(|prefix| prefix.parent())
68+
.into_iter()
69+
.flat_map(|git_root| {
70+
// Enumerate the locations where `sh.exe` usually is. To avoid breaking shell
71+
// scripts that assume the shell's own path contains no `\`, and to produce
5172
// more readable messages, append literally with `/` separators. The path from
5273
// `git --exec-path` will already have all `/` separators (and no trailing `/`)
5374
// unless it was explicitly overridden to an unusual value via `GIT_EXEC_PATH`.
54-
raw_path.push("/usr/bin/sh.exe");
55-
raw_path
56-
})
57-
.filter(|raw_path| {
58-
// Check if there is something that could be a usable shell there.
59-
Path::new(raw_path).is_file()
75+
RAW_SUFFIXES.iter().map(|raw_suffix| raw_join(git_root, raw_suffix))
6076
})
77+
.find(|raw_path| Path::new(raw_path).is_file())
6178
.unwrap_or_else(|| "sh.exe".into())
6279
} else {
6380
"/bin/sh".into()

0 commit comments

Comments
 (0)