Change the format of the revprops block sent in svnserve for
[svn.git] / subversion / libsvn_repos / delta.c
bloba2ef0975e5aac38ba516965a0dd1971960afa8b4
1 /*
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 * ====================================================================
20 #include <assert.h>
21 #include <apr_hash.h>
22 #include <apr_md5.h>
24 #include "svn_types.h"
25 #include "svn_delta.h"
26 #include "svn_fs.h"
27 #include "svn_md5.h"
28 #include "svn_path.h"
29 #include "svn_repos.h"
30 #include "svn_pools.h"
31 #include "svn_props.h"
32 #include "svn_private_config.h"
33 #include "repos.h"
37 /* THINGS TODO: Currently the code herein gives only a slight nod to
38 fully supporting directory deltas that involve renames, copies, and
39 such. */
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. */
50 struct context {
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,
68 void *object,
69 const char *name,
70 const svn_string_t *value,
71 apr_pool_t *pool);
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,
81 const char *path,
82 apr_pool_t *pool);
85 /* proplist_change_fn_t property changing functions. */
86 static svn_error_t *change_dir_prop(struct context *c,
87 void *object,
88 const char *path,
89 const svn_string_t *value,
90 apr_pool_t *pool);
92 static svn_error_t *change_file_prop(struct context *c,
93 void *object,
94 const char *path,
95 const svn_string_t *value,
96 apr_pool_t *pool);
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,
104 void *object,
105 apr_pool_t *pool);
108 /* Constructing deltas for file constents. */
109 static svn_error_t *send_text_delta(struct context *c,
110 void *file_baton,
111 const char *base_checksum,
112 svn_txdelta_stream_t *delta_stream,
113 apr_pool_t *pool);
115 static svn_error_t *delta_files(struct context *c,
116 void *file_baton,
117 const char *source_path,
118 const char *target_path,
119 apr_pool_t *pool);
122 /* Generic directory deltafication routines. */
123 static svn_error_t *delete(struct context *c,
124 void *dir_baton,
125 const char *edit_path,
126 apr_pool_t *pool);
128 static svn_error_t *add_file_or_dir(struct context *c,
129 void *dir_baton,
130 svn_depth_t depth,
131 const char *target_path,
132 const char *edit_path,
133 svn_node_kind_t tgt_kind,
134 apr_pool_t *pool);
136 static svn_error_t *replace_file_or_dir(struct context *c,
137 void *dir_baton,
138 svn_depth_t depth,
139 const char *source_path,
140 const char *target_path,
141 const char *edit_path,
142 svn_node_kind_t tgt_kind,
143 apr_pool_t *pool);
145 static svn_error_t *absent_file_or_dir(struct context *c,
146 void *dir_baton,
147 const char *edit_path,
148 svn_node_kind_t tgt_kind,
149 apr_pool_t *pool);
151 static svn_error_t *delta_dirs(struct context *c,
152 void *dir_baton,
153 svn_depth_t depth,
154 const char *source_path,
155 const char *target_path,
156 const char *edit_path,
157 apr_pool_t *pool);
161 #define MAYBE_DEMOTE_DEPTH(depth) \
162 (((depth) == svn_depth_immediates || (depth) == svn_depth_files) \
163 ? svn_depth_empty \
164 : (depth))
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().
173 static svn_error_t *
174 authz_root_check(svn_fs_root_t *root,
175 const char *path,
176 svn_repos_authz_func_t authz_read_func,
177 void *authz_read_baton,
178 apr_pool_t *pool)
180 svn_boolean_t allowed;
182 if (authz_read_func)
184 SVN_ERR(authz_read_func(&allowed, root, path, authz_read_baton, pool));
186 if (! allowed)
187 return svn_error_create(SVN_ERR_AUTHZ_ROOT_UNREADABLE, 0,
188 _("Unable to open root of edit"));
191 return SVN_NO_ERROR;
195 static svn_error_t *
196 not_a_dir_error(const char *role,
197 const char *path)
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. */
207 svn_error_t *
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,
214 void *edit_baton,
215 svn_repos_authz_func_t authz_read_func,
216 void *authz_read_baton,
217 svn_boolean_t text_deltas,
218 svn_depth_t depth,
219 svn_boolean_t entry_props,
220 svn_boolean_t ignore_ancestry,
221 apr_pool_t *pool)
223 void *root_baton = NULL;
224 struct context c;
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;
229 int distance;
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. */
237 if (! tgt_fullpath)
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. */
247 if (*src_entry)
248 authz_root_path = svn_path_dirname(tgt_fullpath, pool);
249 else
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))
261 goto cleanup;
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. */
287 c.editor = editor;
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));
309 goto cleanup;
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));
320 goto cleanup;
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);
328 if (distance == 0)
330 /* They are the same node! No-op (you gotta love those). */
331 goto cleanup;
333 else if (*src_entry)
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. */
349 else
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,
356 tgt_kind, pool));
359 else
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));
369 cleanup:
371 /* Make sure we close the root directory if we opened one above. */
372 if (root_baton)
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. */
379 return SVN_NO_ERROR;
383 svn_error_t *
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,
390 void *edit_baton,
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,
397 apr_pool_t *pool)
399 return svn_repos_dir_delta2(src_root,
400 src_parent_dir,
401 src_entry,
402 tgt_root,
403 tgt_fullpath,
404 editor,
405 edit_baton,
406 authz_read_func,
407 authz_read_baton,
408 text_deltas,
409 SVN_DEPTH_INFINITY_OR_FILES(recurse),
410 entry_props,
411 ignore_ancestry,
412 pool);
417 /* Retrieving the base revision from the path/revision hash. */
420 static svn_revnum_t
421 get_path_revision(svn_fs_root_t *root,
422 const char *path,
423 apr_pool_t *pool)
425 svn_revnum_t revision;
426 svn_error_t *err;
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. */
447 return revision;
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
456 function. */
457 static svn_error_t *
458 change_dir_prop(struct context *c,
459 void *object,
460 const char *name,
461 const svn_string_t *value,
462 apr_pool_t *pool)
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
470 function. */
471 static svn_error_t *
472 change_file_prop(struct context *c,
473 void *object,
474 const char *name,
475 const svn_string_t *value,
476 apr_pool_t *pool)
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
491 CHANGE_FN. */
492 static svn_error_t *
493 delta_proplists(struct context *c,
494 const char *source_path,
495 const char *target_path,
496 proplist_change_fn_t *change_fn,
497 void *object,
498 apr_pool_t *pool)
500 apr_hash_t *s_props = 0;
501 apr_hash_t *t_props = 0;
502 apr_pool_t *subpool;
503 apr_array_header_t *prop_diffs;
504 int i;
506 /* Sanity-check our input. */
507 assert(target_path);
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,
513 here we go! */
514 if (c->entry_props)
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);
527 apr_hash_t *r_props;
528 const char *uuid;
530 /* Transmit the committed-rev. */
531 cr_str = svn_string_createf(subpool, "%ld",
532 committed_rev);
533 SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_COMMITTED_REV,
534 cr_str, subpool));
536 SVN_ERR(svn_fs_revision_proplist(&r_props, fs, committed_rev,
537 pool));
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),
561 subpool));
565 if (source_path)
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));
572 if (! changed)
573 goto cleanup;
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));
579 else
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));
596 cleanup:
597 /* Destroy local subpool. */
598 svn_pool_destroy(subpool);
600 return SVN_NO_ERROR;
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. */
612 static svn_error_t *
613 send_text_delta(struct context *c,
614 void *file_baton,
615 const char *base_checksum,
616 svn_txdelta_stream_t *delta_stream,
617 apr_pool_t *pool)
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,
631 delta_handler,
632 delta_handler_baton,
633 pool));
635 else
637 /* The caller doesn't want text delta data. Just send a single
638 NULL window. */
639 SVN_ERR(delta_handler(NULL, delta_handler_baton));
642 return SVN_NO_ERROR;
645 svn_error_t *
646 svn_repos__compare_files(svn_boolean_t *changed_p,
647 svn_fs_root_t *root1,
648 const char *path1,
649 svn_fs_root_t *root2,
650 const char *path2,
651 apr_pool_t *pool)
653 svn_filesize_t size1, size2;
654 unsigned char digest1[APR_MD5_DIGESTSIZE], digest2[APR_MD5_DIGESTSIZE];
655 svn_stream_t *stream1, *stream2;
656 char *buf1, *buf2;
657 apr_size_t len1, len2;
659 /* If the filesystem claims the things haven't changed, then they
660 haven't changed. */
661 SVN_ERR(svn_fs_contents_changed(changed_p, root1, path1,
662 root2, path2, pool));
663 if (!*changed_p)
664 return SVN_NO_ERROR;
666 /* From this point on, assume things haven't changed. */
667 *changed_p = FALSE;
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));
674 if (size1 != size2)
676 *changed_p = TRUE;
677 return SVN_NO_ERROR;
680 /* Same sizes, huh? Well, if their checksums differ, we know they
681 differ. */
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))
686 *changed_p = TRUE;
687 return SVN_NO_ERROR;
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))
705 *changed_p = TRUE;
706 return SVN_NO_ERROR;
709 while (len1 > 0);
711 return SVN_NO_ERROR;
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. */
717 static svn_error_t *
718 delta_files(struct context *c,
719 void *file_baton,
720 const char *source_path,
721 const char *target_path,
722 apr_pool_t *pool)
724 apr_pool_t *subpool;
725 svn_boolean_t changed = TRUE;
727 /* Sanity-check our input. */
728 assert(target_path);
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));
737 if (source_path)
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,
749 subpool));
750 else
751 SVN_ERR(svn_fs_contents_changed(&changed,
752 c->target_root, target_path,
753 c->source_root, source_path,
754 subpool));
756 else
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. */
764 if (changed)
766 svn_txdelta_stream_t *delta_stream = NULL;
767 unsigned char source_digest[APR_MD5_DIGESTSIZE];
768 const char *source_hex_digest = NULL;
770 if (c->text_deltas)
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
775 (&delta_stream,
776 source_path ? c->source_root : NULL,
777 source_path ? source_path : NULL,
778 c->target_root, target_path, subpool));
781 if (source_path)
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,
787 subpool);
790 SVN_ERR(send_text_delta(c, file_baton, source_hex_digest,
791 delta_stream, subpool));
794 /* Cleanup. */
795 svn_pool_destroy(subpool);
797 return SVN_NO_ERROR;
803 /* Generic directory deltafication routines. */
806 /* Emit a delta to delete the entry named TARGET_ENTRY from DIR_BATON. */
807 static svn_error_t *
808 delete(struct context *c,
809 void *dir_baton,
810 const char *edit_path,
811 apr_pool_t *pool)
813 return c->editor->delete_entry(edit_path, SVN_INVALID_REVNUM,
814 dir_baton, pool);
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. */
822 static svn_error_t *
823 add_file_or_dir(struct context *c, void *dir_baton,
824 svn_depth_t depth,
825 const char *target_path,
826 const char *edit_path,
827 svn_node_kind_t tgt_kind,
828 apr_pool_t *pool)
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));
840 if (!allowed)
841 return absent_file_or_dir(c, dir_baton, edit_path, tgt_kind, pool);
844 if (tgt_kind == svn_node_dir)
846 void *subdir_baton;
848 SVN_ERR(context->editor->add_directory(edit_path, dir_baton, NULL,
849 SVN_INVALID_REVNUM, pool,
850 &subdir_baton));
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));
855 else
857 void *file_baton;
858 unsigned char digest[APR_MD5_DIGESTSIZE];
860 SVN_ERR(context->editor->add_file(edit_path, dir_baton,
861 NULL, SVN_INVALID_REVNUM, pool,
862 &file_baton));
863 SVN_ERR(delta_files(context, file_baton, NULL, target_path, pool));
864 SVN_ERR(svn_fs_file_md5_checksum(digest, context->target_root,
865 target_path, pool));
866 SVN_ERR(context->editor->close_file
867 (file_baton, svn_md5_digest_to_cstring(digest, pool), pool));
870 return SVN_NO_ERROR;
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. */
878 static svn_error_t *
879 replace_file_or_dir(struct context *c,
880 void *dir_baton,
881 svn_depth_t depth,
882 const char *source_path,
883 const char *target_path,
884 const char *edit_path,
885 svn_node_kind_t tgt_kind,
886 apr_pool_t *pool)
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));
898 if (!allowed)
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)
907 void *subdir_baton;
909 SVN_ERR(c->editor->open_directory(edit_path, dir_baton,
910 base_revision, pool,
911 &subdir_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));
916 else
918 void *file_baton;
919 unsigned char digest[APR_MD5_DIGESTSIZE];
921 SVN_ERR(c->editor->open_file(edit_path, dir_baton, base_revision,
922 pool, &file_baton));
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,
925 target_path, pool));
926 SVN_ERR(c->editor->close_file
927 (file_baton, svn_md5_digest_to_cstring(digest, pool), pool));
930 return SVN_NO_ERROR;
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). */
937 static svn_error_t *
938 absent_file_or_dir(struct context *c,
939 void *dir_baton,
940 const char *edit_path,
941 svn_node_kind_t tgt_kind,
942 apr_pool_t *pool)
944 assert(edit_path);
946 if (tgt_kind == svn_node_dir)
947 SVN_ERR(c->editor->absent_directory(edit_path, dir_baton, pool));
948 else
949 SVN_ERR(c->editor->absent_file(edit_path, dir_baton, pool));
951 return SVN_NO_ERROR;
955 /* Emit deltas to turn SOURCE_PATH into TARGET_PATH. Assume that
956 DIR_BATON represents the directory we're constructing to the editor
957 in the context C. */
958 static svn_error_t *
959 delta_dirs(struct context *c,
960 void *dir_baton,
961 svn_depth_t depth,
962 const char *source_path,
963 const char *target_path,
964 const char *edit_path,
965 apr_pool_t *pool)
967 apr_hash_t *s_entries = 0, *t_entries = 0;
968 apr_hash_index_t *hi;
969 apr_pool_t *subpool;
971 assert(target_path);
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,
979 target_path, pool));
980 if (source_path)
981 SVN_ERR(svn_fs_dir_entries(&s_entries, c->source_root,
982 source_path, pool));
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;
1000 const void *key;
1001 void *val;
1002 apr_ssize_t klen;
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);
1013 t_entry = 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
1019 entries hash? */
1020 if (s_entries && ((s_entry = apr_hash_get(s_entries, key, klen)) != 0))
1022 int distance;
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);
1042 if (distance == 0)
1044 /* no-op */
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,
1053 subpool));
1055 else
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,
1061 subpool));
1065 /* Remove the entry from the source_hash. */
1066 apr_hash_set(s_entries, key, APR_HASH_KEY_STRING, NULL);
1068 else
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,
1078 subpool));
1083 /* All that is left in the source entries hash are things that need
1084 to be deleted. Delete them. */
1085 if (s_entries)
1087 for (hi = apr_hash_first(pool, s_entries); hi; hi = apr_hash_next(hi))
1089 const svn_fs_dirent_t *s_entry;
1090 void *val;
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);
1099 s_entry = 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;