Inside the proot environment, the real git binary is either unavailable or too old. A bash wrapper script at /usr/bin/git intercepts calls and translates them, performing SSH-to-HTTPS URL rewriting (hack 11) and routing operations to the host Termux git binary.
The initial wrapper assumed git clone <url> <dest> positional ordering. This broke immediately because npm calls git in at least five different argument patterns during npm install:
git clone <url> <dest>git clone --depth 1 <url> <dest>git clone <url> --depth 1 <dest>git clone --mirror <url>git clone -c advice.detachedHead=false --depth 1 <url> <dest>The wrapper must handle all of these without knowing in advance which pattern npm will use. This is a LEGACY hack, preserved for reference after the native migration eliminated proot.
The wrapper script uses a state-machine argument parser that categorizes each argument:
#!/bin/bash
# /usr/bin/git — proot git wrapper with argument parser
REAL_GIT="/data/data/com.termux/files/usr/bin/git"
SUBCOMMAND="$1"
shift
if [ "$SUBCOMMAND" = "clone" ]; then
URL=""
DEST=""
FLAGS=()
while [ $# -gt 0 ]; do
case "$1" in
--depth|--branch|-b|-c|--reference|--origin|-o)
# Flags that consume the next argument
FLAGS+=("$1" "$2")
shift 2
;;
--mirror|--bare|--recursive|--recurse-submodules|--shallow-submodules|--no-checkout)
# Standalone flags
FLAGS+=("$1")
shift
;;
-*)
# Unknown flag — pass through as standalone
FLAGS+=("$1")
shift
;;
*)
# Positional argument: first is URL, second is DEST
if [ -z "$URL" ]; then
URL="$1"
elif [ -z "$DEST" ]; then
DEST="$1"
fi
shift
;;
esac
done
# Rewrite SSH URLs to HTTPS (see hack 11 for details)
URL=$(echo "$URL" | sed \
-e 's|^git+ssh://git@github.com/|https://github.com/|' \
-e 's|^git+ssh://git@github\.com:|https://github.com/|' \
-e 's|^ssh://git@github.com/|https://github.com/|' \
-e 's|^git@github\.com:|https://github.com/|')
# Normalize .git suffix
URL="${URL%.git}.git"
if [ -n "$DEST" ]; then
exec "$REAL_GIT" clone "${FLAGS[@]}" "$URL" "$DEST"
else
exec "$REAL_GIT" clone "${FLAGS[@]}" "$URL"
fi
else
# All non-clone subcommands pass through unchanged
exec "$REAL_GIT" "$SUBCOMMAND" "$@"
fiDeploy the wrapper inside proot:
# From Termux
ROOTFS="$PREFIX/var/lib/proot-distro/installed-rootfs/ubuntu"
cp /path/to/git-wrapper.sh "$ROOTFS/usr/bin/git"
chmod +x "$ROOTFS/usr/bin/git"Test each argument ordering pattern:
# Standard order
proot-distro login ubuntu -- git clone https://github.com/user/repo /tmp/test1
# Flags before URL
proot-distro login ubuntu -- git clone --depth 1 https://github.com/user/repo /tmp/test2
# Flags between URL and dest
proot-distro login ubuntu -- git clone https://github.com/user/repo --depth 1 /tmp/test3
# Flag-value pair before URL
proot-distro login ubuntu -- git clone -c advice.detachedHead=false --depth 1 \
https://github.com/user/repo /tmp/test4
# Mirror (no dest)
proot-distro login ubuntu -- git clone --mirror https://github.com/user/reponpm install inside proot completes git-dependency resolution for packages with git deps.set -x to the wrapper and running npm install shows the parsed URL and FLAGS for each invocation.-c flag takes a key=value argument that looks positional (e.g., advice.detachedHead=false). Without special handling in the case statement, the parser treats it as the URL. Every flag that consumes a following argument must be explicitly enumerated.-* catch-all handles unknown standalone flags, but new flags that consume a following argument will break parsing silently. Keep the known-flag list updated when npm upgrades.exec at the end is important. Without it, the wrapper forks a child process and the parent shell stays alive, wasting a PID and a small amount of RAM on a 1 GB device."${FLAGS[@]}" as a single string. The [@] form with double quotes correctly preserves each array element as a separate word. Using "${FLAGS[*]}" would concatenate everything into one argument.git fetch, git ls-remote, git rev-parse) pass through without any parsing. npm uses these internally, and they must reach the real git binary unchanged.npm install runs to completion inside proot without git argument ordering failures. The wrapper correctly handles all observed npm git invocation patterns, rewriting SSH URLs to HTTPS and forwarding flags in the correct positions. Combined with the sed URL rewriter (hack 11), this gave proot a fully functional git layer for npm dependency resolution.