For managing large applications, versioning is essential. It helps track deployments and changes over time, making development more organized and reliable. Clear version numbers allow both developers and users to understand what kind of updates have been made—whether new features, bug fixes, or breaking changes. Semantic versioning (SemVer) assigns clear version numbers to software, while Git tags mark those versions in the project history.
This article explains how to version an application using SemVer and effectively manage those versions with Git tags.
1. Semantic Versioning (SemVer)
Basically semantic versioning is a set of rules for versioning a project in a standardized way. Go through the official documentation of semantic versioning for details.
Major Update: The first portion of the version indicates major changes. If the application has changes that are not backward compatible, this number will increase by
1
. Suppose a software is currently running on version2.x.x
, and the next update includes refactoring an API route URI for fetching the product list. In the new version, the previous endpoint will no longer work. This is considered a BREAKING CHANGE, so the next version should be3.x.x
.Minor Update: The second portion of the version indicates minor changes. If the changes are backward compatible, unlike major changes, then the second portion of the version number should be increased by
1
. Suppose a software is currently running on version1.1.x
, and the next update adds new endpoints for searching that do not break existing features. This is not a breaking change, so the next version will be1.2.x
.Patch: The third portion of the version indicates patches, such as bug fixes that are backward compatible. Suppose a software is currently running on version
1.1.1
and it has some bugs. If the next update only fixes those bugs, then the next version will be1.1.2
.Pre-release: This part is added with a hyphen followed by an identifier (
alpha
,beta
,rc
, etc.). It means the software is unstable and meant for testing. There is no strict rule for pre-release names in semantic versioning—alpha
,beta
, andrc
are commonly used conventions, not requirements. Usually,alpha
means the software is in an early stage and may have bugs,beta
means it is more complete but still unstable, andrc
(release candidate) means the update is near-final and likely to become the release unless major bugs are found. A pre-release version can also include dots, such as in1.0.0-alpha.1
. The following image shows a simple example of versioning from one stable version to another.
-
Build Metadata: This portion carries extra information about the version. It is an optional part added after a plus sign (
+
), such as1.3.0+build.456
or1.3.0+build.457
. It is used to provide details like the build number, commit hash, environment, etc. Build metadata does not affect version precedence. For example, if a software is built for two different stages—production
andstaging
—the build metadata can bex.y.z+prod
orx.y.z+staging
. It can also specify environments likex.y.z+linux-prod
orx.y.z+windows-prod
.
1.1. Important Rules
- Numeric identifiers cannot have leading zeros. For example-
01.1.1
,1.0.02
, or,1.2.3-beta01
is not a valid version. - When the minor version is updated, the patch version resets to
0
. When the major version is updated, both the minor and patch versions reset to0
. - The first public release should use version
1.0.0
. Major version zero (0.y.z
) is for initial development.
1.2. Precedence
- Precedence is decided by comparing the version numbers from left to right—major, minor, and patch—using numeric values. Means-
1.2.0
>1.1.3
>1.1.2
. - If the major, minor, and patch versions are the same, a version with a pre-release tag has lower precedence than the one without it. For example-
1.0.0
has higher precedence than1.0.0-rc
. - If the pre-release identifiers are the same, the numeric values that follow are compared. A higher number means higher precedence—for example:
2.1.2-beta.12
>2.1.2-beta.3
>2.1.2-beta.2
.
2. Git Tag
A Git tag is a pointer to a specific commit in a repository. It is useful for marking a particular commit among many others. For example, when a release update is made, a tag can be used to mark the exact commit where the release occurred. This makes it easier to track and reference that point in the project’s history.
Another example could be, in a mobile application development context, when the app is released to the Play Store or App Store, it includes a build version and build number. Every release increases the build version and build number. However, the repository has no information about where the build version and build number were changed. To manage and track these versions, Git tags can be used. Whenever the build version changes, there should be a new Git tag.
Full details can be found in this documentation.
For GitHub, the link
https://github.com/<username>/<repository_name>/compare/<tag_name>...<another_tag_name>
shows the commits between two tags.
2.1. Create Git Tag
There are two types of tags — annotated tag and lightweight tag. An annotated tag contains more information about the tag (like tag message, tagger name and email, creation date and time, etc.), whereas a lightweight tag does not include any information; it is basically the commit checksum stored in a file. For public releases, an annotated tag is recommended. A lightweight tag can be used for quick bookmarks and internal or temporary tags.
Tag name is a string without space
, ~
, ^
, :
,?
, *
, [
, \
characters. Avoid tag name starting with -
, using ..
, @{
, ending with .lock
. Also, a tag name should not look like branch name. The best practice is to keep the tag name as the semantic version of the project, such as v2.0.0
, 1.0.0
, 2.1.3-beta
, etc. The format vX.Y.Z
is commonly used.
The following command will create a Git tag for the commit-
# Annotated Tag
git tag -a <tag_name> -m "<tag_message>"
# Lightweight Tag
git tag <tag_name>
Besides, if there is a need to create tag against a commit from before then it can be achieve by adding the commit checksum (or part of it) like- 9fceb02
at end of the command.
git tag -a <tag_name> <commit_checksum>
Note: Duplicate tag name is not allowed.
2.2. Push Git Tags in Remote Repository
When the git push origin <branch_name>
command run, it does not push tags to the remote repository by default — it only pushes commits. By adding --tags
at the end of the command, the tags will be pushed alongside the commits. If there are no commits to push, it will only push the tags.
git push origin <branch_name> --tags
2.3. Show Git Tags
To see the list of tags in the repository, the following command needs to be run:
git tag
# or
git tag -l
# or
git tag --list
Note: This command lists the tags in alphabetical order.
To see a specific tag information, the following command needs to be run:
git show <tag_name>
An annotated tag will show more information compare to a lightweight tag.
2.4. Delete Git Tag
To delete a tag from local repository, the following command can be used:
git tag -d <tag_name>
The following command will delete the tag from the remote repository too:
git push origin --delete <tag_name>
Conclusion
In conclusion, semantic versioning makes it easier for both developers and users to understand updates by providing a clear and uniform way to describe changes to software. Version control becomes even more useful when combined with Git tags, allowing teams to keep track of releases more accurately and speed up the development process. By adopting these practices, teams can maintain better control over an application’s growth and deliver updates confidently.
Top comments (2)
Is Semantic Versioning (SemVer) useful for tracking software updates?
Yes