diff options
Diffstat (limited to 'subversion/svnrdump/dump_editor.c')
-rw-r--r-- | subversion/svnrdump/dump_editor.c | 943 |
1 files changed, 665 insertions, 278 deletions
diff --git a/subversion/svnrdump/dump_editor.c b/subversion/svnrdump/dump_editor.c index c51becd..faf029f 100644 --- a/subversion/svnrdump/dump_editor.c +++ b/subversion/svnrdump/dump_editor.c @@ -30,9 +30,12 @@ #include "svn_subst.h" #include "svn_dirent_uri.h" -#include "private/svn_fspath.h" +#include "private/svn_subr_private.h" +#include "private/svn_dep_compat.h" +#include "private/svn_editor.h" #include "svnrdump.h" +#include <assert.h> #define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r)) @@ -49,24 +52,72 @@ struct dir_baton struct dump_edit_baton *eb; struct dir_baton *parent_dir_baton; + /* Pool for per-directory allocations */ + apr_pool_t *pool; + /* is this directory a new addition to this revision? */ svn_boolean_t added; /* has this directory been written to the output stream? */ svn_boolean_t written_out; - /* the absolute path to this directory */ - const char *abspath; /* an fspath */ + /* the path to this directory */ + const char *repos_relpath; /* a relpath */ /* Copyfrom info for the node, if any. */ const char *copyfrom_path; /* a relpath */ svn_revnum_t copyfrom_rev; + /* Properties which were modified during change_dir_prop. */ + apr_hash_t *props; + + /* Properties which were deleted during change_dir_prop. */ + apr_hash_t *deleted_props; + /* Hash of paths that need to be deleted, though some -might- be replaced. Maps const char * paths to this dir_baton. Note that they're full paths, because that's what the editor driver gives us, although they're all really within this directory. */ apr_hash_t *deleted_entries; + + /* Flags to trigger dumping props and record termination newlines. */ + svn_boolean_t dump_props; + svn_boolean_t dump_newlines; +}; + +/* A file baton used by all file-related callback functions in the dump + * editor */ +struct file_baton +{ + struct dump_edit_baton *eb; + struct dir_baton *parent_dir_baton; + + /* Pool for per-file allocations */ + apr_pool_t *pool; + + /* the path to this file */ + const char *repos_relpath; /* a relpath */ + + /* Properties which were modified during change_file_prop. */ + apr_hash_t *props; + + /* Properties which were deleted during change_file_prop. */ + apr_hash_t *deleted_props; + + /* The checksum of the file the delta is being applied to */ + const char *base_checksum; + + /* Copy state and source information (if any). */ + svn_boolean_t is_copy; + const char *copyfrom_path; + svn_revnum_t copyfrom_rev; + + /* The action associate with this node. */ + enum svn_node_action action; + + /* Flags to trigger dumping props and text. */ + svn_boolean_t dump_text; + svn_boolean_t dump_props; }; /* A handler baton to be used in window_handler(). */ @@ -81,20 +132,19 @@ struct dump_edit_baton { /* The output stream we write the dumpfile to */ svn_stream_t *stream; - /* Pool for per-revision allocations */ - apr_pool_t *pool; + /* A backdoor ra session to fetch additional information during the edit. */ + svn_ra_session_t *ra_session; - /* Properties which were modified during change_file_prop - * or change_dir_prop. */ - apr_hash_t *props; - - /* Properties which were deleted during change_file_prop - * or change_dir_prop. */ - apr_hash_t *deleted_props; + /* The repository relpath of the anchor of the editor when driven + via the RA update mechanism; NULL otherwise. (When the editor is + driven via the RA "replay" mechanism instead, the editor is + always anchored at the repository, we don't need to prepend an + anchor path to the dumped node paths, and open_root() doesn't + need to manufacture directory additions.) */ + const char *update_anchor_relpath; - /* Temporary buffer to write property hashes to in human-readable - * form. ### Is this really needed? */ - svn_stringbuf_t *propstring; + /* Pool for per-revision allocations */ + apr_pool_t *pool; /* Temporary file used for textdelta application along with its absolute path; these two variables should be allocated in the @@ -102,13 +152,14 @@ struct dump_edit_baton { const char *delta_abspath; apr_file_t *delta_file; - /* The checksum of the file the delta is being applied to */ - const char *base_checksum; + /* The revision we're currently dumping. */ + svn_revnum_t current_revision; - /* Flags to trigger dumping props and text */ - svn_boolean_t dump_text; - svn_boolean_t dump_props; - svn_boolean_t dump_newlines; + /* The kind (file or directory) and baton of the item whose block of + dump stream data has not been fully completed; NULL if there's no + such item. */ + svn_node_kind_t pending_kind; + void *pending_baton; }; /* Make a directory baton to represent the directory at PATH (relative @@ -119,32 +170,28 @@ struct dump_edit_baton { * information is valid, the directory will be compared against its * copy source. * - * PARENT_DIR_BATON is the directory baton of this directory's parent, - * or NULL if this is the top-level directory of the edit. ADDED - * indicates if this directory is newly added in this revision. - * Perform all allocations in POOL. */ + * PB is the directory baton of this directory's parent, or NULL if + * this is the top-level directory of the edit. ADDED indicates if + * this directory is newly added in this revision. Perform all + * allocations in POOL. */ static struct dir_baton * make_dir_baton(const char *path, const char *copyfrom_path, svn_revnum_t copyfrom_rev, void *edit_baton, - void *parent_dir_baton, + struct dir_baton *pb, svn_boolean_t added, apr_pool_t *pool) { struct dump_edit_baton *eb = edit_baton; - struct dir_baton *pb = parent_dir_baton; struct dir_baton *new_db = apr_pcalloc(pool, sizeof(*new_db)); - const char *abspath; + const char *repos_relpath; /* Construct the full path of this node. */ - /* ### FIXME: Not sure why we use an abspath here. If I understand - ### correctly, the only place we used this path is in dump_node(), - ### which immediately converts it into a relpath. -- cmpilato. */ if (pb) - abspath = svn_fspath__canonicalize(path, pool); + repos_relpath = svn_relpath_canonicalize(path, pool); else - abspath = "/"; + repos_relpath = ""; /* Strip leading slash from copyfrom_path so that the path is canonical and svn_relpath_join can be used */ @@ -153,68 +200,133 @@ make_dir_baton(const char *path, new_db->eb = eb; new_db->parent_dir_baton = pb; - new_db->abspath = abspath; - new_db->copyfrom_path = copyfrom_path; + new_db->pool = pool; + new_db->repos_relpath = repos_relpath; + new_db->copyfrom_path = copyfrom_path + ? svn_relpath_canonicalize(copyfrom_path, pool) + : NULL; new_db->copyfrom_rev = copyfrom_rev; new_db->added = added; new_db->written_out = FALSE; + new_db->props = apr_hash_make(pool); + new_db->deleted_props = apr_hash_make(pool); new_db->deleted_entries = apr_hash_make(pool); return new_db; } -/* Extract and dump properties stored in edit baton EB, using POOL for - * any temporary allocations. If TRIGGER_VAR is not NULL, it is set to FALSE. - * Unless DUMP_DATA_TOO is set, only property headers are dumped. - */ +/* Make a file baton to represent the directory at PATH (relative to + * PB->eb). PB is the directory baton of this directory's parent, or + * NULL if this is the top-level directory of the edit. Perform all + * allocations in POOL. */ +static struct file_baton * +make_file_baton(const char *path, + struct dir_baton *pb, + apr_pool_t *pool) +{ + struct file_baton *new_fb = apr_pcalloc(pool, sizeof(*new_fb)); + + new_fb->eb = pb->eb; + new_fb->parent_dir_baton = pb; + new_fb->pool = pool; + new_fb->repos_relpath = svn_relpath_canonicalize(path, pool); + new_fb->props = apr_hash_make(pool); + new_fb->deleted_props = apr_hash_make(pool); + new_fb->is_copy = FALSE; + new_fb->copyfrom_path = NULL; + new_fb->copyfrom_rev = SVN_INVALID_REVNUM; + new_fb->action = svn_node_action_change; + + return new_fb; +} + +/* Return in *HEADER and *CONTENT the headers and content for PROPS. */ static svn_error_t * -do_dump_props(struct dump_edit_baton *eb, - svn_boolean_t *trigger_var, - svn_boolean_t dump_data_too, - apr_pool_t *pool) +get_props_content(svn_stringbuf_t **header, + svn_stringbuf_t **content, + apr_hash_t *props, + apr_hash_t *deleted_props, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { - svn_stream_t *propstream; + svn_stream_t *content_stream; apr_hash_t *normal_props; + const char *buf; - if (trigger_var && !*trigger_var) - return SVN_NO_ERROR; + *content = svn_stringbuf_create_empty(result_pool); + *header = svn_stringbuf_create_empty(result_pool); - SVN_ERR(svn_rdump__normalize_props(&normal_props, eb->props, eb->pool)); - svn_stringbuf_setempty(eb->propstring); - propstream = svn_stream_from_stringbuf(eb->propstring, eb->pool); - SVN_ERR(svn_hash_write_incremental(normal_props, eb->deleted_props, - propstream, "PROPS-END", pool)); - SVN_ERR(svn_stream_close(propstream)); + content_stream = svn_stream_from_stringbuf(*content, scratch_pool); + + SVN_ERR(svn_rdump__normalize_props(&normal_props, props, scratch_pool)); + SVN_ERR(svn_hash_write_incremental(normal_props, deleted_props, + content_stream, "PROPS-END", + scratch_pool)); + SVN_ERR(svn_stream_close(content_stream)); /* Prop-delta: true */ - SVN_ERR(svn_stream_printf(eb->stream, pool, - SVN_REPOS_DUMPFILE_PROP_DELTA - ": true\n")); + *header = svn_stringbuf_createf(result_pool, SVN_REPOS_DUMPFILE_PROP_DELTA + ": true\n"); /* Prop-content-length: 193 */ - SVN_ERR(svn_stream_printf(eb->stream, pool, - SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH - ": %" APR_SIZE_T_FMT "\n", eb->propstring->len)); + buf = apr_psprintf(scratch_pool, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH + ": %" APR_SIZE_T_FMT "\n", (*content)->len); + svn_stringbuf_appendcstr(*header, buf); + + return SVN_NO_ERROR; +} + +/* Extract and dump properties stored in PROPS and property deletions + * stored in DELETED_PROPS. If TRIGGER_VAR is not NULL, it is set to + * FALSE. + * + * If PROPSTRING is non-NULL, set *PROPSTRING to a string containing + * the content block of the property changes; otherwise, dump that to + * the stream, too. + */ +static svn_error_t * +do_dump_props(svn_stringbuf_t **propstring, + svn_stream_t *stream, + apr_hash_t *props, + apr_hash_t *deleted_props, + svn_boolean_t *trigger_var, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_stringbuf_t *header; + svn_stringbuf_t *content; + apr_size_t len; - if (dump_data_too) + if (trigger_var && !*trigger_var) + return SVN_NO_ERROR; + + SVN_ERR(get_props_content(&header, &content, props, deleted_props, + result_pool, scratch_pool)); + len = header->len; + SVN_ERR(svn_stream_write(stream, header->data, &len)); + + if (propstring) + { + *propstring = content; + } + else { /* Content-length: 14 */ - SVN_ERR(svn_stream_printf(eb->stream, pool, + SVN_ERR(svn_stream_printf(stream, scratch_pool, SVN_REPOS_DUMPFILE_CONTENT_LENGTH ": %" APR_SIZE_T_FMT "\n\n", - eb->propstring->len)); + content->len)); - /* The properties. */ - SVN_ERR(svn_stream_write(eb->stream, eb->propstring->data, - &(eb->propstring->len))); + len = content->len; + SVN_ERR(svn_stream_write(stream, content->data, &len)); /* No text is going to be dumped. Write a couple of newlines and wait for the next node/ revision. */ - SVN_ERR(svn_stream_printf(eb->stream, pool, "\n\n")); + SVN_ERR(svn_stream_puts(stream, "\n\n")); /* Cleanup so that data is never dumped twice. */ - SVN_ERR(svn_hash__clear(eb->props, eb->pool)); - SVN_ERR(svn_hash__clear(eb->deleted_props, eb->pool)); + apr_hash_clear(props); + apr_hash_clear(deleted_props); if (trigger_var) *trigger_var = FALSE; } @@ -229,7 +341,7 @@ do_dump_newlines(struct dump_edit_baton *eb, { if (trigger_var && *trigger_var) { - SVN_ERR(svn_stream_printf(eb->stream, pool, "\n\n")); + SVN_ERR(svn_stream_puts(eb->stream, "\n\n")); *trigger_var = FALSE; } return SVN_NO_ERROR; @@ -248,30 +360,36 @@ do_dump_newlines(struct dump_edit_baton *eb, */ static svn_error_t * dump_node(struct dump_edit_baton *eb, - const char *path, /* an absolute path. */ - svn_node_kind_t kind, + const char *repos_relpath, + struct dir_baton *db, + struct file_baton *fb, enum svn_node_action action, svn_boolean_t is_copy, const char *copyfrom_path, svn_revnum_t copyfrom_rev, apr_pool_t *pool) { - /* Remove leading slashes from path and copyfrom_path */ - if (path) - path = svn_relpath_canonicalize(path, pool); + const char *node_relpath = repos_relpath; - if (copyfrom_path) - copyfrom_path = svn_relpath_canonicalize(copyfrom_path, pool); + assert(svn_relpath_is_canonical(repos_relpath)); + assert(!copyfrom_path || svn_relpath_is_canonical(copyfrom_path)); + assert(! (db && fb)); - /* Node-path: commons/STATUS */ + /* Add the edit root relpath prefix if necessary. */ + if (eb->update_anchor_relpath) + node_relpath = svn_relpath_join(eb->update_anchor_relpath, + node_relpath, pool); + + /* Node-path: ... */ SVN_ERR(svn_stream_printf(eb->stream, pool, - SVN_REPOS_DUMPFILE_NODE_PATH ": %s\n", path)); + SVN_REPOS_DUMPFILE_NODE_PATH ": %s\n", + node_relpath)); - /* Node-kind: file */ - if (kind == svn_node_file) + /* Node-kind: "file" | "dir" */ + if (fb) SVN_ERR(svn_stream_printf(eb->stream, pool, SVN_REPOS_DUMPFILE_NODE_KIND ": file\n")); - else if (kind == svn_node_dir) + else if (db) SVN_ERR(svn_stream_printf(eb->stream, pool, SVN_REPOS_DUMPFILE_NODE_KIND ": dir\n")); @@ -282,88 +400,99 @@ dump_node(struct dump_edit_baton *eb, case svn_node_action_change: /* We are here after a change_file_prop or change_dir_prop. They set up whatever dump_props they needed to- nothing to - do here but print node action information */ - SVN_ERR(svn_stream_printf(eb->stream, pool, - SVN_REPOS_DUMPFILE_NODE_ACTION - ": change\n")); + do here but print node action information. + + Node-action: change. */ + SVN_ERR(svn_stream_puts(eb->stream, + SVN_REPOS_DUMPFILE_NODE_ACTION ": change\n")); break; case svn_node_action_replace: - if (!is_copy) + if (is_copy) + { + /* Delete the original, and then re-add the replacement as a + copy using recursive calls into this function. */ + SVN_ERR(dump_node(eb, repos_relpath, db, fb, svn_node_action_delete, + FALSE, NULL, SVN_INVALID_REVNUM, pool)); + SVN_ERR(dump_node(eb, repos_relpath, db, fb, svn_node_action_add, + is_copy, copyfrom_path, copyfrom_rev, pool)); + } + else { /* Node-action: replace */ - SVN_ERR(svn_stream_printf(eb->stream, pool, - SVN_REPOS_DUMPFILE_NODE_ACTION - ": replace\n")); + SVN_ERR(svn_stream_puts(eb->stream, + SVN_REPOS_DUMPFILE_NODE_ACTION + ": replace\n")); /* Wait for a change_*_prop to be called before dumping anything */ - eb->dump_props = TRUE; - break; + if (fb) + fb->dump_props = TRUE; + else if (db) + db->dump_props = TRUE; } - /* More complex case: is_copy is true, and copyfrom_path/ - copyfrom_rev are present: delete the original, and then re-add - it */ - - SVN_ERR(svn_stream_printf(eb->stream, pool, - SVN_REPOS_DUMPFILE_NODE_ACTION - ": delete\n\n")); - - /* Recurse: Print an additional add-with-history record. */ - SVN_ERR(dump_node(eb, path, kind, svn_node_action_add, - is_copy, copyfrom_path, copyfrom_rev, pool)); - - /* We can leave this routine quietly now, don't need to dump any - content; that was already done in the second record. */ break; case svn_node_action_delete: - SVN_ERR(svn_stream_printf(eb->stream, pool, - SVN_REPOS_DUMPFILE_NODE_ACTION - ": delete\n")); + /* Node-action: delete */ + SVN_ERR(svn_stream_puts(eb->stream, + SVN_REPOS_DUMPFILE_NODE_ACTION ": delete\n")); /* We can leave this routine quietly now. Nothing more to do- print a couple of newlines because we're not dumping props or text. */ - SVN_ERR(svn_stream_printf(eb->stream, pool, "\n\n")); + SVN_ERR(svn_stream_puts(eb->stream, "\n\n")); + break; case svn_node_action_add: - SVN_ERR(svn_stream_printf(eb->stream, pool, - SVN_REPOS_DUMPFILE_NODE_ACTION ": add\n")); + /* Node-action: add */ + SVN_ERR(svn_stream_puts(eb->stream, + SVN_REPOS_DUMPFILE_NODE_ACTION ": add\n")); - if (!is_copy) + if (is_copy) { - /* eb->dump_props for files is handled in close_file - which is called immediately. However, directories are not - closed until all the work inside them has been done; - eb->dump_props for directories is handled in all the - functions that can possibly be called after add_directory: - add_directory, open_directory, delete_entry, close_directory, - add_file, open_file. change_dir_prop is a special case. */ - - /* Wait for a change_*_prop to be called before dumping - anything */ - eb->dump_props = TRUE; - break; + /* Node-copyfrom-rev / Node-copyfrom-path */ + SVN_ERR(svn_stream_printf(eb->stream, pool, + SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV + ": %ld\n" + SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH + ": %s\n", + copyfrom_rev, copyfrom_path)); + + /* Ugly hack: If a directory was copied from a previous + revision, nothing like close_file() will be called to write two + blank lines. If change_dir_prop() is called, props are dumped + (along with the necessary PROPS-END\n\n and we're good. So + set DUMP_NEWLINES here to print the newlines unless + change_dir_prop() is called next otherwise the `svnadmin load` + parser will fail. */ + if (db) + db->dump_newlines = TRUE; + } + else + { + /* fb->dump_props (for files) is handled in close_file() + which is called immediately. + + However, directories are not closed until all the work + inside them has been done; db->dump_props (for directories) + is handled (via dump_pending()) in all the functions that + can possibly be called after add_directory(): + + - add_directory() + - open_directory() + - delete_entry() + - close_directory() + - add_file() + - open_file() + + change_dir_prop() is a special case. */ + if (fb) + fb->dump_props = TRUE; + else if (db) + db->dump_props = TRUE; } - - SVN_ERR(svn_stream_printf(eb->stream, pool, - SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV - ": %ld\n" - SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH - ": %s\n", - copyfrom_rev, copyfrom_path)); - - /* Ugly hack: If a directory was copied from a previous - revision, nothing like close_file() will be called to write two - blank lines. If change_dir_prop() is called, props are dumped - (along with the necessary PROPS-END\n\n and we're good. So - set DUMP_NEWLINES here to print the newlines unless - change_dir_prop() is called next otherwise the `svnadmin load` - parser will fail. */ - if (kind == svn_node_dir) - eb->dump_newlines = TRUE; break; } @@ -371,24 +500,156 @@ dump_node(struct dump_edit_baton *eb, } static svn_error_t * +dump_mkdir(struct dump_edit_baton *eb, + const char *repos_relpath, + apr_pool_t *pool) +{ + svn_stringbuf_t *prop_header, *prop_content; + apr_size_t len; + const char *buf; + + /* Node-path: ... */ + SVN_ERR(svn_stream_printf(eb->stream, pool, + SVN_REPOS_DUMPFILE_NODE_PATH ": %s\n", + repos_relpath)); + + /* Node-kind: dir */ + SVN_ERR(svn_stream_printf(eb->stream, pool, + SVN_REPOS_DUMPFILE_NODE_KIND ": dir\n")); + + /* Node-action: add */ + SVN_ERR(svn_stream_puts(eb->stream, + SVN_REPOS_DUMPFILE_NODE_ACTION ": add\n")); + + /* Dump the (empty) property block. */ + SVN_ERR(get_props_content(&prop_header, &prop_content, + apr_hash_make(pool), apr_hash_make(pool), + pool, pool)); + len = prop_header->len; + SVN_ERR(svn_stream_write(eb->stream, prop_header->data, &len)); + len = prop_content->len; + buf = apr_psprintf(pool, SVN_REPOS_DUMPFILE_CONTENT_LENGTH + ": %" APR_SIZE_T_FMT "\n", len); + SVN_ERR(svn_stream_puts(eb->stream, buf)); + SVN_ERR(svn_stream_puts(eb->stream, "\n")); + SVN_ERR(svn_stream_write(eb->stream, prop_content->data, &len)); + + /* Newlines to tie it all off. */ + SVN_ERR(svn_stream_puts(eb->stream, "\n\n")); + + return SVN_NO_ERROR; +} + +/* Dump pending items from the specified node, to allow starting the dump + of a child node */ +static svn_error_t * +dump_pending(struct dump_edit_baton *eb, + apr_pool_t *scratch_pool) +{ + if (! eb->pending_baton) + return SVN_NO_ERROR; + + if (eb->pending_kind == svn_node_dir) + { + struct dir_baton *db = eb->pending_baton; + + /* Some pending properties to dump? */ + SVN_ERR(do_dump_props(NULL, eb->stream, db->props, db->deleted_props, + &(db->dump_props), db->pool, scratch_pool)); + + /* Some pending newlines to dump? */ + SVN_ERR(do_dump_newlines(eb, &(db->dump_newlines), scratch_pool)); + } + else if (eb->pending_kind == svn_node_file) + { + struct file_baton *fb = eb->pending_baton; + + /* Some pending properties to dump? */ + SVN_ERR(do_dump_props(NULL, eb->stream, fb->props, fb->deleted_props, + &(fb->dump_props), fb->pool, scratch_pool)); + } + else + abort(); + + /* Anything that was pending is pending no longer. */ + eb->pending_baton = NULL; + eb->pending_kind = svn_node_none; + + return SVN_NO_ERROR; +} + + + +/*** Editor Function Implementations ***/ + +static svn_error_t * open_root(void *edit_baton, svn_revnum_t base_revision, apr_pool_t *pool, void **root_baton) { struct dump_edit_baton *eb = edit_baton; + struct dir_baton *new_db = NULL; /* Clear the per-revision pool after each revision */ svn_pool_clear(eb->pool); - eb->props = apr_hash_make(eb->pool); - eb->deleted_props = apr_hash_make(eb->pool); - eb->propstring = svn_stringbuf_create("", eb->pool); - - *root_baton = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM, - edit_baton, NULL, FALSE, eb->pool); LDR_DBG(("open_root %p\n", *root_baton)); + if (eb->update_anchor_relpath) + { + int i; + const char *parent_path = eb->update_anchor_relpath; + apr_array_header_t *dirs_to_add = + apr_array_make(pool, 4, sizeof(const char *)); + apr_pool_t *iterpool = svn_pool_create(pool); + + while (! svn_path_is_empty(parent_path)) + { + APR_ARRAY_PUSH(dirs_to_add, const char *) = parent_path; + parent_path = svn_relpath_dirname(parent_path, pool); + } + + for (i = dirs_to_add->nelts; i; --i) + { + const char *dir_to_add = + APR_ARRAY_IDX(dirs_to_add, i - 1, const char *); + + svn_pool_clear(iterpool); + + /* For parents of the source directory, we just manufacture + the adds ourselves. */ + if (i > 1) + { + SVN_ERR(dump_mkdir(eb, dir_to_add, iterpool)); + } + else + { + /* ... but for the source directory itself, we'll defer + to letting the typical plumbing handle this task. */ + new_db = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM, + edit_baton, NULL, TRUE, pool); + SVN_ERR(dump_node(eb, new_db->repos_relpath, new_db, + NULL, svn_node_action_add, FALSE, + NULL, SVN_INVALID_REVNUM, pool)); + + /* Remember that we've started but not yet finished + handling this directory. */ + new_db->written_out = TRUE; + eb->pending_baton = new_db; + eb->pending_kind = svn_node_dir; + } + } + svn_pool_destroy(iterpool); + } + + if (! new_db) + { + new_db = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM, + edit_baton, NULL, FALSE, pool); + } + + *root_baton = new_db; return SVN_NO_ERROR; } @@ -402,16 +663,13 @@ delete_entry(const char *path, LDR_DBG(("delete_entry %s\n", path)); - /* Some pending properties to dump? */ - SVN_ERR(do_dump_props(pb->eb, &(pb->eb->dump_props), TRUE, pool)); - - /* Some pending newlines to dump? */ - SVN_ERR(do_dump_newlines(pb->eb, &(pb->eb->dump_newlines), pool)); + SVN_ERR(dump_pending(pb->eb, pool)); - /* Add this path to the deleted_entries of the parent directory - baton. */ - apr_hash_set(pb->deleted_entries, apr_pstrdup(pb->eb->pool, path), - APR_HASH_KEY_STRING, pb); + /* We don't dump this deletion immediate. Rather, we add this path + to the deleted_entries of the parent directory baton. That way, + we can tell (later) an addition from a replacement. All the real + deletions get handled in close_directory(). */ + svn_hash_sets(pb->deleted_entries, apr_pstrdup(pb->eb->pool, path), pb); return SVN_NO_ERROR; } @@ -431,35 +689,34 @@ add_directory(const char *path, LDR_DBG(("add_directory %s\n", path)); + SVN_ERR(dump_pending(pb->eb, pool)); + new_db = make_dir_baton(path, copyfrom_path, copyfrom_rev, pb->eb, pb, TRUE, pb->eb->pool); - /* Some pending properties to dump? */ - SVN_ERR(do_dump_props(pb->eb, &(pb->eb->dump_props), TRUE, pool)); - - /* Some pending newlines to dump? */ - SVN_ERR(do_dump_newlines(pb->eb, &(pb->eb->dump_newlines), pool)); - /* This might be a replacement -- is the path already deleted? */ - val = apr_hash_get(pb->deleted_entries, path, APR_HASH_KEY_STRING); + val = svn_hash_gets(pb->deleted_entries, path); /* Detect an add-with-history */ is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev); /* Dump the node */ - SVN_ERR(dump_node(pb->eb, path, - svn_node_dir, + SVN_ERR(dump_node(pb->eb, new_db->repos_relpath, new_db, NULL, val ? svn_node_action_replace : svn_node_action_add, is_copy, - is_copy ? copyfrom_path : NULL, + is_copy ? new_db->copyfrom_path : NULL, is_copy ? copyfrom_rev : SVN_INVALID_REVNUM, pool)); if (val) /* Delete the path, it's now been dumped */ - apr_hash_set(pb->deleted_entries, path, APR_HASH_KEY_STRING, NULL); + svn_hash_sets(pb->deleted_entries, path, NULL); + /* Remember that we've started, but not yet finished handling this + directory. */ new_db->written_out = TRUE; + pb->eb->pending_baton = new_db; + pb->eb->pending_kind = svn_node_dir; *child_baton = new_db; return SVN_NO_ERROR; @@ -479,11 +736,7 @@ open_directory(const char *path, LDR_DBG(("open_directory %s\n", path)); - /* Some pending properties to dump? */ - SVN_ERR(do_dump_props(pb->eb, &(pb->eb->dump_props), TRUE, pool)); - - /* Some pending newlines to dump? */ - SVN_ERR(do_dump_newlines(pb->eb, &(pb->eb->dump_newlines), pool)); + SVN_ERR(dump_pending(pb->eb, pool)); /* If the parent directory has explicit comparison path and rev, record the same for this one. */ @@ -497,6 +750,7 @@ open_directory(const char *path, new_db = make_dir_baton(path, copyfrom_path, copyfrom_rev, pb->eb, pb, FALSE, pb->eb->pool); + *child_baton = new_db; return SVN_NO_ERROR; } @@ -506,16 +760,34 @@ close_directory(void *dir_baton, apr_pool_t *pool) { struct dir_baton *db = dir_baton; - struct dump_edit_baton *eb = db->eb; apr_hash_index_t *hi; + svn_boolean_t this_pending; LDR_DBG(("close_directory %p\n", dir_baton)); - /* Some pending properties to dump? */ - SVN_ERR(do_dump_props(eb, &(eb->dump_props), TRUE, pool)); + /* Remember if this directory is the one currently pending. */ + this_pending = (db->eb->pending_baton == db); - /* Some pending newlines to dump? */ - SVN_ERR(do_dump_newlines(eb, &(eb->dump_newlines), pool)); + SVN_ERR(dump_pending(db->eb, pool)); + + /* If this directory was pending, then dump_pending() should have + taken care of all the props and such. Of course, the only way + that would be the case is if this directory was added/replaced. + + Otherwise, if stuff for this directory has already been written + out (at some point in the past, prior to our handling other + nodes), we might need to generate a second "change" record just + to carry the information we've since learned about the + directory. */ + if ((! this_pending) && (db->dump_props)) + { + SVN_ERR(dump_node(db->eb, db->repos_relpath, db, NULL, + svn_node_action_change, FALSE, + NULL, SVN_INVALID_REVNUM, pool)); + db->eb->pending_baton = db; + db->eb->pending_kind = svn_node_dir; + SVN_ERR(dump_pending(db->eb, pool)); + } /* Dump the deleted directory entries */ for (hi = apr_hash_first(pool, db->deleted_entries); hi; @@ -523,11 +795,13 @@ close_directory(void *dir_baton, { const char *path = svn__apr_hash_index_key(hi); - SVN_ERR(dump_node(db->eb, path, svn_node_unknown, svn_node_action_delete, + SVN_ERR(dump_node(db->eb, path, NULL, NULL, svn_node_action_delete, FALSE, NULL, SVN_INVALID_REVNUM, pool)); } - SVN_ERR(svn_hash__clear(db->deleted_entries, pool)); + /* ### should be unnecessary */ + apr_hash_clear(db->deleted_entries); + return SVN_NO_ERROR; } @@ -540,40 +814,33 @@ add_file(const char *path, void **file_baton) { struct dir_baton *pb = parent_baton; + struct file_baton *fb; void *val; - svn_boolean_t is_copy; LDR_DBG(("add_file %s\n", path)); - /* Some pending properties to dump? */ - SVN_ERR(do_dump_props(pb->eb, &(pb->eb->dump_props), TRUE, pool)); + SVN_ERR(dump_pending(pb->eb, pool)); - /* Some pending newlines to dump? */ - SVN_ERR(do_dump_newlines(pb->eb, &(pb->eb->dump_newlines), pool)); + /* Make the file baton. */ + fb = make_file_baton(path, pb, pool); /* This might be a replacement -- is the path already deleted? */ - val = apr_hash_get(pb->deleted_entries, path, APR_HASH_KEY_STRING); + val = svn_hash_gets(pb->deleted_entries, path); /* Detect add-with-history. */ - is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev); - - /* Dump the node. */ - SVN_ERR(dump_node(pb->eb, path, - svn_node_file, - val ? svn_node_action_replace : svn_node_action_add, - is_copy, - is_copy ? copyfrom_path : NULL, - is_copy ? copyfrom_rev : SVN_INVALID_REVNUM, - pool)); + if (ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev)) + { + fb->copyfrom_path = svn_relpath_canonicalize(copyfrom_path, fb->pool); + fb->copyfrom_rev = copyfrom_rev; + fb->is_copy = TRUE; + } + fb->action = val ? svn_node_action_replace : svn_node_action_add; + /* Delete the path, it's now been dumped. */ if (val) - /* delete the path, it's now been dumped. */ - apr_hash_set(pb->deleted_entries, path, APR_HASH_KEY_STRING, NULL); - - /* Build a nice file baton to pass to change_file_prop and - apply_textdelta */ - *file_baton = pb->eb; + svn_hash_sets(pb->deleted_entries, path, NULL); + *file_baton = fb; return SVN_NO_ERROR; } @@ -585,34 +852,26 @@ open_file(const char *path, void **file_baton) { struct dir_baton *pb = parent_baton; - const char *copyfrom_path = NULL; - svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM; + struct file_baton *fb; LDR_DBG(("open_file %s\n", path)); - /* Some pending properties to dump? */ - SVN_ERR(do_dump_props(pb->eb, &(pb->eb->dump_props), TRUE, pool)); + SVN_ERR(dump_pending(pb->eb, pool)); - /* Some pending newlines to dump? */ - SVN_ERR(do_dump_newlines(pb->eb, &(pb->eb->dump_newlines), pool)); + /* Make the file baton. */ + fb = make_file_baton(path, pb, pool); /* If the parent directory has explicit copyfrom path and rev, record the same for this one. */ if (ARE_VALID_COPY_ARGS(pb->copyfrom_path, pb->copyfrom_rev)) { - copyfrom_path = svn_relpath_join(pb->copyfrom_path, - svn_relpath_basename(path, NULL), - pb->eb->pool); - copyfrom_rev = pb->copyfrom_rev; + fb->copyfrom_path = svn_relpath_join(pb->copyfrom_path, + svn_relpath_basename(path, NULL), + pb->eb->pool); + fb->copyfrom_rev = pb->copyfrom_rev; } - SVN_ERR(dump_node(pb->eb, path, svn_node_file, svn_node_action_change, - FALSE, copyfrom_path, copyfrom_rev, pool)); - - /* Build a nice file baton to pass to change_file_prop and - apply_textdelta */ - *file_baton = pb->eb; - + *file_baton = fb; return SVN_NO_ERROR; } @@ -623,37 +882,30 @@ change_dir_prop(void *parent_baton, apr_pool_t *pool) { struct dir_baton *db = parent_baton; + svn_boolean_t this_pending; LDR_DBG(("change_dir_prop %p\n", parent_baton)); - if (svn_property_kind(NULL, name) != svn_prop_regular_kind) + /* This directory is not pending, but something else is, so handle + the "something else". */ + this_pending = (db->eb->pending_baton == db); + if (! this_pending) + SVN_ERR(dump_pending(db->eb, pool)); + + if (svn_property_kind2(name) != svn_prop_regular_kind) return SVN_NO_ERROR; if (value) - apr_hash_set(db->eb->props, apr_pstrdup(db->eb->pool, name), - APR_HASH_KEY_STRING, svn_string_dup(value, db->eb->pool)); + svn_hash_sets(db->props, + apr_pstrdup(db->pool, name), + svn_string_dup(value, db->pool)); else - apr_hash_set(db->eb->deleted_props, apr_pstrdup(db->eb->pool, name), - APR_HASH_KEY_STRING, ""); - - if (! db->written_out) - { - /* If db->written_out is set, it means that the node information - corresponding to this directory has already been written: don't - do anything; do_dump_props() will take care of dumping the - props. If it not, dump the node itself before dumping the - props. */ - - SVN_ERR(dump_node(db->eb, db->abspath, svn_node_dir, - svn_node_action_change, FALSE, db->copyfrom_path, - db->copyfrom_rev, pool)); - db->written_out = TRUE; - } + svn_hash_sets(db->deleted_props, apr_pstrdup(db->pool, name), ""); /* Make sure we eventually output the props, and disable printing a couple of extra newlines */ - db->eb->dump_newlines = FALSE; - db->eb->dump_props = TRUE; + db->dump_newlines = FALSE; + db->dump_props = TRUE; return SVN_NO_ERROR; } @@ -664,24 +916,24 @@ change_file_prop(void *file_baton, const svn_string_t *value, apr_pool_t *pool) { - struct dump_edit_baton *eb = file_baton; + struct file_baton *fb = file_baton; LDR_DBG(("change_file_prop %p\n", file_baton)); - if (svn_property_kind(NULL, name) != svn_prop_regular_kind) + if (svn_property_kind2(name) != svn_prop_regular_kind) return SVN_NO_ERROR; if (value) - apr_hash_set(eb->props, apr_pstrdup(eb->pool, name), - APR_HASH_KEY_STRING, svn_string_dup(value, eb->pool)); + svn_hash_sets(fb->props, + apr_pstrdup(fb->pool, name), + svn_string_dup(value, fb->pool)); else - apr_hash_set(eb->deleted_props, apr_pstrdup(eb->pool, name), - APR_HASH_KEY_STRING, ""); + svn_hash_sets(fb->deleted_props, apr_pstrdup(fb->pool, name), ""); /* Dump the property headers and wait; close_file might need to write text headers too depending on whether apply_textdelta is called */ - eb->dump_props = TRUE; + fb->dump_props = TRUE; return SVN_NO_ERROR; } @@ -708,17 +960,17 @@ apply_textdelta(void *file_baton, const char *base_checksum, svn_txdelta_window_handler_t *handler, void **handler_baton) { - struct dump_edit_baton *eb = file_baton; - - /* Custom handler_baton allocated in a separate pool */ + struct file_baton *fb = file_baton; + struct dump_edit_baton *eb = fb->eb; struct handler_baton *hb; svn_stream_t *delta_filestream; - hb = apr_pcalloc(eb->pool, sizeof(*hb)); - LDR_DBG(("apply_textdelta %p\n", file_baton)); - /* Use a temporary file to measure the text-content-length */ + /* This is custom handler_baton, allocated from a separate pool. */ + hb = apr_pcalloc(eb->pool, sizeof(*hb)); + + /* Use a temporary file to measure the Text-content-length */ delta_filestream = svn_stream_from_aprfile2(eb->delta_file, TRUE, pool); /* Prepare to write the delta to the delta_filestream */ @@ -726,9 +978,9 @@ apply_textdelta(void *file_baton, const char *base_checksum, delta_filestream, 0, SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool); - eb->dump_text = TRUE; - eb->base_checksum = apr_pstrdup(eb->pool, base_checksum); - SVN_ERR(svn_stream_close(delta_filestream)); + /* Record that there's text to be dumped, and its base checksum. */ + fb->dump_text = TRUE; + fb->base_checksum = apr_pstrdup(eb->pool, base_checksum); /* The actual writing takes place when this function has finished. Set handler and handler_baton now so for @@ -744,35 +996,46 @@ close_file(void *file_baton, const char *text_checksum, apr_pool_t *pool) { - struct dump_edit_baton *eb = file_baton; + struct file_baton *fb = file_baton; + struct dump_edit_baton *eb = fb->eb; apr_finfo_t *info = apr_pcalloc(pool, sizeof(apr_finfo_t)); + svn_stringbuf_t *propstring; LDR_DBG(("close_file %p\n", file_baton)); - /* Some pending properties to dump? Dump just the headers- dump the - props only after dumping the text headers too (if present) */ - SVN_ERR(do_dump_props(eb, &(eb->dump_props), FALSE, pool)); + SVN_ERR(dump_pending(eb, pool)); + + /* Dump the node. */ + SVN_ERR(dump_node(eb, fb->repos_relpath, NULL, fb, + fb->action, fb->is_copy, fb->copyfrom_path, + fb->copyfrom_rev, pool)); + + /* Some pending properties to dump? We'll dump just the headers for + now, then dump the actual propchange content only after dumping + the text headers too (if present). */ + SVN_ERR(do_dump_props(&propstring, eb->stream, fb->props, fb->deleted_props, + &(fb->dump_props), pool, pool)); /* Dump the text headers */ - if (eb->dump_text) + if (fb->dump_text) { apr_status_t err; /* Text-delta: true */ - SVN_ERR(svn_stream_printf(eb->stream, pool, - SVN_REPOS_DUMPFILE_TEXT_DELTA - ": true\n")); + SVN_ERR(svn_stream_puts(eb->stream, + SVN_REPOS_DUMPFILE_TEXT_DELTA + ": true\n")); err = apr_file_info_get(info, APR_FINFO_SIZE, eb->delta_file); if (err) SVN_ERR(svn_error_wrap_apr(err, NULL)); - if (eb->base_checksum) + if (fb->base_checksum) /* Text-delta-base-md5: */ SVN_ERR(svn_stream_printf(eb->stream, pool, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5 ": %s\n", - eb->base_checksum)); + fb->base_checksum)); /* Text-content-length: 39 */ SVN_ERR(svn_stream_printf(eb->stream, pool, @@ -789,31 +1052,31 @@ close_file(void *file_baton, /* Content-length: 1549 */ /* If both text and props are absent, skip this header */ - if (eb->dump_props) + if (fb->dump_props) SVN_ERR(svn_stream_printf(eb->stream, pool, SVN_REPOS_DUMPFILE_CONTENT_LENGTH ": %ld\n\n", - (unsigned long)info->size + eb->propstring->len)); - else if (eb->dump_text) + (unsigned long)info->size + propstring->len)); + else if (fb->dump_text) SVN_ERR(svn_stream_printf(eb->stream, pool, SVN_REPOS_DUMPFILE_CONTENT_LENGTH ": %ld\n\n", (unsigned long)info->size)); /* Dump the props now */ - if (eb->dump_props) + if (fb->dump_props) { - SVN_ERR(svn_stream_write(eb->stream, eb->propstring->data, - &(eb->propstring->len))); + SVN_ERR(svn_stream_write(eb->stream, propstring->data, + &(propstring->len))); /* Cleanup */ - eb->dump_props = FALSE; - SVN_ERR(svn_hash__clear(eb->props, eb->pool)); - SVN_ERR(svn_hash__clear(eb->deleted_props, eb->pool)); + fb->dump_props = FALSE; + apr_hash_clear(fb->props); + apr_hash_clear(fb->deleted_props); } /* Dump the text */ - if (eb->dump_text) + if (fb->dump_text) { /* Seek to the beginning of the delta file, map it to a stream, and copy the stream to eb->stream. Then close the stream and @@ -830,12 +1093,11 @@ close_file(void *file_baton, /* Cleanup */ SVN_ERR(svn_stream_close(delta_filestream)); SVN_ERR(svn_io_file_trunc(eb->delta_file, 0, pool)); - eb->dump_text = FALSE; } /* Write a couple of blank lines for matching output with `svnadmin dump` */ - SVN_ERR(svn_stream_printf(eb->stream, pool, "\n\n")); + SVN_ERR(svn_stream_puts(eb->stream, "\n\n")); return SVN_NO_ERROR; } @@ -846,19 +1108,134 @@ close_edit(void *edit_baton, apr_pool_t *pool) return SVN_NO_ERROR; } +static svn_error_t * +fetch_base_func(const char **filename, + void *baton, + const char *path, + svn_revnum_t base_revision, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct dump_edit_baton *eb = baton; + svn_stream_t *fstream; + svn_error_t *err; + + if (path[0] == '/') + path += 1; + + if (! SVN_IS_VALID_REVNUM(base_revision)) + base_revision = eb->current_revision - 1; + + SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL, + svn_io_file_del_on_pool_cleanup, + result_pool, scratch_pool)); + + err = svn_ra_get_file(eb->ra_session, path, base_revision, + fstream, NULL, NULL, scratch_pool); + if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) + { + svn_error_clear(err); + SVN_ERR(svn_stream_close(fstream)); + + *filename = NULL; + return SVN_NO_ERROR; + } + else if (err) + return svn_error_trace(err); + + SVN_ERR(svn_stream_close(fstream)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +fetch_props_func(apr_hash_t **props, + void *baton, + const char *path, + svn_revnum_t base_revision, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct dump_edit_baton *eb = baton; + svn_node_kind_t node_kind; + + if (path[0] == '/') + path += 1; + + if (! SVN_IS_VALID_REVNUM(base_revision)) + base_revision = eb->current_revision - 1; + + SVN_ERR(svn_ra_check_path(eb->ra_session, path, base_revision, &node_kind, + scratch_pool)); + + if (node_kind == svn_node_file) + { + SVN_ERR(svn_ra_get_file(eb->ra_session, path, base_revision, + NULL, NULL, props, result_pool)); + } + else if (node_kind == svn_node_dir) + { + apr_array_header_t *tmp_props; + + SVN_ERR(svn_ra_get_dir2(eb->ra_session, NULL, NULL, props, path, + base_revision, 0 /* Dirent fields */, + result_pool)); + tmp_props = svn_prop_hash_to_array(*props, result_pool); + SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props, + result_pool)); + *props = svn_prop_array_to_hash(tmp_props, result_pool); + } + else + { + *props = apr_hash_make(result_pool); + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +fetch_kind_func(svn_node_kind_t *kind, + void *baton, + const char *path, + svn_revnum_t base_revision, + apr_pool_t *scratch_pool) +{ + struct dump_edit_baton *eb = baton; + + if (path[0] == '/') + path += 1; + + if (! SVN_IS_VALID_REVNUM(base_revision)) + base_revision = eb->current_revision - 1; + + SVN_ERR(svn_ra_check_path(eb->ra_session, path, base_revision, kind, + scratch_pool)); + + return SVN_NO_ERROR; +} + svn_error_t * svn_rdump__get_dump_editor(const svn_delta_editor_t **editor, void **edit_baton, + svn_revnum_t revision, svn_stream_t *stream, + svn_ra_session_t *ra_session, + const char *update_anchor_relpath, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *pool) { struct dump_edit_baton *eb; svn_delta_editor_t *de; + svn_delta_shim_callbacks_t *shim_callbacks = + svn_delta_shim_callbacks_default(pool); eb = apr_pcalloc(pool, sizeof(struct dump_edit_baton)); eb->stream = stream; + eb->ra_session = ra_session; + eb->update_anchor_relpath = update_anchor_relpath; + eb->current_revision = revision; + eb->pending_kind = svn_node_none; /* Create a special per-revision pool */ eb->pool = svn_pool_create(pool); @@ -888,6 +1265,16 @@ svn_rdump__get_dump_editor(const svn_delta_editor_t **editor, *editor = de; /* Wrap this editor in a cancellation editor. */ - return svn_delta_get_cancellation_editor(cancel_func, cancel_baton, - de, eb, editor, edit_baton, pool); + SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton, + de, eb, editor, edit_baton, pool)); + + shim_callbacks->fetch_base_func = fetch_base_func; + shim_callbacks->fetch_props_func = fetch_props_func; + shim_callbacks->fetch_kind_func = fetch_kind_func; + shim_callbacks->fetch_baton = eb; + + SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton, + NULL, NULL, shim_callbacks, pool, pool)); + + return SVN_NO_ERROR; } |