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

Re: Merge and reverse merge dont seem to cancel out

From: Paul Burba <ptburba_at_gmail.com>
Date: Thu, 24 Jul 2008 11:57:17 -0400

On Tue, Jul 22, 2008 at 12:16 PM, Karl Fogel <kfogel_at_red-bean.com> wrote:
> JetMark <extrapic_at_extrapic.com> writes:
>> If I do
>> svn merge -r12:13 repository/branch
>> A f1
>> A f2
>> A f3
>>
>> I get the addition of 3 files - exactly what I expected in the
>> circumstances.
>>
>> but if I now do
>>
>> svn merge -r13:12 repository/branch
>> I get
>> Skipped 'f1'
>> Skipped 'f2'
>> Skipped 'f3'
>>
>> (And the files are not removed in the working copy.)
>> It seems that the unmerge does not manage to undo a merge. What else do you
>> have to do to ensure that this happens ?
>>
>> Can anyone say what I am doing wrong?
>
> I'm surprised -- I would also think this should work the way you expect.

Not sure if I'm surprised or not, but Subversion has skipped the
attempted deletion of paths schedule for addition since 1.4.0 (and
probably always but I didn't check back any further). This behavior
makes some sense since you may have modified those added files after
the first merge added them and if the reverse merge simply deleted
them you would lose your work. The immediate solution is to use the
the --force option when doing the reverse merge, it will delete f1,
f2, f3.

This is also a good time to point out that if the first merge was
*committed* and then the subsequent reverse merge would work (though
in 1.5 the working copy would nee to be up to date as explained
further on). I suspect that is why this hasn't been brought up much
in the past, since most people would probably just do a revert of the
local changes the first merge made, rather than trying to reverse
merge them away.

Now as to Karl's script, comments below:

> I've written a script to demonstrate this bug along with a few more
> surprising things:
>
> --------------------------------------------------------------------------
> #!/bin/sh
>
> # The next line is the only line you should need to adjust.
> SVNDIR=/home/kfogel/src/subversion
>
> SVN=${SVNDIR}/subversion/svn/svn
> SVNSERVE=${SVNDIR}/subversion/svnserve/svnserve
> SVNADMIN=${SVNDIR}/subversion/svnadmin/svnadmin
>
> # Select an access method. If svn://, the svnserve setup is
> # handled automagically by this script; but if http://, then
> # you'll have to configure it yourself first.
> #
> # URL=http://localhost/SOMETHING/repos
> # URL=svn://localhost/repos
> URL=file:///`pwd`/repos
>
> rm -rf repos wc trunk-wc branch-wc import-me
>
> ${SVNADMIN} create repos
>
> # These are for svnserve only.
> echo "[general]" > repos/conf/svnserve.conf
> echo "anon-access = write" >> repos/conf/svnserve.conf
> echo "auth-access = write" >> repos/conf/svnserve.conf
>
> # The server will only be contacted if $URL is svn://foo, of course.
> ${SVNSERVE} --pid-file svnserve-pid -d -r `pwd`
> # And put the kill command in a file, in case need to run it manually.
> echo "kill -9 `cat svnserve-pid`" > k
> chmod a+rwx k
>
> echo "### Making a Greek Tree for import..."
> mkdir import-me
> mkdir import-me/trunk
> mkdir import-me/tags
> mkdir import-me/branches
> mkdir import-me/trunk/A
> mkdir import-me/trunk/A/B/
> mkdir import-me/trunk/A/C/
> mkdir import-me/trunk/A/D/
> mkdir import-me/trunk/A/B/E/
> mkdir import-me/trunk/A/B/F/
> mkdir import-me/trunk/A/D/G/
> mkdir import-me/trunk/A/D/H/
> echo "This is the file 'iota'." > import-me/trunk/iota
> echo "This is the file 'A/mu'." > import-me/trunk/A/mu
> echo "This is the file 'A/B/lambda'." > import-me/trunk/A/B/lambda
> echo "This is the file 'A/B/E/alpha'." > import-me/trunk/A/B/E/alpha
> echo "This is the file 'A/B/E/beta'." > import-me/trunk/A/B/E/beta
> echo "This is the file 'A/D/gamma'." > import-me/trunk/A/D/gamma
> echo "This is the file 'A/D/G/pi'." > import-me/trunk/A/D/G/pi
> echo "This is the file 'A/D/G/rho'." > import-me/trunk/A/D/G/rho
> echo "This is the file 'A/D/G/tau'." > import-me/trunk/A/D/G/tau
> echo "This is the file 'A/D/H/chi'." > import-me/trunk/A/D/H/chi
> echo "This is the file 'A/D/H/omega'." > import-me/trunk/A/D/H/omega
> echo "This is the file 'A/D/H/psi'." > import-me/trunk/A/D/H/psi
> echo "### Done."
> echo ""
> echo "### Importing it..."
> (cd import-me; ${SVN} import -q -m "Initial import." ${URL})
> echo "### Done."
> echo ""
>
> ${SVN} cp -q -m "Create a branch." ${URL}/trunk ${URL}/branches/mybranch
>
> ${SVN} co -q ${URL}/trunk trunk-wc
> ${SVN} co -q ${URL}/branches/mybranch branch-wc
>
> cd branch-wc
> echo "New file 1." > newfile1
> echo "New file 2." > newfile2
> echo "New file 3." > newfile3
> ${SVN} add -q newfile*
> ${SVN} ci -q -m "Add three new files in r3."
> cd ..
>
> cd trunk-wc
> echo "### Merge the branch change (three added files in r3) into trunk..."
> ${SVN} merge -r2:3 ${URL}/branches/mybranch
> echo ""

(Using the current 1.5.x branch in these examples r32291 - a.k.a. the
likely 1.5.1 release)

Ok, we do the first merge and it adds the expected three paths:

  1.5.1>svn.exe merge -r2:3 %URL%/branches/mybranch
  --- Merging r3 into '.':
  A newfile1
  A newfile2
  A newfile3

And with 1.5 it also adds mergeinfo on the merge target describing the merge:

  1.5.1>svn st
   M .
  A + newfile1
  A + newfile2
  A + newfile3

  1.5.1>svn diff -N

  Property changes on: .
  ___________________________________________________________________
  Added: svn:mergeinfo
     Merged /branches/mybranch:r3

Now we try the reverse merge, and as in < 1.5 the files scheduled for
addition are skipped:

  1.5.1>svn.exe merge -r3:2 %URL%/branches/mybranch
  Skipped 'newfile1'
  Skipped 'newfile2'
  Skipped 'newfile3'

> echo "### Now try to un-merge that not-yet-committed merge..."
> ${SVN} merge -r3:2 ${URL}/branches/mybranch
> echo ""

But in the 1.5 world we need to deal with mergeinfo. Notice that the
reverse merge removed the mergeinfo on the merge target. This agrees
with the decision we (the developer community) made to always record
mergeinfo for merges, operative or not.

  1.5.1>svn.exe merge -r3:2 %URL%/branches/mybranch
  Skipped 'newfile1'
  Skipped 'newfile2'
  Skipped 'newfile3'

> echo "### Hmmm, look how 'svn status' still shows the local adds:"
> ${SVN} status
> echo ""

Yup, but again this is not new behavior in 1.5.

  1.5.1>svn st
  A + newfile1
  A + newfile2
  A + newfile3

What has changed is that the three local adds now have explicit
mergeinfo. This is due to the merge logic which notes skipped child
paths when merging and sets explicit mergeinfo on them so they don't
end up inheriting the wrong mergeinfo from the merge target - IOW the
skipped subtree wasn't actually merged to, but would seem to if
allowed to inherit the mergeinfo from the merge target. Hopefully
that makes some sense. I admit that in this situation the behavior is
confusing. I'll look into a way to fix it, but svn:mergeinfo's
inheritable nature makes a lot of things that seem simple at first end
up not so simple...

  1.5.1>svn pl -vR
  Properties on 'newfile1':
    svn:mergeinfo :
  Properties on 'newfile2':
    svn:mergeinfo :
  Properties on 'newfile3':
    svn:mergeinfo :

> echo "### Hmmm. Okay, what if we commit the result of the merge..."
> ${SVN} ci -m "Merged r3 from mybranch to trunk."
> echo ""

Keep in mind that in 1.5 what we are effectively doing here is merging
-r2:3, removing the mergeinfo reflecting this (via the reverse merge),
and then committing anyway:

  1.5.1>svn ci -m "commit merge of -r2:3 although we removed the
mergeinfo reflecting this merge"
  Adding newfile1
  Adding newfile2
  Adding newfile3

  Committed revision 4.

> echo "### ...and then un-merge? Nope, still no effect (but no Skips either):"
> ${SVN} merge -r3:2 ${URL}/branches/mybranch
> echo ""

The reason this doesn't work in 1.5 is because merge tracking prevents
repeated merges. For forward merges this means that if mergeinfo
(explicit or inherited) describes a merge as having already been done,
then the merge isn't repeated. Due to a limitation of svn:mergeinfo
-- specifically that we have no way of describing reverse merges (see
issue http://subversion.tigris.org/issues/show_bug.cgi?id=2881) --
reverse merges are allowed only if the merge has already been done
(per the mergeinfo or the path's natural history). In this case we
are trying to reverse merge revision 3 which exists neither in
/branches/mybranch mergeinfo nor its natural history.

  1.5.1>svn.exe merge -r3:2 %URL%/branches/mybranch

  1.5.1>

One simple solution is to do the merge using the --ignore-ancestry
option, which effectively disregards mergeinfo (and doesn't update it
either) thus attempting to repeat the merge (which in this case is
exactly what we want):

  1.5.1>svn.exe merge -r3:2 %URL%/branches/mybranch --ignore-ancestry
  --- Reverse-merging r3 into '.':
  D newfile1
  D newfile2
  D newfile3

  1.5.1>svn st
  D newfile1
  D newfile2
  D newfile3

  1.5.1>svn pl -vR

  1.5.1>

> echo "### And 'svn status' shows that un-merging changed nothing:"
> ${SVN} status
> echo ""

> echo "### So what if we update the whole trunk working copy first..."
> ${SVN} up
> echo ""

Because mergeinfo is inheritable, it is always a good idea to merge
into a uniform revision working copy. Within a mixed-revision working
copy mergeinfo inheritance and elision are quite limited - see the
section "Mixed Revision Working Copies and Mergeinfo" in this article
http://www.collab.net/community/subversion/articles/merge-info.html
for more.

> echo "### ...and then try un-merging again? Nope, still no effect..."
> ${SVN} merge -r3:2 ${URL}/branches/mybranch
> echo ""

  1.5.1>svn.exe merge -r3:2 %URL%/branches/mybranch

  1.5.1>

> echo "### ...but now 'svn status' shows property mods:"
> ${SVN} status
> echo ""
> echo "### I'll bet those are svn:mergeinfo changes, right? Let's see:"
> ${SVN} diff
> echo ""

  1.5.1>svn diff

  Property changes on: newfile1
  ___________________________________________________________________
  Deleted: svn:mergeinfo

  Property changes on: newfile2
  ___________________________________________________________________
  Deleted: svn:mergeinfo

  Property changes on: newfile3
  ___________________________________________________________________
  Deleted: svn:mergeinfo

So the empty mergeinfo on the three files was removed. Why did this
happen now? As mentioned above mergeinfo inheritance and elision need
a uniform working copy revision to work fully. You updated your wc,
then tried the reverse merge again. The end of every merge away merge
tries to elide explicit mergeinfo up the working copy tree and then to
the repository if possible.

This time the merge tracking logic does the merge (which for reasons
already discussed is inoperative) then it sees that it has three
subtrees with explicit mergeinfo: newfile1, newfile2, newfile3.

Taking each newfileX in turn it sees that the file's explicit
mergeinfo is the special "empty" type of mergeinfo - see "Empty
Mergeinfo" in http://www.collab.net/community/subversion/articles/merge-info.html.

Looking at newfileX's working copy parent 'trunk-wc' it sees that this
path has no explicit mergeinfo. Then Subversion contacts the
repository asking essentially "Does this merginfo on
%URL%/trunk/newfile1 have any parent with explicit mergeinfo?" The
answer is no. Now in most cases it wouldn't elide explicit mergeinfo,
but empty mergeinfo is a special case since all it means is
"effectively nothing has been merged here".

Since /trunk/newfile1 doesn't inherit any mergeinfo it is safe to
elide the empty mergeinfo. Elision of course being a fancy word for
"delete", we see the explicit mergeinfo has been deleted when we diff
the wc.

> echo "### Yup. So let's get this straight: when we reverse-merged before"
> echo "### updating, no output was shown at merge time, and there was no effect"
> echo "### in the working copy. Then after we updated, reverse-merging still"
> echo "### showed no output, and still didn't remove the files as it should,"
> echo "### but it did affect their svn:mergeinfo properties."
> echo "###"
> echo "### This is so messed up."

I hope that helps explain what is going on a bit better.

To sum up:

Q: Want to reverse merge a uncommitted merge which added subtrees?
A: Just use svn merge --force or svn revert

Q: Want to reverse merge a committed merge that added subtrees?
A1: Pre-Subversion 1.5: Just do a normal reverse merge
A2: Subversion 1.5: As above, but make sure your working copy is at a
uniform revision

Paul

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe_at_subversion.tigris.org
For additional commands, e-mail: dev-help_at_subversion.tigris.org
Received on 2008-07-24 17:58:04 CEST

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

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