1 /*
2 * delta.c: an editor driver for expressing differences between two trees
3 *
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
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
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
20 * under the License.
21 * ====================================================================
22 */
23
24
25 #include <apr_hash.h>
26
27 #include "svn_hash.h"
28 #include "svn_types.h"
29 #include "svn_delta.h"
30 #include "svn_fs.h"
31 #include "svn_checksum.h"
32 #include "svn_path.h"
33 #include "svn_repos.h"
34 #include "svn_pools.h"
35 #include "svn_props.h"
36 #include "svn_private_config.h"
37 #include "repos.h"
38
39
40
41 /* THINGS TODO: Currently the code herein gives only a slight nod to
42 fully supporting directory deltas that involve renames, copies, and
43 such. */
44
45
46 /* Some datatypes and declarations used throughout the file. */
47
48
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. */
54 struct context {
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;
64 };
65
66
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,
72 void *object,
73 const char *name,
74 const svn_string_t *value,
75 apr_pool_t *pool);
76
77
78
79 /* Some prototypes for functions used throughout. See each individual
80 function for information about what it does. */
81
82
83 /* Retrieving the base revision from the path/revision hash. */
84 static svn_revnum_t get_path_revision(svn_fs_root_t *root,
85 const char *path,
86 apr_pool_t *pool);
87
88
89 /* proplist_change_fn_t property changing functions. */
90 static svn_error_t *change_dir_prop(struct context *c,
91 void *object,
92 const char *path,
93 const svn_string_t *value,
94 apr_pool_t *pool);
95
96 static svn_error_t *change_file_prop(struct context *c,
97 void *object,
98 const char *path,
99 const svn_string_t *value,
100 apr_pool_t *pool);
101
102
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,
108 void *object,
109 apr_pool_t *pool);
110
111
112 /* Constructing deltas for file constents. */
113 static svn_error_t *send_text_delta(struct context *c,
114 void *file_baton,
115 const char *base_checksum,
116 svn_txdelta_stream_t *delta_stream,
117 apr_pool_t *pool);
118
119 static svn_error_t *delta_files(struct context *c,
120 void *file_baton,
121 const char *source_path,
122 const char *target_path,
123 apr_pool_t *pool);
124
125
126 /* Generic directory deltafication routines. */
127 static svn_error_t *delete(struct context *c,
128 void *dir_baton,
129 const char *edit_path,
130 apr_pool_t *pool);
131
132 static svn_error_t *add_file_or_dir(struct context *c,
133 void *dir_baton,
134 svn_depth_t depth,
135 const char *target_path,
136 const char *edit_path,
137 svn_node_kind_t tgt_kind,
138 apr_pool_t *pool);
139
140 static svn_error_t *replace_file_or_dir(struct context *c,
141 void *dir_baton,
142 svn_depth_t depth,
143 const char *source_path,
144 const char *target_path,
145 const char *edit_path,
146 svn_node_kind_t tgt_kind,
147 apr_pool_t *pool);
148
149 static svn_error_t *absent_file_or_dir(struct context *c,
150 void *dir_baton,
151 const char *edit_path,
152 svn_node_kind_t tgt_kind,
153 apr_pool_t *pool);
154
155 static svn_error_t *delta_dirs(struct context *c,
156 void *dir_baton,
157 svn_depth_t depth,
158 const char *source_path,
159 const char *target_path,
160 const char *edit_path,
161 apr_pool_t *pool);
162
163
164
165 #define MAYBE_DEMOTE_DEPTH(depth) \
166 (((depth) == svn_depth_immediates || (depth) == svn_depth_files) \
167 ? svn_depth_empty \
168 : (depth))
169
170
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.
173 *
174 * PATH should be the implicit root path of an editor drive, that is,
175 * the path used by editor->open_root().
176 */
177 static svn_error_t *
authz_root_check(svn_fs_root_t * root,const char * path,svn_repos_authz_func_t authz_read_func,void * authz_read_baton,apr_pool_t * pool)178 authz_root_check(svn_fs_root_t *root,
179 const char *path,
180 svn_repos_authz_func_t authz_read_func,
181 void *authz_read_baton,
182 apr_pool_t *pool)
183 {
184 svn_boolean_t allowed;
185
186 if (authz_read_func)
187 {
188 SVN_ERR(authz_read_func(&allowed, root, path, authz_read_baton, pool));
189
190 if (! allowed)
191 return svn_error_create(SVN_ERR_AUTHZ_ROOT_UNREADABLE, 0,
192 _("Unable to open root of edit"));
193 }
194
195 return SVN_NO_ERROR;
196 }
197
198
199 /* Public interface to computing directory deltas. */
200 svn_error_t *
svn_repos_dir_delta2(svn_fs_root_t * src_root,const char * src_parent_dir,const char * src_entry,svn_fs_root_t * tgt_root,const char * tgt_fullpath,const svn_delta_editor_t * editor,void * edit_baton,svn_repos_authz_func_t authz_read_func,void * authz_read_baton,svn_boolean_t text_deltas,svn_depth_t depth,svn_boolean_t entry_props,svn_boolean_t ignore_ancestry,apr_pool_t * pool)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,
207 void *edit_baton,
208 svn_repos_authz_func_t authz_read_func,
209 void *authz_read_baton,
210 svn_boolean_t text_deltas,
211 svn_depth_t depth,
212 svn_boolean_t entry_props,
213 svn_boolean_t ignore_ancestry,
214 apr_pool_t *pool)
215 {
216 void *root_baton = NULL;
217 struct context c;
218 const char *src_fullpath, *canonicalized_path;
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;
223
224 /* SRC_PARENT_DIR must be valid. */
225 if (src_parent_dir)
226 {
227 SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL,
228 src_parent_dir, pool, pool));
229 src_parent_dir = canonicalized_path;
230 }
231 else
232 return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, 0,
233 "Invalid source parent directory '(null)'");
234
235 /* TGT_FULLPATH must be valid. */
236 if (tgt_fullpath)
237 {
238 SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL,
239 tgt_fullpath, pool, pool));
240 tgt_fullpath = canonicalized_path;
241 }
242 else
243 return svn_error_create(SVN_ERR_FS_PATH_SYNTAX, 0,
244 _("Invalid target path"));
245
246 if (depth == svn_depth_exclude)
247 return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL,
248 _("Delta depth 'exclude' not supported"));
249
250 /* Calculate the fs path implicitly used for editor->open_root, so
251 we can do an authz check on that path first. */
252 if (*src_entry)
253 authz_root_path = svn_relpath_dirname(tgt_fullpath, pool);
254 else
255 authz_root_path = tgt_fullpath;
256
257 /* Construct the full path of the source item. */
258 src_fullpath = svn_relpath_join(src_parent_dir, src_entry, pool);
259
260 /* Get the node kinds for the source and target paths. */
261 SVN_ERR(svn_fs_check_path(&tgt_kind, tgt_root, tgt_fullpath, pool));
262 SVN_ERR(svn_fs_check_path(&src_kind, src_root, src_fullpath, pool));
263
264 /* If neither of our paths exists, we don't really have anything to do. */
265 if ((tgt_kind == svn_node_none) && (src_kind == svn_node_none))
266 goto cleanup;
267
268 /* If either the source or the target is a non-directory, we
269 require that a SRC_ENTRY be supplied. */
270 if ((! *src_entry) && ((src_kind != svn_node_dir)
271 || tgt_kind != svn_node_dir))
272 return svn_error_create
273 (SVN_ERR_FS_PATH_SYNTAX, 0,
274 _("Invalid editor anchoring; at least one of the "
275 "input paths is not a directory and there was no source entry"));
276
277 /* Don't report / compare stale revprops. However, revprop changes that
278 * are made by a 3rd party outside this delta operation, may not be
279 * detected as per our visibility guarantees. Reset the revprop caches
280 * for both roots in case they belong to different svn_fs_t instances. */
281 SVN_ERR(svn_fs_refresh_revision_props(svn_fs_root_fs(tgt_root), pool));
282 SVN_ERR(svn_fs_refresh_revision_props(svn_fs_root_fs(src_root), pool));
283
284 /* Set the global target revision if one can be determined. */
285 if (svn_fs_is_revision_root(tgt_root))
286 {
287 SVN_ERR(editor->set_target_revision
288 (edit_baton, svn_fs_revision_root_revision(tgt_root), pool));
289 }
290 else if (svn_fs_is_txn_root(tgt_root))
291 {
292 SVN_ERR(editor->set_target_revision
293 (edit_baton, svn_fs_txn_root_base_revision(tgt_root), pool));
294 }
295
296 /* Setup our pseudo-global structure here. We need these variables
297 throughout the deltafication process, so pass them around by
298 reference to all the helper functions. */
299 c.editor = editor;
300 c.source_root = src_root;
301 c.target_root = tgt_root;
302 c.authz_read_func = authz_read_func;
303 c.authz_read_baton = authz_read_baton;
304 c.text_deltas = text_deltas;
305 c.entry_props = entry_props;
306 c.ignore_ancestry = ignore_ancestry;
307
308 /* Get our editor root's revision. */
309 rootrev = get_path_revision(src_root, src_parent_dir, pool);
310
311 /* If one or the other of our paths doesn't exist, we have to handle
312 those cases specially. */
313 if (tgt_kind == svn_node_none)
314 {
315 /* Caller thinks that target still exists, but it doesn't.
316 So transform their source path to "nothing" by deleting it. */
317 SVN_ERR(authz_root_check(tgt_root, authz_root_path,
318 authz_read_func, authz_read_baton, pool));
319 SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton));
320 SVN_ERR(delete(&c, root_baton, src_entry, pool));
321 goto cleanup;
322 }
323 if (src_kind == svn_node_none)
324 {
325 /* The source path no longer exists, but the target does.
326 So transform "nothing" into "something" by adding. */
327 SVN_ERR(authz_root_check(tgt_root, authz_root_path,
328 authz_read_func, authz_read_baton, pool));
329 SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton));
330 SVN_ERR(add_file_or_dir(&c, root_baton, depth, tgt_fullpath,
331 src_entry, tgt_kind, pool));
332 goto cleanup;
333 }
334
335 /* Get and compare the node IDs for the source and target. */
336 SVN_ERR(svn_fs_node_relation(&relation, tgt_root, tgt_fullpath,
337 src_root, src_fullpath, pool));
338
339 if (relation == svn_fs_node_unchanged)
340 {
341 /* They are the same node! No-op (you gotta love those). */
342 goto cleanup;
343 }
344 else if (*src_entry)
345 {
346 /* If the nodes have different kinds, we must delete the one and
347 add the other. Also, if they are completely unrelated and
348 our caller is interested in relatedness, we do the same thing. */
349 if ((src_kind != tgt_kind)
350 || ((relation == svn_fs_node_unrelated) && (! ignore_ancestry)))
351 {
352 SVN_ERR(authz_root_check(tgt_root, authz_root_path,
353 authz_read_func, authz_read_baton, pool));
354 SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton));
355 SVN_ERR(delete(&c, root_baton, src_entry, pool));
356 SVN_ERR(add_file_or_dir(&c, root_baton, depth, tgt_fullpath,
357 src_entry, tgt_kind, pool));
358 }
359 /* Otherwise, we just replace the one with the other. */
360 else
361 {
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(replace_file_or_dir(&c, root_baton, depth, src_fullpath,
366 tgt_fullpath, src_entry,
367 tgt_kind, pool));
368 }
369 }
370 else
371 {
372 /* There is no entry given, so delta the whole parent directory. */
373 SVN_ERR(authz_root_check(tgt_root, authz_root_path,
374 authz_read_func, authz_read_baton, pool));
375 SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton));
376 SVN_ERR(delta_dirs(&c, root_baton, depth, src_fullpath,
377 tgt_fullpath, "", pool));
378 }
379
380 cleanup:
381
382 /* Make sure we close the root directory if we opened one above. */
383 if (root_baton)
384 SVN_ERR(editor->close_directory(root_baton, pool));
385
386 /* Close the edit. */
387 return editor->close_edit(edit_baton, pool);
388 }
389
390
391 /* Retrieving the base revision from the path/revision hash. */
392
393
394 static svn_revnum_t
get_path_revision(svn_fs_root_t * root,const char * path,apr_pool_t * pool)395 get_path_revision(svn_fs_root_t *root,
396 const char *path,
397 apr_pool_t *pool)
398 {
399 svn_revnum_t revision;
400 svn_error_t *err;
401
402 /* Easy out -- if ROOT is a revision root, we can use the revision
403 that it's a root of. */
404 if (svn_fs_is_revision_root(root))
405 return svn_fs_revision_root_revision(root);
406
407 /* Else, this must be a transaction root, so ask the filesystem in
408 what revision this path was created. */
409 if ((err = svn_fs_node_created_rev(&revision, root, path, pool)))
410 {
411 revision = SVN_INVALID_REVNUM;
412 svn_error_clear(err);
413 }
414
415 /* If we don't get back a valid revision, this path is mutable in
416 the transaction. We should probably examine the node on which it
417 is based, doable by querying for the node-id of the path, and
418 then examining that node-id's predecessor. ### This predecessor
419 determination isn't exposed via the FS public API right now, so
420 for now, we'll just return the SVN_INVALID_REVNUM. */
421 return revision;
422 }
423
424
425 /* proplist_change_fn_t property changing functions. */
426
427
428 /* Call the directory property-setting function of C->editor to set
429 the property NAME to given VALUE on the OBJECT passed to this
430 function. */
431 static svn_error_t *
change_dir_prop(struct context * c,void * object,const char * name,const svn_string_t * value,apr_pool_t * pool)432 change_dir_prop(struct context *c,
433 void *object,
434 const char *name,
435 const svn_string_t *value,
436 apr_pool_t *pool)
437 {
438 return c->editor->change_dir_prop(object, name, value, pool);
439 }
440
441
442 /* Call the file property-setting function of C->editor to set the
443 property NAME to given VALUE on the OBJECT passed to this
444 function. */
445 static svn_error_t *
change_file_prop(struct context * c,void * object,const char * name,const svn_string_t * value,apr_pool_t * pool)446 change_file_prop(struct context *c,
447 void *object,
448 const char *name,
449 const svn_string_t *value,
450 apr_pool_t *pool)
451 {
452 return c->editor->change_file_prop(object, name, value, pool);
453 }
454
455
456
457
458 /* Constructing deltas for properties of files and directories. */
459
460
461 /* Generate the appropriate property editing calls to turn the
462 properties of SOURCE_PATH into those of TARGET_PATH. If
463 SOURCE_PATH is NULL, this is an add, so assume the target starts
464 with no properties. Pass OBJECT on to the editor function wrapper
465 CHANGE_FN. */
466 static svn_error_t *
delta_proplists(struct context * c,const char * source_path,const char * target_path,proplist_change_fn_t * change_fn,void * object,apr_pool_t * pool)467 delta_proplists(struct context *c,
468 const char *source_path,
469 const char *target_path,
470 proplist_change_fn_t *change_fn,
471 void *object,
472 apr_pool_t *pool)
473 {
474 apr_hash_t *s_props = 0;
475 apr_hash_t *t_props = 0;
476 apr_pool_t *subpool;
477 apr_array_header_t *prop_diffs;
478 int i;
479
480 SVN_ERR_ASSERT(target_path);
481
482 /* Make a subpool for local allocations. */
483 subpool = svn_pool_create(pool);
484
485 /* If we're supposed to send entry props for all non-deleted items,
486 here we go! */
487 if (c->entry_props)
488 {
489 svn_revnum_t committed_rev = SVN_INVALID_REVNUM;
490 svn_string_t *cr_str = NULL;
491 svn_string_t *committed_date = NULL;
492 svn_string_t *last_author = NULL;
493
494 /* Get the CR and two derivative props. ### check for error returns. */
495 SVN_ERR(svn_fs_node_created_rev(&committed_rev, c->target_root,
496 target_path, subpool));
497 if (SVN_IS_VALID_REVNUM(committed_rev))
498 {
499 svn_fs_t *fs = svn_fs_root_fs(c->target_root);
500 apr_hash_t *r_props;
501 const char *uuid;
502
503 /* Transmit the committed-rev. */
504 cr_str = svn_string_createf(subpool, "%ld",
505 committed_rev);
506 SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_COMMITTED_REV,
507 cr_str, subpool));
508
509 SVN_ERR(svn_fs_revision_proplist2(&r_props, fs, committed_rev,
510 FALSE, pool, subpool));
511
512 /* Transmit the committed-date. */
513 committed_date = svn_hash_gets(r_props, SVN_PROP_REVISION_DATE);
514 if (committed_date || source_path)
515 {
516 SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_COMMITTED_DATE,
517 committed_date, subpool));
518 }
519
520 /* Transmit the last-author. */
521 last_author = svn_hash_gets(r_props, SVN_PROP_REVISION_AUTHOR);
522 if (last_author || source_path)
523 {
524 SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_LAST_AUTHOR,
525 last_author, subpool));
526 }
527
528 /* Transmit the UUID. */
529 SVN_ERR(svn_fs_get_uuid(fs, &uuid, subpool));
530 SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_UUID,
531 svn_string_create(uuid, subpool),
532 subpool));
533 }
534 }
535
536 if (source_path)
537 {
538 svn_boolean_t changed;
539
540 /* Is this deltification worth our time? */
541 SVN_ERR(svn_fs_props_different(&changed, c->target_root, target_path,
542 c->source_root, source_path, subpool));
543 if (! changed)
544 goto cleanup;
545
546 /* If so, go ahead and get the source path's properties. */
547 SVN_ERR(svn_fs_node_proplist(&s_props, c->source_root,
548 source_path, subpool));
549 }
550 else
551 {
552 s_props = apr_hash_make(subpool);
553 }
554
555 /* Get the target path's properties */
556 SVN_ERR(svn_fs_node_proplist(&t_props, c->target_root,
557 target_path, subpool));
558
559 /* Now transmit the differences. */
560 SVN_ERR(svn_prop_diffs(&prop_diffs, t_props, s_props, subpool));
561 for (i = 0; i < prop_diffs->nelts; i++)
562 {
563 const svn_prop_t *pc = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t);
564 SVN_ERR(change_fn(c, object, pc->name, pc->value, subpool));
565 }
566
567 cleanup:
568 /* Destroy local subpool. */
569 svn_pool_destroy(subpool);
570
571 return SVN_NO_ERROR;
572 }
573
574
575
576
577 /* Constructing deltas for file contents. */
578
579
580 /* Change the contents of FILE_BATON in C->editor, according to the
581 text delta from DELTA_STREAM. Pass BASE_CHECKSUM along to
582 C->editor->apply_textdelta. */
583 static svn_error_t *
send_text_delta(struct context * c,void * file_baton,const char * base_checksum,svn_txdelta_stream_t * delta_stream,apr_pool_t * pool)584 send_text_delta(struct context *c,
585 void *file_baton,
586 const char *base_checksum,
587 svn_txdelta_stream_t *delta_stream,
588 apr_pool_t *pool)
589 {
590 svn_txdelta_window_handler_t delta_handler;
591 void *delta_handler_baton;
592
593 /* Get a handler that will apply the delta to the file. */
594 SVN_ERR(c->editor->apply_textdelta
595 (file_baton, base_checksum, pool,
596 &delta_handler, &delta_handler_baton));
597
598 if (c->text_deltas && delta_stream)
599 {
600 /* Deliver the delta stream to the file. */
601 return svn_txdelta_send_txstream(delta_stream,
602 delta_handler,
603 delta_handler_baton,
604 pool);
605 }
606 else
607 {
608 /* The caller doesn't want text delta data. Just send a single
609 NULL window. */
610 return delta_handler(NULL, delta_handler_baton);
611 }
612 }
613
614 /* Make the appropriate edits on FILE_BATON to change its contents and
615 properties from those in SOURCE_PATH to those in TARGET_PATH. */
616 static svn_error_t *
delta_files(struct context * c,void * file_baton,const char * source_path,const char * target_path,apr_pool_t * pool)617 delta_files(struct context *c,
618 void *file_baton,
619 const char *source_path,
620 const char *target_path,
621 apr_pool_t *pool)
622 {
623 apr_pool_t *subpool;
624 svn_boolean_t changed = TRUE;
625
626 SVN_ERR_ASSERT(target_path);
627
628 /* Make a subpool for local allocations. */
629 subpool = svn_pool_create(pool);
630
631 /* Compare the files' property lists. */
632 SVN_ERR(delta_proplists(c, source_path, target_path,
633 change_file_prop, file_baton, subpool));
634
635 if (source_path)
636 {
637 SVN_ERR(svn_fs_contents_different(&changed,
638 c->target_root, target_path,
639 c->source_root, source_path,
640 subpool));
641 }
642 else
643 {
644 /* If there isn't a source path, this is an add, which
645 necessarily has textual mods. */
646 }
647
648 /* If there is a change, and the context indicates that we should
649 care about it, then hand it off to a delta stream. */
650 if (changed)
651 {
652 svn_txdelta_stream_t *delta_stream = NULL;
653 svn_checksum_t *source_checksum;
654 const char *source_hex_digest = NULL;
655
656 if (c->text_deltas)
657 {
658 /* Get a delta stream turning an empty file into one having
659 TARGET_PATH's contents. */
660 SVN_ERR(svn_fs_get_file_delta_stream
661 (&delta_stream,
662 source_path ? c->source_root : NULL,
663 source_path ? source_path : NULL,
664 c->target_root, target_path, subpool));
665 }
666
667 if (source_path)
668 {
669 SVN_ERR(svn_fs_file_checksum(&source_checksum, svn_checksum_md5,
670 c->source_root, source_path, TRUE,
671 subpool));
672
673 source_hex_digest = svn_checksum_to_cstring(source_checksum,
674 subpool);
675 }
676
677 SVN_ERR(send_text_delta(c, file_baton, source_hex_digest,
678 delta_stream, subpool));
679 }
680
681 /* Cleanup. */
682 svn_pool_destroy(subpool);
683
684 return SVN_NO_ERROR;
685 }
686
687
688
689
690 /* Generic directory deltafication routines. */
691
692
693 /* Emit a delta to delete the entry named TARGET_ENTRY from DIR_BATON. */
694 static svn_error_t *
delete(struct context * c,void * dir_baton,const char * edit_path,apr_pool_t * pool)695 delete(struct context *c,
696 void *dir_baton,
697 const char *edit_path,
698 apr_pool_t *pool)
699 {
700 return c->editor->delete_entry(edit_path, SVN_INVALID_REVNUM,
701 dir_baton, pool);
702 }
703
704
705 /* If authorized, emit a delta to create the entry named TARGET_ENTRY
706 at the location EDIT_PATH. If not authorized, indicate that
707 EDIT_PATH is absent. Pass DIR_BATON through to editor functions
708 that require it. DEPTH is the depth from this point downward. */
709 static svn_error_t *
add_file_or_dir(struct context * c,void * dir_baton,svn_depth_t depth,const char * target_path,const char * edit_path,svn_node_kind_t tgt_kind,apr_pool_t * pool)710 add_file_or_dir(struct context *c, void *dir_baton,
711 svn_depth_t depth,
712 const char *target_path,
713 const char *edit_path,
714 svn_node_kind_t tgt_kind,
715 apr_pool_t *pool)
716 {
717 struct context *context = c;
718 svn_boolean_t allowed;
719
720 SVN_ERR_ASSERT(target_path && edit_path);
721
722 if (c->authz_read_func)
723 {
724 SVN_ERR(c->authz_read_func(&allowed, c->target_root, target_path,
725 c->authz_read_baton, pool));
726 if (!allowed)
727 return absent_file_or_dir(c, dir_baton, edit_path, tgt_kind, pool);
728 }
729
730 if (tgt_kind == svn_node_dir)
731 {
732 void *subdir_baton;
733
734 SVN_ERR(context->editor->add_directory(edit_path, dir_baton, NULL,
735 SVN_INVALID_REVNUM, pool,
736 &subdir_baton));
737 SVN_ERR(delta_dirs(context, subdir_baton, MAYBE_DEMOTE_DEPTH(depth),
738 NULL, target_path, edit_path, pool));
739 return context->editor->close_directory(subdir_baton, pool);
740 }
741 else
742 {
743 void *file_baton;
744 svn_checksum_t *checksum;
745
746 SVN_ERR(context->editor->add_file(edit_path, dir_baton,
747 NULL, SVN_INVALID_REVNUM, pool,
748 &file_baton));
749 SVN_ERR(delta_files(context, file_baton, NULL, target_path, pool));
750 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
751 context->target_root, target_path,
752 TRUE, pool));
753 return context->editor->close_file
754 (file_baton, svn_checksum_to_cstring(checksum, pool), pool);
755 }
756 }
757
758
759 /* If authorized, emit a delta to modify EDIT_PATH with the changes
760 from SOURCE_PATH to TARGET_PATH. If not authorized, indicate that
761 EDIT_PATH is absent. Pass DIR_BATON through to editor functions
762 that require it. DEPTH is the depth from this point downward. */
763 static svn_error_t *
replace_file_or_dir(struct context * c,void * dir_baton,svn_depth_t depth,const char * source_path,const char * target_path,const char * edit_path,svn_node_kind_t tgt_kind,apr_pool_t * pool)764 replace_file_or_dir(struct context *c,
765 void *dir_baton,
766 svn_depth_t depth,
767 const char *source_path,
768 const char *target_path,
769 const char *edit_path,
770 svn_node_kind_t tgt_kind,
771 apr_pool_t *pool)
772 {
773 svn_revnum_t base_revision = SVN_INVALID_REVNUM;
774 svn_boolean_t allowed;
775
776 SVN_ERR_ASSERT(target_path && source_path && edit_path);
777
778 if (c->authz_read_func)
779 {
780 SVN_ERR(c->authz_read_func(&allowed, c->target_root, target_path,
781 c->authz_read_baton, pool));
782 if (!allowed)
783 return absent_file_or_dir(c, dir_baton, edit_path, tgt_kind, pool);
784 }
785
786 /* Get the base revision for the entry from the hash. */
787 base_revision = get_path_revision(c->source_root, source_path, pool);
788
789 if (tgt_kind == svn_node_dir)
790 {
791 void *subdir_baton;
792
793 SVN_ERR(c->editor->open_directory(edit_path, dir_baton,
794 base_revision, pool,
795 &subdir_baton));
796 SVN_ERR(delta_dirs(c, subdir_baton, MAYBE_DEMOTE_DEPTH(depth),
797 source_path, target_path, edit_path, pool));
798 return c->editor->close_directory(subdir_baton, pool);
799 }
800 else
801 {
802 void *file_baton;
803 svn_checksum_t *checksum;
804
805 SVN_ERR(c->editor->open_file(edit_path, dir_baton, base_revision,
806 pool, &file_baton));
807 SVN_ERR(delta_files(c, file_baton, source_path, target_path, pool));
808 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
809 c->target_root, target_path, TRUE,
810 pool));
811 return c->editor->close_file
812 (file_baton, svn_checksum_to_cstring(checksum, pool), pool);
813 }
814 }
815
816
817 /* In directory DIR_BATON, indicate that EDIT_PATH (relative to the
818 edit root) is absent by invoking C->editor->absent_directory or
819 C->editor->absent_file (depending on TGT_KIND). */
820 static svn_error_t *
absent_file_or_dir(struct context * c,void * dir_baton,const char * edit_path,svn_node_kind_t tgt_kind,apr_pool_t * pool)821 absent_file_or_dir(struct context *c,
822 void *dir_baton,
823 const char *edit_path,
824 svn_node_kind_t tgt_kind,
825 apr_pool_t *pool)
826 {
827 SVN_ERR_ASSERT(edit_path);
828
829 if (tgt_kind == svn_node_dir)
830 return c->editor->absent_directory(edit_path, dir_baton, pool);
831 else
832 return c->editor->absent_file(edit_path, dir_baton, pool);
833 }
834
835
836 /* Emit deltas to turn SOURCE_PATH into TARGET_PATH. Assume that
837 DIR_BATON represents the directory we're constructing to the editor
838 in the context C. */
839 static svn_error_t *
delta_dirs(struct context * c,void * dir_baton,svn_depth_t depth,const char * source_path,const char * target_path,const char * edit_path,apr_pool_t * pool)840 delta_dirs(struct context *c,
841 void *dir_baton,
842 svn_depth_t depth,
843 const char *source_path,
844 const char *target_path,
845 const char *edit_path,
846 apr_pool_t *pool)
847 {
848 apr_hash_t *s_entries = 0, *t_entries = 0;
849 apr_hash_index_t *hi;
850 apr_pool_t *subpool;
851
852 SVN_ERR_ASSERT(target_path);
853
854 /* Compare the property lists. */
855 SVN_ERR(delta_proplists(c, source_path, target_path,
856 change_dir_prop, dir_baton, pool));
857
858 /* Get the list of entries in each of source and target. */
859 SVN_ERR(svn_fs_dir_entries(&t_entries, c->target_root,
860 target_path, pool));
861 if (source_path)
862 SVN_ERR(svn_fs_dir_entries(&s_entries, c->source_root,
863 source_path, pool));
864
865 /* Make a subpool for local allocations. */
866 subpool = svn_pool_create(pool);
867
868 /* Loop over the hash of entries in the target, searching for its
869 partner in the source. If we find the matching partner entry,
870 use editor calls to replace the one in target with a new version
871 if necessary, then remove that entry from the source entries
872 hash. If we can't find a related node in the source, we use
873 editor calls to add the entry as a new item in the target.
874 Having handled all the entries that exist in target, any entries
875 still remaining the source entries hash represent entries that no
876 longer exist in target. Use editor calls to delete those entries
877 from the target tree. */
878 for (hi = apr_hash_first(pool, t_entries); hi; hi = apr_hash_next(hi))
879 {
880 const void *key = apr_hash_this_key(hi);
881 apr_ssize_t klen = apr_hash_this_key_len(hi);
882 const svn_fs_dirent_t *t_entry = apr_hash_this_val(hi);
883 const svn_fs_dirent_t *s_entry;
884 const char *t_fullpath;
885 const char *e_fullpath;
886 const char *s_fullpath;
887 svn_node_kind_t tgt_kind;
888
889 /* Clear out our subpool for the next iteration... */
890 svn_pool_clear(subpool);
891
892 tgt_kind = t_entry->kind;
893 t_fullpath = svn_relpath_join(target_path, t_entry->name, subpool);
894 e_fullpath = svn_relpath_join(edit_path, t_entry->name, subpool);
895
896 /* Can we find something with the same name in the source
897 entries hash? */
898 if (s_entries && ((s_entry = apr_hash_get(s_entries, key, klen)) != 0))
899 {
900 svn_node_kind_t src_kind;
901
902 s_fullpath = svn_relpath_join(source_path, t_entry->name, subpool);
903 src_kind = s_entry->kind;
904
905 if (depth == svn_depth_infinity
906 || src_kind != svn_node_dir
907 || (src_kind == svn_node_dir
908 && depth == svn_depth_immediates))
909 {
910 /* Use svn_fs_compare_ids() to compare our current
911 source and target ids.
912
913 0: means they are the same id, and this is a noop.
914 -1: means they are unrelated, so we have to delete the
915 old one and add the new one.
916 1: means the nodes are related through ancestry, so go
917 ahead and do the replace directly. */
918 int distance = svn_fs_compare_ids(s_entry->id, t_entry->id);
919 if (distance == 0)
920 {
921 /* no-op */
922 }
923 else if ((src_kind != tgt_kind)
924 || ((distance == -1) && (! c->ignore_ancestry)))
925 {
926 SVN_ERR(delete(c, dir_baton, e_fullpath, subpool));
927 SVN_ERR(add_file_or_dir(c, dir_baton,
928 MAYBE_DEMOTE_DEPTH(depth),
929 t_fullpath, e_fullpath, tgt_kind,
930 subpool));
931 }
932 else
933 {
934 SVN_ERR(replace_file_or_dir(c, dir_baton,
935 MAYBE_DEMOTE_DEPTH(depth),
936 s_fullpath, t_fullpath,
937 e_fullpath, tgt_kind,
938 subpool));
939 }
940 }
941
942 /* Remove the entry from the source_hash. */
943 svn_hash_sets(s_entries, key, NULL);
944 }
945 else
946 {
947 if (depth == svn_depth_infinity
948 || tgt_kind != svn_node_dir
949 || (tgt_kind == svn_node_dir
950 && depth == svn_depth_immediates))
951 {
952 SVN_ERR(add_file_or_dir(c, dir_baton,
953 MAYBE_DEMOTE_DEPTH(depth),
954 t_fullpath, e_fullpath, tgt_kind,
955 subpool));
956 }
957 }
958 }
959
960 /* All that is left in the source entries hash are things that need
961 to be deleted. Delete them. */
962 if (s_entries)
963 {
964 for (hi = apr_hash_first(pool, s_entries); hi; hi = apr_hash_next(hi))
965 {
966 const svn_fs_dirent_t *s_entry = apr_hash_this_val(hi);
967 const char *e_fullpath;
968 svn_node_kind_t src_kind;
969
970 /* Clear out our subpool for the next iteration... */
971 svn_pool_clear(subpool);
972
973 src_kind = s_entry->kind;
974 e_fullpath = svn_relpath_join(edit_path, s_entry->name, subpool);
975
976 /* Do we actually want to delete the dir if we're non-recursive? */
977 if (depth == svn_depth_infinity
978 || src_kind != svn_node_dir
979 || (src_kind == svn_node_dir
980 && depth == svn_depth_immediates))
981 {
982 SVN_ERR(delete(c, dir_baton, e_fullpath, subpool));
983 }
984 }
985 }
986
987 /* Destroy local allocation subpool. */
988 svn_pool_destroy(subpool);
989
990 return SVN_NO_ERROR;
991 }
992