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