Subversion 1.5 Mergeinfo - Understanding the Internals

This article is mirrored with permission from the original location Inactive links have been removed or updated.

Author: Paul Burba

Summary: Understanding the internals of Subversion's merge tracking feature.

Posted: 2008-05-06

This article assumes basic familiarity with the topics covered in chapter 4 of the Subversion book and/or some experience with Subversion 1.5; either the early adopter 1.5 binaries available on or one of the 1.5.0 release candidates.  The examples in this article were done with release candidate 4.  As of May 6, 2008 the most current RC is Subversion 1.5.0 Release Candidate 5.


Mergeinfo, or more specifically the versioned property svn:mergeinfo, is the history of merges made into a given file or directory.  This article gives a detailed look at mergeinfo; what it means and how it works.

The svn:mergeinfo property value is simply a newline separated list of merge source paths (relative to the repository root), each source path is followed by a colon, and after the colon a list of revisions previously merged from that source.  If you are curious about the exact grammar, it is specified right in the Subversion code, see the comment 'Overview of the @c SVN_PROP_MERGEINFO property' in svn_mergeinfo.h.

Note: This is a long article and covers a lot of atypical use cases.  So if you follow the 'synch and reintegrate' paradigm described here in the Subversion Book, then much of this article is probably overkill.  In that case you might want to read only the first few sections through "Explicit Mergeinfo Inheritance" and then skip ahead to the "Parting Thoughts" section.  

Explicit Mergeinfo

When a path has the svn:mergeinfo property set on it, that path is said to have explicit mergeinfo.  Explicit mergeinfo is typically created (or modified if it already exists) on the working copy target of a merge (we'll refer to this simply as the merge target from here on).  I say "usually" because there cases where a merge does not set or modify mergeinfo, see the section "Where's My Mergeinfo?".

Explicit mergeinfo may also be created or modified on subtrees of the merge target.  Usually these subtrees have explicit mergeinfo prior to the merge because they were once merge targets themselves.  Though in some cases mergeinfo is created on subtrees where prior to the merge there was no explicit mergeinfo, see the section "Mergeinfo Inheritance and Non-Inheritable Ranges".

Let's look at some explicit mergeinfo in the wild, in this case a working copy for Subversion's own 1.5.x branch.  This branch is a copy of trunk that we split off in preparation of releasing 1.5.  Almost immediately after its creation we start selectively merging changes (i.e. cherry picking) from trunk to the branch:

>svn info \svn\src-1.5.x
Path: \SVN\src-1.5.x
Repository Root:
Repository UUID: 612f8ebc-c883-4be0-9ee0-a4e9ef946e3a
Revision: 30056
Node Kind: directory
Schedule: normal
Last Changed Author: hwright
Last Changed Rev: 30056
Last Changed Date: 2008-03-26 00:32:28 -0400 (Wed, 26 Mar 2008)

>svn pg svn:mergeinfo \svn\src-1.5.x –recursive
\SVN\src-1.5.x - /trunk:29085-29089,29091,29094-29107,29111,29114,29117,29126-29127, 29129-29133,29135-29150,29153-29164,29166,29174,29176-29186,29188-29189, 29193-29194,29198-29200,29202-29206,29208-29251,29254-29256,29261, 29267-29273,29277,29280-29281,29284,29287-29303,29305-29307,29309-29325, 29327-29343,29345-29348,29358-29379,29381-29392,29397,29399,29401,29409, 29412,29414-29415,29417-29423,29425-29426,29429,29433-29434,29436-29447, 29449-29466,29468-29478,29482,29484,29486-29487,29489,29491,29493,29496, 29498,29508,29527-29528,29531,29533,29539-29540,29542,29544,29546,29551, 29553,29556,29559,29565,29567-29569,29571-29578,29581,29583,29591,29594, 29600,29603,29607,29611,29613-29614,29619,29623,29625-29626,29630-29631, 29634,29642,29648,29650,29656,29659-29660,29663-29664,29671-29672, 29677-29680,29692,29738-29739,29742-29744,29746,29751,29763,29769-29770, 29784,29787,29797,29801,29821,29824,29828,29835,29855,29868-29869,29878, 29883-29884,29895,29898,29900,29914,29920,29925,29930,29940,29950,29958, 29962,29968,29980,29994-29997,30004,30020

Note: You'll notice a plague of paths with backslashes in these examples.  Don't try to adjust your monitor or make an appointment with your optometrist...Yes, I do my Subversion development on Windows.

In the previous example there is only one merge source path, /trunk, followed by the list of revisions previously merged from it to the 1.5.x branch.  Note that unlike the familiar -rX:Y notation used by many Subversion subcommands, the version ranges in svn:mergeinfo use an A-B format in which the revision A is inclusive.  In other words, if you merge -r3:7 expect to see mergeinfo with range 4-7.

Inoperative Vs. Operative Revisions In Mergeinfo

It is important to note that not all revisions listed in the svn:mergeinfo property for a given source need be operative on that source.  Looking at the previous example the mergeinfo for the 1.5.x branch shows that r29093:29107 was merged in from trunk.  But if we look at the log for r29095 we see that it is not a change that affects trunk:

>svn log -v -r29095

So why is r29095 included in the mergeinfo for 1.5.x?  Because at some point someone merged a range of revisions -rX:Y from trunk to 1.5.x in which X < 29095 <= Y. The development community decided to record mergeinfo for inoperative revisions in cases like this so that the mergeinfo property is less fragmented and therefore easier on human eyes.  Looking at the mergeinfo on 1.5.x you may wonder "What's the point?  It is already quite fragmented!".  And you're right, as 1.5.x is a release branch, but for other use cases, say a feature branch that is eventually reintegrated to trunk, this behavior can keep the mergeinfo nice and tidy, often just a single range. 

Where's My Mergeinfo?

There are a few cases where a merge won't create or modify mergeinfo:

>svn co %url% wc
A    wc\A
A    wc\A\B
A    wc\A\B\lambda
A    wc\A\B\E
A    wc\A\B\E\alpha
A    wc\A\B\E\beta
A    wc\A\B\F
A    wc\A\mu
A    wc\A\C
A    wc\A\D
A    wc\A\D\gamma
A    wc\A\D\G
A    wc\A\D\G\pi
A    wc\A\D\G\rho
A    wc\A\D\G\tau
A    wc\A\D\H
A    wc\A\D\H\chi
A    wc\A\D\H\omega
A    wc\A\D\H\psi
A    wc\iota
Checked out revision 1.

Note that this working copy has no mergeinfo:

>svn pg svn:mergeinfo wc -R

Then we make a simple text change and commit it as r2:

>echo 'text change to a file' > wc\A\mu
>svn ci -m "" wc
Sending        wc\A\mu
Transmitting file data .
Committed revision 2.

Oops, we realize we don't want that change and reverse merge r2 from itself. Subversion always allows you to do this type of merge, regardless of what mergeinfo is set:

>svn merge %url%/A/mu wc/A/mu -c-2
--- Reverse-merging r2 into 'wc\A\mu':
U    wc\A\mu

Since the current mergeinfo implementation has no way of representing reverse merges, the previous merge leaves no mergeinfo evidence:

>svn pg svn:mergeinfo wc -R

And there we have a merge that doesn't set or modify any mergeinfo.

Explicit Mergeinfo Inheritance

Look again at the earlier example examining the mergeinfo on the subversion 1.5.x branch working copy.  Notice the --recursive option?  This means we are getting a list of all the svn:mergeinfo properties set on the entire working copy tree rooted at \SVN\src-1.5.x.  But the only explicit mergeinfo we see is set on \SVN\src-1.5.x.  Surely the numerous merges from trunk to \SVN\src-1.5.x must have affected subtrees of \SVN\src-1.5.x?  Picking a recently merged revision from trunk we can see that this is true:

>svn log -v -q -c30020
r30020 | pburba | 2008-03-24 11:15:48 -0400 (Mon, 24 Mar 2008)
Changed paths:
    M /trunk/subversion/libsvn_client/copy.c

So how does \SVN\src-1.5.x\subversion\libsvn_client\copy.c "know" that r30020 from trunk was merged into it?  It knows via mergeinfo inheritance.  If a path doesn't have explicit svn:mergeinfo it can still have inherited mergeinfo if it has a parent (or grandparent, or great-grandparent, etc.) with explicit mergeinfo.

The concept of "nearest parent" is not limited to the working copy. When determining the inherited mergeinfo on a path with no explicit mergeinfo, Subversion will first crawl as far up the working copy as it can looking for a parent with explicit mergeinfo.  If it reaches the top of the working copy and can't find such a parent, it will then ask the repository about any other parent paths, going as far as the root of the repository if necessary.  Only if no inheritable mergeinfo is found in the repository can we finally say the path has no mergeinfo whatsoever (and sometimes we can't even say it then as we'll see in the next section).

For a real example of inheritance let's look again at \SVN\src-1.5.x\subversion\libsvn_client\copy.c.  We know from the mergeinfo on \SVN\src-1.5.x that the merge of r30020 from trunk should have changed this file, but we also know that copy.c doesn't have any explicit mergeinfo itself.  But since we also know that copy.c's nearest parent with explicit mergeinfo is \SVN\src-1.5.x, then because of mergeinfo inheritance, copy.c's inherited mergeinfo is equivalent to that on \SVN\src-1.5.x.

Unfortunately there is no easy way to see this directly from the command line.  1.5 does provide the new svn mergeinfo subcommand, but for 1.5 it provides only a list of revisions previously merged (or are eligible for merging) from a given source:

>svn mergeinfo --show-revs merged\svn\src-1.5.x\subversion\libsvn_client\copy.c
<snipping a *long* list of revisions>

As you can see, the output is more intended for input into a script as each revision is listed individually.  Also note that unlike svn:mergeinfo , only operative revisions are listed; each revision listed in the preceding output actually made a change to

It is easier to see mergeinfo inheritance implicitly.  Let's try to merge two revisions directly to copy.c in the 1.5.x working copy.  One of those revisions is r30020 which should be ignored since copy.c's inherited mergeinfo shows that is was already merged.  To find another operative revision eligible for merging we use svn mergeinfo again, but this time with the --show-revs eligible option:

>svn mergeinfo --show-revs eligible\svn\src-1.5.x\subversion\libsvn_client\copy.c

Ok, let's merge r29167 and r30020 into copy.c (notice that in 1.5 we can now specify multiple revisions with the merge's -c option):

>svn merge -c29167,30020\svn\src-1.5.x\subversion\libsvn_client\copy.c
--- Merging r29167 into '\SVN\src-1.5.x\subversion\libsvn_client\copy.c':
U    \SVN\src-1.5.x\subversion\libsvn_client\copy.c

Recall from the Subversion book that one of the key features of 1.5 merge tracking is the avoidance of repeat merges.  From the preceding output we see that Subversion only tried to merge r29167 since it realized that revision r30020, based on copy.c's inherited mergeinfo, was already merged!

That covers most of the basics of explicit mergeinfo and mergeinfo inheritance.  Before moving on to more advanced topics there are two final points to always keep in mind about mergeinfo and inheritance:

First, if a path has explicit mergeinfo then that mergeinfo fully describes the merges done to that path, the path doesn't inherit anything else from anywhere.  In other words, inheritance can only come into play if a path has no explicit mergeinfo.

Second, and this is probably terribly obvious but it is worth emphasizing: When a path inherits mergeinfo, it does so only from its nearest parent with explicit mergeinfo.  So if we have a working copy with the following mergeinfo:

>svn pg svn:mergeinfo -R src-branch
src-branch\subversion - /trunk/subversion:30045-30191,30210
src-branch - /trunk:30045-30197
src-branch\www - /trunk/www:30045-30212

What mergeinfo would the file src-branch\subversion\libsvn_repos\reporter.c inherit?  There is mergeinfo on src-branch\www but that is not a parent path of reporter.c.  There is mergeinfo on src-branch and that is a parent of reporter.c, but there is a nearer parent, src-branch\subversion.  So that is where reporter.c gets mergeinfo.

Natural History and Implicit Mergeinfo

Regardless of a path's explicit or inherited mergeinfo, every path has a natural history and Subversion considers this as implicit mergeinfo.  Honestly, this isn't something a typical user needs to understand, so if you're just looking for an overview you can safely skip this topic.

Still here?  Then let's checkout a working copy for a feature branch that one of the Subversion developers is working on and see implicit mergeinfo in action:

>svn co mem-cache-wc -q

By running a couple of log subcommands we can see that this branch was recently made from a copy of Subversion's trunk in r29755:

>svn log --stop-on-copy -q mem-cache-wc
r30314 | glasser | 2008-04-04 19:08:12 -0400 (Fri, 04 Apr 2008)
r30313 | glasser | 2008-04-04 19:07:41 -0400 (Fri, 04 Apr 2008)
r30312 | glasser | 2008-04-04 19:06:14 -0400 (Fri, 04 Apr 2008)
<snipping some of the output for brevity>
r29775 | glasser | 2008-03-07 13:57:45 -0500 (Fri, 07 Mar 2008)
r29773 | glasser | 2008-03-07 13:23:54 -0500 (Fri, 07 Mar 2008)
r29755 | glasser | 2008-03-06 19:46:43 -0500 (Thu, 06 Mar 2008)

>svn log -v -r29755 mem-cache-wc
r29755 | glasser | 2008-03-06 19:46:43 -0500 (Thu, 06 Mar 2008) | 14 lines
Changed paths:
    A /branches/in-memory-cache (from /trunk:29754)
<snipping the actual log message>

Now look at the branch's mergeinfo:

>svn pg svn:mergeinfo -R mem-cache-wc
mem-cache-wc - /branches/svn-mergeinfo-enhancements:30045-30214

Notice how the mergeinfo from /trunk starts at r29755?  What if we tried to merge some revisions prior to r29754?  Will Subversion attempt to merge in those changes from the in-memory-cache's own history?  Let's try, instead of doing a normal feature branch synchronization with trunk, in which we don't specify a revision range, let's explicitly set a range.

>svn merge mem-cache-wc -r23000:HEAD
--- Merging r30313 through r30422 into 'mem-cache-wc':
U    mem-cache-wc\COMMITTERS
U    mem-cache-wc\subversion\libsvn_fs_base\tree.c
U    mem-cache-wc\subversion\libsvn_fs_base\bdb\node-origins-table.c
U    mem-cache-wc\contrib\client-side\svnmucc\
U    mem-cache-wc\
G    mem-cache-wc

Look at the notification, Subversion only tried to merge r30313 onwards. From what we've seen already we don't expect Subversion to merge r29754:30312 as that range was already in the explicit mergeinfo.  But what about -r23000:29754?  Why did Subversion not attempt to merge those revisions?  The answer lies in the implicit mergeinfo of /branches/in-memory-cache.  Because /branches/in-memory-cache was copied from /trunk in r29755, the former, as a copy, shares it's history with /trunk from r29754 and earlier.  Subversion considers this "natural" history as implicit mergeinfo when deciding what to merge from trunk.  Effectively this means /branches/in-memory-cache has the implicit mergeinfo "/trunk:1-29754" in addition to it's explicit mergeinfo.  Notice also that the mergeinfo on the in-memory-cache branch only has "/trunk:30313-30422" added to it after the merge (because mergeinfo describing a path's own history is redundant):

>svn pg svn:mergeinfo -R mem-cache-wc
mem-cache-wc - /branches/1.5.x-r30215:30238

Mergeinfo Inheritance and Non-Inheritable Ranges

Subversion allows a working copies which are incomplete representations of the repository.  This is possible with with shallow checkouts, switched subtrees, or because of authorization restrictions that prevent parts of a tree from being checked out.  You can merge into an incomplete tree if you wish and Subversion endeavors to keep your mergeinfo accurate.  It does this primarily with non-inheritable mergeinfo ranges.  The easiest way to show how these work is with an example:

First we create an incomplete working copy by doing a shallow checkout of the Subversion 1.5.x branch:

Note: The --depth option is also new in 1.5 and restricts operation of Subversion subcommands to a certain depth within the target working copy or URL.  In this case we use the 'immediates' value which gives us only the root of the 1.5.x branch plus each immediate file or directory child of the root.

>svn co 1.5.x --depth immediates
A    1.5.x\
A    1.5.x\STATUS
A    1.5.x\build.conf
A    1.5.x\www
A    1.5.x\
A    1.5.x\notes
A    1.5.x\README
A    1.5.x\subversion
A    1.5.x\build
A    1.5.x\tools
A    1.5.x\BUGS
A    1.5.x\contrib
A    1.5.x\
A    1.5.x\HACKING
A    1.5.x\doc
A    1.5.x\INSTALL
A    1.5.x\COPYING
A    1.5.x\CHANGES
A    1.5.x\
A    1.5.x\
A    1.5.x\aclocal.m4
A    1.5.x\packages
U   1.5.x
Checked out revision 30435.

Now change the depth of the 'subversion' directory so the whole subtree is present:

Note: Here we use the --set-depth option, also new in 1.5.  Where --depth is essentially a limit on subcommands in general, --set-depth is an active operation for update.  Here we use the 'infinity' value which expands the 1.5.x\subversion subtree to its full extent.

>svn up 1.5.x\subversion --set-depth infinity --quiet

Take a moment to look at the mergeinfo we have on the 1.5.x branch:

>svn pg svn:mergeinfo -R 1.5.x
1.5.x\CHANGES - /branches/1.5.x-r30215/CHANGES:30236,30238,30245,30288
1.5.x - /trunk:29085-29089,29091,29094-29107,29111,29114,29117,29126-29127,29129-29133,29135-29150,29153-29164,29166-29170,29174,29176-29186,29188-29189,29193-29194,29198-29206,29208-29251,29254-29256,29261,29267-29273,29277,29280-29281,29284,29287-29303,29305-29307,29309-29343,29345-29348,29358-29379,29381-29392,29397,29399,29401,29409,29412,29414-29415,29417-29423,29425-29426,29429,29433-29434,29436-29447,29449-29466,29468-29478,29482,29484,29486-29487,29489,29491,29493,29496,29498,29508,29527-29528,29531,29533,29539-29540,29542,29544,29546,29551,29553,29556,29559,29565,29567-29569,29571-29578,29581,29583,29591,29594,29600,29603,29607,29611,29613-29614,29619,29623,29625-29626,29630-29631,29633-29634,29642,29645,29648,29650,29656,29659-29660,29663-29666,29671-29672,29677-29680,29692,29738-29739,29741-29744,29746,29751,29763,29767,29769-29770,29784,29786-29787,29797,29801,29815,29821,29824,29828,29835,29852,29854-29855,29857-29859,29868-29869,29876,29878,29883-29884,29895,29898,29900,29914,29920,29922,29925,29930,29939-29940,29942,29950,29958,29962,29965,29967-29968,29980,29986,29994-29997,30004,30009,30020,30030,30050,30053-30054,30059,30061-30062,30067,30070,30074,30086,30098,30101,30117,30124,30129-30130,30137,30145,30151,30159,30161-30162,30180-30181,30185,30210,30233,30237,30239,30246,30249,30256,30278-30279,30281,30285,30297,30299,30304,30319-30321,30328,30335-30336,30340,30342,30347,30362,30368,30373,30375,30378,30380,30392,30402,30407-30409,30412

1.5.x\CHANGES has had merges done directly to it (i.e. it was once a merge target), so notice that it has its own explicit mergeinfo.  When merging to 1.5.x we refer to 1.5.x\CHANGES as a subtree with differing mergeinfo. We'll get back to these subtrees later on.

Now let's merge some changes from trunk into this working copy.  We'll merge two revisions from trunk in one go.  One revision will make changes to parts of the working copy that are present and one will try to make some changes to paths not present due to the sparse checkout.

The specific revisions we'll use are r30431 and r30435: 

>svn log --verbose --quiet -r30430:30435
r30431 | epg | 2008-04-07 19:40:11 -0400 (Mon, 07 Apr 2008)
Changed paths:
   M /trunk/subversion/tests/cmdline/
r30435 | julianfoad | 2008-04-08 09:56:02 -0400 (Tue, 08 Apr 2008)
Changed paths:
   A /trunk/notes/tree-conflicts/policy.txt

Ok, let's do the merge:

Note: We use --depth infinity here to force the merge into any children of 1.5.x which are present.  Due to the original shallow checkout 1.5.x's depth is 'immediates' and the depth of the merge operation defaults to the depth of the merge target.  If we didn't specify infinite depth, Subversion wouldn't even attempt to merge r30435.

>svn merge --depth infinity 1.5.x -r30430:30435
Skipped missing target: '1.5.x\notes\tree-conflicts\policy.txt'
Skipped missing target: '1.5.x\notes\tree-conflicts'
--- Merging r30431 through r30435 into '1.5.x':
U    1.5.x\subversion\tests\cmdline\

Since 1.5.x\notes\tree-conflicts and 1.5.x\notes\tree-conflicts\policy.txt are not present due to the sparse working copy, they are skipped when we attempt to merge r30435.  The file 1.5.x\subversion\tests\cmdline\ is present though, so the changes from r30431 get merged into that.

Let's look at what mergeinfo exists on 1.5.x now:

>svn pg svn:mergeinfo 1.5.x

We can see that -r30430:30435 from /trunk was added to 1.5.x's mergeinfo. That makes sense, it is the range we requested in the merge!  Note also that r30432, r30433, r30434, while all inoperative revisions on trunk are still recorded in the mergeinfo as discussed in the "Inoperative Vs. Operative Revisions In Mergeinfo" section.

Now you might be thinking, "Fine, but what happens if we commit this change and then someone else checks out a full 1.5.x working copy?  Won't their copy of notes\tree-conflicts\policy.txt incorrectly inherit r30430:30435 from the root?  That file was never actually changed in the merge!"  That's absolutely correct, but I was a bit misleading only looking at the mergeinfo on the root of the working copy earlier.  Let's look a bit further and check the status of the working copy:

>svn st 1.5.x
M     1.5.x
M     1.5.x\www
M     1.5.x\notes
M     1.5.x\build
M     1.5.x\subversion\tests\cmdline\
M     1.5.x\contrib
M     1.5.x\tools
M     1.5.x\doc
M     1.5.x\CHANGES
M     1.5.x\packages

Clearly Subversion did more than it notified us about during the merge, as there are property modifications on each child of 1.5.x that is checked out at depth empty.  So what exactly are all those property modifications?  Let's look at one using svn diff:

>svn diff 1.5.x\www
Property changes on: 1.5.x\www
Added: svn:mergeinfo
Merged /trunk/www:r29085-29089,29091,29094-29107,29111,29114,29117,29126-29127,29129-29133,29135-29150,29153-29164,29166-29170,29174,29176-29186,29188-29189,29193-29194,29198-29206,29208-29251,29254-29256,29261,29267-29273,29277,29280-29281,29284,29287-29303,29305-29307,29309-29343,29345-29348,29358-29379,29381-29392,29397,29399,29401,29409,29412,29414-29415,29417-29423,29425-29426,29429,29433-29434,29436-29447,29449-29466,29468-29478,29482,29484,29486-29487,29489,29491,29493,29496,29498,29508,29527-29528,29531,29533,29539-29540,29542,29544,29546,29551,29553,29556,29559,29565,29567-29569,29571-29578,29581,29583,29591,29594,29600,29603,29607,29611,29613-29614,29619,29623,29625-29626,29630-29631,29633-29634,29642,29645,29648,29650,29656,29659-29660,29663-29666,29671-29672,29677-29680,29692,29738-29739,29741-29744,29746,29751,29763,29767,29769-29770,29784,29786-29787,29797,29801,29815,29821,29824,29828,29835,29852,29854-29855,29857-29859,29868-29869,29876,29878,29883-29884,29895,29898,29900,29914,29920,29922,29925,29930,29939-29940,29942,29950,29958,29962,29965,29967-29968,29980,29986,29994-29997,30004,30009,30020,30030,30050,30053-30054,30059,30061-30062,30067,30070,30074,30086,30098,30101,30117,30124,30129-30130,30137,30145,30151,30159,30161-30162,30180-30181,30185,30210,30233,30237,30239,30246,30249,30256,30278-30279,30281,30285,30297,30299,30304,30319-30321,30328,30335-30336,30340,30342,30347,30362,30368,30373,30375,30378,30380,30392,30402,30407-30409,30412,30431-30435*

1.5.x\www now has explicit mergeinfo describing merges from /trunk/www.  Look carefully and you'll see that the revisions themselves are identical to those on 1.5.x, with one important exception: The range r30430:30435 that was added to 1.5.x in this merge has a '*' suffix for 1.5.x\www. This '*' is the marker for a non-inheritable mergeinfo range.  The '*' means that only the path on which the mergeinfo is explicitly set has had this range merged into it.

I won't show this in detail, but now each empty child of 1.5.x has its own explicit mergeinfo now.  This mergeinfo is a combination of what the child inherited from 1.5.x prior to the merge, plus the addition of 30431-30435* signifying that those revisions where merged only as far as the empty child.

If we were to commit this change and another user was to do a full --depth infinity checkout of the 1.5.x branch, then their copy of notes\tree-conflicts\policy.txt would inherit all the mergeinfo from notes except 30431-30435, which is a good thing since those revisions were never actually merged into policy.txt.

A similar process happens any time Subversion can't access a subtree of a merge target.  Subtrees are inaccessible if they are switched to another URL or simply aren't present on disk.  The latter can be caused by a shallow working copy as above, or may be due to authorization restrictions.  Regardless of the reason a subtree is missing, Subversion always tries to do what we just saw above:

Note: Subtrees missing because they are switched are even a bit more complicated than what is described here, since the switched paths themselves also get explicit mergeinfo, but I'll leave this for a future article.

Now typically most users won't merge to a target with missing subtrees, but if you need to, rest assured that Subversion attempts to keep the explicit/inherited mergeinfo on each path an accurate reflection of only what was merged to that path.

Empty Mergeinfo

Empty mergeinfo is a svn:mergeinfo property which has the empty string as a value.  It simply means "the state of this path is as if nothing was ever merged into it".  Empty mergeinfo typically occurs when doing a working copy to working copy move or copy (the subject of a forthcoming Submerged post) or when reverse merging all prior merges out of a subtree.  Let's look at an example of the latter:

Our current working directory is a simple branch:

>svn ls -R .

Looking at the mergeinfo we see that several revisions were merged in to this branch already:

>svn pl -vR .
Properties on '.':
  svn:mergeinfo : /A:2-6

What happens if we reverse merge all of those revisions out of subtree of the branch?

>svn merge %url92%/A/D/H .\D\H -r6:1
--- Reverse-merging r6 through r2 into 'D\H':
U    D\H\omega
U    D\H\psi
>svn pl -vR .
Properties on '.':
  svn:mergeinfo : /A:2-6
Properties on 'D\H':
  svn:mergeinfo :

Empty mergeinfo is what happens.  If .\D\H didn't have explicit empty mergeinfo, it would incorrectly inherit the mergeinfo /A/D/H:2-6 from '.'.

Mergeinfo Elision

At the end of every merge Subversion tries to "consolidate" any redundant subtree mergeinfo.  This consolidation process is called elision.  Once a merge is completed, Subversion walks the working copy tree rooted at the merge target.  If it finds a path with explicit mergeinfo which has a subtree with equivalent explicit mergeinfo, then the subtree's mergeinfo is elided (removed).  "Equivalency" here is defined this way:

In other words, removing the subtree's mergeinfo is safe to do since, if the subtree's mergeinfo is equivalent to its nearest parent with explicit mergeinfo, then the mergeinfo the subtree inherits from that parent is already sufficient to describe the merges to the subtree.  It's probably becoming clear now that mergeinfo inheritance and elision are just two ways of looking at the same thing:  Inhertance is mergeinfo sliding "down" the tree from parent to child, elision is mergeinfo sliding "up" the tree from child to parent.

Enough theory, let's take a look at elision in action.  In this example we'll checkout a working copy for a current Subversion feature branch:

>svn co no_pass_wc -q

>svn pg svn:mergeinfo -R no_pass_wc
no_pass_wc - /branches/1.5.x-r30215:30238

The developers of this branch have periodically synched it up with Subversion's trunk.  But development on trunk happens almost constantly, and using the svn mergeinfo subcommand we see that there are some revisions on trunk available for merging:

>svn mergeinfo --show-revs eligible no_pass_wc

Let's say we are working on this branch and we are a Windows developer.  We know that a change on trunk (r30754) is needed to build the branch on Windows, but we don't want to merge all available changes from trunk just now (perhaps we know that there will be many conflicts to resolve and we are not ready to do that).  We decide to merge just the small fix we need directly to the offending file build.conf on our branch:

>svn merge\build.conf -c30754
--- Merging r30754 into 'no_pass_wc\build.conf':
U    no_pass_wc\build.conf

As expected the diff of the branch shows that explicit mergeinfo was added to build.conf and it looks equivalent to mergeinfo on the root of the working copy with the exception of r30754.

>svn diff no_pass_wc
Index: no_pass_wc/build.conf
--- no_pass_wc/build.conf       (revision 30756)
+++ no_pass_wc/build.conf       (working copy)
<snip text diff>
Property changes on: no_pass_wc\build.conf
Added: svn:mergeinfo
  Merged /branches/diff-callbacks3/build.conf:r29985-30687
  Merged /trunk/build.conf:r30654-30731,30754
  Merged /branches/1.5.x-r30215/build.conf:r30238
  Merged /branches/svn-mergeinfo-enhancements/build.conf:r30045-30214

Later we decide that we are ready to synch up with trunk, and do that, momentarily forgetting about the merge we've already done to build.conf:

>svn merge no_pass_wc
--- Merging r30732 through r30753 into 'no_pass_wc':
U    no_pass_wc\subversion\include\svn_client.h
A    no_pass_wc\subversion\include\private\svn_opt_private.h
U    no_pass_wc\subversion\include\svn_opt.h
U    no_pass_wc\subversion\libsvn_wc\status.c
U    no_pass_wc\subversion\libsvn_subr\opt.c
U    no_pass_wc\subversion\libsvn_subr\mergeinfo.c
A    no_pass_wc\subversion\libsvn_client\cmdline.c
U    no_pass_wc\subversion\libsvn_client\merge.c
U    no_pass_wc\subversion\libsvn_client\prop_commands.c
U    no_pass_wc\subversion\libsvn_client\mergeinfo.h
U    no_pass_wc\subversion\tests\libsvn_client\client-test.c
U    no_pass_wc\subversion\tests\cmdline\
U    no_pass_wc\subversion\tests\cmdline\
U    no_pass_wc\subversion\tests\cmdline\
U    no_pass_wc\subversion\tests\cmdline\
U    no_pass_wc\subversion\svn\merge-cmd.c
U    no_pass_wc\subversion\svn\cl.h
U    no_pass_wc\subversion\svn\propdel-cmd.c
U    no_pass_wc\subversion\svn\checkout-cmd.c
U    no_pass_wc\subversion\svn\move-cmd.c
U    no_pass_wc\subversion\svn\mkdir-cmd.c
U    no_pass_wc\subversion\svn\cat-cmd.c
U    no_pass_wc\subversion\svn\revert-cmd.c
U    no_pass_wc\subversion\svn\diff-cmd.c
U    no_pass_wc\subversion\svn\copy-cmd.c
U    no_pass_wc\subversion\svn\mergeinfo-cmd.c
U    no_pass_wc\subversion\svn\list-cmd.c
U    no_pass_wc\subversion\svn\util.c
U    no_pass_wc\subversion\svn\blame-cmd.c
U    no_pass_wc\subversion\svn\propget-cmd.c
U    no_pass_wc\subversion\svn\changelist-cmd.c
U    no_pass_wc\subversion\svn\log-cmd.c
U    no_pass_wc\subversion\svn\update-cmd.c
U    no_pass_wc\subversion\svn\resolved-cmd.c
U    no_pass_wc\subversion\svn\cleanup-cmd.c
U    no_pass_wc\subversion\svn\commit-cmd.c
U    no_pass_wc\subversion\svn\add-cmd.c
U    no_pass_wc\subversion\svn\propset-cmd.c
U    no_pass_wc\subversion\svn\switch-cmd.c
U    no_pass_wc\subversion\svn\delete-cmd.c
U    no_pass_wc\subversion\svn\import-cmd.c
U    no_pass_wc\subversion\svn\proplist-cmd.c
U    no_pass_wc\subversion\svn\resolve-cmd.c
U    no_pass_wc\subversion\svn\export-cmd.c
U    no_pass_wc\subversion\svn\status-cmd.c
U    no_pass_wc\subversion\svn\propedit-cmd.c
U    no_pass_wc\subversion\svn\lock-cmd.c
U    no_pass_wc\subversion\svn\info-cmd.c
U    no_pass_wc\subversion\svn\unlock-cmd.c
U    no_pass_wc\subversion\libsvn_fs_fs\structure
--- Merging r30754 through r30757 into 'no_pass_wc':
U    no_pass_wc\subversion\include\svn_config.h
U    no_pass_wc\subversion\libsvn_wc\merge.c

Notice that Subversion realizes r30754 was already merged to build.conf and doesn't attempt to repeat that portion of the merge.  Not only that, but once the merge is done, Subversion notices that the mergeinfo on the root of the working copy and the mergeinfo on build.conf is equivalent and elides the latter away.  We can see that this is true in the status of the working copy or by looking at the svn:mergeinfo property directly:

>svn st no_pass_wc
M     no_pass_wc
M     no_pass_wc\build.conf
M     no_pass_wc\subversion\svn\unlock-cmd.c
M     no_pass_wc\subversion\libsvn_fs_fs\structure

>svn pg svn:mergeinfo -R no_pass_wc
no_pass_wc - /branches/1.5.x-r30215:30238

If we could have stopped subversion after the merge was completed but before elision occurred we would have seen this mergeinfo:

no_pass_wc - /branches/1.5.x-r30215:30238
no_pass_wc\build.conf - /branches/1.5.x-r30215/build.conf:30238

Because the mergeinfo on no_pass_wc/build.conf is equivalent to that on no_pass_wc, the former is elided away.

Record Only Merges

A new merge subcommand option in 1.5 is --record-only.  Merges done with the --record-only option don't attempt to merge anything, but they do record and elide mergeinfo as if a real merge took place.  This is useful for making it appear that a change was merged without actually merging it (blocking) and also for cleaning up subtree mergeinfo.

Blocking in 1.5 is quite simple, just use --record-only to merge a revision and Subversion makes the mergeinfo look like a merge has taken place.  From then on, Subversion sees the mergeinfo and acts as if the revision really was merged, blocking future attempts to merge it.  For example, looking again at the clean checkout from our previous example, we first merge an eligible revision without --record-only:

>svn merge no_pass_wc -c30757
--- Merging r30757 into 'no_pass_wc':
U    no_pass_wc\subversion\include\svn_config.h

As expected this merge updates the mergeinfo on the merge target:

>svn st no_pass_wc
M   no_pass_wc
M   no_pass_wc\subversion\include\svn_config.h

>svn diff no_pass_wc --depth empty
Property changes on: no_pass_wc
Modified: svn:mergeinfo
   Merged /trunk:r30757

Now we revert that merge and repeat it, but this time using --record-only:

>svn revert -R no_pass_wc
Reverted 'no_pass_wc'
Reverted 'no_pass_wc\subversion\include\svn_config.h'

>svn merge no_pass_wc -c30757 –record-only

There is no output from the merge, which makes sense as nothing was merged.  The mergeinfo has changed though, as a diff shows:

>svn diff no_pass_wc
Property changes on: no_pass_wc
Modified: svn:mergeinfo
   Merged /trunk:r30757

If we try the merge yet again without --record-only we see that it is blocked:

>svn merge no_pass_wc -c30757

Note: 1.5 blocking isn't what some refer to as "true blocking", where it is readily obvious what was actually merged vs. what was only blocked.  It is possible in 1.5 to determine if all the sources and ranges represented by some path's given mergeinfo were actually merged or only blocked, but this is not always trivial.  The development community is considering some type of true blocking for a future release.

As to using --record-only to clean up subtree mergeinfo, let's look at a simple example.  Checkout a very simple repository:

>svn co %SIMPLEURL% simple_wc
A    simple_wc\A
A    simple_wc\A\B
A    simple_wc\A\B\lambda
A    simple_wc\A\B\E
A    simple_wc\A\B\E\alpha
A    simple_wc\A\B\E\beta
A    simple_wc\A\B\F
A    simple_wc\A\mu
A    simple_wc\A\C
A    simple_wc\A\D
A    simple_wc\A\D\gamma
A    simple_wc\A\D\G
A    simple_wc\A\D\G\pi
A    simple_wc\A\D\G\rho
A    simple_wc\A\D\G\tau
A    simple_wc\A\D\H
A    simple_wc\A\D\H\chi
A    simple_wc\A\D\H\omega
A    simple_wc\A\D\H\psi
A    simple_wc\iota
A    simple_wc\A_branch
A    simple_wc\A_branch\B
A    simple_wc\A_branch\B\lambda
A    simple_wc\A_branch\B\E
A    simple_wc\A_branch\B\E\alpha
A    simple_wc\A_branch\B\E\beta
A    simple_wc\A_branch\B\F
A    simple_wc\A_branch\mu
A    simple_wc\A_branch\C
A    simple_wc\A_branch\D
A    simple_wc\A_branch\D\gamma
A    simple_wc\A_branch\D\G
A    simple_wc\A_branch\D\G\pi
A    simple_wc\A_branch\D\G\rho
A    simple_wc\A_branch\D\G\tau
A    simple_wc\A_branch\D\H
A    simple_wc\A_branch\D\H\chi
A    simple_wc\A_branch\D\H\omega
A    simple_wc\A_branch\D\H\psi
Checked out revision 4.

From the log we see that this repos consists of a tree rooted at A which was added in r1, copied to a branch in r2, and then modified in r3 and r4:

>svn log --verbose -r1:HEAD simple_wc
r1 | jrandom | 2008-04-23 13:00:56 -0400 (Wed, 23 Apr 2008) | 1 line
Changed paths:
    A /A
    A /A/B
    A /A/B/E
    A /A/B/E/alpha
    A /A/B/E/beta
    A /A/B/F
    A /A/B/lambda
    A /A/C
    A /A/D
    A /A/D/G
    A /A/D/G/pi
    A /A/D/G/rho
    A /A/D/G/tau
    A /A/D/H
    A /A/D/H/chi
    A /A/D/H/omega
    A /A/D/H/psi
    A /A/D/gamma
    A /A/mu
    A /iota
Log message for revision 1.
r2 | pburba | 2008-04-23 13:02:04 -0400 (Wed, 23 Apr 2008) | 1 line
Changed paths:

    A /A_branch (from /A:1)
Make a branch from A
r3 | pburba | 2008-04-23 13:02:44 -0400 (Wed, 23 Apr 2008) | 1 line
Changed paths:
    M /A/D/H/psi
r4 | pburba | 2008-04-23 13:03:09 -0400 (Wed, 23 Apr 2008) | 1 line
Changed paths:
    M /A/B/E/beta

First we merge r3 directly to A_branch\D\H\psi:

>svn merge %SIMPLEURL%/A/D/H/psi simple_wc\A_branch\D\H\psi -c3
--- Merging r3 into 'simple_wc\A_branch\D\H\psi':
U    simple_wc\A_branch\D\H\psi

>svn ci -m "merged r3 to A_branch/D/H/psi" simple_wc
Sending        simple_wc\A_branch\D\H\psi
Transmitting file data .
Committed revision 5.

>svn pl -vR merge_tests-92

>svn pl -vR simple_wc
Properties on 'simple_wc\A_branch\D\H\psi':
  svn:mergeinfo : /A/D/H/psi:3

Then we merge r4 to the root of the branch:

>svn merge %SIMPLEURL%/A simple_wc\A_branch -c4
--- Merging r4 into 'simple_wc\A_branch':
U    simple_wc\A_branch\B\E\beta

>svn pl -vR simple_wc
Properties on 'simple_wc\A_branch':
  svn:mergeinfo : /A:4
Properties on 'simple_wc\A_branch\D\H\psi':
  svn:mergeinfo : /A/D/H/psi:3-4

Since simple_wc is at revision 5 in the repository we must update the working copy to avoid an out-of-date error whencommitting:

>svn up simple_wc
At revision 5.
>svn ci -m "" simple_wc
Sending        simple_wc\A_branch
Sending        simple_wc\A_branch\B\E\beta
Sending        simple_wc\A_branch\D\H\psi
Transmitting file data .
Committed revision 6.

Notice that the subtree of the merge target, A_branch\D\H\psi got its mergeinfo updated too:

>svn pl --verbose -R simple_wc
Properties on 'simple_wc\A_branch':
  svn:mergeinfo : /A:4
Properties on 'simple_wc\A_branch\D\H\psi':
  svn:mergeinfo : /A/D/H/psi:3-4

Oops, we just remembered that our company policy is to merge only to the root of branches so as to keep the explicit mergeinfo consolidated there. We forgot about that when we did the first merge and now our pointy haired boss is giving us heat.  How can we fix this?  Well, we know that r3 affects only A/D/H/psi, so if we added "/A:r3" to the mergeinfo for A_branch then our mergeinfo is still semantically equivalent to what we have now.  Of course if we did that, the mergeinfo on A_branch\D\H\psi is then equivalent to that on A_branch and could elide.  We can do this in one quick step with a --record-only merge. First though we need to update the working copy (see "Mixed Revision Working Copies" for why this is necessary):

>svn up simple_wc
At revision 6.

>svn merge %SIMPLEURL%/A simple_wc\A_branch -c3 –record-only

>svn st simple_wc
M     simple_wc\A_branch
M     simple_wc\A_branch\D\H\psi

>svn pl -vR simple_wc
Properties on 'simple_wc\A_branch':
  svn:mergeinfo : /A:3-4

See that the --record-only merge added r3 to the mergeinfo on A_branch and then elided the equivalent mergeinfo on A_branch\D\H\psi.  Time to ask the PHB for a raise!

Mixed Revision Working Copies and Mergeinfo

One of Subversion's basic design principles is to be as flexible as possible.  Often flexibility also means greater complexity and with greater complexity there is always the possibility for confusion.  Such is the case with mixed revision working copies and mergeinfo:

The flexibility is: Subversion allows you to merge into mixed-revision working copies.

The complexity is: Mergeinfo inheritance and elision are dependent on uniform working revisions across the working copy.

The confusion is: Mergeinfo inheritance and elision may not work like you expect when dealing with a mixed revision working copy.

The problems are most likely to arise when doing "subtree merges" (i.e. merges not to the root of a branch, but to a subtree of the root).  For the following example we use the sample repository from openCOLLABNET's merge-tracking project.

First we checkout a new working copy of the sample repository:

>svn co %URL% wc --quiet

The repos is structured in fairly common way, with trunk, branches, and tags folders off the root...

>svn ls wc

...and several copies of trunk under branches:

>svn ls wc\branches

We are going to work with the c branch, so let's look at the mergeinfo there:

>svn pg svn:mergeinfo -R wc\branches\c
wc\branches\c - /branches/a:3-11

Now we make some changes to trunk:

Doing some work...
>svn ci -m "some changes under trunk" wc
Sending        wc\trunk\jobs\index.html
Transmitting file data .
Committed revision 18.

Doing some work...
>svn ci -m "some changes under trunk" wc
Sending        wc\trunk\jobs\index.html
Transmitting file data .
Committed revision 19.

Doing some work...
>svn ci -m "some changes under trunk" wc
Sending        wc\trunk\about\index.html
Sending        wc\trunk\jobs\index.html
Transmitting file data ..
Committed revision 20.

Now to start merging.  Assume we need to merge the changes from r18, r19, and r20 to the c branch, but we only want the changes that affect the jobs subtree, not the changes to the about subtree, so we merge the changes directly to branches\c\jobs:

>svn merge %URL%/trunk/jobs wc\branches\c\jobs -c18,19,20
--- Merging r18 into 'wc\branches\c\jobs':
U    wc\branches\c\jobs\index.html
--- Merging r19 into 'wc\branches\c\jobs':
G    wc\branches\c\jobs\index.html
--- Merging r20 into 'wc\branches\c\jobs':
G    wc\branches\c\jobs\index.html

This of course means that branches\c\jobs now has it's own explicit mergeinfo that differs from branches\c:

>svn pg svn:mergeinfo -R wc\branches\c
wc\branches\c - /branches/a:3-11
wc\branches\c\jobs - /branches/a/jobs:3-11

Once we commit the merge notice that we now have a mixed revision working copy:

>svn ci -m "Merged some changes from trunk to a subtree of c branch" wc
Sending        wc\branches\c\jobs
Sending        wc\branches\c\jobs\index.html
Transmitting file data .
Committed revision 25.

>svnversion wc\branches\c

Revision 25?  Looks like some other developer made changes to the repository resulting in new revisions r21-r24 in the time between our initial checkout and this commit.

Later we need to merge the other changes from r20 to branches\c that touch the about subtree.  Recalling our boss' rant about merging to the root of branches, we decide to merge r20 to the root of the branch.  We also elect to re-merge r18 and r19, knowing that the merge tracking logic should prevent a repeated merge and also elide away the mergeinfo on branches\c\jobs:

>svn merge %URL%/trunk wc\branches\c -c18,19,20
--- Merging r20 into 'wc\branches\c':
U    wc\branches\c\about\index.html

Before we commit we check the mergeinfo on the branches\c.  What's this? branches\c\jobs\index.html still has mergeinfo, and it looks perfectly equivalent to that on branches\c?  Why didn't the mergeinfo on branches\c\jobs elide to branches\c?

>svn pg svn:mergeinfo -R wc\branches\c
wc\branches\c - /branches/a:3-11
wc\branches\c\jobs - /branches/a/jobs:3-11

The problem lies with our mixed revision working copy:

>svn st -v wc\branches\c
M              17       16 cuser        wc\branches\c
               17       15 merger       wc\branches\c\products
               17        2 user         wc\branches\c\products\little.html
               17       15 merger       wc\branches\c\products\medium.html
               17        2 user         wc\branches\c\products\big.html
               17       15 merger       wc\branches\c\products\roadmap.html
               17       15 merger       wc\branches\c\products\index.html
               17       15 merger       wc\branches\c\about
M              17       15 merger       wc\branches\c\about\index.html
               17       15 merger       wc\branches\c\index.html
               17       15 merger       wc\branches\c\news
               17       15 merger       wc\branches\c\news\index.html
               17        2 user         wc\branches\c\support
               17        2 user         wc\branches\c\support\index.html
               25       25 pburba       wc\branches\c\jobs
               25       25 pburba       wc\branches\c\jobs\index.html

Notice that branches\c\jobs is at working revision 25, while the rest of branches\c is at r17.  Subversions elision logic won't try to elide the mergeinfo on branches\c\jobs up the working copy to branches\c because the two are at different working revisions.  Why not?  Because there is no way of knowing if the mergeinfo found on branches\c@17 is the same as branches\c@25.  It's possible the changes our colleague made in r21-r24 changed to mergeinfo of branches\c.  If this were the case and we try to commit this merge we'd get an out of date error and have to update the working copy before committing.

We can check if r21-r24 affect the branches\c branch with a --show-updates status:

>svn st --show-updates --verbose wc\branches\c
               17        2 user         wc\branches\c\products\little.html
               17       15 merger       wc\branches\c\products\medium.html
               17        2 user         wc\branches\c\products\big.html
               17       15 merger       wc\branches\c\products\roadmap.html
               17       15 merger       wc\branches\c\products\index.html
               17       15 merger       wc\branches\c\products
M              17       15 merger       wc\branches\c\about\index.html
               17       15 merger       wc\branches\c\about
               17       15 merger       wc\branches\c\index.html
               17       15 merger       wc\branches\c\news\index.html
               17       15 merger       wc\branches\c\news
               17        2 user         wc\branches\c\support\index.html
               17        2 user         wc\branches\c\support
               25       25 pburba       wc\branches\c\jobs\index.html
               25       25 pburba       wc\branches\c\jobs
M              17       16 cuser        wc\branches\c
Status against revision:     25

Nope, no one has made changes to branches\c, if they had we'd see the '*' out of date marker in the 8th column.  So we decide to update branches\c:

>svn up wc\branches\c
At revision 25.

Now the branch is at a uniform working revision (and still has the local modifications from the uncommitted merge):

>svnversion wc\branches\c

We still want to get rid of that redundant explicit mergeinfo on branches/c/jobs so we repeat the merge, relying on the merge tracking logic to avoid any repeated merge (we could also use a --record-only merge here, in this case there is no effective difference between the two):

>svn merge %URL%/trunk wc\branches\c -c18,19,20


Good, no merging occurred, but sure enough, mergeinfo elision, now that the working copy is at a uniform revision, has taken place:

>svn st wc\branches\c -v
M              25       25 pburba       wc\branches\c
               25       15 merger       wc\branches\c\products
               25        2 user         wc\branches\c\products\little.html
               25       15 merger       wc\branches\c\products\medium.html
               25        2 user         wc\branches\c\products\big.html
               25       15 merger       wc\branches\c\products\roadmap.html
               25       15 merger       wc\branches\c\products\index.html
               25       15 merger       wc\branches\c\about
M              25       15 merger       wc\branches\c\about\index.html
               25       15 merger       wc\branches\c\index.html
               25       15 merger       wc\branches\c\news
               25       15 merger       wc\branches\c\news\index.html
               25        2 user         wc\branches\c\support
               25        2 user         wc\branches\c\support\index.html
M              25       25 pburba       wc\branches\c\jobs
               25       25 pburba       wc\branches\c\jobs\index.html

>svn pg svn:mergeinfo -R wc\branches\c
wc\branches\c - /branches/a:3-11

This example demonstrated problems with mergeinfo elision.  As mentioned previously elision and inheritance are essentially the same thing so you can see problems with mixed-revision working copies involving inheritance too.  Let's say we have the following mixed-revision working copy sometime after we've committed the previous merge as r26:

>svn st -v wc\branches\c
               26       26 pburba       wc\branches\c
               26       15 merger       wc\branches\c\products
              26        2 user         wc\branches\c\products\little.html
               26       15 merger       wc\branches\c\products\medium.html
               26        2 user         wc\branches\c\products\big.html
               26       15 merger       wc\branches\c\products\roadmap.html
               26       15 merger       wc\branches\c\products\index.html
               25       15 merger       wc\branches\c\about
               26       26 pburba       wc\branches\c\about\index.html
               26       15 merger       wc\branches\c\index.html
               26       15 merger       wc\branches\c\news
               26       15 merger       wc\branches\c\news\index.html
               26        2 user         wc\branches\c\support
               26        2 user         wc\branches\c\support\index.html
               26       26 pburba       wc\branches\c\jobs
               26       25 pburba       wc\branches\c\jobs\index.html

>svn pg svn:mergeinfo -R wc\branches\c
wc\branches\c - /branches/a:3-11

If we try to merge revisions 17:20 directly into branches\c\about, Subversion tries to figure out what the mergeinfo for branches\c\about is so it can avoid any repeat merges.  Unfortunately it can't inherit the explicit mergeinfo from branches\c because that is at a different working revision.  So instead it Subversion asks the server what the explicit or inherited mergeinfo for branches\c\about@25 is.  That path has no explicit mergeinfo at that revision but does inherit mergeinfo from branches\c@25, but this doesn't include r18-20 since that was not committed until r26!  So Subversion thinks r18-20wasn't applied to the tree rooted at branches/c/about and repeats the merge:

>svn merge %URL%/trunk/about wc\branches\c\about -c18,19,20
--- Merging r20 into 'wc\branches\c\about':
U    wc\branches\c\about\index.html

In this case all that happens is that branches\c\about gets explicit mergeinfo, though a conflict from the repeated merge could easily occur:

>svn st wc\branches\c
M     wc\branches\c\about

>svn diff wc\branches\c
Property changes on: wc\branches\c\about
Added: svn:mergeinfo
   Merged /branches/b/about:r10-13
   Merged /trunk/about:r2,5-14,18-20
   Merged /branches/a/about:r3-11

If we revert that merge and update the merge target to a uniform working copy revision and then repeat the merge we see that nothing happens:

>svn revert -R wc
Reverted 'wc\branches\c\about'

>svn up wc
At revision 26.

>svn merge %URL%/trunk/about wc\branches\c\about -c18,19,20

>svn st wc


This is because branches\c\about could now inherit the explicit mergeinfo from branches\c, which included r18-20.  Seeing that these revisions were already merged to branches\c\about Subversion doesn't attempt any merge at all.

Admittedly both of these example are a bit contrived, and you may never run into anything like them.  But if you see mysterious behavior regarding mergeinfo always check if a mixed-revision working copy is the culprit.

The author sheepishly admits to being confounded a few times by the intersection of mixed-rev working copies and mergeinfo inheritance/elision.  Once even going as far as starting to write up a new issue in Subversions issue tracker before realizing what was going on...

Parting Thoughts

Whew, that covered a lot of ground!  Hopefully you have a better understanding of how mergeinfo works, particularly in some atypical use cases.  But what if you'd simply like to avoid this whole lot of complexities?  Depending on your needs it isn't so hard to do.  If they are compatible with your development processes, these following rules will keep your mergeinfo (and your merges) as straightforward as possible:

Yes, that's a lot of don'ts, and here's one more: Don't hesitate to do all of these things if you need to. As I said at the start, Subversion always attempts to "Do the Right Thing" for you regarding mergeinfo, and hopefully this post will help you to do the same.