Aust
Aust

Reputation: 11602

Using git merge, how can I merge a bugfix into multiple long-term branches?

Recently at work, we've switched from using SVN to using git. We have to maintain multiple versions of our software, and we had them setup as branches before. So after using git svn clone, we ended up with 4 branches: master, 5.0, 5.1, and 5.2.

Note: We use master as our development branch. 5.0, 5.1, and 5.2 all branch from master and are used as production branches.

A few days have gone by and quite a few changes have gone into master. A bug was just found in 5.2 and we want to fix it. We've tried the the solution from Merging one change to multiple branches in Git with partial luck. We can always merge back into master easily, but whenever we merge back into 5.2 we get a bunch of conflicts. Everything we've seen that shows this process (creating a new branch for bugfix and merging it back into development and production) shows that it should be as simple as:

(master)$ git checkout -b bugfix
# fixed bug and committed

(bugfix)$ git checkout master
(master)$ git merge bugfix
# successful merge

(master)$ git checkout 5.2
(5.2)$ git merge bugfix
# successful merge

However for us when we get to that final line git merge bugfix we get tons and tons of conflicts. Conflicts for files that we haven't even touched in the bugfix. What are we doing wrong?

Note: We have also tried doing this process by starting the bugfix branch from 5.2. Then merging the bugfix into 5.2 is easy but merging the bugfix into master generates the conflicts.

Other places that show the process:

Upvotes: 13

Views: 6781

Answers (3)

Aust
Aust

Reputation: 11602

Our issue stemmed from git svn clone. Although it did give us a git repo and it seemed to give us the correct branches, those branches didn't actually stem from master as we expected. It was rather confusing because when we looked at the log, we could see that the logs prior to the branch creation had the same hashes on both branches. In fact the only thing that tipped us off that this was happening was looking at the Revision Graph from TortoiseGit. I don't have screenshots but this is basically what the graph looked like:

    5.2          master
 origin/5.2   origin/master
      `-.       .-'
         `-. .-'
        a3f2d6205

So we could see that both branches stemmed from the root commit a3f2d6205. We needed to make it so that 5.2 stemmed off of master. So here are the steps that we took that managed to get us there.

(master)$ git reset --hard 0b73ab0
(master)$ git branch 5.2.new
(master)$ git merge --no-ff --log origin/master
(master)$ git push origin master
(master)$ git checkout 5.2.new
(5.2.new)$ git merge -Xtheirs --log origin/5.2
(5.2.new)$ git push -u origin 5.2.new
(5.2.new)$ git push origin --delete 5.2

# hopped on the "central" git repo
(master)$ git branch -m 5.2.new 5.2

# run these commands for all of the developers repos
(master)$ git pull
(master)$ git remote prune origin
  • (master)$ git reset --hard 0b73ab0 - 0b73ab0 is the commit where 5.2 was branched.
  • (master)$ git branch 5.2.new - create a new branch that stems from this point
  • (master)$ git merge --no-ff --log origin/master - We need to catch master back up to where it was, but for whatever reason, if it does a fast-forward merge, it messes up the stemming so mark it --no-ff to ensure that doesn't happen.
  • (master)$ git push origin master - Push our new master to the server
  • (master)$ git checkout 5.2.new - Checkout 5.2.new
  • (5.2.new)$ git merge -Xtheirs --log origin/5.2 - We need to catch 5.2 back up to where it's supposed to be. Use -Xtheirs so we don't have to deal with conflicts.
  • (5.2.new)$ git push -u origin 5.2.new - Push this branch to the "central" git repo.
  • (5.2.new)$ git push origin --delete 5.2 - Delete the dumb 5.2 branch.

Hop onto the "central" git repo.

  • (master)$ git branch -m 5.2.new 5.2 - Rename the 5.2.new branch to be 5.2.

To ensure everyone is up to date:

  • (master)$ git pull - Pull the latest info
  • (master)$ git remote prune origin - Removes any stale branches

Finally after all is said and done, the Revision Graph should look something like:

             5.2
         origin/5.2
          .-'
       .-'
    master
origin/master
      |
      |
  a3f2d6205

Upvotes: 1

jthill
jthill

Reputation: 60255

git merge is of all changes since a common base. If you want to record a merge of just one change to multiple histories, you have to make it the only change to some ancestral content in all the histories, like so:

git checkout -b bugfix `git merge-base 5.0 5.1 5.2 master`
# so now bugfix is an ancestor of all of the above

# fix the bug here, then:
git commit -m 'bug fixed'

git checkout 5.0; git merge bugfix     # bugfix has just the one change
git checkout 5.1; git merge bugfix
git checkout 5.2; git merge bugfix
git checkout master; git merge bugfix

It might be better to go all the way back and branch bugfix off the commit that introduced the bug in the first place, but that's not always going to make for a clean merge if it's been around long enough.

Upvotes: 3

abligh
abligh

Reputation: 25129

We moved a very large project from SVN to git a few years ago. This is the question I kept coming up with.

First of all (this bit isn't an answer to your question, it's an answer to the question "why are you asking the question?"), read this: http://nvie.com/posts/a-successful-git-branching-model/ and accept for the moment that it's likely to be right, which means abandoning some of your SVN mindset. That was the hardest bit to me.

Now you've done that, I'll answer the question.

The best route is to:

  • Ensure you can always merge any older branch into any newer branch without it affecting the newer branch

  • Perform any bug-fix you need to fix on the oldest branch that you want it fixed on. Then merge that sequentially into the new branches, fixing the merge conflicts as you go.

Doing the second of these makes the first always work (if you think about it).

This technique is extensible for lots of branches of different ages, provided that the changesets in any given branch always includes all the changesets from all older branches.

However, the second bullet point above assumes an ideal world where:

  • you know exactly which is the oldest branch you need to perform the bug-fix in at the time you do the bug-fix (i.e. no one ever comes to you and says "hey, we need that bug fix backported, it's causing problems for customer X too).

  • the bug fix is always the same for each branch (e.g. you do a band-aid least-entropy fix for the older branches).

You can work around the first of these issues by using git cherry-pick to pick the commit into the older branch. But then (this is the important bit) merge the fix up to the newer branches (even though it's already there). You can do this with judicious and extremely careful use of:

git checkout newer
git merge older   # check it's all merged up
git checkout older
git cherry-pick xxxxx
... fix up cherry pick, check it works ...
git checkout newer
git merge -s ours older

Note that that marks a merge but effectively ignores everything that has changed in your older branch so it's very important to check it merged up right before your change.

The second case can be handled similarly. Apply the real fix to newer. Check older is merged into newer, then apply your bandaid to older, then use git merge -s ours older.

Upvotes: 13

Related Questions