Question

I've long heard about svn's merge-conflict troubles.

I was relieved, thought, when I learnt that svn a couple of releases ago implemented a feature called mergeinfo. It almost seemed as if its introduction would allow svn to have enough information to solve its merging issues whenever they popped up. Until I got into the following situation:

enter image description here

Scripted example of the above graph:

SVN=${SVN:-svn}
SVNADMIN=${SVNAMDIN:-svnadmin}

rm -rf repo wc

$SVNADMIN create repo

$SVN co file://$PWD/repo wc
cd wc

# r1
$SVN mkdir trunk branches
$SVN ci -m 'structure'
$SVN up

# r2
echo 2 > trunk/t.txt
$SVN add trunk/t.txt
$SVN ci -m 'add t.txt'
$SVN up

# r3
$SVN cp trunk branches/A
$SVN ci -m 'create branch A'
$SVN up

# r4
echo 4 > branches/A/a.txt
$SVN add branches/A/a.txt
$SVN ci -m 'add a.txt'
$SVN up

# r5
$SVN cp trunk branches/B
$SVN ci -m 'create branch B'
$SVN up

# r6
echo 6 > branches/B/b.txt
$SVN add branches/B/b.txt
$SVN ci -m 'add b.txt'
$SVN up

# r7
$SVN merge ^/branches/B branches/A
$SVN ci -m 'merge branch B into A'
$SVN up

# r8
echo 8 > branches/A/d.txt
$SVN add branches/A/d.txt
$SVN ci -m 'add d.txt'
$SVN up

# r9
$SVN merge ^/branches/A branches/B

If svn is keeping history of where each branch is coming from, why can't it understand that b.txt was left untouched @ branch A?

if it can't figure this out, what actual use does svn make of mergeinfo?

If svn is incapable of dealing with this issue, wouldn't it be possible to create a tool to aid me (namely auto-resolving this kind of no-brainer issues..) in this front? I'd guess maybe one would already exist?

Thanks

Was it helpful?

Solution

There are two types of merging in Subversion:

  1. Three-point merges
  2. Two-point merges (aka reintegration merging)

Let's say you're working on trunk, and you have a particular feature that needs to be done. You create a Feature A branch. As you work on the feature, you want the work you do on trunk to be included in Feature A, just so you can keep up with what everyone else does. Subversion will use a three-point merge.

Subversion will look at the difference between trunk and branch Feature A from the point that the branch occurred. The most recent common ancestor. It then considers all of the changes on Feature A that was done (which you don't want to touch) as well as the changes in the code done on trunk.

Subversion will then merge the changes on trunk without overwriting the changes made on the branch. Standard merge procedure, and Subversion does that quite well.

Where does svn:mergeinfo come in? You don't want to merge twice the same changes, so Subversion tracks the changes with the svn:mergeinfo property. If Subversion sees that the change in trunk from Revision 5 has already been merged in, it won't remerge that change. It's very good with this.

Now, you're finished with your feature, and you want those changes to be merged back into trunk. You do one last trunk to branch merge, commit those changes, and now merge from the Feature branch back into trunk.

Here's a bit of a problem. We tracked what we merged from trunk into the Feature branch via svn:mergeinfo. However, since we haven't merged from the Feature branch to trunk, there's no svn:mergeinfo there. If we attempt a normal three-point merge from the Feature branch into trunk, the trunk will assume that all of the changes in the Feature branch should be merged back into the trunk. However, many of those features are actually trunk changes that had been merged.

Truthfully, at this point, we want to do a two-point merge. We want both trunk and the Feature branch to match exactly once we do the merge. After all, we've been merging trunk into the Feature branch on a regular basis now. What we want to do is to incorporate those features back into trunk. Thus, the trunk will be the same as the feature branch.

Before Subversion 1.8, you would have to force a reintegration merge by running svn merge --reintegration. Now, Subversion will look at the merge history and figure out when a reintegration merge should be done.


Now here's the tricky part. Take a careful look at the revision numbers. These will be very, very important!

  • Revision 10: I made my final changes into Trunk, and I need to merge these into the Feature branch.
  • Revision 11: I merge trunk into the Feature branch. svn:mergeinfo will show that all of trunk from Revision 1 to Revision 10 is in the Feature branch. Since the last change on trunk is Revision 10, this makes perfect sense.
  • Revision 12: I merge Revision 11 of the Feature Branch into trunk. This is a reintegration merge. After this what is on the Feature branch and what is in trunk should agree perfectly.

Now, here's the kicker!

  • Revision 13: I make another change in trunk.

Now, I want to merge this into my Feature branch (creating Revision 14). Now, what does the svn:mergeinfo on the feature branch say? It says that trunk from Revision 1 to Revision 10 has been merged into the Feature branch. However, Revision 12 and Revision 13 of trunk have not been. Therefore, Subversion will want to merge Revision 12 and Revision 13 back into the Feature branch.

But wait a second!

Revision 12 on trunk was my merge of all changes in my Feature branch back into trunk! That is, Revision 12 already contains all of the revision changes I've made in my Feature branch. If I merge Revision 12 back into my Feature branch, I'll be saying that all of these changes in Revision 12 on trunk (which were really changes made on the feature branch and merged into trunk) need to be merged onto the feature branch. But, these changes were also made on the Feature branch. Can you say Merge Conflict? I knew you could!


There are two way to handle this:

  • The recommended way: Once you reintegration your feature branch back into trunk, delete the branch. Lock it. Never use it again. Don't touch! This isn't as bad as it sounds. After the reintegration merge, your trunk and that feature branch will match anyway. Deleting and recreating the branch from trunk will not be all that bad.
  • The tricky way which works: What we need to do is to trick Subversion into thinking that Revision #12 (or reintegration merge changes) was already merged into our Feature branch. We could futz around with the svn:mergeinfo property. And, I use to do just that. Where it says trunk:1-11, I would manually change it to trunk:1-12.

    This is tricky, but way too tricky, and risky because Subversion already gives you a way to manipulate the svn:mergeinfo without manually changing it.

It's called a record only merge.

$ svn co svn://branches/feature_a
$ cd feature_a
$ svn merge --record-only -c 12 svn://trunk
$ svn commit -m "Adding in the reintegration merge back into the feature branch."

This changes the svn:mergeinfo on the feature branch without affecting the actual content of the files. No real merge is done, but Subversion now knows that Revision 12 of trunk is already in the Feature branch. Once you do that, you can reuse the feature branch.


Now look at your diagram: When you merged Branch B into Branch A, you merged all of the changes from B into A, and svn:mergeinfo tracked that. When you merge Branch B back into Branch A, you already have all of the changes from Branch B in Branch A, and you don't want these changes to be brought back into branch B. You should have used a reintegration merge:

$ cd $branch_a_working_dir
$ svn merge $REPO/branches/B
$ svn commit -m "Rev 7: All of my changes on Branch B are now in A"
$ vi d.txt
$ svn add d.txt
$ svn commit -m"Rev 8: I added d.txt"
$ cd $branch_b_working_dir
$ svn merge --reintegrate svn://branch/A  # Note this is a REINTEGRATION merge!
$ svn commit -m"Rev 9: I've reintegrated Branch A into Branch B

Now, if we want to continue using branch A for further changes:

$ cd $branch_a_working_dir
$ svn merge -c 9 --record-only $REPO/branches/b
$ svn commit -m"I've reactivated Branch A and can make further changes"

I hope this explains a bit about how svn:mergeinfo works, why you have to know whether you're using the normal three-point merge vs. the two-point reintegration merge, and how to be able to reactivate a branch after you've done a reintegration merge.

As long as you keep this in mind, Subversion merging works pretty well.

OTHER TIPS

David W.'s answer is pretty good but I'm going to provide my own answer that answer this from a more modern approach. What David tells you is true and it's useful to understand his answer when reading my answer so you should read his first if you haven't already. My answer provides a more modern understanding of the problem and solutions to the issues David points out.

So first of all the simple answer is that the situation you present works just fine if you're using Subversion 1.8. The example script I added to the question does not have a conflict when run using Subversion 1.8. Only the client needs to be updated to gain this functionality.

The longer answer here is that with older versions of Subversion you need to know when to use the poorly named --reintegrate option (1.8 figures this out for you). Despite the name --reintegrate is not just for when you reintegrate a branch back to your trunk.

One of the recurring problems that people have is when they are using feature branches is that they want to merge the feature branch back to their trunk and then continue using the branch. As David's answer suggests in the past there were two methods for dealing with that. First of all delete the branch and then make it fresh. Second of all do record only merges. Julian Foad, one of my fellow Subversion developers, when working towards 1.8 realized that neither of these two techniques were necessary. He covered this in his presentation on merging at the Subversion Live 2012 conference. The slides for his presentation are available online here (note this is a PDF of all the slides for the conference so it's not exactly tiny), the part about this issues starts on page 123.

To summarize his presentation the typical feature branch workflow is to create a branch off trunk. Make commits to your feature branch and periodically merge from trunk to your feature branch to sync up with what's on trunk e.g. svn merge ^/trunk $BRANCH_WC. Then when you're ready to merge back to trunk you do svn merge --reintegrate ^/branches/myfeature $TRUNK_WC. Now you're to the point where traditional wisdom was you had to delete your feature branch or do a record only merge. Instead Julian found that you could simply continue using the branch if you followed these rules.

  • Every time you merge in the same direction as the last merge between the two branch you do NOT use the --reintegrate option. Treat the creation of the branch as a merge from that creating location.

  • Every time you change directions that you're merging between the two branches use --reintegrate for that merge.

In your specific case you are changing directions of which you've been merging. r7 is merging from branch B to branch A. r9 merges from branch A to branch B. So when you go to do your r9 merge you need to use --reintegrate. This logic is exactly what Subversion is doing for you with 1.8.

Finally, I don't recommend merging between sibling branches as you're doing here. A lot of simple cases like what you're doing here are going to work well. However, if you for instance split a file (svn cp file file2, remove part of file1 and a different part from file2) then you're going to run into issues when you try to merge the last of the two feature branches back to trunk (assuming you have merged the split to both sibling branches). It is best to restrict merging to be between two branches (child and parent). You can make branches off other branches and merge back to their parent before going to the parent's parent and so on and be fine. We'd like to make even these sorts of things work properly in the future, but this is the situation as things stand right now in 1.8 (far better than we were in the past but not as good as we'd like).

You misunderstood mergeinfo meaning and usage - it holds only already (for this node) merged revisions and merge-origins in order do not merge revision twice, nothing about content

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top