Git SHA Badges - Know Which Commit is Live
Tags: devops, webdev, github, git
Published: 2020-12-14 17:47:58

---
title: Git SHA Badges - Know Which Commit is Live
description: Want a clear visual indicator in your app or documentation so you know *exactly* which commit was pushed and successfully deployed? Then this post is for you.
canonical_url: https://joshuatz.com/posts/2020/git-sha-badges-for-deploys/
tags:
  - devops
  - webdev
  - github
  - git
---
## Demo Repo
I've taken the approaches covered in this post and created a fully-functional demo repo to demonstrate them in action:

 - 💾 [github.com/joshuatz/git-sha-badges](https://github.com/joshuatz/git-sha-badges).

## 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:
> 
> <img src="https://api.netlify.com/api/v1/badges/4b298306-7d69-4214-b790-b37a56e59ec4/deploy-status" alt="Netlify Status" />

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 use `git 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:

```json
{
  "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

{% details Show / Hide Details %}


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](https://docs.netlify.com/configure-builds/environment-variables/)). 
	- 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](https://docs.joshuatz.com/cheatsheets/git/) (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)

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 the `build-badge` command.


{% enddetails %}

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](https://shields.io/#dynamic-badge) 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
```

<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" />

If you wanted to pull it into your code, in CommonJS, that is as easy as:

```js
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](https://github.com/joshuatz/git-sha-badges/blob/main/build.js).

In pseudo code, this approach might look something like:

```js
// 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 😄

<img src="https://git-sha-badges.netlify.app/badge-fancy.svg" style="width:95%;margin:auto;display:block;max-width:400px;">

## 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](https://api.github.com/repos/joshuatz/git-sha-badges/git/refs/heads/main).

If you try that URL, you will get a JSON response, including:

```json
{
	// Other stuff
	"object": {
		"sha": "____",
		"type": "commit"
	}
}
```

Now, we can plug that endpoint directly into Shield's amazing [dynamic badge](https://shields.io/#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!):

![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)

## 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).

## More About Me (Joshua Tzucker):

 - 🔗<a href="https://joshuatz.com/" rel="noopener" target="_blank">joshuatz.com</a>
 - 💬<a href="https://twitter.com/1joshuatz" rel="noopener" target="_blank">@1joshuatz</a>
 - 📘<a href="https://docs.joshuatz.com/" rel="noopener" target="_blank">docs.joshuatz.com</a>
 - 💾<a href="https://github.com/joshuatz" rel="noopener" target="_blank">github.com/joshuatz</a>