Like most things in life, our current software project is powered by Git, and historically we’ve been using git to generate the version numbers for our builds by sucking it out from the tags in our repository via git-describe.
$ git describe --tags --long --match v*
v0.3.0-0-g865eb5f
This works wonderfully well when you have a single-line commit history, as the tag for versioning is the most recent tag.
Recently, however, we switched to using Vincent Driessen’s Git branching model, which opens up a serious hole in the simple versioning system. When you prepare a hotfix release for master, you tag the hotfix branch at the point the hotfix is being applied. This has the unfortunate side-effect of screwing up the way git describe determines the “nearest” tag.
I’ve created a sample repo demonstrating this problem. If you’re not keen on grabbing the repo, follow along with the screenshot below.
Image may be NSFW.
Clik here to view.
Basically what we see here is a hotfix being applied for release v0.1.0 while development continues on v0.2.0. The hotfix is merged back into develop (but decided not to be merged into master as the hotfix could wait until the next release).
Running the git-describe command above *SHOULD* yield v0.2.0-4-gba68c2f as the tag for develop in order to be true, however it comes back as v0.1.1-4-gba68c2f, which leads to our builds being completely mis-versioned. (We just versioned 0.2.0 code as 0.1.1 – how shit are we?)
Okay, so why is git picking up my v0.1.1 tag instead of the v0.2.0 tag? Turns out it has a lot to do with the explanation of how git-describe works:
If multiple tags were found during the walk then the tag which has the fewest commits different from the input committish will be selected and output. Here fewest commits different is defined as the number of commits which would be shown by git log tag..input will be the smallest number of commits possible.
Which is all well and good, however the describe algorithm ended up traversing a merge-branch down from develop and erroneously (for our purposes) finding v0.1.1 because it was closer to HEAD than v0.2.0 (well specifically in this example case they have the same number of commits, but the depth of the merged branch seems to be more appealing to git)
Digging around a bit more, I found that the git log command actually has an argument to have git-log search only the first (oldest) of multiple parent commits (ie: merges). enter –first-parent
Follow only the first parent commit upon seeing a merge commit. This option can give a better overview when viewing the evolution of a particular topic branch, because merges into a topic branch tend to be only about adjusting to updated upstream from time to time, and this option allows you to ignore the individual commits brought in to your history by such a merge.
…and when you use that to find the appropriate tag, it does!
$ git log --oneline --decorate=short --first-parent | grep '(tag:' | head -n1
b4aa13c (tag: v0.2.0) continued work on develop
So first-parent history search behaviour is what I want, but it’s not available on git-describe. Turns out, i’m not the only one who’s come across this…It’s a shame, really because describe does everything else perfectly, except for the algorithm to find the closest tag.
Unfortunately there’s no clear work-around or even a solution as to when a –first-parent argument will be made available for git-describe, which meant I had to come up with this monstrous flake of rake script to get the build version identifier (formatting doesn’t do it justice):
def git_version_identifier tag_number = `git log --oneline --decorate=short --first-parent | grep '(tag:' | head -n1` version_number = /v(d+).(d+).(d+)/.match(tag_number) `git describe --tags --long --match #{version_number}`.chomp end
which (in a nutshell):
- Finds the appropriate tag number for the current branch as per the bash-fu you saw earlier
- Parses the previous output for the tag identifier (vx.x.x as per our convention)
- Fires THAT tag id into git describe to get it to generate the identifier properly, bypassing its search mechanism
Seems convoluted and i’m not really happy with the result. Hoping that someone out there knows something I dont.