views:

175

answers:

4

We have the following scenario: We have several base versions of our game OpenLieroX; right now 0.57, 0.58 and 0.59. For each base version, we have a seperate branch. Each such base version has several releases (like 0.57 beta1-beta8 and rc1, 0.58 beta1-beta9).

When we are working on new stuff, we are working in the highest base version branch (right now that is 0.59). When we are fixing some reported bugs, we do that in the earliest version (mostly 0.58) where that occured. From time to time, we always merge all changes in 0.58 into 0.59 (as long as we will still maintain and do changes on the old branch).

This all works really fine until it comes to some changes which we want to have only in 0.58 but not in 0.59. This happend only for one case so far: The version number. We have some Version.cpp file (and also some other files) which contains the version number. So, when we want to push a new release for 0.58, we change the versionstring in there to "0.58 beta10" (or whatever). Now, when we do the usual merging from 0.58 into 0.59, this change will also be applied. We fix such cases at the moment by just overwriting it again with the right version number (or in cases for other bad commits, probably a revert).

This detail about such unwanted changes seems to be a bit ugly to me. Is the way we manage this in general bad/uncommon? How would be the easiest way to do this to get the same result? Cherry-picking all commits of 0.58 in 0.59 would be much more work.


There is also one further detail which probably makes it more complicated: While working on the code, I have to set already the upcoming version number. This is because we have a network engine and we may have introduced some new functionality and there are checks in the code like 'if(client->version() >= Version(X,Y,Z)) ...'. Now, when we introduce something new, usually it means at some points also such checks. (But we are trying to avoid these changes in older branches.)

Another problem is that we don't just count the version up (like 0.58.1, 0.58.2, ...) but we count like this: 0.58 beta1, 0.58 beta2, ..., 0.58 betaX, 0.58 rc1, ..., 0.58, 0.58.1, 0.58.2, ... This is because we want to mark it as experimental for the beginning (beta stage) and then as mostly stable or stable. In some rare cases, there may be serious changes (maybe network protocol) even between two different beta releases (of course, we try to avoid them, but sometimes it's not possible without).

A: 

It looks like what you're doing is the right way (for this result). But maybe you should reconsider your version system.

You could add a build version, 0.58.1, 0.58.2, etc. that way you can add as much stuff as you want, and you'll still collectively call it "0.58", even though it has a build number to it.

Upon release you could add beta, RC, whatever to it, but that isn't something the versioning system should care about.

The release "0.58.3 (beta1)" just means version 0.58.3. The beta1 part is human information and not a versioning system concern.

Tor Valamo
Well, in "0.58 beta X", the X is the build number. We could have called it also just "0.58.X". The problem is the following: In Version.cpp, we have some static const std::string VERSION = "0.58.8". Now, we do some work and then we want to make a new release, we change that std::string to "0.58.9". This commit of this version change is the problematic commit, because this single commit doesn't make sense in the 0.59 branch (whereby we just want all other commits of 0.58 into 0.59, except this one).
Albert
Maybe exclude version.cpp from the repo, and just include it on builds? Couldn't you also have version be some kind of build variable? I'm not a c/pp programmer, so I'm not too familiar with the way building works.
Tor Valamo
That would only make the whole thing more complicated. Where should I store it? I want to be able to switch a branch and just do some recompiling. Having to care about setting manually the right version by copying some Version.cpp from somewhere doesn't seem so nice.
Albert
I realized that it would be difficult. But what about the build variable option?
Tor Valamo
A: 

If I understand the problem, it sounds like you want to include a certain subset of commits from different branches. If that's the case, you may just need to run git cherry-pick and only apply the commits you want.

More information on the command in the git docs and on Git Ready.

Damien Wilson
Yea, but that wouldn't be a good solution because it would be much more work and I am searching for the easiest/best solution here. There may be 100 commits or so in 0.58 which we want in 0.59 and one single (the version string change) which we don't want.
Albert
Have you tried changing your merge strategy? Maybe a subtree merge? There's another thread on SO about git merge strategies and when a given strategies make sense.http://www.kernel.org/pub/software/scm/git/docs/howto/using-merge-subtree.htmlhttp://stackoverflow.com/questions/366860/when-would-you-use-the-different-git-merge-strategies
Damien Wilson
+4  A: 

With Extra Branches

After 0.59 diverges from 0.58, you could use a separate “release” branch for the version number changes in 0.58. Each version (except the most recent) would have its own “release” branch that only contains merges from the base branch and changes that update the external version number. The branch structure might look something like this:

                A--------o--B                  0.58-release
               /        /
...--o--o--o--o--o--o--o--o                    0.58
      \        \        \  \
       \        \        \  \            C     0.59-release
        \        \        \  \          /
         o--o--o--o--o--o--o--o--o--o--o--o    0.59
                                  \     \
                                   o--o--o     0.60
  • A labels the software “0.58 beta9”
  • B labels the software “0.58 rc1”
  • 0.58 has changes that have not yet been released
  • C labels the software “0.59 beta1”
  • 0.59 has changes that have not yet been released
  • 0.60 is not yet fully up to date with 0.59

Or, if you are very strict in only making changes at A, B, C, etc. that change the external version number (no significant code changes, those belong on the ‘base’ branches: 0.58, 0.59, etc.), then you could do without the “release” branches. Instead you could use a detached HEAD or temporary branch (deleted after the release is versioned) to make the external-version-update commit and save it in a tag.

                A        B
               /        /
...--o--o--o--o--o--o--o--o                    0.58
      \        \        \  \
       \        \        \  \            C
        \        \        \  \          /
         o--o--o--o--o--o--o--o--o--o--o--o    0.59
                                  \     \
                                   o--o--o     0.60
  • A labels the software “0.58 beta9” and is tag 0.58-beta9
  • B labels the software “0.58 rc1” and is tag 0.58-rc1
  • C labels the software “0.59 beta1” and is tag 0.59-beta1

Like Git Does It

You also might look into the way Git does its own versioning.

For builds done out of a Git working tree, the version number is generated from the output of git describe HEAD. The Makefile knows which files need to be recompiled/rebuilt if the version number changes and it always runs the GIT-VERSION-GEN script to make sure it has the most recent version number. The version number is available in the Makefile by inclusion of the generated version file. It is passed to C files by a argument to the compiler (-DGIT_VERSION=…) and it is substituted into scripts by using sed.

There are some provisions for overriding the version number “burned into” a build, but they are generally there only for builds done outside a working tree (e.g. a build done from a tree extracted from a tar file).

Mixing Version-String Changes With Other Changes

In your addendum to your question, you state that you need to adjust the version number while doing development. First, I think that the “0.58-release” branch scheme that seh and I have described can still work for you. It will just take more discipline to separate your changes. If you think about the *-release branches as “released for internal testing” and not just “released to customers (or for external testing)”, then it still makes sense. Always do your development on the base branch (e.g. “0.58”), and always merge the base branch into the release branch (e.g. “0.58-release”) before doing a build that will require a specific version number (always build from such a merged release branch).

If you insist on putting version-number changes and (non-merge) code changes in the same line of history, then is seems to me that you will have little choice but to deal with the conflict when merging (unless you go with git cherry-pick (per Damien Wilson, or an automated edit script aimed at git rebase -i).

If your “version files” only contain versioning information, you might be able to ease the conflict resolution by using .gitattributes to mark your Version.cpp file as unmergable.

.gitattributes in the dir that contains Version.cpp

/Version.cpp -merge

Marking it like this (same as merge=binary) will always cause a conflict if the file is different between merged branches. The post-merge working-tree version will default to the version from the branch you have checked out (not the one from the branch(es) you are merging), so you can just git add Version.cpp && git commit to finish the merge (assuming all other conflicts are also resolved).

Chris Johnsen
I kind of thought already to do it that way, but there is one further problem: While working on the code, I have to set already the upcoming version number. This is because we have a network engine and we may have introduced some new functionality and there are checks in the code like 'if(client->version() >= Version(X,Y,Z)) ...'. Now, when we introduce something new, usually it means at some points also such checks. Another problem is that we don't just count the version up (like 0.58.1, 0.58.2, ...) but at one point, we don't call it beta anymore but rc (like 0.58 betaX < 0.58 rc1).
Albert
I added a new section “Mixing Version-String Changes With Other Changes” with another way to think about the meaning of the *-release branches, and an additional suggestion (mark version files as unmergable). As for the change from numbered beta, to numbered rc, to numbered, I don't see what that has to do with Git. Whatever version strings you want should be fine as long as your code can make sense of them.
Chris Johnsen
Thanks for the additional information. But I think it would be very annoying to always have to switch the branch to do a simple 'make'. To always generate a conflict is also not really a solution - maybe usefull to don't accidently overlook such a change but seems mostly also annoying. But maybe I can set a merging strategy for a simple file? In that case, I could set the 'ours'-strategy to this file. That would actually solve everything.
Albert
A: 

The tricky part is isolating the version designation from the underlying code.

You can float a version-designating branch on top of the released code branch, such that you can safely merge all of that code from your released branch (0.58) to your main branch (0.59) at any point, without mingling the conflicting version designations. That version-designating branch would never be merged back down to the released branch; you will merely rebase it on top of the released branch when you want to include new code in a versioned release.

You can arrange this easily with the following entry in your .git/config file:

[branch "0.58-release"]
        remote = .
        merge = refs/heads/0.58
        rebase = true

With that, your release branch is called "0.58", and you'd make your versioned builds using the "0.58-release" branch. When it's time to make a release build, you'd issue the following commands:

git checkout 0.58-release
git pull
# Edit the version-designating file.
git commit -a -m'Updated version.'

All the changes to the version-designating files live only on the "58-release" branch, and can be moved forward safely with git rebase. Alternately, if you want each change to the version-designating file to stick with a state of the code from the "0.58" branch, you could merge the "0.58" branch into the "0.58-release" branch rather than rebasing "0.58-release" on top of "0.58".

seh
I am not sure that using rebase like that makes much sense. If you have just released 0.58-rc2, then looking backwards through history on the 0.58-release branch, we would see “labeling commits” (commits that only modify the “version-designating file”) for 0.58-rc2, 0.58-rc1, 0.58-beta9, …, 0.58-beta1, then finally the tip “content commit” for 0.58-rc2 itself. If the “labeling commits” only contain commits that stamp a particular version number into the code, why keep dragging all the old ones forward? Using merge produces the first diagram in my answer.
Chris Johnsen
Yes, Chris, I agree. I shared the idea as a repurposed technique that I use for something more appropriate -- keeping a locally-tailored version of a project's Makefile, which should always be rebased on top of the latest upstream changes, and never merged back. My answer hedges toward the end with the recommendation for merging instead of rebasing to maintain the temporal consistency. That matches your answer, which you posted shortly before I finished editing mine. I gave your answer my vote.
seh
This is also problematic with the fact that I have to set the version number of an upcoming release aready while I am working on it (see question description, I extended that part in the end).
Albert