Functional design for Local Moves

Status of this page: IN PROGRESS; NEEDS REVIEW

The design notes in the repo are at:

The aims of this Wiki page are:

  • first, to design the full (end goal) user experience of moves;
  • second, to document what we're doing for 1.8.

To Break or Not To Break?

One of the main UI (and high-level API) changes is that many operations on the path representing one half the move should be aware of the other half and perhaps require that both halves are acted on together: for example, an attempt to commit one half of a move without the other should error out.

We need to go beyond simple path-based addressing now that we have 'move', because it affects two paths so we can't continue to assume a model where a path simply addresses 'the node' and a node has 'a path'. Idea: 'to' path identifies the move; 'from' path identifies any replacement at that path???


Ultimate goal

Goal for 1.8

Trunk status


Require both together? Automatically select both sides? Neither: The 'to' side should represent the move; the 'from' side shouldn't need to be assigned to a changelist in order to be operated on by that changelist; rather, the 'from' side represents any replacement at that path. ???



Test Coverage





Require both together.

As per ultimate goal.


Test Coverage 105 'attempt to commit the copied part of move'
 106 'attempt to commit the deleted part of move'



Applied to the 'from' side: delete the replacement if there is one (this might be the 'to' side of another move); don't affect the move.




Applied to the 'to' side: revert the move; change the 'from' path to a simple deleted(rather than move-away); keep it replaced if it was replaced.


errors out?

Test Coverage





Diff eventually needs to be upgraded to support moves, but how?

Treat as copy & delete, for each path in tree?


Test Coverage





On each move root node (only), show 'Moved To' or 'Moved From' (or both if node is moved-away replaced by moved-here).


also shows on all children

Test Coverage




lock, unlock

Nothing special – As we can only lock paths that exist in the repo, in general we can't lock the 'to' side of a move (although we could do when it is a replacement).



Test Coverage





Nothing special – For each path, apply changes to the actual/working node at that path.



Test Coverage





Applied to the 'from' side: operate on any replacement node; invalid if no replacement. Should be similar to 'delete' in this regard, and should have some commonality with any other operation that operates on a working/actual node that must exist.




Applied to the 'to' side: transitive move, leaving no trace that the subtree was previously moved to a different path. If moved back to its own 'from' path, it should become as if it had never been moved.


move to 'from' path gives bad status 'R + foo // > moved from foo // > moved to foo'

Test Coverage





??? At the moment, a move conflict is flagged on the 'from' side I think. But that's no good if there's a replacement that may have a conflict (question), and anyway we should provide the UI on the side that the user can see (question). Applied to the 'to' side: resolve the move conflict? Applied to 'from' side: resolve any conflict on the replacement node? (Applied to either side: automatically (silently) operate on both sides? No, that would interfere with resolving a conflict on the replacement on the moved-away side.) See section on conflicts (TODO).



Test Coverage





See notes.

Apply to either side independently, reducing the other side to a plain copy or delete.


Test Coverage





On each move root node (only), show the historical 'D' or 'A +' line and then an extra line with '> moved to xxx' or '> moved from xxx' (or both lines if node is moved-away replaced by moved-here, or a single '> swapped with xxx' line if moved-to == moved-from). See notes. As for 'info', show the 'from' path that is within the 'to' side of the parent move (see Nested Moves).


shows a 'D' line for every moved-away child

Test Coverage




update, switch

??? See notes.



Test Coverage




Subcommands not listed should do nothing special on the path of a moved node, continuing to behave just the same as on a deleted or copied node.

Nested Moves

When a child has been moved within (or into or out of) a moved subtree, the UI should present the child's 'from' path as a path within the moved-here parent, not as the original base moved-from path that corresponds to its last known repository path.

For example, the output should look something like this, with '*' marking the paths shown relative to the parent where the original path would be different:

svn checkout
A   A/
A   A/foo
svn move A B
D   A/
A   B/
svn move B/foo B/bar
D   B/foo                  *
A   B/bar
svn status
D   A/
    > moved to 'B'
A + B/
    > moved from 'A'
D   B/foo                  *
    > moved to 'B/bar'
A + B/bar
    > moved from 'B/foo'   *
svn info A/ B/ B/foo B/bar

svn changelist

svn commit

svn delete

svn diff

svn diff --git

Should output a 'rename', not a delete and a copy. (But not a 1.8 blocker – for one thing, we'll only be able to do rename for local edits.)

Because git lacks directory awareness, need one 'rename' record for each file in the moved tree?

svn info

svn lock, unlock

svn merge

svn move (on a moved node)

svn resolve (resolver)

Using theirs-conflict currently resolves a conflict from a local move by converting it to a copy. Is there a real use case for this? Doesn't work after a merge with a local move? Should it?

svn revert

Currently silently converts the other half into a simple delete or simple copy if you only run it on one half of the move. As stsp points out, that's easy to explain.

Should it require 'force' for that, since the user quite likely doesn't want to break the move?

Eventually (after 1.8): need more thought on *what* we want to revert – perhaps not a set of paths but rather the 'move' operation? Certainly seems wrong to default to breaking the move. Perhaps revert on the move-root 'to' side should revert the move just like moving the subtree back to its original location (and revert the node's property changes), keeping all changes within the subtree; error out if the 'from' side has been replaced. And if the 'to' side was itself a replacement, don't revert the 'delete' or 'move-away' on that node. This is all a bit of a change from the previous purely path-based concept of addressing a node; see the section on path-based addressing (TODO).

svn status

Currently shows 'D' for every node in the moved-away tree, but 'A+' only for the root of the moved-here. A minimum change is: show just 'D' for only the root node of the moved-away subtree (and still one 'A+' for the moved-here).

Show moves with a single line and a new code letter? No, not yet.

Group the 'D' and its corresponding 'A+' line adjacent to each other?

svn update, switch


We need to add tests to Subversion to prove that the implementation and design works. In order to do that we need to define all the various moves that may happen.

Basic Moves

These moves can happen against any type of node.

  • Lateral (Rename) move. Node continues to have the same parent but changes name.
  • Sibling move. Node moves into a sibling of the current parent.
  • Shallower move. Node moves shallower (closer to the root) in the tree.
  • Deeper move. Node moves deeper (away from the root) in the tree.

Combining these basic moves or other operations can complete more complex moves.

Complex Moves

  • Swap names. Nodes stay at the same level but swap names. Takes three moves.
  • Nested moves. A directory is moved. Then other children are moved within, into or out of the directory (see above). The reverse ordering of the children being moved within the directory followed by the parent being moved should be tested as well.
  • Swap parent/child. Child node moves shallower. Parent node then moves inside moved child node. Takes two moves. Can only happen with two directories. This is actually a subset of nested moves, but is mentioned specifically because of the circular nature.
  • Replaced. Move a node and replace it with another node (of the same or a different type).
  • Remove parent. Children of a directory are moved shallower. Directory is then removed.
  • Add parent. A new directory is made (or copied) and then nodes are moved into it.
  • No-op move. A move is made and then undone by the reverse move.

Nodes in the above move scenarios should include the following node types: file, dir and symlink. While symlink is actually a file with some special handling, we should ensure there are no conflicts between the move tracking and the special handling of symlinks. Externals need not be tested for moves because they cannot be moved with the move command and will not be tracked as such.

Incoming Updates

On top of testing the moves themselves we need to test receiving updates/switches over the moves and resolving tree conflicts created by the incoming changes.

The following incoming changes should be tested.

  • Add/Copy/Delete/Modify/Replace a moved node, to a node inside a moved directory and to a node outside a moved directory.
  • Move a node inside and outside a moved directory (note there's no move tracking so this is a combination of a copy and a delete).
  • Property modifications to a moved node, to a node inside a moved directory and to a node outside a moved directory.
  • Modifications to an external set on a moved directory, set on a directory inside and outside a moved directory (subset of property modification above but mentioned specifically since client handles externals specially). Both directory and file externals should be tested.
  • Modifications to a symlink inside and outside a moved directory. (subset of property modification above but mentioned specifically since client handles symlinks specially).
  • Modification that is identical to the locally tracked move (since there's no move tracking on the server side we won't be able to recognize this as the identical operation, but we must not regress from our behavior when we had no move tracking in the working copy).

We very likely may not be able to automatically resolve all the conflicts with 1.8 but we should not have any regressions in behavior as compared to our behavior without move tracking in previous versions.

The full set of tests required is the combination of all the move scenarios and all the incoming change scenarios.


These commands should have their output checked for accuracy against all of the move scenarios. Diff testing is needed since the --git option shows tree changes.


Only testing required is the testing of committing part and rejecting it.

  • No labels