| /* |
| * 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; |
| } |
| |
| |
| /* 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, *canonicalized_path; |
| svn_node_kind_t src_kind, tgt_kind; |
| svn_revnum_t rootrev; |
| svn_fs_node_relation_t relation; |
| const char *authz_root_path; |
| |
| /* SRC_PARENT_DIR must be valid. */ |
| if (src_parent_dir) |
| { |
| SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL, |
| src_parent_dir, pool, pool)); |
| src_parent_dir = canonicalized_path; |
| } |
| else |
| return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, 0, |
| "Invalid source parent directory '(null)'"); |
| |
| /* TGT_FULLPATH must be valid. */ |
| if (tgt_fullpath) |
| { |
| SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL, |
| tgt_fullpath, pool, pool)); |
| tgt_fullpath = canonicalized_path; |
| } |
| 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")); |
| |
| /* Don't report / compare stale revprops. However, revprop changes that |
| * are made by a 3rd party outside this delta operation, may not be |
| * detected as per our visibility guarantees. Reset the revprop caches |
| * for both roots in case they belong to different svn_fs_t instances. */ |
| SVN_ERR(svn_fs_refresh_revision_props(svn_fs_root_fs(tgt_root), pool)); |
| SVN_ERR(svn_fs_refresh_revision_props(svn_fs_root_fs(src_root), pool)); |
| |
| /* 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_relation(&relation, tgt_root, tgt_fullpath, |
| src_root, src_fullpath, pool)); |
| |
| if (relation == svn_fs_node_unchanged) |
| { |
| /* 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) |
| || ((relation == svn_fs_node_unrelated) && (! 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_proplist2(&r_props, fs, committed_rev, |
| FALSE, pool, subpool)); |
| |
| /* 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_different(&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); |
| } |
| } |
| |
| /* 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) |
| { |
| SVN_ERR(svn_fs_contents_different(&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 void *key = apr_hash_this_key(hi); |
| apr_ssize_t klen = apr_hash_this_key_len(hi); |
| const svn_fs_dirent_t *t_entry = apr_hash_this_val(hi); |
| const svn_fs_dirent_t *s_entry; |
| 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); |
| |
| 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 = apr_hash_this_val(hi); |
| const char *e_fullpath; |
| svn_node_kind_t src_kind; |
| |
| /* Clear out our subpool for the next iteration... */ |
| svn_pool_clear(subpool); |
| |
| 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; |
| } |