Repository source: docs/specs/history_rewrite_record_v1.md
History rewrite record v1
A history purge rewrites every commit after the first one that touched a purged
path, so each of those commits gets a new identity. This record keeps the old
and new identities together so /source/blob/<commit>/... and
/source/commits/<commit> links written before the purge keep resolving. It is
written once per purge, committed with the purge, and never edited. It is
instance-local: archives and imports neither export nor accept it.
Canonical path rule
records/history-rewrites/history-rewrite-<YYYYMMDD>T<HHMMSS>Z.txt, where the timestamp isRewritten-Atwith punctuation removed.A run with more than 100,000 mappings continues in
records/history-rewrites/history-rewrite-<timestamp>-<part>.txtfor parts 2 and up. All parts of one run share the header values exceptPart.Two runs cannot share a second; the writer refuses to overwrite.
Headers
All headers are required exactly once, in this order. UTF-8, LF and a final newline are required; each file is at most 16 MiB.
Format:history-rewrite-v1Rewritten-At: RFC 3339 UTC timestamp of the rewrite, matching the filenameReason:purgeReceipt-Count: number of accepteddeletereceipts that drove the run, a non-negative integer without sign or leading zerosPart: 1-based part numberParts: total parts in the run;Partmust not exceed it
Body
Zero or more mapping blocks, each separated and terminated by a blank line:
Old-Commit: the commit identity before the rewrite, 40 lowercase hexNew-Commit: the identity after the rewrite, 40 lowercase hex
Within one file Old-Commit values are strictly ascending, no two blocks share a
New-Commit, and Old-Commit never equals New-Commit: commits whose identity
did not change (everything before the first affected commit) are omitted. A file
holds at most 100,000 blocks. Unknown or duplicate headers, header order
changes, unknown block fields, uppercase hex, an identity mapping, a repeated
Old-Commit or New-Commit, out-of-order blocks, or a filename that disagrees
with Rewritten-At and Part are rejected.
Resolution
To resolve an identity, apply every record in ascending Rewritten-At then
Part order: whenever the current identity appears as an Old-Commit, continue
with its New-Commit. The result is the newest identity; an identity that no
record names resolves to itself. Routes consult the map only after Git reports
the commit unknown, so a live identity is never redirected.
Example
Format: history-rewrite-v1
Rewritten-At: 2026-11-01T10:15:00Z
Reason: purge
Receipt-Count: 2
Part: 1
Parts: 1
Old-Commit: 1111111111111111111111111111111111111111
New-Commit: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Old-Commit: 2222222222222222222222222222222222222222
New-Commit: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
Stored at records/history-rewrites/history-rewrite-20261101T101500Z.txt.
Existing records
There are none; this format is introduced with the first purge. A later
format version adds a new Format value and a resolver that reads both.