summaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_repos/delta.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_repos/delta.c')
-rw-r--r--subversion/libsvn_repos/delta.c1074
1 files changed, 1074 insertions, 0 deletions
diff --git a/subversion/libsvn_repos/delta.c b/subversion/libsvn_repos/delta.c
new file mode 100644
index 0000000..51cfda7
--- /dev/null
+++ b/subversion/libsvn_repos/delta.c
@@ -0,0 +1,1074 @@
+/*
+ * delta.c: an editor driver for expressing differences between two trees
+ *
+ * ====================================================================
+ * 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 <apr_hash.h>
+
+#include "svn_hash.h"
+#include "svn_types.h"
+#include "svn_delta.h"
+#include "svn_fs.h"
+#include "svn_checksum.h"
+#include "svn_path.h"
+#include "svn_repos.h"
+#include "svn_pools.h"
+#include "svn_props.h"
+#include "svn_private_config.h"
+#include "repos.h"
+
+
+
+/* THINGS TODO: Currently the code herein gives only a slight nod to
+ fully supporting directory deltas that involve renames, copies, and
+ such. */
+
+
+/* Some datatypes and declarations used throughout the file. */
+
+
+/* Parameters which remain constant throughout a delta traversal.
+ At the top of the recursion, we initialize one of these structures.
+ Then we pass it down to every call. This way, functions invoked
+ deep in the recursion can get access to this traversal's global
+ parameters, without using global variables. */
+struct context {
+ const svn_delta_editor_t *editor;
+ const char *edit_base_path;
+ svn_fs_root_t *source_root;
+ svn_fs_root_t *target_root;
+ svn_repos_authz_func_t authz_read_func;
+ void *authz_read_baton;
+ svn_boolean_t text_deltas;
+ svn_boolean_t entry_props;
+ svn_boolean_t ignore_ancestry;
+};
+
+
+/* The type of a function that accepts changes to an object's property
+ list. OBJECT is the object whose properties are being changed.
+ NAME is the name of the property to change. VALUE is the new value
+ for the property, or zero if the property should be deleted. */
+typedef svn_error_t *proplist_change_fn_t(struct context *c,
+ void *object,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool);
+
+
+
+/* Some prototypes for functions used throughout. See each individual
+ function for information about what it does. */
+
+
+/* Retrieving the base revision from the path/revision hash. */
+static svn_revnum_t get_path_revision(svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool);
+
+
+/* proplist_change_fn_t property changing functions. */
+static svn_error_t *change_dir_prop(struct context *c,
+ void *object,
+ const char *path,
+ const svn_string_t *value,
+ apr_pool_t *pool);
+
+static svn_error_t *change_file_prop(struct context *c,
+ void *object,
+ const char *path,
+ const svn_string_t *value,
+ apr_pool_t *pool);
+
+
+/* Constructing deltas for properties of files and directories. */
+static svn_error_t *delta_proplists(struct context *c,
+ const char *source_path,
+ const char *target_path,
+ proplist_change_fn_t *change_fn,
+ void *object,
+ apr_pool_t *pool);
+
+
+/* Constructing deltas for file constents. */
+static svn_error_t *send_text_delta(struct context *c,
+ void *file_baton,
+ const char *base_checksum,
+ svn_txdelta_stream_t *delta_stream,
+ apr_pool_t *pool);
+
+static svn_error_t *delta_files(struct context *c,
+ void *file_baton,
+ const char *source_path,
+ const char *target_path,
+ apr_pool_t *pool);
+
+
+/* Generic directory deltafication routines. */
+static svn_error_t *delete(struct context *c,
+ void *dir_baton,
+ const char *edit_path,
+ apr_pool_t *pool);
+
+static svn_error_t *add_file_or_dir(struct context *c,
+ void *dir_baton,
+ svn_depth_t depth,
+ const char *target_path,
+ const char *edit_path,
+ svn_node_kind_t tgt_kind,
+ apr_pool_t *pool);
+
+static svn_error_t *replace_file_or_dir(struct context *c,
+ void *dir_baton,
+ svn_depth_t depth,
+ const char *source_path,
+ const char *target_path,
+ const char *edit_path,
+ svn_node_kind_t tgt_kind,
+ apr_pool_t *pool);
+
+static svn_error_t *absent_file_or_dir(struct context *c,
+ void *dir_baton,
+ const char *edit_path,
+ svn_node_kind_t tgt_kind,
+ apr_pool_t *pool);
+
+static svn_error_t *delta_dirs(struct context *c,
+ void *dir_baton,
+ svn_depth_t depth,
+ const char *source_path,
+ const char *target_path,
+ const char *edit_path,
+ apr_pool_t *pool);
+
+
+
+#define MAYBE_DEMOTE_DEPTH(depth) \
+ (((depth) == svn_depth_immediates || (depth) == svn_depth_files) \
+ ? svn_depth_empty \
+ : (depth))
+
+
+/* Return the error 'SVN_ERR_AUTHZ_ROOT_UNREADABLE' if PATH in ROOT is
+ * unreadable according to AUTHZ_READ_FUNC with AUTHZ_READ_BATON.
+ *
+ * PATH should be the implicit root path of an editor drive, that is,
+ * the path used by editor->open_root().
+ */
+static svn_error_t *
+authz_root_check(svn_fs_root_t *root,
+ const char *path,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool)
+{
+ svn_boolean_t allowed;
+
+ if (authz_read_func)
+ {
+ SVN_ERR(authz_read_func(&allowed, root, path, authz_read_baton, pool));
+
+ if (! allowed)
+ return svn_error_create(SVN_ERR_AUTHZ_ROOT_UNREADABLE, 0,
+ _("Unable to open root of edit"));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+not_a_dir_error(const char *role,
+ const char *path)
+{
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_DIRECTORY, 0,
+ "Invalid %s directory '%s'",
+ role, path ? path : "(null)");
+}
+
+
+/* Public interface to computing directory deltas. */
+svn_error_t *
+svn_repos_dir_delta2(svn_fs_root_t *src_root,
+ const char *src_parent_dir,
+ const char *src_entry,
+ svn_fs_root_t *tgt_root,
+ const char *tgt_fullpath,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ svn_boolean_t text_deltas,
+ svn_depth_t depth,
+ svn_boolean_t entry_props,
+ svn_boolean_t ignore_ancestry,
+ apr_pool_t *pool)
+{
+ void *root_baton = NULL;
+ struct context c;
+ const char *src_fullpath;
+ const svn_fs_id_t *src_id, *tgt_id;
+ svn_node_kind_t src_kind, tgt_kind;
+ svn_revnum_t rootrev;
+ int distance;
+ const char *authz_root_path;
+
+ /* SRC_PARENT_DIR must be valid. */
+ if (src_parent_dir)
+ src_parent_dir = svn_relpath_canonicalize(src_parent_dir, pool);
+ else
+ return not_a_dir_error("source parent", src_parent_dir);
+
+ /* TGT_FULLPATH must be valid. */
+ if (tgt_fullpath)
+ tgt_fullpath = svn_relpath_canonicalize(tgt_fullpath, pool);
+ else
+ return svn_error_create(SVN_ERR_FS_PATH_SYNTAX, 0,
+ _("Invalid target path"));
+
+ if (depth == svn_depth_exclude)
+ return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL,
+ _("Delta depth 'exclude' not supported"));
+
+ /* Calculate the fs path implicitly used for editor->open_root, so
+ we can do an authz check on that path first. */
+ if (*src_entry)
+ authz_root_path = svn_relpath_dirname(tgt_fullpath, pool);
+ else
+ authz_root_path = tgt_fullpath;
+
+ /* Construct the full path of the source item. */
+ src_fullpath = svn_relpath_join(src_parent_dir, src_entry, pool);
+
+ /* Get the node kinds for the source and target paths. */
+ SVN_ERR(svn_fs_check_path(&tgt_kind, tgt_root, tgt_fullpath, pool));
+ SVN_ERR(svn_fs_check_path(&src_kind, src_root, src_fullpath, pool));
+
+ /* If neither of our paths exists, we don't really have anything to do. */
+ if ((tgt_kind == svn_node_none) && (src_kind == svn_node_none))
+ goto cleanup;
+
+ /* If either the source or the target is a non-directory, we
+ require that a SRC_ENTRY be supplied. */
+ if ((! *src_entry) && ((src_kind != svn_node_dir)
+ || tgt_kind != svn_node_dir))
+ return svn_error_create
+ (SVN_ERR_FS_PATH_SYNTAX, 0,
+ _("Invalid editor anchoring; at least one of the "
+ "input paths is not a directory and there was no source entry"));
+
+ /* Set the global target revision if one can be determined. */
+ if (svn_fs_is_revision_root(tgt_root))
+ {
+ SVN_ERR(editor->set_target_revision
+ (edit_baton, svn_fs_revision_root_revision(tgt_root), pool));
+ }
+ else if (svn_fs_is_txn_root(tgt_root))
+ {
+ SVN_ERR(editor->set_target_revision
+ (edit_baton, svn_fs_txn_root_base_revision(tgt_root), pool));
+ }
+
+ /* Setup our pseudo-global structure here. We need these variables
+ throughout the deltafication process, so pass them around by
+ reference to all the helper functions. */
+ c.editor = editor;
+ c.source_root = src_root;
+ c.target_root = tgt_root;
+ c.authz_read_func = authz_read_func;
+ c.authz_read_baton = authz_read_baton;
+ c.text_deltas = text_deltas;
+ c.entry_props = entry_props;
+ c.ignore_ancestry = ignore_ancestry;
+
+ /* Get our editor root's revision. */
+ rootrev = get_path_revision(src_root, src_parent_dir, pool);
+
+ /* If one or the other of our paths doesn't exist, we have to handle
+ those cases specially. */
+ if (tgt_kind == svn_node_none)
+ {
+ /* Caller thinks that target still exists, but it doesn't.
+ So transform their source path to "nothing" by deleting it. */
+ SVN_ERR(authz_root_check(tgt_root, authz_root_path,
+ authz_read_func, authz_read_baton, pool));
+ SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton));
+ SVN_ERR(delete(&c, root_baton, src_entry, pool));
+ goto cleanup;
+ }
+ if (src_kind == svn_node_none)
+ {
+ /* The source path no longer exists, but the target does.
+ So transform "nothing" into "something" by adding. */
+ SVN_ERR(authz_root_check(tgt_root, authz_root_path,
+ authz_read_func, authz_read_baton, pool));
+ SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton));
+ SVN_ERR(add_file_or_dir(&c, root_baton, depth, tgt_fullpath,
+ src_entry, tgt_kind, pool));
+ goto cleanup;
+ }
+
+ /* Get and compare the node IDs for the source and target. */
+ SVN_ERR(svn_fs_node_id(&tgt_id, tgt_root, tgt_fullpath, pool));
+ SVN_ERR(svn_fs_node_id(&src_id, src_root, src_fullpath, pool));
+ distance = svn_fs_compare_ids(src_id, tgt_id);
+
+ if (distance == 0)
+ {
+ /* They are the same node! No-op (you gotta love those). */
+ goto cleanup;
+ }
+ else if (*src_entry)
+ {
+ /* If the nodes have different kinds, we must delete the one and
+ add the other. Also, if they are completely unrelated and
+ our caller is interested in relatedness, we do the same thing. */
+ if ((src_kind != tgt_kind)
+ || ((distance == -1) && (! ignore_ancestry)))
+ {
+ SVN_ERR(authz_root_check(tgt_root, authz_root_path,
+ authz_read_func, authz_read_baton, pool));
+ SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton));
+ SVN_ERR(delete(&c, root_baton, src_entry, pool));
+ SVN_ERR(add_file_or_dir(&c, root_baton, depth, tgt_fullpath,
+ src_entry, tgt_kind, pool));
+ }
+ /* Otherwise, we just replace the one with the other. */
+ else
+ {
+ SVN_ERR(authz_root_check(tgt_root, authz_root_path,
+ authz_read_func, authz_read_baton, pool));
+ SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton));
+ SVN_ERR(replace_file_or_dir(&c, root_baton, depth, src_fullpath,
+ tgt_fullpath, src_entry,
+ tgt_kind, pool));
+ }
+ }
+ else
+ {
+ /* There is no entry given, so delta the whole parent directory. */
+ SVN_ERR(authz_root_check(tgt_root, authz_root_path,
+ authz_read_func, authz_read_baton, pool));
+ SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton));
+ SVN_ERR(delta_dirs(&c, root_baton, depth, src_fullpath,
+ tgt_fullpath, "", pool));
+ }
+
+ cleanup:
+
+ /* Make sure we close the root directory if we opened one above. */
+ if (root_baton)
+ SVN_ERR(editor->close_directory(root_baton, pool));
+
+ /* Close the edit. */
+ return editor->close_edit(edit_baton, pool);
+}
+
+
+/* Retrieving the base revision from the path/revision hash. */
+
+
+static svn_revnum_t
+get_path_revision(svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ svn_revnum_t revision;
+ svn_error_t *err;
+
+ /* Easy out -- if ROOT is a revision root, we can use the revision
+ that it's a root of. */
+ if (svn_fs_is_revision_root(root))
+ return svn_fs_revision_root_revision(root);
+
+ /* Else, this must be a transaction root, so ask the filesystem in
+ what revision this path was created. */
+ if ((err = svn_fs_node_created_rev(&revision, root, path, pool)))
+ {
+ revision = SVN_INVALID_REVNUM;
+ svn_error_clear(err);
+ }
+
+ /* If we don't get back a valid revision, this path is mutable in
+ the transaction. We should probably examine the node on which it
+ is based, doable by querying for the node-id of the path, and
+ then examining that node-id's predecessor. ### This predecessor
+ determination isn't exposed via the FS public API right now, so
+ for now, we'll just return the SVN_INVALID_REVNUM. */
+ return revision;
+}
+
+
+/* proplist_change_fn_t property changing functions. */
+
+
+/* Call the directory property-setting function of C->editor to set
+ the property NAME to given VALUE on the OBJECT passed to this
+ function. */
+static svn_error_t *
+change_dir_prop(struct context *c,
+ void *object,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ return c->editor->change_dir_prop(object, name, value, pool);
+}
+
+
+/* Call the file property-setting function of C->editor to set the
+ property NAME to given VALUE on the OBJECT passed to this
+ function. */
+static svn_error_t *
+change_file_prop(struct context *c,
+ void *object,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ return c->editor->change_file_prop(object, name, value, pool);
+}
+
+
+
+
+/* Constructing deltas for properties of files and directories. */
+
+
+/* Generate the appropriate property editing calls to turn the
+ properties of SOURCE_PATH into those of TARGET_PATH. If
+ SOURCE_PATH is NULL, this is an add, so assume the target starts
+ with no properties. Pass OBJECT on to the editor function wrapper
+ CHANGE_FN. */
+static svn_error_t *
+delta_proplists(struct context *c,
+ const char *source_path,
+ const char *target_path,
+ proplist_change_fn_t *change_fn,
+ void *object,
+ apr_pool_t *pool)
+{
+ apr_hash_t *s_props = 0;
+ apr_hash_t *t_props = 0;
+ apr_pool_t *subpool;
+ apr_array_header_t *prop_diffs;
+ int i;
+
+ SVN_ERR_ASSERT(target_path);
+
+ /* Make a subpool for local allocations. */
+ subpool = svn_pool_create(pool);
+
+ /* If we're supposed to send entry props for all non-deleted items,
+ here we go! */
+ if (c->entry_props)
+ {
+ svn_revnum_t committed_rev = SVN_INVALID_REVNUM;
+ svn_string_t *cr_str = NULL;
+ svn_string_t *committed_date = NULL;
+ svn_string_t *last_author = NULL;
+
+ /* Get the CR and two derivative props. ### check for error returns. */
+ SVN_ERR(svn_fs_node_created_rev(&committed_rev, c->target_root,
+ target_path, subpool));
+ if (SVN_IS_VALID_REVNUM(committed_rev))
+ {
+ svn_fs_t *fs = svn_fs_root_fs(c->target_root);
+ apr_hash_t *r_props;
+ const char *uuid;
+
+ /* Transmit the committed-rev. */
+ cr_str = svn_string_createf(subpool, "%ld",
+ committed_rev);
+ SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_COMMITTED_REV,
+ cr_str, subpool));
+
+ SVN_ERR(svn_fs_revision_proplist(&r_props, fs, committed_rev,
+ pool));
+
+ /* Transmit the committed-date. */
+ committed_date = svn_hash_gets(r_props, SVN_PROP_REVISION_DATE);
+ if (committed_date || source_path)
+ {
+ SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_COMMITTED_DATE,
+ committed_date, subpool));
+ }
+
+ /* Transmit the last-author. */
+ last_author = svn_hash_gets(r_props, SVN_PROP_REVISION_AUTHOR);
+ if (last_author || source_path)
+ {
+ SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_LAST_AUTHOR,
+ last_author, subpool));
+ }
+
+ /* Transmit the UUID. */
+ SVN_ERR(svn_fs_get_uuid(fs, &uuid, subpool));
+ SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_UUID,
+ svn_string_create(uuid, subpool),
+ subpool));
+ }
+ }
+
+ if (source_path)
+ {
+ svn_boolean_t changed;
+
+ /* Is this deltification worth our time? */
+ SVN_ERR(svn_fs_props_changed(&changed, c->target_root, target_path,
+ c->source_root, source_path, subpool));
+ if (! changed)
+ goto cleanup;
+
+ /* If so, go ahead and get the source path's properties. */
+ SVN_ERR(svn_fs_node_proplist(&s_props, c->source_root,
+ source_path, subpool));
+ }
+ else
+ {
+ s_props = apr_hash_make(subpool);
+ }
+
+ /* Get the target path's properties */
+ SVN_ERR(svn_fs_node_proplist(&t_props, c->target_root,
+ target_path, subpool));
+
+ /* Now transmit the differences. */
+ SVN_ERR(svn_prop_diffs(&prop_diffs, t_props, s_props, subpool));
+ for (i = 0; i < prop_diffs->nelts; i++)
+ {
+ const svn_prop_t *pc = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t);
+ SVN_ERR(change_fn(c, object, pc->name, pc->value, subpool));
+ }
+
+ cleanup:
+ /* Destroy local subpool. */
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+
+
+/* Constructing deltas for file contents. */
+
+
+/* Change the contents of FILE_BATON in C->editor, according to the
+ text delta from DELTA_STREAM. Pass BASE_CHECKSUM along to
+ C->editor->apply_textdelta. */
+static svn_error_t *
+send_text_delta(struct context *c,
+ void *file_baton,
+ const char *base_checksum,
+ svn_txdelta_stream_t *delta_stream,
+ apr_pool_t *pool)
+{
+ svn_txdelta_window_handler_t delta_handler;
+ void *delta_handler_baton;
+
+ /* Get a handler that will apply the delta to the file. */
+ SVN_ERR(c->editor->apply_textdelta
+ (file_baton, base_checksum, pool,
+ &delta_handler, &delta_handler_baton));
+
+ if (c->text_deltas && delta_stream)
+ {
+ /* Deliver the delta stream to the file. */
+ return svn_txdelta_send_txstream(delta_stream,
+ delta_handler,
+ delta_handler_baton,
+ pool);
+ }
+ else
+ {
+ /* The caller doesn't want text delta data. Just send a single
+ NULL window. */
+ return delta_handler(NULL, delta_handler_baton);
+ }
+}
+
+svn_error_t *
+svn_repos__compare_files(svn_boolean_t *changed_p,
+ svn_fs_root_t *root1,
+ const char *path1,
+ svn_fs_root_t *root2,
+ const char *path2,
+ apr_pool_t *pool)
+{
+ svn_filesize_t size1, size2;
+ svn_checksum_t *checksum1, *checksum2;
+ svn_stream_t *stream1, *stream2;
+ svn_boolean_t same;
+
+ /* If the filesystem claims the things haven't changed, then they
+ haven't changed. */
+ SVN_ERR(svn_fs_contents_changed(changed_p, root1, path1,
+ root2, path2, pool));
+ if (!*changed_p)
+ return SVN_NO_ERROR;
+
+ /* If the SHA1 checksums match for these things, we'll claim they
+ have the same contents. (We don't give quite as much weight to
+ MD5 checksums.) */
+ SVN_ERR(svn_fs_file_checksum(&checksum1, svn_checksum_sha1,
+ root1, path1, FALSE, pool));
+ SVN_ERR(svn_fs_file_checksum(&checksum2, svn_checksum_sha1,
+ root2, path2, FALSE, pool));
+ if (checksum1 && checksum2)
+ {
+ *changed_p = !svn_checksum_match(checksum1, checksum2);
+ return SVN_NO_ERROR;
+ }
+
+ /* From this point on, our default answer is "Nothing's changed". */
+ *changed_p = FALSE;
+
+ /* Different filesizes means the contents are different. */
+ SVN_ERR(svn_fs_file_length(&size1, root1, path1, pool));
+ SVN_ERR(svn_fs_file_length(&size2, root2, path2, pool));
+ if (size1 != size2)
+ {
+ *changed_p = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ /* Different MD5 checksums means the contents are different. */
+ SVN_ERR(svn_fs_file_checksum(&checksum1, svn_checksum_md5, root1, path1,
+ FALSE, pool));
+ SVN_ERR(svn_fs_file_checksum(&checksum2, svn_checksum_md5, root2, path2,
+ FALSE, pool));
+ if (! svn_checksum_match(checksum1, checksum2))
+ {
+ *changed_p = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ /* And finally, different contents means the ... uh ... contents are
+ different. */
+ SVN_ERR(svn_fs_file_contents(&stream1, root1, path1, pool));
+ SVN_ERR(svn_fs_file_contents(&stream2, root2, path2, pool));
+ SVN_ERR(svn_stream_contents_same2(&same, stream1, stream2, pool));
+ *changed_p = !same;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Make the appropriate edits on FILE_BATON to change its contents and
+ properties from those in SOURCE_PATH to those in TARGET_PATH. */
+static svn_error_t *
+delta_files(struct context *c,
+ void *file_baton,
+ const char *source_path,
+ const char *target_path,
+ apr_pool_t *pool)
+{
+ apr_pool_t *subpool;
+ svn_boolean_t changed = TRUE;
+
+ SVN_ERR_ASSERT(target_path);
+
+ /* Make a subpool for local allocations. */
+ subpool = svn_pool_create(pool);
+
+ /* Compare the files' property lists. */
+ SVN_ERR(delta_proplists(c, source_path, target_path,
+ change_file_prop, file_baton, subpool));
+
+ if (source_path)
+ {
+ /* Is this delta calculation worth our time? If we are ignoring
+ ancestry, then our editor implementor isn't concerned by the
+ theoretical differences between "has contents which have not
+ changed with respect to" and "has the same actual contents
+ as". We'll do everything we can to avoid transmitting even
+ an empty text-delta in that case. */
+ if (c->ignore_ancestry)
+ SVN_ERR(svn_repos__compare_files(&changed,
+ c->target_root, target_path,
+ c->source_root, source_path,
+ subpool));
+ else
+ SVN_ERR(svn_fs_contents_changed(&changed,
+ c->target_root, target_path,
+ c->source_root, source_path,
+ subpool));
+ }
+ else
+ {
+ /* If there isn't a source path, this is an add, which
+ necessarily has textual mods. */
+ }
+
+ /* If there is a change, and the context indicates that we should
+ care about it, then hand it off to a delta stream. */
+ if (changed)
+ {
+ svn_txdelta_stream_t *delta_stream = NULL;
+ svn_checksum_t *source_checksum;
+ const char *source_hex_digest = NULL;
+
+ if (c->text_deltas)
+ {
+ /* Get a delta stream turning an empty file into one having
+ TARGET_PATH's contents. */
+ SVN_ERR(svn_fs_get_file_delta_stream
+ (&delta_stream,
+ source_path ? c->source_root : NULL,
+ source_path ? source_path : NULL,
+ c->target_root, target_path, subpool));
+ }
+
+ if (source_path)
+ {
+ SVN_ERR(svn_fs_file_checksum(&source_checksum, svn_checksum_md5,
+ c->source_root, source_path, TRUE,
+ subpool));
+
+ source_hex_digest = svn_checksum_to_cstring(source_checksum,
+ subpool);
+ }
+
+ SVN_ERR(send_text_delta(c, file_baton, source_hex_digest,
+ delta_stream, subpool));
+ }
+
+ /* Cleanup. */
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+
+
+/* Generic directory deltafication routines. */
+
+
+/* Emit a delta to delete the entry named TARGET_ENTRY from DIR_BATON. */
+static svn_error_t *
+delete(struct context *c,
+ void *dir_baton,
+ const char *edit_path,
+ apr_pool_t *pool)
+{
+ return c->editor->delete_entry(edit_path, SVN_INVALID_REVNUM,
+ dir_baton, pool);
+}
+
+
+/* If authorized, emit a delta to create the entry named TARGET_ENTRY
+ at the location EDIT_PATH. If not authorized, indicate that
+ EDIT_PATH is absent. Pass DIR_BATON through to editor functions
+ that require it. DEPTH is the depth from this point downward. */
+static svn_error_t *
+add_file_or_dir(struct context *c, void *dir_baton,
+ svn_depth_t depth,
+ const char *target_path,
+ const char *edit_path,
+ svn_node_kind_t tgt_kind,
+ apr_pool_t *pool)
+{
+ struct context *context = c;
+ svn_boolean_t allowed;
+
+ SVN_ERR_ASSERT(target_path && edit_path);
+
+ if (c->authz_read_func)
+ {
+ SVN_ERR(c->authz_read_func(&allowed, c->target_root, target_path,
+ c->authz_read_baton, pool));
+ if (!allowed)
+ return absent_file_or_dir(c, dir_baton, edit_path, tgt_kind, pool);
+ }
+
+ if (tgt_kind == svn_node_dir)
+ {
+ void *subdir_baton;
+
+ SVN_ERR(context->editor->add_directory(edit_path, dir_baton, NULL,
+ SVN_INVALID_REVNUM, pool,
+ &subdir_baton));
+ SVN_ERR(delta_dirs(context, subdir_baton, MAYBE_DEMOTE_DEPTH(depth),
+ NULL, target_path, edit_path, pool));
+ return context->editor->close_directory(subdir_baton, pool);
+ }
+ else
+ {
+ void *file_baton;
+ svn_checksum_t *checksum;
+
+ SVN_ERR(context->editor->add_file(edit_path, dir_baton,
+ NULL, SVN_INVALID_REVNUM, pool,
+ &file_baton));
+ SVN_ERR(delta_files(context, file_baton, NULL, target_path, pool));
+ SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
+ context->target_root, target_path,
+ TRUE, pool));
+ return context->editor->close_file
+ (file_baton, svn_checksum_to_cstring(checksum, pool), pool);
+ }
+}
+
+
+/* If authorized, emit a delta to modify EDIT_PATH with the changes
+ from SOURCE_PATH to TARGET_PATH. If not authorized, indicate that
+ EDIT_PATH is absent. Pass DIR_BATON through to editor functions
+ that require it. DEPTH is the depth from this point downward. */
+static svn_error_t *
+replace_file_or_dir(struct context *c,
+ void *dir_baton,
+ svn_depth_t depth,
+ const char *source_path,
+ const char *target_path,
+ const char *edit_path,
+ svn_node_kind_t tgt_kind,
+ apr_pool_t *pool)
+{
+ svn_revnum_t base_revision = SVN_INVALID_REVNUM;
+ svn_boolean_t allowed;
+
+ SVN_ERR_ASSERT(target_path && source_path && edit_path);
+
+ if (c->authz_read_func)
+ {
+ SVN_ERR(c->authz_read_func(&allowed, c->target_root, target_path,
+ c->authz_read_baton, pool));
+ if (!allowed)
+ return absent_file_or_dir(c, dir_baton, edit_path, tgt_kind, pool);
+ }
+
+ /* Get the base revision for the entry from the hash. */
+ base_revision = get_path_revision(c->source_root, source_path, pool);
+
+ if (tgt_kind == svn_node_dir)
+ {
+ void *subdir_baton;
+
+ SVN_ERR(c->editor->open_directory(edit_path, dir_baton,
+ base_revision, pool,
+ &subdir_baton));
+ SVN_ERR(delta_dirs(c, subdir_baton, MAYBE_DEMOTE_DEPTH(depth),
+ source_path, target_path, edit_path, pool));
+ return c->editor->close_directory(subdir_baton, pool);
+ }
+ else
+ {
+ void *file_baton;
+ svn_checksum_t *checksum;
+
+ SVN_ERR(c->editor->open_file(edit_path, dir_baton, base_revision,
+ pool, &file_baton));
+ SVN_ERR(delta_files(c, file_baton, source_path, target_path, pool));
+ SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
+ c->target_root, target_path, TRUE,
+ pool));
+ return c->editor->close_file
+ (file_baton, svn_checksum_to_cstring(checksum, pool), pool);
+ }
+}
+
+
+/* In directory DIR_BATON, indicate that EDIT_PATH (relative to the
+ edit root) is absent by invoking C->editor->absent_directory or
+ C->editor->absent_file (depending on TGT_KIND). */
+static svn_error_t *
+absent_file_or_dir(struct context *c,
+ void *dir_baton,
+ const char *edit_path,
+ svn_node_kind_t tgt_kind,
+ apr_pool_t *pool)
+{
+ SVN_ERR_ASSERT(edit_path);
+
+ if (tgt_kind == svn_node_dir)
+ return c->editor->absent_directory(edit_path, dir_baton, pool);
+ else
+ return c->editor->absent_file(edit_path, dir_baton, pool);
+}
+
+
+/* Emit deltas to turn SOURCE_PATH into TARGET_PATH. Assume that
+ DIR_BATON represents the directory we're constructing to the editor
+ in the context C. */
+static svn_error_t *
+delta_dirs(struct context *c,
+ void *dir_baton,
+ svn_depth_t depth,
+ const char *source_path,
+ const char *target_path,
+ const char *edit_path,
+ apr_pool_t *pool)
+{
+ apr_hash_t *s_entries = 0, *t_entries = 0;
+ apr_hash_index_t *hi;
+ apr_pool_t *subpool;
+
+ SVN_ERR_ASSERT(target_path);
+
+ /* Compare the property lists. */
+ SVN_ERR(delta_proplists(c, source_path, target_path,
+ change_dir_prop, dir_baton, pool));
+
+ /* Get the list of entries in each of source and target. */
+ SVN_ERR(svn_fs_dir_entries(&t_entries, c->target_root,
+ target_path, pool));
+ if (source_path)
+ SVN_ERR(svn_fs_dir_entries(&s_entries, c->source_root,
+ source_path, pool));
+
+ /* Make a subpool for local allocations. */
+ subpool = svn_pool_create(pool);
+
+ /* Loop over the hash of entries in the target, searching for its
+ partner in the source. If we find the matching partner entry,
+ use editor calls to replace the one in target with a new version
+ if necessary, then remove that entry from the source entries
+ hash. If we can't find a related node in the source, we use
+ editor calls to add the entry as a new item in the target.
+ Having handled all the entries that exist in target, any entries
+ still remaining the source entries hash represent entries that no
+ longer exist in target. Use editor calls to delete those entries
+ from the target tree. */
+ for (hi = apr_hash_first(pool, t_entries); hi; hi = apr_hash_next(hi))
+ {
+ const svn_fs_dirent_t *s_entry, *t_entry;
+ const void *key;
+ void *val;
+ apr_ssize_t klen;
+ const char *t_fullpath;
+ const char *e_fullpath;
+ const char *s_fullpath;
+ svn_node_kind_t tgt_kind;
+
+ /* Clear out our subpool for the next iteration... */
+ svn_pool_clear(subpool);
+
+ /* KEY is the entry name in target, VAL the dirent */
+ apr_hash_this(hi, &key, &klen, &val);
+ t_entry = val;
+ tgt_kind = t_entry->kind;
+ t_fullpath = svn_relpath_join(target_path, t_entry->name, subpool);
+ e_fullpath = svn_relpath_join(edit_path, t_entry->name, subpool);
+
+ /* Can we find something with the same name in the source
+ entries hash? */
+ if (s_entries && ((s_entry = apr_hash_get(s_entries, key, klen)) != 0))
+ {
+ svn_node_kind_t src_kind;
+
+ s_fullpath = svn_relpath_join(source_path, t_entry->name, subpool);
+ src_kind = s_entry->kind;
+
+ if (depth == svn_depth_infinity
+ || src_kind != svn_node_dir
+ || (src_kind == svn_node_dir
+ && depth == svn_depth_immediates))
+ {
+ /* Use svn_fs_compare_ids() to compare our current
+ source and target ids.
+
+ 0: means they are the same id, and this is a noop.
+ -1: means they are unrelated, so we have to delete the
+ old one and add the new one.
+ 1: means the nodes are related through ancestry, so go
+ ahead and do the replace directly. */
+ int distance = svn_fs_compare_ids(s_entry->id, t_entry->id);
+ if (distance == 0)
+ {
+ /* no-op */
+ }
+ else if ((src_kind != tgt_kind)
+ || ((distance == -1) && (! c->ignore_ancestry)))
+ {
+ SVN_ERR(delete(c, dir_baton, e_fullpath, subpool));
+ SVN_ERR(add_file_or_dir(c, dir_baton,
+ MAYBE_DEMOTE_DEPTH(depth),
+ t_fullpath, e_fullpath, tgt_kind,
+ subpool));
+ }
+ else
+ {
+ SVN_ERR(replace_file_or_dir(c, dir_baton,
+ MAYBE_DEMOTE_DEPTH(depth),
+ s_fullpath, t_fullpath,
+ e_fullpath, tgt_kind,
+ subpool));
+ }
+ }
+
+ /* Remove the entry from the source_hash. */
+ svn_hash_sets(s_entries, key, NULL);
+ }
+ else
+ {
+ if (depth == svn_depth_infinity
+ || tgt_kind != svn_node_dir
+ || (tgt_kind == svn_node_dir
+ && depth == svn_depth_immediates))
+ {
+ SVN_ERR(add_file_or_dir(c, dir_baton,
+ MAYBE_DEMOTE_DEPTH(depth),
+ t_fullpath, e_fullpath, tgt_kind,
+ subpool));
+ }
+ }
+ }
+
+ /* All that is left in the source entries hash are things that need
+ to be deleted. Delete them. */
+ if (s_entries)
+ {
+ for (hi = apr_hash_first(pool, s_entries); hi; hi = apr_hash_next(hi))
+ {
+ const svn_fs_dirent_t *s_entry;
+ void *val;
+ const char *e_fullpath;
+ svn_node_kind_t src_kind;
+
+ /* Clear out our subpool for the next iteration... */
+ svn_pool_clear(subpool);
+
+ /* KEY is the entry name in source, VAL the dirent */
+ apr_hash_this(hi, NULL, NULL, &val);
+ s_entry = val;
+ src_kind = s_entry->kind;
+ e_fullpath = svn_relpath_join(edit_path, s_entry->name, subpool);
+
+ /* Do we actually want to delete the dir if we're non-recursive? */
+ if (depth == svn_depth_infinity
+ || src_kind != svn_node_dir
+ || (src_kind == svn_node_dir
+ && depth == svn_depth_immediates))
+ {
+ SVN_ERR(delete(c, dir_baton, e_fullpath, subpool));
+ }
+ }
+ }
+
+ /* Destroy local allocation subpool. */
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
OpenPOWER on IntegriCloud