-*- text -*- TREE CONFLICTS USE CASES AND DESIRED BEHAVIOURS Issue reference: http://subversion.tigris.org/issues/show_bug.cgi?id=2282 (These use cases are based on a scenario paper "SVN move/rename problems & suggested improvements" submitted by a corporate Subversion user, which may be found attached to issue #2282.) -------------------------------------------------------------------------- ========== USE CASE 1 ========== Description During an update, a file modification is merged onto a file move. Current Behavior Developer A modifies Foo.c and commits it to the repository. Developer B has simultaneously moved Foo.c to Bar.c in his working copy. B cannot commit because the working copy is out of date, so B runs 'svn update'. The update will apply A's modifications to Foo.c in the repository to Foo.c in B's working copy. Problems With Current Behavior First problem: A's modification of Foo.c will effectively be reverted by B's new revision. Foo.c will be deleted in the new revision, and Bar.c will be added with the content of the original Foo.c before A's modifications. Hence A will likely get angry with B. Second problem: B is not explicitly warned about reverting A's modification of Foo.c. The only visible warning is that Foo.c is left behind unversioned in B's working copy because it has "local" modifications (which were in fact made by A). This will likely escape B's attention. Diagram of current behavior (edit) wcA -- Foo.c' -------> / | / |commit repos / v -- Foo.c -------------- Foo.c' --------------- Bar.c ---> \ | ^ \ |update |commit \ v | wcB -- +Bar.c ---------- +Bar.c ---- Bar.c ---> (move) -Foo.c -Foo.c' ?Foo.c' (unversioned) Desired behavior When user B updates, A's modifications to Foo.c should be merged into Bar.c. Signal a text conflict if necessary. Foo.c should be deleted from B's working copy. A tree conflict should be signalled to inform B of the new changes to Bar.c, so that B can review the modified Bar.c before committing it. Diagram of desired behaviour (edit) wcA -- Foo.c' -------> / | / |commit repos / v -- Foo.c --------------- Foo.c' ------------------------ Bar.c' ---> \ | ^ ^ \ |update |commit |commit \ v |(fails) | wcB -- +Bar.c ------------ +Bar.c' --------------> (move) -Foo.c -Foo.c ^ | resolved ========== USE CASE 2 ========== Description During an update, a file move is merged onto a file modification. This is essentially the same as Use Case 1, with the difference that this time, B does the edit and A does the move. Current Behavior Developer B modifies Foo.c in his working copy. Developer A has simultaneously moved Foo.c to Bar.c and commits the move to the repository. B cannot commit because his working copy is out of date, so B runs 'svn update'. The next update will add Bar.c (with the same content as the original Foo.c) to B's working copy, and delete Foo.c from B's working copy. Since B made local modifications to Foo.c, it will not be deleted from disk but left behind unversioned. Problems with Current Behavior Developer B may not notice that Foo.c fell out of version control. B's source tree in the working copy likely builds fine because Foo.c is still present on disk. So B may commit an incomplete change set, possibly breaking the tree. Everybody will get angry with B if this happens. Diagram of Current Behaviour (move) wcA -- +Bar.c -------> / -Foo.c | / |commit repos / v -- Foo.c --------------- Bar.c -----------------------> \ | ^ \ |update |commit \ v |(no-op) wcB -- Foo.c' ------------ Bar.c -------> (edit) ?Foo.c' (unversioned) Desired Behavior In B's working copy, the update should add Bar.c and merge the local modifications to Foo.c into Bar.c. Signal a text conflict if necessary. Foo.c should be deleted from B's working copy. A tree conflict should be signaled to inform B that Foo.c has been renamed to Bar.c Diagram of Desired Behaviour (move) wcA -- +Bar.c -------> / -Foo.c | / |commit repos / v -- Foo.c --------------- Bar.c -------------------------- Bar.c'---> \ | ^ ^ \ |update |commit |commit \ v |(fails) | wcB -- Foo.c' ------------+Bar.c' ------------------------> (edit) -Foo.c' ^ | resolved ========== USE CASE 3 ========== Description During an update, a file move is merged onto a conflicting file move. Current Behavior Developer A moves Foo.c to Bar.c and commits the move to the repository. Developer B has moved Foo.c to Bix.c in his working copy. B cannot commit because his working copy is out of date, so B runs 'svn update'. The update will add Bar.c to B's working copy and delete Foo.c from B's working copy (the latter is a no-op). Problems with Current Behavior After B's next commit, the content of the original Foo.c will exist twice in the source tree under two different paths, namely Bar.c and Bix.c, respectively. This may not have been intended. Diagram of Current Behavior (move) wcA -- +Bar.c ------> / -Foo.c | / |commit archive / v -- Foo.c --------------- Bar.c ------------------ Bar.c ---> \ | ^ Bix.c \ |update |commit \ v | wcB -- +Bix.c ---------- +Bix.c -------> (move) -Foo.c Bar.c Desired Behavior A tree conflict should be signaled to inform B of the conflicting rename operation. B can now decide on deleting either file or committing both. Diagram of Desired Behavior (move) wcA -- +Bar.c ------> / -Foo.c | / |commit archive / v -- Foo.c --------------- Bar.c -------------------------- Bar.c --> \ | ^ ^ (or Bix.c, \ |update |commit |commit or both) \ v |(fails) | wcB -- +Bix.c ---------- +Bix.c --------------> (move) -Foo.c Bar.c ^ | resolved ========== USE CASE 4 ========== Description A file modification is merged onto the source of a file move. Current Behavior Developer A modifies Foo.c and commits it to the repository. Developer B moves Foo.c to Bar.c and commits it to the repository. Developer B merges A's new revision into his working copy. The merge will apply A's modification to Foo.c to the Foo.c in B's working copy. Problems With Current Behavior First problem: A's modification of Foo.c will not be merged to B's line of development because the merge skips the absent file. Second problem: B is not explicitly warned about reverting A's modification of Foo.c, except for a "skipped" warning in the output of the merge command, which might not be noticed. Diagram of current behavior (edit) urlA -- Foo.c' ------------------> / (r50) | / | -- Foo.c - |merge -c50 \ |(Foo.c skipped) \ | urlB -- +Bar.c ------------|-----------------> (move) -Foo.c \ | ^ \ | |commit \ v |(no-op) wcB -- Bar.c -- Bar.c ------ --> Desired behavior When user B merges, A's modifications to Foo.c should be merged into Bar.c. Signal a text conflict if necessary. A tree conflict should be signalled to inform B of the new changes to Bar.c, so that B can review the modified Bar.c before committing it. Diagram of desired behaviour (edit) urlA -- Foo.c' ------------------> / (r50) | / | -- Foo.c - |merge -c50 \ |(tree conflict) \ | urlB -- +Bar.c -------------|-------------------- Bar.c' --> (move) -Foo.c \ | ^ ^ \ | |commit |commit \ v |(fails) | wcB -- Bar.c -- Bar.c' ---------------> ^ | resolved ========== USE CASE 5 ========== Description A file move is merged onto a modification of the move-source. This is essentially the same as Use Case 4, with the difference that this time, B does the edit and A does the move. Current Behavior Developer A moves Foo.c to Bar.c and commits it to the repository. Developer B modifies Foo.c and commits it to the repository. Developer B merges A's new revision into his working copy. The merge will add Bar.c (with the same content as the original Foo.c) and will delete B's Foo.c. Problems With Current Behavior First problem: B's has modified Foo.c in the past. This modification will be lost unless B reviews the history of Foo.c and Bar.c at both URLs and corrects the problem (e.g., via 'svn copy'). Diagram of current behavior (move) urlA -- +Bar.c -------------------> / -Foo.c | / (r50) | -- Foo.c - |merge -c50 \ | \ | urlB -- Foo.c' -------------|------------ Bar.c ---> (edit) \ | ^ \ | |commit \ v | wcB -- Foo.c' -- +Bar.c ------> -Foo.c' Desired behavior In B's working copy, the update should add Bar.c and merge the local modifications to Foo.c into Bar.c. Signal a text conflict if necessary. Foo.c should be deleted from B's working copy. A tree conflict should be signaled to inform B that Foo.c has been renamed to Bar.c Diagram of desired behaviour (move) urlA -- +Bar.c -------------------> / -Foo.c | / (r50) | -- Foo.c - |merge -c50 \ |(tree conflict) \ | urlB -- Foo.c' -------------|-------------------- Bar.c'--> (edit) \ | ^ ^ \ | |commit |commit \ v |(fails) | wcB -- Foo.c' -- Bar.c' ---------------> -Foo.c' ^ | resolved ========== USE CASE 6 ========== Description A file move is merged onto a conflicting file move. Current Behavior Developer A moves Foo.c to Bar.c and commits it to the repository. Developer B moves Foo.c to Bix.c and commits it to the repository. Developer B merges A's new revision into his working copy. The merge will add Bar.c with history in B's working copy. Problems With Current Behavior After B's next commit, the content of the original Foo.c will exist twice in the source tree under two different paths (Bar.c and Bix.c). This may not have been intended. Diagram of current behavior (move) urlA -- +Bar.c ------------------> / -Foo.c | / (r50) | -- Foo.c - |merge -c50 \ | \ | urlB -- +Bix.c ------------|---------------- Bix.c ---> (move) -Foo.c \ | ^ Bar.c \ | |commit \ v | wcB -- Bix.c -- Bix.c ----------> +Bar.c Desired behavior A tree conflict should be signaled to inform B of the conflicting rename operation. B can delete either file or commit both. Diagram of desired behaviour (move) urlA -- +Bar.c -----------------> / -Foo.c | / (r50) | -- Foo.c - |merge -c50 \ |(tree conflict) \ | urlB -- +Bix.c ------------|------------------------- Bar.c --> (move) -Foo.c \ | ^ ^ (or Bix.c, \ | |commit |commit or both) \ v |(fails) | wcB -- Bix.c -- Bix.c --------------> +Bar.c ^ | resolved