There are two types of merging in Subversion:
- Three-point merges
- 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.