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