delta.c revision 299742
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.  */
54struct 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.  */
71typedef 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.  */
84static 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.  */
90static 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
96static 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.  */
104static 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.  */
113static 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
119static 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.  */
127static svn_error_t *delete(struct context *c,
128                           void *dir_baton,
129                           const char *edit_path,
130                           apr_pool_t *pool);
131
132static 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
140static 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
149static 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
155static 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 */
177static svn_error_t *
178authz_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.  */
200svn_error_t *
201svn_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
376/* Retrieving the base revision from the path/revision hash.  */
377
378
379static svn_revnum_t
380get_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
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. */
416static svn_error_t *
417change_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. */
430static svn_error_t *
431change_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
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. */
451static svn_error_t *
452delta_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
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. */
568static svn_error_t *
569send_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
599svn_error_t *
600svn_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. */
614static svn_error_t *
615delta_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
688/* Generic directory deltafication routines.  */
689
690
691/* Emit a delta to delete the entry named TARGET_ENTRY from DIR_BATON.  */
692static svn_error_t *
693delete(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. */
707static svn_error_t *
708add_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. */
761static svn_error_t *
762replace_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). */
818static svn_error_t *
819absent_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.  */
837static svn_error_t *
838delta_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}
990