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:
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
There are two types of merging in Subversion:
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!
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.Now, here's the kicker!
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:
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
.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.