Search code examples
svnmergetree-conflict

svn tree conflicts: what am i doing wrong?


Anytime I try to merge in SVN I get piles of tree conflicts. Well, in the case of this sample script just one, but still.

#!/bin/bash
svnadmin create repo
svn checkout file://`pwd`/repo wc
cd wc
mkdir trunk branches
svn add trunk branches
svn commit -m 'created trunk and branches'
echo red > trunk/colors
svn add trunk/colors
svn commit trunk -m 'created trunk/colors with red inside'
svn copy trunk branches/a
svn commit branches/a -m 'created branches/a'
echo green >> trunk/colors
svn commit trunk -m 'added green to trunk/colors'
echo blue >> branches/a/colors
svn commit branches/a -m 'added blue to branches/a/colors'
svn update
svn merge ^/trunk branches/a

My result is:

Checked out revision 0.
A         trunk
A         branches
Adding         branches
Adding         trunk

Committed revision 1.
A         trunk/colors
Adding         trunk/colors
Transmitting file data .
Committed revision 2.
A         branches/a
Adding         branches/a
Adding         branches/a/colors

Committed revision 3.
Sending        trunk/colors
Transmitting file data .
Committed revision 4.
Sending        branches/a/colors
Transmitting file data .
Committed revision 5.
Updating '.':
At revision 5.
--- Merging r2 through r5 into 'branches/a':
   C branches/a/colors
--- Recording mergeinfo for merge of r2 through r5 into 'branches/a':
 U   branches/a
Summary of conflicts:
  Tree conflicts: 1

I know that SVN isn't known for being merge friendly, but I have to assume that in this case it is my fault somehow. Thanks for any pointers.


Solution

  • The problem you've encountered is not due to the 'local copy'[1] per se. The problem lies in the fact that, in revision 3, you copy a mixed-revision working copy (http://svnbook.red-bean.com/en/1.7/svn-book.html#svn.basic.in-action.mixedrevs).

    If we run your script right up until you copy 'trunk' to 'branches/a' we find that we have a mixed-revision working copy:

    >svn st -v
                     0        0  ?           .
                     1        1 pburba       branches
                     1        1 pburba       trunk
                     2        2 pburba       trunk\colors
    

    So when you copy 'trunk' to 'branches/a', you are actually copying trunk@1 and trunk/colors@2:

    >svn copy trunk branches\a
    A         branches\a
    
    >svn st -v
                     0        0  ?           .
                     1        1 pburba       branches
    A  +             -        1 pburba       branches\a
    A  +             -        2 pburba       branches\a\colors
                     1        1 pburba       trunk
                     2        2 pburba       trunk\colors
    
    >svn ci -m "Copy mixed-rev trunk"
    Adding         branches\a
    Adding         branches\a\colors
    
    Committed revision 3.
    

    We can see this most clearly when looking at the verbose log for r3:

    >svn log -v -r3
    ------------------------------------------------------------------------
    r3 | pburba | 2013-03-11 15:31:23 -0400 (Mon, 11 Mar 2013) | 1 line
    Changed paths:
       A /branches/a (from /trunk:1)
       A /branches/a/colors (from /trunk/colors:2)
    
    Copy mixed-rev trunk
    ------------------------------------------------------------------------
    

    Skipping ahead to the problematic merge, we have a "clean" working copy with no mixed-revisions and no local modifications. So far so good:

    >svn st -v
                     5        5 pburba       .
                     5        5 pburba       branches
                     5        5 pburba       branches\a
                     5        5 pburba       branches\a\colors
                     5        4 pburba       trunk
                     5        4 pburba       trunk\colors
    

    But if we use the svn mergeinfo command to preview what revisions will be merged we notice that revsion 2 is eligible:

    >svn mergeinfo --show-revs eligible ^/trunk branches\a
    r2
    r4
    

    But wait, revison 2 is the addition of 'colors',

    >svn log -v -r2
    ------------------------------------------------------------------------
    r2 | pburba | 2013-03-11 15:43:52 -0400 (Mon, 11 Mar 2013) | 1 line
    Changed paths:
       A /trunk/colors
    
    created trunk/colors with red inside
    ------------------------------------------------------------------------
    

    and we already copied that when we created the branch in revision 3! So why is Subversion trying to merge it again? The reason comes back to the mixed-revision WC-to-WC copy we made. The merge target 'branch/a' knows that it was copied from trunk@1, prior to the addition of 'trunk/colors'. So the merge thinks that revision 2 hasn't been been merged to branch/a and tries to merge this change, adding 'colors' into a 'a' where a file of the same name already exists, which results in a tree conflict:

    >svn merge ^/trunk branches\a
    --- Merging r2 through r5 into 'branches\a':
       C branches\a\colors
    --- Recording mergeinfo for merge of r2 through r5 into 'branches\a':
     U   branches\a
    Summary of conflicts:
      Tree conflicts: 1
    
    >svn st
     M      branches\a
          C branches\a\colors
          >   local file obstruction, incoming file add upon merge
    Summary of conflicts:
      Tree conflicts: 1
    

    So we tricked Subversion with our mixed-revision WC-to-WC copy, which effectively brought revision 2 to the branch during the copy. So why can't Subversion detect this and skip revision 2? In this case we conceivably could do that, but what if revision 2 included other changes to 'trunk'? e.g.:

    >svn log -v -r2
    ------------------------------------------------------------------------
    r2 | pburba | 2013-03-11 15:43:52 -0400 (Mon, 11 Mar 2013) | 1 line
    Changed paths:
       A /trunk/colors
       A /trunk/README
       M /trunk
    
    created trunk/colors with red inside, add a README file, and set the
    svn:ignore property on trunk
    ------------------------------------------------------------------------
    

    Subversion doesn't support merging part of a revision to a given path, it's either all-or-nothing.

    ~~~~~

    So how to avoid this problem? As you've already discovered, doing a URL-to-URL copy solves it. Why? Because when the copy source is a URL, it is, by definition, at some uniform revision:

    >svn log -v -r3
    ------------------------------------------------------------------------
    r3 | pburba | 2013-03-11 16:02:59 -0400 (Mon, 11 Mar 2013) | 1 line
    Changed paths:
       A /branches/a (from /trunk:2)
    
    Create branch 'a' from 'trunk' with a URL-to-URL copy.
    ------------------------------------------------------------------------
    

    However you can also accomplish the same thing with a URL-to-WC copy:

     svn commit trunk -m 'created trunk/colors with red inside'
    -svn copy trunk branches/a
    +svn copy ^/trunk branches/a
     svn commit branches/a -m 'created branches/a'
    

    Or simply by updating the WC prior to the WC-to-WC copy in your original script:

     svn commit trunk -m 'created trunk/colors with red inside'
    +svn update
     svn copy trunk branches/a
     svn commit branches/a -m 'created branches/a'
    

    Your original solution, the URL-to-URL copy followed by an update, is the best option IMO. The update & WC-to-WC copy and the URL-to-WC copy both allow for the possibility that additional changes can be made to the copy before it is committed -- it's best that the revision in which a branch is created has no other changes besides the copy. That said, all of these option will work fine[2].

    [1] In Subversion-speak we usually refer to this as a working-copy-to-working-copy copy, or WC-to-WC copy for short. Contrast this with URL-to-URL, URL-to-WC, or WC-to-URL copies. WC-to-URL copies are also vulnerable to the problem described above. See also 'svn copy --help'.

    [2] Of course even using one of these three options still results in a text conflict, but that is expected since we made incompatible changes to the 'colors' file on both trunk and the branch after the branch was created.

    >svn merge ^/trunk branches\a
    --- Merging r3 through r5 into 'branches\a':
    C    branches\a\colors
    --- Recording mergeinfo for merge of r3 through r5 into 'branches\a':
     U   branches\a
    Summary of conflicts:
      Text conflicts: 1
    Conflict discovered in file 'branches\a\colors'.
    Select: (p) postpone, (df) diff-full, (e) edit, (m) merge,
            (mc) mine-conflict, (tc) theirs-conflict, (s) show all options: p
    
    >svn st
     M      branches\a
    C       branches\a\colors
    ?       branches\a\colors.merge-left.r2
    ?       branches\a\colors.merge-right.r5
    ?       branches\a\colors.working
    Summary of conflicts:
      Text conflicts: 1