When creating a public library or some kind of API we have to keep in mind that there will be other people using our product. This forces us to keep the project backward compatible so it doesn’t break with every release.

But at some point, we may want to release a big update that has to break some things 😳

To make the life of other developers easier we should start properly versioning our project. With good versioning, it would be possible to lock our project version and always have it compatible.

And it’s easy to do with git tags and semantic versioning.

What are git tags?

We can think of tags in git as “bookmarks” pointing to a specific commit. They kind of behave like branches (for example you can checkout to them), but we won’t delete them once we finish using them. They are often used to mark specific version releases.

Let’s imagine that we’re creating some kind of a library for other people. At some point we decide to add some new amazing changes to it 🙌 In order to do this we create a new branch and start adding new code there.

After working for some time we decide that it’s ready and we want to merge it to master. Since we won’t be working on this branch anymore we delete it from the repository.

It turns out that our changes were all good and everything runs without any errors. We would like to somehow remember this specific point and be able to run our project from this specific commit. One way of doing this is obviously remembering a commit hash, which is f855792 (but it sounds like a terrible idea, doesn’t it? 🙈)

Let’s add the first tag

A better way of doing things is to add a tag and give it some name that is easy to remember. Since it’s our first working version, let’s go with v1. It should be obvious that to add tags we will use git tag command 🤓 This command expects a tag name and a hash of a commit where the tag should be added. So we execute git tag -a v1 f855792 , provide a short description and that’s all. Our commit can now be referenced by v1 tag name.

Now if we want to go back to this commit we can do git checkout v1 (just like branches)

Let’s look at all the magic

You may be wondering what’s up with -a in the command we used to add tag 🔮 This tells git to create an “annotated tag”, which apart from name also stores a description and some metadata. This metadata contains a date and a person adding a tag. Since this information may be useful for other people, we almost exclusively use annotated tags.

That’s how annotated tag looks in git

To see the difference we can add another tag and compare the results. This time we don’t use -a option, so we execute git tag v1-test f855792 . We can use git show command to see what git knows about out tags. This new tag, called “lightweight”, doesn’t store any information apart from those already in the commit.

You don’t know who added this lightweight tag, and it even doesn’t have a description

Let’s share

What good are our tags if we only store them locally? We have to push them to the repository for others to see. We, obviously, use git push for pushing and simply provide tag name as an argument. In our case we want to execute git push origin v1 (again, just like branches).

If we have more than one tag we can push all of them at once with git push origin --tags . However, we have to be careful as this command pushes all tags automatically (remember to clean up any mess before pushing).

Let’s watch some tags

We can already create and push tags. At some point, we may want to also see what tags are already present in the repository. To do this we can simply execute git tag and see all of them. If we’re looking for specific ones there is a simple search option with -l flag. To find all tags ending with “-test” we can do git tag -l *-test .

How to tag like a pro?

At first, our v1 tag seemed like a good idea, but in the future, we will see v2 , v3 , etc. In the meantime, there will be some bugs so we will get v3-fixed or maybe v3.1 . At some point, it will be hard to keep things consistent 🤷‍

That’s actually how versioning work in real life. Source: CommitStrip

This problem can be solved with semantic versioning. That’s one of the popular standards that uses a few simple rules. Every version is formatted as MAJOR.MINOR.PATCH and each part changes according to the following rules.

We increment:

MAJOR when breaking backward compatibility, MINOR when adding a new feature which doesn’t break compatibility, PATCH when fixing a bug without breaking compatibility

The three rules explained

It may sound simple, but usually, the hardest thing when starting with semantic versioning is deciding which changes are MAJOR and which are MINOR . People tend to think “I’ve only changed one small thing so it’s definitely a minor change”, which may be misleading.

We’re should ask ourselves “If I was using my library in some other project will everything work after the update?”. If using the new version would require us to update some things in other projects than we’ve got a change that’s not backward-compatible. Such a change is usually called a breaking change. This means that it’s definitely a major change. Otherwise, go with the minor.

When creating a library, such breaking change will be removing a public method or changing its parameters. If someone was using this method their application will stop working 🔥

If you’re working on an API then removing an endpoint or changing the response format will be your typical breaking changes.

Adding new things or modifying internals doesn’t break anything and is considered a minor update.

It’s still too much hassle

Good thing that we’re living in the 21st century. There are tools that do all this stuff automatically 🤖 And they also give us changelogs and standardized commit messages.

Those commit messages sound like fun

One tool that helps with committing is commitizen. Once we install it with npm install -g commitizen our git gets superpowers. We can now use git cz which will guide us through the process of creating a good commit message.

Apart from choosing the type of change, we can also list breaking changes or reference issue that we were working on.

In the end, we get a commit message that follows another common standard

Moving on to changelogs

The second tool we want to use is Standard Version. After adding it to the project we can execute npm run release to automatically generate a changelog, bump project version, create a release commit and add git tags.

This tool is smart enough to understand commit messages generated by commitizen and figure everything out. If we only had bugfixes it will bump patch version, adding new features will give us a new minor release, and introducing breaking changes will result in a major version bump.

If you enjoyed this post, please hit the clap button below 👏👏👏

You can also follow us on Facebook, Twitter and LinkedIn.