From 1117251c608eaf752d5d6f034da4f9552203d5d0 Mon Sep 17 00:00:00 2001 From: Zoltan Csizmadia Date: Wed, 16 Sep 2026 20:25:07 -0500 Subject: [PATCH] mingit: ship BusyBox as sh.exe in the BusyBox-based MinGit The BusyBox-based MinGit ships `busybox.exe` and, via the Git wrapper, `ash.exe`, but no `sh.exe`. Git resolves the shell for `!` aliases, hooks and shell-driven commands with `git_shell_path()`, which looks up `sh` in the PATH and uses the result as argv[0]. When no `sh.exe` is found, the BusyBox fallback in `compat/mingw.c` returns the path of `busybox.exe` itself, and BusyBox then takes `-c` for an applet name: $ git hello -c: applet not found $ git clone C:\src C:\dst -c: applet not found fatal: Could not read from remote repository. This is what https://github.com/git-for-windows/git/issues/5184, https://github.com/git-for-windows/git/issues/5469 and the first half of https://github.com/git-for-windows/git/issues/6107 report, and it still reproduces with MinGit-2.55.0.5-busybox-64-bit.zip when nothing else on the PATH provides an `sh.exe`. BusyBox picks the applet from its own file name, so shipping a copy of `busybox.exe` as `sh.exe` makes it act as `sh` without any extra indirection, as suggested in https://github.com/git-for-windows/git/issues/5184#issuecomment-2527343278. With that copy in place, `!` aliases, clones from local paths and hooks with `#!/bin/sh` and `#!/bin/bash` shebangs all work in a PATH-restricted environment. The copy adds one 844 kB executable to the ~33 MB archive. The Git wrapper could stand in as `sh.exe` instead, as it does for `ash.exe`, at the cost of an extra process launch per shell invocation. This is independent of git-for-windows/git#3301 (`core.shell`): once that lands, MinGit-BusyBox can point `core.shell` at this `sh.exe`. Signed-off-by: Zoltan Csizmadia --- mingit/release.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mingit/release.sh b/mingit/release.sh index 6723637f1d..743c7a2034 100755 --- a/mingit/release.sh +++ b/mingit/release.sh @@ -174,6 +174,17 @@ $BIN_DIR/busybox.exe esac rm "$SCRIPT_PATH/root/$BIN_DIR"/busybox.exe + + # Ship BusyBox as sh.exe, too. Git looks up `sh` via the PATH and + # uses the result as argv[0] (see `git_shell_path()`). Without an + # sh.exe, the BusyBox fallback in compat/mingw.c returns the path + # of busybox.exe itself, and BusyBox then takes `-c` for the name of + # an applet ("-c: applet not found"). This breaks `!` aliases, + # clones from local paths and every other code path that runs a + # shell command. BusyBox picks the applet from its own file name, + # so a copy named sh.exe acts as `sh` without extra indirection. + cp "/$BIN_DIR"/busybox.exe "$SCRIPT_PATH/root/$BIN_DIR"/sh.exe || + die "Could not copy busybox.exe to sh.exe" esac test ! -f "$TARGET" || rm "$TARGET" || die "Could not remove $TARGET"