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