[svn.haxx.se] · SVN Dev · SVN Users · SVN Org · TSVN Dev · TSVN Users · Subclipse Dev · Subclipse Users · this month's index

Re: unexpected tree conflict on merge for same source file

From: Stefan Sperling <stsp_at_elego.de>
Date: Tue, 16 Oct 2012 18:02:55 +0200

On Tue, Oct 16, 2012 at 01:17:40PM +0200, Sven Uhlig wrote:
> Below is a .bat file for creating the repo with some more conflicts that
> we do not understand. I would be happy if someone could explain these as
> well.

Please see my remarks below.

> @REM testsvn.bat
>
> cls
>
> svn --version
> @REM svn, Version 1.7.2 (r1207936)
> @pause
> svnadmin --version
> @REM svnadmin, Version 1.7.2 (r1207936)
> @pause
> D:
> cd \
> mkdir test
> cd test
>
> svnadmin create repo
> svn checkout file:///D:/test/repo wc
>
> cd wc
> mkdir branches
> mkdir tags
> mkdir trunk
> svn add branches tags trunk
> svn commit -m "basic repo structure"
> svn switch --ignore-ancestry file:///D:/test/repo/trunk
>
> svn info
> @REM Working Copy Root Path: D:\test\wc
> @REM URL: file:///D:/test/repo/trunk
> @pause
>
> echo .>first.txt
> svn add first.txt
> svn commit -m "1st"
>
> svn copy -m "prj0" . "^/branches/prj0"

You're copying a mixed-revision working copy here.
Is that really what you want?
  ^/branches/prj0 is a copy of ^/trunk_at_1
  ^/branches/prj0/first.txt is a copy of ^/trunk/first.txt_at_2.
See http://svnbook.red-bean.com/en/1.7/svn.basic.in-action.html#svn.basic.in-action.mixedrevs

Usually, you'd copy URLs to create branches, as you do on the
next line in the script:

> svn copy -r HEAD -m "prj1" "^/trunk" "^/branches/prj1"
> svn switch "^/branches/prj1"

The switch, like an update, results in a single-rev working copy.

> echo .>second.txt
> svn add second.txt
> svn commit -m "2nd"
>
> svn switch "^/trunk"
> svn copy -r HEAD -m "testing" "^/trunk" "^/branches/testing"
>
> echo "change" > first.txt
> svn commit -m "change"
> svn copy -r HEAD -m "prj2" "^/trunk" "^/branches/prj2"
>
> echo "change2" > first.txt
> svn commit -m "change2"
> svn copy -m "prj3" . "^/branches/prj3"

Mixed-rev copy again. This time ^/branches/prj3 is a copy of ^/trunk_at_9,
and ^/branches/prj3/first.txt is a copy of ^/trunk/first.txt_at_8.

(I hope I'm getting these numbers right. I'm not actually running the script.)

> svn switch "^/branches/prj2"
> svn merge "^/branches/prj1"

prj1 is a branch off trunk, and so is prj2.
Which means prj1 and prj2 are not ancestrally related.

The merge you are running is equivalent to:

  svn merge ^/branches/prj1_at_4 ^/branches/prj1_at_HEAD .

The common ancestor of prj1 and prj2 is trunk_at_3. Because this is younger
than r2, which added first.txt, you do not get an 'incoming add vs. local
add' tree conflict on first.txt. So this merge happens to work as you expect.

Ideally, you'd reintegrate prj1 into trunk first and then merge the
changes into prj2 from trunk, using a sequence such as:

  svn switch ^/trunk
  svn merge --reintegrate ^/branches/prj1
  svn commit
  svn switch ^/branches/prj2
  svn merge ^/trunk

> svn commit -m "prj1 into prj2"
>
> svn switch "^/branches/prj3"
> svn merge "^/branches/prj0"

Here you run into a tree conflict because the common ancestor is trunk_at_1,
which /branches/prj0 was copied from. Because you created prj0 as a
mixed-rev copy, first.txt has a different common ancestor: first.txt_at_2.

Subversion merges all eligible changes made between ^/trunk_at_1 and
^/branches/prj0_at_HEAD. This changeset includes the addition of first.txt,
which the merge attempts to apply to the merge target prj3. There is
already a node called first.txt in prj3, so you get a tree-conflict:

> @REM C first.txt
> @REM --- Recording mergeinfo for merge of r2 through r11 into '.':
> @REM U .
> @pause
> svn st
> @REM M .
> @REM C first.txt
> @REM > local add, incoming add upon merge
> @pause
> svn revert -R .
>
> svn switch "^/branches/prj1"
> svn merge "^/trunk"
> svn commit -m "prj1 sync"

What is the purpose of this sync merge?

> svn switch "^/branches/testing"
> svn merge "^/branches/prj1"
> svn commit -m "prj1 into testing"
>
> svn merge "^/branches/prj2"

Again, you hit a similar problem here.

> @REM --- Recording mergeinfo for merge of r2 through r7 into '.':
> @REM U .
> @REM -- Zusammenfuehren von r8 bis r13 in '.':
> @REM C second.txt
> @REM G .
> @REM --- Recording mergeinfo for merge of r8 through r13 into '.':
> @REM U .
> @pause
> svn st
> @REM M .
> @REM C second.txt
> @REM > local add, incoming add upon merge
> @pause
> svn revert -R .
>
> cd \

What is the purpose of the separate 'testing' branch? Why are you not
testing prj1 directly, reintegrate it into trunk when tests succeed,
and then sync all your other branches to trunk to merge prj1 changes
into them? If you did that, your approach of using 'svn merge ^/some-branch'
would work fine. But make sure to use the --reintegrate option whenever
you merge one of your branches back into trunk (fortunately, the requirement
to use --reintegrate will go away in Subversion 1.8, but for now you must
still use it).

Your troubles come from merging between ancestrally unrelated branches
and (probably to a lesser extent) from creating branches from mixed-revision
working copies. And that you are applying of the 'svn merge ^/foo' short-hand
notation to situations it wasn't designed for.

See the output of 'svn help merge' for more details. It has some diagrams
to illustrate which 'svn merge' invocation is appropriate for a given
merge scenario.

If you really must merge between unrelated branches, use the 2-URL
merge syntax instead of the 'svn merge ^/foo' short-hand syntax and
specify revisions for each of the 2 URL arguments to merge meaningful
changesets. For instance, say you created '^/branches/A' from trunk in
r399, and then made some interesting changes between r410 and r450 on
branch A. You want to merge those changes into a completely unrelated
branch called 'testing'. Try something like this:

  svn switch ^/branches/testing
  svn merge ^/branches/A_at_410 ^/branches/prj_at_450 .

In some cases you might also have to use the --ignore-ancestry option to
prevent Subversion from tracing history backwards to find a common ancestor.
Note that this option prevents mergeinfo from being recorded, so I wouldn't
recommend this approach if you can avoid it.
Received on 2012-10-16 18:03:31 CEST

This is an archived mail posted to the Subversion Users mailing list.

This site is subject to the Apache Privacy Policy and the Apache Public Forum Archive Policy.