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

Re: tree conflicts from user's point of view

From: Neels J. Hofmeyr <neels_at_elego.de>
Date: Sun, 16 Nov 2008 03:30:56 +0100

Stefan Sperling wrote:
> On Fri, Nov 14, 2008 at 01:43:24PM +0000, Stefan Sperling wrote:
>> Hi,
>>
>> I took the time to run the tree-conflict scripts I've created for my
>> thesis (https://www.inf.fu-berlin.de/w/SE/ThesisTreeConflicts)
>> against current Subversion trunk (r34187).
[...]
> In case you've missed the previous mail in this thread, the scripts
> used to create the example tree conflict scenarios used below are
> inside this tarball:
> https://www.mi.fu-berlin.de/wiki/pub/SE/ThesisTreeConflicts/thesis-tree-conflicts.tar.gz
>
> === svn-files-copy2-created
>
> In this case, we are merging from trunk into a branch,
> and are confronted with the following tree conflict:
>
> $ svn status
> M .
> A C alpha.copied
> $ svn info alpha.copied
> [...]
> Tree conflict:
> The merge attempted to add 'alpha.copied'.
> This action was obstructed by an item in the working copy.
> $
>
> By the way, I don't like having to run "svn info <victim>" here,
> and there's no indication from svn to the user that this is what
> she should be doing. "svn info" just does not feel like a tool to
> query conflict-type information, since it has historically never
> been used for this purpose, and it prints lots of irrelevant
> information that I've omitted from the transcript above.
>
> I'd rather type something like 'svn status --human-readable'
> with an alias for that which goes 'svn status -h' in order
> to get at the human readable conflict description.
>
> Also, we should continue this thread and enhance 'svn status'
> to summarise the nature of the tree conflict, so that advanced
> users can skip the human readable messages altogether:
> http://subversion.tigris.org/servlets/ReadMsg?listName=dev&msgNo=144323

+1!

>
> Now, let's try to get rid of this conflict. So the problem is
> that the merge tried to copy a file to a location where I've
> locally created a file. Let's assume I'm fine with my file
> being called something else, and as a first step towards
> resolving the conflict, I move my file out of the way:
>
> $ svn mv alpha.copied alpha.copied2
> A alpha.copied2
> D alpha.copied
> $
>
> The tree conflict is still recorded:
> $ svn st
> M .
> ! C alpha.copied
> A alpha.copied2
> $
>
> The commit succeeds nonetheless:
>
> $ svn commit -m "commit anyway"
> Sending . <---(mergeinfo)
> Adding alpha.copied2
> Transmitting file data .
> Committed revision 4.
> $
>
> Huh... Do we want to allow this?

We are allowing commits of *siblings* of tree-conflicts.
Technically, alpha.copied2 has nothing to do with the tree-conflict.

But why doesn't this print an error on the conflicted item? Probably because
a commit is skipping missing items.

Then again, a commit would bail out completely if failing on one of its
targets. So it's a bug. Don't skip tree-conflicted missing items upon commit.

>
> The conflict is still recorded ...
>
> $ svn st
> ! C alpha.copied
> $
>
> ... but the mergeinfo got committed before the conflict was resolved.

ohje...

>
> OK, so let's try to merge the copy again anyway. I'm a smart user so
> I realise the mergeinfo which is now on my top-level branch directory
> will prevent me from merging the copy (which my branch does not have yet)
> into my branch again:
>
> $ svn mergeinfo --show-revs=eligible ^/trunk
> $
>
> So let's first do a record-only reverse-merge (i.e. this is the part
> where your typical "I never use branches anyway" user is gonna run away
> screaming). We check the log (hey wasn't merging supposed to be
> automatic?) and find out that the copy was made in r3, so we run:
>
> $ svn merge --record-only -r3:2 ^/trunk
> $ svn diff
>
> Property changes on: .
> ___________________________________________________________________
> Modified: svn:mergeinfo
> Reverse-merged /trunk:r3
>
> $
>
> OK, now we can merge the copy again, there's nothing in the way:
>
> $ svn merge ^/trunk
> --- Merging r3 through r4 into '.':
> A alpha.copied
> $ svn status
> M .
> A + C alpha.copied
> $
> $ svn resolved alpha.copied
> Resolved conflicted state of 'alpha.copied'
> $ svn status
> M .
> A + alpha.copied
>
> So now I have both new files at different paths, which is what I wanted.
> I can commit the desired merge result:
>
> $ ls
> alpha alpha.copied alpha.copied2 beta epsilon/ gamma
> $ svn ci -m "resolved tree conflict on alpha.copied"
> Sending .
> Adding alpha.copied
>
> Committed revision 5.

So far so good, but fixing the commit to notice the tree-conflict would cut
the detour from the middle and make this a lot smoother.

>
>
> === svn-files-destroy-modified
>
> This is a popular case: Someone on trunk removed a file
> that I've modified on the branch:
>
> $ svn status
> M .
> C alpha
> $ svn info alpha
> [...]
> Tree conflict:
> The merge attempted to delete 'alpha'
> (possibly as part of a rename operation).
> Either you have edited 'alpha' locally, or it has been edited in the
> history of the branch you are merging into, but those edits are not
> present on the branch you are merging from.

Er, shouldn't that be: ", but this node has been deleted in the branch you
are merging from" ?

>
> ^ The indentation seems off here. (Oh, in fact, it's off in other cases, too.)
>
> First gut reaction: Is my file still there?
>
> $ cat alpha
> alpha, but modified

phew.

>
> OK, it is. Now, I don't know yet who was trying to remove the file
> and why, but I'm going to find out. Actually, I have to go find out
> because the file may have been moved somewhere else! So I go browse
> the log, and find out the file has indeed been deleted.
>
> After some discussion with other developers we decide to keep the
> file in place on the branch. So all I have to do is this:
>
> $ svn resolve --accept=mine-full alpha
> Resolved conflicted state of 'alpha'
> $ svn status
> M .
> $
>
> Great, issue settled.
>
> $ svn ci -m "sync branch with trunk"
> Sending .
> subversion/libsvn_client/commit.c:860: (apr_err=160028)
> svn: Commit failed (details follow):
> subversion/libsvn_repos/commit.c:124: (apr_err=160028)
> svn: Directory '/branch' is out of date
>
> Huh? Why that?

Hm, resolve probably fails to step up some revision number.

>
> $ svn up
> At revision 4.
> $ svn ci -m "sync branch with trunk"
> Sending .
>
> Committed revision 5.
> $
>
> As a user, I don't really grok why the commit failed the first time
> around since the update apparently did nothing, but I'm happy to know
> that my data made it into the repository the second time. I go home
> from work and hope svn won't play such weird tricks on me again tomorrow.
> Maybe I'm just seeing things...
>
> As a developer, I'm asking myself whether this is related to us
> not updating text-bases in some cases? If so, shouldn't the UI
> indicate that this has happened?

I have no idea, but I would expect `resolve' to take care of everything for me.

>
>
> === svn-files-move1-moved
>
> This is an all-time favourite and actually one of the most likely
> tree conflict cases. Two teams do refactoring and happen to end
> up doing divergent renames of the same file:
>
> $ svn status
> M .
> A + alpha.moved1
> A + alpha.moved2
> D C alpha
> $ svn info alpha
> [...]
> Tree conflict:
> The merge attempted to delete 'alpha'
> (possibly as part of a rename operation).
> You have deleted 'alpha' locally.
> Maybe you renamed it?
>
> Looking at the logs and status output, I figure out what happened.
> I escalate this high-priority problem to the head of department,
> who decides to hold a developer meeting instantly in order to settle
> the dispute. After much bikeshedding and coffee I finally give up
> and we "agree" on the other team's stupid filename, alpha.moved1.

heh

>
> So I diff the files -- renames often go along with internal changes,
> but luckily in this case, the files remained the same.
>
> $ svn diff alpha.moved1 alpha.moved2
> $ svn rm alpha.moved2
> subversion/svn/util.c:864: (apr_err=195006)
> svn: Use --force to override this restriction
> subversion/libsvn_client/delete.c:65: (apr_err=195006)
> svn: 'alpha.moved2' has local modifications
> $ svn diff alpha.moved2
> $

Weird. Might that again be that alpha.moved2 is still considered to be at an
earlier revision?

>
> Huh? I never modified the file locally as far as I know,
> but let's try using the force:
>
> $ svn rm --force alpha.moved2
> D alpha.moved2
> $
>
> OK, seems to have worked. Now let's resolve:
>
> $ svn resolve --accept=working alpha
> Resolved conflicted state of 'alpha'
> $ svn status
> M .
> A + alpha.moved1
> D alpha
> $
>
> That was it!
>
> The above is a bit unrealistic. In real life, renaming files and
> file modifications often must go together (e.g. when refactoring
> Java classes, or when changing references to things in other
> files which were also renamed), so people will want to merge
> the files textually.
>
> Let's repeat, and patch the scenario script so that we have textual
> modifications in both renamed files:
>
> --- svn-files-move1-moved.sh.orig Fri Nov 14 17:40:19 2008
> +++ svn-files-move1-moved.sh Fri Nov 14 17:41:05 2008
> @@ -5,10 +5,14 @@
>
> # Move a file in trunk
> svn move $trunk/alpha $trunk/alpha.moved1
> +# Do a text mod:
> +echo "alpha.moved1" > $trunk/alpha.moved1
> svn commit -m "moved alpha" $trunk
>
> # Move the same file to a different path in the branch
> svn move $branch/alpha $branch/alpha.moved2
> +# Do a text mod:
> +echo "alpha.moved2" > $branch/alpha.moved2
> # Committing here does not affect the merge result
>
> # Merge the rename into the branch
>
> $ svn status
> M .
> A + alpha.moved1
> A + alpha.moved2
> D C alpha
> $ svn info alpha
> [...]
> Tree conflict:
> The merge attempted to delete 'alpha'
> (possibly as part of a rename operation).
> You have deleted 'alpha' locally.
> Maybe you renamed it?
>
> So this time, the textual modifications add to the problem:
>
> $ diff -u alpha.moved1 alpha.moved2
> --- alpha.moved1 Fri Nov 14 17:46:18 2008
> +++ alpha.moved2 Fri Nov 14 17:46:18 2008
> @@ -1 +1 @@
> -alpha.moved1
> +alpha.moved2
>
> Now, as a naive user, I try the following trick: I revert the merge,
> rename the file to match the name on trunk, re-apply my textual
> modifications (they've not yet been committed either as I made them
> in the same go as the rename), and merge again. That should give me
> a text conflict on the file, right?
>
> We save the local textmods in /tmp/alpha1.diff, conveniently piping
> "svn diff" output through sed to fix up the destination path in the diff:
>
> $ svn diff alpha.moved1 alpha.moved2 \
> | sed -e 's/^\(+++ alpha.\)moved2/\1moved1/' > /tmp/alpha.moved1.diff
> $ cat /tmp/alpha.moved1.diff
> Index: alpha.moved2
> ===================================================================
> --- alpha.moved2 (revision 2)
> +++ alpha.moved1 (working copy)
> @@ -1 +1 @@
> -alpha
> +alpha.moved2
>
> Now, on to the remaining steps:
>
> $ svn revert -R .
> Reverted '.'
> Reverted 'alpha.moved1'
> Reverted 'alpha.moved2'
> Reverted 'alpha'
> $ svn status
> ? alpha.moved1
> ? alpha.moved2
> $ rm alpha.moved[12]
> $ svn status

It would yield simpler results to merge in trunk at this point and apply the
patch afterwards [1]. But let's see what happens.

> $ svn mv alpha alpha.moved1
> A alpha.moved1
> D alpha
> $ patch -p0 < /tmp/alpha.moved1.diff
> Hmm... Looks like a unified diff to me...
> The text leading up to this was:
> --------------------------
> |Index: alpha.moved2
> |===================================================================
> |--- alpha.moved2 (revision 2)
> |+++ alpha.moved1 (working copy)
> --------------------------
> Patching file alpha.moved1 using Plan A...
> Hunk #1 succeeded at 1.
> done
> $ svn diff
> Index: alpha.moved1
> ===================================================================
> --- alpha.moved1 (revision 2)
> +++ alpha.moved1 (working copy)
> @@ -1 +1 @@
> -alpha
> +alpha.moved2

The thing here is that in the case of a Java rename, if the filename is now
alpha.moved1, the class name would now also have to be alpha.moved1 and not
*2. Nevertheless, it's better to play through conflicting changes.

> Index: alpha
> ===================================================================
> --- alpha (revision 2)
> +++ alpha (working copy)
> @@ -1 +0,0 @@
> -alpha
> $ svn merge ^/trunk
> --- Merging r2 through r3 into '.':
> C alpha.moved1
> --- Merging r2 through r3 into 'alpha':
> C alpha
> Summary of conflicts:
> Tree conflicts: 2
>
> Hell's bells, the tree conflicts just multiplied!
>
> $ svn status
> M .
> ? alpha.moved1.orig
> A + C alpha.moved1
> D C alpha
>
> $ svn info alpha.moved1
> [...]
> Tree conflict:
> The merge attempted to add 'alpha.moved1'.
> This action was obstructed by an item in the working copy.
> $ svn info alpha
> [...]
> Tree conflict:
> The merge attempted to delete 'alpha'
> (possibly as part of a rename operation).
> You have deleted 'alpha' locally.
> Maybe you renamed it?

Hm, that may seem pretty dumb to users out there. But not doing this would
abandon the D->D and A->A tree-conflicts. Even with proper move information,
this would still have to be a Move->Move tree-conflict.

>
> So there seems to be no way to make subversion treat the
> scenario as a text conflict. Let's try a different route,
> make a backup of our file, remove it from version control,
> and re-run the merge.

How about just running `resolved' on the two conflicts instead and mending
the situation manually? [2]

>
> $ svn revert -R .
> Reverted '.'
> Reverted 'alpha.moved1'
> Reverted 'alpha'
> $ svn status
> ? alpha.moved1.orig

Why doesn't alpha.moved1 show up here? Didn't we revert everything? It
*said* "Reverted 'alpha.moved1', and according to `ls' below, it is still
around.

> $ cat alpha.moved1.orig
> alpha
> $ rm alpha.moved1.orig
> $ ls
> alpha.moved1 beta epsilon/ gamma/
> $ cat alpha.moved1
> alpha.moved2
> $ cat /tmp/alpha.moved2.diff
> Index: alpha.moved2
> ===================================================================
> --- alpha.moved2 (revision 2)
> +++ alpha.moved2 (working copy)
> @@ -1 +1 @@
> -alpha
> +alpha.moved2
> $ cp alpha.moved1 alpha.moved1.mine
> $ svn rm alpha.moved1
> D alpha.moved1
> $ svn merge ^/trunk
> --- Merging r2 through r4 into 'alpha.moved1':
> A alpha.moved1
> --- Merging r2 through r4 into '.':
> C alpha
> Summary of conflicts:
> Tree conflicts: 1
> $ ls
> alpha.moved1 alpha.moved1.mine beta epsilon/ gamma/

This is a little more complex than it needs to be. But it nevertheless is a
situation people may end up with.

>
> Now we manually merge alpha.moved1 and alpha.moved1.mine, which for
> sake of argument, results in:
>
> $ cat alpha.moved1
> alpha.moved1
> alpha.moved2
>
> We're now left with a single tree conflict on alpha,
> which we can safely mark as resolved:
>
> $ rm alpha.moved1.mine
> $ ls
> alpha.moved1 beta epsilon/ gamma/
> $ svn status
> M .
> ! C alpha
> R + alpha.moved1
> $ svn resolved alpha
> Resolved conflicted state of 'alpha'
> $ svn status
> M .
> R + alpha.moved1
>
> The alpha.moved1 on the branch has now been replaced with
> its counterpart on trunk, and it includes local changes
> made to it on the branch.
>
> Before we can commit, the spurious out-of-date problem
> comes up again:
>
> $ svn commit -m "synced branch with trunk"
> Sending .
> subversion/libsvn_client/commit.c:860: (apr_err=160028)
> svn: Commit failed (details follow):
> subversion/libsvn_repos/commit.c:124: (apr_err=160028)
> svn: Directory '/branch' is out of date
> $ svn update
> At revision 4.
> $ svn commit -m "synced branch with trunk"
> Sending .
> Replacing alpha.moved1
> Transmitting file data .
> Committed revision 5.

Well, I don't get why this would be a "Replace". Actually, it should just be
a "merge trunk to my branch and apply text changes that I've stored away
before". This related to my question above about alpha.moved1 not being
reverted although the output stated that it was.

[1], [2]: Would you mind playing through these two variants as well?

>
>
> That's it for now. I might look into more cases another time.

Thanks! It's great to see stuff like this. These cases should become new
cmdline tests once the behaviour is settled.
~Neels

>
> It seems like handling tree conflicts is not going to be easy for users.
> In any case, resolving these conflicts requires quite a bit of patience
> and skill from the user. Given the complexity of the problem, this does
> not surprise me. I hope, however, that we can make further improvements
> to the UI to make the resolution process a bit more straightforward,
> if possible.
>
> Comments, ideas?
>
> Thanks,
> Stefan
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: dev-unsubscribe_at_subversion.tigris.org
> For additional commands, e-mail: dev-help_at_subversion.tigris.org
>

Received on 2008-11-16 03:31:25 CET

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.