Github: locally invoked release process for a Go binary

The GitHub “Release” page for a repository can provide your consumers a convenient way to download a binary version of your software as well as track the latest changes and enhancements.

In this article, I will show how to invoke a local release process for a Go language binary.  A new Github release will be created based on semantic tags (e.g. v1.0.1) that includes the relevant commits as release notes and the statically-compiled Go binary as an asset.

Prerequisites

Calculate Semantic version tag

Our process will be to create a semantic tag for every release (e.g. v1.0.1).   If there is already a semantic tag defined, we will increment its minor patch to get the next release (e.g. v1.0.1 -> v1.0.2).

We need to discover if the current branch is already semantically tagged.  If it is, then we will increment the minor patch.  If not, then we assume we are at “v1.0.0”

# get current list of semantic tags
git tag --sort=-committerdate | grep ^v

# get latest semantic tag, will be empty if none defined yet
semantic_version=$(git tag --sort=-committerdate | grep ^v | grep -Po '^v[0-9]*.[0-9]*.[0-9]*' | head -n1)

# if non-empty, calculate next patch version
major_minor=$(echo "$semantic_version" | cut -d'.' -f1-2)
patch=$(echo "$semantic_version" | cut -d'.' -f3)
((patch++))
newtag="${major_minor}.${patch}"

# if latest semantic_version was empty, that means we are starting fresh
[ -n "$semantic_version" ] || newtag="v1.0.0"

echo "The new semantic tag is: $newtag"

Compile GoLang binary

This will be specific to your project and its packaging/dependencies, but for a simple source file ‘src/main.go’ with minimal dependencies, here is an example that would create a ‘build/main’ executable.

# build simple GoLang executable
mkdir -p build
cp src/main.go build/.
cd build
go mod init myowner/mypackage
go mod tidy
go build main.go
cd ..

# path to executable for upload to the release later
go_binary=build/main

Generate Release notes

Each release has a set of optional release notes so consumers can see what has changed, been fixed, etc.

For our small, single contributor project we just want the release notes to be based on direct commits to the repository.

# commits between latest HEAD and the previous version
git log HEAD...${semantic_version} --pretty="- %s " > /tmp/$newtag.log

# if semantic_version was empty and newtag=v1.0.0
git log --pretty="- %s " > /tmp/$newtag.log

Note: the Github CLI has a ‘generate-notes‘ flag that will create release notes based on merged pull requests.

Create Github Release

Finally, create the release in the Github repository.

# commit all changes
git commit -a -m "changes for new tag $newtag"

# create new tag and push
git tag $newtag && git push origin $newtag

# pull all commits
git push

# create release, attach Go binary
gh release create $newtag -F /tmp/$newtag.log $go_binary

Now if you go to your Github repo’s “Releases” section, you should see something similar to the screenshot below.

I have wrapped up the logic in this article into a Bash script, create_new_gh_release.sh

REFERENCES

stackoverflow, git commits between tags

cli.github.com, Github CLI manual

docs.github.com, Releases