2 * delta.c: an editor driver for expressing differences between two trees
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
28 #include "svn_types.h"
29 #include "svn_delta.h"
31 #include "svn_checksum.h"
33 #include "svn_repos.h"
34 #include "svn_pools.h"
35 #include "svn_props.h"
36 #include "svn_private_config.h"
41 /* THINGS TODO: Currently the code herein gives only a slight nod to
42 fully supporting directory deltas that involve renames, copies, and
46 /* Some datatypes and declarations used throughout the file. */
49 /* Parameters which remain constant throughout a delta traversal.
50 At the top of the recursion, we initialize one of these structures.
51 Then we pass it down to every call. This way, functions invoked
52 deep in the recursion can get access to this traversal's global
53 parameters, without using global variables. */
55 const svn_delta_editor_t *editor;
56 const char *edit_base_path;
57 svn_fs_root_t *source_root;
58 svn_fs_root_t *target_root;
59 svn_repos_authz_func_t authz_read_func;
60 void *authz_read_baton;
61 svn_boolean_t text_deltas;
62 svn_boolean_t entry_props;
63 svn_boolean_t ignore_ancestry;
67 /* The type of a function that accepts changes to an object's property
68 list. OBJECT is the object whose properties are being changed.
69 NAME is the name of the property to change. VALUE is the new value
70 for the property, or zero if the property should be deleted. */
71 typedef svn_error_t *proplist_change_fn_t(struct context *c,
74 const svn_string_t *value,
79 /* Some prototypes for functions used throughout. See each individual
80 function for information about what it does. */
83 /* Retrieving the base revision from the path/revision hash. */
84 static svn_revnum_t get_path_revision(svn_fs_root_t *root,
89 /* proplist_change_fn_t property changing functions. */
90 static svn_error_t *change_dir_prop(struct context *c,
93 const svn_string_t *value,
96 static svn_error_t *change_file_prop(struct context *c,
99 const svn_string_t *value,
103 /* Constructing deltas for properties of files and directories. */
104 static svn_error_t *delta_proplists(struct context *c,
105 const char *source_path,
106 const char *target_path,
107 proplist_change_fn_t *change_fn,
112 /* Constructing deltas for file constents. */
113 static svn_error_t *send_text_delta(struct context *c,
115 const char *base_checksum,
116 svn_txdelta_stream_t *delta_stream,
119 static svn_error_t *delta_files(struct context *c,
121 const char *source_path,
122 const char *target_path,
126 /* Generic directory deltafication routines. */
127 static svn_error_t *delete(struct context *c,
129 const char *edit_path,
132 static svn_error_t *add_file_or_dir(struct context *c,
135 const char *target_path,
136 const char *edit_path,
137 svn_node_kind_t tgt_kind,
140 static svn_error_t *replace_file_or_dir(struct context *c,
143 const char *source_path,
144 const char *target_path,
145 const char *edit_path,
146 svn_node_kind_t tgt_kind,
149 static svn_error_t *absent_file_or_dir(struct context *c,
151 const char *edit_path,
152 svn_node_kind_t tgt_kind,
155 static svn_error_t *delta_dirs(struct context *c,
158 const char *source_path,
159 const char *target_path,
160 const char *edit_path,
165 #define MAYBE_DEMOTE_DEPTH(depth) \
166 (((depth) == svn_depth_immediates || (depth) == svn_depth_files) \
171 /* Return the error 'SVN_ERR_AUTHZ_ROOT_UNREADABLE' if PATH in ROOT is
172 * unreadable according to AUTHZ_READ_FUNC with AUTHZ_READ_BATON.
174 * PATH should be the implicit root path of an editor drive, that is,
175 * the path used by editor->open_root().
178 authz_root_check(svn_fs_root_t *root,
180 svn_repos_authz_func_t authz_read_func,
181 void *authz_read_baton,
184 svn_boolean_t allowed;
188 SVN_ERR(authz_read_func(&allowed, root, path, authz_read_baton, pool));
191 return svn_error_create(SVN_ERR_AUTHZ_ROOT_UNREADABLE, 0,
192 _("Unable to open root of edit"));
199 /* Public interface to computing directory deltas. */
201 svn_repos_dir_delta2(svn_fs_root_t *src_root,
202 const char *src_parent_dir,
203 const char *src_entry,
204 svn_fs_root_t *tgt_root,
205 const char *tgt_fullpath,
206 const svn_delta_editor_t *editor,
208 svn_repos_authz_func_t authz_read_func,
209 void *authz_read_baton,
210 svn_boolean_t text_deltas,
212 svn_boolean_t entry_props,
213 svn_boolean_t ignore_ancestry,
216 void *root_baton = NULL;
218 const char *src_fullpath;
219 svn_node_kind_t src_kind, tgt_kind;
220 svn_revnum_t rootrev;
221 svn_fs_node_relation_t relation;
222 const char *authz_root_path;
224 /* SRC_PARENT_DIR must be valid. */
226 src_parent_dir = svn_relpath_canonicalize(src_parent_dir, pool);
228 return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, 0,
229 "Invalid source parent directory '(null)'");
231 /* TGT_FULLPATH must be valid. */
233 tgt_fullpath = svn_relpath_canonicalize(tgt_fullpath, pool);
235 return svn_error_create(SVN_ERR_FS_PATH_SYNTAX, 0,
236 _("Invalid target path"));
238 if (depth == svn_depth_exclude)
239 return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL,
240 _("Delta depth 'exclude' not supported"));
242 /* Calculate the fs path implicitly used for editor->open_root, so
243 we can do an authz check on that path first. */
245 authz_root_path = svn_relpath_dirname(tgt_fullpath, pool);
247 authz_root_path = tgt_fullpath;
249 /* Construct the full path of the source item. */
250 src_fullpath = svn_relpath_join(src_parent_dir, src_entry, pool);
252 /* Get the node kinds for the source and target paths. */
253 SVN_ERR(svn_fs_check_path(&tgt_kind, tgt_root, tgt_fullpath, pool));
254 SVN_ERR(svn_fs_check_path(&src_kind, src_root, src_fullpath, pool));
256 /* If neither of our paths exists, we don't really have anything to do. */
257 if ((tgt_kind == svn_node_none) && (src_kind == svn_node_none))
260 /* If either the source or the target is a non-directory, we
261 require that a SRC_ENTRY be supplied. */
262 if ((! *src_entry) && ((src_kind != svn_node_dir)
263 || tgt_kind != svn_node_dir))
264 return svn_error_create
265 (SVN_ERR_FS_PATH_SYNTAX, 0,
266 _("Invalid editor anchoring; at least one of the "
267 "input paths is not a directory and there was no source entry"));
269 /* Don't report / compare stale revprops. However, revprop changes that
270 * are made by a 3rd party outside this delta operation, may not be
271 * detected as per our visibility guarantees. Reset the revprop caches
272 * for both roots in case they belong to different svn_fs_t instances. */
273 SVN_ERR(svn_fs_refresh_revision_props(svn_fs_root_fs(tgt_root), pool));
274 SVN_ERR(svn_fs_refresh_revision_props(svn_fs_root_fs(src_root), pool));
276 /* Set the global target revision if one can be determined. */
277 if (svn_fs_is_revision_root(tgt_root))
279 SVN_ERR(editor->set_target_revision
280 (edit_baton, svn_fs_revision_root_revision(tgt_root), pool));
282 else if (svn_fs_is_txn_root(tgt_root))
284 SVN_ERR(editor->set_target_revision
285 (edit_baton, svn_fs_txn_root_base_revision(tgt_root), pool));
288 /* Setup our pseudo-global structure here. We need these variables
289 throughout the deltafication process, so pass them around by
290 reference to all the helper functions. */
292 c.source_root = src_root;
293 c.target_root = tgt_root;
294 c.authz_read_func = authz_read_func;
295 c.authz_read_baton = authz_read_baton;
296 c.text_deltas = text_deltas;
297 c.entry_props = entry_props;
298 c.ignore_ancestry = ignore_ancestry;
300 /* Get our editor root's revision. */
301 rootrev = get_path_revision(src_root, src_parent_dir, pool);
303 /* If one or the other of our paths doesn't exist, we have to handle
304 those cases specially. */
305 if (tgt_kind == svn_node_none)
307 /* Caller thinks that target still exists, but it doesn't.
308 So transform their source path to "nothing" by deleting it. */
309 SVN_ERR(authz_root_check(tgt_root, authz_root_path,
310 authz_read_func, authz_read_baton, pool));
311 SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton));
312 SVN_ERR(delete(&c, root_baton, src_entry, pool));
315 if (src_kind == svn_node_none)
317 /* The source path no longer exists, but the target does.
318 So transform "nothing" into "something" by adding. */
319 SVN_ERR(authz_root_check(tgt_root, authz_root_path,
320 authz_read_func, authz_read_baton, pool));
321 SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton));
322 SVN_ERR(add_file_or_dir(&c, root_baton, depth, tgt_fullpath,
323 src_entry, tgt_kind, pool));
327 /* Get and compare the node IDs for the source and target. */
328 SVN_ERR(svn_fs_node_relation(&relation, tgt_root, tgt_fullpath,
329 src_root, src_fullpath, pool));
331 if (relation == svn_fs_node_unchanged)
333 /* They are the same node! No-op (you gotta love those). */
338 /* If the nodes have different kinds, we must delete the one and
339 add the other. Also, if they are completely unrelated and
340 our caller is interested in relatedness, we do the same thing. */
341 if ((src_kind != tgt_kind)
342 || ((relation == svn_fs_node_unrelated) && (! ignore_ancestry)))
344 SVN_ERR(authz_root_check(tgt_root, authz_root_path,
345 authz_read_func, authz_read_baton, pool));
346 SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton));
347 SVN_ERR(delete(&c, root_baton, src_entry, pool));
348 SVN_ERR(add_file_or_dir(&c, root_baton, depth, tgt_fullpath,
349 src_entry, tgt_kind, pool));
351 /* Otherwise, we just replace the one with the other. */
354 SVN_ERR(authz_root_check(tgt_root, authz_root_path,
355 authz_read_func, authz_read_baton, pool));
356 SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton));
357 SVN_ERR(replace_file_or_dir(&c, root_baton, depth, src_fullpath,
358 tgt_fullpath, src_entry,
364 /* There is no entry given, so delta the whole parent directory. */
365 SVN_ERR(authz_root_check(tgt_root, authz_root_path,
366 authz_read_func, authz_read_baton, pool));
367 SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton));
368 SVN_ERR(delta_dirs(&c, root_baton, depth, src_fullpath,
369 tgt_fullpath, "", pool));
374 /* Make sure we close the root directory if we opened one above. */
376 SVN_ERR(editor->close_directory(root_baton, pool));
378 /* Close the edit. */
379 return editor->close_edit(edit_baton, pool);
383 /* Retrieving the base revision from the path/revision hash. */
387 get_path_revision(svn_fs_root_t *root,
391 svn_revnum_t revision;
394 /* Easy out -- if ROOT is a revision root, we can use the revision
395 that it's a root of. */
396 if (svn_fs_is_revision_root(root))
397 return svn_fs_revision_root_revision(root);
399 /* Else, this must be a transaction root, so ask the filesystem in
400 what revision this path was created. */
401 if ((err = svn_fs_node_created_rev(&revision, root, path, pool)))
403 revision = SVN_INVALID_REVNUM;
404 svn_error_clear(err);
407 /* If we don't get back a valid revision, this path is mutable in
408 the transaction. We should probably examine the node on which it
409 is based, doable by querying for the node-id of the path, and
410 then examining that node-id's predecessor. ### This predecessor
411 determination isn't exposed via the FS public API right now, so
412 for now, we'll just return the SVN_INVALID_REVNUM. */
417 /* proplist_change_fn_t property changing functions. */
420 /* Call the directory property-setting function of C->editor to set
421 the property NAME to given VALUE on the OBJECT passed to this
424 change_dir_prop(struct context *c,
427 const svn_string_t *value,
430 return c->editor->change_dir_prop(object, name, value, pool);
434 /* Call the file property-setting function of C->editor to set the
435 property NAME to given VALUE on the OBJECT passed to this
438 change_file_prop(struct context *c,
441 const svn_string_t *value,
444 return c->editor->change_file_prop(object, name, value, pool);
450 /* Constructing deltas for properties of files and directories. */
453 /* Generate the appropriate property editing calls to turn the
454 properties of SOURCE_PATH into those of TARGET_PATH. If
455 SOURCE_PATH is NULL, this is an add, so assume the target starts
456 with no properties. Pass OBJECT on to the editor function wrapper
459 delta_proplists(struct context *c,
460 const char *source_path,
461 const char *target_path,
462 proplist_change_fn_t *change_fn,
466 apr_hash_t *s_props = 0;
467 apr_hash_t *t_props = 0;
469 apr_array_header_t *prop_diffs;
472 SVN_ERR_ASSERT(target_path);
474 /* Make a subpool for local allocations. */
475 subpool = svn_pool_create(pool);
477 /* If we're supposed to send entry props for all non-deleted items,
481 svn_revnum_t committed_rev = SVN_INVALID_REVNUM;
482 svn_string_t *cr_str = NULL;
483 svn_string_t *committed_date = NULL;
484 svn_string_t *last_author = NULL;
486 /* Get the CR and two derivative props. ### check for error returns. */
487 SVN_ERR(svn_fs_node_created_rev(&committed_rev, c->target_root,
488 target_path, subpool));
489 if (SVN_IS_VALID_REVNUM(committed_rev))
491 svn_fs_t *fs = svn_fs_root_fs(c->target_root);
495 /* Transmit the committed-rev. */
496 cr_str = svn_string_createf(subpool, "%ld",
498 SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_COMMITTED_REV,
501 SVN_ERR(svn_fs_revision_proplist2(&r_props, fs, committed_rev,
502 FALSE, pool, subpool));
504 /* Transmit the committed-date. */
505 committed_date = svn_hash_gets(r_props, SVN_PROP_REVISION_DATE);
506 if (committed_date || source_path)
508 SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_COMMITTED_DATE,
509 committed_date, subpool));
512 /* Transmit the last-author. */
513 last_author = svn_hash_gets(r_props, SVN_PROP_REVISION_AUTHOR);
514 if (last_author || source_path)
516 SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_LAST_AUTHOR,
517 last_author, subpool));
520 /* Transmit the UUID. */
521 SVN_ERR(svn_fs_get_uuid(fs, &uuid, subpool));
522 SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_UUID,
523 svn_string_create(uuid, subpool),
530 svn_boolean_t changed;
532 /* Is this deltification worth our time? */
533 SVN_ERR(svn_fs_props_different(&changed, c->target_root, target_path,
534 c->source_root, source_path, subpool));
538 /* If so, go ahead and get the source path's properties. */
539 SVN_ERR(svn_fs_node_proplist(&s_props, c->source_root,
540 source_path, subpool));
544 s_props = apr_hash_make(subpool);
547 /* Get the target path's properties */
548 SVN_ERR(svn_fs_node_proplist(&t_props, c->target_root,
549 target_path, subpool));
551 /* Now transmit the differences. */
552 SVN_ERR(svn_prop_diffs(&prop_diffs, t_props, s_props, subpool));
553 for (i = 0; i < prop_diffs->nelts; i++)
555 const svn_prop_t *pc = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t);
556 SVN_ERR(change_fn(c, object, pc->name, pc->value, subpool));
560 /* Destroy local subpool. */
561 svn_pool_destroy(subpool);
569 /* Constructing deltas for file contents. */
572 /* Change the contents of FILE_BATON in C->editor, according to the
573 text delta from DELTA_STREAM. Pass BASE_CHECKSUM along to
574 C->editor->apply_textdelta. */
576 send_text_delta(struct context *c,
578 const char *base_checksum,
579 svn_txdelta_stream_t *delta_stream,
582 svn_txdelta_window_handler_t delta_handler;
583 void *delta_handler_baton;
585 /* Get a handler that will apply the delta to the file. */
586 SVN_ERR(c->editor->apply_textdelta
587 (file_baton, base_checksum, pool,
588 &delta_handler, &delta_handler_baton));
590 if (c->text_deltas && delta_stream)
592 /* Deliver the delta stream to the file. */
593 return svn_txdelta_send_txstream(delta_stream,
600 /* The caller doesn't want text delta data. Just send a single
602 return delta_handler(NULL, delta_handler_baton);
606 /* Make the appropriate edits on FILE_BATON to change its contents and
607 properties from those in SOURCE_PATH to those in TARGET_PATH. */
609 delta_files(struct context *c,
611 const char *source_path,
612 const char *target_path,
616 svn_boolean_t changed = TRUE;
618 SVN_ERR_ASSERT(target_path);
620 /* Make a subpool for local allocations. */
621 subpool = svn_pool_create(pool);
623 /* Compare the files' property lists. */
624 SVN_ERR(delta_proplists(c, source_path, target_path,
625 change_file_prop, file_baton, subpool));
629 SVN_ERR(svn_fs_contents_different(&changed,
630 c->target_root, target_path,
631 c->source_root, source_path,
636 /* If there isn't a source path, this is an add, which
637 necessarily has textual mods. */
640 /* If there is a change, and the context indicates that we should
641 care about it, then hand it off to a delta stream. */
644 svn_txdelta_stream_t *delta_stream = NULL;
645 svn_checksum_t *source_checksum;
646 const char *source_hex_digest = NULL;
650 /* Get a delta stream turning an empty file into one having
651 TARGET_PATH's contents. */
652 SVN_ERR(svn_fs_get_file_delta_stream
654 source_path ? c->source_root : NULL,
655 source_path ? source_path : NULL,
656 c->target_root, target_path, subpool));
661 SVN_ERR(svn_fs_file_checksum(&source_checksum, svn_checksum_md5,
662 c->source_root, source_path, TRUE,
665 source_hex_digest = svn_checksum_to_cstring(source_checksum,
669 SVN_ERR(send_text_delta(c, file_baton, source_hex_digest,
670 delta_stream, subpool));
674 svn_pool_destroy(subpool);
682 /* Generic directory deltafication routines. */
685 /* Emit a delta to delete the entry named TARGET_ENTRY from DIR_BATON. */
687 delete(struct context *c,
689 const char *edit_path,
692 return c->editor->delete_entry(edit_path, SVN_INVALID_REVNUM,
697 /* If authorized, emit a delta to create the entry named TARGET_ENTRY
698 at the location EDIT_PATH. If not authorized, indicate that
699 EDIT_PATH is absent. Pass DIR_BATON through to editor functions
700 that require it. DEPTH is the depth from this point downward. */
702 add_file_or_dir(struct context *c, void *dir_baton,
704 const char *target_path,
705 const char *edit_path,
706 svn_node_kind_t tgt_kind,
709 struct context *context = c;
710 svn_boolean_t allowed;
712 SVN_ERR_ASSERT(target_path && edit_path);
714 if (c->authz_read_func)
716 SVN_ERR(c->authz_read_func(&allowed, c->target_root, target_path,
717 c->authz_read_baton, pool));
719 return absent_file_or_dir(c, dir_baton, edit_path, tgt_kind, pool);
722 if (tgt_kind == svn_node_dir)
726 SVN_ERR(context->editor->add_directory(edit_path, dir_baton, NULL,
727 SVN_INVALID_REVNUM, pool,
729 SVN_ERR(delta_dirs(context, subdir_baton, MAYBE_DEMOTE_DEPTH(depth),
730 NULL, target_path, edit_path, pool));
731 return context->editor->close_directory(subdir_baton, pool);
736 svn_checksum_t *checksum;
738 SVN_ERR(context->editor->add_file(edit_path, dir_baton,
739 NULL, SVN_INVALID_REVNUM, pool,
741 SVN_ERR(delta_files(context, file_baton, NULL, target_path, pool));
742 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
743 context->target_root, target_path,
745 return context->editor->close_file
746 (file_baton, svn_checksum_to_cstring(checksum, pool), pool);
751 /* If authorized, emit a delta to modify EDIT_PATH with the changes
752 from SOURCE_PATH to TARGET_PATH. If not authorized, indicate that
753 EDIT_PATH is absent. Pass DIR_BATON through to editor functions
754 that require it. DEPTH is the depth from this point downward. */
756 replace_file_or_dir(struct context *c,
759 const char *source_path,
760 const char *target_path,
761 const char *edit_path,
762 svn_node_kind_t tgt_kind,
765 svn_revnum_t base_revision = SVN_INVALID_REVNUM;
766 svn_boolean_t allowed;
768 SVN_ERR_ASSERT(target_path && source_path && edit_path);
770 if (c->authz_read_func)
772 SVN_ERR(c->authz_read_func(&allowed, c->target_root, target_path,
773 c->authz_read_baton, pool));
775 return absent_file_or_dir(c, dir_baton, edit_path, tgt_kind, pool);
778 /* Get the base revision for the entry from the hash. */
779 base_revision = get_path_revision(c->source_root, source_path, pool);
781 if (tgt_kind == svn_node_dir)
785 SVN_ERR(c->editor->open_directory(edit_path, dir_baton,
788 SVN_ERR(delta_dirs(c, subdir_baton, MAYBE_DEMOTE_DEPTH(depth),
789 source_path, target_path, edit_path, pool));
790 return c->editor->close_directory(subdir_baton, pool);
795 svn_checksum_t *checksum;
797 SVN_ERR(c->editor->open_file(edit_path, dir_baton, base_revision,
799 SVN_ERR(delta_files(c, file_baton, source_path, target_path, pool));
800 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
801 c->target_root, target_path, TRUE,
803 return c->editor->close_file
804 (file_baton, svn_checksum_to_cstring(checksum, pool), pool);
809 /* In directory DIR_BATON, indicate that EDIT_PATH (relative to the
810 edit root) is absent by invoking C->editor->absent_directory or
811 C->editor->absent_file (depending on TGT_KIND). */
813 absent_file_or_dir(struct context *c,
815 const char *edit_path,
816 svn_node_kind_t tgt_kind,
819 SVN_ERR_ASSERT(edit_path);
821 if (tgt_kind == svn_node_dir)
822 return c->editor->absent_directory(edit_path, dir_baton, pool);
824 return c->editor->absent_file(edit_path, dir_baton, pool);
828 /* Emit deltas to turn SOURCE_PATH into TARGET_PATH. Assume that
829 DIR_BATON represents the directory we're constructing to the editor
832 delta_dirs(struct context *c,
835 const char *source_path,
836 const char *target_path,
837 const char *edit_path,
840 apr_hash_t *s_entries = 0, *t_entries = 0;
841 apr_hash_index_t *hi;
844 SVN_ERR_ASSERT(target_path);
846 /* Compare the property lists. */
847 SVN_ERR(delta_proplists(c, source_path, target_path,
848 change_dir_prop, dir_baton, pool));
850 /* Get the list of entries in each of source and target. */
851 SVN_ERR(svn_fs_dir_entries(&t_entries, c->target_root,
854 SVN_ERR(svn_fs_dir_entries(&s_entries, c->source_root,
857 /* Make a subpool for local allocations. */
858 subpool = svn_pool_create(pool);
860 /* Loop over the hash of entries in the target, searching for its
861 partner in the source. If we find the matching partner entry,
862 use editor calls to replace the one in target with a new version
863 if necessary, then remove that entry from the source entries
864 hash. If we can't find a related node in the source, we use
865 editor calls to add the entry as a new item in the target.
866 Having handled all the entries that exist in target, any entries
867 still remaining the source entries hash represent entries that no
868 longer exist in target. Use editor calls to delete those entries
869 from the target tree. */
870 for (hi = apr_hash_first(pool, t_entries); hi; hi = apr_hash_next(hi))
872 const void *key = apr_hash_this_key(hi);
873 apr_ssize_t klen = apr_hash_this_key_len(hi);
874 const svn_fs_dirent_t *t_entry = apr_hash_this_val(hi);
875 const svn_fs_dirent_t *s_entry;
876 const char *t_fullpath;
877 const char *e_fullpath;
878 const char *s_fullpath;
879 svn_node_kind_t tgt_kind;
881 /* Clear out our subpool for the next iteration... */
882 svn_pool_clear(subpool);
884 tgt_kind = t_entry->kind;
885 t_fullpath = svn_relpath_join(target_path, t_entry->name, subpool);
886 e_fullpath = svn_relpath_join(edit_path, t_entry->name, subpool);
888 /* Can we find something with the same name in the source
890 if (s_entries && ((s_entry = apr_hash_get(s_entries, key, klen)) != 0))
892 svn_node_kind_t src_kind;
894 s_fullpath = svn_relpath_join(source_path, t_entry->name, subpool);
895 src_kind = s_entry->kind;
897 if (depth == svn_depth_infinity
898 || src_kind != svn_node_dir
899 || (src_kind == svn_node_dir
900 && depth == svn_depth_immediates))
902 /* Use svn_fs_compare_ids() to compare our current
903 source and target ids.
905 0: means they are the same id, and this is a noop.
906 -1: means they are unrelated, so we have to delete the
907 old one and add the new one.
908 1: means the nodes are related through ancestry, so go
909 ahead and do the replace directly. */
910 int distance = svn_fs_compare_ids(s_entry->id, t_entry->id);
915 else if ((src_kind != tgt_kind)
916 || ((distance == -1) && (! c->ignore_ancestry)))
918 SVN_ERR(delete(c, dir_baton, e_fullpath, subpool));
919 SVN_ERR(add_file_or_dir(c, dir_baton,
920 MAYBE_DEMOTE_DEPTH(depth),
921 t_fullpath, e_fullpath, tgt_kind,
926 SVN_ERR(replace_file_or_dir(c, dir_baton,
927 MAYBE_DEMOTE_DEPTH(depth),
928 s_fullpath, t_fullpath,
929 e_fullpath, tgt_kind,
934 /* Remove the entry from the source_hash. */
935 svn_hash_sets(s_entries, key, NULL);
939 if (depth == svn_depth_infinity
940 || tgt_kind != svn_node_dir
941 || (tgt_kind == svn_node_dir
942 && depth == svn_depth_immediates))
944 SVN_ERR(add_file_or_dir(c, dir_baton,
945 MAYBE_DEMOTE_DEPTH(depth),
946 t_fullpath, e_fullpath, tgt_kind,
952 /* All that is left in the source entries hash are things that need
953 to be deleted. Delete them. */
956 for (hi = apr_hash_first(pool, s_entries); hi; hi = apr_hash_next(hi))
958 const svn_fs_dirent_t *s_entry = apr_hash_this_val(hi);
959 const char *e_fullpath;
960 svn_node_kind_t src_kind;
962 /* Clear out our subpool for the next iteration... */
963 svn_pool_clear(subpool);
965 src_kind = s_entry->kind;
966 e_fullpath = svn_relpath_join(edit_path, s_entry->name, subpool);
968 /* Do we actually want to delete the dir if we're non-recursive? */
969 if (depth == svn_depth_infinity
970 || src_kind != svn_node_dir
971 || (src_kind == svn_node_dir
972 && depth == svn_depth_immediates))
974 SVN_ERR(delete(c, dir_baton, e_fullpath, subpool));
979 /* Destroy local allocation subpool. */
980 svn_pool_destroy(subpool);