24

A product version, such as v1.0.0.100, represents not only a unique production release of software, but helps identify feature sets and hotfix stages for said product. Right now I see two ways to maintain the final package/build/binary version of a product:

  1. Version Control. A file somewhere stores the version number. Continuous Integration (CI) build server will have a script to build the software that uses this checked-in version number to apply it to all areas of the software needed (binaries, installer packages, help pages, documentation, etc).

  2. Environment and/or build parameters. These are maintained outside of version control (i.e. they are not tied to the snapshot/tag/branch). The build scripts distribute and use the number in the same way, however they just obtain the value differently (it is provided to the build script, instead of having the script know where to get it relative to the source tree).

The problem with the first approach is that it can complicate merges across mainline branches. If you still maintain 2 parallel releases of the same software, you will resolve conflicts when merging between the two mainlines if the version has changed on both since the last merge.

The problem with the second approach is reconciliation. When you go back to a release 1 year ago, you will rely solely on the tag information to identify its release number.

In both cases, there might be certain aspects of the version number that are not known prior to the CI build. For example, a CI build may programmatically put in a 4th component that is really the automated build number (e.g. 140th build on the branch). It might also be a revision number in VCS.

What is the best way to keep up with a software's version number? Should the "known" parts always be maintained in VCS? And if so, are the conflicts across mainline branches an issue?

Right now we maintain our version number via parameters specified and maintained in the CI build plan (Atlassian Bamboo). We have to be careful before merging to our master branch that the version numbers are properly setup in advance of the CI build kicking off. With regards to the Gitflow workflow, I feel that if the version number were tracked in source control, we could guarantee it is setup properly when we create our release branch in preparation of the release. QA would perform final integration/smoke/regression testing on this branch and upon signoff, a merge to master takes place which signals commitment to release.

gnat
  • 21,442
  • 29
  • 112
  • 288
void.pointer
  • 4,983
  • 8
  • 30
  • 40
  • 3
    Is a merge conflict between two versions of a file `version.txt` where one version contains the single line `1.0.7` and the other `1.2.0` really that hard to resolve? If this is the only conflict in merging two branches that went apart, I'd consider myself very lucky. How often does it occur? If it does occur, isn't it a good thing that you are forced to *think* about what version number the merged version should have? (Sorry for the ambiguous use of the word “version”.) – 5gon12eder Aug 17 '15 at 22:29
  • 1
    @5gon12eder The difficulties or feelings about the merge itself is irrelevant. It's just a negative aspect of the overall solution. – void.pointer Aug 18 '15 at 01:34

2 Answers2

32

Personally, I choose option 3: keep versioning information in VCS metadata, specifically, tags.

Git makes it very easy to do so, because there is a command git describe, which can uniquely describe a commit based on a tag. Here's how it works:

  • If the current commit is tagged, output the name of the tag.
  • Otherwise, walk the history backwards until you find a tag and then output a description in the following format: <tag>-<number of commits since the tag>-g<abbreviated commit hash>.
  • If there are uncommitted changes in the workingtree, append -dirty.

So, if you are doing a release build, and have the commit tagged 1.2.3, it will output 1.2.3. If you are currently working on 1.2.4 and you made 4 commits since 1.2.3, and have uncommitted changes in the tree, it will output 1.2.3-4-gdeadbee-dirty.

This is guaranteed to be unique and monotonic, as well as human-readable, and thus can be used directly as a version string. The only thing you have to ensure is a proper naming convention for tags.

Jörg W Mittag
  • 101,921
  • 24
  • 218
  • 318
  • I really love this idea, but this seems difficult to manage with JIRA + Bamboo. Bamboo only functions on branches, not tags, so you'd have to make sure a tag is pushed before a build is generated. This is error prone. – void.pointer Aug 18 '15 at 01:43
  • 1
    `git describe` also works with branches: "**--all** – Instead of using only the annotated tags, use any ref found in `refs/` namespace. This option enables matching any known branch, remote-tracking branch, or lightweight tag." Not sure how this works with Bamboo, though. (This will of course require careful naming of branches, just like the normal mode does with tags.) – Jörg W Mittag Aug 18 '15 at 01:58
  • 1
    I know some folks which do completely automatic releases from Git. The version string is built by `git describe`, the ChangeLog is generated from `git shortlog` (well, actually from a script which parses the output of `git log --pretty=tformat:`), and the release notes are generated from the tag description and `git notes` attached to important milestone commits. – Jörg W Mittag Aug 18 '15 at 02:05
  • My point is, that the tag would need to be created *in advance* of the release so that commits after it are properly versioned with a base number. This goes against the principle of tagging *during* or *at the time of* release. Bamboo picks up builds automatically based on commits to `master` (from `develop`, remember I'm using gitflow). What if someone pushes a merge to `master` without a tag? It won't use the proper version (in fact it would use the version of the *last* release) – void.pointer Aug 18 '15 at 02:07
  • If you use a scheme like this, tagging *is* releasing. Ah, I see what you're getting at, I think. So, currently, your CI server is the release driver, and with this change, the SCM is the release driver, but you would like it to remain that way? – Jörg W Mittag Aug 18 '15 at 02:13
  • Hmm, I'm not sure what you mean by "you would like it to remain that way". However, you're right that the CI build server controls pretty much everything, especially concerning releases. I guess the real question is: what about our CI server process has to change to support the new SCM-driven release model? It certainly seems as if automated, per-commit builds won't work on the master branch. However, I can't customize the build rules on a per-branch basis without another plan. We're probably getting too bamboo-specific at this point, and risk going off topic. – void.pointer Aug 18 '15 at 03:59
  • I find myself unable to accept this answer, as nice as it sounds, because it has a fundamental flaw: The tag must exist before the build. This does not mesh well with CI build systems, because the builds usually occur immediately on push. And you won't accept something as a release until CI builds and tests are a pass. The flow goes like this: `CI Build -> Tests -> Release (Tag)` whereas this flow requires: `Release (Tag) -> CI Build -> Tests` If the build or tests fail, I have a bad tag. – void.pointer Aug 20 '15 at 21:28
  • Also between releases, builds will be tied to previously released version instead of the next version (because for weeks, `git describe` will pick up the tag of the previous release, future builds for the next release will use this number, which is misleading) – void.pointer Aug 20 '15 at 21:30
  • I'm surprised this idea gets so many upvotes (probably because it *sounds* good) while the issues mentioned in my previous comments are not addressed. – void.pointer Sep 02 '15 at 14:14
  • I hate to leave yet another comment here, but can anyone address my questions above? – void.pointer Oct 01 '15 at 13:09
  • I don't understand how this is supposed to work. "If the current commit is tagged" <- when building, there is no such thing as a "current commit" or "commit". There's just the code. The code does not include its own VCS meta-data. If you mean you use an out-of-repository build mechanism which is VCS-aware and checks for tags, that's basically option #2 in the OP. – einpoklum Aug 07 '22 at 12:59
8

Yes. It is good practice to keep most of the version number in vcs. If we consider semantic versioning semver.org where we have major.minor.patch.build the first three must live in vcs. The last one can be a incrementing number from your build server used to backtrack the specific commit that a binary is made from.

To facilitate this in .NET we have made a small cmd line exe that is committed to git. With a pre-build event, it picks up the build number which teamcity tagged during build. This cmd line tool auto generates a class with one constant containing the build number. The rest of the version number: major.minor.patch is just a normal constant in another file. These two shared files are included in every assembly in a solution as a link(alt+shift-drag).

This approach is powerfull enough that we can have teamcity build and test our code. Push to azure and have kudu build it again but with the teamcity build number as the version of the dll's.

Esben Skov Pedersen
  • 5,098
  • 2
  • 21
  • 24
  • 1. Why is it good practice? You have described what you guiys do, but not why it's better to do things this way. 2. You seem to be contradicting yourself. If the version number contains a build index / build number indicator, then you're not keeping the unique version number in the VCS, only part of it. – einpoklum Aug 07 '22 at 13:06