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

tree conflicts from user's point of view (was: State of tree conflict detection in trunk (for files))

From: Stefan Sperling <stsp_at_elego.de>
Date: Fri, 14 Nov 2008 18:52:57 +0000

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).
>
> These scripts provoke many different tree conflicts scenarios.
> For the thesis I ran them with Subversion 1.5, and not a single
> tree conflict was detected.
>
> The results for current trunk however are most pleasing. Note that
> I've only tested for files (since directories are, AFAIK, not the
> main focus for 1.6).
>
> I've noticed a few minor issues, see comments inline marked XXX.
>
> I have not yet tested behaviour of subsequent update, revert, resolve,
> and commit operations, but I am planning to do so later today and post
> my results here.

Right, so I've played around with this a bit, in order to get a feel
what the current user experience is like, and what kind of background
knowledge and skills our current UI is expecting from users to be able
to deal with tree conflicts.

I have not gotten around to try all cases I have scripts for, but I
hope what I've written below will be helpful anyway.

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

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?

The conflict is still recorded ...

$ svn st
! C alpha.copied
$

... but the mergeinfo got committed before the conflict was resolved.

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.

=== 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.

^ 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

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?

$ 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?

=== 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.

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
$

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
$ 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
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?

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.

$ svn revert -R .
Reverted '.'
Reverted 'alpha.moved1'
Reverted 'alpha'
$ svn status
? alpha.moved1.orig
$ 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/

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.

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

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-14 19:53:19 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.