Code versions, releases and tags

Last updated on 2025-10-31 | Edit this page

Overview

Questions

  • What is a Git tag and how does it differ from a branch?
  • How can I tag commits?
  • How and when should I release a new version of my code?
  • What is the difference between major and minor version changes?
  • How can I effectively communicate what has changed between versions?
  • How can I publish a release on Github?

Objectives

  • Understand what is meant by a release and a version
  • Know how to tag a given commit
  • Understand how to give your software meaningful version numbers with semantic versioning
  • Know how to push your tags and publish a release of your software

Background: To release or not to release?


All of you will already be familiar with the concept of software versioning. Often when you download a piece of software from its website it’ll tell you that it’s v13.4.2 (or whatever) that you’re downloading.

Releasing software with an explicit version number like this is a common practice and one that you may eventually consider for some of your own projects. We will show you how to do this using Git and GitHub. Even if you never end up making a release yourself, rest assured that sooner or later you will have to work with a repository which uses releases like this, so it’s important to understand the concepts at least.

The first question you might have is:

Why do I need a “version” for my software? Isn’t Git tracking the version anyway?

Sort of. The problem here is that the word “version” can mean several different things in the context of Git. Ordinarily, when people talk about a particular version of a piece of software, they mean a version with a particular release number, such as v13.4.2. However, in another sense, each commit in your repository represents a different version of the code and can be represented by a unique commit hash (e.g. a34042b).

To avoid confusion, people usually refer to the first kind of version as a “release” and the second as a “commit”, which is what we’ll do here.

When should I consider creating releases?

You might consider creating releases if:

  • You have a large, distributed user base and you don’t want them just using some random commit from your repository
  • You periodically add or change features in a non-backwards-compatible way
  • You want to bundle these features together and advertise them to your users
  • Your software is complex and you can’t guarantee that every commit on main has been thoroughly tested
  • You want to be able to support several versions of your software simultaneously (e.g. you intend to maintain a v2 of your software, even after v3 has been released)
  • You want to provide a place to download files that you’ve generated (such as .exe files) and distribute them to users

In general, though, creating a release is also just a convenient way to label a particular commit, so it’s a useful way to ensure that the users you’re sharing it with -- who may not be technically savvy – are using a specific commit, rather than simply whatever the latest commit on main is.

When don’t I need to create releases for my software?

You probably don’t need to create releases for your software if:

  • The code never changes
  • You’re the only developer and the only user (e.g. it’s some analysis scripts that only you use)
  • (OR there are only a few of you and you all just use the latest commit etc.)
  • The software is simple and doesn’t change much from one commit to the next

In this case, if you just need to clarify which commit you’re working from (e.g. to a colleague), you can always obtain the current commit hash like so:

commands, sh git rev-parse --short HEAD

OUTPUT

a34042b

Labelling a particular commit with git tag


Git tags provide a way to give human-readable names to specific commits. We will now go through how to add and remove tags to your repository.

Firstly, remind yourself what the history for your recipe repository looks like with git graph. Mine looks like this:

git graph

OUTPUT

* a34042b (HEAD -> spicy) Chillies added to the mix
* d10e1e9 (main) Guacamole must be served cold
* 5344d8f Revert "Added 1/2 onion to ingredients"
*  fe0d257 Merge branch 'experiment'
* 99b2352 Reduced the amount of coriander
* 2c2d0e2 Merge branch 'experiment'
|\
* | 6a2a76f Corrected typo in ingredients.md
| * d9043d2 try with some coriander
|/
* 57d4505 (origin/main) Revert "Added instruction to enjoy"
* 5cb4883 Added 1/2 onion to ingredients
* 43536f3 Added instruction to enjoy
* 745fb8b adding ingredients and instructions

(Note that yours may look different depending on whether you followed the steps yourself or downloaded the pre-made repository.)

Let’s say that you have decided that the point at which you added half an onion was a highpoint in the recipe’s history and you want to make a note of which commit that was for a future date by giving it the tag “tasty”. You can do this like so:

git tag tasty [commit hash]

In my case, I ran:

git tag tasty 5cb4883

You can list the tags for your repo by running git tag without any arguments:

git tag

OUTPUT

tasty

To check which commit hash this corresponds to, use:

git rev-parse --short tasty

OUTPUT

5cb4883

Double-check that this is the commit you intended to tag by running git log (or git graph) again.

Note that tasty can now be used like other git references, such as commit hashes and branch names. For example, you can run git switch --detach tasty to (temporarily) update the contents of your repo to be as they were back when you added half an onion to the instructions. (Note that you need to include the --detach option!)

You may now be wondering, if this is the case, then how is a tag different from a branch? Try switching to tasty to see what happens:

git switch tasty

OUTPUT

fatal: a branch is expected, got tag 'tasty'
hint: If you want to detach HEAD at the commit, try again with the --detach option.

Git provides some helpful output here. To “detach HEAD” means to change the current working state to a commit that isn’t on any branch at all, so if you commit any changes, they won’t be saved to any branch. Note that your tag will stay pointing to the same commit it was before. This is the difference between a branch and a tag. The tip of a branch points to the last committed change to the branch, whereas a tag always points to a specific commit. Think about it this way: when you release a piece of software, you want that version – say, v1.0 – to represent the code in one unique state. You don’t want two of your users to be using two different versions of the code both labelled v1.0, for example.

Let’s try again using the --detach option:

git switch --detach tasty

OUTPUT

HEAD is now at 5cb4883 Added 1/2 onion to ingredients

Now it works. Fortunately, a detached HEAD is a much less serious affliction for git repos than human beings, and you can reattach it by simply checking out a branch:

git switch main

Assume now that you have decided that you no longer want this tag (perhaps on eating, it turned out not to be tasty after all). You can delete the tag like so:

git tag -d tasty

OUTPUT

Deleted tag 'tasty' (was 5cb4883)
Discussion

Exercise: Try creating your own tag

Now try it yourself. Choose a different commit and give it a label using git tag. Confirm that you can check out this commit. Once you have finished, delete it.

There is one last important thing to know about git tags. Like branches, they are not automatically synced with your remote (e.g. GitHub) and have to be pushed explicitly. We will cover this later, but first let’s discuss how to give your software a descriptive version number.

What’s in a version number?


While your code will no doubt smell as sweet however you number your releases, it is useful for your users if you use a versioning scheme that conveys some information about the kind of thing that is likely to have changed since the last version. Unfortunately, this practice is not universal. Often with software releases, the meaning behind the version number is rather opaque, except for the fact that higher numbers generally mean “newer”. It often isn’t obvious to what extent a new version of a piece of software is compatible with older versions – if at all!

Amidst this confusion, a convention that is becoming increasingly common is so-called semantic versioning. A semantic version number is composed of three numbers separated by dots, e.g. v1.2.3. In order, these numbers are referred to as the “major version”, “minor version” and “patch version”. Generally speaking, changes to the numbers are less significant as you go to the right, i.e. an increase in the major version number indicates that more has changed than an increase in the minor version number.

However, the semantic versioning specification actually has stricter requirements than this, namely that you should increment:

  1. The major version when you make incompatible changes
  2. The minor version when you add functionality in a backwards compatible manner
  3. The patch version when you make backwards compatible bug fixes

While this degree of precision may not be required for any of your own projects, it is a good convention to stick to nonetheless as other developers will probably assume that this is what you are using.

Let’s add a proper version tag to the recipe repository. Give the first commit to the repository (in mine this is 745fb8b) the tag v0.0.1, which is often used as the first tagged release for a project. (Another common convention is to indicate that the software is still experimental by giving it a major version number of zero.)

git tag v0.0.1 745fb8b

Verify that the tag has been added:

git tag

OUTPUT

v0.0.1

Now your repository has a proper version tag. Next, let’s push this tag to GitHub so the rest of the world can see it.

Pushing your tags and publishing a release


To push your tags to GitHub, do the following:

git push --tags

You should see something like this:

OUTPUT

Total 0 (delta 0), reused 0 (delta 0), pack-reused 0
To https://github.com/alexdewar/recipe.git
 * [new tag]         v0.0.1 -> v0.0.1

Now open your browser and go to the GitHub page for your recipe repository (see the link in the command output). Mine is here, for example.

If you look in the right-hand pane, under “Releases”, you should now see “1 tags”:

1 tags

This refers to the v0.0.1 tag you just pushed. (If there are two tags, you may have forgotten to delete the tasty tag, which doesn’t matter much.)

Under “1 tags”, there is a link entitled “Create a new release”. Click it and you should see something like the following:

Creating a new release

Click “Choose a tag” then select your tag “v0.0.1” from the dropdown list:

Choose your tag

For the release title, you can just put “v0.0.1” again. Then add a description of your choosing. (You can check the “Set as pre-release” box if you want to indicate to your users that the recipe isn’t production ready.) Note that there is another field: “Attach binaries”. We won’t be using this now, but if you did have a compiled version of your software (e.g. as an .exe file), this is where you could upload it.

When you’re finished, click “Publish release”:

Publish your release

Now you should be redirected to a page that looks like this:

View release

Congratulations, you have made your first release! You can share the link to this page with others if you want to notify them of the release. Alternatively, users can find your release from the repo’s main page by clicking on “Releases”.

Discussion

Exercise: Publish another release

Now try creating another release corresponding to a newer version of the recipe, following the same steps you did for v0.0.1.

Your first task will be to choose a sensible version number for the release, using semantic versioning. This is necessarily a bit subjective, but you should be able to justify your decision .

End by pushing the tag to GitHub and issuing another release, with an appropriate description.

Key Points
  • A version of your code with a release number (e.g. v13.4.2) is referred to as a release
  • A version of your code represented by a commit hash (e.g. 047e4fe) is just referred to as a commit
  • Publishing a release can be a good way to bundle features and ensure your users use a specific version of your code
  • git tag allows you to give a commit a human-readable name, such as a version number
  • Semantic versioning is a common convention for conveying to your users what a new version number means
  • Tags need to be explicitly pushed to remotes with git push --tags
  • You can use a tag as the basis for a release on GitHub