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