In exploring functionality in Subversion, I attempted to test the use case described in the Undoing Changes subsection of the Basic Merging section of the Branching and Merging chapter of the svnbook. I’m using version 1.6.4, but the text for that section is the same in both versions of the book.
In my working copy directory, I edit a file testcode.py, adding one line per edit, and committing after each edit. After several commits, the file reads as follows:
this is my first import to trunk. r1.
this is my first commit, first edit of testcode.py. r2.
this is another edit of testcode.py. r3.
this is an edit of testcode.py. i'll get rid of this one. r4.
this is another edit of testcode.py. keeping it. r5.
yet another edit. keeping it. r6.
The revision numbers in the repository match up to the lines in the file such that in /trunk/testcode.py@rN, the last line of the file is the one ending with rN. What I want to do is remove the line ending in r4, keeping everything else before and after unchanged.
Following the example in the Undoing Changes section of the svnbook, I run the command
svn merge -c -4 file:///path_to_repos/trunk
This creates a conflict (upon running that command, not on commit), whereby the merge-left file contains everything up until line r4, and the merge-right file contains everything up until line r3. In other words, instead of removing a past change, the command seems to want to revert the entire file back to either revision 3 or 4, removing changes in subsequent revisions (5 and 6, in this case).
The way I read the example in the svnbook, which has the user reversing a change committed in revision 303 and committing the result to revision 350 with no conflicts, the command I ran should have produced a file with an svn status of M that retains all lines except the one ending in r4.
Am I reading the book’s example incorrectly, is the example wrong, or is there some other form of user error I fell into unawares?
The basic issue is that Subversion’s diff algorithm handles changes at the beginning and end of files in a way that’s not necessarily intuitive. Your example hits that corner case, while the majority of changes in the wild do not. Consider a file that looks like this after a series of commits:
Trying to revert the commits to the beginning or end of the file (revisions 2 and 4 in the example), gives a conflict. Reverting the change to the middle of the file works as expected.
Conceptually, it might help to think of changesets as having a scope limited by surrounding lines. A change to the middle of a file is bounded by the surrounding unchanged lines. The scope of a change at the beginning or end of a file extends all the way to the beginning or end of the file regardless of how far away that point is subsequently moved.
So in the example above, the second line added in revision 5 comes right in the middle of revision 4’s scope. In the same way that you’d expect a conflict reverting revision 10 here because changes in revision 11 are smack dab in the middle of it:
you should expect a conflict here, for the same reason:
Note that this is only meant as a conceptual explanation of why the beginning and end of the file are seemingly treated differently, not as a comprehensive explanation for understanding Subversion’s merge process.