compat.c revision 299742
1/*
2 * compat.c :  Wrappers and callbacks for compatibility.
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#include <stddef.h>
25
26#include "svn_types.h"
27#include "svn_error.h"
28#include "svn_delta.h"
29#include "svn_sorts.h"
30#include "svn_dirent_uri.h"
31#include "svn_path.h"
32#include "svn_hash.h"
33#include "svn_props.h"
34#include "svn_pools.h"
35
36#include "svn_private_config.h"
37
38#include "private/svn_delta_private.h"
39#include "private/svn_sorts_private.h"
40#include "svn_private_config.h"
41
42
43struct file_rev_handler_wrapper_baton {
44  void *baton;
45  svn_file_rev_handler_old_t handler;
46};
47
48/* This implements svn_file_rev_handler_t. */
49static svn_error_t *
50file_rev_handler_wrapper(void *baton,
51                         const char *path,
52                         svn_revnum_t rev,
53                         apr_hash_t *rev_props,
54                         svn_boolean_t result_of_merge,
55                         svn_txdelta_window_handler_t *delta_handler,
56                         void **delta_baton,
57                         apr_array_header_t *prop_diffs,
58                         apr_pool_t *pool)
59{
60  struct file_rev_handler_wrapper_baton *fwb = baton;
61
62  if (fwb->handler)
63    return fwb->handler(fwb->baton,
64                        path,
65                        rev,
66                        rev_props,
67                        delta_handler,
68                        delta_baton,
69                        prop_diffs,
70                        pool);
71
72  return SVN_NO_ERROR;
73}
74
75void
76svn_compat_wrap_file_rev_handler(svn_file_rev_handler_t *handler2,
77                                 void **handler2_baton,
78                                 svn_file_rev_handler_old_t handler,
79                                 void *handler_baton,
80                                 apr_pool_t *pool)
81{
82  struct file_rev_handler_wrapper_baton *fwb = apr_pcalloc(pool, sizeof(*fwb));
83
84  /* Set the user provided old format callback in the baton. */
85  fwb->baton = handler_baton;
86  fwb->handler = handler;
87
88  *handler2_baton = fwb;
89  *handler2 = file_rev_handler_wrapper;
90}
91
92
93/* The following code maps the calls to a traditional delta editor to an
94 * Editorv2 editor.  It does this by keeping track of a lot of state, and
95 * then communicating that state to Ev2 upon closure of the file or dir (or
96 * edit).  Note that Ev2 calls add_symlink() and alter_symlink() are not
97 * present in the delta editor paradigm, so we never call them.
98 *
99 * The general idea here is that we have to see *all* the actions on a node's
100 * parent before we can process that node, which means we need to buffer a
101 * large amount of information in the dir batons, and then process it in the
102 * close_directory() handler.
103 *
104 * There are a few ways we alter the callback stream.  One is when unlocking
105 * paths.  To tell a client a path should be unlocked, the server sends a
106 * prop-del for the SVN_PROP_ENTRY_LOCK_TOKEN property.  This causes problems,
107 * since the client doesn't have this property in the first place, but the
108 * deletion has side effects (unlike deleting a non-existent regular property
109 * would).  To solve this, we introduce *another* function into the API, not
110 * a part of the Ev2 callbacks, but a companion which is used to register
111 * the unlock of a path.  See ev2_change_file_prop() for implemenation
112 * details.
113 */
114
115struct ev2_edit_baton
116{
117  svn_editor_t *editor;
118
119  apr_hash_t *changes;  /* REPOS_RELPATH -> struct change_node  */
120
121  apr_array_header_t *path_order;
122  int paths_processed;
123
124  /* For calculating relpaths from Ev1 copyfrom urls. */
125  const char *repos_root;
126  const char *base_relpath;
127
128  apr_pool_t *edit_pool;
129  struct svn_delta__extra_baton *exb;
130  svn_boolean_t closed;
131
132  svn_boolean_t *found_abs_paths; /* Did we strip an incoming '/' from the
133                                     paths?  */
134
135  svn_delta_fetch_props_func_t fetch_props_func;
136  void *fetch_props_baton;
137
138  svn_delta_fetch_base_func_t fetch_base_func;
139  void *fetch_base_baton;
140
141  svn_delta__unlock_func_t do_unlock;
142  void *unlock_baton;
143};
144
145struct ev2_dir_baton
146{
147  struct ev2_edit_baton *eb;
148  const char *path;
149  svn_revnum_t base_revision;
150
151  const char *copyfrom_relpath;
152  svn_revnum_t copyfrom_rev;
153};
154
155struct ev2_file_baton
156{
157  struct ev2_edit_baton *eb;
158  const char *path;
159  svn_revnum_t base_revision;
160  const char *delta_base;
161};
162
163enum restructure_action_t
164{
165  RESTRUCTURE_NONE = 0,
166  RESTRUCTURE_ADD,         /* add the node, maybe replacing. maybe copy  */
167  RESTRUCTURE_ADD_ABSENT,  /* add an absent node, possibly replacing  */
168  RESTRUCTURE_DELETE       /* delete this node  */
169};
170
171/* Records everything about how this node is to be changed.  */
172struct change_node
173{
174  /* what kind of (tree) restructure is occurring at this node?  */
175  enum restructure_action_t action;
176
177  svn_node_kind_t kind;  /* the NEW kind of this node  */
178
179  /* We need two revisions: one to specify the revision we are altering,
180     and a second to specify the revision to delete/replace. These are
181     mutually exclusive, but they need to be separate to ensure we don't
182     confuse the operation on this node. For example, we may delete a
183     node and replace it we use DELETING for REPLACES_REV, and ignore
184     the value placed into CHANGING when properties were set/changed
185     on the new node. Or we simply change a node (setting CHANGING),
186     and DELETING remains SVN_INVALID_REVNUM, indicating we are not
187     attempting to replace a node.  */
188  svn_revnum_t changing;
189  svn_revnum_t deleting;
190
191  apr_hash_t *props;  /* new/final set of props to apply  */
192
193  svn_boolean_t contents_changed; /* the file contents changed */
194  const char *contents_abspath;  /* file containing new fulltext  */
195  svn_checksum_t *checksum;  /* checksum of new fulltext  */
196
197  /* If COPYFROM_PATH is not NULL, then copy PATH@REV to this node.
198     RESTRUCTURE must be RESTRUCTURE_ADD.  */
199  const char *copyfrom_path;
200  svn_revnum_t copyfrom_rev;
201
202  /* Record whether an incoming propchange unlocked this node.  */
203  svn_boolean_t unlock;
204};
205
206
207static struct change_node *
208locate_change(struct ev2_edit_baton *eb,
209              const char *relpath)
210{
211  struct change_node *change = svn_hash_gets(eb->changes, relpath);
212
213  if (change != NULL)
214    return change;
215
216  /* Shift RELPATH into the proper pool, and record the observed order.  */
217  relpath = apr_pstrdup(eb->edit_pool, relpath);
218  APR_ARRAY_PUSH(eb->path_order, const char *) = relpath;
219
220  /* Return an empty change. Callers will tweak as needed.  */
221  change = apr_pcalloc(eb->edit_pool, sizeof(*change));
222  change->changing = SVN_INVALID_REVNUM;
223  change->deleting = SVN_INVALID_REVNUM;
224  change->kind = svn_node_unknown;
225
226  svn_hash_sets(eb->changes, relpath, change);
227
228  return change;
229}
230
231
232static svn_error_t *
233apply_propedit(struct ev2_edit_baton *eb,
234               const char *relpath,
235               svn_node_kind_t kind,
236               svn_revnum_t base_revision,
237               const char *name,
238               const svn_string_t *value,
239               apr_pool_t *scratch_pool)
240{
241  struct change_node *change = locate_change(eb, relpath);
242
243  SVN_ERR_ASSERT(change->kind == svn_node_unknown || change->kind == kind);
244  change->kind = kind;
245
246  /* We're now changing the node. Record the revision.  */
247  SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(change->changing)
248                 || change->changing == base_revision);
249  change->changing = base_revision;
250
251  if (change->props == NULL)
252    {
253      /* Fetch the original set of properties. We'll apply edits to create
254         the new/target set of properties.
255
256         If this is a copied/moved now, then the original properties come
257         from there. If the node has been added, it starts with empty props.
258         Otherwise, we get the properties from BASE.  */
259
260      if (change->copyfrom_path)
261        SVN_ERR(eb->fetch_props_func(&change->props,
262                                     eb->fetch_props_baton,
263                                     change->copyfrom_path,
264                                     change->copyfrom_rev,
265                                     eb->edit_pool, scratch_pool));
266      else if (change->action == RESTRUCTURE_ADD)
267        change->props = apr_hash_make(eb->edit_pool);
268      else
269        SVN_ERR(eb->fetch_props_func(&change->props,
270                                     eb->fetch_props_baton,
271                                     relpath, base_revision,
272                                     eb->edit_pool, scratch_pool));
273    }
274
275  if (value == NULL)
276    svn_hash_sets(change->props, name, NULL);
277  else
278    svn_hash_sets(change->props,
279                  apr_pstrdup(eb->edit_pool, name),
280                  svn_string_dup(value, eb->edit_pool));
281
282  return SVN_NO_ERROR;
283}
284
285
286/* Find all the paths which are immediate children of PATH and return their
287   basenames in a list. */
288static apr_array_header_t *
289get_children(struct ev2_edit_baton *eb,
290             const char *path,
291             apr_pool_t *pool)
292{
293  apr_array_header_t *children = apr_array_make(pool, 1, sizeof(const char *));
294  apr_hash_index_t *hi;
295
296  for (hi = apr_hash_first(pool, eb->changes); hi; hi = apr_hash_next(hi))
297    {
298      const char *repos_relpath = apr_hash_this_key(hi);
299      const char *child;
300
301      /* Find potential children. */
302      child = svn_relpath_skip_ancestor(path, repos_relpath);
303      if (!child || !*child)
304        continue;
305
306      /* If we have a path separator, it's a deep child, so just ignore it.
307         ### Is there an API we should be using for this? */
308      if (strchr(child, '/') != NULL)
309        continue;
310
311      APR_ARRAY_PUSH(children, const char *) = child;
312    }
313
314  return children;
315}
316
317
318static svn_error_t *
319process_actions(struct ev2_edit_baton *eb,
320                const char *repos_relpath,
321                const struct change_node *change,
322                apr_pool_t *scratch_pool)
323{
324  apr_hash_t *props = NULL;
325  svn_stream_t *contents = NULL;
326  svn_checksum_t *checksum = NULL;
327  svn_node_kind_t kind = svn_node_unknown;
328
329  SVN_ERR_ASSERT(change != NULL);
330
331  if (change->unlock)
332    SVN_ERR(eb->do_unlock(eb->unlock_baton, repos_relpath, scratch_pool));
333
334  if (change->action == RESTRUCTURE_DELETE)
335    {
336      /* If the action was left as RESTRUCTURE_DELETE, then a
337         replacement is not occurring. Just do the delete and bail.  */
338      SVN_ERR(svn_editor_delete(eb->editor, repos_relpath,
339                                change->deleting));
340
341      /* No further work possible on this node.  */
342      return SVN_NO_ERROR;
343    }
344  if (change->action == RESTRUCTURE_ADD_ABSENT)
345    {
346      SVN_ERR(svn_editor_add_absent(eb->editor, repos_relpath,
347                                    change->kind, change->deleting));
348
349      /* No further work possible on this node.  */
350      return SVN_NO_ERROR;
351    }
352
353  if (change->contents_changed)
354    {
355      /* We can only set text on files. */
356      /* ### validate we aren't overwriting KIND?  */
357      kind = svn_node_file;
358
359      if (change->contents_abspath)
360        {
361          /* ### the checksum might be in CHANGE->CHECKSUM  */
362          SVN_ERR(svn_io_file_checksum2(&checksum, change->contents_abspath,
363                                        svn_checksum_sha1, scratch_pool));
364          SVN_ERR(svn_stream_open_readonly(&contents, change->contents_abspath,
365                                           scratch_pool, scratch_pool));
366        }
367      else
368        {
369          contents = svn_stream_empty(scratch_pool);
370          checksum = svn_checksum_empty_checksum(svn_checksum_sha1,
371                                                 scratch_pool);
372        }
373    }
374
375  if (change->props != NULL)
376    {
377      /* ### validate we aren't overwriting KIND?  */
378      kind = change->kind;
379      props = change->props;
380    }
381
382  if (change->action == RESTRUCTURE_ADD)
383    {
384      /* An add might be a replace. Grab the revnum we're replacing.  */
385      svn_revnum_t replaces_rev = change->deleting;
386
387      kind = change->kind;
388
389      if (change->copyfrom_path != NULL)
390        {
391          SVN_ERR(svn_editor_copy(eb->editor, change->copyfrom_path,
392                                  change->copyfrom_rev,
393                                  repos_relpath, replaces_rev));
394          /* Fall through to possibly make changes post-copy.  */
395        }
396      else
397        {
398          /* If no properties were defined, then use an empty set.  */
399          if (props == NULL)
400            props = apr_hash_make(scratch_pool);
401
402          if (kind == svn_node_dir)
403            {
404              const apr_array_header_t *children;
405
406              children = get_children(eb, repos_relpath, scratch_pool);
407              SVN_ERR(svn_editor_add_directory(eb->editor, repos_relpath,
408                                               children, props,
409                                               replaces_rev));
410            }
411          else
412            {
413              /* If this file was added, but apply_txdelta() was not
414                 called (i.e., CONTENTS_CHANGED is FALSE), then we're adding
415                 an empty file.  */
416              if (change->contents_abspath == NULL)
417                {
418                  contents = svn_stream_empty(scratch_pool);
419                  checksum = svn_checksum_empty_checksum(svn_checksum_sha1,
420                                                         scratch_pool);
421                }
422
423              SVN_ERR(svn_editor_add_file(eb->editor, repos_relpath,
424                                          checksum, contents, props,
425                                          replaces_rev));
426            }
427
428          /* No further work possible on this node.  */
429          return SVN_NO_ERROR;
430        }
431    }
432
433#if 0
434  /* There *should* be work for this node. But it seems that isn't true
435     in some cases. Future investigation...  */
436  SVN_ERR_ASSERT(props || contents);
437#endif
438  if (props || contents)
439    {
440      /* Changes to properties or content should have indicated the revision
441         it was intending to change.
442
443         Oop. Not true. The node may be locally-added.  */
444#if 0
445      SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(change->changing));
446#endif
447
448      /* ### we need to gather up the target set of children  */
449
450      if (kind == svn_node_dir)
451        SVN_ERR(svn_editor_alter_directory(eb->editor, repos_relpath,
452                                           change->changing, NULL, props));
453      else
454        SVN_ERR(svn_editor_alter_file(eb->editor, repos_relpath,
455                                      change->changing,
456                                      checksum, contents, props));
457    }
458
459  return SVN_NO_ERROR;
460}
461
462static svn_error_t *
463run_ev2_actions(struct ev2_edit_baton *eb,
464                apr_pool_t *scratch_pool)
465{
466  apr_pool_t *iterpool;
467
468  iterpool = svn_pool_create(scratch_pool);
469
470  /* Possibly pick up where we left off. Ocassionally, we do some of these
471     as part of close_edit() and then some more as part of abort_edit()  */
472  for (; eb->paths_processed < eb->path_order->nelts; ++eb->paths_processed)
473    {
474      const char *repos_relpath = APR_ARRAY_IDX(eb->path_order,
475                                                eb->paths_processed,
476                                                const char *);
477      const struct change_node *change = svn_hash_gets(eb->changes,
478                                                       repos_relpath);
479
480      svn_pool_clear(iterpool);
481
482      SVN_ERR(process_actions(eb, repos_relpath, change, iterpool));
483    }
484  svn_pool_destroy(iterpool);
485
486  return SVN_NO_ERROR;
487}
488
489
490static const char *
491map_to_repos_relpath(struct ev2_edit_baton *eb,
492                     const char *path_or_url,
493                     apr_pool_t *result_pool)
494{
495  if (svn_path_is_url(path_or_url))
496    {
497      return svn_uri_skip_ancestor(eb->repos_root, path_or_url, result_pool);
498    }
499  else
500    {
501      return svn_relpath_join(eb->base_relpath,
502                              path_or_url[0] == '/'
503                                    ? path_or_url + 1 : path_or_url,
504                              result_pool);
505    }
506}
507
508
509static svn_error_t *
510ev2_set_target_revision(void *edit_baton,
511                        svn_revnum_t target_revision,
512                        apr_pool_t *scratch_pool)
513{
514  struct ev2_edit_baton *eb = edit_baton;
515
516  if (eb->exb->target_revision)
517    SVN_ERR(eb->exb->target_revision(eb->exb->baton, target_revision,
518                                     scratch_pool));
519
520  return SVN_NO_ERROR;
521}
522
523static svn_error_t *
524ev2_open_root(void *edit_baton,
525              svn_revnum_t base_revision,
526              apr_pool_t *result_pool,
527              void **root_baton)
528{
529  struct ev2_dir_baton *db = apr_pcalloc(result_pool, sizeof(*db));
530  struct ev2_edit_baton *eb = edit_baton;
531
532  db->eb = eb;
533  db->path = apr_pstrdup(eb->edit_pool, eb->base_relpath);
534  db->base_revision = base_revision;
535
536  *root_baton = db;
537
538  if (eb->exb->start_edit)
539    SVN_ERR(eb->exb->start_edit(eb->exb->baton, base_revision));
540
541  return SVN_NO_ERROR;
542}
543
544static svn_error_t *
545ev2_delete_entry(const char *path,
546                 svn_revnum_t revision,
547                 void *parent_baton,
548                 apr_pool_t *scratch_pool)
549{
550  struct ev2_dir_baton *pb = parent_baton;
551  svn_revnum_t base_revision;
552  const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool);
553  struct change_node *change = locate_change(pb->eb, relpath);
554
555  if (SVN_IS_VALID_REVNUM(revision))
556    base_revision = revision;
557  else
558    base_revision = pb->base_revision;
559
560  SVN_ERR_ASSERT(change->action == RESTRUCTURE_NONE);
561  change->action = RESTRUCTURE_DELETE;
562
563  SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(change->deleting)
564                 || change->deleting == base_revision);
565  change->deleting = base_revision;
566
567  return SVN_NO_ERROR;
568}
569
570static svn_error_t *
571ev2_add_directory(const char *path,
572                  void *parent_baton,
573                  const char *copyfrom_path,
574                  svn_revnum_t copyfrom_revision,
575                  apr_pool_t *result_pool,
576                  void **child_baton)
577{
578  /* ### fix this?  */
579  apr_pool_t *scratch_pool = result_pool;
580  struct ev2_dir_baton *pb = parent_baton;
581  struct ev2_dir_baton *cb = apr_pcalloc(result_pool, sizeof(*cb));
582  const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool);
583  struct change_node *change = locate_change(pb->eb, relpath);
584
585  /* ### assert that RESTRUCTURE is NONE or DELETE?  */
586  change->action = RESTRUCTURE_ADD;
587  change->kind = svn_node_dir;
588
589  cb->eb = pb->eb;
590  cb->path = apr_pstrdup(result_pool, relpath);
591  cb->base_revision = pb->base_revision;
592  *child_baton = cb;
593
594  if (!copyfrom_path)
595    {
596      if (pb->copyfrom_relpath)
597        {
598          const char *name = svn_relpath_basename(relpath, scratch_pool);
599          cb->copyfrom_relpath = svn_relpath_join(pb->copyfrom_relpath, name,
600                                                  result_pool);
601          cb->copyfrom_rev = pb->copyfrom_rev;
602        }
603    }
604  else
605    {
606      /* A copy */
607
608      change->copyfrom_path = map_to_repos_relpath(pb->eb, copyfrom_path,
609                                                   pb->eb->edit_pool);
610      change->copyfrom_rev = copyfrom_revision;
611
612      cb->copyfrom_relpath = change->copyfrom_path;
613      cb->copyfrom_rev = change->copyfrom_rev;
614    }
615
616  return SVN_NO_ERROR;
617}
618
619static svn_error_t *
620ev2_open_directory(const char *path,
621                   void *parent_baton,
622                   svn_revnum_t base_revision,
623                   apr_pool_t *result_pool,
624                   void **child_baton)
625{
626  /* ### fix this?  */
627  apr_pool_t *scratch_pool = result_pool;
628  struct ev2_dir_baton *pb = parent_baton;
629  struct ev2_dir_baton *db = apr_pcalloc(result_pool, sizeof(*db));
630  const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool);
631
632  db->eb = pb->eb;
633  db->path = apr_pstrdup(result_pool, relpath);
634  db->base_revision = base_revision;
635
636  if (pb->copyfrom_relpath)
637    {
638      /* We are inside a copy. */
639      const char *name = svn_relpath_basename(relpath, scratch_pool);
640
641      db->copyfrom_relpath = svn_relpath_join(pb->copyfrom_relpath, name,
642                                              result_pool);
643      db->copyfrom_rev = pb->copyfrom_rev;
644    }
645
646  *child_baton = db;
647  return SVN_NO_ERROR;
648}
649
650static svn_error_t *
651ev2_change_dir_prop(void *dir_baton,
652                    const char *name,
653                    const svn_string_t *value,
654                    apr_pool_t *scratch_pool)
655{
656  struct ev2_dir_baton *db = dir_baton;
657
658  SVN_ERR(apply_propedit(db->eb, db->path, svn_node_dir, db->base_revision,
659                         name, value, scratch_pool));
660
661  return SVN_NO_ERROR;
662}
663
664static svn_error_t *
665ev2_close_directory(void *dir_baton,
666                    apr_pool_t *scratch_pool)
667{
668  return SVN_NO_ERROR;
669}
670
671static svn_error_t *
672ev2_absent_directory(const char *path,
673                     void *parent_baton,
674                     apr_pool_t *scratch_pool)
675{
676  struct ev2_dir_baton *pb = parent_baton;
677  const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool);
678  struct change_node *change = locate_change(pb->eb, relpath);
679
680  /* ### assert that RESTRUCTURE is NONE or DELETE?  */
681  change->action = RESTRUCTURE_ADD_ABSENT;
682  change->kind = svn_node_dir;
683
684  return SVN_NO_ERROR;
685}
686
687static svn_error_t *
688ev2_add_file(const char *path,
689             void *parent_baton,
690             const char *copyfrom_path,
691             svn_revnum_t copyfrom_revision,
692             apr_pool_t *result_pool,
693             void **file_baton)
694{
695  /* ### fix this?  */
696  apr_pool_t *scratch_pool = result_pool;
697  struct ev2_file_baton *fb = apr_pcalloc(result_pool, sizeof(*fb));
698  struct ev2_dir_baton *pb = parent_baton;
699  const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool);
700  struct change_node *change = locate_change(pb->eb, relpath);
701
702  /* ### assert that RESTRUCTURE is NONE or DELETE?  */
703  change->action = RESTRUCTURE_ADD;
704  change->kind = svn_node_file;
705
706  fb->eb = pb->eb;
707  fb->path = apr_pstrdup(result_pool, relpath);
708  fb->base_revision = pb->base_revision;
709  *file_baton = fb;
710
711  if (!copyfrom_path)
712    {
713      /* Don't bother fetching the base, as in an add we don't have a base. */
714      fb->delta_base = NULL;
715    }
716  else
717    {
718      /* A copy */
719
720      change->copyfrom_path = map_to_repos_relpath(fb->eb, copyfrom_path,
721                                                   fb->eb->edit_pool);
722      change->copyfrom_rev = copyfrom_revision;
723
724      SVN_ERR(fb->eb->fetch_base_func(&fb->delta_base,
725                                      fb->eb->fetch_base_baton,
726                                      change->copyfrom_path,
727                                      change->copyfrom_rev,
728                                      result_pool, scratch_pool));
729    }
730
731  return SVN_NO_ERROR;
732}
733
734static svn_error_t *
735ev2_open_file(const char *path,
736              void *parent_baton,
737              svn_revnum_t base_revision,
738              apr_pool_t *result_pool,
739              void **file_baton)
740{
741  /* ### fix this?  */
742  apr_pool_t *scratch_pool = result_pool;
743  struct ev2_file_baton *fb = apr_pcalloc(result_pool, sizeof(*fb));
744  struct ev2_dir_baton *pb = parent_baton;
745  const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool);
746
747  fb->eb = pb->eb;
748  fb->path = apr_pstrdup(result_pool, relpath);
749  fb->base_revision = base_revision;
750
751  if (pb->copyfrom_relpath)
752    {
753      /* We're in a copied directory, so the delta base is going to be
754         based up on the copy source. */
755      const char *name = svn_relpath_basename(relpath, scratch_pool);
756      const char *copyfrom_relpath = svn_relpath_join(pb->copyfrom_relpath,
757                                                      name,
758                                                      scratch_pool);
759
760      SVN_ERR(fb->eb->fetch_base_func(&fb->delta_base,
761                                      fb->eb->fetch_base_baton,
762                                      copyfrom_relpath, pb->copyfrom_rev,
763                                      result_pool, scratch_pool));
764    }
765  else
766    {
767      SVN_ERR(fb->eb->fetch_base_func(&fb->delta_base,
768                                      fb->eb->fetch_base_baton,
769                                      relpath, base_revision,
770                                      result_pool, scratch_pool));
771    }
772
773  *file_baton = fb;
774  return SVN_NO_ERROR;
775}
776
777struct handler_baton
778{
779  svn_txdelta_window_handler_t apply_handler;
780  void *apply_baton;
781
782  svn_stream_t *source;
783
784  apr_pool_t *pool;
785};
786
787static svn_error_t *
788window_handler(svn_txdelta_window_t *window, void *baton)
789{
790  struct handler_baton *hb = baton;
791  svn_error_t *err;
792
793  err = hb->apply_handler(window, hb->apply_baton);
794  if (window != NULL && !err)
795    return SVN_NO_ERROR;
796
797  SVN_ERR(svn_stream_close(hb->source));
798
799  svn_pool_destroy(hb->pool);
800
801  return svn_error_trace(err);
802}
803
804/* Lazy-open handler for getting a read-only stream of the delta base. */
805static svn_error_t *
806open_delta_base(svn_stream_t **stream, void *baton,
807                apr_pool_t *result_pool, apr_pool_t *scratch_pool)
808{
809  const char *const delta_base = baton;
810  return svn_stream_open_readonly(stream, delta_base,
811                                  result_pool, scratch_pool);
812}
813
814/* Lazy-open handler for opening a stream for the delta result. */
815static svn_error_t *
816open_delta_target(svn_stream_t **stream, void *baton,
817                apr_pool_t *result_pool, apr_pool_t *scratch_pool)
818{
819  const char **delta_target = baton;
820  return svn_stream_open_unique(stream, delta_target, NULL,
821                                svn_io_file_del_on_pool_cleanup,
822                                result_pool, scratch_pool);
823}
824
825static svn_error_t *
826ev2_apply_textdelta(void *file_baton,
827                    const char *base_checksum,
828                    apr_pool_t *result_pool,
829                    svn_txdelta_window_handler_t *handler,
830                    void **handler_baton)
831{
832  struct ev2_file_baton *fb = file_baton;
833  apr_pool_t *handler_pool = svn_pool_create(fb->eb->edit_pool);
834  struct handler_baton *hb = apr_pcalloc(handler_pool, sizeof(*hb));
835  struct change_node *change;
836  svn_stream_t *target;
837
838  change = locate_change(fb->eb, fb->path);
839  SVN_ERR_ASSERT(!change->contents_changed);
840  SVN_ERR_ASSERT(change->contents_abspath == NULL);
841  SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(change->changing)
842                 || change->changing == fb->base_revision);
843  change->changing = fb->base_revision;
844
845  if (! fb->delta_base)
846    hb->source = svn_stream_empty(handler_pool);
847  else
848    hb->source = svn_stream_lazyopen_create(open_delta_base,
849                                            (char*)fb->delta_base,
850                                            FALSE, handler_pool);
851
852  change->contents_changed = TRUE;
853  target = svn_stream_lazyopen_create(open_delta_target,
854                                      &change->contents_abspath,
855                                      FALSE, fb->eb->edit_pool);
856
857  svn_txdelta_apply(hb->source, target,
858                    NULL, NULL,
859                    handler_pool,
860                    &hb->apply_handler, &hb->apply_baton);
861
862  hb->pool = handler_pool;
863
864  *handler_baton = hb;
865  *handler = window_handler;
866
867  return SVN_NO_ERROR;
868}
869
870static svn_error_t *
871ev2_change_file_prop(void *file_baton,
872                     const char *name,
873                     const svn_string_t *value,
874                     apr_pool_t *scratch_pool)
875{
876  struct ev2_file_baton *fb = file_baton;
877
878  if (!strcmp(name, SVN_PROP_ENTRY_LOCK_TOKEN) && value == NULL)
879    {
880      /* We special case the lock token propery deletion, which is the
881         server's way of telling the client to unlock the path. */
882
883      /* ### this duplicates much of apply_propedit(). fix in future.  */
884      const char *relpath = map_to_repos_relpath(fb->eb, fb->path,
885                                                 scratch_pool);
886      struct change_node *change = locate_change(fb->eb, relpath);
887
888      change->unlock = TRUE;
889    }
890
891  SVN_ERR(apply_propedit(fb->eb, fb->path, svn_node_file, fb->base_revision,
892                         name, value, scratch_pool));
893
894  return SVN_NO_ERROR;
895}
896
897static svn_error_t *
898ev2_close_file(void *file_baton,
899               const char *text_checksum,
900               apr_pool_t *scratch_pool)
901{
902  return SVN_NO_ERROR;
903}
904
905static svn_error_t *
906ev2_absent_file(const char *path,
907                void *parent_baton,
908                apr_pool_t *scratch_pool)
909{
910  struct ev2_dir_baton *pb = parent_baton;
911  const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool);
912  struct change_node *change = locate_change(pb->eb, relpath);
913
914  /* ### assert that RESTRUCTURE is NONE or DELETE?  */
915  change->action = RESTRUCTURE_ADD_ABSENT;
916  change->kind = svn_node_file;
917
918  return SVN_NO_ERROR;
919}
920
921static svn_error_t *
922ev2_close_edit(void *edit_baton,
923               apr_pool_t *scratch_pool)
924{
925  struct ev2_edit_baton *eb = edit_baton;
926
927  SVN_ERR(run_ev2_actions(edit_baton, scratch_pool));
928  eb->closed = TRUE;
929  return svn_error_trace(svn_editor_complete(eb->editor));
930}
931
932static svn_error_t *
933ev2_abort_edit(void *edit_baton,
934               apr_pool_t *scratch_pool)
935{
936  struct ev2_edit_baton *eb = edit_baton;
937
938  SVN_ERR(run_ev2_actions(edit_baton, scratch_pool));
939  if (!eb->closed)
940    return svn_error_trace(svn_editor_abort(eb->editor));
941  else
942    return SVN_NO_ERROR;
943}
944
945/* Return a svn_delta_editor_t * in DEDITOR, with an accompanying baton in
946 * DEDITOR_BATON, which will drive EDITOR.  These will both be
947 * allocated in RESULT_POOL, which may become large and long-lived;
948 * SCRATCH_POOL is used for temporary allocations.
949 *
950 * The other parameters are as follows:
951 *  - UNLOCK_FUNC / UNLOCK_BATON: A callback / baton which will be called
952 *         when an unlocking action is received.
953 *  - FOUND_ABS_PATHS: A pointer to a boolean flag which will be set if
954 *         this shim determines that it is receiving absolute paths.
955 *  - FETCH_PROPS_FUNC / FETCH_PROPS_BATON: A callback / baton pair which
956 *         will be used by the shim handlers if they need to determine the
957 *         existing properties on a  path.
958 *  - FETCH_BASE_FUNC / FETCH_BASE_BATON: A callback / baton pair which will
959 *         be used by the shims handlers if they need to determine the base
960 *         text of a path.  It should only be invoked for files.
961 *  - EXB: An 'extra baton' which is used to communicate between the shims.
962 *         Its callbacks should be invoked at the appropriate time by this
963 *         shim.
964 */
965svn_error_t *
966svn_delta__delta_from_editor(const svn_delta_editor_t **deditor,
967                  void **dedit_baton,
968                  svn_editor_t *editor,
969                  svn_delta__unlock_func_t unlock_func,
970                  void *unlock_baton,
971                  svn_boolean_t *found_abs_paths,
972                  const char *repos_root,
973                  const char *base_relpath,
974                  svn_delta_fetch_props_func_t fetch_props_func,
975                  void *fetch_props_baton,
976                  svn_delta_fetch_base_func_t fetch_base_func,
977                  void *fetch_base_baton,
978                  struct svn_delta__extra_baton *exb,
979                  apr_pool_t *pool)
980{
981  /* Static 'cause we don't want it to be on the stack. */
982  static svn_delta_editor_t delta_editor = {
983      ev2_set_target_revision,
984      ev2_open_root,
985      ev2_delete_entry,
986      ev2_add_directory,
987      ev2_open_directory,
988      ev2_change_dir_prop,
989      ev2_close_directory,
990      ev2_absent_directory,
991      ev2_add_file,
992      ev2_open_file,
993      ev2_apply_textdelta,
994      ev2_change_file_prop,
995      ev2_close_file,
996      ev2_absent_file,
997      ev2_close_edit,
998      ev2_abort_edit
999    };
1000  struct ev2_edit_baton *eb = apr_pcalloc(pool, sizeof(*eb));
1001
1002  if (!base_relpath)
1003    base_relpath = "";
1004  else if (base_relpath[0] == '/')
1005    base_relpath += 1;
1006
1007  eb->editor = editor;
1008  eb->changes = apr_hash_make(pool);
1009  eb->path_order = apr_array_make(pool, 1, sizeof(const char *));
1010  eb->edit_pool = pool;
1011  eb->found_abs_paths = found_abs_paths;
1012  *eb->found_abs_paths = FALSE;
1013  eb->exb = exb;
1014  eb->repos_root = apr_pstrdup(pool, repos_root);
1015  eb->base_relpath = apr_pstrdup(pool, base_relpath);
1016
1017  eb->fetch_props_func = fetch_props_func;
1018  eb->fetch_props_baton = fetch_props_baton;
1019
1020  eb->fetch_base_func = fetch_base_func;
1021  eb->fetch_base_baton = fetch_base_baton;
1022
1023  eb->do_unlock = unlock_func;
1024  eb->unlock_baton = unlock_baton;
1025
1026  *dedit_baton = eb;
1027  *deditor = &delta_editor;
1028
1029  return SVN_NO_ERROR;
1030}
1031
1032
1033/* ### note the similarity to struct change_node. these structures will
1034   ### be combined in the future.  */
1035struct operation {
1036  /* ### leave these two here for now. still used.  */
1037  svn_revnum_t base_revision;
1038  void *baton;
1039};
1040
1041struct editor_baton
1042{
1043  const svn_delta_editor_t *deditor;
1044  void *dedit_baton;
1045
1046  svn_delta_fetch_kind_func_t fetch_kind_func;
1047  void *fetch_kind_baton;
1048
1049  svn_delta_fetch_props_func_t fetch_props_func;
1050  void *fetch_props_baton;
1051
1052  struct operation root;
1053  svn_boolean_t *make_abs_paths;
1054  const char *repos_root;
1055  const char *base_relpath;
1056
1057  /* REPOS_RELPATH -> struct change_node *  */
1058  apr_hash_t *changes;
1059
1060  apr_pool_t *edit_pool;
1061};
1062
1063
1064/* Insert a new change for RELPATH, or return an existing one.  */
1065static struct change_node *
1066insert_change(const char *relpath,
1067              apr_hash_t *changes)
1068{
1069  apr_pool_t *result_pool;
1070  struct change_node *change;
1071
1072  change = svn_hash_gets(changes, relpath);
1073  if (change != NULL)
1074    return change;
1075
1076  result_pool = apr_hash_pool_get(changes);
1077
1078  /* Return an empty change. Callers will tweak as needed.  */
1079  change = apr_pcalloc(result_pool, sizeof(*change));
1080  change->changing = SVN_INVALID_REVNUM;
1081  change->deleting = SVN_INVALID_REVNUM;
1082
1083  svn_hash_sets(changes, apr_pstrdup(result_pool, relpath), change);
1084
1085  return change;
1086}
1087
1088
1089/* This implements svn_editor_cb_add_directory_t */
1090static svn_error_t *
1091add_directory_cb(void *baton,
1092                 const char *relpath,
1093                 const apr_array_header_t *children,
1094                 apr_hash_t *props,
1095                 svn_revnum_t replaces_rev,
1096                 apr_pool_t *scratch_pool)
1097{
1098  struct editor_baton *eb = baton;
1099  struct change_node *change = insert_change(relpath, eb->changes);
1100
1101  change->action = RESTRUCTURE_ADD;
1102  change->kind = svn_node_dir;
1103  change->deleting = replaces_rev;
1104  change->props = svn_prop_hash_dup(props, eb->edit_pool);
1105
1106  return SVN_NO_ERROR;
1107}
1108
1109/* This implements svn_editor_cb_add_file_t */
1110static svn_error_t *
1111add_file_cb(void *baton,
1112            const char *relpath,
1113            const svn_checksum_t *checksum,
1114            svn_stream_t *contents,
1115            apr_hash_t *props,
1116            svn_revnum_t replaces_rev,
1117            apr_pool_t *scratch_pool)
1118{
1119  struct editor_baton *eb = baton;
1120  const char *tmp_filename;
1121  svn_stream_t *tmp_stream;
1122  svn_checksum_t *md5_checksum;
1123  struct change_node *change = insert_change(relpath, eb->changes);
1124
1125  /* We may need to re-checksum these contents */
1126  if (!(checksum && checksum->kind == svn_checksum_md5))
1127    contents = svn_stream_checksummed2(contents, &md5_checksum, NULL,
1128                                       svn_checksum_md5, TRUE, scratch_pool);
1129  else
1130    md5_checksum = (svn_checksum_t *)checksum;
1131
1132  /* Spool the contents to a tempfile, and provide that to the driver. */
1133  SVN_ERR(svn_stream_open_unique(&tmp_stream, &tmp_filename, NULL,
1134                                 svn_io_file_del_on_pool_cleanup,
1135                                 eb->edit_pool, scratch_pool));
1136  SVN_ERR(svn_stream_copy3(contents, tmp_stream, NULL, NULL, scratch_pool));
1137
1138  change->action = RESTRUCTURE_ADD;
1139  change->kind = svn_node_file;
1140  change->deleting = replaces_rev;
1141  change->props = svn_prop_hash_dup(props, eb->edit_pool);
1142  change->contents_changed = TRUE;
1143  change->contents_abspath = tmp_filename;
1144  change->checksum = svn_checksum_dup(md5_checksum, eb->edit_pool);
1145
1146  return SVN_NO_ERROR;
1147}
1148
1149/* This implements svn_editor_cb_add_symlink_t */
1150static svn_error_t *
1151add_symlink_cb(void *baton,
1152               const char *relpath,
1153               const char *target,
1154               apr_hash_t *props,
1155               svn_revnum_t replaces_rev,
1156               apr_pool_t *scratch_pool)
1157{
1158#if 0
1159  struct editor_baton *eb = baton;
1160  struct change_node *change = insert_change(relpath, eb->changes);
1161
1162  change->action = RESTRUCTURE_ADD;
1163  change->kind = svn_node_symlink;
1164  change->deleting = replaces_rev;
1165  change->props = svn_prop_hash_dup(props, eb->edit_pool);
1166  /* ### target  */
1167#endif
1168
1169  SVN__NOT_IMPLEMENTED();
1170}
1171
1172/* This implements svn_editor_cb_add_absent_t */
1173static svn_error_t *
1174add_absent_cb(void *baton,
1175              const char *relpath,
1176              svn_node_kind_t kind,
1177              svn_revnum_t replaces_rev,
1178              apr_pool_t *scratch_pool)
1179{
1180  struct editor_baton *eb = baton;
1181  struct change_node *change = insert_change(relpath, eb->changes);
1182
1183  change->action = RESTRUCTURE_ADD_ABSENT;
1184  change->kind = kind;
1185  change->deleting = replaces_rev;
1186
1187  return SVN_NO_ERROR;
1188}
1189
1190/* This implements svn_editor_cb_alter_directory_t */
1191static svn_error_t *
1192alter_directory_cb(void *baton,
1193                   const char *relpath,
1194                   svn_revnum_t revision,
1195                   const apr_array_header_t *children,
1196                   apr_hash_t *props,
1197                   apr_pool_t *scratch_pool)
1198{
1199  struct editor_baton *eb = baton;
1200  struct change_node *change = insert_change(relpath, eb->changes);
1201
1202  /* ### should we verify the kind is truly a directory?  */
1203
1204  /* ### do we need to do anything with CHILDREN?  */
1205
1206  /* Note: this node may already have information in CHANGE as a result
1207     of an earlier copy/move operation.  */
1208  change->kind = svn_node_dir;
1209  change->changing = revision;
1210  change->props = svn_prop_hash_dup(props, eb->edit_pool);
1211
1212  return SVN_NO_ERROR;
1213}
1214
1215/* This implements svn_editor_cb_alter_file_t */
1216static svn_error_t *
1217alter_file_cb(void *baton,
1218              const char *relpath,
1219              svn_revnum_t revision,
1220              const svn_checksum_t *checksum,
1221              svn_stream_t *contents,
1222              apr_hash_t *props,
1223              apr_pool_t *scratch_pool)
1224{
1225  struct editor_baton *eb = baton;
1226  const char *tmp_filename;
1227  svn_stream_t *tmp_stream;
1228  svn_checksum_t *md5_checksum;
1229  struct change_node *change = insert_change(relpath, eb->changes);
1230
1231  /* ### should we verify the kind is truly a file?  */
1232
1233  if (contents)
1234    {
1235      /* We may need to re-checksum these contents */
1236      if (checksum && checksum->kind == svn_checksum_md5)
1237        md5_checksum = (svn_checksum_t *)checksum;
1238      else
1239        contents = svn_stream_checksummed2(contents, &md5_checksum, NULL,
1240                                           svn_checksum_md5, TRUE,
1241                                           scratch_pool);
1242
1243      /* Spool the contents to a tempfile, and provide that to the driver. */
1244      SVN_ERR(svn_stream_open_unique(&tmp_stream, &tmp_filename, NULL,
1245                                     svn_io_file_del_on_pool_cleanup,
1246                                     eb->edit_pool, scratch_pool));
1247      SVN_ERR(svn_stream_copy3(contents, tmp_stream, NULL, NULL,
1248                               scratch_pool));
1249    }
1250
1251  /* Note: this node may already have information in CHANGE as a result
1252     of an earlier copy/move operation.  */
1253
1254  change->kind = svn_node_file;
1255  change->changing = revision;
1256  if (props != NULL)
1257    change->props = svn_prop_hash_dup(props, eb->edit_pool);
1258  if (contents != NULL)
1259    {
1260      change->contents_changed = TRUE;
1261      change->contents_abspath = tmp_filename;
1262      change->checksum = svn_checksum_dup(md5_checksum, eb->edit_pool);
1263    }
1264
1265  return SVN_NO_ERROR;
1266}
1267
1268/* This implements svn_editor_cb_alter_symlink_t */
1269static svn_error_t *
1270alter_symlink_cb(void *baton,
1271                 const char *relpath,
1272                 svn_revnum_t revision,
1273                 const char *target,
1274                 apr_hash_t *props,
1275                 apr_pool_t *scratch_pool)
1276{
1277  /* ### should we verify the kind is truly a symlink?  */
1278
1279  /* ### do something  */
1280
1281  SVN__NOT_IMPLEMENTED();
1282}
1283
1284/* This implements svn_editor_cb_delete_t */
1285static svn_error_t *
1286delete_cb(void *baton,
1287          const char *relpath,
1288          svn_revnum_t revision,
1289          apr_pool_t *scratch_pool)
1290{
1291  struct editor_baton *eb = baton;
1292  struct change_node *change = insert_change(relpath, eb->changes);
1293
1294  change->action = RESTRUCTURE_DELETE;
1295  /* change->kind = svn_node_unknown;  */
1296  change->deleting = revision;
1297
1298  return SVN_NO_ERROR;
1299}
1300
1301/* This implements svn_editor_cb_copy_t */
1302static svn_error_t *
1303copy_cb(void *baton,
1304        const char *src_relpath,
1305        svn_revnum_t src_revision,
1306        const char *dst_relpath,
1307        svn_revnum_t replaces_rev,
1308        apr_pool_t *scratch_pool)
1309{
1310  struct editor_baton *eb = baton;
1311  struct change_node *change = insert_change(dst_relpath, eb->changes);
1312
1313  change->action = RESTRUCTURE_ADD;
1314  /* change->kind = svn_node_unknown;  */
1315  change->deleting = replaces_rev;
1316  change->copyfrom_path = apr_pstrdup(eb->edit_pool, src_relpath);
1317  change->copyfrom_rev = src_revision;
1318
1319  /* We need the source's kind to know whether to call add_directory()
1320     or add_file() later on.  */
1321  SVN_ERR(eb->fetch_kind_func(&change->kind, eb->fetch_kind_baton,
1322                              change->copyfrom_path,
1323                              change->copyfrom_rev,
1324                              scratch_pool));
1325
1326  /* Note: this node may later have alter_*() called on it.  */
1327
1328  return SVN_NO_ERROR;
1329}
1330
1331/* This implements svn_editor_cb_move_t */
1332static svn_error_t *
1333move_cb(void *baton,
1334        const char *src_relpath,
1335        svn_revnum_t src_revision,
1336        const char *dst_relpath,
1337        svn_revnum_t replaces_rev,
1338        apr_pool_t *scratch_pool)
1339{
1340  struct editor_baton *eb = baton;
1341  struct change_node *change;
1342
1343  /* Remap a move into a DELETE + COPY.  */
1344
1345  change = insert_change(src_relpath, eb->changes);
1346  change->action = RESTRUCTURE_DELETE;
1347  /* change->kind = svn_node_unknown;  */
1348  change->deleting = src_revision;
1349
1350  change = insert_change(dst_relpath, eb->changes);
1351  change->action = RESTRUCTURE_ADD;
1352  /* change->kind = svn_node_unknown;  */
1353  change->deleting = replaces_rev;
1354  change->copyfrom_path = apr_pstrdup(eb->edit_pool, src_relpath);
1355  change->copyfrom_rev = src_revision;
1356
1357  /* We need the source's kind to know whether to call add_directory()
1358     or add_file() later on.  */
1359  SVN_ERR(eb->fetch_kind_func(&change->kind, eb->fetch_kind_baton,
1360                              change->copyfrom_path,
1361                              change->copyfrom_rev,
1362                              scratch_pool));
1363
1364  /* Note: this node may later have alter_*() called on it.  */
1365
1366  return SVN_NO_ERROR;
1367}
1368
1369static int
1370count_components(const char *relpath)
1371{
1372  int count = 1;
1373  const char *slash = strchr(relpath, '/');
1374
1375  while (slash != NULL)
1376    {
1377      ++count;
1378      slash = strchr(slash + 1, '/');
1379    }
1380  return count;
1381}
1382
1383
1384static int
1385sort_deletes_first(const svn_sort__item_t *item1,
1386                   const svn_sort__item_t *item2)
1387{
1388  const char *relpath1 = item1->key;
1389  const char *relpath2 = item2->key;
1390  const struct change_node *change1 = item1->value;
1391  const struct change_node *change2 = item2->value;
1392  const char *slash1;
1393  const char *slash2;
1394  ptrdiff_t len1;
1395  ptrdiff_t len2;
1396
1397  /* Force the root to always sort first. Otherwise, it may look like a
1398     sibling of its children (no slashes), and could get sorted *after*
1399     any children that get deleted.  */
1400  if (*relpath1 == '\0')
1401    return -1;
1402  if (*relpath2 == '\0')
1403    return 1;
1404
1405  /* Are these two items siblings? The 'if' statement tests if they are
1406     siblings in the root directory, or that slashes were found in both
1407     paths, that the length of the paths to those slashes match, and that
1408     the path contents up to those slashes also match.  */
1409  slash1 = strrchr(relpath1, '/');
1410  slash2 = strrchr(relpath2, '/');
1411  if ((slash1 == NULL && slash2 == NULL)
1412      || (slash1 != NULL
1413          && slash2 != NULL
1414          && (len1 = slash1 - relpath1) == (len2 = slash2 - relpath2)
1415          && memcmp(relpath1, relpath2, len1) == 0))
1416    {
1417      if (change1->action == RESTRUCTURE_DELETE)
1418        {
1419          if (change2->action == RESTRUCTURE_DELETE)
1420            {
1421              /* If both items are being deleted, then we don't care about
1422                 the order. State they are equal.  */
1423              return 0;
1424            }
1425
1426          /* ITEM1 is being deleted. Sort it before the surviving item.  */
1427          return -1;
1428        }
1429      if (change2->action == RESTRUCTURE_DELETE)
1430        /* ITEM2 is being deleted. Sort it before the surviving item.  */
1431        return 1;
1432
1433      /* Normally, we don't care about the ordering of two siblings. However,
1434         if these siblings are directories, then we need to provide an
1435         ordering so that the quicksort algorithm will further sort them
1436         relative to the maybe-directory's children.
1437
1438         Without this additional ordering, we could see that A/B/E and A/B/F
1439         are equal. And then A/B/E/child is sorted before A/B/F. But since
1440         E and F are "equal", A/B/E could arrive *after* A/B/F and after the
1441         A/B/E/child node.  */
1442
1443      /* FALLTHROUGH */
1444    }
1445
1446  /* Paths-to-be-deleted with fewer components always sort earlier.
1447
1448     For example, gamma will sort before E/alpha.
1449
1450     Without this test, E/alpha lexicographically sorts before gamma,
1451     but gamma sorts before E when gamma is to be deleted. This kind of
1452     ordering would place E/alpha before E. Not good.
1453
1454     With this test, gamma sorts before E/alpha. E and E/alpha are then
1455     sorted by svn_path_compare_paths() (which places E before E/alpha).  */
1456  if (change1->action == RESTRUCTURE_DELETE
1457      || change2->action == RESTRUCTURE_DELETE)
1458    {
1459      int count1 = count_components(relpath1);
1460      int count2 = count_components(relpath2);
1461
1462      if (count1 < count2 && change1->action == RESTRUCTURE_DELETE)
1463        return -1;
1464      if (count1 > count2 && change2->action == RESTRUCTURE_DELETE)
1465        return 1;
1466    }
1467
1468  /* Use svn_path_compare_paths() to get correct depth-based ordering.  */
1469  return svn_path_compare_paths(relpath1, relpath2);
1470}
1471
1472
1473static const apr_array_header_t *
1474get_sorted_paths(apr_hash_t *changes,
1475                 const char *base_relpath,
1476                 apr_pool_t *scratch_pool)
1477{
1478  const apr_array_header_t *items;
1479  apr_array_header_t *paths;
1480  int i;
1481
1482  /* Construct a sorted array of svn_sort__item_t structs. Within a given
1483     directory, nodes that are to be deleted will appear first.  */
1484  items = svn_sort__hash(changes, sort_deletes_first, scratch_pool);
1485
1486  /* Build a new array with just the paths, trimmed to relative paths for
1487     the Ev1 drive.  */
1488  paths = apr_array_make(scratch_pool, items->nelts, sizeof(const char *));
1489  for (i = items->nelts; i--; )
1490    {
1491      const svn_sort__item_t *item;
1492
1493      item = &APR_ARRAY_IDX(items, i, const svn_sort__item_t);
1494      APR_ARRAY_IDX(paths, i, const char *)
1495        = svn_relpath_skip_ancestor(base_relpath, item->key);
1496    }
1497
1498  /* We didn't use PUSH, so set the proper number of elements.  */
1499  paths->nelts = items->nelts;
1500
1501  return paths;
1502}
1503
1504
1505static svn_error_t *
1506drive_ev1_props(const struct editor_baton *eb,
1507                const char *repos_relpath,
1508                const struct change_node *change,
1509                void *node_baton,
1510                apr_pool_t *scratch_pool)
1511{
1512  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1513  apr_hash_t *old_props;
1514  apr_array_header_t *propdiffs;
1515  int i;
1516
1517  /* If there are no properties to install, then just exit.  */
1518  if (change->props == NULL)
1519    return SVN_NO_ERROR;
1520
1521  if (change->copyfrom_path)
1522    {
1523      /* The pristine properties are from the copy/move source.  */
1524      SVN_ERR(eb->fetch_props_func(&old_props, eb->fetch_props_baton,
1525                                   change->copyfrom_path,
1526                                   change->copyfrom_rev,
1527                                   scratch_pool, iterpool));
1528    }
1529  else if (change->action == RESTRUCTURE_ADD)
1530    {
1531      /* Locally-added nodes have no pristine properties.
1532
1533         Note: we can use iterpool; this hash only needs to survive to
1534         the propdiffs call, and there are no contents to preserve.  */
1535      old_props = apr_hash_make(iterpool);
1536    }
1537  else
1538    {
1539      /* Fetch the pristine properties for whatever we're editing.  */
1540      SVN_ERR(eb->fetch_props_func(&old_props, eb->fetch_props_baton,
1541                                   repos_relpath, change->changing,
1542                                   scratch_pool, iterpool));
1543    }
1544
1545  SVN_ERR(svn_prop_diffs(&propdiffs, change->props, old_props, scratch_pool));
1546
1547  for (i = 0; i < propdiffs->nelts; i++)
1548    {
1549      /* Note: the array returned by svn_prop_diffs() is an array of
1550         actual structures, not pointers to them. */
1551      const svn_prop_t *prop = &APR_ARRAY_IDX(propdiffs, i, svn_prop_t);
1552
1553      svn_pool_clear(iterpool);
1554
1555      if (change->kind == svn_node_dir)
1556        SVN_ERR(eb->deditor->change_dir_prop(node_baton,
1557                                             prop->name, prop->value,
1558                                             iterpool));
1559      else
1560        SVN_ERR(eb->deditor->change_file_prop(node_baton,
1561                                              prop->name, prop->value,
1562                                              iterpool));
1563    }
1564
1565  /* Handle the funky unlock protocol. Note: only possibly on files.  */
1566  if (change->unlock)
1567    {
1568      SVN_ERR_ASSERT(change->kind == svn_node_file);
1569      SVN_ERR(eb->deditor->change_file_prop(node_baton,
1570                                            SVN_PROP_ENTRY_LOCK_TOKEN, NULL,
1571                                            iterpool));
1572    }
1573
1574  svn_pool_destroy(iterpool);
1575  return SVN_NO_ERROR;
1576}
1577
1578
1579/* Conforms to svn_delta_path_driver_cb_func_t  */
1580static svn_error_t *
1581apply_change(void **dir_baton,
1582             void *parent_baton,
1583             void *callback_baton,
1584             const char *ev1_relpath,
1585             apr_pool_t *result_pool)
1586{
1587  /* ### fix this?  */
1588  apr_pool_t *scratch_pool = result_pool;
1589  const struct editor_baton *eb = callback_baton;
1590  const struct change_node *change;
1591  const char *relpath;
1592  void *file_baton = NULL;
1593
1594  /* Typically, we are not creating new directory batons.  */
1595  *dir_baton = NULL;
1596
1597  relpath = svn_relpath_join(eb->base_relpath, ev1_relpath, scratch_pool);
1598  change = svn_hash_gets(eb->changes, relpath);
1599
1600  /* The callback should only be called for paths in CHANGES.  */
1601  SVN_ERR_ASSERT(change != NULL);
1602
1603  /* Are we editing the root of the tree?  */
1604  if (parent_baton == NULL)
1605    {
1606      /* The root was opened in start_edit_func()  */
1607      *dir_baton = eb->root.baton;
1608
1609      /* Only property edits are allowed on the root.  */
1610      SVN_ERR_ASSERT(change->action == RESTRUCTURE_NONE);
1611      SVN_ERR(drive_ev1_props(eb, relpath, change, *dir_baton, scratch_pool));
1612
1613      /* No further action possible for the root.  */
1614      return SVN_NO_ERROR;
1615    }
1616
1617  if (change->action == RESTRUCTURE_DELETE)
1618    {
1619      SVN_ERR(eb->deditor->delete_entry(ev1_relpath, change->deleting,
1620                                        parent_baton, scratch_pool));
1621
1622      /* No futher action possible for this node.  */
1623      return SVN_NO_ERROR;
1624    }
1625
1626  /* If we're not deleting this node, then we should know its kind.  */
1627  SVN_ERR_ASSERT(change->kind != svn_node_unknown);
1628
1629  if (change->action == RESTRUCTURE_ADD_ABSENT)
1630    {
1631      if (change->kind == svn_node_dir)
1632        SVN_ERR(eb->deditor->absent_directory(ev1_relpath, parent_baton,
1633                                              scratch_pool));
1634      else
1635        SVN_ERR(eb->deditor->absent_file(ev1_relpath, parent_baton,
1636                                         scratch_pool));
1637
1638      /* No further action possible for this node.  */
1639      return SVN_NO_ERROR;
1640    }
1641  /* RESTRUCTURE_NONE or RESTRUCTURE_ADD  */
1642
1643  if (change->action == RESTRUCTURE_ADD)
1644    {
1645      const char *copyfrom_url = NULL;
1646      svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
1647
1648      /* Do we have an old node to delete first?  */
1649      if (SVN_IS_VALID_REVNUM(change->deleting))
1650        SVN_ERR(eb->deditor->delete_entry(ev1_relpath, change->deleting,
1651                                          parent_baton, scratch_pool));
1652
1653      /* Are we copying the node from somewhere?  */
1654      if (change->copyfrom_path)
1655        {
1656          if (eb->repos_root)
1657            copyfrom_url = svn_path_url_add_component2(eb->repos_root,
1658                                                       change->copyfrom_path,
1659                                                       scratch_pool);
1660          else
1661            {
1662              copyfrom_url = change->copyfrom_path;
1663
1664              /* Make this an FS path by prepending "/" */
1665              if (copyfrom_url[0] != '/')
1666                copyfrom_url = apr_pstrcat(scratch_pool, "/",
1667                                           copyfrom_url, SVN_VA_NULL);
1668            }
1669
1670          copyfrom_rev = change->copyfrom_rev;
1671        }
1672
1673      if (change->kind == svn_node_dir)
1674        SVN_ERR(eb->deditor->add_directory(ev1_relpath, parent_baton,
1675                                           copyfrom_url, copyfrom_rev,
1676                                           result_pool, dir_baton));
1677      else
1678        SVN_ERR(eb->deditor->add_file(ev1_relpath, parent_baton,
1679                                      copyfrom_url, copyfrom_rev,
1680                                      result_pool, &file_baton));
1681    }
1682  else
1683    {
1684      if (change->kind == svn_node_dir)
1685        SVN_ERR(eb->deditor->open_directory(ev1_relpath, parent_baton,
1686                                            change->changing,
1687                                            result_pool, dir_baton));
1688      else
1689        SVN_ERR(eb->deditor->open_file(ev1_relpath, parent_baton,
1690                                       change->changing,
1691                                       result_pool, &file_baton));
1692    }
1693
1694  /* Apply any properties in CHANGE to the node.  */
1695  if (change->kind == svn_node_dir)
1696    SVN_ERR(drive_ev1_props(eb, relpath, change, *dir_baton, scratch_pool));
1697  else
1698    SVN_ERR(drive_ev1_props(eb, relpath, change, file_baton, scratch_pool));
1699
1700  if (change->contents_changed && change->contents_abspath)
1701    {
1702      svn_txdelta_window_handler_t handler;
1703      void *handler_baton;
1704      svn_stream_t *contents;
1705
1706      /* ### would be nice to have a BASE_CHECKSUM, but hey: this is the
1707         ### shim code...  */
1708      SVN_ERR(eb->deditor->apply_textdelta(file_baton, NULL, scratch_pool,
1709                                           &handler, &handler_baton));
1710      SVN_ERR(svn_stream_open_readonly(&contents, change->contents_abspath,
1711                                       scratch_pool, scratch_pool));
1712      /* ### it would be nice to send a true txdelta here, but whatever.  */
1713      SVN_ERR(svn_txdelta_send_stream(contents, handler, handler_baton,
1714                                      NULL, scratch_pool));
1715      SVN_ERR(svn_stream_close(contents));
1716    }
1717
1718  if (file_baton)
1719    {
1720      const char *digest = svn_checksum_to_cstring(change->checksum,
1721                                                   scratch_pool);
1722
1723      SVN_ERR(eb->deditor->close_file(file_baton, digest, scratch_pool));
1724    }
1725
1726  return SVN_NO_ERROR;
1727}
1728
1729
1730static svn_error_t *
1731drive_changes(const struct editor_baton *eb,
1732              apr_pool_t *scratch_pool)
1733{
1734  struct change_node *change;
1735  const apr_array_header_t *paths;
1736
1737  /* If we never opened a root baton, then the caller aborted the editor
1738     before it even began. There is nothing to do. Bail.  */
1739  if (eb->root.baton == NULL)
1740    return SVN_NO_ERROR;
1741
1742  /* We need to make the path driver believe we want to make changes to
1743     the root. Otherwise, it will attempt an open_root(), which we already
1744     did in start_edit_func(). We can forge up a change record, if one
1745     does not already exist.  */
1746  change = insert_change(eb->base_relpath, eb->changes);
1747  change->kind = svn_node_dir;
1748  /* No property changes (tho they might exist from a real change).  */
1749
1750  /* Get a sorted list of Ev1-relative paths.  */
1751  paths = get_sorted_paths(eb->changes, eb->base_relpath, scratch_pool);
1752  SVN_ERR(svn_delta_path_driver2(eb->deditor, eb->dedit_baton, paths,
1753                                 FALSE, apply_change, (void *)eb,
1754                                 scratch_pool));
1755
1756  return SVN_NO_ERROR;
1757}
1758
1759
1760/* This implements svn_editor_cb_complete_t */
1761static svn_error_t *
1762complete_cb(void *baton,
1763            apr_pool_t *scratch_pool)
1764{
1765  struct editor_baton *eb = baton;
1766  svn_error_t *err;
1767
1768  /* Drive the tree we've created. */
1769  err = drive_changes(eb, scratch_pool);
1770  if (!err)
1771     {
1772       err = svn_error_compose_create(err, eb->deditor->close_edit(
1773                                                            eb->dedit_baton,
1774                                                            scratch_pool));
1775     }
1776
1777  if (err)
1778    svn_error_clear(eb->deditor->abort_edit(eb->dedit_baton, scratch_pool));
1779
1780  return svn_error_trace(err);
1781}
1782
1783/* This implements svn_editor_cb_abort_t */
1784static svn_error_t *
1785abort_cb(void *baton,
1786         apr_pool_t *scratch_pool)
1787{
1788  struct editor_baton *eb = baton;
1789  svn_error_t *err;
1790  svn_error_t *err2;
1791
1792  /* We still need to drive anything we collected in the editor to this
1793     point. */
1794
1795  /* Drive the tree we've created. */
1796  err = drive_changes(eb, scratch_pool);
1797
1798  err2 = eb->deditor->abort_edit(eb->dedit_baton, scratch_pool);
1799
1800  if (err2)
1801    {
1802      if (err)
1803        svn_error_clear(err2);
1804      else
1805        err = err2;
1806    }
1807
1808  return svn_error_trace(err);
1809}
1810
1811static svn_error_t *
1812start_edit_func(void *baton,
1813                svn_revnum_t base_revision)
1814{
1815  struct editor_baton *eb = baton;
1816
1817  eb->root.base_revision = base_revision;
1818
1819  /* For some Ev1 editors (such as the repos commit editor), the root must
1820     be open before can invoke any callbacks. The open_root() call sets up
1821     stuff (eg. open an FS txn) which will be needed.  */
1822  SVN_ERR(eb->deditor->open_root(eb->dedit_baton, eb->root.base_revision,
1823                                 eb->edit_pool, &eb->root.baton));
1824
1825  return SVN_NO_ERROR;
1826}
1827
1828static svn_error_t *
1829target_revision_func(void *baton,
1830                     svn_revnum_t target_revision,
1831                     apr_pool_t *scratch_pool)
1832{
1833  struct editor_baton *eb = baton;
1834
1835  SVN_ERR(eb->deditor->set_target_revision(eb->dedit_baton, target_revision,
1836                                           scratch_pool));
1837
1838  return SVN_NO_ERROR;
1839}
1840
1841static svn_error_t *
1842do_unlock(void *baton,
1843          const char *path,
1844          apr_pool_t *scratch_pool)
1845{
1846  struct editor_baton *eb = baton;
1847
1848  {
1849    /* PATH is REPOS_RELPATH  */
1850    struct change_node *change = insert_change(path, eb->changes);
1851
1852    /* We will need to propagate a deletion of SVN_PROP_ENTRY_LOCK_TOKEN  */
1853    change->unlock = TRUE;
1854  }
1855
1856  return SVN_NO_ERROR;
1857}
1858
1859/* Return an svn_editor_t * in EDITOR_P which will drive
1860 * DEDITOR/DEDIT_BATON.  EDITOR_P is allocated in RESULT_POOL, which may
1861 * become large and long-lived; SCRATCH_POOL is used for temporary
1862 * allocations.
1863 *
1864 * The other parameters are as follows:
1865 *  - EXB: An 'extra_baton' used for passing information between the coupled
1866 *         shims.  This includes actions like 'start edit' and 'set target'.
1867 *         As this shim receives these actions, it provides the extra baton
1868 *         to its caller.
1869 *  - UNLOCK_FUNC / UNLOCK_BATON: A callback / baton pair which a caller
1870 *         can use to notify this shim that a path should be unlocked (in the
1871 *         'svn lock' sense).  As this shim receives this action, it provides
1872 *         this callback / baton to its caller.
1873 *  - SEND_ABS_PATHS: A pointer which will be set prior to this edit (but
1874 *         not necessarily at the invocation of editor_from_delta()),and
1875 *         which indicates whether incoming paths should be expected to
1876 *         be absolute or relative.
1877 *  - CANCEL_FUNC / CANCEL_BATON: The usual; folded into the produced editor.
1878 *  - FETCH_KIND_FUNC / FETCH_KIND_BATON: A callback / baton pair which will
1879 *         be used by the shim handlers if they need to determine the kind of
1880 *         a path.
1881 *  - FETCH_PROPS_FUNC / FETCH_PROPS_BATON: A callback / baton pair which
1882 *         will be used by the shim handlers if they need to determine the
1883 *         existing properties on a path.
1884 */
1885svn_error_t *
1886svn_delta__editor_from_delta(svn_editor_t **editor_p,
1887                  struct svn_delta__extra_baton **exb,
1888                  svn_delta__unlock_func_t *unlock_func,
1889                  void **unlock_baton,
1890                  const svn_delta_editor_t *deditor,
1891                  void *dedit_baton,
1892                  svn_boolean_t *send_abs_paths,
1893                  const char *repos_root,
1894                  const char *base_relpath,
1895                  svn_cancel_func_t cancel_func,
1896                  void *cancel_baton,
1897                  svn_delta_fetch_kind_func_t fetch_kind_func,
1898                  void *fetch_kind_baton,
1899                  svn_delta_fetch_props_func_t fetch_props_func,
1900                  void *fetch_props_baton,
1901                  apr_pool_t *result_pool,
1902                  apr_pool_t *scratch_pool)
1903{
1904  svn_editor_t *editor;
1905  static const svn_editor_cb_many_t editor_cbs = {
1906      add_directory_cb,
1907      add_file_cb,
1908      add_symlink_cb,
1909      add_absent_cb,
1910      alter_directory_cb,
1911      alter_file_cb,
1912      alter_symlink_cb,
1913      delete_cb,
1914      copy_cb,
1915      move_cb,
1916      complete_cb,
1917      abort_cb
1918    };
1919  struct editor_baton *eb = apr_pcalloc(result_pool, sizeof(*eb));
1920  struct svn_delta__extra_baton *extra_baton = apr_pcalloc(result_pool,
1921                                                sizeof(*extra_baton));
1922
1923  if (!base_relpath)
1924    base_relpath = "";
1925  else if (base_relpath[0] == '/')
1926    base_relpath += 1;
1927
1928  eb->deditor = deditor;
1929  eb->dedit_baton = dedit_baton;
1930  eb->edit_pool = result_pool;
1931  eb->repos_root = apr_pstrdup(result_pool, repos_root);
1932  eb->base_relpath = apr_pstrdup(result_pool, base_relpath);
1933
1934  eb->changes = apr_hash_make(result_pool);
1935
1936  eb->fetch_kind_func = fetch_kind_func;
1937  eb->fetch_kind_baton = fetch_kind_baton;
1938  eb->fetch_props_func = fetch_props_func;
1939  eb->fetch_props_baton = fetch_props_baton;
1940
1941  eb->root.base_revision = SVN_INVALID_REVNUM;
1942
1943  eb->make_abs_paths = send_abs_paths;
1944
1945  SVN_ERR(svn_editor_create(&editor, eb, cancel_func, cancel_baton,
1946                            result_pool, scratch_pool));
1947  SVN_ERR(svn_editor_setcb_many(editor, &editor_cbs, scratch_pool));
1948
1949  *editor_p = editor;
1950
1951  *unlock_func = do_unlock;
1952  *unlock_baton = eb;
1953
1954  extra_baton->start_edit = start_edit_func;
1955  extra_baton->target_revision = target_revision_func;
1956  extra_baton->baton = eb;
1957
1958  *exb = extra_baton;
1959
1960  return SVN_NO_ERROR;
1961}
1962
1963svn_delta_shim_callbacks_t *
1964svn_delta_shim_callbacks_default(apr_pool_t *result_pool)
1965{
1966  svn_delta_shim_callbacks_t *shim_callbacks = apr_pcalloc(result_pool,
1967                                                     sizeof(*shim_callbacks));
1968  return shim_callbacks;
1969}
1970
1971/* To enable editor shims throughout Subversion, ENABLE_EV2_SHIMS should be
1972 * defined.  This can be done manually, or by providing `--enable-ev2-shims'
1973 * to `configure'.  */
1974
1975svn_error_t *
1976svn_editor__insert_shims(const svn_delta_editor_t **deditor_out,
1977                         void **dedit_baton_out,
1978                         const svn_delta_editor_t *deditor_in,
1979                         void *dedit_baton_in,
1980                         const char *repos_root,
1981                         const char *base_relpath,
1982                         svn_delta_shim_callbacks_t *shim_callbacks,
1983                         apr_pool_t *result_pool,
1984                         apr_pool_t *scratch_pool)
1985{
1986#ifndef ENABLE_EV2_SHIMS
1987  /* Shims disabled, just copy the editor and baton directly. */
1988  *deditor_out = deditor_in;
1989  *dedit_baton_out = dedit_baton_in;
1990#else
1991  /* Use our shim APIs to create an intermediate svn_editor_t, and then
1992     wrap that again back into a svn_delta_editor_t.  This introduces
1993     a lot of overhead. */
1994  svn_editor_t *editor;
1995
1996  /* The "extra baton" is a set of functions and a baton which allows the
1997     shims to communicate additional events to each other.
1998     svn_delta__editor_from_delta() returns a pointer to this baton, which
1999     svn_delta__delta_from_editor() should then store. */
2000  struct svn_delta__extra_baton *exb;
2001
2002  /* The reason this is a pointer is that we don't know the appropriate
2003     value until we start receiving paths.  So process_actions() sets the
2004     flag, which drive_tree() later consumes. */
2005  svn_boolean_t *found_abs_paths = apr_palloc(result_pool,
2006                                              sizeof(*found_abs_paths));
2007
2008  svn_delta__unlock_func_t unlock_func;
2009  void *unlock_baton;
2010
2011  SVN_ERR_ASSERT(shim_callbacks->fetch_kind_func != NULL);
2012  SVN_ERR_ASSERT(shim_callbacks->fetch_props_func != NULL);
2013  SVN_ERR_ASSERT(shim_callbacks->fetch_base_func != NULL);
2014
2015  SVN_ERR(svn_delta__editor_from_delta(&editor, &exb,
2016                            &unlock_func, &unlock_baton,
2017                            deditor_in, dedit_baton_in,
2018                            found_abs_paths, repos_root, base_relpath,
2019                            NULL, NULL,
2020                            shim_callbacks->fetch_kind_func,
2021                            shim_callbacks->fetch_baton,
2022                            shim_callbacks->fetch_props_func,
2023                            shim_callbacks->fetch_baton,
2024                            result_pool, scratch_pool));
2025  SVN_ERR(svn_delta__delta_from_editor(deditor_out, dedit_baton_out, editor,
2026                            unlock_func, unlock_baton,
2027                            found_abs_paths,
2028                            repos_root, base_relpath,
2029                            shim_callbacks->fetch_props_func,
2030                            shim_callbacks->fetch_baton,
2031                            shim_callbacks->fetch_base_func,
2032                            shim_callbacks->fetch_baton,
2033                            exb, result_pool));
2034
2035#endif
2036  return SVN_NO_ERROR;
2037}
2038