Remapping Evidence Hashes After a Git History Scrub
Commit hashes change in bulk when git history is rewritten. Remap Bukti evidence references via commit-map, including zero-hash handling.
TL;DR
A PII history rewrite silently killed seven commit hashes stored as evidence on an issue board. Rewriting any commit changes its hash plus every descendant's, and only the commit-map file from git filter-repo reliably maps old hashes to new ones. Fix it by remapping and verifying each hash, then store future evidence as tags or exported mappings.
Seven issues on the work board of a public event management project keep a Bukti (evidence) field holding a commit hash. Running git cat-file -e against the first hash returns nothing but an error. The PII scrub over the repository history had finished and looked successful, yet nobody had checked its side effect on the evidence fields: all seven links went dead without a sound.
What a History Rewrite Does to Hashes
The natural first assumption: only the commits carrying sensitive data got new hashes. That guess is wrong. A commit hash is computed over the commit's contents, including the message, author identity, tree, and its parent's hash [3]. Change one old commit and that commit plus every descendant gets a new hash, chained all the way to the tip. GitHub's documentation lists changed commit hashes as a first-class side effect: tools and automation that depend on hashes staying stable will break or misbehave [2]. Other documented side effects ride along: stale clones that pull and push again can reintroduce the sensitive data, diff views of closed pull requests disappear, and commit and tag signatures are dropped [2].
The only trustworthy source for an old-to-new mapping is the .git/filter-repo/commit-map file, written by git filter-repo on every run [1]. It covers every commit in the rewrite range, including the ones whose hash did not change [1]. The first line is a header with the columns old and new [1]. A zero value in the new column means the commit was removed entirely [1]; no remap can bring it back, the reference has to move to an artifact that survived. Note that filter-repo also rewrites hash references inside commit messages by default [1], and it can create replace-refs so old full-length IDs keep resolving, but only if those replace refs are pushed and fetched manually [1]. The tool itself is the Git project's recommended replacement for filter-branch and needs git 2.36.0 or newer plus Python 3.6 [4].
The Remap Procedure
The repair runs in four steps. First, export the stale hashes from the issue board into a text file, say stale_hashes.txt. Second, build the mapping from commit-map. Rows with a zero hash in the new column are skipped and logged as unrecoverable; their references move to a surviving artifact such as the replacement commit or a release tag. Third, update every Bukti field with the mapped new hash. Fourth, verify each new hash actually points at a living commit:
git cat-file -e <new_hash>^{commit}
Exit code 0 means the hash is valid. Steps two through four fit in one short script:
#!/bin/bash
# Remap evidence hashes: read stale hashes, look them up in commit-map
MAP_FILE=".git/filter-repo/commit-map"
HASH_FILE="stale_hashes.txt"
while read -r old_hash; do
mapped_line=$(grep "^${old_hash}" "$MAP_FILE")
if [ -z "$mapped_line" ]; then
echo "Hash $old_hash tidak ditemukan di commit-map"
else
new_hash=$(echo "$mapped_line" | awk '{print $2}')
if [ "$new_hash" = "0000000000000000000000000000000000000000" ]; then
echo "Hash $old_hash -> REMOVED (arahkan ke artefak pengganti)"
else
echo "Hash $old_hash -> $new_hash"
fi
fi
done < "$HASH_FILE"
The script deliberately changes nothing. It only prints the mapping, so the result can be reviewed before the board's evidence fields are updated one by one.
Two symptoms show up most often when this is run without reading the fine print. First, the old hash is missing from commit-map entirely; usually the file was overwritten by a later filter-repo run, and the only way out is restoring from a snapshot or the old repository backup. Second, every hash passes verification yet the issues still look wrong; a correct mapping does not mean the board was actually updated, so the final check has to re-read the field contents, not the script output.
Keeping References Alive
Every hash stored outside the repository, in an issue board, QA documents, or approval sheets, is collateral damage of every history rewrite. The longer-lived pattern: store evidence as tags or branches named after the issue, or point at content-addressed artifacts. When a history rewrite is unavoidable, export commit-map to a durable location immediately after the run, before the next run overwrites the file. Relying on raw hashes as long-term references is a fragile design; the mapping happens once and only commit-map catches it.