Author: artagnon
Date: Sun Jul 25 08:44:21 2010
New Revision: 979002
URL: http://svn.apache.org/viewvc?rev=979002&view=rev
Log:
Add svnrload: currently a heavy WIP, with most of the code imported
from svnrdump
* subversion/svnrload
* subversion/svnrload/load_editor.c: From svnrdump/dump_editor.c
* subversion/svnrload/svnrload.c: From svnrdump/svnrdump.c
* subversion/svnrload/load_editor.h: From svnrdump/dump_editor.h
* subversion/svnrload/parse_dumpstream.c: Functionality to parse a
dumpstream
Added:
subversion/branches/svnrload/subversion/svnrload/
subversion/branches/svnrload/subversion/svnrload/load_editor.c
subversion/branches/svnrload/subversion/svnrload/load_editor.h
subversion/branches/svnrload/subversion/svnrload/parse_dumpstream.c
subversion/branches/svnrload/subversion/svnrload/svnrload.c
Added: subversion/branches/svnrload/subversion/svnrload/load_editor.c
URL:
http://svn.apache.org/viewvc/subversion/branches/svnrload/subversion/svnrload/load_editor.c?rev=979002&view=auto
==============================================================================
--- subversion/branches/svnrload/subversion/svnrload/load_editor.c (added)
+++ subversion/branches/svnrload/subversion/svnrload/load_editor.c Sun Jul 25
08:44:21 2010
@@ -0,0 +1,780 @@
+/*
+ * dump_editor.c: The svn_delta_editor_t editor used by svnrdump
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_repos.h"
+#include "svn_path.h"
+#include "svn_props.h"
+#include "svn_dirent_uri.h"
+
+#include "dump_editor.h"
+
+#define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r))
+
+/* The baton used by the dump editor. */
+struct load_edit_baton {
+ /* The output stream we write the dumpfile to */
+ svn_stream_t *stream;
+
+ /* Pool for per-edit-session allocations */
+ apr_pool_t *pool;
+
+ /* 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;
+
+ /* Temporary buffer to write property hashes to in human-readable
+ * form. ### Is this really needed? */
+ svn_stringbuf_t *propstring;
+
+ /* Temporary file to write delta to along with its checksum. */
+ char *delta_abspath;
+
+ /* The checksum of the file the delta is being applied to */
+ const char *base_checksum;
+
+ /* Flags to trigger dumping props and text */
+ svn_boolean_t load_props;
+ svn_boolean_t load_text;
+ svn_boolean_t load_props_pending;
+};
+
+/* Make a directory baton to represent the directory at path (relative
+ * to the edit_baton).
+ *
+ * COPYFROM_PATH/COPYFROM_REV are the path/revision against which this
+ * directory should be compared for changes. If the copyfrom
+ * 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. */
+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,
+ svn_boolean_t added,
+ apr_pool_t *pool)
+{
+ struct load_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;
+
+ /* Disallow a path relative to nothing. */
+ SVN_ERR_ASSERT_NO_RETURN(!path || pb);
+
+ /* Construct the full path of this node. */
+ if (pb)
+ abspath = svn_uri_join("/", path, pool);
+ else
+ abspath = "/";
+
+ /* Remove leading slashes from copyfrom paths. */
+ if (copyfrom_path && strcmp(copyfrom_path, "/"))
+ copyfrom_path = ((*copyfrom_path == '/') ?
+ copyfrom_path + 1 : copyfrom_path);
+
+ new_db->eb = eb;
+ new_db->parent_dir_baton = pb;
+ new_db->abspath = abspath;
+ new_db->copyfrom_path = copyfrom_path ?
+ apr_pstrdup(pool, copyfrom_path) : NULL;
+ new_db->copyfrom_rev = copyfrom_rev;
+ new_db->added = added;
+ new_db->written_out = FALSE;
+ 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 LOAD_DATA_TOO is set, only property headers are dumped.
+ */
+static svn_error_t *
+load_props(struct load_edit_baton *eb,
+ svn_boolean_t *trigger_var,
+ svn_boolean_t load_data_too,
+ apr_pool_t *pool)
+{
+ svn_stream_t *propstream;
+
+ if (trigger_var && !*trigger_var)
+ return SVN_NO_ERROR;
+
+ svn_stringbuf_setempty(eb->propstring);
+ propstream = svn_stream_from_stringbuf(eb->propstring, eb->pool);
+ SVN_ERR(svn_hash_write_incremental(eb->props, eb->deleted_props,
+ propstream, "PROPS-END", pool));
+ SVN_ERR(svn_stream_close(propstream));
+
+ /* Prop-delta: true */
+ SVN_ERR(svn_stream_printf(eb->stream, 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));
+
+ if (load_data_too)
+ {
+ /* Content-length: 14 */
+ SVN_ERR(svn_stream_printf(eb->stream, pool,
+ SVN_REPOS_DUMPFILE_CONTENT_LENGTH
+ ": %" APR_SIZE_T_FMT "\n\n",
+ eb->propstring->len));
+
+ /* The properties. */
+ SVN_ERR(svn_stream_write(eb->stream, eb->propstring->data,
+ &(eb->propstring->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"));
+
+ /* Cleanup so that data is never dumped twice. */
+ svn_hash__clear(eb->props, pool);
+ svn_hash__clear(eb->deleted_props, pool);
+ if (trigger_var)
+ *trigger_var = FALSE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * Write out a node record for PATH of type KIND under EB->FS_ROOT.
+ * ACTION describes what is happening to the node (see enum
+ * svn_node_action). Write record to writable EB->STREAM, using
+ * EB->BUFFER to write in chunks.
+ *
+ * If the node was itself copied, IS_COPY is TRUE and the
+ * path/revision of the copy source are in COPYFROM_PATH/COPYFROM_REV.
+ * If IS_COPY is FALSE, yet COPYFROM_PATH/COPYFROM_REV are valid, this
+ * node is part of a copied subtree.
+ */
+static svn_error_t *
+load_node(struct load_edit_baton *eb,
+ const char *path, /* an absolute path. */
+ svn_node_kind_t kind,
+ 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 && strcmp(path, "/"))
+ path = ((*path == '/') ? path + 1 : path);
+
+ if (copyfrom_path && strcmp(copyfrom_path, "/"))
+ copyfrom_path = ((*copyfrom_path == '/') ?
+ copyfrom_path + 1 : copyfrom_path);
+
+ /* Node-path: commons/STATUS */
+ SVN_ERR(svn_stream_printf(eb->stream, pool,
+ SVN_REPOS_DUMPFILE_NODE_PATH ": %s\n", path));
+
+ /* Node-kind: file */
+ if (kind == svn_node_file)
+ SVN_ERR(svn_stream_printf(eb->stream, pool,
+ SVN_REPOS_DUMPFILE_NODE_KIND ": file\n"));
+ else if (kind == svn_node_dir)
+ SVN_ERR(svn_stream_printf(eb->stream, pool,
+ SVN_REPOS_DUMPFILE_NODE_KIND ": dir\n"));
+
+
+ /* Write the appropriate Node-action header */
+ switch (action)
+ {
+ case svn_node_action_change:
+ SVN_ERR(svn_stream_printf(eb->stream, pool,
+ SVN_REPOS_DUMPFILE_NODE_ACTION
+ ": change\n"));
+ break;
+
+ case svn_node_action_replace:
+ if (!is_copy)
+ {
+ /* Node-action: replace */
+ SVN_ERR(svn_stream_printf(eb->stream, pool,
+ SVN_REPOS_DUMPFILE_NODE_ACTION
+ ": replace\n"));
+
+ eb->load_props_pending = TRUE;
+ break;
+ }
+ /* 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(load_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. */
+ eb->load_props = FALSE;
+ break;
+
+ case svn_node_action_delete:
+ SVN_ERR(svn_stream_printf(eb->stream, pool,
+ SVN_REPOS_DUMPFILE_NODE_ACTION
+ ": delete\n"));
+
+ /* We can leave this routine quietly now, don't need to dump
+ any content. */
+ SVN_ERR(svn_stream_printf(eb->stream, pool, "\n\n"));
+ eb->load_props = FALSE;
+ break;
+
+ case svn_node_action_add:
+ SVN_ERR(svn_stream_printf(eb->stream, pool,
+ SVN_REPOS_DUMPFILE_NODE_ACTION ": add\n"));
+
+ if (!is_copy)
+ {
+ /* eb->load_props_pending 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->load_props_pending 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. */
+
+ eb->load_props_pending = TRUE;
+ break;
+ }
+
+ 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 else can be done, and close_file won't be called to
+ write two blank lines. Write them here otherwise the `svnadmin
+ load` parser will fail. */
+ if (kind == svn_node_dir)
+ SVN_ERR(svn_stream_printf(eb->stream, pool, "\n\n"));
+
+ break;
+ }
+
+ /* Dump property headers */
+ SVN_ERR(load_props(eb, &(eb->load_props), FALSE, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+open_root(void *edit_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **root_baton)
+{
+ struct load_edit_baton *eb = edit_baton;
+ /* Allocate a special pool for the edit_baton to avoid pool
+ lifetime issues */
+ eb->pool = svn_pool_create(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, pool);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+delete_entry(const char *path,
+ svn_revnum_t revision,
+ void *parent_baton,
+ apr_pool_t *pool)
+{
+ struct dir_baton *pb = parent_baton;
+ const char *mypath = apr_pstrdup(pool, path);
+
+ /* Some pending properties to dump? */
+ SVN_ERR(load_props(pb->eb, &(pb->eb->load_props_pending), TRUE, pool));
+
+ /* Add this path to the deleted_entries of the parent directory
+ baton. */
+ apr_hash_set(pb->deleted_entries, mypath, APR_HASH_KEY_STRING, pb);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+add_directory(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_rev,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ void *val;
+ struct dir_baton *new_db
+ = make_dir_baton(path, copyfrom_path, copyfrom_rev, pb->eb, pb, TRUE,
pool);
+ svn_boolean_t is_copy;
+
+ /* Some pending properties to dump? */
+ SVN_ERR(load_props(pb->eb, &(pb->eb->load_props_pending), TRUE, pool));
+
+ /* This might be a replacement -- is the path already deleted? */
+ val = apr_hash_get(pb->deleted_entries, path, APR_HASH_KEY_STRING);
+
+ /* Detect an add-with-history */
+ is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
+
+ /* Dump the node */
+ SVN_ERR(load_node(pb->eb, path,
+ svn_node_dir,
+ 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 (val)
+ /* Delete the path, it's now been dumped */
+ apr_hash_set(pb->deleted_entries, path, APR_HASH_KEY_STRING, NULL);
+
+ new_db->written_out = TRUE;
+
+ *child_baton = new_db;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+open_directory(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct dir_baton *new_db;
+ const char *copyfrom_path = NULL;
+ svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
+
+ /* Some pending properties to dump? */
+ SVN_ERR(load_props(pb->eb, &(pb->eb->load_props_pending), TRUE, pool));
+
+ /* If the parent directory has explicit comparison path and rev,
+ record the same for this one. */
+ if (pb && ARE_VALID_COPY_ARGS(pb->copyfrom_path, pb->copyfrom_rev))
+ {
+ copyfrom_path = svn_uri_join(pb->copyfrom_path,
+ svn_relpath_basename(path, pool),
+ pool);
+ copyfrom_rev = pb->copyfrom_rev;
+ }
+
+ new_db = make_dir_baton(path, copyfrom_path, copyfrom_rev, pb->eb, pb,
+ FALSE, pool);
+ *child_baton = new_db;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+close_directory(void *dir_baton,
+ apr_pool_t *pool)
+{
+ struct dir_baton *db = dir_baton;
+ struct load_edit_baton *eb = db->eb;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ /* Some pending properties to dump? */
+ SVN_ERR(load_props(eb, &(eb->load_props_pending), TRUE, pool));
+
+ /* Dump the directory entries */
+ for (hi = apr_hash_first(pool, db->deleted_entries); hi;
+ hi = apr_hash_next(hi))
+ {
+ const void *key;
+ const char *path;
+ apr_hash_this(hi, &key, NULL, NULL);
+ path = key;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(load_node(db->eb, path, svn_node_unknown, svn_node_action_delete,
+ FALSE, NULL, SVN_INVALID_REVNUM, iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+add_file(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_rev,
+ apr_pool_t *pool,
+ void **file_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ void *val;
+ svn_boolean_t is_copy;
+
+ /* Some pending properties to dump? */
+ SVN_ERR(load_props(pb->eb, &(pb->eb->load_props_pending), TRUE, pool));
+
+ /* This might be a replacement -- is the path already deleted? */
+ val = apr_hash_get(pb->deleted_entries, path, APR_HASH_KEY_STRING);
+
+ /* Detect add-with-history. */
+ is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
+
+ /* Dump the node. */
+ SVN_ERR(load_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 (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;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+open_file(const char *path,
+ void *parent_baton,
+ svn_revnum_t ancestor_revision,
+ apr_pool_t *pool,
+ void **file_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ const char *copyfrom_path = NULL;
+ svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
+ apr_array_header_t *compose_path;
+
+ /* Some pending properties to dump? */
+ SVN_ERR(load_props(pb->eb, &(pb->eb->load_props_pending), TRUE, pool));
+
+ compose_path = apr_array_make(pool, 2, sizeof(const char *));
+
+ /* If the parent directory has explicit copyfrom path and rev,
+ record the same for this one. */
+ if (pb && ARE_VALID_COPY_ARGS(pb->copyfrom_path, pb->copyfrom_rev))
+ {
+ APR_ARRAY_PUSH(compose_path, const char *) = pb->copyfrom_path;
+ APR_ARRAY_PUSH(compose_path, const char *) =
+ svn_relpath_basename(path, pool);
+ copyfrom_path = svn_path_compose(compose_path, pool);
+ copyfrom_rev = pb->copyfrom_rev;
+ }
+
+ SVN_ERR(load_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;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+change_dir_prop(void *parent_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct dir_baton *db = parent_baton;
+
+ if (svn_property_kind(NULL, name) != svn_prop_regular_kind)
+ return SVN_NO_ERROR;
+
+ if (value)
+ apr_hash_set(db->eb->props, apr_pstrdup(pool, name),
+ APR_HASH_KEY_STRING, svn_string_dup(value, pool));
+ else
+ apr_hash_set(db->eb->deleted_props, apr_pstrdup(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; load_props_pending will take care of dumping the
+ props. If it not, dump the node itself before dumping the
+ props. */
+
+ SVN_ERR(load_node(db->eb, db->abspath, svn_node_dir,
+ svn_node_action_change, FALSE, db->copyfrom_path,
+ db->copyfrom_rev, pool));
+
+ SVN_ERR(load_props(db->eb, NULL, TRUE, pool));
+ db->written_out = TRUE;
+ }
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+change_file_prop(void *file_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct load_edit_baton *eb = file_baton;
+
+ if (svn_property_kind(NULL, name) != svn_prop_regular_kind)
+ return SVN_NO_ERROR;
+
+ if (value)
+ apr_hash_set(eb->props, apr_pstrdup(pool, name),
+ APR_HASH_KEY_STRING, svn_string_dup(value, pool));
+ else
+ apr_hash_set(eb->deleted_props, apr_pstrdup(pool, name),
+ APR_HASH_KEY_STRING, "");
+
+ /* Dump the property headers and wait; close_file might need
+ to write text headers too depending on whether
+ apply_textdelta is called */
+ eb->load_props_pending = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+window_handler(svn_txdelta_window_t *window, void *baton)
+{
+ struct handler_baton *hb = baton;
+ struct load_edit_baton *eb = hb->eb;
+ static svn_error_t *err;
+
+ err = hb->apply_handler(window, hb->apply_baton);
+ if (window != NULL && !err)
+ return SVN_NO_ERROR;
+
+ if (err)
+ SVN_ERR(err);
+
+ /* Write information about the filepath to hb->eb */
+ eb->delta_abspath = apr_pstrdup(eb->pool, hb->delta_abspath);
+
+ /* Cleanup */
+ svn_pool_destroy(hb->pool);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+apply_textdelta(void *file_baton, const char *base_checksum,
+ apr_pool_t *pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton)
+{
+ struct load_edit_baton *eb = file_baton;
+
+ /* Custom handler_baton allocated in a separate pool */
+ apr_pool_t *handler_pool = svn_pool_create(pool);
+ struct handler_baton *hb = apr_pcalloc(handler_pool, sizeof(*hb));
+ hb->pool = handler_pool;
+ hb->eb = eb;
+
+ /* Use a temporary file to measure the text-content-length */
+ SVN_ERR(svn_stream_open_unique(&(hb->delta_filestream), &hb->delta_abspath,
+ NULL, svn_io_file_del_none, hb->pool,
+ hb->pool));
+
+ /* Prepare to write the delta to the temporary file. */
+ svn_txdelta_to_svndiff2(&(hb->apply_handler), &(hb->apply_baton),
+ hb->delta_filestream, 0, hb->pool);
+ eb->load_text = TRUE;
+ eb->base_checksum = apr_pstrdup(pool, base_checksum);
+
+ /* The actual writing takes place when this function has
+ finished. Set handler and handler_baton now so for
+ window_handler() */
+ *handler = window_handler;
+ *handler_baton = hb;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+close_file(void *file_baton,
+ const char *text_checksum,
+ apr_pool_t *pool)
+{
+ struct load_edit_baton *eb = file_baton;
+ apr_file_t *delta_file;
+ svn_stream_t *delta_filestream;
+ apr_finfo_t *info = apr_pcalloc(pool, sizeof(apr_finfo_t));
+
+ /* Some pending properties to dump? */
+ SVN_ERR(load_props(eb, &(eb->load_props_pending), FALSE, pool));
+
+ /* The prop headers have already been dumped in load_node; now dump
+ the text headers. */
+ if (eb->load_text)
+ {
+ /* Text-delta: true */
+ SVN_ERR(svn_stream_printf(eb->stream, pool,
+ SVN_REPOS_DUMPFILE_TEXT_DELTA
+ ": true\n"));
+
+ SVN_ERR(svn_io_stat(info, eb->delta_abspath, APR_FINFO_SIZE, pool));
+
+ if (eb->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));
+
+ /* Text-content-length: 39 */
+ SVN_ERR(svn_stream_printf(eb->stream, pool,
+ SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH
+ ": %lu\n",
+ (unsigned long)info->size));
+
+ /* Text-content-md5: 82705804337e04dcd0e586bfa2389a7f */
+ SVN_ERR(svn_stream_printf(eb->stream, pool,
+ SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5
+ ": %s\n",
+ text_checksum));
+ }
+
+ /* Content-length: 1549 */
+ /* If both text and props are absent, skip this header */
+ if (eb->load_props || eb->load_props_pending)
+ 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->load_text)
+ SVN_ERR(svn_stream_printf(eb->stream, pool,
+ SVN_REPOS_DUMPFILE_CONTENT_LENGTH
+ ": %ld\n\n",
+ (unsigned long)info->size));
+
+ /* Dump the props; the propstring should have already been
+ written in load_node or above */
+ if (eb->load_props || eb->load_props_pending)
+ {
+ SVN_ERR(svn_stream_write(eb->stream, eb->propstring->data,
+ &(eb->propstring->len)));
+
+ /* Cleanup */
+ eb->load_props = eb->load_props_pending = FALSE;
+ svn_hash__clear(eb->props, pool);
+ svn_hash__clear(eb->deleted_props, pool);
+ }
+
+ /* Dump the text */
+ if (eb->load_text)
+ {
+ /* Open the temporary file, map it to a stream, copy
+ the stream to eb->stream, close and delete the
+ file */
+ SVN_ERR(svn_io_file_open(&delta_file, eb->delta_abspath, APR_READ,
+ APR_OS_DEFAULT, pool));
+ delta_filestream = svn_stream_from_aprfile2(delta_file, TRUE, pool);
+ SVN_ERR(svn_stream_copy3(delta_filestream, eb->stream, NULL, NULL,
pool));
+
+ /* Cleanup */
+ SVN_ERR(svn_io_file_close(delta_file, pool));
+ SVN_ERR(svn_stream_close(delta_filestream));
+ SVN_ERR(svn_io_remove_file2(eb->delta_abspath, TRUE, pool));
+ eb->load_text = FALSE;
+ }
+
+ SVN_ERR(svn_stream_printf(eb->stream, pool, "\n\n"));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+close_edit(void *edit_baton, apr_pool_t *pool)
+{
+ struct load_edit_baton *eb = edit_baton;
+ svn_pool_destroy(eb->pool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+get_load_editor(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_stream_t *stream,
+ apr_pool_t *pool)
+{
+ struct load_edit_baton *eb;
+ svn_delta_editor_t *de;
+
+ eb = apr_pcalloc(pool, sizeof(struct load_edit_baton));
+ eb->stream = stream;
+
+ de = svn_delta_default_editor(pool);
+ de->open_root = open_root;
+ de->delete_entry = delete_entry;
+ de->add_directory = add_directory;
+ de->open_directory = open_directory;
+ de->close_directory = close_directory;
+ de->change_dir_prop = change_dir_prop;
+ de->change_file_prop = change_file_prop;
+ de->apply_textdelta = apply_textdelta;
+ de->add_file = add_file;
+ de->open_file = open_file;
+ de->close_file = close_file;
+ de->close_edit = close_edit;
+
+ /* Set the edit_baton and editor. */
+ *edit_baton = eb;
+ *editor = de;
+
+ return SVN_NO_ERROR;
+}
Added: subversion/branches/svnrload/subversion/svnrload/load_editor.h
URL:
http://svn.apache.org/viewvc/subversion/branches/svnrload/subversion/svnrload/load_editor.h?rev=979002&view=auto
==============================================================================
--- subversion/branches/svnrload/subversion/svnrload/load_editor.h (added)
+++ subversion/branches/svnrload/subversion/svnrload/load_editor.h Sun Jul 25
08:44:21 2010
@@ -0,0 +1,89 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file load_editor.h
+ * @brief The svn_delta_editor_t editor used by svnrdump
+ */
+
+#ifndef LOAD_EDITOR_H_
+#define LOAD_EDITOR_H_
+
+/**
+ * A directory baton used by all directory-related callback functions
+ * in the dump editor.
+ */
+struct dir_baton
+{
+ struct load_edit_baton *eb;
+ struct dir_baton *parent_dir_baton;
+
+ /* 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;
+
+ /* Copyfrom info for the node, if any */
+ const char *copyfrom_path;
+ svn_revnum_t copyfrom_rev;
+
+ /* 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;
+};
+
+/**
+ * A handler baton to be used in window_handler().
+ */
+struct handler_baton
+{
+ svn_txdelta_window_handler_t apply_handler;
+ void *apply_baton;
+
+ /* Pool used for temporary allocations during delta application in
+ window_handler() */
+ apr_pool_t *pool;
+
+ /* Information about the path of the temporary file used */
+ const char *delta_abspath;
+ svn_stream_t *delta_filestream;
+
+ /* Global edit baton */
+ struct load_edit_baton *eb;
+};
+
+/**
+ * Get a dump editor @a editor along with a @a edit_baton allocated in
+ * @a pool. The editor will write output to @a stream.
+ */
+svn_error_t *
+get_load_editor(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_stream_t *stream,
+ apr_pool_t *pool);
+
+#endif
Added: subversion/branches/svnrload/subversion/svnrload/parse_dumpstream.c
URL:
http://svn.apache.org/viewvc/subversion/branches/svnrload/subversion/svnrload/parse_dumpstream.c?rev=979002&view=auto
==============================================================================
--- subversion/branches/svnrload/subversion/svnrload/parse_dumpstream.c (added)
+++ subversion/branches/svnrload/subversion/svnrload/parse_dumpstream.c Sun Jul
25 08:44:21 2010
@@ -0,0 +1,129 @@
+#include "svn_pools.h"
+#include "svn_cmdline.h"
+#include "svn_repos.h"
+#include "svn_io.h"
+
+static svn_error_t *
+new_revision_record(void **revision_baton,
+ apr_hash_t *headers,
+ void *parse_baton,
+ apr_pool_t *pool)
+{
+ printf("new_revision_record called");
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+new_node_record(void **node_baton,
+ apr_hash_t *headers,
+ void *revision_baton,
+ apr_pool_t *pool)
+{
+ printf("new_node_record called");
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+uuid_record(const char *uuid,
+ void *parse_baton,
+ apr_pool_t *pool)
+{
+ printf("uuid_record called");
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+set_revision_property(void *baton,
+ const char *name,
+ const svn_string_t *value)
+{
+ printf("set_revision_property called");
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+set_node_property(void *baton,
+ const char *name,
+ const svn_string_t *value)
+{
+ printf("set_node_property called");
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+remove_node_props(void *baton)
+{
+ printf("remove_node_props called");
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+set_fulltext(svn_stream_t **stream,
+ void *node_baton)
+{
+ printf("set_fulltext called");
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+close_node(void *baton)
+{
+ printf("close_node called");
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+close_revision(void *baton)
+{
+ printf("close_revision called");
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+delete_node_property(void *baton,
+ const char *name)
+{
+ printf("delete_node_property called");
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+apply_textdelta(svn_txdelta_window_handler_t *handler,
+ void **handler_baton,
+ void *node_baton)
+{
+ printf("apply_textdelta called");
+ return SVN_NO_ERROR;
+}
+
+int main()
+{
+ apr_pool_t *pool;
+ apr_file_t *dumpfile;
+ svn_stream_t *dumpstream;
+ svn_repos_parse_fns2_t *parser;
+
+ if (svn_cmdline_init ("parse_dumpstream", stderr) != EXIT_SUCCESS)
+ return EXIT_FAILURE;
+ pool = svn_pool_create(NULL);
+
+ parser = apr_pcalloc(pool, sizeof(*parser));
+
+ parser->new_revision_record = new_revision_record;
+ parser->new_node_record = new_node_record;
+ parser->uuid_record = uuid_record;
+ parser->set_revision_property = set_revision_property;
+ parser->set_node_property = set_node_property;
+ parser->remove_node_props = remove_node_props;
+ parser->set_fulltext = set_fulltext;
+ parser->close_node = close_node;
+ parser->close_revision = close_revision;
+ parser->delete_node_property = delete_node_property;
+ parser->apply_textdelta = apply_textdelta;
+
+ apr_file_open_stdin(&dumpfile, pool);
+ dumpstream = svn_stream_from_aprfile2(dumpfile, FALSE, pool);
+ svn_repos_parse_dumpstream2(dumpstream, parser, NULL, NULL, NULL, pool);
+ svn_pool_destroy(pool);
+ return 0;
+}
Added: subversion/branches/svnrload/subversion/svnrload/svnrload.c
URL:
http://svn.apache.org/viewvc/subversion/branches/svnrload/subversion/svnrload/svnrload.c?rev=979002&view=auto
==============================================================================
--- subversion/branches/svnrload/subversion/svnrload/svnrload.c (added)
+++ subversion/branches/svnrload/subversion/svnrload/svnrload.c Sun Jul 25
08:44:21 2010
@@ -0,0 +1,468 @@
+/*
+ * svnrload.c: Load a dumpfile to a remote repository.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_pools.h"
+#include "svn_cmdline.h"
+#include "svn_client.h"
+#include "svn_hash.h"
+#include "svn_ra.h"
+#include "svn_repos.h"
+#include "svn_path.h"
+#include "svn_utf.h"
+#include "svn_string.h"
+#include "svn_props.h"
+
+#include "load_editor.h"
+
+enum svn_svnrload__longopt_t
+ {
+ opt_config_dir = SVN_OPT_FIRST_LONGOPT_ID,
+ opt_auth_username,
+ opt_auth_password,
+ opt_non_interactive,
+ opt_auth_nocache,
+ opt_version,
+ };
+
+static const apr_getopt_option_t svnrload__options[] =
+ {
+ {"revision", 'r', 1, "REV1[:REV2] range of revisions to dump"},
+ {"quiet", 'q', 0, "no progress (only errors to stderr"},
+ {"config-dir", opt_config_dir, 1, "read user configuration files from"
+ " directory ARG" },
+ {"username", opt_auth_username, 1, "specify a username ARG"},
+ {"password", opt_auth_password, 1, "specify a password ARG"},
+ {"non-interactive", opt_non_interactive, 0, "do no interactive"
+ " prompting"},
+ {"no-auth-cache", opt_auth_nocache, 0, "do not cache authentication"
+ " tokens"},
+
+ {"help", 'h', 0, "display this help"},
+ {"version", opt_version, 0, "show program version information"},
+
+ {0, 0, 0, 0}
+ };
+
+/* Baton for the RA replay session. */
+struct replay_baton {
+ /* The editor producing diffs. */
+ const svn_delta_editor_t *editor;
+
+ /* Baton for the editor. */
+ void *edit_baton;
+
+ /* Whether to be quiet. */
+ svn_boolean_t quiet;
+};
+
+static svn_error_t *
+replay_revstart(svn_revnum_t revision,
+ void *replay_baton,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ apr_hash_t *rev_props,
+ apr_pool_t *pool)
+{
+ struct replay_baton *rb = replay_baton;
+ svn_stringbuf_t *propstring;
+ svn_stream_t *stdout_stream;
+ svn_stream_t *revprop_stream;
+
+ svn_stream_for_stdout(&stdout_stream, pool);
+
+ /* Revision-number: 19 */
+ SVN_ERR(svn_stream_printf(stdout_stream, pool,
+ SVN_REPOS_DUMPFILE_REVISION_NUMBER
+ ": %ld\n", revision));
+ propstring = svn_stringbuf_create_ensure(0, pool);
+ revprop_stream = svn_stream_from_stringbuf(propstring, pool);
+ SVN_ERR(svn_hash_write2(rev_props, revprop_stream, "PROPS-END", pool));
+ SVN_ERR(svn_stream_close(revprop_stream));
+
+ /* Prop-content-length: 13 */
+ SVN_ERR(svn_stream_printf(stdout_stream, pool,
+ SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
+ ": %" APR_SIZE_T_FMT "\n", propstring->len));
+
+ /* Content-length: 29 */
+ SVN_ERR(svn_stream_printf(stdout_stream, pool,
+ SVN_REPOS_DUMPFILE_CONTENT_LENGTH
+ ": %" APR_SIZE_T_FMT "\n\n", propstring->len));
+
+ /* Property data. */
+ SVN_ERR(svn_stream_write(stdout_stream, propstring->data,
+ &(propstring->len)));
+
+ SVN_ERR(svn_stream_printf(stdout_stream, pool, "\n"));
+ SVN_ERR(svn_stream_close(stdout_stream));
+
+ /* Extract editor and editor_baton from the replay_baton and
+ set them so that the editor callbacks can use them. */
+ *editor = rb->editor;
+ *edit_baton = rb->edit_baton;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+replay_revend(svn_revnum_t revision,
+ void *replay_baton,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ apr_hash_t *rev_props,
+ apr_pool_t *pool)
+{
+ /* No resources left to free. */
+ struct replay_baton *rb = replay_baton;
+ if (! rb->quiet)
+ svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu\n", revision);
+ return SVN_NO_ERROR;
+}
+
+/* Return in *SESSION a new RA session to URL.
+ * Allocate *SESSION and related data structures in POOL.
+ * Use CONFIG_DIR and pass USERNAME, PASSWORD, CONFIG_DIR and
+ * NO_AUTH_CACHE to initialize the authorization baton.*/
+static svn_error_t *
+open_connection(svn_ra_session_t **session,
+ const char *url,
+ svn_boolean_t non_interactive,
+ const char *username,
+ const char *password,
+ const char *config_dir,
+ svn_boolean_t no_auth_cache,
+ apr_pool_t *pool)
+{
+ svn_client_ctx_t *ctx = NULL;
+ svn_config_t *cfg_config;
+
+ SVN_ERR(svn_ra_initialize(pool));
+
+ SVN_ERR(svn_config_ensure(config_dir, pool));
+ SVN_ERR(svn_client_create_context(&ctx, pool));
+
+ SVN_ERR(svn_config_get_config(&(ctx->config), config_dir, pool));
+
+ cfg_config = apr_hash_get(ctx->config, SVN_CONFIG_CATEGORY_CONFIG,
+ APR_HASH_KEY_STRING);
+
+ /* Default authentication providers for non-interactive use */
+ SVN_ERR(svn_cmdline_create_auth_baton(&(ctx->auth_baton), non_interactive,
+ username, password, config_dir,
+ no_auth_cache, FALSE, cfg_config,
+ ctx->cancel_func, ctx->cancel_baton,
+ pool));
+ SVN_ERR(svn_client_open_ra_session(session, url, ctx, pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+replay_range(svn_ra_session_t *session, const char *url,
+ svn_revnum_t start_revision, svn_revnum_t end_revision,
+ svn_boolean_t quiet, apr_pool_t *pool)
+{
+ const svn_delta_editor_t *load_editor;
+ struct replay_baton *replay_baton;
+ void *load_baton;
+ const char *uuid;
+ svn_stream_t *stdout_stream;
+
+ SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
+
+ SVN_ERR(get_load_editor(&load_editor, &load_baton, stdout_stream, pool));
+
+ replay_baton = apr_pcalloc(pool, sizeof(*replay_baton));
+ replay_baton->editor = load_editor;
+ replay_baton->edit_baton = load_baton;
+ replay_baton->quiet = quiet;
+
+ /* Write the magic header and UUID */
+ SVN_ERR(svn_stream_printf(stdout_stream, pool,
+ SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n",
+ SVN_REPOS_DUMPFILE_FORMAT_VERSION));
+ SVN_ERR(svn_ra_get_uuid2(session, &uuid, pool));
+ SVN_ERR(svn_stream_printf(stdout_stream, pool,
+ SVN_REPOS_DUMPFILE_UUID ": %s\n\n", uuid));
+
+ /* Fake revision 0 if necessary */
+ if (start_revision == 0)
+ {
+ apr_hash_t *prophash;
+ svn_stringbuf_t *propstring;
+ svn_stream_t *propstream;
+ SVN_ERR(svn_stream_printf(stdout_stream, pool,
+ SVN_REPOS_DUMPFILE_REVISION_NUMBER
+ ": %ld\n", start_revision));
+
+ prophash = apr_hash_make(pool);
+ propstring = svn_stringbuf_create("", pool);
+
+ SVN_ERR(svn_ra_rev_proplist(session, start_revision,
+ &prophash, pool));
+
+ propstream = svn_stream_from_stringbuf(propstring, pool);
+ SVN_ERR(svn_hash_write2(prophash, propstream, "PROPS-END", pool));
+ SVN_ERR(svn_stream_close(propstream));
+
+ /* Property-content-length: 14; Content-length: 14 */
+ SVN_ERR(svn_stream_printf(stdout_stream, pool,
+ SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
+ ": %" APR_SIZE_T_FMT "\n",
+ propstring->len));
+ SVN_ERR(svn_stream_printf(stdout_stream, pool,
+ SVN_REPOS_DUMPFILE_CONTENT_LENGTH
+ ": %" APR_SIZE_T_FMT "\n\n",
+ propstring->len));
+ /* The properties */
+ SVN_ERR(svn_stream_write(stdout_stream, propstring->data,
+ &(propstring->len)));
+ SVN_ERR(svn_stream_printf(stdout_stream, pool, "\n"));
+ if (! quiet)
+ svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu\n",
start_revision);
+
+ start_revision++;
+ }
+
+ SVN_ERR(svn_ra_replay_range(session, start_revision, end_revision,
+ 0, TRUE, replay_revstart, replay_revend,
+ replay_baton, pool));
+ SVN_ERR(svn_stream_close(stdout_stream));
+ return SVN_NO_ERROR;
+}
+
+static const char *
+ensure_appname(const char *progname, apr_pool_t *pool)
+{
+ if (!progname)
+ return "svnrdump";
+
+ progname = svn_dirent_internal_style(progname, pool);
+ return svn_dirent_basename(progname, NULL);
+}
+
+static svn_error_t *
+usage(const char *progname, apr_pool_t *pool)
+{
+ progname = ensure_appname(progname, pool);
+
+ SVN_ERR(svn_cmdline_fprintf(stderr, pool,
+ "Type '%s --help' for usage.\n",
+ progname));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+help(const char *progname, apr_pool_t *pool)
+{
+ apr_size_t i;
+
+ progname = ensure_appname(progname, pool);
+
+ SVN_ERR(svn_cmdline_printf(
+ pool,
+ "usage: %s URL [-r LOWER[:UPPER]]\n\n"
+ "Dump the contents of repository at remote URL "
+ "to stdout in a 'dumpfile' portable format.\n"
+ "Dump revisions LOWER rev through UPPER rev.\n"
+ "LOWER defaults to 0 and UPPER defaults to the "
+ "highest possible revision if omitted.\n\n"
+ "Valid options:\n",
+ progname));
+
+ for (i = 0; svnrload__options[i].name && svnrload__options[i].optch; i++)
+ {
+ const char *optstr;
+ svn_opt_format_option(&optstr, svnrload__options + i, TRUE, pool);
+ SVN_ERR(svn_cmdline_fprintf(stdout, pool, " %s\n", optstr));
+ }
+ return svn_cmdline_fprintf(stdout, pool, "\n");
+}
+
+static svn_error_t *
+version(const char *progname, apr_pool_t *pool)
+{
+ progname = ensure_appname(progname, pool);
+
+ return svn_opt_print_help3(NULL, progname, TRUE, FALSE, NULL,
+ NULL, NULL, NULL, NULL, NULL, pool);
+}
+
+
+/** A statement macro, similar to @c SVN_ERR, but returns an integer.
+ *
+ * Evaluate @a expr. If it yields an error, handle that error and
+ * return @c EXIT_FAILURE.
+ */
+#define SVNRLOAD_ERR(expr) \
+ do \
+ { \
+ svn_error_t *svn_err__temp = (expr); \
+ if (svn_err__temp) \
+ { \
+ svn_handle_error2(svn_err__temp, stderr, FALSE, "svnrdump: "); \
+ svn_error_clear(svn_err__temp); \
+ return EXIT_FAILURE; \
+ } \
+ } \
+ while (0)
+
+
+int
+main(int argc, const char **argv)
+{
+ const char *url = NULL;
+ char *revision_cut = NULL;
+ svn_revnum_t start_revision = svn_opt_revision_unspecified;
+ svn_revnum_t end_revision = svn_opt_revision_unspecified;
+ svn_revnum_t latest_revision = svn_opt_revision_unspecified;
+ svn_boolean_t quiet = FALSE;
+ apr_pool_t *pool = NULL;
+ svn_ra_session_t *session = NULL;
+ const char *config_dir = NULL;
+ const char *username = NULL;
+ const char *password = NULL;
+ svn_boolean_t no_auth_cache = FALSE;
+ svn_boolean_t non_interactive = FALSE;
+ apr_getopt_t *os;
+
+ if (svn_cmdline_init ("svnrdump", stderr) != EXIT_SUCCESS)
+ return EXIT_FAILURE;
+
+ pool = svn_pool_create(NULL);
+
+ SVNRLOAD_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
+
+ os->interleave = TRUE; /* Options and arguments can be interleaved */
+
+ while (1)
+ {
+ int opt;
+ const char *opt_arg;
+ apr_status_t status = apr_getopt_long(os, svnrload__options, &opt,
+ &opt_arg);
+
+ if (APR_STATUS_IS_EOF(status))
+ break;
+ if (status != APR_SUCCESS)
+ {
+ SVNRLOAD_ERR(usage(argv[0], pool));
+ exit(EXIT_FAILURE);
+ }
+
+ switch(opt)
+ {
+ case 'r':
+ {
+ revision_cut = strchr(opt_arg, ':');
+ if (revision_cut)
+ {
+ start_revision = (svn_revnum_t)strtoul(opt_arg,
+ &revision_cut, 10);
+ end_revision = (svn_revnum_t)strtoul(revision_cut + 1,
+ NULL, 10);
+ }
+ else
+ start_revision = (svn_revnum_t)strtoul(opt_arg, NULL, 10);
+ }
+ break;
+ case 'q':
+ quiet = TRUE;
+ break;
+ case opt_config_dir:
+ config_dir = opt_arg;
+ break;
+ case opt_version:
+ SVNRLOAD_ERR(version(argv[0], pool));
+ exit(EXIT_SUCCESS);
+ break;
+ case 'h':
+ SVNRLOAD_ERR(help(argv[0], pool));
+ exit(EXIT_SUCCESS);
+ break;
+ case opt_auth_username:
+ SVNRLOAD_ERR(svn_utf_cstring_to_utf8(&username, opt_arg, pool));
+ break;
+ case opt_auth_password:
+ SVNRLOAD_ERR(svn_utf_cstring_to_utf8(&password, opt_arg, pool));
+ break;
+ case opt_auth_nocache:
+ no_auth_cache = TRUE;
+ break;
+ case opt_non_interactive:
+ non_interactive = TRUE;
+ break;
+ }
+ }
+
+ /* Only continue if the only not option argument is a url, to allow
+ implementing 'svnrdump dump URL' like handling later without breaking
+ backward compatibility */
+ if ((os->ind != os->argc-1)
+ || !svn_path_is_url(os->argv[os->ind]))
+ {
+ SVNRLOAD_ERR(usage(argv[0], pool));
+ exit(EXIT_FAILURE);
+ }
+
+ SVNRLOAD_ERR(svn_utf_cstring_to_utf8(&url, os->argv[os->ind], pool));
+
+ url = svn_uri_canonicalize(os->argv[os->ind], pool);
+
+
+ SVNRLOAD_ERR(open_connection(&session,
+ url,
+ non_interactive,
+ username,
+ password,
+ config_dir,
+ no_auth_cache,
+ pool));
+
+ /* Have sane start_revision and end_revision defaults if unspecified */
+ SVNRLOAD_ERR(svn_ra_get_latest_revnum(session, &latest_revision, pool));
+ if (start_revision == svn_opt_revision_unspecified)
+ start_revision = 0;
+ if (end_revision == svn_opt_revision_unspecified)
+ end_revision = latest_revision;
+ if (end_revision > latest_revision)
+ {
+ SVN_INT_ERR(svn_cmdline_fprintf(stderr, pool,
+ "Revision %ld does not exist.\n",
+ end_revision));
+ exit(EXIT_FAILURE);
+ }
+ if (end_revision < start_revision)
+ {
+ SVN_INT_ERR(svn_cmdline_fprintf(stderr, pool,
+ "LOWER cannot be greater "
+ "than UPPER.\n"));
+ exit(EXIT_FAILURE);
+ }
+
+ SVNRLOAD_ERR(replay_range(session, url, start_revision, end_revision,
+ quiet, pool));
+
+ svn_pool_destroy(pool);
+
+ return 0;
+}