147

My organization is considering moving from SVN to Git. One argument against moving is as follows:

How do we do versioning?

We have an SDK distribution based on the NetBeans Platform. As the SVN revisions are simple numbers we can use them to extend the version numbers of our plugins and SDK builds. How do we handle this when we move to Git?

Possible solutions:

  • Using the build number from Hudson (Problem: you have to check Hudson to correlate that to an actual Git version)
  • Manually upping the version for nightly and stable (Problem: Learning curve, human error)

If someone else has encountered a similar problem and solved it, we'd love to hear how.

4
  • 4
    Could you get your hudson (not jenkins?) server to automatically add a git tag after each successful build? This would have the added advantage that it makes it really clear which git commits have build issues or test failures, since they would remain un-tagged.
    – Mark Booth
    Commented Mar 29, 2012 at 18:39
  • 2
    see stackoverflow.com/questions/677436/…
    – CharlesB
    Commented Mar 30, 2012 at 21:00
  • As a side note, you can add the build count to the tag by tracking the build times.
    – Shahbaz
    Commented Jul 9, 2014 at 9:07
  • Not sure if a viable solution, but how about exporting from git to a svn repo right before every build? Then just build from the svn repo - if centralized is what we want, just use that instead.
    – Jonny
    Commented Dec 4, 2015 at 20:54

6 Answers 6

178

Use tags to mark commits with version numbers:

git tag -a v2.5 -m 'Version 2.5'

Push tags upstream—this is not done by default:

git push --tags

Then use the describe command:

git describe --tags --long

This gives you a string of the format:

v2.5-0-gdeadbee
^    ^ ^^
|    | ||
|    | |'-- SHA of HEAD (first seven chars)
|    | '-- "g" is for git
|    '---- number of commits since last tag
|
'--------- last tag
12
  • Agree - it should be easy to automate nightly tag numbering if you need that, and promotion to stable is manual anyway.
    – Useless
    Commented Mar 28, 2012 at 19:00
  • 26
    Small improvement: git describe --long --tags --dirty --always. 'Dirty' will tell you if there were local changes when the 'describe' was done (meaning it can't fully describe the state of the repo). 'Always' means you won't get an error when there are no tags. It will fallback to just a commit hash. So you can get 76001f2-dirty as an example. Obviously, seeing 'dirty' means somebody messed up. Commented Mar 14, 2013 at 13:00
  • 3
    How can this work when the tag is generated last. Normally you want builds going forward to have the next version of your product. But they will always be forced to use the last version in this case. Only the final, shipped build will have the proper number. Commented Aug 20, 2015 at 21:34
  • @void.pointer: Sure, this version number answers the question “which release was this commit based on?” not “which release will this commit be in?” However, you’re free to interpret tags differently. For example, if you tag HEAD as v2.5, you can just as well interpret that as the start of the 2.5 release cycle, then tag v2.5-release or whatever you like.
    – Jon Purdy
    Commented Aug 20, 2015 at 22:44
  • 11
    Another small improvement. If you want to have other tags as well, but use a specifically patterned tag for revision generation, you may use the --match option like this: git describe --long --tags --dirty --always --match 'v[0-9]\.[0-9]' Commented Jun 27, 2016 at 15:13
56

This has come up on a few projects for me. The best solution I've had so far is to generate a version number like this:

x.y.<number of commits>.r<git-hash>

Typically, it's generated by our build system using a combination of some static file or tag to get the major revision numbers, git rev-list HEAD | wc -l (which was faster than using git log), and git rev-parse HEAD. The reasoning was follows:

  1. We needed the ability to have high-level versioning happen explicitly (i.e. x.y)
  2. When parallel development was happening, we needed to NEVER generate the same version number.
  3. We wanted to easily track down where a version came from.
  4. When parallel lines were merged, we wanted the new version to resolve higher than either of the branches.

Number 2 is invisible to most people, but is really important, and really difficult with distributed source control. SVN helps with this by giving you a single revision number. It turns out that a commit count is as close as you can get, while magically solving #4 as well. In the presence of branches, this is still not unique, in which case we add the hash, which neatly solves #3 as well.

Most of this was to accommodate deploying via Python's pip. This guaranteed that pip install would maybe be a bit odd during parallel development (i.e. packages from people on different branches would intermingle, but in a deterministic fashion), but that after merges, everything sorted out. Barring the presence of an exposed rebase or amend, this worked quite nicely for the above requirements.

In case you're wondering, we chose to put the r in front of the hash due to some weirdness with how Python packaging handles letters in version numbers (i.e. a-e are less than 0, which would make "1.3.10.a1234" < "1.3.10" < "1.3.10.1234").

5
  • 1
    btw, how did you deal with the chicken-egg problem of determining the git-hash before you check it in? Did you use some form of .gitignore or some other trick?
    – kfmfe04
    Commented Nov 16, 2012 at 23:58
  • 5
    I didn't. I don't use the hash until package build time, which is long after check-in. Different languages have different ways to inject this. For Python, I use './setup.py egg_info -b ".${BUILD_VERSION}" sdist'. For C and C++, I define a macro at compile time with 'CFLAGS=-D "${BUILD_VERSION}"'. For Go, I define a symbol at link time with 'go install -ldflags appmodule.BuildVersion"-X .${BUILD_VERSION}"'.
    – Jayson
    Commented Jun 12, 2013 at 23:12
  • 3
    This should be the best answer.
    – alvinabad
    Commented Sep 5, 2018 at 23:46
  • very good answer
    – haelix
    Commented Sep 18, 2018 at 15:00
  • How do you handle branches being deleted. Doesn't the number go down then?
    – MaTePe
    Commented Sep 18, 2020 at 6:51
11

This might be a bit overkill, but I'll let you know how we do it.

We use a branching structure very similar to this.

Hudson builds off our "develop" branches and increments build numbers starting from 0. The build number is unique to each project and gets tagged in version control. The reason is so that you can tell exactly which develop branch build 42 came from, for example (each project can have several develop branches in parallel, because each project can have several teams working on different aspects of the project).

When we decide that a particular build is good enough to be released, the commit that triggered that build gets tagged with a release version number, which is decided by marketing. This means that the dev teams don't care about what the final version number is and marketing is free to shuffle around version numbers as it sees fit. The final version number and build number are both present in the released product.

Example: 2.1.0 build 1337

This means, for a specific product release, you can tell which was the last team to have worked on it and you can query git for all the commits leading up to release to diagnose a problem if you need to.

10

Versions are identified hashing the SHA1 hashes of all the files in the stored directory tree at the time of checkin. This hash is stored alongside the hashes of the parent checkin(s) so that the full history can be read.

Take a look at the process of using 'git-describe' by way of GIT-VERSION-GEN and how you can add this via your build process when you tag your release.

Here is a nice blog that gives an example of how to get what you want:

http://cd34.com/blog/programming/using-git-to-generate-an-automatic-version-number/

0

Jon Purdy has the right idea. git flow makes the actual management of these branches easy, as well, and branch management is an argument for moving to git.

Let's start with a basic rundown of git, since you're coming from the svn-to-git perspective. Consider in git the following:

master--...............-.....-..............-
        \             /     /              /
         ---develop---------............../
                            \            /
                             --feature---

Above, you branch master to develop (denoted by the \), and branch develop to a feature branch. We merge those branches back up (denoted by /), with commits (-) along a branch. (If there's no commit but the merge is way to the right, there are . indicators to show that the next - is the next commit).

Easy enough. What if we have a hotfix in our main release?

master--...............-.....-................-...........-.........-
        \             /     /                / \         /|        /
         \           /     /                /   -hotfix-- V       /
          ---develop---------............../..............-...----
                             \            / \             V   /
                              --feature---   --feature2...----

Above, develop branched from master. The bug discovered in master was fixed by branching from master, fixing it, and merging back into master. We then merged master into develop, and then develop into feature2, which rolled the new code from hotfix into these branches.

When you merge feature2 back to develop, its history includes develop with the hotfix. Likewise, develop is merged into feature2 with the new code from master, so merging develop back to master will happen without a hitch, as it's based on that commit in master at that time—as if you had branched from master at that point.

So here's another way to do that.

master--..........-........-
        \        /\       /
         ---1.0--  --1.1-- 

Your 1.0 releases get tagged—1.0.1, 1.0.2, 1.0.3, and so forth.

Now here's a trick: you found a bug in 1.0 and it affects 1.1, 1.2, and 1.3. What do you do?

You branch off your latest or earliest maintained release and fix it. Then you merge your new hotfix branch into 1.3—and into 1.2, 1.1, and 1.0. Don't branch from each of the maintenance version branches; don't merge 1.0 into master or merge master back into 1.0. Take the one hotfix branch and merge it into all your version branches. If there are conflicts, it will tell you; review your code to ensure the changes are correct (git diff is your friend).

Now that specific change is applied everywhere. The lineage is branched, but it's okay. It's not haphazard. Tag the 1.3 head as 1.3.17, merge it into every feature-in-progress branched from 1.3, and move on.

The git flow extension helps manage these maintenance, feature, and hotfix branches for you. Once you get the workflow down, this is trivial and takes a huge amount of trouble out of source code management.

I've seen this done on programming teams, but I've not worked that deeply as a programmer myself, so I'm still getting my head around the day-to-day workflow myself.

-3

Pro Git in section 7.2 "Git Attributes" in "Keyword" Expansion part contains a nice example of using smudge&clean filters for generating RCS-style keywords. You can use the same technique for embedding some-version-string into code, formatted and calculated according to your rules. You still can use git describe as a staring point, but you have the possibility to transform to any more appropriate form and get from v2.5-14-feebdaed, for example, clean 2.5.14

4
  • 9
    -1 for ruining a good answer with completely uncalled for ad hominem attacks. Commented Mar 29, 2012 at 0:52
  • 9
    Who's to say it was git-boys voting you down. It could easily be people who prefer a little civility.
    – Mark Booth
    Commented Mar 29, 2012 at 18:19
  • FYI, I've just edited the answer. Commented Jun 5, 2012 at 2:38
  • git describe outputs the tag name unless --long is passed or there are commits since the last tag, so it's perfectly clean already. If you weren't changing the defaults, it would have given you exactly what you wanted.
    – strcat
    Commented Jan 25, 2015 at 4:36

Not the answer you're looking for? Browse other questions tagged or ask your own question.