Subversion
|
Traversing tree deltas. More...
Data Structures | |
struct | svn_delta_editor_t |
A structure full of callback functions the delta source will invoke as it produces the delta. More... | |
Typedefs | |
typedef struct svn_delta_editor_t | svn_delta_editor_t |
A structure full of callback functions the delta source will invoke as it produces the delta. More... | |
Functions | |
svn_delta_editor_t * | svn_delta_default_editor (apr_pool_t *pool) |
Return a default delta editor template, allocated in pool. More... | |
svn_error_t * | svn_delta_noop_window_handler (svn_txdelta_window_t *window, void *baton) |
A text-delta window handler which does nothing. More... | |
svn_error_t * | svn_delta_get_cancellation_editor (svn_cancel_func_t cancel_func, void *cancel_baton, const svn_delta_editor_t *wrapped_editor, void *wrapped_baton, const svn_delta_editor_t **editor, void **edit_baton, apr_pool_t *pool) |
Set *editor and *edit_baton to a cancellation editor that wraps wrapped_editor and wrapped_baton. More... | |
svn_error_t * | svn_delta_depth_filter_editor (const svn_delta_editor_t **editor, void **edit_baton, const svn_delta_editor_t *wrapped_editor, void *wrapped_edit_baton, svn_depth_t requested_depth, svn_boolean_t has_target, apr_pool_t *pool) |
Set *editor and *edit_baton to an depth-based filtering editor that wraps wrapped_editor and wrapped_baton. More... | |
Traversing tree deltas.
In Subversion, we've got various producers and consumers of tree deltas.
In processing a ‘commit’ command:
In processing an ‘update’ command, the process is reversed:
The simplest approach would be to represent tree deltas using the obvious data structure. To do an update, the server would construct a delta structure, and the working copy library would apply that structure to the working copy; the network layer's job would simply be to get the structure across the net intact.
However, we expect that these deltas will occasionally be too large to fit in a typical workstation's swap area. For example, in checking out a 200Mb source tree, the entire source tree is represented by a single tree delta. So it's important to handle deltas that are too large to fit in swap all at once.
So instead of representing the tree delta explicitly, we define a standard way for a consumer to process each piece of a tree delta as soon as the producer creates it. The svn_delta_editor_t structure is a set of callback functions to be defined by a delta consumer, and invoked by a delta producer. Each invocation of a callback function describes a piece of the delta — a file's contents changing, something being renamed, etc.
typedef struct svn_delta_editor_t svn_delta_editor_t |
A structure full of callback functions the delta source will invoke as it produces the delta.
Here's how to use these functions to express a tree delta.
The delta consumer implements the callback functions described in this structure, and the delta producer invokes them. So the caller (producer) is pushing tree delta data at the callee (consumer).
At the start of traversal, the consumer provides edit_baton, a baton global to the entire delta edit. If there is a target revision that needs to be set for this operation, the producer should call the set_target_revision
function at this point.
Next, if there are any tree deltas to express, the producer should pass the edit_baton to the open_root
function, to get a baton representing root of the tree being edited.
Most of the callbacks work in the obvious way:
@c delete_entry @c add_file @c add_directory @c open_file @c open_directory
Each of these takes a directory baton, indicating the directory in which the change takes place, and a path argument, giving the path of the file, subdirectory, or directory entry to change.
The path argument to each of the callbacks is relative to the root of the edit. Editors will usually want to join this relative path with some base stored in the edit baton (e.g. a URL, or a location in the OS filesystem).
Since every call requires a parent directory baton, including add_directory
and open_directory
, where do we ever get our initial directory baton, to get things started? The open_root
function returns a baton for the top directory of the change. In general, the producer needs to invoke the editor's open_root
function before it can get anything of interest done.
While open_root
provides a directory baton for the root of the tree being changed, the add_directory
and open_directory
callbacks provide batons for other directories. Like the callbacks above, they take a parent_baton and a relative path path, and then return a new baton for the subdirectory being created / modified — child_baton. The producer can then use child_baton to make further changes in that subdirectory.
So, if we already have subdirectories named ‘foo’ and ‘foo/bar’, then the producer can create a new file named ‘foo/bar/baz.c’ by calling:
open_root
() — yielding a baton root for the top directoryopen_directory
(root, "foo") — yielding a baton f for ‘foo’open_directory
(f, "foo/bar") — yielding a baton b for ‘foo/bar’add_file
(b, "foo/bar/baz.c")When the producer is finished making changes to a directory, it should call close_directory
. This lets the consumer do any necessary cleanup, and free the baton's storage.
The add_file
and open_file
callbacks each return a baton for the file being created or changed. This baton can then be passed to apply_textdelta
or apply_textdelta_stream
to change the file's contents, or change_file_prop
to change the file's properties. When the producer is finished making changes to a file, it should call close_file
, to let the consumer clean up and free the baton.
The add_file
and add_directory
functions each take arguments copyfrom_path and copyfrom_revision. If copyfrom_path is non-NULL
, then copyfrom_path and copyfrom_revision indicate where the file or directory should be copied from (to create the file or directory being added). In that case, copyfrom_path must be either a path relative to the root of the edit, or a URI from the repository being edited. If copyfrom_path is NULL
, then copyfrom_revision must be SVN_INVALID_REVNUM; it is invalid to pass a mix of valid and invalid copyfrom arguments.
There are six restrictions on the order in which the producer may use the batons:
open_directory
, add_directory
, open_file
, add_file
at most once on any given directory entry. delete_entry
may be called at most once on any given directory entry and may later be followed by add_directory
or add_file
on the same directory entry. delete_entry
may not be called on any directory entry after open_directory
, add_directory
, open_file
or add_file
has been called on that directory entry.open_directory
or add_directory
, it must specify the most recently opened of the currently open directory batons. Put another way, the producer cannot have two sibling directory batons open at the same time.change_dir_prop
on a directory either before opening any of the directory's subdirs or after closing them, but not in the middle.When the producer calls open_file
or add_file
, either:
(a) The producer must follow with any changes to the file (change_file_prop
and/or apply_textdelta
/ apply_textdelta_stream
, as applicable), followed by a close_file
call, before issuing any other file or directory calls, or
(b) The producer must follow with a change_file_prop
call if it is applicable, before issuing any other file or directory calls; later, after all directory batons including the root have been closed, the producer must issue apply_textdelta
/ apply_textdelta_stream
and close_file
calls.
apply_textdelta
, it must make all of the window handler calls (including the NULL
window at the end) before issuing any other svn_delta_editor_t calls.So, the producer needs to use directory and file batons as if it is doing a single depth-first traversal of the tree, with the exception that the producer may keep file batons open in order to make apply_textdelta
/ apply_textdelta_stream
calls at the end.
Many editor functions are invoked multiple times, in a sequence determined by the editor "driver". The driver is responsible for creating a pool for use on each iteration of the editor function, and clearing that pool between each iteration. The driver passes the appropriate pool on each function invocation.
Based on the requirement of calling the editor functions in a depth-first style, it is usually customary for the driver to similarly nest the pools. However, this is only a safety feature to ensure that pools associated with deeper items are always cleared when the top-level items are also cleared. The interface does not assume, nor require, any particular organization of the pools passed to these functions. In fact, if "postfix deltas" are used for files, the file pools definitely need to live outside the scope of their parent directories' pools.
Note that close_directory can be called before a file in that directory has been closed. That is, the directory's baton is closed before the file's baton. The implication is that apply_textdelta
/ apply_textdelta_stream
and close_file
should not refer to a parent directory baton UNLESS the editor has taken precautions to allocate it in a pool of the appropriate lifetime (the result_pool passed to open_directory
and add_directory
definitely does not have the proper lifetime). In general, it is recommended to simply avoid keeping a parent directory baton in a file baton.
At least one implementation of the editor interface is asynchronous; an error from one operation may be detected some number of operations later. As a result, an editor driver must not assume that an error from an editing function resulted from the particular operation being detected. Moreover, once an editing function (including close_edit
) returns an error, the edit is dead; the only further operation which may be called on the editor is abort_edit
.
svn_delta_editor_t* svn_delta_default_editor | ( | apr_pool_t * | pool | ) |
Return a default delta editor template, allocated in pool.
The editor functions in the template do only the most basic baton-swapping: each editor function that produces a baton does so by copying its incoming baton into the outgoing baton reference.
This editor is not intended to be useful by itself, but is meant to be the basis for a useful editor. After getting a default editor, you substitute in your own implementations for the editor functions you care about. The ones you don't care about, you don't have to implement – you can rely on the template's implementation to safely do nothing of consequence.
svn_error_t* svn_delta_depth_filter_editor | ( | const svn_delta_editor_t ** | editor, |
void ** | edit_baton, | ||
const svn_delta_editor_t * | wrapped_editor, | ||
void * | wrapped_edit_baton, | ||
svn_depth_t | requested_depth, | ||
svn_boolean_t | has_target, | ||
apr_pool_t * | pool | ||
) |
Set *editor and *edit_baton to an depth-based filtering editor that wraps wrapped_editor and wrapped_baton.
The editor will track the depth of this drive against the requested_depth, taking into account whether not the edit drive is making use of a target (via has_target), and forward editor calls which operate "within" the request depth range through to wrapped_editor.
requested_depth must be one of the following depth values: svn_depth_infinity, svn_depth_empty, svn_depth_files, svn_depth_immediates, or svn_depth_unknown.
If filtering is deemed unnecessary (or if requested_depth is svn_depth_unknown), *editor and *edit_baton will be set to wrapped_editor and wrapped_baton, respectively; otherwise, they'll be set to new objects allocated from pool.
delete_entry()
function doesn't carry node kind information, a depth-based filtering editor being asked to filter for svn_depth_files but receiving a delete_entry()
call on an immediate child of the editor's target is unable to know if that deletion should be allowed or filtered out – a delete of a top-level file is okay in this case, a delete of a top-level subdirectory is not. As such, this filtering editor takes a conservative approach, and ignores top-level deletion requests when filtering for svn_depth_files. Fortunately, most non-depth-aware (pre-1.5) Subversion editor drivers can be told to drive non-recursively (where non-recursive means essentially svn_depth_files), which means they won't transmit out-of-scope editor commands anyway.svn_error_t* svn_delta_get_cancellation_editor | ( | svn_cancel_func_t | cancel_func, |
void * | cancel_baton, | ||
const svn_delta_editor_t * | wrapped_editor, | ||
void * | wrapped_baton, | ||
const svn_delta_editor_t ** | editor, | ||
void ** | edit_baton, | ||
apr_pool_t * | pool | ||
) |
Set *editor and *edit_baton to a cancellation editor that wraps wrapped_editor and wrapped_baton.
The editor will call cancel_func with cancel_baton when each of its functions is called, continuing on to call the corresponding wrapped function if cancel_func returns SVN_NO_ERROR.
If cancel_func is NULL
, set *editor to wrapped_editor and *edit_baton to wrapped_baton.
svn_error_t* svn_delta_noop_window_handler | ( | svn_txdelta_window_t * | window, |
void * | baton | ||
) |
A text-delta window handler which does nothing.
Editors can return this handler from apply_textdelta
if they don't care about text delta windows.