Ev1.5 Representation of Moves
Summary
The original “svn_delta_editor_t” is colloquially known as “Ev1” now that a proposed new “svn_editor_t” known as “Editor version 2” (Ev2) has been around for some time.
This document sets out a proposal for revising Ev1 to support move tracking – specifically, to enable move semantics to be transmitted over the editor interface.
Since the name “Ev2” is already taken, let's call the result “Ev1.5”.
Specification
Changes to svn_delta_editor_t to support moves:
- Add an operation: move-away(path, id, parent_baton). The path references a child of the directory represented by parent_baton. The node-line at path existed in the initial tree state: it was not created within this edit. The id is different from that of every other move-away in the whole edit. After this operation, the path and all its descendents are no longer visible in the current tree state.
- Add an operation: move-here(path, id, parent_baton) → child_baton. The path references a non-existent child name in the directory represented by parent_baton. The id matches the id given in a previous move-away. The path is then “open” for editing, like when a path is created by the “add” operation.
- Add an operation: rename(path1, path2, parent_baton) → child_baton. The path1 references a child of the directory represented by parent_baton. The node-line at path existed in the initial tree state: it was not created within this edit. The path2 references a non-existent child name in the same directory. The path is then “open” for editing, like when a path is created by the “add” operation. This is functionally identical to move-away(path1) immediately followed by move-here(path2).
- The edit driver MUST open the parent directory before and close the parent directory after each of these operations, like all other operations in the editor.
- The edit driver MUST send exactly one move-away and one move-here for each id.
- The name and/or the parent directory node-line MUST differ between move-away and the corresponding move-here.
- The edit driver SHOULD NOT move-away a subtree that has been altered (except by move-away of deeper subtrees).
- The edit driver SHOULD NOT move-away a path that has been moved here (the path itself or through any parent).
- The edit driver SHOULD use the rename operation instead of move-away and move-here, when possible.
Examples
Here are some examples of move scenarios that can be difficult, and their solutions.
Example 1: Insert a directory level
| | +--A mv--\ (add) +--A \ | \--> +--B
The add cannot happen before the move-away, but must happen before the move-here.
- move-away(A, id=1)
- add-directory(A, copy-from=null)
- move-here(A/B, id=1)
- close-directory(A/B)
- close-directory(A)
Example 1b: Remove a directory level (by deletion)
| | +--A (del) /--> +--A | / +--B mv--/
The move-away must happen before the delete, while the move-here cannot happen before the delete.
- open-directory(A)
- move-away(A/B, id=1)
- close-directory(A)
- delete(A)
- move-here(A, id=1)
- close-directory(A)
Example 1c: Remove a directory level (by move-away)
| | | /--> +--X | / | +--A mv----/ /--> +--A | / +--B mv----/
Move-away B, then move-away A, then move-here original B at new path A:
- open-directory(A)
- move-away(A/B, id=1)
- close-directory(A)
- move-away(A, id=2)
- move-here(A, id=1)
- close-directory(A)
- move-here(X, id=2)
- close-directory(X)
(A possible alternative, starting with move-away A, then move-away B from inside the moved A, is only possible if we've been able to complete that move by this time. It also violates the rule that the edit driver should not move a previously moved node.)
Example 2: Swap two siblings
| | +--A mv--\ /--> +--A | X | +--B mv--/ \--> +--B
Neither of the moves can be completed before doing the move-away part of the other one.
- move-away(A, id=1)
- rename(B, A)
- close-directory(A)
- move-here(B, id=1)
- close-directory(B)
Example 3: Swap two directory levels
| | +--A mv--\ /--> +--A | X | +--B mv--/ \--> +--B
Neither of the moves can be completed before doing the move-away part of the other one.
- open-directory(A)
- move-away(A/B, id=1)
- close-directory(A)
- move-away(A, id=2)
- move-here(A, id=1)
- move-here(A/B, id=2)
- close-directory(A/B)
- close-directory(A)
Example 4: Swap A with A/B/C
| | +-- A mv--\ /--> +-- A | \ / | +-- B mv--- X ---> +-- B | / \ | +-- C mv--/ \--> +-- C
- open-directory(A)
- open-directory(A/B)
- move-away(A/B/C, id=1)
- close-directory(A/B)
- move-away(A/B, id=2)
- close-directory(A)
- move-away(A, id=3)
- move-here(A, id=1)
- move-here(A/B, id=2)
- move-here(A/B/C, id=3)
- close-directory(A/B/C)
- close-directory(A/B)
- close-directory(A)
(Other similar examples are possible. Instead of B being moved (as shown), it could be deleted from the old A and re-created in the new A, or the original B could move away and the new B could move into place, each by naturally following along with the move of its own parent directory.)
Concerns and Enhancements
Half-Moves at the Edge of the Subtree
An edit is scoped to a pathwise subtree of the repository (for a commit) or of the WC (for an update). It is possible for the edit to involve a node that, if seen in a wider scope, would move into or out of this subtree. With this specification the edit driver has to describe such a move as an add or delete.
There are potential advantages to describing it as a move (or rather as half of a move). This would give the consumer the ability to inform or warn the user, raise a tree conflict, or even perhaps commit or update an additional subtree in order to avoid breaking the move.
The consumer can convert a move-out to a delete trivially. The consumer can convert a move-in to an add (of sorts) by requesting the node's content from the server, which is trivial during a commit (when the consumer is the server), but hard during an update (when the consumer is the client).
References
Related Proposals
MoveDev MovesOverDeltaEditor – a proposal for a way to transparently encode move semantics into an old Ev1 editor drive and decode it on the receiving end – in other words, a way to tunnel move semantics through an old Ev1 connection.
MoveDev Ev2MovesDesign – a proposal for defining moves in Ev2.