Demo Repo
I’ve taken the approaches covered in this post and created a fully-functional demo repo to demonstrate them in action:
Intro
When building a project that lives in a Git repository and auto-deploys elsewhere, a common concern is how to be 100% absolutely sure that the live “deployed” version is up-to-date. If I go to the live, production application, how can I trust that what I am seeing is the latest pushed code in my repository?
Just because my CI/CD pipeline passed does not necessarily mean that the latest commit is live yet. Furthermore, if something goes bad, I want to be able to know at a glance exactly what code is live in production. A simple “pass / fail” badge cannot tell me that.
Close, but Not Good Enough
Many automated deploy systems provide a build status badge, but this doesn’t tell us much:
Standard Netlify Status Badge:
Many apps also display in the deployed UI the current version string of the app (e.g. 1.8.2-beta.1.10
), but this actually still leaves a lot of room for ambiguity; what if a developer used --force
to override the head of the main branch? What if there have been commits added to the main branch, but no new release authored? Etc.
Instead of a version string, a nice alternative (or better yet, an addition) is to use the actual SHA of the latest commit as a status indicator. This makes it very easy to compare against the latest commit in version control, and verify that the correct set of changes are live.
Furthermore, no two commits can have the same SHA, even if --force
has been used, so if I know the SHA that is “live”, I know exactly what code is running, and can even quickly “checkout” that exact version of code in my local environment.
💡 Similar to how you can use
git checkout {branch_name}
, you can also usegit checkout {SHA}
to checkout your entire repo at that commit. It kind of feels like time travelling!
Methods for Creating SHA Badges
The general concept that we are going to employ is that our build step, which runs on every deploy, is now going to save the current SHA of the branch (aka the tip) in an accessible format that can be used for visual indicators.
Two easy approaches that build off this concept, and which I’ll explore here, are JSON files and raw SVG files.
JSON File
The basic idea with the JSON file approach is that, at build time, we stash the value of the latest SHA in a shared JSON file. This makes it easy to reuse across our codebase, possibly even at runtime, and in a widely compatible format.
The JSON file approach is my favorite, for a couple of reasons:
- It is easier than dealing with SVG string building, but can be combined with that approach easily (more details further below)
- The JSON file can be re-used, and even imported into runtime code (assuming they share access)!
- Usable by APIs and 3rd party services (including Shields.io for badges)
JSON File – Getting and Saving the SHA
The implementation of this task could be accomplished in an endless number of ways, and the best option probably depends on the specifics of your build environment, project type, and operating system.
For a generic example, here is how I how I have integrated this approach into a NodeJS based project, within a package.json
file:
{
"scripts": {
"build-badge": "$NETLIFY && git rev-parse --short HEAD | xargs -I % printf '{\"sha\":\"%\"}' > ./static/build-info.json",
"build": "(yarn run build-badge || true) && gatsby build"
}
}
The most important thing about the above command is that it modifies our build command, which used to be just gatsby build
, to include a step that saves the current SHA to a JSON file (./static/build-info.json
). If you wanted to, you could replace a lot of the bash stuff with a NodeJS or python script that does the same thing.
If you want a breakdown of the above command, you can view it below
Open Command Explanation
To break this down further, the build-badge
command is composed of:
$NETLIFY
is an environmental variable that only exists within my build environment (Netlify servers).- You could use any value that evaluates to true in your build system
- Or, if you always want this file generated regardless of local vs production, you could omit the whole
$VARIABLE &&
part
git rev-parse --short HEAD
- This is a git command to get the short version of the last commit SHA
- You could use the full SHA instead; just omit
--short
- If you are looking for a lot of nifty git commands like this one, here is a shameless plug to check out my Git Cheat Sheet (this command is on it!)
xargs -I % printf '{\"sha\":\"%\"}'
- Not going to get too much into this, but it is a fancy way of passing the SHA from the last section into a stringified JSON representation
> ./static/build-info.json
- Save the generated JSON string to a file. This could be any file, and it doesn’t even necessarily need the
.json
extension - You should make sure that whatever directory you save it to is going to be accessible after the build is complete, either internally and/or externally (hosted)
- Save the generated JSON string to a file. This could be any file, and it doesn’t even necessarily need the
And I’ve changed my build command from gatsby build
to (yarn run build-badge || true) && gatsby build
:
- This is an easy way to make sure that
gatsby build
, the true build command, always runs regardless of the success or failure of thebuild-badge
command.
Again, as an alternative to a long bash command you could alternatively extract and save the SHA entirely in your favorite scripting language of choice (NodeJS, Python, etc.).
JSON File – Using the Stored SHA
As-is, once we have our JSON file, that alone is enough to be able to add badges or pull the SHA value into front-end code. For example, we can use the dynamic badge feature of Shields.io to easily get a SVG URL that displays our SHA. Something like:
<img src="https://img.shields.io/badge/dynamic/json?color=blue&label=Deployed%20SHA&query=sha&url=https%3A%2F%2Fgit-sha-badges.netlify.app%2Fbuild-info.json" alt="Deployed SHA" />
Notice the importance of:
- query=sha
- url=HOSTED_SITE/build-info.json
If you wanted to pull it into your code, in CommonJS, that is as easy as:
const buildInfo = require(`${BUILD_DIR}/build-info.json`);
console.log(buildInfo.sha);
SVG File
Rather than displaying your SHA in front-end code by pulling it in via AJAX or inside framework components (e.g. a React component), you could also pre-generate SVG badges at build time, with just a little bit of code.
The easiest approach is to have 99% of the SVG badge ready-to-go, stored as a string. You can use whatever method you prefer for designing your badge. The main thing to do is leave room for the SHA to be inserted at build time, as a <text></text>
element inside the SVG.
At build time, you combine your SVG template string with the latest SHA (this is where it is handy to have that build-info.json
already generated), and then write out the resulting string to an actual .svg
file, which can be served.
You can checkout how I put this all together in build.js
in my demo repo.
In pseudo code, this approach might look something like:
// Compose full SVG string
const svgStr = '<svg>' + svgTemplateStr + '<text>' + sha + '</text></svg>';
// Save to SVG
saveFile(svgStr, 'my-badge.svg');
And don’t forget, you can get as fancy as you want with your generated SVGs 😄
Bonus: Github Commit Badges
If all you want is a badge that shows the current HEAD of a particular branch in Github (i.e. the most recent pushed commit) – you can actually do this entirely with just Shields.io and the public Github API (no credentials required).
Caveat: this requires that your repo is published as public
First, you need get the API endpoint that returns HEAD info, for your specific repo and branch combo. The syntax follows:
https://api.github.com/repos/{USER}/{REPO}/git/refs/heads/{BRANCH}
So, for example, to get the currently pushed tip for my main
branch in my demo repo for this post (joshuatz/git-sha-badges
), I can use api.github.com/repos/joshuatz/git-sha-badges/git/refs/heads/main.
If you try that URL, you will get a JSON response, including:
{
// Other stuff
"object": {
"sha": "____",
"type": "commit"
}
}
Now, we can plug that endpoint directly into Shield’s amazing dynamic badge generator, which accepts a JSON endpoint and lets us control badge settings through query string parameters. By tweaking the parameters, we can get an orange badge that displays the object.sha
value in the endpoint:
API Endpoint:
https://api.github.com/repos/joshuatz/git-sha-badges/git/refs/heads/main
Shield Badge Base Endpoint
https://img.shields.io/badge/dynamic/json
Shield Badge URL (raw)
https://img.shields.io/badge/dynamic/json?color=orange&label=Github SHA&query=object.sha&url=https://api.github.com/repos/joshuatz/git-sha-badges/git/refs/heads/main
Same URL, encoded:
https://img.shields.io/badge/dynamic/json?color=orange&label=Github%20SHA&query=object.sha&url=https%3A%2F%2Fapi.github.com%2Frepos%2Fjoshuatz%2Fgit-sha-badges%2Fgit%2Frefs%2Fheads%2Fmain
---
Final badge code
---
Markdown:
![Github SHA](https://img.shields.io/badge/dynamic/json?color=orange&label=Github%20SHA&query=object.sha&url=https%3A%2F%2Fapi.github.com%2Frepos%2Fjoshuatz%2Fgit-sha-badges%2Fgit%2Frefs%2Fheads%2Fmain)
HTML
<img src="https://img.shields.io/badge/dynamic/json?color=orange&label=Github%20SHA&query=object.sha&url=https%3A%2F%2Fapi.github.com%2Frepos%2Fjoshuatz%2Fgit-sha-badges%2Fgit%2Frefs%2Fheads%2Fmain" alt="Github SHA" />
And, here it is (this is a live badge!):
Wrap Up
I want to wrap up this post by pointing out that, although I gave some specific examples and code samples, the approaches throughout this post are fairly generic in nature and could be extended to solutions beyond just adding Git SHA badges.
For example, if you maintain several build and deploy targets (e.g. dev
, test
, staging-alpha
, production
, etc.), you could re-use most of the solutions through this post to add visual indicators to each environment that makes it clear what system you are looking at.
Hope this post helps someone out there! (And kudos to anyone that understands and appreciates what my fancy SVG example is referencing).