Skip to content

Instantly share code, notes, and snippets.

@sueszli
Created April 17, 2026 10:22
Show Gist options
  • Select an option

  • Save sueszli/5bd5c6963152e595706f21dc6e537576 to your computer and use it in GitHub Desktop.

Select an option

Save sueszli/5bd5c6963152e595706f21dc6e537576 to your computer and use it in GitHub Desktop.
#!/usr/bin/env bash
# Reproduce: on a case-insensitive filesystem (macOS APFS default), two remote
# branches whose names differ only by letter case make `git fetch --prune`
# race itself and fail with:
# Unable to create '.../refs/remotes/origin/<name>.lock': File exists
#
# Self-contained. Builds throwaway repos under /tmp, triggers the failure,
# prints a tight summary, and cleans up. Safe to run repeatedly.
#
# Exit codes:
# 0 bug reproduced (your environment is affected)
# 1 bug NOT reproduced (different git version / reftable backend / etc.)
# 2 skipped — filesystem is case-sensitive, bug cannot manifest here
set -u
DIR=$(mktemp -d "/tmp/case-ref-collision.XXXXXX")
trap 'rm -rf "$DIR"' EXIT
# 1. Filesystem check
: > "$DIR/a"
if [ ! -e "$DIR/A" ]; then
echo "SKIP: filesystem is case-sensitive; this bug cannot occur here."
exit 2
fi
rm -f "$DIR/a"
# 2. Build a bare "remote" with two case-colliding branches.
# `git branch TEST` fails on a case-insensitive FS, so inject directly into
# the bare remote's packed-refs (plain text file, no FS collision).
git init --bare -b main "$DIR/remote.git" >/dev/null
git init -q -b main "$DIR/seed"
(
cd "$DIR/seed"
git config user.email reproducer@example.com
git config user.name reproducer
echo x > f
git add f
git -c commit.gpgsign=false commit -q -m init
git remote add origin ../remote.git
git push -q origin main
)
SHA=$(git -C "$DIR/seed" rev-parse HEAD)
cat > "$DIR/remote.git/packed-refs" <<EOF
# pack-refs with: peeled fully-peeled sorted
$SHA refs/heads/main
$SHA refs/heads/test
$SHA refs/heads/TEST
EOF
# 3. Clone. Both colliding refs land in the client's packed-refs.
git clone -q "$DIR/remote.git" "$DIR/work"
# 4. Delete both colliding branches upstream.
cat > "$DIR/remote.git/packed-refs" <<EOF
# pack-refs with: peeled fully-peeled sorted
$SHA refs/heads/main
EOF
# 5. Trigger the race.
OUT=$(git -C "$DIR/work" fetch --prune 2>&1) && RC=0 || RC=$?
ERR=$(printf '%s\n' "$OUT" | grep -E "Unable to create .*\.lock.*File exists" | head -n1)
if [ -n "$ERR" ]; then
echo "REPRODUCED on $(git --version):"
echo " $ERR"
echo
echo "Root cause:"
echo " On a case-insensitive FS, refs/remotes/origin/test.lock and .../TEST.lock"
echo " resolve to the same path. git fetch --prune creates one .lock then tries to"
echo " create the other at the same path -> File exists. git is racing itself."
echo
echo "Fix in an affected real-world repo:"
echo " git update-ref -d refs/remotes/origin/<one-of-the-two-colliding-refs>"
echo " git fetch --prune"
exit 0
fi
echo "NOT REPRODUCED. git fetch --prune exit=$RC"
echo "--- full output ---"
printf '%s\n' "$OUT"
echo "-------------------"
echo "Your git may use the reftable backend or include a fix for this case."
exit 1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment