1251881Speter/*
2251881Speter * delta.c:   an editor driver for expressing differences between two trees
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter
25251881Speter#include <apr_hash.h>
26251881Speter
27251881Speter#include "svn_hash.h"
28251881Speter#include "svn_types.h"
29251881Speter#include "svn_delta.h"
30251881Speter#include "svn_fs.h"
31251881Speter#include "svn_checksum.h"
32251881Speter#include "svn_path.h"
33251881Speter#include "svn_repos.h"
34251881Speter#include "svn_pools.h"
35251881Speter#include "svn_props.h"
36251881Speter#include "svn_private_config.h"
37251881Speter#include "repos.h"
38251881Speter
39251881Speter
40251881Speter
41251881Speter/* THINGS TODO:  Currently the code herein gives only a slight nod to
42251881Speter   fully supporting directory deltas that involve renames, copies, and
43251881Speter   such.  */
44251881Speter
45251881Speter
46251881Speter/* Some datatypes and declarations used throughout the file.  */
47251881Speter
48251881Speter
49251881Speter/* Parameters which remain constant throughout a delta traversal.
50251881Speter   At the top of the recursion, we initialize one of these structures.
51251881Speter   Then we pass it down to every call.  This way, functions invoked
52251881Speter   deep in the recursion can get access to this traversal's global
53251881Speter   parameters, without using global variables.  */
54251881Speterstruct context {
55251881Speter  const svn_delta_editor_t *editor;
56251881Speter  const char *edit_base_path;
57251881Speter  svn_fs_root_t *source_root;
58251881Speter  svn_fs_root_t *target_root;
59251881Speter  svn_repos_authz_func_t authz_read_func;
60251881Speter  void *authz_read_baton;
61251881Speter  svn_boolean_t text_deltas;
62251881Speter  svn_boolean_t entry_props;
63251881Speter  svn_boolean_t ignore_ancestry;
64251881Speter};
65251881Speter
66251881Speter
67251881Speter/* The type of a function that accepts changes to an object's property
68251881Speter   list.  OBJECT is the object whose properties are being changed.
69251881Speter   NAME is the name of the property to change.  VALUE is the new value
70251881Speter   for the property, or zero if the property should be deleted.  */
71251881Spetertypedef svn_error_t *proplist_change_fn_t(struct context *c,
72251881Speter                                          void *object,
73251881Speter                                          const char *name,
74251881Speter                                          const svn_string_t *value,
75251881Speter                                          apr_pool_t *pool);
76251881Speter
77251881Speter
78251881Speter
79251881Speter/* Some prototypes for functions used throughout.  See each individual
80251881Speter   function for information about what it does.  */
81251881Speter
82251881Speter
83251881Speter/* Retrieving the base revision from the path/revision hash.  */
84251881Speterstatic svn_revnum_t get_path_revision(svn_fs_root_t *root,
85251881Speter                                      const char *path,
86251881Speter                                      apr_pool_t *pool);
87251881Speter
88251881Speter
89251881Speter/* proplist_change_fn_t property changing functions.  */
90251881Speterstatic svn_error_t *change_dir_prop(struct context *c,
91251881Speter                                    void *object,
92251881Speter                                    const char *path,
93251881Speter                                    const svn_string_t *value,
94251881Speter                                    apr_pool_t *pool);
95251881Speter
96251881Speterstatic svn_error_t *change_file_prop(struct context *c,
97251881Speter                                     void *object,
98251881Speter                                     const char *path,
99251881Speter                                     const svn_string_t *value,
100251881Speter                                     apr_pool_t *pool);
101251881Speter
102251881Speter
103251881Speter/* Constructing deltas for properties of files and directories.  */
104251881Speterstatic svn_error_t *delta_proplists(struct context *c,
105251881Speter                                    const char *source_path,
106251881Speter                                    const char *target_path,
107251881Speter                                    proplist_change_fn_t *change_fn,
108251881Speter                                    void *object,
109251881Speter                                    apr_pool_t *pool);
110251881Speter
111251881Speter
112251881Speter/* Constructing deltas for file constents.  */
113251881Speterstatic svn_error_t *send_text_delta(struct context *c,
114251881Speter                                    void *file_baton,
115251881Speter                                    const char *base_checksum,
116251881Speter                                    svn_txdelta_stream_t *delta_stream,
117251881Speter                                    apr_pool_t *pool);
118251881Speter
119251881Speterstatic svn_error_t *delta_files(struct context *c,
120251881Speter                                void *file_baton,
121251881Speter                                const char *source_path,
122251881Speter                                const char *target_path,
123251881Speter                                apr_pool_t *pool);
124251881Speter
125251881Speter
126251881Speter/* Generic directory deltafication routines.  */
127251881Speterstatic svn_error_t *delete(struct context *c,
128251881Speter                           void *dir_baton,
129251881Speter                           const char *edit_path,
130251881Speter                           apr_pool_t *pool);
131251881Speter
132251881Speterstatic svn_error_t *add_file_or_dir(struct context *c,
133251881Speter                                    void *dir_baton,
134251881Speter                                    svn_depth_t depth,
135251881Speter                                    const char *target_path,
136251881Speter                                    const char *edit_path,
137251881Speter                                    svn_node_kind_t tgt_kind,
138251881Speter                                    apr_pool_t *pool);
139251881Speter
140251881Speterstatic svn_error_t *replace_file_or_dir(struct context *c,
141251881Speter                                        void *dir_baton,
142251881Speter                                        svn_depth_t depth,
143251881Speter                                        const char *source_path,
144251881Speter                                        const char *target_path,
145251881Speter                                        const char *edit_path,
146251881Speter                                        svn_node_kind_t tgt_kind,
147251881Speter                                        apr_pool_t *pool);
148251881Speter
149251881Speterstatic svn_error_t *absent_file_or_dir(struct context *c,
150251881Speter                                       void *dir_baton,
151251881Speter                                       const char *edit_path,
152251881Speter                                       svn_node_kind_t tgt_kind,
153251881Speter                                       apr_pool_t *pool);
154251881Speter
155251881Speterstatic svn_error_t *delta_dirs(struct context *c,
156251881Speter                               void *dir_baton,
157251881Speter                               svn_depth_t depth,
158251881Speter                               const char *source_path,
159251881Speter                               const char *target_path,
160251881Speter                               const char *edit_path,
161251881Speter                               apr_pool_t *pool);
162251881Speter
163251881Speter
164251881Speter
165251881Speter#define MAYBE_DEMOTE_DEPTH(depth)                                  \
166251881Speter  (((depth) == svn_depth_immediates || (depth) == svn_depth_files) \
167251881Speter   ? svn_depth_empty                                               \
168251881Speter   : (depth))
169251881Speter
170251881Speter
171251881Speter/* Return the error 'SVN_ERR_AUTHZ_ROOT_UNREADABLE' if PATH in ROOT is
172251881Speter * unreadable according to AUTHZ_READ_FUNC with AUTHZ_READ_BATON.
173251881Speter *
174251881Speter * PATH should be the implicit root path of an editor drive, that is,
175251881Speter * the path used by editor->open_root().
176251881Speter */
177251881Speterstatic svn_error_t *
178251881Speterauthz_root_check(svn_fs_root_t *root,
179251881Speter                 const char *path,
180251881Speter                 svn_repos_authz_func_t authz_read_func,
181251881Speter                 void *authz_read_baton,
182251881Speter                 apr_pool_t *pool)
183251881Speter{
184251881Speter  svn_boolean_t allowed;
185251881Speter
186251881Speter  if (authz_read_func)
187251881Speter    {
188251881Speter      SVN_ERR(authz_read_func(&allowed, root, path, authz_read_baton, pool));
189251881Speter
190251881Speter      if (! allowed)
191251881Speter        return svn_error_create(SVN_ERR_AUTHZ_ROOT_UNREADABLE, 0,
192251881Speter                                _("Unable to open root of edit"));
193251881Speter    }
194251881Speter
195251881Speter  return SVN_NO_ERROR;
196251881Speter}
197251881Speter
198251881Speter
199251881Speter/* Public interface to computing directory deltas.  */
200251881Spetersvn_error_t *
201251881Spetersvn_repos_dir_delta2(svn_fs_root_t *src_root,
202251881Speter                     const char *src_parent_dir,
203251881Speter                     const char *src_entry,
204251881Speter                     svn_fs_root_t *tgt_root,
205251881Speter                     const char *tgt_fullpath,
206251881Speter                     const svn_delta_editor_t *editor,
207251881Speter                     void *edit_baton,
208251881Speter                     svn_repos_authz_func_t authz_read_func,
209251881Speter                     void *authz_read_baton,
210251881Speter                     svn_boolean_t text_deltas,
211251881Speter                     svn_depth_t depth,
212251881Speter                     svn_boolean_t entry_props,
213251881Speter                     svn_boolean_t ignore_ancestry,
214251881Speter                     apr_pool_t *pool)
215251881Speter{
216251881Speter  void *root_baton = NULL;
217251881Speter  struct context c;
218251881Speter  const char *src_fullpath;
219251881Speter  svn_node_kind_t src_kind, tgt_kind;
220251881Speter  svn_revnum_t rootrev;
221299742Sdim  svn_fs_node_relation_t relation;
222251881Speter  const char *authz_root_path;
223251881Speter
224251881Speter  /* SRC_PARENT_DIR must be valid. */
225251881Speter  if (src_parent_dir)
226251881Speter    src_parent_dir = svn_relpath_canonicalize(src_parent_dir, pool);
227251881Speter  else
228299742Sdim    return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, 0,
229299742Sdim                            "Invalid source parent directory '(null)'");
230251881Speter
231251881Speter  /* TGT_FULLPATH must be valid. */
232251881Speter  if (tgt_fullpath)
233251881Speter    tgt_fullpath = svn_relpath_canonicalize(tgt_fullpath, pool);
234251881Speter  else
235251881Speter    return svn_error_create(SVN_ERR_FS_PATH_SYNTAX, 0,
236251881Speter                            _("Invalid target path"));
237251881Speter
238251881Speter  if (depth == svn_depth_exclude)
239251881Speter    return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL,
240251881Speter                            _("Delta depth 'exclude' not supported"));
241251881Speter
242251881Speter  /* Calculate the fs path implicitly used for editor->open_root, so
243251881Speter     we can do an authz check on that path first. */
244251881Speter  if (*src_entry)
245251881Speter    authz_root_path = svn_relpath_dirname(tgt_fullpath, pool);
246251881Speter  else
247251881Speter    authz_root_path = tgt_fullpath;
248251881Speter
249251881Speter  /* Construct the full path of the source item. */
250251881Speter  src_fullpath = svn_relpath_join(src_parent_dir, src_entry, pool);
251251881Speter
252251881Speter  /* Get the node kinds for the source and target paths.  */
253251881Speter  SVN_ERR(svn_fs_check_path(&tgt_kind, tgt_root, tgt_fullpath, pool));
254251881Speter  SVN_ERR(svn_fs_check_path(&src_kind, src_root, src_fullpath, pool));
255251881Speter
256251881Speter  /* If neither of our paths exists, we don't really have anything to do. */
257251881Speter  if ((tgt_kind == svn_node_none) && (src_kind == svn_node_none))
258251881Speter    goto cleanup;
259251881Speter
260251881Speter  /* If either the source or the target is a non-directory, we
261251881Speter     require that a SRC_ENTRY be supplied. */
262251881Speter  if ((! *src_entry) && ((src_kind != svn_node_dir)
263251881Speter                         || tgt_kind != svn_node_dir))
264251881Speter    return svn_error_create
265251881Speter      (SVN_ERR_FS_PATH_SYNTAX, 0,
266251881Speter       _("Invalid editor anchoring; at least one of the "
267251881Speter         "input paths is not a directory and there was no source entry"));
268251881Speter
269251881Speter  /* Set the global target revision if one can be determined. */
270251881Speter  if (svn_fs_is_revision_root(tgt_root))
271251881Speter    {
272251881Speter      SVN_ERR(editor->set_target_revision
273251881Speter              (edit_baton, svn_fs_revision_root_revision(tgt_root), pool));
274251881Speter    }
275251881Speter  else if (svn_fs_is_txn_root(tgt_root))
276251881Speter    {
277251881Speter      SVN_ERR(editor->set_target_revision
278251881Speter              (edit_baton, svn_fs_txn_root_base_revision(tgt_root), pool));
279251881Speter    }
280251881Speter
281251881Speter  /* Setup our pseudo-global structure here.  We need these variables
282251881Speter     throughout the deltafication process, so pass them around by
283251881Speter     reference to all the helper functions. */
284251881Speter  c.editor = editor;
285251881Speter  c.source_root = src_root;
286251881Speter  c.target_root = tgt_root;
287251881Speter  c.authz_read_func = authz_read_func;
288251881Speter  c.authz_read_baton = authz_read_baton;
289251881Speter  c.text_deltas = text_deltas;
290251881Speter  c.entry_props = entry_props;
291251881Speter  c.ignore_ancestry = ignore_ancestry;
292251881Speter
293251881Speter  /* Get our editor root's revision. */
294251881Speter  rootrev = get_path_revision(src_root, src_parent_dir, pool);
295251881Speter
296251881Speter  /* If one or the other of our paths doesn't exist, we have to handle
297251881Speter     those cases specially. */
298251881Speter  if (tgt_kind == svn_node_none)
299251881Speter    {
300251881Speter      /* Caller thinks that target still exists, but it doesn't.
301251881Speter         So transform their source path to "nothing" by deleting it. */
302251881Speter      SVN_ERR(authz_root_check(tgt_root, authz_root_path,
303251881Speter                               authz_read_func, authz_read_baton, pool));
304251881Speter      SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton));
305251881Speter      SVN_ERR(delete(&c, root_baton, src_entry, pool));
306251881Speter      goto cleanup;
307251881Speter    }
308251881Speter  if (src_kind == svn_node_none)
309251881Speter    {
310251881Speter      /* The source path no longer exists, but the target does.
311251881Speter         So transform "nothing" into "something" by adding. */
312251881Speter      SVN_ERR(authz_root_check(tgt_root, authz_root_path,
313251881Speter                               authz_read_func, authz_read_baton, pool));
314251881Speter      SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton));
315251881Speter      SVN_ERR(add_file_or_dir(&c, root_baton, depth, tgt_fullpath,
316251881Speter                              src_entry, tgt_kind, pool));
317251881Speter      goto cleanup;
318251881Speter    }
319251881Speter
320251881Speter  /* Get and compare the node IDs for the source and target. */
321299742Sdim  SVN_ERR(svn_fs_node_relation(&relation, tgt_root, tgt_fullpath,
322299742Sdim                               src_root, src_fullpath, pool));
323251881Speter
324299742Sdim  if (relation == svn_fs_node_unchanged)
325251881Speter    {
326251881Speter      /* They are the same node!  No-op (you gotta love those). */
327251881Speter      goto cleanup;
328251881Speter    }
329251881Speter  else if (*src_entry)
330251881Speter    {
331251881Speter      /* If the nodes have different kinds, we must delete the one and
332251881Speter         add the other.  Also, if they are completely unrelated and
333251881Speter         our caller is interested in relatedness, we do the same thing. */
334251881Speter      if ((src_kind != tgt_kind)
335299742Sdim          || ((relation == svn_fs_node_unrelated) && (! ignore_ancestry)))
336251881Speter        {
337251881Speter          SVN_ERR(authz_root_check(tgt_root, authz_root_path,
338251881Speter                                   authz_read_func, authz_read_baton, pool));
339251881Speter          SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton));
340251881Speter          SVN_ERR(delete(&c, root_baton, src_entry, pool));
341251881Speter          SVN_ERR(add_file_or_dir(&c, root_baton, depth, tgt_fullpath,
342251881Speter                                  src_entry, tgt_kind, pool));
343251881Speter        }
344251881Speter      /* Otherwise, we just replace the one with the other. */
345251881Speter      else
346251881Speter        {
347251881Speter          SVN_ERR(authz_root_check(tgt_root, authz_root_path,
348251881Speter                                   authz_read_func, authz_read_baton, pool));
349251881Speter          SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton));
350251881Speter          SVN_ERR(replace_file_or_dir(&c, root_baton, depth, src_fullpath,
351251881Speter                                      tgt_fullpath, src_entry,
352251881Speter                                      tgt_kind, pool));
353251881Speter        }
354251881Speter    }
355251881Speter  else
356251881Speter    {
357251881Speter      /* There is no entry given, so delta the whole parent directory. */
358251881Speter      SVN_ERR(authz_root_check(tgt_root, authz_root_path,
359251881Speter                               authz_read_func, authz_read_baton, pool));
360251881Speter      SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton));
361251881Speter      SVN_ERR(delta_dirs(&c, root_baton, depth, src_fullpath,
362251881Speter                         tgt_fullpath, "", pool));
363251881Speter    }
364251881Speter
365251881Speter cleanup:
366251881Speter
367251881Speter  /* Make sure we close the root directory if we opened one above. */
368251881Speter  if (root_baton)
369251881Speter    SVN_ERR(editor->close_directory(root_baton, pool));
370251881Speter
371251881Speter  /* Close the edit. */
372251881Speter  return editor->close_edit(edit_baton, pool);
373251881Speter}
374251881Speter
375251881Speter
376251881Speter/* Retrieving the base revision from the path/revision hash.  */
377251881Speter
378251881Speter
379251881Speterstatic svn_revnum_t
380251881Speterget_path_revision(svn_fs_root_t *root,
381251881Speter                  const char *path,
382251881Speter                  apr_pool_t *pool)
383251881Speter{
384251881Speter  svn_revnum_t revision;
385251881Speter  svn_error_t *err;
386251881Speter
387251881Speter  /* Easy out -- if ROOT is a revision root, we can use the revision
388251881Speter     that it's a root of. */
389251881Speter  if (svn_fs_is_revision_root(root))
390251881Speter    return svn_fs_revision_root_revision(root);
391251881Speter
392251881Speter  /* Else, this must be a transaction root, so ask the filesystem in
393251881Speter     what revision this path was created. */
394251881Speter  if ((err = svn_fs_node_created_rev(&revision, root, path, pool)))
395251881Speter    {
396251881Speter      revision = SVN_INVALID_REVNUM;
397251881Speter      svn_error_clear(err);
398251881Speter    }
399251881Speter
400251881Speter  /* If we don't get back a valid revision, this path is mutable in
401251881Speter     the transaction.  We should probably examine the node on which it
402251881Speter     is based, doable by querying for the node-id of the path, and
403251881Speter     then examining that node-id's predecessor.  ### This predecessor
404251881Speter     determination isn't exposed via the FS public API right now, so
405251881Speter     for now, we'll just return the SVN_INVALID_REVNUM. */
406251881Speter  return revision;
407251881Speter}
408251881Speter
409251881Speter
410251881Speter/* proplist_change_fn_t property changing functions.  */
411251881Speter
412251881Speter
413251881Speter/* Call the directory property-setting function of C->editor to set
414251881Speter   the property NAME to given VALUE on the OBJECT passed to this
415251881Speter   function. */
416251881Speterstatic svn_error_t *
417251881Speterchange_dir_prop(struct context *c,
418251881Speter                void *object,
419251881Speter                const char *name,
420251881Speter                const svn_string_t *value,
421251881Speter                apr_pool_t *pool)
422251881Speter{
423251881Speter  return c->editor->change_dir_prop(object, name, value, pool);
424251881Speter}
425251881Speter
426251881Speter
427251881Speter/* Call the file property-setting function of C->editor to set the
428251881Speter   property NAME to given VALUE on the OBJECT passed to this
429251881Speter   function. */
430251881Speterstatic svn_error_t *
431251881Speterchange_file_prop(struct context *c,
432251881Speter                 void *object,
433251881Speter                 const char *name,
434251881Speter                 const svn_string_t *value,
435251881Speter                 apr_pool_t *pool)
436251881Speter{
437251881Speter  return c->editor->change_file_prop(object, name, value, pool);
438251881Speter}
439251881Speter
440251881Speter
441251881Speter
442251881Speter
443251881Speter/* Constructing deltas for properties of files and directories.  */
444251881Speter
445251881Speter
446251881Speter/* Generate the appropriate property editing calls to turn the
447251881Speter   properties of SOURCE_PATH into those of TARGET_PATH.  If
448251881Speter   SOURCE_PATH is NULL, this is an add, so assume the target starts
449251881Speter   with no properties.  Pass OBJECT on to the editor function wrapper
450251881Speter   CHANGE_FN. */
451251881Speterstatic svn_error_t *
452251881Speterdelta_proplists(struct context *c,
453251881Speter                const char *source_path,
454251881Speter                const char *target_path,
455251881Speter                proplist_change_fn_t *change_fn,
456251881Speter                void *object,
457251881Speter                apr_pool_t *pool)
458251881Speter{
459251881Speter  apr_hash_t *s_props = 0;
460251881Speter  apr_hash_t *t_props = 0;
461251881Speter  apr_pool_t *subpool;
462251881Speter  apr_array_header_t *prop_diffs;
463251881Speter  int i;
464251881Speter
465251881Speter  SVN_ERR_ASSERT(target_path);
466251881Speter
467251881Speter  /* Make a subpool for local allocations. */
468251881Speter  subpool = svn_pool_create(pool);
469251881Speter
470251881Speter  /* If we're supposed to send entry props for all non-deleted items,
471251881Speter     here we go! */
472251881Speter  if (c->entry_props)
473251881Speter    {
474251881Speter      svn_revnum_t committed_rev = SVN_INVALID_REVNUM;
475251881Speter      svn_string_t *cr_str = NULL;
476251881Speter      svn_string_t *committed_date = NULL;
477251881Speter      svn_string_t *last_author = NULL;
478251881Speter
479251881Speter      /* Get the CR and two derivative props. ### check for error returns. */
480251881Speter      SVN_ERR(svn_fs_node_created_rev(&committed_rev, c->target_root,
481251881Speter                                      target_path, subpool));
482251881Speter      if (SVN_IS_VALID_REVNUM(committed_rev))
483251881Speter        {
484251881Speter          svn_fs_t *fs = svn_fs_root_fs(c->target_root);
485251881Speter          apr_hash_t *r_props;
486251881Speter          const char *uuid;
487251881Speter
488251881Speter          /* Transmit the committed-rev. */
489251881Speter          cr_str = svn_string_createf(subpool, "%ld",
490251881Speter                                      committed_rev);
491251881Speter          SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_COMMITTED_REV,
492251881Speter                            cr_str, subpool));
493251881Speter
494251881Speter          SVN_ERR(svn_fs_revision_proplist(&r_props, fs, committed_rev,
495251881Speter                                           pool));
496251881Speter
497251881Speter          /* Transmit the committed-date. */
498251881Speter          committed_date = svn_hash_gets(r_props, SVN_PROP_REVISION_DATE);
499251881Speter          if (committed_date || source_path)
500251881Speter            {
501251881Speter              SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_COMMITTED_DATE,
502251881Speter                                committed_date, subpool));
503251881Speter            }
504251881Speter
505251881Speter          /* Transmit the last-author. */
506251881Speter          last_author = svn_hash_gets(r_props, SVN_PROP_REVISION_AUTHOR);
507251881Speter          if (last_author || source_path)
508251881Speter            {
509251881Speter              SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_LAST_AUTHOR,
510251881Speter                                last_author, subpool));
511251881Speter            }
512251881Speter
513251881Speter          /* Transmit the UUID. */
514251881Speter          SVN_ERR(svn_fs_get_uuid(fs, &uuid, subpool));
515251881Speter          SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_UUID,
516251881Speter                            svn_string_create(uuid, subpool),
517251881Speter                            subpool));
518251881Speter        }
519251881Speter    }
520251881Speter
521251881Speter  if (source_path)
522251881Speter    {
523251881Speter      svn_boolean_t changed;
524251881Speter
525251881Speter      /* Is this deltification worth our time? */
526299742Sdim      SVN_ERR(svn_fs_props_different(&changed, c->target_root, target_path,
527299742Sdim                                     c->source_root, source_path, subpool));
528251881Speter      if (! changed)
529251881Speter        goto cleanup;
530251881Speter
531251881Speter      /* If so, go ahead and get the source path's properties. */
532251881Speter      SVN_ERR(svn_fs_node_proplist(&s_props, c->source_root,
533251881Speter                                   source_path, subpool));
534251881Speter    }
535251881Speter  else
536251881Speter    {
537251881Speter      s_props = apr_hash_make(subpool);
538251881Speter    }
539251881Speter
540251881Speter  /* Get the target path's properties */
541251881Speter  SVN_ERR(svn_fs_node_proplist(&t_props, c->target_root,
542251881Speter                               target_path, subpool));
543251881Speter
544251881Speter  /* Now transmit the differences. */
545251881Speter  SVN_ERR(svn_prop_diffs(&prop_diffs, t_props, s_props, subpool));
546251881Speter  for (i = 0; i < prop_diffs->nelts; i++)
547251881Speter    {
548251881Speter      const svn_prop_t *pc = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t);
549251881Speter      SVN_ERR(change_fn(c, object, pc->name, pc->value, subpool));
550251881Speter    }
551251881Speter
552251881Speter cleanup:
553251881Speter  /* Destroy local subpool. */
554251881Speter  svn_pool_destroy(subpool);
555251881Speter
556251881Speter  return SVN_NO_ERROR;
557251881Speter}
558251881Speter
559251881Speter
560251881Speter
561251881Speter
562251881Speter/* Constructing deltas for file contents.  */
563251881Speter
564251881Speter
565251881Speter/* Change the contents of FILE_BATON in C->editor, according to the
566251881Speter   text delta from DELTA_STREAM.  Pass BASE_CHECKSUM along to
567251881Speter   C->editor->apply_textdelta. */
568251881Speterstatic svn_error_t *
569251881Spetersend_text_delta(struct context *c,
570251881Speter                void *file_baton,
571251881Speter                const char *base_checksum,
572251881Speter                svn_txdelta_stream_t *delta_stream,
573251881Speter                apr_pool_t *pool)
574251881Speter{
575251881Speter  svn_txdelta_window_handler_t delta_handler;
576251881Speter  void *delta_handler_baton;
577251881Speter
578251881Speter  /* Get a handler that will apply the delta to the file.  */
579251881Speter  SVN_ERR(c->editor->apply_textdelta
580251881Speter          (file_baton, base_checksum, pool,
581251881Speter           &delta_handler, &delta_handler_baton));
582251881Speter
583251881Speter  if (c->text_deltas && delta_stream)
584251881Speter    {
585251881Speter      /* Deliver the delta stream to the file.  */
586251881Speter      return svn_txdelta_send_txstream(delta_stream,
587251881Speter                                       delta_handler,
588251881Speter                                       delta_handler_baton,
589251881Speter                                       pool);
590251881Speter    }
591251881Speter  else
592251881Speter    {
593251881Speter      /* The caller doesn't want text delta data.  Just send a single
594251881Speter         NULL window. */
595251881Speter      return delta_handler(NULL, delta_handler_baton);
596251881Speter    }
597251881Speter}
598251881Speter
599251881Spetersvn_error_t *
600251881Spetersvn_repos__compare_files(svn_boolean_t *changed_p,
601251881Speter                         svn_fs_root_t *root1,
602251881Speter                         const char *path1,
603251881Speter                         svn_fs_root_t *root2,
604251881Speter                         const char *path2,
605251881Speter                         apr_pool_t *pool)
606251881Speter{
607299742Sdim  return svn_error_trace(svn_fs_contents_different(changed_p, root1, path1,
608299742Sdim                                                   root2, path2, pool));
609251881Speter}
610251881Speter
611251881Speter
612251881Speter/* Make the appropriate edits on FILE_BATON to change its contents and
613251881Speter   properties from those in SOURCE_PATH to those in TARGET_PATH. */
614251881Speterstatic svn_error_t *
615251881Speterdelta_files(struct context *c,
616251881Speter            void *file_baton,
617251881Speter            const char *source_path,
618251881Speter            const char *target_path,
619251881Speter            apr_pool_t *pool)
620251881Speter{
621251881Speter  apr_pool_t *subpool;
622251881Speter  svn_boolean_t changed = TRUE;
623251881Speter
624251881Speter  SVN_ERR_ASSERT(target_path);
625251881Speter
626251881Speter  /* Make a subpool for local allocations. */
627251881Speter  subpool = svn_pool_create(pool);
628251881Speter
629251881Speter  /* Compare the files' property lists.  */
630251881Speter  SVN_ERR(delta_proplists(c, source_path, target_path,
631251881Speter                          change_file_prop, file_baton, subpool));
632251881Speter
633251881Speter  if (source_path)
634251881Speter    {
635299742Sdim      SVN_ERR(svn_fs_contents_different(&changed,
636251881Speter                                        c->target_root, target_path,
637251881Speter                                        c->source_root, source_path,
638251881Speter                                        subpool));
639251881Speter    }
640251881Speter  else
641251881Speter    {
642251881Speter      /* If there isn't a source path, this is an add, which
643251881Speter         necessarily has textual mods. */
644251881Speter    }
645251881Speter
646251881Speter  /* If there is a change, and the context indicates that we should
647251881Speter     care about it, then hand it off to a delta stream.  */
648251881Speter  if (changed)
649251881Speter    {
650251881Speter      svn_txdelta_stream_t *delta_stream = NULL;
651251881Speter      svn_checksum_t *source_checksum;
652251881Speter      const char *source_hex_digest = NULL;
653251881Speter
654251881Speter      if (c->text_deltas)
655251881Speter        {
656251881Speter          /* Get a delta stream turning an empty file into one having
657251881Speter             TARGET_PATH's contents.  */
658251881Speter          SVN_ERR(svn_fs_get_file_delta_stream
659251881Speter                  (&delta_stream,
660251881Speter                   source_path ? c->source_root : NULL,
661251881Speter                   source_path ? source_path : NULL,
662251881Speter                   c->target_root, target_path, subpool));
663251881Speter        }
664251881Speter
665251881Speter      if (source_path)
666251881Speter        {
667251881Speter          SVN_ERR(svn_fs_file_checksum(&source_checksum, svn_checksum_md5,
668251881Speter                                       c->source_root, source_path, TRUE,
669251881Speter                                       subpool));
670251881Speter
671251881Speter          source_hex_digest = svn_checksum_to_cstring(source_checksum,
672251881Speter                                                      subpool);
673251881Speter        }
674251881Speter
675251881Speter      SVN_ERR(send_text_delta(c, file_baton, source_hex_digest,
676251881Speter                              delta_stream, subpool));
677251881Speter    }
678251881Speter
679251881Speter  /* Cleanup. */
680251881Speter  svn_pool_destroy(subpool);
681251881Speter
682251881Speter  return SVN_NO_ERROR;
683251881Speter}
684251881Speter
685251881Speter
686251881Speter
687251881Speter
688251881Speter/* Generic directory deltafication routines.  */
689251881Speter
690251881Speter
691251881Speter/* Emit a delta to delete the entry named TARGET_ENTRY from DIR_BATON.  */
692251881Speterstatic svn_error_t *
693251881Speterdelete(struct context *c,
694251881Speter       void *dir_baton,
695251881Speter       const char *edit_path,
696251881Speter       apr_pool_t *pool)
697251881Speter{
698251881Speter  return c->editor->delete_entry(edit_path, SVN_INVALID_REVNUM,
699251881Speter                                 dir_baton, pool);
700251881Speter}
701251881Speter
702251881Speter
703251881Speter/* If authorized, emit a delta to create the entry named TARGET_ENTRY
704251881Speter   at the location EDIT_PATH.  If not authorized, indicate that
705251881Speter   EDIT_PATH is absent.  Pass DIR_BATON through to editor functions
706251881Speter   that require it.  DEPTH is the depth from this point downward. */
707251881Speterstatic svn_error_t *
708251881Speteradd_file_or_dir(struct context *c, void *dir_baton,
709251881Speter                svn_depth_t depth,
710251881Speter                const char *target_path,
711251881Speter                const char *edit_path,
712251881Speter                svn_node_kind_t tgt_kind,
713251881Speter                apr_pool_t *pool)
714251881Speter{
715251881Speter  struct context *context = c;
716251881Speter  svn_boolean_t allowed;
717251881Speter
718251881Speter  SVN_ERR_ASSERT(target_path && edit_path);
719251881Speter
720251881Speter  if (c->authz_read_func)
721251881Speter    {
722251881Speter      SVN_ERR(c->authz_read_func(&allowed, c->target_root, target_path,
723251881Speter                                 c->authz_read_baton, pool));
724251881Speter      if (!allowed)
725251881Speter        return absent_file_or_dir(c, dir_baton, edit_path, tgt_kind, pool);
726251881Speter    }
727251881Speter
728251881Speter  if (tgt_kind == svn_node_dir)
729251881Speter    {
730251881Speter      void *subdir_baton;
731251881Speter
732251881Speter      SVN_ERR(context->editor->add_directory(edit_path, dir_baton, NULL,
733251881Speter                                             SVN_INVALID_REVNUM, pool,
734251881Speter                                             &subdir_baton));
735251881Speter      SVN_ERR(delta_dirs(context, subdir_baton, MAYBE_DEMOTE_DEPTH(depth),
736251881Speter                         NULL, target_path, edit_path, pool));
737251881Speter      return context->editor->close_directory(subdir_baton, pool);
738251881Speter    }
739251881Speter  else
740251881Speter    {
741251881Speter      void *file_baton;
742251881Speter      svn_checksum_t *checksum;
743251881Speter
744251881Speter      SVN_ERR(context->editor->add_file(edit_path, dir_baton,
745251881Speter                                        NULL, SVN_INVALID_REVNUM, pool,
746251881Speter                                        &file_baton));
747251881Speter      SVN_ERR(delta_files(context, file_baton, NULL, target_path, pool));
748251881Speter      SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
749251881Speter                                   context->target_root, target_path,
750251881Speter                                   TRUE, pool));
751251881Speter      return context->editor->close_file
752251881Speter             (file_baton, svn_checksum_to_cstring(checksum, pool), pool);
753251881Speter    }
754251881Speter}
755251881Speter
756251881Speter
757251881Speter/* If authorized, emit a delta to modify EDIT_PATH with the changes
758251881Speter   from SOURCE_PATH to TARGET_PATH.  If not authorized, indicate that
759251881Speter   EDIT_PATH is absent.  Pass DIR_BATON through to editor functions
760251881Speter   that require it.  DEPTH is the depth from this point downward. */
761251881Speterstatic svn_error_t *
762251881Speterreplace_file_or_dir(struct context *c,
763251881Speter                    void *dir_baton,
764251881Speter                    svn_depth_t depth,
765251881Speter                    const char *source_path,
766251881Speter                    const char *target_path,
767251881Speter                    const char *edit_path,
768251881Speter                    svn_node_kind_t tgt_kind,
769251881Speter                    apr_pool_t *pool)
770251881Speter{
771251881Speter  svn_revnum_t base_revision = SVN_INVALID_REVNUM;
772251881Speter  svn_boolean_t allowed;
773251881Speter
774251881Speter  SVN_ERR_ASSERT(target_path && source_path && edit_path);
775251881Speter
776251881Speter  if (c->authz_read_func)
777251881Speter    {
778251881Speter      SVN_ERR(c->authz_read_func(&allowed, c->target_root, target_path,
779251881Speter                                 c->authz_read_baton, pool));
780251881Speter      if (!allowed)
781251881Speter        return absent_file_or_dir(c, dir_baton, edit_path, tgt_kind, pool);
782251881Speter    }
783251881Speter
784251881Speter  /* Get the base revision for the entry from the hash. */
785251881Speter  base_revision = get_path_revision(c->source_root, source_path, pool);
786251881Speter
787251881Speter  if (tgt_kind == svn_node_dir)
788251881Speter    {
789251881Speter      void *subdir_baton;
790251881Speter
791251881Speter      SVN_ERR(c->editor->open_directory(edit_path, dir_baton,
792251881Speter                                        base_revision, pool,
793251881Speter                                        &subdir_baton));
794251881Speter      SVN_ERR(delta_dirs(c, subdir_baton, MAYBE_DEMOTE_DEPTH(depth),
795251881Speter                         source_path, target_path, edit_path, pool));
796251881Speter      return c->editor->close_directory(subdir_baton, pool);
797251881Speter    }
798251881Speter  else
799251881Speter    {
800251881Speter      void *file_baton;
801251881Speter      svn_checksum_t *checksum;
802251881Speter
803251881Speter      SVN_ERR(c->editor->open_file(edit_path, dir_baton, base_revision,
804251881Speter                                   pool, &file_baton));
805251881Speter      SVN_ERR(delta_files(c, file_baton, source_path, target_path, pool));
806251881Speter      SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
807251881Speter                                   c->target_root, target_path, TRUE,
808251881Speter                                   pool));
809251881Speter      return c->editor->close_file
810251881Speter             (file_baton, svn_checksum_to_cstring(checksum, pool), pool);
811251881Speter    }
812251881Speter}
813251881Speter
814251881Speter
815251881Speter/* In directory DIR_BATON, indicate that EDIT_PATH  (relative to the
816251881Speter   edit root) is absent by invoking C->editor->absent_directory or
817251881Speter   C->editor->absent_file (depending on TGT_KIND). */
818251881Speterstatic svn_error_t *
819251881Speterabsent_file_or_dir(struct context *c,
820251881Speter                   void *dir_baton,
821251881Speter                   const char *edit_path,
822251881Speter                   svn_node_kind_t tgt_kind,
823251881Speter                   apr_pool_t *pool)
824251881Speter{
825251881Speter  SVN_ERR_ASSERT(edit_path);
826251881Speter
827251881Speter  if (tgt_kind == svn_node_dir)
828251881Speter    return c->editor->absent_directory(edit_path, dir_baton, pool);
829251881Speter  else
830251881Speter    return c->editor->absent_file(edit_path, dir_baton, pool);
831251881Speter}
832251881Speter
833251881Speter
834251881Speter/* Emit deltas to turn SOURCE_PATH into TARGET_PATH.  Assume that
835251881Speter   DIR_BATON represents the directory we're constructing to the editor
836251881Speter   in the context C.  */
837251881Speterstatic svn_error_t *
838251881Speterdelta_dirs(struct context *c,
839251881Speter           void *dir_baton,
840251881Speter           svn_depth_t depth,
841251881Speter           const char *source_path,
842251881Speter           const char *target_path,
843251881Speter           const char *edit_path,
844251881Speter           apr_pool_t *pool)
845251881Speter{
846251881Speter  apr_hash_t *s_entries = 0, *t_entries = 0;
847251881Speter  apr_hash_index_t *hi;
848251881Speter  apr_pool_t *subpool;
849251881Speter
850251881Speter  SVN_ERR_ASSERT(target_path);
851251881Speter
852251881Speter  /* Compare the property lists.  */
853251881Speter  SVN_ERR(delta_proplists(c, source_path, target_path,
854251881Speter                          change_dir_prop, dir_baton, pool));
855251881Speter
856251881Speter  /* Get the list of entries in each of source and target.  */
857251881Speter  SVN_ERR(svn_fs_dir_entries(&t_entries, c->target_root,
858251881Speter                             target_path, pool));
859251881Speter  if (source_path)
860251881Speter    SVN_ERR(svn_fs_dir_entries(&s_entries, c->source_root,
861251881Speter                               source_path, pool));
862251881Speter
863251881Speter  /* Make a subpool for local allocations. */
864251881Speter  subpool = svn_pool_create(pool);
865251881Speter
866251881Speter  /* Loop over the hash of entries in the target, searching for its
867251881Speter     partner in the source.  If we find the matching partner entry,
868251881Speter     use editor calls to replace the one in target with a new version
869251881Speter     if necessary, then remove that entry from the source entries
870251881Speter     hash.  If we can't find a related node in the source, we use
871251881Speter     editor calls to add the entry as a new item in the target.
872251881Speter     Having handled all the entries that exist in target, any entries
873251881Speter     still remaining the source entries hash represent entries that no
874251881Speter     longer exist in target.  Use editor calls to delete those entries
875251881Speter     from the target tree. */
876251881Speter  for (hi = apr_hash_first(pool, t_entries); hi; hi = apr_hash_next(hi))
877251881Speter    {
878299742Sdim      const void *key = apr_hash_this_key(hi);
879299742Sdim      apr_ssize_t klen = apr_hash_this_key_len(hi);
880299742Sdim      const svn_fs_dirent_t *t_entry = apr_hash_this_val(hi);
881299742Sdim      const svn_fs_dirent_t *s_entry;
882251881Speter      const char *t_fullpath;
883251881Speter      const char *e_fullpath;
884251881Speter      const char *s_fullpath;
885251881Speter      svn_node_kind_t tgt_kind;
886251881Speter
887251881Speter      /* Clear out our subpool for the next iteration... */
888251881Speter      svn_pool_clear(subpool);
889251881Speter
890251881Speter      tgt_kind = t_entry->kind;
891251881Speter      t_fullpath = svn_relpath_join(target_path, t_entry->name, subpool);
892251881Speter      e_fullpath = svn_relpath_join(edit_path, t_entry->name, subpool);
893251881Speter
894251881Speter      /* Can we find something with the same name in the source
895251881Speter         entries hash? */
896251881Speter      if (s_entries && ((s_entry = apr_hash_get(s_entries, key, klen)) != 0))
897251881Speter        {
898251881Speter          svn_node_kind_t src_kind;
899251881Speter
900251881Speter          s_fullpath = svn_relpath_join(source_path, t_entry->name, subpool);
901251881Speter          src_kind = s_entry->kind;
902251881Speter
903251881Speter          if (depth == svn_depth_infinity
904251881Speter              || src_kind != svn_node_dir
905251881Speter              || (src_kind == svn_node_dir
906251881Speter                  && depth == svn_depth_immediates))
907251881Speter            {
908251881Speter              /* Use svn_fs_compare_ids() to compare our current
909251881Speter                 source and target ids.
910251881Speter
911251881Speter                    0: means they are the same id, and this is a noop.
912251881Speter                   -1: means they are unrelated, so we have to delete the
913251881Speter                       old one and add the new one.
914251881Speter                    1: means the nodes are related through ancestry, so go
915251881Speter                       ahead and do the replace directly.  */
916251881Speter              int distance = svn_fs_compare_ids(s_entry->id, t_entry->id);
917251881Speter              if (distance == 0)
918251881Speter                {
919251881Speter                  /* no-op */
920251881Speter                }
921251881Speter              else if ((src_kind != tgt_kind)
922251881Speter                       || ((distance == -1) && (! c->ignore_ancestry)))
923251881Speter                {
924251881Speter                  SVN_ERR(delete(c, dir_baton, e_fullpath, subpool));
925251881Speter                  SVN_ERR(add_file_or_dir(c, dir_baton,
926251881Speter                                          MAYBE_DEMOTE_DEPTH(depth),
927251881Speter                                          t_fullpath, e_fullpath, tgt_kind,
928251881Speter                                          subpool));
929251881Speter                }
930251881Speter              else
931251881Speter                {
932251881Speter                  SVN_ERR(replace_file_or_dir(c, dir_baton,
933251881Speter                                              MAYBE_DEMOTE_DEPTH(depth),
934251881Speter                                              s_fullpath, t_fullpath,
935251881Speter                                              e_fullpath, tgt_kind,
936251881Speter                                              subpool));
937251881Speter                }
938251881Speter            }
939251881Speter
940251881Speter          /*  Remove the entry from the source_hash. */
941251881Speter          svn_hash_sets(s_entries, key, NULL);
942251881Speter        }
943251881Speter      else
944251881Speter        {
945251881Speter          if (depth == svn_depth_infinity
946251881Speter              || tgt_kind != svn_node_dir
947251881Speter              || (tgt_kind == svn_node_dir
948251881Speter                  && depth == svn_depth_immediates))
949251881Speter            {
950251881Speter              SVN_ERR(add_file_or_dir(c, dir_baton,
951251881Speter                                      MAYBE_DEMOTE_DEPTH(depth),
952251881Speter                                      t_fullpath, e_fullpath, tgt_kind,
953251881Speter                                      subpool));
954251881Speter            }
955251881Speter        }
956251881Speter    }
957251881Speter
958251881Speter  /* All that is left in the source entries hash are things that need
959251881Speter     to be deleted.  Delete them.  */
960251881Speter  if (s_entries)
961251881Speter    {
962251881Speter      for (hi = apr_hash_first(pool, s_entries); hi; hi = apr_hash_next(hi))
963251881Speter        {
964299742Sdim          const svn_fs_dirent_t *s_entry = apr_hash_this_val(hi);
965251881Speter          const char *e_fullpath;
966251881Speter          svn_node_kind_t src_kind;
967251881Speter
968251881Speter          /* Clear out our subpool for the next iteration... */
969251881Speter          svn_pool_clear(subpool);
970251881Speter
971251881Speter          src_kind = s_entry->kind;
972251881Speter          e_fullpath = svn_relpath_join(edit_path, s_entry->name, subpool);
973251881Speter
974251881Speter          /* Do we actually want to delete the dir if we're non-recursive? */
975251881Speter          if (depth == svn_depth_infinity
976251881Speter              || src_kind != svn_node_dir
977251881Speter              || (src_kind == svn_node_dir
978251881Speter                  && depth == svn_depth_immediates))
979251881Speter            {
980251881Speter              SVN_ERR(delete(c, dir_baton, e_fullpath, subpool));
981251881Speter            }
982251881Speter        }
983251881Speter    }
984251881Speter
985251881Speter  /* Destroy local allocation subpool. */
986251881Speter  svn_pool_destroy(subpool);
987251881Speter
988251881Speter  return SVN_NO_ERROR;
989251881Speter}
990