Most Subversion users are familiar with text conflicts. The classic case: You have a locally edited file in your working copy, an svn update brings a change to the same file from the repository, that incoming change cannot be merged cleanly into your local change, and the result is a text conflict. Subversion 1.6.0 expands this concept to cover conflicts at the directory level, e.g. you locally delete a file then an update tries to bring a text change down on that file. These new types of conflicts are called tree conflicts.
Let’s look at a simple example of what you can expect with tree conflicts, but first we’ll look at the “old” behavior under Subversion 1.5.6.
Like text conflicts, tree conflicts can occur during updates, switches, or merges (and technically you can get them on checkouts too). In this example we will look at a merge from our ‘trunk’ into our current working directory which is a working copy for a branch of trunk.
In our working copy we have a file with some text edits:
1.5.6> svn stM notesobliterateobliterate-functional-spec.txt
Checking what is available to be merged from trunk we find r36680:
1.5.6> svn mergeinfo --show-revs eligible %URL%/trunk .r36680
Consulting the log for r36680 we see that Barry renamed the very file that we have local edits on:
1.5.6> svn log -v -r36680 %URL%------------------------------------------------------------------------r36680 | Barry | 2009-03-19 11:21:25 -0400 (Thu, 19 Mar 2009) | 1 lineChanged paths:A /trunk/notes/obliterate/obliterate-func-spec.txt (from/trunk/notes/obliterate/obliterate-functional-spec.txt:36679)D /trunk/notes/obliterate/obliterate-functional-spec.txt
Just making a file name a bit shorter------------------------------------------------------------------------
Recall that Subversion performs renames as a combination of a copy and a delete. When we merge r36680 the addition half of the rename occurs, but the deletion half does not because the file to be deleted has local modifications and Subversion generally endeavors to not remove unversioned modifications:
1.5.6> svn merge %URL%/trunk . -c36680--- Merging r36680 into '.':A notesobliterateobliterate-func-spec.txtSkipped 'notesobliterateobliterate-functional-spec.txt'
After the merge we are left with two versioned copies of our spec file, the one added by Barry as part of the rename and our original local edit. We could commit this as-is, but it is quite unlikely this is what we really want. Getting the correct result in this case, namely our text edits in the renamed file, requires jumping through a few hoops due to Subversion’s method of treating renames as separate copy and delete actions:
First we need to copy our modified file onto its new location with an OS copy:
1.5.6> copy notesobliterateobliterate-functional-spec.txt notesobliterateobliterate-func-spec.txtOverwrite notesobliterateobliterate-func-spec.txt? (Yes/No/All): y 1 file(s) copied.
Then use Subversion to revert our original locally modified file:
1.5.6> svn revert notesobliterateobliterate-functional-spec.txtReverted 'notesobliterateobliterate-functional-spec.txt'
Which now allows us to delete it:
Alternatively you could simply use the –force option with delete and combine the first two steps. IMHO that is a bad habit though, as it is a good way to wipe out all your local changes when you don’t mean to. Recall how I said Subversion endeavors preserve unversioned local modifications? svn delete –force is one of the cases outside of the revert subcommand that does just that, so use with caution.
1.5.6> svn del notesobliterateobliterate-functional-spec.txtD notesobliterateobliterate-functional-spec.txt
Leaving us with what we likely want, ‘notesobliterateobliterate-func-spec.txt’ added but with our original local changes.
1.5.6> svn st M .D notesobliterateobliterate-functional-spec.txtA + notesobliterateobliterate-func-spec.txt
Now in the above example it is fairly obvious what happened. But what if we were merging in hundreds of revisions and changing hundreds of paths? It would be quite easy to miss those ‘Skipped’ messages and commit the merge. Our mistake might not be caught until much later.
Now let’s look at what happens with tree conflict handling in 1.6.0. Given the same starting branch working copy:
1.6.0> svn stM notesobliterateobliterate-functional-spec.txt
We perform the same merge. Notice the first significant change wrought by tree conflicts, in the notifications we see a tree conflict reported rather than a skip:
1.6.0> svn merge %URL%/trunk . -c36680--- Merging r36680 into '.':A notesobliterateobliterate-func-spec.txtC notesobliterateobliterate-functional-spec.txtSummary of conflicts: Tree conflicts: 1
Checking the status of our working copy we see a second difference compared with 1.5.6. The addition half of the rename occurs as before, but now we see a tree conflict reported on ‘notesobliterateobliterate-functional-spec.txt’ (the ‘C’ in the 7th column). Also, there is the additional information about the nature of the tree conflict, specifically that we have a’local edit’ with an ‘incoming delete’ when we performed a ‘merge’.
1.6.0> svn st M .M C notesobliterateobliterate-functional-spec.txtA + notesobliterateobliterate-func-spec.txt
Let’s assume for a moment we don’t care about the tree conflict and want to commit anyway. Here is a third significant change, Subversion will not allow you to commit a working copy with unresolved tree conflicts:
1.6.0> svn ci -m "I don't care what happened! Commit away" .svn: Commit failed (details follow):svn: Aborting commit: 'C:SVN1.6.0.WCmy_branch_WCnotesobliterateobliterate-functional-spec.txt' remains in conflict
To commit this merge we need to decide what we want. If for some reason we wanted both files to remain we could simply resolve and commit the change. Chances are we want to apply our changes to the new file. In that case we would resolve the conflict:
1.6.0> svn resolve --accept working -R .Resolved conflicted state of 'notesobliterateobliterate-functional-spec.txt'
1.6.0> svn st M .M notesobliterateobliterate-functional-spec.txtA + notesobliterateobliterate-func-spec.txt
Then follow the same steps we did in 1.5.6.
Note: In 1.6.0 the various –accept options to the svn resolve subcommand have no differing effects on tree conflicts. As far as tree conflicts go, the result is the same as if you used the deprecated svn resolved command. Only with traditional text conflicts does the –accept option matter.
It is worth remembering that the concept of tree conflicts is not new. You could have a tree conflict in any version of Subversion prior to 1.6. The 1.5.6 example above is a “tree conflict”, it simply isn’t flagged as such nor handled in a very useful way. 1.6.0′s tree conflict features are primarily about identifying tree conflicts and preventing them from getting into the repository without first being resolved.
As with any new feature, more remains to be done in future releases. In this case making command line resolution of tree conflicts easier is a high priority. If you are a user of the Eclipse based Collabnet Desktop however, then you are in luck. The 1.8 release of the Desktop, scheduled for March of 2009, includes a host of tree conflict resolution features. These features do much of the “heavy lifting” of tree conflict resolution for you. In our previous example you could easily merge your local changes from ‘obliterate-functional-spec.txt’ to ‘obliterate-func-spec.txt’ and remove the former from version control and from disk, all via one dialog. So if you are interested in 1.6 for its tree conflict features, but are not using the Collabnet Desktop, it might be a good time to check it out.