> It seemed reasonable to me record the deployment history in the repository
That doesn't seem reasonable to me. You'd be using Git for persistence concerns that are not directly related to source code management.
"Environment X had commit a1b2c3d deployed at 1970-01-01T00:00:00Z" is all you need to store, and I don't see why you'd want to do that in the repo itself. Commit a1b2c3d exists regardless of where or when it's deployed.
Store that data in the thing that handles the deployments.
I've never thought having the environment in the version tag (or whatever resembles that) was a good idea. It is a remarkably common thing, though.
But it means that, by definition, we are deploying a different tag to dev, staging, and prod. (Or whatever your pre-prod envs are called.) Hopefully, these all point at the same commit … hopefully¹. (Worse, some schemes out there — not the TFA's — force them to never specify the same commit(!) and we devolve to "let's pray those different commits specify the same tree.)
Just tag a version number. Use the date, if you can't think of a version number. Separately, you maintain some file containing what tag/revision you want on each of your dev/stage/prod/etc. environments. This is basically the same rough scheme as what TFA lays out … but not special. Just text files, in a repo², like everything else. You can do it in a separate repository (I'd recommend) or the same one (I wouldn't recommend, but it can work).
The exemplar question,
> When did we last deploy the www.our-app app to production?
Is just the `git log` of the file that contains the hash that that app should be deployed to.
The SRE in me notes, however, that gitops-based deployments (what all of these schemes are) represent the point in time that a deployment is requested — not when it actually rolls out. Deploys that fail or are blocked on some externality can converge much much later than the git commit instructing them to occur is minted. Also, the SRE in me wants to notes that deploys in any realistic scenario are not a point in time. (It starts when you start bringing up the first replica of your app, and it finishes when the last replica of the older deployment has spun down. Depending on how much bloat you have … it can be a good little span of time.)
(¹TFA's scheme seems to contain a textual reference to the tree sha, but is that checked?)
²If you have worked with Flux or ArgoCD, you're almost certainly doing exactly what I am describing, though those are not the only two such systems capable of it.
Am I missing something but tag in git says nothing about actual deploy. It's at most a deploy attempt. What's wrong with CD which tracks actual deploys and records which commits are deployed and all related info like who and where?
It seems like storing the refs in a trie would be a relatively easy change, though backwards compatibility would be annoying.
Alternatively the names of the refs could be sorted.
Another reminder of how little of git I actually ever use.
Release should be separated from deployment, there should be virtually no difference between the code running production and other envs, instead of creating a release per environment you should just tag your code with an easy to read label (e.g. a version number) produce artifacts from that version of the code, then at deploy time you decide (either manually or through an automated logic) which version of the code you want to use. Ideally all "differences" at runtime between production and other envs should be defined at, well, runtime i.e. through environment variables (even the frontend stuff that you see usually made at build time, there should be a mechanism at runtime to avoid it) this way you have much more predictable behavior and it's easier to reason on, also you can physically separate building from deployment pipelines, so you can have much more agility on switching to whatever is needed, it's also much more easier on the brain (and for LLMs) to clearly see the steps and fix ci pipelines problems.
If you really need build time differences, then you should identify this difference by what really is not by the env you assume it will run on, because explicit is always better than implicit, especially for LLMs and new devs.