]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - contrib/subversion/subversion/libsvn_repos/delta.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.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 static svn_error_t *
200 not_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.  */
211 svn_error_t *
212 svn_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 \f
388 /* Retrieving the base revision from the path/revision hash.  */
389
390
391 static svn_revnum_t
392 get_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 \f
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. */
428 static svn_error_t *
429 change_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. */
442 static svn_error_t *
443 change_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 \f
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. */
463 static svn_error_t *
464 delta_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 \f
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. */
580 static svn_error_t *
581 send_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
611 svn_error_t *
612 svn_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. */
680 static svn_error_t *
681 delta_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 \f
766 /* Generic directory deltafication routines.  */
767
768
769 /* Emit a delta to delete the entry named TARGET_ENTRY from DIR_BATON.  */
770 static svn_error_t *
771 delete(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. */
785 static svn_error_t *
786 add_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. */
839 static svn_error_t *
840 replace_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). */
896 static svn_error_t *
897 absent_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.  */
915 static svn_error_t *
916 delta_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 }