Photo by Yancy Min on Unsplash
Git Tagging — How to Use them for Success
Writing maintainable and scalable code is hard when you are trying to engineer the next big craze in software. You have to version your software so that multiple people can help you build this tool, and on top of that you need a way to mark when the product is stable for use and when newer features are available. Thankfully, we have Git to help us out!
This article will introduce you to the basics of Git, including a brief history, the use of Git tags, using git tags with a Git branching strategy, and how to use GitHub Actions based on tags to create Releases.
What is Git?
Git is a version control system (VCS) created in 2005 by the Linux Community and in particular Linus Torvalds, creator of Linux. It was developed in a response to a previous VCS being used for the Linux kernel becoming a pay-to-use service. When creating a new VCS the goal was for the system to embody these goals:
- Speed
- Simple design
- Strong support for non-linear development (thousands of parallel branches)
- Fully distributed
- Able to handle large projects like the Linux kernel efficiently (speed and data size)
Now, Git is the most widely used VCS for software development. Most other version control systems use delta-based version control, but Git is a stream of snapshots to reference older versions, generally building upon the software. Most common git commands when contributing to a project are:
git clone <repo url, or git uri> #clones remote code to local machine
git branch #lists branches on local machine, -a will list remote and local
git checkout #switch to a different branch -b to create a branch and switch to it
git add <file name or names under a directory> #adds files to staging state to before commiting them
git commit -m "message here" #commits files in staging state with message
git push #push local commits to remote branch
git merge <commit or branch to merge into current branch> #merges work from one branch to another
git rebase #move or combine a series of commits to a new base commit
git cherry-pick #grab a commit and append it to your current working HEAD (HEAD being currently checked out latest commit)
For anyone who is interested in data structures, think of Git like a Directed Acyclic Graph (DAG). DAGs are, by design, always directed forward to the next node, without a cycle back to a previous node. Check out this Medium article for a more in depth explanation of DAG data structures.
How Does Tagging Work
That’s some of the basics of Git — but what are Git tags? Git tags are a reference to a commit (commits send latest changes of your revisions to the VCS, you use git push to then send those to the remote repository). Tags help create a readable history of the progression for your software; instead of remembering the commit c8ad8cc we can remember that this commit has a Semantic Version tag of v1.0.0 (major, minor, patch respectively). Some of the helpful Git commands for tagging are as followed:
git tag <tag name> #Create Lightweight Tag
git tag -a <tag name> -m <tag message> #Create Annotated Tag
git tag -n #List annotated tags
git tag -d <tag name> #Delete tag
git push origin <tag name> #Push a tag to remote Git repository
The big difference between lightweight tags and annotated tags are when an engineer is listing the annotated tags with git tag -n. Annotated tags will give a user more context around why the tag was used. Most commonly your tags will be Semantic Versions and these tags can be straightforward until one may see postfixes of -rc or a tag that may not be a version and instead be a string like development. Annotated Tags allow you to provide a small message that can clear up the meaning behind some of these other tags present in your Git History.
Another benefit of Git tags is that the majority of VCS platforms like GitHub, Bitbucket, or GitLab provide a user-friendly way to display source code at the point in time of a tag.
Why Versioning Matters
Versioning your software matters, and majority of the industry is familiar with Semantic Versioning. Depending on your team agreement, you can tag any new work as a new version or group pieces of work, like multiple bugfixes in a new version. For a quick explanation of how Semantic Versioning works:
- Patch Bump for bugfixes = v1.0.0 -> v1.0.1
- Minor Bumps for new features without breaking old functionality = v1.0.1 -> v1.2.0
- Major Bumps for big new additions and changes in how the application works = v1.2.0 -> v2.0.0
So why does versioning matter? It is to keep the progress of software trackable and show that the application is evolving, even if the interface may not change. Engineers can go back to a previous version that is currently in production and patch bugs the users may have found. It can also help establish milestones — what we call releases that may have a changelog of new features and bugs that have been patched since the last release.
The Software Development Lifecycle can take myriad forms — but let’s examine, for example, a project that may have multiple engineers working on it. For simplicity, we will be merging our ticket instead of rebasing. Everything starts on main branch, and in this scenario, it contains the code that is present in the teams Test environment. We have been tasked with creating a new feature, so a new branch will be made with the name of feature (or feat) and the ticket or issue number from our project management tool. By the time we made our first commit, others have committed more code to main branch.
We continue our work and open a Pull Request so an approver can check that our work is a valid contribution. When they approve, we merge this branch back to main and tag the merge commit on main with v1.0.0.
Merging feature branch and tagging merge commit
After the branch is merged, we can delete the branch so that the main branch has a clean, clear history. Our v1.0.0 tag makes it to Production and has been in use for a while. The contributors continue to build out future features — when all of the sudden, a bug is noticed by a user. The Tech Lead determines this bug needs to be fixed before new features are released, so the contributors fix the bug in one of the latest commits and verify the fix in Test environment.
Delete feature branch, during new development v1 has a bug, fix is made and pushed to main
In order to release the software, we must tag it to trigger an automated workflow. Our Tech Lead would like us to release the new patch without any new feature development that has been worked on; in order to do this, we create a branch off of our Git tag at v1.0.0 and use git cherry-pick 254214 to grab only the verified fix off of our main branch. When pushing this new commit to our branch, we tag it with v1.0.1 and delete the branch to clean up. The commit and tag will stay at this reference point and our automation will take care of releasing this new patch.
GitHub Tags option when looking at switch branches selector
GitHub Actions for Automation
Tags are a great trigger for automation, and most VCS platforms will provide this feature to you. Let’s use GitHub Workflows to build on our previous example the automation that takes place when a tag is created. Workflows consist of jobs, that in turn contain Actions, and Actions can be created for reusability in your jobs or other’s if published publicly.
Disclaimer: There are GitHub Actions that more effective in handling your tagging needs and they can help ensure your GitHub Workflows are readable. These are just to demonstrate an action with the use of the git tag command, similar to how one can manually push git tags. I encourage you to checkout the existing GitHub Actions and create automation useful to your project’s needs!
In this scenario we have a Node project, and with it the package.json that stores the version of our application. We want a workflow to create a Git tag every time something is merged into main or pushed to main (this could be an overzealous approach and purely up to team agreement). We can create something like the below to do this:
#.github/workflows/tagger.yml
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Get Version
run: echo "VERSION=$(cat package.json | grep \"version\" | cut -d'"' -f 4)" >> $GITHUB_ENV
- name: Tag
run: git tag -a v${{ env.VERSION }} -m "Releases new version"
- name: Push Tag
run: git push origin v${{ env.VERSION }}
We may want to take this a step further, to trigger a Workflow every time a tag is created and pushed to our remote repository. This next Workflow can create a GitHub Release of our application and generate specific information like a changelog of features and patches since our last release! This will help our users see the progress our app has made and new features that are available to them.
ArgoCD release page with related tag
In order to automate something like this we can provide another workflow definition with a couple of steps. This will build and zip our project as an artifact to attach to our release for downloading, creating the release found on our GitHub Repository!
#.github/workflows/releaser.yml
name: GitHub Actions Releaser
on:
push:d
tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
jobs:
build:
name: Create Release
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v2
- name: Build project # Using zip as an example
run: |
zip my-artifact README.md
- name: Create Release
id: releaser
uses: actions/create_release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
body: |
Changelog body goes here, this can be more dynamic than hard-coded
draft: false
prerelease: false
- name: Upload Release
id: upload-release
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.releaser.outputs.upload_url }} # This pulls from the CREATE RELEASE step above
asset_path: ./my-artifact.zip
asset_name: my-artifact.zip
asset_content_type: application/zip
Conclusion
Software is a game of decision-making and trade-offs. A common issue one must face when developing the next big app is how to version the software and how they may want to leverage their VCS to help. Git tagging offers a great solution to this problem and is a very common pattern in projects you will find in open-source communities and in workplace settings. Tagging can aid in onboarding of new contributors by providing a clear history of your applications evolution and even help reduce overhead by automating your releases and changelogs. Git is one of the most popular VCS and understanding the utility it can provide will help you in your role as a Software Engineer.
Top comments (0)