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