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.
6 Answers 6
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
-
26Small 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 get76001f2-dirty
as an example. Obviously, seeing 'dirty' means somebody messed up.Mike Weller– Mike Weller2013年03月14日 13:00:51 +00:00Commented Mar 14, 2013 at 13:00 -
4How 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.void.pointer– void.pointer2015年08月20日 21:34:08 +00:00Commented Aug 20, 2015 at 21:34
-
11Another 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]'
Alexander Amelkin– Alexander Amelkin2016年06月27日 15:13:25 +00:00Commented Jun 27, 2016 at 15:13 -
1This will not work if your build artifact, e.g., RPM package, requires the build number stamped inside. This is because you will need to tag first before you can build. Automated builds are always run on the latest commit (HEAD). You cannot possibly create a tag for every commit.alvinabad– alvinabad2018年09月05日 23:44:40 +00:00Commented Sep 5, 2018 at 23:44
-
1Today the answer to this question should likely be to use gitversion, which follows the semantic versioning specification. gitversion.net, semver.orgqueeg– queeg2023年07月02日 21:04:47 +00:00Commented Jul 2, 2023 at 21:04
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:
- We needed the ability to have high-level versioning happen explicitly (i.e. x.y)
- When parallel development was happening, we needed to NEVER generate the same version number.
- We wanted to easily track down where a version came from.
- 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").
-
1btw, 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– kfmfe042012年11月16日 23:58:59 +00:00Commented Nov 16, 2012 at 23:58
-
5I 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– Jayson2013年06月12日 23:12:52 +00:00Commented Jun 12, 2013 at 23:12
-
3This should be the best answer.alvinabad– alvinabad2018年09月05日 23:46:12 +00:00Commented Sep 5, 2018 at 23:46
-
-
How do you handle branches being deleted. Doesn't the number go down then?MaTePe– MaTePe2020年09月18日 06:51:36 +00:00Commented Sep 18, 2020 at 6:51
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.
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/
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.
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
-
9-1 for ruining a good answer with completely uncalled for ad hominem attacks.Jörg W Mittag– Jörg W Mittag2012年03月29日 00:52:54 +00:00Commented Mar 29, 2012 at 0:52
-
10Who's to say it was git-boys voting you down. It could easily be people who prefer a little civility.Mark Booth– Mark Booth2012年03月29日 18:19:34 +00:00Commented Mar 29, 2012 at 18:19
-
FYI, I've just edited the answer.Keith Thompson– Keith Thompson2012年06月05日 02:38:11 +00:00Commented 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– strcat2015年01月25日 04:36:57 +00:00Commented Jan 25, 2015 at 4:36
Explore related questions
See similar questions with these tags.
git
tag after each successful build? This would have the added advantage that it makes it really clear whichgit
commits have build issues or test failures, since they would remain un-tagged.