Dear Wiki user,

You have subscribed to a wiki page or wiki category on "Subversion Wiki" for 
change notification.

The "ResolvingIncomingMoves" page has been changed by StefanSperling:
https://wiki.apache.org/subversion/ResolvingIncomingMoves

Comment:
Initial notes on how the resolver can deal with incoming moves

New page:
= How the conflict resolver deals with incoming moves =

An incoming move during an update/switch/merge operation appears
as disjoint additions and deletions of working copy nodes.

The conflict resolver scans the revision log to match up copies and deletions.
See 'struct repos_move_info' and 'find_moves_in_revision()' in the file
subversion/libsvn_client/conflicts.c. While this approach is not perfect
(it does not corrrectly handle deletions which happened inside copies,
for instance) it is good enough to support users during conflict resolution
in many use cases.

== Incoming move during updates ==

Consider the following pre-update NODES table:

|| op_depth || local_relpath || presence ||
||    0     ||    A          || normal   ||
||    0     ||    A/f        || normal   ||

with a locally modified file A/f.

After A is moved to B in the repostory the user updates to the HEAD
revision. The NODES table will now look as follows:

|| op_depth || local_relpath || presence ||
||    0     ||    B          || normal   ||
||    0     ||    B/f        || normal   ||
||    1     ||    A          || normal   ||
||    1     ||    A/f        || normal   ||

The copy op-root A was created when a local edit vs incoming delete
tree conflict was raised on A. This is how the update process attempts
to preserve the user's local changes. The conflict resolver relies on
this behaviour.

The ACTUAL_NODE table now stores a tree conflict on A (the conflict
skel shown here is heavily abbreviated):

|| local_relpath || tree_conflict_data ||
||    A          || ((update ((trunk/A 2 dir) (trunk/A 3 none))) ((tree () 
edited deleted))) ||

The resolver scans the revision log and determines that A was moved to B.
Merging the incoming move involves merging all changes within conflict
victim A into the newly added subtree B.

In our simple example, this means running a 3-way merge of the files
pristine(A/f), A/f, and B/f, into B/f. Afterwards, the rows for A can be
deleted from the NODES table and the conflict skel for A can be removed:

|| op_depth || local_relpath || presence ||
||    0     ||    B          || normal   ||
||    0     ||    B/f        || normal   ||

Things get more interesting if local changes are present in the NODES
table before the update, such as an added file n and a deleted file d:

|| op_depth || local_relpath || presence     ||
||    0     ||    A          || normal       ||
||    0     ||    A/f        || normal       ||
||    0     ||    A/d        || normal       ||
||    2     ||    A/n        || normal       ||
||    2     ||    A/d        || base-deleted ||

The post-update state is:

|| op_depth || local_relpath || presence     ||
||    0     ||    B          || normal       ||
||    0     ||    B/f        || normal       ||
||    0     ||    B/d        || normal       ||
||    1     ||    A          || normal       ||
||    1     ||    A/f        || normal       ||
||    1     ||    A/d        || normal       ||
||    2     ||    A/n        || normal       ||
||    2     ||    A/d        || base-deleted ||

Merging the incoming move involves moving all changes within
conflict victim A into the newly added subtree B and removing
the rows for A.

This operation must be performed by an editor which edits the B subtree.
It refers to the A subtree at op_depth 1 (actually relpath_depth('A')) as
the base tree, and to the A subtree at op_depth > 1 as the changed tree.
Any nodes at op_depth > 1 trigger corresponding edits in the B subtree.

The result of this edit should be:

|| op_depth || local_relpath || presence     ||
||    0     ||    B          || normal       ||
||    0     ||    B/f        || normal       ||
||    0     ||    B/d        || normal       ||
||    2     ||    B/n        || normal       ||
||    2     ||    B/d        || base-deleted ||

File contents within A from the pristine store are part of the editor's
base tree, while the working files within A on disk are part of the
changed tree. This results in the previously discussed 3-way merge of
pristine(A/f), A/f, and B/f, into B/f.

Any nodes within the B subtree at op-depths > 1 trigger a tree conflict.
Suppose the user adds 'B/n' and moves 'B/d' to 'B/c' before running
the conflict resolver:

|| op_depth || local_relpath || presence     || moved_to || moved_here ||
||    0     ||    B          || normal       ||          ||            ||
||    0     ||    B/f        || normal       ||          ||            ||
||    0     ||    B/d        || normal       ||          ||            ||
||    2     ||    B/n        || normal       ||          ||            ||
||    2     ||    B/d        || base-deleted || B/c      ||            ||
||    2     ||    B/c        || normal       ||          || 1          ||
||    1     ||    A          || normal       ||          ||            ||
||    1     ||    A/f        || normal       ||          ||            ||
||    1     ||    A/d        || normal       ||          ||            ||
||    2     ||    A/n        || normal       ||          ||            ||
||    2     ||    A/d        || base-deleted ||          ||            ||

While editing the B tree, the resolver could now create tree conflicts for
B/n (local add vs incoming add) and B/d (local move vs incoming deletion)
in the ACTUAL_NODE table. This raises several new problems:

* The meaning of the terms 'local' and 'incoming' in conflict descriptions
  is now misleading because both of the conflicting tree changes were made
  in the working copy ('local').

* The resolver will have to be taught to resolve such conflicts (this is
  not impossible but implies extra implementation effort).

* The resolver will have to choose whether the existing node in B should
  be left as-is, or whether it should be replaced with the node from A.
  Either way, information is lost unless the rows for conflicting nodes
  are left unmodified in both A and B.

The resolver could also reject conflict resolution if local changes are
present in the B tree. However, this approach would work only for updates
but not for merges.

== Incoming move during merges ==

For a merge from trunk to a branch, the pre-merge NODES table might look like:

|| op_depth || local_relpath || presence || repos_path ||
||    0     ||    A          || normal   || branch/A   ||
||    0     ||    A/f        || normal   || branch/A/f ||

where file A/f differs from the corresponding file on the trunk (for now,
let's assume the merge target working copy contains no local modifications).

While running a merge from the trunk, the repository-side move of A to B
results in the deletion of A and the addition of B as a copy from the trunk.
The NODES now table looks like:

|| op_depth || local_relpath || presence || repos_path ||
||    0     ||    A          || normal   || branch/A   ||
||    0     ||    A/f        || normal   || branch/A/f ||
||    1     ||    B          || normal   || trunk/B    ||
||    1     ||    B/f        || normal   || trunk/B/f  ||

And the ACTUAL_NODE table stores a tree conflict marker for A:

|| local_relpath || tree_conflict_data ||
||    A          || ((merge ((trunk/A 1 dir) (trunk/A 4 none))) ((tree () 
edited deleted))) ||

The conflict resolver scans the log and determines that A has been renamed to B.

The resolver must now run a 3-way merge of YCA(A/f, B/f), A/f, and B/f,
into B/f. To do this, the youngest common ancestor of A/f and B/f must be
fetched from the repository and stored in a temporary file (the YCA content
could already be present in the pristine store in some cases, but making
use of this fact is an optimization).

Let's examine the situation with a pre-merge NODES table that has already
committed tree changes relative to the trunk: a deleted file A/d and a new
file A/n:

|| op_depth || local_relpath || presence     || repos_path ||
||    0     ||    A          || normal       || branch/A   ||
||    0     ||    A/f        || normal       || branch/A/f ||
||    0     ||    A/n        || normal       || branch/A/n ||

The post-merge state would look like this:

|| op_depth || local_relpath || presence     || repos_path ||
||    0     ||    A          || normal       || branch/A   ||
||    0     ||    A/f        || normal       || branch/A/f ||
||    0     ||    A/n        || normal       || branch/A/n ||
||    1     ||    B          || normal       || trunk/B    ||
||    1     ||    B/d        || normal       || trunk/B/d  ||

Merging the incoming move involves moving all changes within conflict
victim A into the newly added subtree B, and marking a move of A to B
in the NODES table.

Part of this operation must be performed by an editor which edits the B
subtree. It refers to the repository-side A subtree at the common-ancestor
revision rYCA(A,B) as the base tree, and to the repository-side A subtree
at the BASE revision of A as the changed tree.
Any nodes which have changed since rYCA trigger edits in the B subtree.
This can be implemented by running the equivalent of a standard merge
-rYCA:BASE ^/A@BASE into B.

This results in:

|| op_depth || local_relpath || presence     || repos_path ||
||    0     ||    A          || normal       || branch/A   ||
||    0     ||    A/f        || normal       || branch/A/f ||
||    0     ||    A/n        || normal       || branch/A/n ||
||    1     ||    B          || normal       || trunk/B    ||
||    1     ||    B/d        || normal       || trunk/B/d  ||
||    1     ||    B/f        || normal       || trunk/B/f  ||
||    2     ||    B/d        || base-deleted ||            ||
||    2     ||    B/n        || normal       || branch/A/n ||

Marking a move in the working copy requires adding a base-deleted row for
A with a moved_to column which points at B and setting B's moved_here flag:

|| op_depth || local_relpath || presence     || repos_path || moved_to || 
moved_here ||
||    0     ||    A          || normal       || branch/A   ||          ||       
     ||
||    0     ||    A/f        || normal       || branch/A/f ||          ||       
     ||
||    0     ||    A/n        || normal       || branch/A/n ||          ||       
     ||
||    1     ||    A          || base_deleted ||            || B        ||       
     ||
||    1     ||    B          || normal       || trunk/B    ||          || 1     
     ||
||    1     ||    B/d        || normal       || trunk/B/d  ||          ||       
     ||
||    1     ||    B/f        || normal       || trunk/B/f  ||          ||       
     ||
||    2     ||    B/d        || base-deleted ||            ||          ||       
     ||
||    2     ||    B/n        || normal       || branch/A/n ||          ||       
     ||

However, B's repository path still points to the trunk.
Only newly added children within B appear as copied from the branch.

A better the resolution result would be a move of A to B on the branch:

|| op_depth || local_relpath || presence     || repos_path || moved_to || 
moved_here ||
||    0     ||    A          || normal       || branch/A   ||          ||       
     ||
||    0     ||    A/f        || normal       || branch/A/f ||          ||       
     ||
||    0     ||    A/n        || normal       || branch/A/n ||          ||       
     ||
||    1     ||    A          || base_deleted ||            || B        ||       
     ||
||    1     ||    B          || normal       || branch/A   ||          || 1     
     ||
||    1     ||    B/f        || normal       || branch/A/f ||          ||       
     ||
||    1     ||    B/n        || normal       || branch/A/n ||          ||       
     ||

With changes from the trunk merged in.

Reply via email to