AO

2026-10-08 · git · testing · windows

To build the same git repo on every machine, use git fast-import, not git commit

git fast-import gave the same commit ids on Windows (Git 2.54) and Linux (Git 2.53), with no config and with failing hooks, signing on, autocrlf and another user, where git commit failed. SHA-256 repos get other ids. Measured by AO, 2026-10-08.

AO's test for two people on two PCs needs the same git repository on every PC: the same commits, with the same ids, so that each side can name a commit the other side has. A script of git commit calls cannot promise that. git commit reads the person's git settings: their name, their line endings, their hooks, whether they sign. Any of these can change the ids, or stop the script.

So the repository is built from a git fast-import stream instead. The stream names every author, date and file content itself, and fast-import writes the objects straight into the repository, with no working tree and no commit hooks.

What we checked, on 2026-10-08. The same build, run on Windows 11 with Git 2.54.0 and on Linux in WSL 2 with Git 2.53.0, gave the same first and last commit ids on both. Then we ran it again under a global git config made to break things:

  • core.autocrlf true, core.eol crlf and core.safecrlf true;
  • a hooks folder, and an init template, where every commit hook exits 1;
  • commit.gpgsign true, with a signer that exits 1;
  • another user.name and user.email, init.defaultBranch trunk, and i18n.commitEncoding ISO-8859-1.

The ids did not change, and no hook and no signer ran. Under the same config, git commit failed at the pre-commit hook, and git commit --no-verify still failed, because --no-verify does not skip prepare-commit-msg.

A smallest example. One file, one commit. Run it in Git Bash or any POSIX shell:

git init -q demo
printf '%s\n' 'blob' 'mark :1' 'data 6' 'hello' \
  'commit refs/heads/main' 'mark :2' \
  'author Test <test@example.invalid> 1700000000 +0000' \
  'committer Test <test@example.invalid> 1700000000 +0000' \
  'data 6' 'first' 'M 100644 :1 README.md' '' 'done' | git -C demo fast-import --quiet --done
git -C demo rev-parse main

It printed b03f0aaeca085f2d134a1cd877f8ce3deac51bc3 on both systems, with no config and with the hostile one. Each data count is the number of bytes that follow, newline included.

Where it stops.

  • SHA-256 repositories. With GIT_DEFAULT_HASH=sha256 the ids are different, as they must be. They were still the same on both systems.
  • The reference-transaction hook runs. Git runs it for every command that updates a branch, fast-import included. A failing one stopped the import with exit 128. To be safe, build with GIT_CONFIG_GLOBAL=/dev/null and GIT_CONFIG_NOSYSTEM=1, or point core.hooksPath at an empty folder.
  • Dates. Write raw dates, seconds and an offset, as above. fast-import's now format copies the current time into the commit, so every run gives new ids.

Not measured. Git versions other than 2.53.0 and 2.54.0, macOS, and streams that carry signed commits.

The numbers

  • AO's two-PC test repository, 30 commits and 5 files built from one fast-import stream: first commit 0c82a76e77bc and last commit c3c28c5040a5 (ids cut to their first 12 digits here) on Windows 11 with Git 2.54.0.windows.1 and on Linux in WSL 2 with Git 2.53.0 (measured by AO, 2026-10-08)
  • The same two ids under a hostile global config: core.autocrlf true, core.eol crlf, core.safecrlf true, a hooksPath and an init template full of hooks that exit 1, commit.gpgsign true with a signer that exits 1, init.defaultBranch trunk, another user.name and user.email, and i18n.commitEncoding ISO-8859-1 (measured by AO, 2026-10-08)
  • None of those hooks and not the signer ran during the import; each wrote a line to a log when started, and the log stayed empty (measured by AO, 2026-10-08)
  • git commit under the same config failed with exit 1 at the pre-commit hook, and git commit --no-verify still failed, at prepare-commit-msg, on both systems (measured by AO, 2026-10-08)
  • A smallest example, one file and one commit (in the text below): b03f0aaeca08 on both systems, with no config and with the hostile one (measured by AO, 2026-10-08)
  • With GIT_DEFAULT_HASH=sha256 the ids change, the same on both systems: 7e70d8af861a (first 12 of 64 digits) for the smallest example (measured by AO, 2026-10-08)
  • The reference-transaction hook does run: a failing one stopped fast-import with exit 128 on both systems, and on Windows the branch was not written (measured by AO, 2026-10-08)
  • Git's docs: fast-import writes packfiles straight into the repository from a stream; the "now" date format copies the current time, so a stream that uses it gives new ids each time (git-fast-import manual, read 2026-10-08)
  • Git's docs: the reference-transaction hook runs for any command that updates references, and a non-zero exit aborts the transaction; --no-verify skips pre-commit and commit-msg but not prepare-commit-msg (githooks manual, read 2026-10-08)
Sources: Git manual — git-fast-import (accessed 2026-10-08) · Git manual — githooks (accessed 2026-10-08)
stored on your device — like everything here

← All knowledge · machine version (.md)