A good audit trail can become a bad index.
Mark Dominus describes a monorepo that created one Git tag for every deployment target, application, timestamp, and released tree. The tags kept a complete deployment record. After thousands of releases, ref lookup slowed down and routine command output filled with release names.[1]
The naming convention also carried data that Git could not validate. To answer "what ran before this release?" the release system had to list every tag, filter the names, sort the timestamps, and select the previous entry.
Keep the audit trail. Stop making every old release a permanent dashboard switch.
Use commits for history and a ref for the head.
The proposal gives each deployment target one ref, such as refs/release/prod.app. That ref points to the newest release-record commit. Each record uses its first parent for the previous release record and its second parent for the exact application commit that ran.[1]
Git already knows how to walk commit parents. git log can list the release chain. HEAD^2 selects the current released commit. HEAD~1^2 selects the previous one. A diff between those two commits answers what changed without parsing timestamped ref names.
The plumbing supports the shape. git commit-tree creates a commit from a tree and accepts more than one parent. Git defines such a commit as a merge between lines of history.[2]
Move the pointer with a guard.
A release record is useful only if concurrent publishers cannot silently overwrite each other. git update-ref <ref> <new> <old> updates the ref only when its current object ID still matches the expected old value. Its standard-input mode can apply several ref changes together.[3]
That compare-and-swap check belongs in the release path. It does not make the deployment immutable. A user with permission can still delete or rewrite the ref and its history. Protect the namespace, retain remote logs, and decide who can repair a broken chain.
We tested the graph, not a production release system.
We built a disposable Git repository with a root record, two application commits, two release-record commits, and one release ref. The current record's second parent resolved to the second application commit. The previous record resolved through the first-parent chain. A guarded update-ref left the ref on the expected new record.
The fixture produced one ref for two release records. A diff between the two second parents reported one file changed, with one insertion and one deletion. This proves the graph and commands work in a small repository. It does not measure ref performance, remote push races, access control, garbage collection, or disaster recovery.
Keep the human changelog separate.
A deployment ledger answers what ran, where, and when. A changelog explains notable changes to people. Keep a Changelog says a changelog should be curated and should not be a dump of Git history.[4] One record should not do both jobs.
Adopt the chain only if release refs are a measured problem or if previous-deployment queries are brittle. Start with one noncritical target. Verify the current release, previous release, diff, rollback, concurrent update failure, export, and restore. The drive chain below drafts that test sheet. It does not publish a ref or approve a rollout.