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  struct change_node *change = baton;
820  return svn_stream_open_unique(stream, &change->contents_abspath,
821                                NULL,
822                                svn_io_file_del_on_pool_cleanup,
823                                result_pool, scratch_pool);
824}
825
826static svn_error_t *
827ev2_apply_textdelta(void *file_baton,
828                    const char *base_checksum,
829                    apr_pool_t *result_pool,
830                    svn_txdelta_window_handler_t *handler,
831                    void **handler_baton)
832{
833  struct ev2_file_baton *fb = file_baton;
834  apr_pool_t *handler_pool = svn_pool_create(fb->eb->edit_pool);
835  struct handler_baton *hb = apr_pcalloc(handler_pool, sizeof(*hb));
836  struct change_node *change;
837  svn_stream_t *target;
838
839  change = locate_change(fb->eb, fb->path);
840  SVN_ERR_ASSERT(!change->contents_changed);
841  SVN_ERR_ASSERT(change->contents_abspath == NULL);
842  SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(change->changing)
843                 || change->changing == fb->base_revision);
844  change->changing = fb->base_revision;
845
846  if (! fb->delta_base)
847    hb->source = svn_stream_empty(handler_pool);
848  else
849    hb->source = svn_stream_lazyopen_create(open_delta_base,
850                                            (char*)fb->delta_base,
851                                            FALSE, handler_pool);
852
853  change->contents_changed = TRUE;
854  target = svn_stream_lazyopen_create(open_delta_target, change,
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  svn_stream_t *tmp_stream;
1227  struct change_node *change = insert_change(relpath, eb->changes);
1228
1229  /* Note: this node may already have information in CHANGE as a result
1230     of an earlier copy/move operation.  */
1231
1232  /* ### should we verify the kind is truly a file?  */
1233  change->kind = svn_node_file;
1234  change->changing = revision;
1235  if (props != NULL)
1236    change->props = svn_prop_hash_dup(props, eb->edit_pool);
1237
1238  if (contents)
1239    {
1240      const char *tmp_filename;
1241      svn_checksum_t *md5_checksum;
1242
1243      /* We may need to re-checksum these contents */
1244      if (checksum && checksum->kind == svn_checksum_md5)
1245        md5_checksum = (svn_checksum_t *)checksum;
1246      else
1247        contents = svn_stream_checksummed2(contents, &md5_checksum, NULL,
1248                                           svn_checksum_md5, TRUE,
1249                                           scratch_pool);
1250
1251      /* Spool the contents to a tempfile, and provide that to the driver. */
1252      SVN_ERR(svn_stream_open_unique(&tmp_stream, &tmp_filename, NULL,
1253                                     svn_io_file_del_on_pool_cleanup,
1254                                     eb->edit_pool, scratch_pool));
1255      SVN_ERR(svn_stream_copy3(contents, tmp_stream, NULL, NULL,
1256                               scratch_pool));
1257
1258      change->contents_changed = TRUE;
1259      change->contents_abspath = tmp_filename;
1260      change->checksum = svn_checksum_dup(md5_checksum, eb->edit_pool);
1261    }
1262
1263  return SVN_NO_ERROR;
1264}
1265
1266/* This implements svn_editor_cb_alter_symlink_t */
1267static svn_error_t *
1268alter_symlink_cb(void *baton,
1269                 const char *relpath,
1270                 svn_revnum_t revision,
1271                 const char *target,
1272                 apr_hash_t *props,
1273                 apr_pool_t *scratch_pool)
1274{
1275  /* ### should we verify the kind is truly a symlink?  */
1276
1277  /* ### do something  */
1278
1279  SVN__NOT_IMPLEMENTED();
1280}
1281
1282/* This implements svn_editor_cb_delete_t */
1283static svn_error_t *
1284delete_cb(void *baton,
1285          const char *relpath,
1286          svn_revnum_t revision,
1287          apr_pool_t *scratch_pool)
1288{
1289  struct editor_baton *eb = baton;
1290  struct change_node *change = insert_change(relpath, eb->changes);
1291
1292  change->action = RESTRUCTURE_DELETE;
1293  /* change->kind = svn_node_unknown;  */
1294  change->deleting = revision;
1295
1296  return SVN_NO_ERROR;
1297}
1298
1299/* This implements svn_editor_cb_copy_t */
1300static svn_error_t *
1301copy_cb(void *baton,
1302        const char *src_relpath,
1303        svn_revnum_t src_revision,
1304        const char *dst_relpath,
1305        svn_revnum_t replaces_rev,
1306        apr_pool_t *scratch_pool)
1307{
1308  struct editor_baton *eb = baton;
1309  struct change_node *change = insert_change(dst_relpath, eb->changes);
1310
1311  change->action = RESTRUCTURE_ADD;
1312  /* change->kind = svn_node_unknown;  */
1313  change->deleting = replaces_rev;
1314  change->copyfrom_path = apr_pstrdup(eb->edit_pool, src_relpath);
1315  change->copyfrom_rev = src_revision;
1316
1317  /* We need the source's kind to know whether to call add_directory()
1318     or add_file() later on.  */
1319  SVN_ERR(eb->fetch_kind_func(&change->kind, eb->fetch_kind_baton,
1320                              change->copyfrom_path,
1321                              change->copyfrom_rev,
1322                              scratch_pool));
1323
1324  /* Note: this node may later have alter_*() called on it.  */
1325
1326  return SVN_NO_ERROR;
1327}
1328
1329/* This implements svn_editor_cb_move_t */
1330static svn_error_t *
1331move_cb(void *baton,
1332        const char *src_relpath,
1333        svn_revnum_t src_revision,
1334        const char *dst_relpath,
1335        svn_revnum_t replaces_rev,
1336        apr_pool_t *scratch_pool)
1337{
1338  struct editor_baton *eb = baton;
1339  struct change_node *change;
1340
1341  /* Remap a move into a DELETE + COPY.  */
1342
1343  change = insert_change(src_relpath, eb->changes);
1344  change->action = RESTRUCTURE_DELETE;
1345  /* change->kind = svn_node_unknown;  */
1346  change->deleting = src_revision;
1347
1348  change = insert_change(dst_relpath, eb->changes);
1349  change->action = RESTRUCTURE_ADD;
1350  /* change->kind = svn_node_unknown;  */
1351  change->deleting = replaces_rev;
1352  change->copyfrom_path = apr_pstrdup(eb->edit_pool, src_relpath);
1353  change->copyfrom_rev = src_revision;
1354
1355  /* We need the source's kind to know whether to call add_directory()
1356     or add_file() later on.  */
1357  SVN_ERR(eb->fetch_kind_func(&change->kind, eb->fetch_kind_baton,
1358                              change->copyfrom_path,
1359                              change->copyfrom_rev,
1360                              scratch_pool));
1361
1362  /* Note: this node may later have alter_*() called on it.  */
1363
1364  return SVN_NO_ERROR;
1365}
1366
1367static int
1368count_components(const char *relpath)
1369{
1370  int count = 1;
1371  const char *slash = strchr(relpath, '/');
1372
1373  while (slash != NULL)
1374    {
1375      ++count;
1376      slash = strchr(slash + 1, '/');
1377    }
1378  return count;
1379}
1380
1381
1382static int
1383sort_deletes_first(const svn_sort__item_t *item1,
1384                   const svn_sort__item_t *item2)
1385{
1386  const char *relpath1 = item1->key;
1387  const char *relpath2 = item2->key;
1388  const struct change_node *change1 = item1->value;
1389  const struct change_node *change2 = item2->value;
1390  const char *slash1;
1391  const char *slash2;
1392  ptrdiff_t len1;
1393  ptrdiff_t len2;
1394
1395  /* Force the root to always sort first. Otherwise, it may look like a
1396     sibling of its children (no slashes), and could get sorted *after*
1397     any children that get deleted.  */
1398  if (*relpath1 == '\0')
1399    return -1;
1400  if (*relpath2 == '\0')
1401    return 1;
1402
1403  /* Are these two items siblings? The 'if' statement tests if they are
1404     siblings in the root directory, or that slashes were found in both
1405     paths, that the length of the paths to those slashes match, and that
1406     the path contents up to those slashes also match.  */
1407  slash1 = strrchr(relpath1, '/');
1408  slash2 = strrchr(relpath2, '/');
1409  if ((slash1 == NULL && slash2 == NULL)
1410      || (slash1 != NULL
1411          && slash2 != NULL
1412          && (len1 = slash1 - relpath1) == (len2 = slash2 - relpath2)
1413          && memcmp(relpath1, relpath2, len1) == 0))
1414    {
1415      if (change1->action == RESTRUCTURE_DELETE)
1416        {
1417          if (change2->action == RESTRUCTURE_DELETE)
1418            {
1419              /* If both items are being deleted, then we don't care about
1420                 the order. State they are equal.  */
1421              return 0;
1422            }
1423
1424          /* ITEM1 is being deleted. Sort it before the surviving item.  */
1425          return -1;
1426        }
1427      if (change2->action == RESTRUCTURE_DELETE)
1428        /* ITEM2 is being deleted. Sort it before the surviving item.  */
1429        return 1;
1430
1431      /* Normally, we don't care about the ordering of two siblings. However,
1432         if these siblings are directories, then we need to provide an
1433         ordering so that the quicksort algorithm will further sort them
1434         relative to the maybe-directory's children.
1435
1436         Without this additional ordering, we could see that A/B/E and A/B/F
1437         are equal. And then A/B/E/child is sorted before A/B/F. But since
1438         E and F are "equal", A/B/E could arrive *after* A/B/F and after the
1439         A/B/E/child node.  */
1440
1441      /* FALLTHROUGH */
1442    }
1443
1444  /* Paths-to-be-deleted with fewer components always sort earlier.
1445
1446     For example, gamma will sort before E/alpha.
1447
1448     Without this test, E/alpha lexicographically sorts before gamma,
1449     but gamma sorts before E when gamma is to be deleted. This kind of
1450     ordering would place E/alpha before E. Not good.
1451
1452     With this test, gamma sorts before E/alpha. E and E/alpha are then
1453     sorted by svn_path_compare_paths() (which places E before E/alpha).  */
1454  if (change1->action == RESTRUCTURE_DELETE
1455      || change2->action == RESTRUCTURE_DELETE)
1456    {
1457      int count1 = count_components(relpath1);
1458      int count2 = count_components(relpath2);
1459
1460      if (count1 < count2 && change1->action == RESTRUCTURE_DELETE)
1461        return -1;
1462      if (count1 > count2 && change2->action == RESTRUCTURE_DELETE)
1463        return 1;
1464    }
1465
1466  /* Use svn_path_compare_paths() to get correct depth-based ordering.  */
1467  return svn_path_compare_paths(relpath1, relpath2);
1468}
1469
1470
1471static const apr_array_header_t *
1472get_sorted_paths(apr_hash_t *changes,
1473                 const char *base_relpath,
1474                 apr_pool_t *scratch_pool)
1475{
1476  const apr_array_header_t *items;
1477  apr_array_header_t *paths;
1478  int i;
1479
1480  /* Construct a sorted array of svn_sort__item_t structs. Within a given
1481     directory, nodes that are to be deleted will appear first.  */
1482  items = svn_sort__hash(changes, sort_deletes_first, scratch_pool);
1483
1484  /* Build a new array with just the paths, trimmed to relative paths for
1485     the Ev1 drive.  */
1486  paths = apr_array_make(scratch_pool, items->nelts, sizeof(const char *));
1487  for (i = items->nelts; i--; )
1488    {
1489      const svn_sort__item_t *item;
1490
1491      item = &APR_ARRAY_IDX(items, i, const svn_sort__item_t);
1492      APR_ARRAY_IDX(paths, i, const char *)
1493        = svn_relpath_skip_ancestor(base_relpath, item->key);
1494    }
1495
1496  /* We didn't use PUSH, so set the proper number of elements.  */
1497  paths->nelts = items->nelts;
1498
1499  return paths;
1500}
1501
1502
1503static svn_error_t *
1504drive_ev1_props(const struct editor_baton *eb,
1505                const char *repos_relpath,
1506                const struct change_node *change,
1507                void *node_baton,
1508                apr_pool_t *scratch_pool)
1509{
1510  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1511  apr_hash_t *old_props;
1512  apr_array_header_t *propdiffs;
1513  int i;
1514
1515  /* If there are no properties to install, then just exit.  */
1516  if (change->props == NULL)
1517    return SVN_NO_ERROR;
1518
1519  if (change->copyfrom_path)
1520    {
1521      /* The pristine properties are from the copy/move source.  */
1522      SVN_ERR(eb->fetch_props_func(&old_props, eb->fetch_props_baton,
1523                                   change->copyfrom_path,
1524                                   change->copyfrom_rev,
1525                                   scratch_pool, iterpool));
1526    }
1527  else if (change->action == RESTRUCTURE_ADD)
1528    {
1529      /* Locally-added nodes have no pristine properties.
1530
1531         Note: we can use iterpool; this hash only needs to survive to
1532         the propdiffs call, and there are no contents to preserve.  */
1533      old_props = apr_hash_make(iterpool);
1534    }
1535  else
1536    {
1537      /* Fetch the pristine properties for whatever we're editing.  */
1538      SVN_ERR(eb->fetch_props_func(&old_props, eb->fetch_props_baton,
1539                                   repos_relpath, change->changing,
1540                                   scratch_pool, iterpool));
1541    }
1542
1543  SVN_ERR(svn_prop_diffs(&propdiffs, change->props, old_props, scratch_pool));
1544
1545  for (i = 0; i < propdiffs->nelts; i++)
1546    {
1547      /* Note: the array returned by svn_prop_diffs() is an array of
1548         actual structures, not pointers to them. */
1549      const svn_prop_t *prop = &APR_ARRAY_IDX(propdiffs, i, svn_prop_t);
1550
1551      svn_pool_clear(iterpool);
1552
1553      if (change->kind == svn_node_dir)
1554        SVN_ERR(eb->deditor->change_dir_prop(node_baton,
1555                                             prop->name, prop->value,
1556                                             iterpool));
1557      else
1558        SVN_ERR(eb->deditor->change_file_prop(node_baton,
1559                                              prop->name, prop->value,
1560                                              iterpool));
1561    }
1562
1563  /* Handle the funky unlock protocol. Note: only possibly on files.  */
1564  if (change->unlock)
1565    {
1566      SVN_ERR_ASSERT(change->kind == svn_node_file);
1567      SVN_ERR(eb->deditor->change_file_prop(node_baton,
1568                                            SVN_PROP_ENTRY_LOCK_TOKEN, NULL,
1569                                            iterpool));
1570    }
1571
1572  svn_pool_destroy(iterpool);
1573  return SVN_NO_ERROR;
1574}
1575
1576
1577/* Conforms to svn_delta_path_driver_cb_func_t  */
1578static svn_error_t *
1579apply_change(void **dir_baton,
1580             const svn_delta_editor_t *deditor,
1581             void *dedit_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(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(deditor->absent_directory(ev1_relpath, parent_baton,
1633                                          scratch_pool));
1634      else
1635        SVN_ERR(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(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(deditor->add_directory(ev1_relpath, parent_baton,
1675                                       copyfrom_url, copyfrom_rev,
1676                                       result_pool, dir_baton));
1677      else
1678        SVN_ERR(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(deditor->open_directory(ev1_relpath, parent_baton,
1686                                        change->changing,
1687                                        result_pool, dir_baton));
1688      else
1689        SVN_ERR(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(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(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_driver3(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