Rickard Lindberg | Blog | Projects
All Posts | 2026 | 2026-01 | 2026-01-11 | All Bibs | All Tags | #agile

How to assign version numbers to software?

Written by Rickard Lindberg .

My thoughts on software development are highly influenced by agile ideas. When I think about how software evolves, I think about small incremental changes that are applied to a code repository. Each change goes through a continuous integration pipeline, and at every stage of that pipeline we gain confidence that the change is good through various automated tests. If the change makes it all the way to the end of the pipeline, we can choose to deploy that version to production or release it to users.

           Build   Test   Deploy Test
           -----   ----   -----------
version 1:  ok   >  ok  >    fail
version 2:  ok   >  ok  >     ok      > Deploy Pro?
version 3:  ok   >  ok  >     ok      > Deploy Pro?

But what version number do we give it? And how is that version number used?

When I think about a version number of a piece of software, I think about dot separated numbers, something like 1.2.0. The version number might also include parts to indicate that it is not an official release, such as 1.2.0-beta.3. These version numbers are separate from the commit identifiers in the version control system. They are more human friendly, and usually carry some meaning that is useful to the user of the software.

What do I expect from these version numbers?

I expect that when I run the application it can output the version number. Maybe in an about dialog. Maybe through the command line like this <program> --version. That means that we have to pick the version number at the very beginning of the continuous integration pipeline so that we can bake it into the application.

Furthermore, I expect a higher version number to be released later in time. That is one way they are more human friendly compared to version control commit identifiers. I also expect bigger changes to the software if the numbers to the left are incremented. For example, I expect there to be bigger changes between 1.2.0 and 1.3.0 than between 1.2.0 and 1.2.1. But every piece of software can use their own scheme here.

For an official release (a version number without any beta indicators or such), I also expect to find a changelog so that I can see what has changed. I expect the changelog to be more carefully written than just a list of commit messages. I expect the changelog to include only changes that are relevant for the users of the software. In an agile way of working, refactoring is a common task. That means to improve the code without changing its external behavior. The users of the software should never be able to observe that a refactoring has taken place. In case they do, for example if the refactoring introduced a bug, it was not a refactoring. In any case, those types of changes are not relevant to mention in a changelog.

Two types of software

When I think about what version number to assign to a piece of software, I find it useful to distinguish between two types:

  • Software that is used internally and provided as a service

  • Software that is released for others to install

If our software is used internally and provided as a service, it might make sense to deploy every version that passes the continuous integration pipeline to production. We want feedback on our changes as soon as possible. And in an internal service setting, that is possible.

However if we build a binary installer for example, and users need to install it, we impose some kind of work on them every time we produce a new version. So now we have to think about if that is worth it or not. As a user of a piece of software, you probably don't like spending all your time upgrading. You probably favour more stability. And perhaps that the bugs that affect you gets fixed. And that the features that you lack get implemented. Otherwise, what is the point of upgrading. It can only get worse.

Even in the case of software provided as a service, where you don't impose work on the users to install new versions, you might still upset them if you change how the software works in a way which is incompatible with how they work. Or annoy them with new graphical changes.

Version number schemes

How to assign version numbers to these two categories of software?

For software that is used internally and provided as a service, the version numbers can probably just be a stream of incrementing numbers like 1, 2, 3 and so on. Since there is no one installing the software, there is no need to assign a specific meaning to the version number other than an identifier that we can talk about internally. And talking about it is easier if it is incrementing, human readable and not some sha1 hash. Here we can also use a scheme based on dates. For example 202601.16 might mean the 16th version produced in January 2026. This will make the version number more human readable since a single incrementing identifier can get quite larger after a while. The date component might perhaps also be useful in some situations.

For software that is released for others to install, we need to think a little harder about the version number that we give it. First of all, let's tackle the case of when to release. We've already established that it doesn't make sense to release every single change. But the continuous integration pipeline should still build every change (and produce binaries for it that can be installed). To distinguish them, I suggest that intermediate builds are assigned an identifier such as beta or pre-release. For example 1.2.0-beta.3 might be the 3rd beta version of the upcoming 1.2.0 release. When we choose to release it, we strip the beta suffix and we have the official 1.2.0 release.

We also have to think about what meaning we assign to the components in the version number. The version number that we assign communicates something to the users of the software.

How to set a version in the build?

When the continuous integration pipeline receives a new change to build, it must choose a version number. That version number should also be written to a file and included in the build so that the application can display it.

I think that ideally, the version number should be determined by only looking at the given change and not include some external data like a build system id or current date and time.

Let's see how we can do that for a number of versioning schemes. We will assume that Git is used, but the ideas should be transferable to other version control systems.

Incrementing numbers

Say we have a major version and then we want to increment numbers from there:

  • 1.1

  • 1.2

  • 1.4

In this case we put the major version 1 in the repository. We hard code it in the build script for example.

For the incrementing part, we could count the number of commits like this: git rev-list HEAD --count. That will give us an incrementing number. But it will increment by the number of commits. So unless you push one commit at a time, there will be gaps in the version number.

Time based

The previous scheme will eventually render very high incrementing numbers. To prevent that, and make the version numbers more human friendly, we can designate a second component to be <year><month> and have the third component be incremented. But the third component should only count the number of commits done in that year and month. For example:

  • 1.202512.13

  • 1.202512.14

  • 1.202601.1

Here we still hard code the 1 in the build script. For the date part, we can extract the date from the commit like this:

$ TZ=UTC0 git log -1 --pretty=format:%cd --date=format-local:%Y%m
202601

For the incrementing part, we can count how many commits are done in January 2026 something like this:

$ TZ=UTC0 git log --pretty=format:%cd --date=format-local:%Y%m | grep '^202601$' | wc -l
23

This is a little fragile though because it depends on committer dates being set correctly. So it doesn't guarantee that a later commit gets a higher version number.

Release based

In this scenario, the version number of the release is fully encoded in the repo somehow. Say we are working on version 1.3.0. For pre releases, or betas, or whatever we want to call them, we want to generate version numbers like this:

  • 1.3.0-beta.1

  • 1.3.0-beta.2

For the incrementing part, we can calculate the number of commits since the previous release. Something like this:

$ git rev-list HEAD ^1.2.0 --count
1

And when we choose to make a release, we want to generate just 1.3.0.

So we also have to encode in the repo if we should make a release or not. That means that in order to make a release, we have to make a commit to flip this switch. Perhaps that commit also includes an update to the changelog with the date when the release was made.

What about branches?

The previous schemes do not work for branch builds. Two branches that are the same number of commits ahead of the main branch will get the same version number.

Since I am influenced by agile ideas, I don't think that you should do branch builds. I think you should practice continuous integration and trunk based development. And do proper CI.

With that said, the above schemes can work for branches as well if you append the commit identifier (like the sha1 hash for Git). For example:

  • 1.3.0-beta.3+<sha1>

  • 1.3.0-branch.3+<sha1>

A tool

When I started thinking about how to version my own projects. I created the tool changelog2version. It extracts the current version from the changelog in README.md as well as the last released version. Then it generates version numbers based on the scheme

<version>[-beta.<commits-since-last-release>+<sha1>]

Check it our for more information.

Summary

  • The build system should generate a version number

  • The version number should only be based on the current commit

  • If your software need releases (need the ability to not push all changes to production) then use a beta/pre-release scheme

What is Rickard working on and thinking about right now?

Every month I write a newsletter about just that. You will get updates about my current projects and thoughts about programming, and also get a chance to hit reply and interact with me. Subscribe to it below.

Powered by Buttondown (My Newsletter)