Skip to content

mingit: ship BusyBox as sh.exe in the BusyBox-based MinGit - #746

Open
zcsizmadia wants to merge 1 commit into
git-for-windows:mainfrom
zcsizmadia:mingit-busybox-sh
Open

zcsizmadia wants to merge 1 commit into
git-for-windows:mainfrom
zcsizmadia:mingit-busybox-sh

Conversation

@zcsizmadia

@zcsizmadia zcsizmadia commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

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 other 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 exists, 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 git-for-windows/git#5184, git-for-windows/git#5469 and the first half of git-for-windows/git#6107 report. It still reproduces with MinGit-2.55.0.5-busybox-64-bit.zip when the PATH is restricted to C:\Windows\System32, C:\Windows and MinGit's cmd directory. Full GIT_TRACE=1 output shows .../mingw64/bin/busybox.exe -c 'echo Hello World' ... being spawned.

This PR ships a copy of busybox.exe as mingw64/bin/sh.exe in the BusyBox variant, following the suggestion in git-for-windows/git#5184 (comment). BusyBox picks the applet from its own file name, so the copy acts as sh with no extra indirection.

Validation, done by copying busybox.exe to sh.exe inside the extracted 2.55.0.5 zip and re-running with the same restricted PATH:

Scenario Before After
git config alias.hello "!echo Hello World" then git hello -c: applet not found, exit 127 Hello World, exit 0
git clone C:\path\src dst -c: applet not found, exit 128 clone succeeds
pre-commit hook with #!/bin/sh not reached runs, uname reports MINGW(BusyBox/Win32)
pre-commit hook with #!/bin/bash not reached runs
sh.exe -c 'echo $0 && uname' n/a .../mingw64/bin/sh, MINGW(BusyBox/Win32)

The -c spawn now shows as .../mingw64/bin/sh.exe -c 'echo Hello World' ... in the trace.

Trade-offs

  • Adds one 844 kB executable to the ~33 MB archive. The Git wrapper already knows how to stand in as sh.exe the way it does for ash.exe, which would be 42 kB instead, at the cost of one extra process launch per shell invocation. I went with the direct copy because of the ongoing work to reduce process launches in ash: avoid unnecessary Windows process launches busybox-w32#17, but I am happy to switch.
  • Independent of Allow specifying at runtime which POSIX shell to use git#3301 (core.shell): once that lands, MinGit-BusyBox can point core.shell at this sh.exe. It also keeps working if the applet heuristic in compat/mingw.c is dropped, since a plain PATH lookup finds sh.exe.
  • I could not run release.sh end to end here (no Git for Windows SDK on this machine), so the change is limited to the copy plus a die on failure, mirroring the surrounding code. A sh -n syntax check passes.

The second half of git-for-windows/git#6107, bash: can't create /dev/tty, is a separate issue in the terminal prompt fallback and is not addressed here.

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 git-for-windows/git#5184,
git-for-windows/git#5469 and the first half
of git-for-windows/git#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
git-for-windows/git#5184 (comment).
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 <[email protected]>
@dscho

dscho commented Sep 17, 2026

Copy link
Copy Markdown
Member
  • Adds one 844 kB executable to the ~33 MB archive.

That's prohibitive. We started out MinGit-busybox with less than 20MB and we need to go reducing MinGit again, not increasing it.

The better plan is to drive #3301 forward and supersede this here PR. Once MinGit-busybox specifies core.shell = ash.exe (after merging that PR, once properly updated and tested), there simply is no need for sh.exe.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants