2 * delta.c: an editor driver for expressing differences between two trees
4 * ====================================================================
5 * Copyright (c) 2000-2006 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
24 #include "svn_types.h"
25 #include "svn_delta.h"
29 #include "svn_repos.h"
30 #include "svn_pools.h"
31 #include "svn_props.h"
32 #include "svn_private_config.h"
37 /* THINGS TODO: Currently the code herein gives only a slight nod to
38 fully supporting directory deltas that involve renames, copies, and
42 /* Some datatypes and declarations used throughout the file. */
45 /* Parameters which remain constant throughout a delta traversal.
46 At the top of the recursion, we initialize one of these structures.
47 Then we pass it down to every call. This way, functions invoked
48 deep in the recursion can get access to this traversal's global
49 parameters, without using global variables. */
51 const svn_delta_editor_t
*editor
;
52 const char *edit_base_path
;
53 svn_fs_root_t
*source_root
;
54 svn_fs_root_t
*target_root
;
55 svn_repos_authz_func_t authz_read_func
;
56 void *authz_read_baton
;
57 svn_boolean_t text_deltas
;
58 svn_boolean_t entry_props
;
59 svn_boolean_t ignore_ancestry
;
63 /* The type of a function that accepts changes to an object's property
64 list. OBJECT is the object whose properties are being changed.
65 NAME is the name of the property to change. VALUE is the new value
66 for the property, or zero if the property should be deleted. */
67 typedef svn_error_t
*proplist_change_fn_t(struct context
*c
,
70 const svn_string_t
*value
,
75 /* Some prototypes for functions used throughout. See each individual
76 function for information about what it does. */
79 /* Retrieving the base revision from the path/revision hash. */
80 static svn_revnum_t
get_path_revision(svn_fs_root_t
*root
,
85 /* proplist_change_fn_t property changing functions. */
86 static svn_error_t
*change_dir_prop(struct context
*c
,
89 const svn_string_t
*value
,
92 static svn_error_t
*change_file_prop(struct context
*c
,
95 const svn_string_t
*value
,
99 /* Constructing deltas for properties of files and directories. */
100 static svn_error_t
*delta_proplists(struct context
*c
,
101 const char *source_path
,
102 const char *target_path
,
103 proplist_change_fn_t
*change_fn
,
108 /* Constructing deltas for file constents. */
109 static svn_error_t
*send_text_delta(struct context
*c
,
111 const char *base_checksum
,
112 svn_txdelta_stream_t
*delta_stream
,
115 static svn_error_t
*delta_files(struct context
*c
,
117 const char *source_path
,
118 const char *target_path
,
122 /* Generic directory deltafication routines. */
123 static svn_error_t
*delete(struct context
*c
,
125 const char *edit_path
,
128 static svn_error_t
*add_file_or_dir(struct context
*c
,
131 const char *target_path
,
132 const char *edit_path
,
133 svn_node_kind_t tgt_kind
,
136 static svn_error_t
*replace_file_or_dir(struct context
*c
,
139 const char *source_path
,
140 const char *target_path
,
141 const char *edit_path
,
142 svn_node_kind_t tgt_kind
,
145 static svn_error_t
*absent_file_or_dir(struct context
*c
,
147 const char *edit_path
,
148 svn_node_kind_t tgt_kind
,
151 static svn_error_t
*delta_dirs(struct context
*c
,
154 const char *source_path
,
155 const char *target_path
,
156 const char *edit_path
,
161 #define MAYBE_DEMOTE_DEPTH(depth) \
162 (((depth) == svn_depth_immediates || (depth) == svn_depth_files) \
167 /* Return the error 'SVN_ERR_AUTHZ_ROOT_UNREADABLE' if PATH in ROOT is
168 * unreadable according to AUTHZ_READ_FUNC with AUTHZ_READ_BATON.
170 * PATH should be the implicit root path of an editor drive, that is,
171 * the path used by editor->open_root().
174 authz_root_check(svn_fs_root_t
*root
,
176 svn_repos_authz_func_t authz_read_func
,
177 void *authz_read_baton
,
180 svn_boolean_t allowed
;
184 SVN_ERR(authz_read_func(&allowed
, root
, path
, authz_read_baton
, pool
));
187 return svn_error_create(SVN_ERR_AUTHZ_ROOT_UNREADABLE
, 0,
188 _("Unable to open root of edit"));
196 not_a_dir_error(const char *role
,
199 return svn_error_createf
200 (SVN_ERR_FS_NOT_DIRECTORY
, 0,
201 "Invalid %s directory '%s'",
202 role
, path
? path
: "(null)");
206 /* Public interface to computing directory deltas. */
208 svn_repos_dir_delta2(svn_fs_root_t
*src_root
,
209 const char *src_parent_dir
,
210 const char *src_entry
,
211 svn_fs_root_t
*tgt_root
,
212 const char *tgt_fullpath
,
213 const svn_delta_editor_t
*editor
,
215 svn_repos_authz_func_t authz_read_func
,
216 void *authz_read_baton
,
217 svn_boolean_t text_deltas
,
219 svn_boolean_t entry_props
,
220 svn_boolean_t ignore_ancestry
,
223 void *root_baton
= NULL
;
225 const char *src_fullpath
;
226 const svn_fs_id_t
*src_id
, *tgt_id
;
227 svn_node_kind_t src_kind
, tgt_kind
;
228 svn_revnum_t rootrev
;
230 const char *authz_root_path
;
232 /* SRC_PARENT_DIR must be valid. */
233 if (! src_parent_dir
)
234 return not_a_dir_error("source parent", src_parent_dir
);
236 /* TGT_FULLPATH must be valid. */
238 return svn_error_create(SVN_ERR_FS_PATH_SYNTAX
, 0,
239 _("Invalid target path"));
241 if (depth
== svn_depth_exclude
)
242 return svn_error_create(SVN_ERR_REPOS_BAD_ARGS
, NULL
,
243 _("Delta depth 'exclude' not supported"));
245 /* Calculate the fs path implicitly used for editor->open_root, so
246 we can do an authz check on that path first. */
248 authz_root_path
= svn_path_dirname(tgt_fullpath
, pool
);
250 authz_root_path
= tgt_fullpath
;
252 /* Construct the full path of the source item. */
253 src_fullpath
= svn_path_join(src_parent_dir
, src_entry
, pool
);
255 /* Get the node kinds for the source and target paths. */
256 SVN_ERR(svn_fs_check_path(&tgt_kind
, tgt_root
, tgt_fullpath
, pool
));
257 SVN_ERR(svn_fs_check_path(&src_kind
, src_root
, src_fullpath
, pool
));
259 /* If neither of our paths exists, we don't really have anything to do. */
260 if ((tgt_kind
== svn_node_none
) && (src_kind
== svn_node_none
))
263 /* If either the source or the target is a non-directory, we
264 require that a SRC_ENTRY be supplied. */
265 if ((! *src_entry
) && ((src_kind
!= svn_node_dir
)
266 || tgt_kind
!= svn_node_dir
))
267 return svn_error_create
268 (SVN_ERR_FS_PATH_SYNTAX
, 0,
269 _("Invalid editor anchoring; at least one of the "
270 "input paths is not a directory and there was no source entry"));
272 /* Set the global target revision if one can be determined. */
273 if (svn_fs_is_revision_root(tgt_root
))
275 SVN_ERR(editor
->set_target_revision
276 (edit_baton
, svn_fs_revision_root_revision(tgt_root
), pool
));
278 else if (svn_fs_is_txn_root(tgt_root
))
280 SVN_ERR(editor
->set_target_revision
281 (edit_baton
, svn_fs_txn_root_base_revision(tgt_root
), pool
));
284 /* Setup our pseudo-global structure here. We need these variables
285 throughout the deltafication process, so pass them around by
286 reference to all the helper functions. */
288 c
.source_root
= src_root
;
289 c
.target_root
= tgt_root
;
290 c
.authz_read_func
= authz_read_func
;
291 c
.authz_read_baton
= authz_read_baton
;
292 c
.text_deltas
= text_deltas
;
293 c
.entry_props
= entry_props
;
294 c
.ignore_ancestry
= ignore_ancestry
;
296 /* Get our editor root's revision. */
297 rootrev
= get_path_revision(src_root
, src_parent_dir
, pool
);
299 /* If one or the other of our paths doesn't exist, we have to handle
300 those cases specially. */
301 if (tgt_kind
== svn_node_none
)
303 /* Caller thinks that target still exists, but it doesn't.
304 So transform their source path to "nothing" by deleting it. */
305 SVN_ERR(authz_root_check(tgt_root
, authz_root_path
,
306 authz_read_func
, authz_read_baton
, pool
));
307 SVN_ERR(editor
->open_root(edit_baton
, rootrev
, pool
, &root_baton
));
308 SVN_ERR(delete(&c
, root_baton
, src_entry
, pool
));
311 if (src_kind
== svn_node_none
)
313 /* The source path no longer exists, but the target does.
314 So transform "nothing" into "something" by adding. */
315 SVN_ERR(authz_root_check(tgt_root
, authz_root_path
,
316 authz_read_func
, authz_read_baton
, pool
));
317 SVN_ERR(editor
->open_root(edit_baton
, rootrev
, pool
, &root_baton
));
318 SVN_ERR(add_file_or_dir(&c
, root_baton
, depth
, tgt_fullpath
,
319 src_entry
, tgt_kind
, pool
));
323 /* Get and compare the node IDs for the source and target. */
324 SVN_ERR(svn_fs_node_id(&tgt_id
, tgt_root
, tgt_fullpath
, pool
));
325 SVN_ERR(svn_fs_node_id(&src_id
, src_root
, src_fullpath
, pool
));
326 distance
= svn_fs_compare_ids(src_id
, tgt_id
);
330 /* They are the same node! No-op (you gotta love those). */
335 /* If the nodes have different kinds, we must delete the one and
336 add the other. Also, if they are completely unrelated and
337 our caller is interested in relatedness, we do the same thing. */
338 if ((src_kind
!= tgt_kind
)
339 || ((distance
== -1) && (! ignore_ancestry
)))
341 SVN_ERR(authz_root_check(tgt_root
, authz_root_path
,
342 authz_read_func
, authz_read_baton
, pool
));
343 SVN_ERR(editor
->open_root(edit_baton
, rootrev
, pool
, &root_baton
));
344 SVN_ERR(delete(&c
, root_baton
, src_entry
, pool
));
345 SVN_ERR(add_file_or_dir(&c
, root_baton
, depth
, tgt_fullpath
,
346 src_entry
, tgt_kind
, pool
));
348 /* Otherwise, we just replace the one with the other. */
351 SVN_ERR(authz_root_check(tgt_root
, authz_root_path
,
352 authz_read_func
, authz_read_baton
, pool
));
353 SVN_ERR(editor
->open_root(edit_baton
, rootrev
, pool
, &root_baton
));
354 SVN_ERR(replace_file_or_dir(&c
, root_baton
, depth
, src_fullpath
,
355 tgt_fullpath
, src_entry
,
361 /* There is no entry given, so delta the whole parent directory. */
362 SVN_ERR(authz_root_check(tgt_root
, authz_root_path
,
363 authz_read_func
, authz_read_baton
, pool
));
364 SVN_ERR(editor
->open_root(edit_baton
, rootrev
, pool
, &root_baton
));
365 SVN_ERR(delta_dirs(&c
, root_baton
, depth
, src_fullpath
,
366 tgt_fullpath
, "", pool
));
371 /* Make sure we close the root directory if we opened one above. */
373 SVN_ERR(editor
->close_directory(root_baton
, pool
));
375 /* Close the edit. */
376 SVN_ERR(editor
->close_edit(edit_baton
, pool
));
378 /* All's well that ends well. */
384 svn_repos_dir_delta(svn_fs_root_t
*src_root
,
385 const char *src_parent_dir
,
386 const char *src_entry
,
387 svn_fs_root_t
*tgt_root
,
388 const char *tgt_fullpath
,
389 const svn_delta_editor_t
*editor
,
391 svn_repos_authz_func_t authz_read_func
,
392 void *authz_read_baton
,
393 svn_boolean_t text_deltas
,
394 svn_boolean_t recurse
,
395 svn_boolean_t entry_props
,
396 svn_boolean_t ignore_ancestry
,
399 return svn_repos_dir_delta2(src_root
,
409 SVN_DEPTH_INFINITY_OR_FILES(recurse
),
417 /* Retrieving the base revision from the path/revision hash. */
421 get_path_revision(svn_fs_root_t
*root
,
425 svn_revnum_t revision
;
428 /* Easy out -- if ROOT is a revision root, we can use the revision
429 that it's a root of. */
430 if (svn_fs_is_revision_root(root
))
431 return svn_fs_revision_root_revision(root
);
433 /* Else, this must be a transaction root, so ask the filesystem in
434 what revision this path was created. */
435 if ((err
= svn_fs_node_created_rev(&revision
, root
, path
, pool
)))
437 revision
= SVN_INVALID_REVNUM
;
438 svn_error_clear(err
);
441 /* If we don't get back a valid revision, this path is mutable in
442 the transaction. We should probably examine the node on which it
443 is based, doable by querying for the node-id of the path, and
444 then examining that node-id's predecessor. ### This predecessor
445 determination isn't exposed via the FS public API right now, so
446 for now, we'll just return the SVN_INVALID_REVNUM. */
451 /* proplist_change_fn_t property changing functions. */
454 /* Call the directory property-setting function of C->editor to set
455 the property NAME to given VALUE on the OBJECT passed to this
458 change_dir_prop(struct context
*c
,
461 const svn_string_t
*value
,
464 return c
->editor
->change_dir_prop(object
, name
, value
, pool
);
468 /* Call the file property-setting function of C->editor to set the
469 property NAME to given VALUE on the OBJECT passed to this
472 change_file_prop(struct context
*c
,
475 const svn_string_t
*value
,
478 return c
->editor
->change_file_prop(object
, name
, value
, pool
);
484 /* Constructing deltas for properties of files and directories. */
487 /* Generate the appropriate property editing calls to turn the
488 properties of SOURCE_PATH into those of TARGET_PATH. If
489 SOURCE_PATH is NULL, this is an add, so assume the target starts
490 with no properties. Pass OBJECT on to the editor function wrapper
493 delta_proplists(struct context
*c
,
494 const char *source_path
,
495 const char *target_path
,
496 proplist_change_fn_t
*change_fn
,
500 apr_hash_t
*s_props
= 0;
501 apr_hash_t
*t_props
= 0;
503 apr_array_header_t
*prop_diffs
;
506 /* Sanity-check our input. */
509 /* Make a subpool for local allocations. */
510 subpool
= svn_pool_create(pool
);
512 /* If we're supposed to send entry props for all non-deleted items,
516 svn_revnum_t committed_rev
= SVN_INVALID_REVNUM
;
517 svn_string_t
*cr_str
= NULL
;
518 svn_string_t
*committed_date
= NULL
;
519 svn_string_t
*last_author
= NULL
;
521 /* Get the CR and two derivative props. ### check for error returns. */
522 SVN_ERR(svn_fs_node_created_rev(&committed_rev
, c
->target_root
,
523 target_path
, subpool
));
524 if (SVN_IS_VALID_REVNUM(committed_rev
))
526 svn_fs_t
*fs
= svn_fs_root_fs(c
->target_root
);
530 /* Transmit the committed-rev. */
531 cr_str
= svn_string_createf(subpool
, "%ld",
533 SVN_ERR(change_fn(c
, object
, SVN_PROP_ENTRY_COMMITTED_REV
,
536 SVN_ERR(svn_fs_revision_proplist(&r_props
, fs
, committed_rev
,
539 /* Transmit the committed-date. */
540 committed_date
= apr_hash_get(r_props
, SVN_PROP_REVISION_DATE
,
541 APR_HASH_KEY_STRING
);
542 if (committed_date
|| source_path
)
544 SVN_ERR(change_fn(c
, object
, SVN_PROP_ENTRY_COMMITTED_DATE
,
545 committed_date
, subpool
));
548 /* Transmit the last-author. */
549 last_author
= apr_hash_get(r_props
, SVN_PROP_REVISION_AUTHOR
,
550 APR_HASH_KEY_STRING
);
551 if (last_author
|| source_path
)
553 SVN_ERR(change_fn(c
, object
, SVN_PROP_ENTRY_LAST_AUTHOR
,
554 last_author
, subpool
));
557 /* Transmit the UUID. */
558 SVN_ERR(svn_fs_get_uuid(fs
, &uuid
, subpool
));
559 SVN_ERR(change_fn(c
, object
, SVN_PROP_ENTRY_UUID
,
560 svn_string_create(uuid
, subpool
),
567 svn_boolean_t changed
;
569 /* Is this deltification worth our time? */
570 SVN_ERR(svn_fs_props_changed(&changed
, c
->target_root
, target_path
,
571 c
->source_root
, source_path
, subpool
));
575 /* If so, go ahead and get the source path's properties. */
576 SVN_ERR(svn_fs_node_proplist(&s_props
, c
->source_root
,
577 source_path
, subpool
));
581 s_props
= apr_hash_make(subpool
);
584 /* Get the target path's properties */
585 SVN_ERR(svn_fs_node_proplist(&t_props
, c
->target_root
,
586 target_path
, subpool
));
588 /* Now transmit the differences. */
589 SVN_ERR(svn_prop_diffs(&prop_diffs
, t_props
, s_props
, subpool
));
590 for (i
= 0; i
< prop_diffs
->nelts
; i
++)
592 const svn_prop_t
*pc
= &APR_ARRAY_IDX(prop_diffs
, i
, svn_prop_t
);
593 SVN_ERR(change_fn(c
, object
, pc
->name
, pc
->value
, subpool
));
597 /* Destroy local subpool. */
598 svn_pool_destroy(subpool
);
606 /* Constructing deltas for file contents. */
609 /* Change the contents of FILE_BATON in C->editor, according to the
610 text delta from DELTA_STREAM. Pass BASE_CHECKSUM along to
611 C->editor->apply_textdelta. */
613 send_text_delta(struct context
*c
,
615 const char *base_checksum
,
616 svn_txdelta_stream_t
*delta_stream
,
619 svn_txdelta_window_handler_t delta_handler
;
620 void *delta_handler_baton
;
622 /* Get a handler that will apply the delta to the file. */
623 SVN_ERR(c
->editor
->apply_textdelta
624 (file_baton
, base_checksum
, pool
,
625 &delta_handler
, &delta_handler_baton
));
627 if (c
->text_deltas
&& delta_stream
)
629 /* Deliver the delta stream to the file. */
630 SVN_ERR(svn_txdelta_send_txstream(delta_stream
,
637 /* The caller doesn't want text delta data. Just send a single
639 SVN_ERR(delta_handler(NULL
, delta_handler_baton
));
646 svn_repos__compare_files(svn_boolean_t
*changed_p
,
647 svn_fs_root_t
*root1
,
649 svn_fs_root_t
*root2
,
653 svn_filesize_t size1
, size2
;
654 unsigned char digest1
[APR_MD5_DIGESTSIZE
], digest2
[APR_MD5_DIGESTSIZE
];
655 svn_stream_t
*stream1
, *stream2
;
657 apr_size_t len1
, len2
;
659 /* If the filesystem claims the things haven't changed, then they
661 SVN_ERR(svn_fs_contents_changed(changed_p
, root1
, path1
,
662 root2
, path2
, pool
));
666 /* From this point on, assume things haven't changed. */
669 /* So, things have changed. But we need to know if the two sets of
670 file contents are actually different. If they have differing
671 sizes, then we know they differ. */
672 SVN_ERR(svn_fs_file_length(&size1
, root1
, path1
, pool
));
673 SVN_ERR(svn_fs_file_length(&size2
, root2
, path2
, pool
));
680 /* Same sizes, huh? Well, if their checksums differ, we know they
682 SVN_ERR(svn_fs_file_md5_checksum(digest1
, root1
, path1
, pool
));
683 SVN_ERR(svn_fs_file_md5_checksum(digest2
, root2
, path2
, pool
));
684 if (! svn_md5_digests_match(digest1
, digest2
))
690 /* Same sizes, same checksums. Chances are reallllly good that they
691 don't differ, but to be absolute sure, we need to compare bytes. */
692 SVN_ERR(svn_fs_file_contents(&stream1
, root1
, path1
, pool
));
693 SVN_ERR(svn_fs_file_contents(&stream2
, root2
, path2
, pool
));
695 buf1
= apr_palloc(pool
, SVN__STREAM_CHUNK_SIZE
);
696 buf2
= apr_palloc(pool
, SVN__STREAM_CHUNK_SIZE
);
699 len1
= len2
= SVN__STREAM_CHUNK_SIZE
;
700 SVN_ERR(svn_stream_read(stream1
, buf1
, &len1
));
701 SVN_ERR(svn_stream_read(stream2
, buf2
, &len2
));
703 if (len1
!= len2
|| memcmp(buf1
, buf2
, len1
))
715 /* Make the appropriate edits on FILE_BATON to change its contents and
716 properties from those in SOURCE_PATH to those in TARGET_PATH. */
718 delta_files(struct context
*c
,
720 const char *source_path
,
721 const char *target_path
,
725 svn_boolean_t changed
= TRUE
;
727 /* Sanity-check our input. */
730 /* Make a subpool for local allocations. */
731 subpool
= svn_pool_create(pool
);
733 /* Compare the files' property lists. */
734 SVN_ERR(delta_proplists(c
, source_path
, target_path
,
735 change_file_prop
, file_baton
, subpool
));
739 /* Is this delta calculation worth our time? If we are ignoring
740 ancestry, then our editor implementor isn't concerned by the
741 theoretical differences between "has contents which have not
742 changed with respect to" and "has the same actual contents
743 as". We'll do everything we can to avoid transmitting even
744 an empty text-delta in that case. */
745 if (c
->ignore_ancestry
)
746 SVN_ERR(svn_repos__compare_files(&changed
,
747 c
->target_root
, target_path
,
748 c
->source_root
, source_path
,
751 SVN_ERR(svn_fs_contents_changed(&changed
,
752 c
->target_root
, target_path
,
753 c
->source_root
, source_path
,
758 /* If there isn't a source path, this is an add, which
759 necessarily has textual mods. */
762 /* If there is a change, and the context indicates that we should
763 care about it, then hand it off to a delta stream. */
766 svn_txdelta_stream_t
*delta_stream
= NULL
;
767 unsigned char source_digest
[APR_MD5_DIGESTSIZE
];
768 const char *source_hex_digest
= NULL
;
772 /* Get a delta stream turning an empty file into one having
773 TARGET_PATH's contents. */
774 SVN_ERR(svn_fs_get_file_delta_stream
776 source_path
? c
->source_root
: NULL
,
777 source_path
? source_path
: NULL
,
778 c
->target_root
, target_path
, subpool
));
783 SVN_ERR(svn_fs_file_md5_checksum
784 (source_digest
, c
->source_root
, source_path
, subpool
));
786 source_hex_digest
= svn_md5_digest_to_cstring(source_digest
,
790 SVN_ERR(send_text_delta(c
, file_baton
, source_hex_digest
,
791 delta_stream
, subpool
));
795 svn_pool_destroy(subpool
);
803 /* Generic directory deltafication routines. */
806 /* Emit a delta to delete the entry named TARGET_ENTRY from DIR_BATON. */
808 delete(struct context
*c
,
810 const char *edit_path
,
813 return c
->editor
->delete_entry(edit_path
, SVN_INVALID_REVNUM
,
818 /* If authorized, emit a delta to create the entry named TARGET_ENTRY
819 at the location EDIT_PATH. If not authorized, indicate that
820 EDIT_PATH is absent. Pass DIR_BATON through to editor functions
821 that require it. DEPTH is the depth from this point downward. */
823 add_file_or_dir(struct context
*c
, void *dir_baton
,
825 const char *target_path
,
826 const char *edit_path
,
827 svn_node_kind_t tgt_kind
,
830 struct context
*context
= c
;
831 svn_boolean_t allowed
;
833 /* Sanity-check our input. */
834 assert(target_path
&& edit_path
);
836 if (c
->authz_read_func
)
838 SVN_ERR(c
->authz_read_func(&allowed
, c
->target_root
, target_path
,
839 c
->authz_read_baton
, pool
));
841 return absent_file_or_dir(c
, dir_baton
, edit_path
, tgt_kind
, pool
);
844 if (tgt_kind
== svn_node_dir
)
848 SVN_ERR(context
->editor
->add_directory(edit_path
, dir_baton
, NULL
,
849 SVN_INVALID_REVNUM
, pool
,
851 SVN_ERR(delta_dirs(context
, subdir_baton
, MAYBE_DEMOTE_DEPTH(depth
),
852 NULL
, target_path
, edit_path
, pool
));
853 SVN_ERR(context
->editor
->close_directory(subdir_baton
, pool
));
858 unsigned char digest
[APR_MD5_DIGESTSIZE
];
860 SVN_ERR(context
->editor
->add_file(edit_path
, dir_baton
,
861 NULL
, SVN_INVALID_REVNUM
, pool
,
863 SVN_ERR(delta_files(context
, file_baton
, NULL
, target_path
, pool
));
864 SVN_ERR(svn_fs_file_md5_checksum(digest
, context
->target_root
,
866 SVN_ERR(context
->editor
->close_file
867 (file_baton
, svn_md5_digest_to_cstring(digest
, pool
), pool
));
874 /* If authorized, emit a delta to modify EDIT_PATH with the changes
875 from SOURCE_PATH to TARGET_PATH. If not authorized, indicate that
876 EDIT_PATH is absent. Pass DIR_BATON through to editor functions
877 that require it. DEPTH is the depth from this point downward. */
879 replace_file_or_dir(struct context
*c
,
882 const char *source_path
,
883 const char *target_path
,
884 const char *edit_path
,
885 svn_node_kind_t tgt_kind
,
888 svn_revnum_t base_revision
= SVN_INVALID_REVNUM
;
889 svn_boolean_t allowed
;
891 /* Sanity-check our input. */
892 assert(target_path
&& source_path
&& edit_path
);
894 if (c
->authz_read_func
)
896 SVN_ERR(c
->authz_read_func(&allowed
, c
->target_root
, target_path
,
897 c
->authz_read_baton
, pool
));
899 return absent_file_or_dir(c
, dir_baton
, edit_path
, tgt_kind
, pool
);
902 /* Get the base revision for the entry from the hash. */
903 base_revision
= get_path_revision(c
->source_root
, source_path
, pool
);
905 if (tgt_kind
== svn_node_dir
)
909 SVN_ERR(c
->editor
->open_directory(edit_path
, dir_baton
,
912 SVN_ERR(delta_dirs(c
, subdir_baton
, MAYBE_DEMOTE_DEPTH(depth
),
913 source_path
, target_path
, edit_path
, pool
));
914 SVN_ERR(c
->editor
->close_directory(subdir_baton
, pool
));
919 unsigned char digest
[APR_MD5_DIGESTSIZE
];
921 SVN_ERR(c
->editor
->open_file(edit_path
, dir_baton
, base_revision
,
923 SVN_ERR(delta_files(c
, file_baton
, source_path
, target_path
, pool
));
924 SVN_ERR(svn_fs_file_md5_checksum(digest
, c
->target_root
,
926 SVN_ERR(c
->editor
->close_file
927 (file_baton
, svn_md5_digest_to_cstring(digest
, pool
), pool
));
934 /* In directory DIR_BATON, indicate that EDIT_PATH (relative to the
935 edit root) is absent by invoking C->editor->absent_directory or
936 C->editor->absent_file (depending on TGT_KIND). */
938 absent_file_or_dir(struct context
*c
,
940 const char *edit_path
,
941 svn_node_kind_t tgt_kind
,
946 if (tgt_kind
== svn_node_dir
)
947 SVN_ERR(c
->editor
->absent_directory(edit_path
, dir_baton
, pool
));
949 SVN_ERR(c
->editor
->absent_file(edit_path
, dir_baton
, pool
));
955 /* Emit deltas to turn SOURCE_PATH into TARGET_PATH. Assume that
956 DIR_BATON represents the directory we're constructing to the editor
959 delta_dirs(struct context
*c
,
962 const char *source_path
,
963 const char *target_path
,
964 const char *edit_path
,
967 apr_hash_t
*s_entries
= 0, *t_entries
= 0;
968 apr_hash_index_t
*hi
;
973 /* Compare the property lists. */
974 SVN_ERR(delta_proplists(c
, source_path
, target_path
,
975 change_dir_prop
, dir_baton
, pool
));
977 /* Get the list of entries in each of source and target. */
978 SVN_ERR(svn_fs_dir_entries(&t_entries
, c
->target_root
,
981 SVN_ERR(svn_fs_dir_entries(&s_entries
, c
->source_root
,
984 /* Make a subpool for local allocations. */
985 subpool
= svn_pool_create(pool
);
987 /* Loop over the hash of entries in the target, searching for its
988 partner in the source. If we find the matching partner entry,
989 use editor calls to replace the one in target with a new version
990 if necessary, then remove that entry from the source entries
991 hash. If we can't find a related node in the source, we use
992 editor calls to add the entry as a new item in the target.
993 Having handled all the entries that exist in target, any entries
994 still remaining the source entries hash represent entries that no
995 longer exist in target. Use editor calls to delete those entries
996 from the target tree. */
997 for (hi
= apr_hash_first(pool
, t_entries
); hi
; hi
= apr_hash_next(hi
))
999 const svn_fs_dirent_t
*s_entry
, *t_entry
;
1003 const char *t_fullpath
;
1004 const char *e_fullpath
;
1005 const char *s_fullpath
;
1006 svn_node_kind_t tgt_kind
;
1008 /* Clear out our subpool for the next iteration... */
1009 svn_pool_clear(subpool
);
1011 /* KEY is the entry name in target, VAL the dirent */
1012 apr_hash_this(hi
, &key
, &klen
, &val
);
1014 tgt_kind
= t_entry
->kind
;
1015 t_fullpath
= svn_path_join(target_path
, t_entry
->name
, subpool
);
1016 e_fullpath
= svn_path_join(edit_path
, t_entry
->name
, subpool
);
1018 /* Can we find something with the same name in the source
1020 if (s_entries
&& ((s_entry
= apr_hash_get(s_entries
, key
, klen
)) != 0))
1023 svn_node_kind_t src_kind
;
1025 s_fullpath
= svn_path_join(source_path
, t_entry
->name
, subpool
);
1026 src_kind
= s_entry
->kind
;
1028 if (depth
== svn_depth_infinity
1029 || src_kind
!= svn_node_dir
1030 || (src_kind
== svn_node_dir
1031 && depth
== svn_depth_immediates
))
1033 /* Use svn_fs_compare_ids() to compare our current
1034 source and target ids.
1036 0: means they are the same id, and this is a noop.
1037 -1: means they are unrelated, so we have to delete the
1038 old one and add the new one.
1039 1: means the nodes are related through ancestry, so go
1040 ahead and do the replace directly. */
1041 distance
= svn_fs_compare_ids(s_entry
->id
, t_entry
->id
);
1046 else if ((src_kind
!= tgt_kind
)
1047 || ((distance
== -1) && (! c
->ignore_ancestry
)))
1049 SVN_ERR(delete(c
, dir_baton
, e_fullpath
, subpool
));
1050 SVN_ERR(add_file_or_dir(c
, dir_baton
,
1051 MAYBE_DEMOTE_DEPTH(depth
),
1052 t_fullpath
, e_fullpath
, tgt_kind
,
1057 SVN_ERR(replace_file_or_dir(c
, dir_baton
,
1058 MAYBE_DEMOTE_DEPTH(depth
),
1059 s_fullpath
, t_fullpath
,
1060 e_fullpath
, tgt_kind
,
1065 /* Remove the entry from the source_hash. */
1066 apr_hash_set(s_entries
, key
, APR_HASH_KEY_STRING
, NULL
);
1070 if (depth
== svn_depth_infinity
1071 || tgt_kind
!= svn_node_dir
1072 || (tgt_kind
== svn_node_dir
1073 && depth
== svn_depth_immediates
))
1075 SVN_ERR(add_file_or_dir(c
, dir_baton
,
1076 MAYBE_DEMOTE_DEPTH(depth
),
1077 t_fullpath
, e_fullpath
, tgt_kind
,
1083 /* All that is left in the source entries hash are things that need
1084 to be deleted. Delete them. */
1087 for (hi
= apr_hash_first(pool
, s_entries
); hi
; hi
= apr_hash_next(hi
))
1089 const svn_fs_dirent_t
*s_entry
;
1091 const char *e_fullpath
;
1092 svn_node_kind_t src_kind
;
1094 /* Clear out our subpool for the next iteration... */
1095 svn_pool_clear(subpool
);
1097 /* KEY is the entry name in source, VAL the dirent */
1098 apr_hash_this(hi
, NULL
, NULL
, &val
);
1100 src_kind
= s_entry
->kind
;
1101 e_fullpath
= svn_path_join(edit_path
, s_entry
->name
, subpool
);
1103 /* Do we actually want to delete the dir if we're non-recursive? */
1104 if (depth
== svn_depth_infinity
1105 || src_kind
!= svn_node_dir
1106 || (src_kind
== svn_node_dir
1107 && depth
== svn_depth_immediates
))
1109 SVN_ERR(delete(c
, dir_baton
, e_fullpath
, subpool
));
1114 /* Destroy local allocation subpool. */
1115 svn_pool_destroy(subpool
);
1117 return SVN_NO_ERROR
;