Where Did That Mergeinfo Come From?

One area of merge tracking that has caused confusion is how svn:mergeinfo properties are set.  In many common use cases only mergeinfo on the merge target is set.  There are however, many scenarios where mergeinfo under the target, so called "subtree mergeinfo", is created or updated.  These situations often leave users wondering if something went wrong during the merge and if they should commit these subtree mergeinfo changes.  That confusion is compounded by the fact that a given subtree may have no changes other than those made to the svn:mergeinfo property itself.

This post describes some common cases where subtree mergeinfo is created or updated during a merge.

Pre-existing Subtree Mergeinfo

By far the most common cause of subtree mergeinfo changes is when your working copy merge target has subtree mergeinfo prior to a merge.

For example, we have a branch B1.0, created from trunk, that has some subtree mergeinfo:

1.6.6>svn propget svn:mergeinfo branches/B1.0 -vR
Properties on 'branches/B1.0/A/D/H/psi':

We merge revision 12 from trunk to B1.0 and the output of the merge shows that a new file is added:

1.6.6>svn merge ^/trunk branches/B1.0 -c12
— Merging r12 into 'branches/B1.0':
A    branches/B1.0/A/C/nu

But when we check the status of the working copy we see some property changes:

1.6.6>svn status
 M      branches/B1.0
A  +    branches/B1.0/A/C/nu
 M      branches/B1.0/A/D/H/psi

Checking the diff on B1.0 alone we see that mergeinfo has been added which describes the merge we just performed, namely r12 from trunk.  That makes sense, it's what we just did!

1.6.6>svn diff –depth empty branches/B1.0

Property changes on: branches/B1.0
Added: svn:mergeinfo
   Merged /trunk:r12

But what about the property change on A/D/H/psi?

1.6.6>svn diff branches/B1.0/A/D/H/psi

Property changes on: branches/B1.0/A/D/H/psi
Modified: svn:mergeinfo
   Merged /trunk/A/D/H/psi:r12

Oh, it's a mergeinfo change.  But wait, we happen to know that r12 didn't affect psi and we check the log to confirm this:

1.6.6>svn log -v -r12
r12 | pburba | 2009-11-17 18:21:47 -0500 (Tue, 17 Nov 2009) | 1 line
Changed paths:
   A /trunk/A/C/nu.c

So why did the mergeinfo on psi change?  The short answer is that for all versions of Subversion up to 1.6.x, subtree mergeinfo is always updated to describe the merge, regardless of whether the subtree was affected by the merge.  This was done to support easier mergeinfo elision* and for consistency in the resulting mergeinfo between single merges of a range and multiple individual merges amounting to the same thing (e.g. svn merge ^/trunk branch -r 100:103 or svn merge ^/trunk branch -c101, svn merge ^/trunk branch -c102, and svn merge ^/trunk branch -c103 would both result in the same mergeinfo).

Early on, we in the development community realized this behavior was causing more confusion than was justified by the somewhat minor benefits.  Surely only subtrees affected by a given merge should have their mergeinfo updated.  There was little dissent on that point, and from a coding perspective the change was almost laughably simple.  Unfortunately making this change had some unforeseen consequences on subsequent merges.  I won't go into the details here, but one of these consequences was the potential for dramatically decreased merge performance.

The good news is that ultimately we were able to preserve (and in many cases improve) merge performance while stopping the recording of mergeinfo changes on subtrees unaffected by a merge.  The bad news is that these changes were extensive and will not be backported to the 1.5.x or 1.6.x releases but rather will be available only in 1.7.

In the meantime, if you are wondering if you should commit these subtree mergeinfo changes, you should.  If you elect to revert them before committing your merge you won't be able to use the merge –reintegrate option any more and worse, for long lived branches you will suffer a performance hit on subsequent merges that can easily reach intolerable levels.

Property Diffs

The svn:mergeinfo property is a versioned property and just like any other versioned property the difference applied by a merge may include changes to or additions of that property.

For example, we might have a branch working copy with no mergeinfo at all:

1.6.6>svn propget svn:mergeinfo branches/B3.0 -vR

We cherry pick a single revision from our trunk:

1.6.6>svn merge ^/trunk branches/B3.0 -c21
— Merging r21 into 'branches/B3.0':
UU   branches/B3.0/A/D/gamma

Notice the 'U' in the second column?  A property change was part of r21, which we can see when checking the working copy's status:

1.6.6>svn status
 M      branches/B3.0
MM      branches/B3.0/A/D/gamma

Given the topic of this blog it's no surprise that incoming property change was to the svn:mergeinfo property:

1.6.6>svn propget svn:mergeinfo branches/B3.0 -vR
Properties on 'branches/B3.0':
Properties on 'branches/B3.0/A/D/gamma':

If we check the diff of the merge source we can see that a svn:mergeinfo property was added to trunk/A/D/gamma in r21:

1.6.6>svn diff -r20:21 ^/trunk
Index: A/D/gamma
— A/D/gamma   (revision 20)
+++ A/D/gamma   (revision 21)
@@ -1 +1 @@
-the new gamma file

Property changes on: A/D/gamma
Added: svn:mergeinfo
   Merged /branches/B1.0/A/D/gamma:r20

Situations like this can happen in any number of ways, but typically involve subtree merges (i.e. merges targeting some target below the root of a branch) which are later merged as whole branch merges (i.e. merges targeting the root of the branch) to some other branch.

Missing Subtrees

The remaining cases all have one thing in common, subtree mergeinfo is recorded because parts of the merge target are "missing".  They can be missing for several different reasons, but the resulting mergeinfo is quite similar.

Shallow Working Copies

If you merge to shallow working copies (i.e. those set to a depth other than infinity) then you will see subtree mergeinfo created.

For example, say we check out a branch working copy at depth immediates:

1.6.6>svn checkout %ROOT_URL%/branches/B2.0–depth immediates
A    B2.0/A
Checked out revision 13.

In this case the branch has no mergeinfo whatsoever:

1.6.6>svn propget svn:mergeinfo -vR B2.0

Now we merge all available revisions from trunk.  Since part of the working copy is missing due to the shallow checkout we get several tree conflicts:

1.6.6>svn merge ^/trunk B2.0
— Merging r4 through r13 into 'B2.0/A':
   C B2.0/A/mu
   C B2.0/A/C
   C B2.0/A/D
Summary of conflicts:
  Tree conflicts: 3

We also get new mergeinfo added to B2.0/A:

1.6.6>svn status B2.0
 M      B2.0
 M      B2.0/A
!     C B2.0/A/mu
      >   local missing, incoming edit upon merge
!     C B2.0/A/C
      >   local delete, incoming edit upon merge
!     C B2.0/A/D
      >   local delete, incoming edit upon merge

1.6.6>svn propget svn:mergeinfo -vR B2.0
Properties on 'B2.0':
Properties on 'B2.0/A':

This happens because the svn:mergeinfo property is inheritable.  Since we don't actually have any of the children of B2.0/A in our working copy we haven't actually merged r3:13 to them from trunk.  The non-inheritable mergeinfo range on B2.0/A, "/trunk/A:4-13*", means effectively that "we have merged r3:13 from trunk to this depth, but no further".

Without this non-inheritable mergeinfo on B2.0/A, if we resolved the tree conflicts and committed this merge and then another user checked out a full depth working copy of the branch it would appear to them that r3:13 was completely merged to the branch, which is obviously not the case.

This type of subtree mergeinfo is easily avoided if you always merge into full depth working copies.  If you need to merge into shallow working copies just keep this behavior in mind and be sure to commit all the subtree mergeinfo.

Shallow Merges

Closely related to shallow working copies are shallow merges.  Here it is not the depth of the merge target that is shallow, but rather the depth of the merge itself as specified with the –depth option to the merge command (you can even do shallow merges into shallow working copies).

For example, we check out a full depth working copy of a branch:

1.6.6>svn checkout %ROOT_URL%/branches/B2.0/branches/B2.0 B2
A    B2/A
A    B2/A/B
A    B2/A/B/lambda
A    B2/A/B/E
A    B2/A/B/E/alpha
A    B2/A/B/E/beta
A    B2/A/B/F
A    B2/A/mu
A    B2/A/C
A    B2/A/D
A    B2/A/D/gamma
A    B2/A/D/G
A    B2/A/D/G/pi
A    B2/A/D/G/rho
A    B2/A/D/G/tau
A    B2/A/D/H
A    B2/A/D/H/chi
A    B2/A/D/H/omega
A    B2/A/D/H/psi
Checked out revision 13.

A shallow merge "descends" only as far as the requested depth, in this case the to the target's immediate children:

1.6.6>svn merge ^/trunk/A B2/A –depth immediates
— Merging r4 through r13 into 'B2/A/mu':
U    B2/A/mu

Like a merge into a shallow working copy, non-inheritable mergeinfo is set on the limits of what was merged to:

1.6.6>svn status B2
 M      B2
 M      B2/A/B
M       B2/A/mu
 M      B2/A/C
 M      B2/A/D

1.6.6>svn propget svn:mergeinfo -vR B2
Properties on 'B2':
Properties on 'B2/A/B':
Properties on 'B2/A/C':
Properties on 'B2/A/D':

Again, this can be avoided by doing full –depth infinity merges.

Switched Subtrees

If you have switched subtrees in your merge target these are treated almost exactly the same as if those subtrees were at depth empty.  The main difference is that the merge will record normal inheritable mergeinfo on the root of the switched subtree, since from that point downward you do have the full working copy (barring switched subtrees within switched subtrees, or shallow switched subtrees, etc).

Authorization Restrictions

Another case of "missing subtrees" is when you merge into a working copy which you don't have full read access to.  Again you will see non-inheritable mergeinfo added around the missing subtree.  I suspect this is a rare occurrence, but if you suddenly see subtree mergeinfo appearing after a merge, and none of the preceding reasons apply, this may be the cause.

* If you are unfamiliar with mergeinfo elision you can read about it here http://www.open.collab.net/community/subversion/articles/merge-info.html.

Paul Burba

Paul is a committer on the Apache Software Foundation's Subversion project and has worked on Subversion for the past nine years. He works as a software engineer for Collabnet from his home in New Hampshire and when not coding he can usually be found skiing with his nephews, mountain biking with friends, or traveling with his wife. Some time in the distant past Paul graduated from the University of New Hampshire with a degree in Business. Somewhat more recently he obtained a masters in computer science from Boston University.

Posted in Subversion
connect with CollabNet
   Contact Us
looking for something

CollabNet: Our latest version of #TeamForge provides the industry's top #SCM platform: https://t.co/qivDcYwi8a #Git #SVN
Date: 25 November 2015 | 11:00 pm

CollabNet: Our CEO @Flint_Brenton: tells us how managers and #agile can get along: https://t.co/3LZrIdoJGN @SYSCONmedia:
Date: 25 November 2015 | 8:00 pm

CollabNet: "By using #RBAC #TeamForge safeguards source #code from rogue tampering and prevents unauthorized code changes": https://t.co/6Vh2hJnRCc
Date: 25 November 2015 | 7:00 pm