Skip to content

Z40 = '0' * 40 hardcoding breaks git push --delete on SHA-256 repos #3664

@rlichtenwalter

Description

@rlichtenwalter

search you tried in the issue tracker

sha256, SHA-256 push, sha-256 delete, Z40, object-format sha256, "Invalid revision range", "bad object", zero SHA, pre-push delete, deletion push

The closest related hit is #3434 (closed), but that is about fetching sha1-format hook repos into a sha256 local repo — a different codepath.

describe your issue

pre_commit/commands/hook_impl.py hardcodes the SHA-1 zero OID:

Z40 = '0' * 40

It is compared against local_sha / remote_sha to detect deletion-only pushes and new branches:

if local_sha == Z40:
    continue
elif remote_sha != Z40 and _rev_exists(remote_sha):
    ...

In a repository initialized with --object-format=sha256, git's pre-push hook protocol emits 64-character zero OIDs rather than 40. The equality never matches, the deletion-only short-circuit never fires, and pre-commit falls through into git diff --name-only ... <old>..<64-zeros> (and then <old>...<64-zeros>). Both error out:

fatal: Invalid revision range 3dac4c61...6dce8d..000000...(×64)
fatal: Invalid symmetric difference expression 3dac4c61...6dce8d...000000...(×64)

pre-commit surfaces these as An unexpected error has occurred: CalledProcessError and exits non-zero, so git push origin --delete <branch> is refused on every sha256 repo. Branch deletion has to go through the forge's web UI or API to succeed.

Minimal reproducer:

git init --object-format=sha256 repro
cd repro
git config user.email t@e.com && git config user.name T
git commit --allow-empty -m init
cat > .pre-commit-config.yaml <<'EOF'
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v5.0.0
    hooks:
      - id: trailing-whitespace
EOF
git add .pre-commit-config.yaml && git commit -m "pc"
pre-commit install --hook-type pre-push

git checkout -b doomed
git commit --allow-empty -m x
SHA=$(git rev-parse HEAD)
ZEROS=$(printf '0%.0s' $(seq 1 64))

# Feed git's pre-push stdin format directly — simulates `git push origin --delete doomed`
printf '(delete) %s refs/heads/doomed %s\n' "$ZEROS" "$SHA" | .git/hooks/pre-push origin .
# exit code 3, error above

Suggested fix: accept an all-zero OID of either supported length (40 or 64), or (more general) read the repo's zero OID at runtime. A one-liner that covers both currently-supported object formats:

def _is_zero_oid(oid: str) -> bool:
    return len(oid) in (40, 64) and set(oid) == {'0'}

Callers become if _is_zero_oid(local_sha): continue and elif not _is_zero_oid(remote_sha) and _rev_exists(remote_sha):. A strictly hash-agnostic alternative reads git rev-parse --show-object-format once per hook invocation and computes the zero string from it.

Happy to open a PR if helpful.

pre-commit --version

pre-commit 4.5.1 (reproduced); Z40 = '0' * 40 is still on line 14 of hook_impl.py on current main (f35134b) and in the 4.6.0 tag, so the bug is present on both.

.pre-commit-config.yaml

repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v5.0.0
    hooks:
      - id: trailing-whitespace

~/.cache/pre-commit/pre-commit.log (if present)

### version information

pre-commit version: 4.5.1
git --version: git version 2.47.3
sys.version:
    3.12.12 (main, Jan 16 2026, 00:00:00) [GCC 14.3.1 20250617 (Red Hat 14.3.1-2)]
sys.executable: /home/rlichten/.local/share/uv/tools/pre-commit/bin/python
os.name: posix
sys.platform: linux

### error information

An unexpected error has occurred: CalledProcessError: command: ('/usr/bin/git', 'diff', '--name-only', '--no-ext-diff', '-z', '<sha>..<64-zeros>')
return code: 128
stdout: (none)
stderr:
    fatal: Invalid revision range <sha>..<64-zeros>

Traceback (most recent call last):
  File ".../pre_commit/git.py", line 161, in get_changed_files
    _, out, _ = cmd_output(*diff_cmd, f'{old}...{new}')
  ...
pre_commit.util.CalledProcessError: command: ('/usr/bin/git', 'diff', '--name-only', '--no-ext-diff', '-z', '<sha>...<64-zeros>')
return code: 128
stderr:
    fatal: Invalid symmetric difference expression <sha>...<64-zeros>

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions