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"));
200 not_a_dir_error(const char *role,
203 return svn_error_createf
204 (SVN_ERR_FS_NOT_DIRECTORY, 0,
205 "Invalid %s directory '%s'",
206 role, path ? path : "(null)");
210 /* Public interface to computing directory deltas. */
212 svn_repos_dir_delta2(svn_fs_root_t *src_root,
213 const char *src_parent_dir,
214 const char *src_entry,
215 svn_fs_root_t *tgt_root,
216 const char *tgt_fullpath,
217 const svn_delta_editor_t *editor,
219 svn_repos_authz_func_t authz_read_func,
220 void *authz_read_baton,
221 svn_boolean_t text_deltas,
223 svn_boolean_t entry_props,
224 svn_boolean_t ignore_ancestry,
227 void *root_baton = NULL;
229 const char *src_fullpath;
230 const svn_fs_id_t *src_id, *tgt_id;
231 svn_node_kind_t src_kind, tgt_kind;
232 svn_revnum_t rootrev;
234 const char *authz_root_path;
236 /* SRC_PARENT_DIR must be valid. */
238 src_parent_dir = svn_relpath_canonicalize(src_parent_dir, pool);
240 return not_a_dir_error("source parent", src_parent_dir);
242 /* TGT_FULLPATH must be valid. */
244 tgt_fullpath = svn_relpath_canonicalize(tgt_fullpath, pool);
246 return svn_error_create(SVN_ERR_FS_PATH_SYNTAX, 0,
247 _("Invalid target path"));
249 if (depth == svn_depth_exclude)
250 return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL,
251 _("Delta depth 'exclude' not supported"));
253 /* Calculate the fs path implicitly used for editor->open_root, so
254 we can do an authz check on that path first. */
256 authz_root_path = svn_relpath_dirname(tgt_fullpath, pool);
258 authz_root_path = tgt_fullpath;
260 /* Construct the full path of the source item. */
261 src_fullpath = svn_relpath_join(src_parent_dir, src_entry, pool);
263 /* Get the node kinds for the source and target paths. */
264 SVN_ERR(svn_fs_check_path(&tgt_kind, tgt_root, tgt_fullpath, pool));
265 SVN_ERR(svn_fs_check_path(&src_kind, src_root, src_fullpath, pool));
267 /* If neither of our paths exists, we don't really have anything to do. */
268 if ((tgt_kind == svn_node_none) && (src_kind == svn_node_none))
271 /* If either the source or the target is a non-directory, we
272 require that a SRC_ENTRY be supplied. */
273 if ((! *src_entry) && ((src_kind != svn_node_dir)
274 || tgt_kind != svn_node_dir))
275 return svn_error_create
276 (SVN_ERR_FS_PATH_SYNTAX, 0,
277 _("Invalid editor anchoring; at least one of the "
278 "input paths is not a directory and there was no source entry"));
280 /* Set the global target revision if one can be determined. */
281 if (svn_fs_is_revision_root(tgt_root))
283 SVN_ERR(editor->set_target_revision
284 (edit_baton, svn_fs_revision_root_revision(tgt_root), pool));
286 else if (svn_fs_is_txn_root(tgt_root))
288 SVN_ERR(editor->set_target_revision
289 (edit_baton, svn_fs_txn_root_base_revision(tgt_root), pool));
292 /* Setup our pseudo-global structure here. We need these variables
293 throughout the deltafication process, so pass them around by
294 reference to all the helper functions. */
296 c.source_root = src_root;
297 c.target_root = tgt_root;
298 c.authz_read_func = authz_read_func;
299 c.authz_read_baton = authz_read_baton;
300 c.text_deltas = text_deltas;
301 c.entry_props = entry_props;
302 c.ignore_ancestry = ignore_ancestry;
304 /* Get our editor root's revision. */
305 rootrev = get_path_revision(src_root, src_parent_dir, pool);
307 /* If one or the other of our paths doesn't exist, we have to handle
308 those cases specially. */
309 if (tgt_kind == svn_node_none)
311 /* Caller thinks that target still exists, but it doesn't.
312 So transform their source path to "nothing" by deleting it. */
313 SVN_ERR(authz_root_check(tgt_root, authz_root_path,
314 authz_read_func, authz_read_baton, pool));
315 SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton));
316 SVN_ERR(delete(&c, root_baton, src_entry, pool));
319 if (src_kind == svn_node_none)
321 /* The source path no longer exists, but the target does.
322 So transform "nothing" into "something" by adding. */
323 SVN_ERR(authz_root_check(tgt_root, authz_root_path,
324 authz_read_func, authz_read_baton, pool));
325 SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton));
326 SVN_ERR(add_file_or_dir(&c, root_baton, depth, tgt_fullpath,
327 src_entry, tgt_kind, pool));
331 /* Get and compare the node IDs for the source and target. */
332 SVN_ERR(svn_fs_node_id(&tgt_id, tgt_root, tgt_fullpath, pool));
333 SVN_ERR(svn_fs_node_id(&src_id, src_root, src_fullpath, pool));
334 distance = svn_fs_compare_ids(src_id, tgt_id);
338 /* They are the same node! No-op (you gotta love those). */
343 /* If the nodes have different kinds, we must delete the one and
344 add the other. Also, if they are completely unrelated and
345 our caller is interested in relatedness, we do the same thing. */
346 if ((src_kind != tgt_kind)
347 || ((distance == -1) && (! ignore_ancestry)))
349 SVN_ERR(authz_root_check(tgt_root, authz_root_path,
350 authz_read_func, authz_read_baton, pool));
351 SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton));
352 SVN_ERR(delete(&c, root_baton, src_entry, pool));
353 SVN_ERR(add_file_or_dir(&c, root_baton, depth, tgt_fullpath,
354 src_entry, tgt_kind, pool));
356 /* Otherwise, we just replace the one with the other. */
359 SVN_ERR(authz_root_check(tgt_root, authz_root_path,
360 authz_read_func, authz_read_baton, pool));
361 SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton));
362 SVN_ERR(replace_file_or_dir(&c, root_baton, depth, src_fullpath,
363 tgt_fullpath, src_entry,
369 /* There is no entry given, so delta the whole parent directory. */
370 SVN_ERR(authz_root_check(tgt_root, authz_root_path,
371 authz_read_func, authz_read_baton, pool));
372 SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton));
373 SVN_ERR(delta_dirs(&c, root_baton, depth, src_fullpath,
374 tgt_fullpath, "", pool));
379 /* Make sure we close the root directory if we opened one above. */
381 SVN_ERR(editor->close_directory(root_baton, pool));
383 /* Close the edit. */
384 return editor->close_edit(edit_baton, pool);
388 /* Retrieving the base revision from the path/revision hash. */
392 get_path_revision(svn_fs_root_t *root,
396 svn_revnum_t revision;
399 /* Easy out -- if ROOT is a revision root, we can use the revision
400 that it's a root of. */
401 if (svn_fs_is_revision_root(root))
402 return svn_fs_revision_root_revision(root);
404 /* Else, this must be a transaction root, so ask the filesystem in
405 what revision this path was created. */
406 if ((err = svn_fs_node_created_rev(&revision, root, path, pool)))
408 revision = SVN_INVALID_REVNUM;
409 svn_error_clear(err);
412 /* If we don't get back a valid revision, this path is mutable in
413 the transaction. We should probably examine the node on which it
414 is based, doable by querying for the node-id of the path, and
415 then examining that node-id's predecessor. ### This predecessor
416 determination isn't exposed via the FS public API right now, so
417 for now, we'll just return the SVN_INVALID_REVNUM. */
422 /* proplist_change_fn_t property changing functions. */
425 /* Call the directory property-setting function of C->editor to set
426 the property NAME to given VALUE on the OBJECT passed to this
429 change_dir_prop(struct context *c,
432 const svn_string_t *value,
435 return c->editor->change_dir_prop(object, name, value, pool);
439 /* Call the file property-setting function of C->editor to set the
440 property NAME to given VALUE on the OBJECT passed to this
443 change_file_prop(struct context *c,
446 const svn_string_t *value,
449 return c->editor->change_file_prop(object, name, value, pool);
455 /* Constructing deltas for properties of files and directories. */
458 /* Generate the appropriate property editing calls to turn the
459 properties of SOURCE_PATH into those of TARGET_PATH. If
460 SOURCE_PATH is NULL, this is an add, so assume the target starts
461 with no properties. Pass OBJECT on to the editor function wrapper
464 delta_proplists(struct context *c,
465 const char *source_path,
466 const char *target_path,
467 proplist_change_fn_t *change_fn,
471 apr_hash_t *s_props = 0;
472 apr_hash_t *t_props = 0;
474 apr_array_header_t *prop_diffs;
477 SVN_ERR_ASSERT(target_path);
479 /* Make a subpool for local allocations. */
480 subpool = svn_pool_create(pool);
482 /* If we're supposed to send entry props for all non-deleted items,
486 svn_revnum_t committed_rev = SVN_INVALID_REVNUM;
487 svn_string_t *cr_str = NULL;
488 svn_string_t *committed_date = NULL;
489 svn_string_t *last_author = NULL;
491 /* Get the CR and two derivative props. ### check for error returns. */
492 SVN_ERR(svn_fs_node_created_rev(&committed_rev, c->target_root,
493 target_path, subpool));
494 if (SVN_IS_VALID_REVNUM(committed_rev))
496 svn_fs_t *fs = svn_fs_root_fs(c->target_root);
500 /* Transmit the committed-rev. */
501 cr_str = svn_string_createf(subpool, "%ld",
503 SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_COMMITTED_REV,
506 SVN_ERR(svn_fs_revision_proplist(&r_props, fs, committed_rev,
509 /* Transmit the committed-date. */
510 committed_date = svn_hash_gets(r_props, SVN_PROP_REVISION_DATE);
511 if (committed_date || source_path)
513 SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_COMMITTED_DATE,
514 committed_date, subpool));
517 /* Transmit the last-author. */
518 last_author = svn_hash_gets(r_props, SVN_PROP_REVISION_AUTHOR);
519 if (last_author || source_path)
521 SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_LAST_AUTHOR,
522 last_author, subpool));
525 /* Transmit the UUID. */
526 SVN_ERR(svn_fs_get_uuid(fs, &uuid, subpool));
527 SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_UUID,
528 svn_string_create(uuid, subpool),
535 svn_boolean_t changed;
537 /* Is this deltification worth our time? */
538 SVN_ERR(svn_fs_props_changed(&changed, c->target_root, target_path,
539 c->source_root, source_path, subpool));
543 /* If so, go ahead and get the source path's properties. */
544 SVN_ERR(svn_fs_node_proplist(&s_props, c->source_root,
545 source_path, subpool));
549 s_props = apr_hash_make(subpool);
552 /* Get the target path's properties */
553 SVN_ERR(svn_fs_node_proplist(&t_props, c->target_root,
554 target_path, subpool));
556 /* Now transmit the differences. */
557 SVN_ERR(svn_prop_diffs(&prop_diffs, t_props, s_props, subpool));
558 for (i = 0; i < prop_diffs->nelts; i++)
560 const svn_prop_t *pc = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t);
561 SVN_ERR(change_fn(c, object, pc->name, pc->value, subpool));
565 /* Destroy local subpool. */
566 svn_pool_destroy(subpool);
574 /* Constructing deltas for file contents. */
577 /* Change the contents of FILE_BATON in C->editor, according to the
578 text delta from DELTA_STREAM. Pass BASE_CHECKSUM along to
579 C->editor->apply_textdelta. */
581 send_text_delta(struct context *c,
583 const char *base_checksum,
584 svn_txdelta_stream_t *delta_stream,
587 svn_txdelta_window_handler_t delta_handler;
588 void *delta_handler_baton;
590 /* Get a handler that will apply the delta to the file. */
591 SVN_ERR(c->editor->apply_textdelta
592 (file_baton, base_checksum, pool,
593 &delta_handler, &delta_handler_baton));
595 if (c->text_deltas && delta_stream)
597 /* Deliver the delta stream to the file. */
598 return svn_txdelta_send_txstream(delta_stream,
605 /* The caller doesn't want text delta data. Just send a single
607 return delta_handler(NULL, delta_handler_baton);
612 svn_repos__compare_files(svn_boolean_t *changed_p,
613 svn_fs_root_t *root1,
615 svn_fs_root_t *root2,
619 svn_filesize_t size1, size2;
620 svn_checksum_t *checksum1, *checksum2;
621 svn_stream_t *stream1, *stream2;
624 /* If the filesystem claims the things haven't changed, then they
626 SVN_ERR(svn_fs_contents_changed(changed_p, root1, path1,
627 root2, path2, pool));
631 /* If the SHA1 checksums match for these things, we'll claim they
632 have the same contents. (We don't give quite as much weight to
634 SVN_ERR(svn_fs_file_checksum(&checksum1, svn_checksum_sha1,
635 root1, path1, FALSE, pool));
636 SVN_ERR(svn_fs_file_checksum(&checksum2, svn_checksum_sha1,
637 root2, path2, FALSE, pool));
638 if (checksum1 && checksum2)
640 *changed_p = !svn_checksum_match(checksum1, checksum2);
644 /* From this point on, our default answer is "Nothing's changed". */
647 /* Different filesizes means the contents are different. */
648 SVN_ERR(svn_fs_file_length(&size1, root1, path1, pool));
649 SVN_ERR(svn_fs_file_length(&size2, root2, path2, pool));
656 /* Different MD5 checksums means the contents are different. */
657 SVN_ERR(svn_fs_file_checksum(&checksum1, svn_checksum_md5, root1, path1,
659 SVN_ERR(svn_fs_file_checksum(&checksum2, svn_checksum_md5, root2, path2,
661 if (! svn_checksum_match(checksum1, checksum2))
667 /* And finally, different contents means the ... uh ... contents are
669 SVN_ERR(svn_fs_file_contents(&stream1, root1, path1, pool));
670 SVN_ERR(svn_fs_file_contents(&stream2, root2, path2, pool));
671 SVN_ERR(svn_stream_contents_same2(&same, stream1, stream2, pool));
678 /* Make the appropriate edits on FILE_BATON to change its contents and
679 properties from those in SOURCE_PATH to those in TARGET_PATH. */
681 delta_files(struct context *c,
683 const char *source_path,
684 const char *target_path,
688 svn_boolean_t changed = TRUE;
690 SVN_ERR_ASSERT(target_path);
692 /* Make a subpool for local allocations. */
693 subpool = svn_pool_create(pool);
695 /* Compare the files' property lists. */
696 SVN_ERR(delta_proplists(c, source_path, target_path,
697 change_file_prop, file_baton, subpool));
701 /* Is this delta calculation worth our time? If we are ignoring
702 ancestry, then our editor implementor isn't concerned by the
703 theoretical differences between "has contents which have not
704 changed with respect to" and "has the same actual contents
705 as". We'll do everything we can to avoid transmitting even
706 an empty text-delta in that case. */
707 if (c->ignore_ancestry)
708 SVN_ERR(svn_repos__compare_files(&changed,
709 c->target_root, target_path,
710 c->source_root, source_path,
713 SVN_ERR(svn_fs_contents_changed(&changed,
714 c->target_root, target_path,
715 c->source_root, source_path,
720 /* If there isn't a source path, this is an add, which
721 necessarily has textual mods. */
724 /* If there is a change, and the context indicates that we should
725 care about it, then hand it off to a delta stream. */
728 svn_txdelta_stream_t *delta_stream = NULL;
729 svn_checksum_t *source_checksum;
730 const char *source_hex_digest = NULL;
734 /* Get a delta stream turning an empty file into one having
735 TARGET_PATH's contents. */
736 SVN_ERR(svn_fs_get_file_delta_stream
738 source_path ? c->source_root : NULL,
739 source_path ? source_path : NULL,
740 c->target_root, target_path, subpool));
745 SVN_ERR(svn_fs_file_checksum(&source_checksum, svn_checksum_md5,
746 c->source_root, source_path, TRUE,
749 source_hex_digest = svn_checksum_to_cstring(source_checksum,
753 SVN_ERR(send_text_delta(c, file_baton, source_hex_digest,
754 delta_stream, subpool));
758 svn_pool_destroy(subpool);
766 /* Generic directory deltafication routines. */
769 /* Emit a delta to delete the entry named TARGET_ENTRY from DIR_BATON. */
771 delete(struct context *c,
773 const char *edit_path,
776 return c->editor->delete_entry(edit_path, SVN_INVALID_REVNUM,
781 /* If authorized, emit a delta to create the entry named TARGET_ENTRY
782 at the location EDIT_PATH. If not authorized, indicate that
783 EDIT_PATH is absent. Pass DIR_BATON through to editor functions
784 that require it. DEPTH is the depth from this point downward. */
786 add_file_or_dir(struct context *c, void *dir_baton,
788 const char *target_path,
789 const char *edit_path,
790 svn_node_kind_t tgt_kind,
793 struct context *context = c;
794 svn_boolean_t allowed;
796 SVN_ERR_ASSERT(target_path && edit_path);
798 if (c->authz_read_func)
800 SVN_ERR(c->authz_read_func(&allowed, c->target_root, target_path,
801 c->authz_read_baton, pool));
803 return absent_file_or_dir(c, dir_baton, edit_path, tgt_kind, pool);
806 if (tgt_kind == svn_node_dir)
810 SVN_ERR(context->editor->add_directory(edit_path, dir_baton, NULL,
811 SVN_INVALID_REVNUM, pool,
813 SVN_ERR(delta_dirs(context, subdir_baton, MAYBE_DEMOTE_DEPTH(depth),
814 NULL, target_path, edit_path, pool));
815 return context->editor->close_directory(subdir_baton, pool);
820 svn_checksum_t *checksum;
822 SVN_ERR(context->editor->add_file(edit_path, dir_baton,
823 NULL, SVN_INVALID_REVNUM, pool,
825 SVN_ERR(delta_files(context, file_baton, NULL, target_path, pool));
826 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
827 context->target_root, target_path,
829 return context->editor->close_file
830 (file_baton, svn_checksum_to_cstring(checksum, pool), pool);
835 /* If authorized, emit a delta to modify EDIT_PATH with the changes
836 from SOURCE_PATH to TARGET_PATH. If not authorized, indicate that
837 EDIT_PATH is absent. Pass DIR_BATON through to editor functions
838 that require it. DEPTH is the depth from this point downward. */
840 replace_file_or_dir(struct context *c,
843 const char *source_path,
844 const char *target_path,
845 const char *edit_path,
846 svn_node_kind_t tgt_kind,
849 svn_revnum_t base_revision = SVN_INVALID_REVNUM;
850 svn_boolean_t allowed;
852 SVN_ERR_ASSERT(target_path && source_path && edit_path);
854 if (c->authz_read_func)
856 SVN_ERR(c->authz_read_func(&allowed, c->target_root, target_path,
857 c->authz_read_baton, pool));
859 return absent_file_or_dir(c, dir_baton, edit_path, tgt_kind, pool);
862 /* Get the base revision for the entry from the hash. */
863 base_revision = get_path_revision(c->source_root, source_path, pool);
865 if (tgt_kind == svn_node_dir)
869 SVN_ERR(c->editor->open_directory(edit_path, dir_baton,
872 SVN_ERR(delta_dirs(c, subdir_baton, MAYBE_DEMOTE_DEPTH(depth),
873 source_path, target_path, edit_path, pool));
874 return c->editor->close_directory(subdir_baton, pool);
879 svn_checksum_t *checksum;
881 SVN_ERR(c->editor->open_file(edit_path, dir_baton, base_revision,
883 SVN_ERR(delta_files(c, file_baton, source_path, target_path, pool));
884 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
885 c->target_root, target_path, TRUE,
887 return c->editor->close_file
888 (file_baton, svn_checksum_to_cstring(checksum, pool), pool);
893 /* In directory DIR_BATON, indicate that EDIT_PATH (relative to the
894 edit root) is absent by invoking C->editor->absent_directory or
895 C->editor->absent_file (depending on TGT_KIND). */
897 absent_file_or_dir(struct context *c,
899 const char *edit_path,
900 svn_node_kind_t tgt_kind,
903 SVN_ERR_ASSERT(edit_path);
905 if (tgt_kind == svn_node_dir)
906 return c->editor->absent_directory(edit_path, dir_baton, pool);
908 return c->editor->absent_file(edit_path, dir_baton, pool);
912 /* Emit deltas to turn SOURCE_PATH into TARGET_PATH. Assume that
913 DIR_BATON represents the directory we're constructing to the editor
916 delta_dirs(struct context *c,
919 const char *source_path,
920 const char *target_path,
921 const char *edit_path,
924 apr_hash_t *s_entries = 0, *t_entries = 0;
925 apr_hash_index_t *hi;
928 SVN_ERR_ASSERT(target_path);
930 /* Compare the property lists. */
931 SVN_ERR(delta_proplists(c, source_path, target_path,
932 change_dir_prop, dir_baton, pool));
934 /* Get the list of entries in each of source and target. */
935 SVN_ERR(svn_fs_dir_entries(&t_entries, c->target_root,
938 SVN_ERR(svn_fs_dir_entries(&s_entries, c->source_root,
941 /* Make a subpool for local allocations. */
942 subpool = svn_pool_create(pool);
944 /* Loop over the hash of entries in the target, searching for its
945 partner in the source. If we find the matching partner entry,
946 use editor calls to replace the one in target with a new version
947 if necessary, then remove that entry from the source entries
948 hash. If we can't find a related node in the source, we use
949 editor calls to add the entry as a new item in the target.
950 Having handled all the entries that exist in target, any entries
951 still remaining the source entries hash represent entries that no
952 longer exist in target. Use editor calls to delete those entries
953 from the target tree. */
954 for (hi = apr_hash_first(pool, t_entries); hi; hi = apr_hash_next(hi))
956 const svn_fs_dirent_t *s_entry, *t_entry;
960 const char *t_fullpath;
961 const char *e_fullpath;
962 const char *s_fullpath;
963 svn_node_kind_t tgt_kind;
965 /* Clear out our subpool for the next iteration... */
966 svn_pool_clear(subpool);
968 /* KEY is the entry name in target, VAL the dirent */
969 apr_hash_this(hi, &key, &klen, &val);
971 tgt_kind = t_entry->kind;
972 t_fullpath = svn_relpath_join(target_path, t_entry->name, subpool);
973 e_fullpath = svn_relpath_join(edit_path, t_entry->name, subpool);
975 /* Can we find something with the same name in the source
977 if (s_entries && ((s_entry = apr_hash_get(s_entries, key, klen)) != 0))
979 svn_node_kind_t src_kind;
981 s_fullpath = svn_relpath_join(source_path, t_entry->name, subpool);
982 src_kind = s_entry->kind;
984 if (depth == svn_depth_infinity
985 || src_kind != svn_node_dir
986 || (src_kind == svn_node_dir
987 && depth == svn_depth_immediates))
989 /* Use svn_fs_compare_ids() to compare our current
990 source and target ids.
992 0: means they are the same id, and this is a noop.
993 -1: means they are unrelated, so we have to delete the
994 old one and add the new one.
995 1: means the nodes are related through ancestry, so go
996 ahead and do the replace directly. */
997 int distance = svn_fs_compare_ids(s_entry->id, t_entry->id);
1002 else if ((src_kind != tgt_kind)
1003 || ((distance == -1) && (! c->ignore_ancestry)))
1005 SVN_ERR(delete(c, dir_baton, e_fullpath, subpool));
1006 SVN_ERR(add_file_or_dir(c, dir_baton,
1007 MAYBE_DEMOTE_DEPTH(depth),
1008 t_fullpath, e_fullpath, tgt_kind,
1013 SVN_ERR(replace_file_or_dir(c, dir_baton,
1014 MAYBE_DEMOTE_DEPTH(depth),
1015 s_fullpath, t_fullpath,
1016 e_fullpath, tgt_kind,
1021 /* Remove the entry from the source_hash. */
1022 svn_hash_sets(s_entries, key, NULL);
1026 if (depth == svn_depth_infinity
1027 || tgt_kind != svn_node_dir
1028 || (tgt_kind == svn_node_dir
1029 && depth == svn_depth_immediates))
1031 SVN_ERR(add_file_or_dir(c, dir_baton,
1032 MAYBE_DEMOTE_DEPTH(depth),
1033 t_fullpath, e_fullpath, tgt_kind,
1039 /* All that is left in the source entries hash are things that need
1040 to be deleted. Delete them. */
1043 for (hi = apr_hash_first(pool, s_entries); hi; hi = apr_hash_next(hi))
1045 const svn_fs_dirent_t *s_entry;
1047 const char *e_fullpath;
1048 svn_node_kind_t src_kind;
1050 /* Clear out our subpool for the next iteration... */
1051 svn_pool_clear(subpool);
1053 /* KEY is the entry name in source, VAL the dirent */
1054 apr_hash_this(hi, NULL, NULL, &val);
1056 src_kind = s_entry->kind;
1057 e_fullpath = svn_relpath_join(edit_path, s_entry->name, subpool);
1059 /* Do we actually want to delete the dir if we're non-recursive? */
1060 if (depth == svn_depth_infinity
1061 || src_kind != svn_node_dir
1062 || (src_kind == svn_node_dir
1063 && depth == svn_depth_immediates))
1065 SVN_ERR(delete(c, dir_baton, e_fullpath, subpool));
1070 /* Destroy local allocation subpool. */
1071 svn_pool_destroy(subpool);
1073 return SVN_NO_ERROR;