Deemwar Monads & Abstractions

Git Release History

Source: Maybe a better way to track the release history of a Git repo, by Mark Dominus

One tag per deploy turns “what do we roll back to?” into a list, filter and sort over thousands of refs. Mark Dominus proposed one ref per app and tier instead, where each deploy adds a commit with two parents. He wrote it in 2019 and doubts it was ever adopted.

1 ref per (tier, app)
2 parents per deploy commit
2019 proposal year

The pain

At ZipRecruiter, every deploy to staging or production created a tag object named like refs/release/stg.www.our-app-2019-09-06T114410…-treesha-…. Tier, app, time and tree hash all lived in the name.

There came to be thousands of them. Git, he writes, handles thousands of commits well but not thousands of refs. At the time, refs lived in a packed-refs file, and resolving a name meant searching it, locally and on the Git server. git fetch and git branch output filled with release tags.

The natural questions got hard. “When did we last deploy www.our-app to production?” meant: list every tag, filter for prod.www.our-app, sort by date, take the last. Rolling back a bad staging deploy meant the same list, then the second-last item.

Listall tags
Filtertier + app
Sortby date
Lastor second-last

He names two more problems: the metadata was only a naming convention Git could not read, and tags are not immutable. They can be deleted or added under any name.

The proposal

Keep one ref per (tier, app), for example stg.www.our-app. It points to a metainfo commit. That commit has two parents: the previous metainfo commit, and the commit that was actually released. The release time is the metainfo commit’s own creation time.

first parent → previous record · second parent → what shipped

One deploy, step by step

  1. 1

    Write a message

    release tool

    Any useful text. He suggests TOML or git interpret-trailers for structure.

  2. 2

    Create the two-parent commit

    git commit-tree

    git commit-tree -p refs/release/stg.www.our-app -p H H^{tree} with the message on stdin. It prints the new commit’s SHA, N.

  3. 3

    Move the ref

    git push

    git push origin N:refs/release/stg.www.our-app. The ref count stays at one.

Reading it back

QuestionBefore: tagsProposal
Current releaselist, filter, sort, lastHISTORY^2
Rollback targetlist, filter, sort, second-lastHISTORY~1^2
Release n agolist, filter, sort, count backHISTORY~n^2
Full historyparse tag namesgit log HISTORY
Last release before a dateparse and compare namesgit log -1 --until=date HISTORY
Diff of last two releasesfind both tags firstgit diff HISTORY~1^2 HISTORY^2

Fetch the whole history once with git fetch origin refs/release/stg.www.our-app:HISTORY. A rollback can point a new metainfo commit at the exact same release commit as before.

On performance, he notes this is the same linked list git log already walks: Git took a few seconds to trace their 275,000-commit monorepo history, so a few hundred release commits is not a concern.

Guardrails

Read the source

Mark Dominus: a proposal for release history in Git.

Discussion on Hacker News.

Sources: every fact on this page is from the Dominus post above. “1 ref”, “2 parents” and “2019” are stated there.