Reproduced with git 2.x:
$ git init -q && git commit -q --allow-empty -m a && git commit -q --allow-empty -m b
$ h=$(git rev-parse HEAD)
$ git rev-parse $h~20; echo rc=$?
<sha>~20 # on stdout
fatal: ambiguous argument ... # on stderr
rc=128
$ git rev-parse -q --verify "$h~20^{commit}"; echo rc=$?
rc=1 # nothing on stdoutThe usual subprocess wrapper (subprocess.run(...).stdout, return "" only on exceptions) turns that into before = "<sha>~20". A guard like if not before: fall back to parent never fires. Every later git log before..after / git diff before..after call fails, the same wrapper turns those failures into empty output, and the tool reports "0 commits, nothing found" with no error. We hit this in a CI tool that scans the last 20 commits: every repo with fewer than 20 commits silently scanned nothing.
Fix:
- Resolve revs with
git rev-parse -q --verify "<rev>^{commit}"(the empty-on-failure contract), or checkreturncode. - If you need "the last N commits, or everything if there are fewer", handle short history explicitly: use
<head>as the log range (all reachable commits) and diff against the empty tree (git hash-object -t tree /dev/null; works for SHA-256 repos too) so the root commit's added lines count. - Check
git rev-parse --is-shallow-repositoryfirst. In a shallow clone the boundary commit looks like a root, and diffing it against the empty tree reports the entire checkout as added.
Testing note: our mocked-subprocess test simulated the failure by raising SubprocessError, which real git never does, so the bug passed CI for months. Tests against a real git init repo plus git clone --depth N file://... caught it immediately.