]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_delta/compat.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.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 #include "private/svn_sorts_private.h"
40 #include "svn_private_config.h"
41
42
43 struct 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. */
49 static svn_error_t *
50 file_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
75 void
76 svn_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 \f
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
115 struct 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
145 struct 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
155 struct 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
163 enum 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.  */
172 struct 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
207 static struct change_node *
208 locate_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
232 static svn_error_t *
233 apply_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. */
288 static apr_array_header_t *
289 get_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
318 static svn_error_t *
319 process_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
462 static svn_error_t *
463 run_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
490 static const char *
491 map_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
509 static svn_error_t *
510 ev2_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
523 static svn_error_t *
524 ev2_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
544 static svn_error_t *
545 ev2_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
570 static svn_error_t *
571 ev2_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
619 static svn_error_t *
620 ev2_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
650 static svn_error_t *
651 ev2_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
664 static svn_error_t *
665 ev2_close_directory(void *dir_baton,
666                     apr_pool_t *scratch_pool)
667 {
668   return SVN_NO_ERROR;
669 }
670
671 static svn_error_t *
672 ev2_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
687 static svn_error_t *
688 ev2_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
734 static svn_error_t *
735 ev2_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
777 struct 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
787 static svn_error_t *
788 window_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. */
805 static svn_error_t *
806 open_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. */
815 static svn_error_t *
816 open_delta_target(svn_stream_t **stream, void *baton,
817                 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
818 {
819   const char **delta_target = baton;
820   return svn_stream_open_unique(stream, delta_target, NULL,
821                                 svn_io_file_del_on_pool_cleanup,
822                                 result_pool, scratch_pool);
823 }
824
825 static svn_error_t *
826 ev2_apply_textdelta(void *file_baton,
827                     const char *base_checksum,
828                     apr_pool_t *result_pool,
829                     svn_txdelta_window_handler_t *handler,
830                     void **handler_baton)
831 {
832   struct ev2_file_baton *fb = file_baton;
833   apr_pool_t *handler_pool = svn_pool_create(fb->eb->edit_pool);
834   struct handler_baton *hb = apr_pcalloc(handler_pool, sizeof(*hb));
835   struct change_node *change;
836   svn_stream_t *target;
837
838   change = locate_change(fb->eb, fb->path);
839   SVN_ERR_ASSERT(!change->contents_changed);
840   SVN_ERR_ASSERT(change->contents_abspath == NULL);
841   SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(change->changing)
842                  || change->changing == fb->base_revision);
843   change->changing = fb->base_revision;
844
845   if (! fb->delta_base)
846     hb->source = svn_stream_empty(handler_pool);
847   else
848     hb->source = svn_stream_lazyopen_create(open_delta_base,
849                                             (char*)fb->delta_base,
850                                             FALSE, handler_pool);
851
852   change->contents_changed = TRUE;
853   target = svn_stream_lazyopen_create(open_delta_target,
854                                       &change->contents_abspath,
855                                       FALSE, fb->eb->edit_pool);
856
857   svn_txdelta_apply(hb->source, target,
858                     NULL, NULL,
859                     handler_pool,
860                     &hb->apply_handler, &hb->apply_baton);
861
862   hb->pool = handler_pool;
863
864   *handler_baton = hb;
865   *handler = window_handler;
866
867   return SVN_NO_ERROR;
868 }
869
870 static svn_error_t *
871 ev2_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
897 static svn_error_t *
898 ev2_close_file(void *file_baton,
899                const char *text_checksum,
900                apr_pool_t *scratch_pool)
901 {
902   return SVN_NO_ERROR;
903 }
904
905 static svn_error_t *
906 ev2_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
921 static svn_error_t *
922 ev2_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
932 static svn_error_t *
933 ev2_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  */
965 svn_error_t *
966 svn_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.  */
1035 struct operation {
1036   /* ### leave these two here for now. still used.  */
1037   svn_revnum_t base_revision;
1038   void *baton;
1039 };
1040
1041 struct 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.  */
1065 static struct change_node *
1066 insert_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 */
1090 static svn_error_t *
1091 add_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 */
1110 static svn_error_t *
1111 add_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 */
1150 static svn_error_t *
1151 add_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 */
1173 static svn_error_t *
1174 add_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 */
1191 static svn_error_t *
1192 alter_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 */
1216 static svn_error_t *
1217 alter_file_cb(void *baton,
1218               const char *relpath,
1219               svn_revnum_t revision,
1220               const svn_checksum_t *checksum,
1221               svn_stream_t *contents,
1222               apr_hash_t *props,
1223               apr_pool_t *scratch_pool)
1224 {
1225   struct editor_baton *eb = baton;
1226   const char *tmp_filename;
1227   svn_stream_t *tmp_stream;
1228   svn_checksum_t *md5_checksum;
1229   struct change_node *change = insert_change(relpath, eb->changes);
1230
1231   /* ### should we verify the kind is truly a file?  */
1232
1233   if (contents)
1234     {
1235       /* We may need to re-checksum these contents */
1236       if (checksum && checksum->kind == svn_checksum_md5)
1237         md5_checksum = (svn_checksum_t *)checksum;
1238       else
1239         contents = svn_stream_checksummed2(contents, &md5_checksum, NULL,
1240                                            svn_checksum_md5, TRUE,
1241                                            scratch_pool);
1242
1243       /* Spool the contents to a tempfile, and provide that to the driver. */
1244       SVN_ERR(svn_stream_open_unique(&tmp_stream, &tmp_filename, NULL,
1245                                      svn_io_file_del_on_pool_cleanup,
1246                                      eb->edit_pool, scratch_pool));
1247       SVN_ERR(svn_stream_copy3(contents, tmp_stream, NULL, NULL,
1248                                scratch_pool));
1249     }
1250
1251   /* Note: this node may already have information in CHANGE as a result
1252      of an earlier copy/move operation.  */
1253
1254   change->kind = svn_node_file;
1255   change->changing = revision;
1256   if (props != NULL)
1257     change->props = svn_prop_hash_dup(props, eb->edit_pool);
1258   if (contents != NULL)
1259     {
1260       change->contents_changed = TRUE;
1261       change->contents_abspath = tmp_filename;
1262       change->checksum = svn_checksum_dup(md5_checksum, eb->edit_pool);
1263     }
1264
1265   return SVN_NO_ERROR;
1266 }
1267
1268 /* This implements svn_editor_cb_alter_symlink_t */
1269 static svn_error_t *
1270 alter_symlink_cb(void *baton,
1271                  const char *relpath,
1272                  svn_revnum_t revision,
1273                  const char *target,
1274                  apr_hash_t *props,
1275                  apr_pool_t *scratch_pool)
1276 {
1277   /* ### should we verify the kind is truly a symlink?  */
1278
1279   /* ### do something  */
1280
1281   SVN__NOT_IMPLEMENTED();
1282 }
1283
1284 /* This implements svn_editor_cb_delete_t */
1285 static svn_error_t *
1286 delete_cb(void *baton,
1287           const char *relpath,
1288           svn_revnum_t revision,
1289           apr_pool_t *scratch_pool)
1290 {
1291   struct editor_baton *eb = baton;
1292   struct change_node *change = insert_change(relpath, eb->changes);
1293
1294   change->action = RESTRUCTURE_DELETE;
1295   /* change->kind = svn_node_unknown;  */
1296   change->deleting = revision;
1297
1298   return SVN_NO_ERROR;
1299 }
1300
1301 /* This implements svn_editor_cb_copy_t */
1302 static svn_error_t *
1303 copy_cb(void *baton,
1304         const char *src_relpath,
1305         svn_revnum_t src_revision,
1306         const char *dst_relpath,
1307         svn_revnum_t replaces_rev,
1308         apr_pool_t *scratch_pool)
1309 {
1310   struct editor_baton *eb = baton;
1311   struct change_node *change = insert_change(dst_relpath, eb->changes);
1312
1313   change->action = RESTRUCTURE_ADD;
1314   /* change->kind = svn_node_unknown;  */
1315   change->deleting = replaces_rev;
1316   change->copyfrom_path = apr_pstrdup(eb->edit_pool, src_relpath);
1317   change->copyfrom_rev = src_revision;
1318
1319   /* We need the source's kind to know whether to call add_directory()
1320      or add_file() later on.  */
1321   SVN_ERR(eb->fetch_kind_func(&change->kind, eb->fetch_kind_baton,
1322                               change->copyfrom_path,
1323                               change->copyfrom_rev,
1324                               scratch_pool));
1325
1326   /* Note: this node may later have alter_*() called on it.  */
1327
1328   return SVN_NO_ERROR;
1329 }
1330
1331 /* This implements svn_editor_cb_move_t */
1332 static svn_error_t *
1333 move_cb(void *baton,
1334         const char *src_relpath,
1335         svn_revnum_t src_revision,
1336         const char *dst_relpath,
1337         svn_revnum_t replaces_rev,
1338         apr_pool_t *scratch_pool)
1339 {
1340   struct editor_baton *eb = baton;
1341   struct change_node *change;
1342
1343   /* Remap a move into a DELETE + COPY.  */
1344
1345   change = insert_change(src_relpath, eb->changes);
1346   change->action = RESTRUCTURE_DELETE;
1347   /* change->kind = svn_node_unknown;  */
1348   change->deleting = src_revision;
1349
1350   change = insert_change(dst_relpath, eb->changes);
1351   change->action = RESTRUCTURE_ADD;
1352   /* change->kind = svn_node_unknown;  */
1353   change->deleting = replaces_rev;
1354   change->copyfrom_path = apr_pstrdup(eb->edit_pool, src_relpath);
1355   change->copyfrom_rev = src_revision;
1356
1357   /* We need the source's kind to know whether to call add_directory()
1358      or add_file() later on.  */
1359   SVN_ERR(eb->fetch_kind_func(&change->kind, eb->fetch_kind_baton,
1360                               change->copyfrom_path,
1361                               change->copyfrom_rev,
1362                               scratch_pool));
1363
1364   /* Note: this node may later have alter_*() called on it.  */
1365
1366   return SVN_NO_ERROR;
1367 }
1368
1369 static int
1370 count_components(const char *relpath)
1371 {
1372   int count = 1;
1373   const char *slash = strchr(relpath, '/');
1374
1375   while (slash != NULL)
1376     {
1377       ++count;
1378       slash = strchr(slash + 1, '/');
1379     }
1380   return count;
1381 }
1382
1383
1384 static int
1385 sort_deletes_first(const svn_sort__item_t *item1,
1386                    const svn_sort__item_t *item2)
1387 {
1388   const char *relpath1 = item1->key;
1389   const char *relpath2 = item2->key;
1390   const struct change_node *change1 = item1->value;
1391   const struct change_node *change2 = item2->value;
1392   const char *slash1;
1393   const char *slash2;
1394   ptrdiff_t len1;
1395   ptrdiff_t len2;
1396
1397   /* Force the root to always sort first. Otherwise, it may look like a
1398      sibling of its children (no slashes), and could get sorted *after*
1399      any children that get deleted.  */
1400   if (*relpath1 == '\0')
1401     return -1;
1402   if (*relpath2 == '\0')
1403     return 1;
1404
1405   /* Are these two items siblings? The 'if' statement tests if they are
1406      siblings in the root directory, or that slashes were found in both
1407      paths, that the length of the paths to those slashes match, and that
1408      the path contents up to those slashes also match.  */
1409   slash1 = strrchr(relpath1, '/');
1410   slash2 = strrchr(relpath2, '/');
1411   if ((slash1 == NULL && slash2 == NULL)
1412       || (slash1 != NULL
1413           && slash2 != NULL
1414           && (len1 = slash1 - relpath1) == (len2 = slash2 - relpath2)
1415           && memcmp(relpath1, relpath2, len1) == 0))
1416     {
1417       if (change1->action == RESTRUCTURE_DELETE)
1418         {
1419           if (change2->action == RESTRUCTURE_DELETE)
1420             {
1421               /* If both items are being deleted, then we don't care about
1422                  the order. State they are equal.  */
1423               return 0;
1424             }
1425
1426           /* ITEM1 is being deleted. Sort it before the surviving item.  */
1427           return -1;
1428         }
1429       if (change2->action == RESTRUCTURE_DELETE)
1430         /* ITEM2 is being deleted. Sort it before the surviving item.  */
1431         return 1;
1432
1433       /* Normally, we don't care about the ordering of two siblings. However,
1434          if these siblings are directories, then we need to provide an
1435          ordering so that the quicksort algorithm will further sort them
1436          relative to the maybe-directory's children.
1437
1438          Without this additional ordering, we could see that A/B/E and A/B/F
1439          are equal. And then A/B/E/child is sorted before A/B/F. But since
1440          E and F are "equal", A/B/E could arrive *after* A/B/F and after the
1441          A/B/E/child node.  */
1442
1443       /* FALLTHROUGH */
1444     }
1445
1446   /* Paths-to-be-deleted with fewer components always sort earlier.
1447
1448      For example, gamma will sort before E/alpha.
1449
1450      Without this test, E/alpha lexicographically sorts before gamma,
1451      but gamma sorts before E when gamma is to be deleted. This kind of
1452      ordering would place E/alpha before E. Not good.
1453
1454      With this test, gamma sorts before E/alpha. E and E/alpha are then
1455      sorted by svn_path_compare_paths() (which places E before E/alpha).  */
1456   if (change1->action == RESTRUCTURE_DELETE
1457       || change2->action == RESTRUCTURE_DELETE)
1458     {
1459       int count1 = count_components(relpath1);
1460       int count2 = count_components(relpath2);
1461
1462       if (count1 < count2 && change1->action == RESTRUCTURE_DELETE)
1463         return -1;
1464       if (count1 > count2 && change2->action == RESTRUCTURE_DELETE)
1465         return 1;
1466     }
1467
1468   /* Use svn_path_compare_paths() to get correct depth-based ordering.  */
1469   return svn_path_compare_paths(relpath1, relpath2);
1470 }
1471
1472
1473 static const apr_array_header_t *
1474 get_sorted_paths(apr_hash_t *changes,
1475                  const char *base_relpath,
1476                  apr_pool_t *scratch_pool)
1477 {
1478   const apr_array_header_t *items;
1479   apr_array_header_t *paths;
1480   int i;
1481
1482   /* Construct a sorted array of svn_sort__item_t structs. Within a given
1483      directory, nodes that are to be deleted will appear first.  */
1484   items = svn_sort__hash(changes, sort_deletes_first, scratch_pool);
1485
1486   /* Build a new array with just the paths, trimmed to relative paths for
1487      the Ev1 drive.  */
1488   paths = apr_array_make(scratch_pool, items->nelts, sizeof(const char *));
1489   for (i = items->nelts; i--; )
1490     {
1491       const svn_sort__item_t *item;
1492
1493       item = &APR_ARRAY_IDX(items, i, const svn_sort__item_t);
1494       APR_ARRAY_IDX(paths, i, const char *)
1495         = svn_relpath_skip_ancestor(base_relpath, item->key);
1496     }
1497
1498   /* We didn't use PUSH, so set the proper number of elements.  */
1499   paths->nelts = items->nelts;
1500
1501   return paths;
1502 }
1503
1504
1505 static svn_error_t *
1506 drive_ev1_props(const struct editor_baton *eb,
1507                 const char *repos_relpath,
1508                 const struct change_node *change,
1509                 void *node_baton,
1510                 apr_pool_t *scratch_pool)
1511 {
1512   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1513   apr_hash_t *old_props;
1514   apr_array_header_t *propdiffs;
1515   int i;
1516
1517   /* If there are no properties to install, then just exit.  */
1518   if (change->props == NULL)
1519     return SVN_NO_ERROR;
1520
1521   if (change->copyfrom_path)
1522     {
1523       /* The pristine properties are from the copy/move source.  */
1524       SVN_ERR(eb->fetch_props_func(&old_props, eb->fetch_props_baton,
1525                                    change->copyfrom_path,
1526                                    change->copyfrom_rev,
1527                                    scratch_pool, iterpool));
1528     }
1529   else if (change->action == RESTRUCTURE_ADD)
1530     {
1531       /* Locally-added nodes have no pristine properties.
1532
1533          Note: we can use iterpool; this hash only needs to survive to
1534          the propdiffs call, and there are no contents to preserve.  */
1535       old_props = apr_hash_make(iterpool);
1536     }
1537   else
1538     {
1539       /* Fetch the pristine properties for whatever we're editing.  */
1540       SVN_ERR(eb->fetch_props_func(&old_props, eb->fetch_props_baton,
1541                                    repos_relpath, change->changing,
1542                                    scratch_pool, iterpool));
1543     }
1544
1545   SVN_ERR(svn_prop_diffs(&propdiffs, change->props, old_props, scratch_pool));
1546
1547   for (i = 0; i < propdiffs->nelts; i++)
1548     {
1549       /* Note: the array returned by svn_prop_diffs() is an array of
1550          actual structures, not pointers to them. */
1551       const svn_prop_t *prop = &APR_ARRAY_IDX(propdiffs, i, svn_prop_t);
1552
1553       svn_pool_clear(iterpool);
1554
1555       if (change->kind == svn_node_dir)
1556         SVN_ERR(eb->deditor->change_dir_prop(node_baton,
1557                                              prop->name, prop->value,
1558                                              iterpool));
1559       else
1560         SVN_ERR(eb->deditor->change_file_prop(node_baton,
1561                                               prop->name, prop->value,
1562                                               iterpool));
1563     }
1564
1565   /* Handle the funky unlock protocol. Note: only possibly on files.  */
1566   if (change->unlock)
1567     {
1568       SVN_ERR_ASSERT(change->kind == svn_node_file);
1569       SVN_ERR(eb->deditor->change_file_prop(node_baton,
1570                                             SVN_PROP_ENTRY_LOCK_TOKEN, NULL,
1571                                             iterpool));
1572     }
1573
1574   svn_pool_destroy(iterpool);
1575   return SVN_NO_ERROR;
1576 }
1577
1578
1579 /* Conforms to svn_delta_path_driver_cb_func_t  */
1580 static svn_error_t *
1581 apply_change(void **dir_baton,
1582              void *parent_baton,
1583              void *callback_baton,
1584              const char *ev1_relpath,
1585              apr_pool_t *result_pool)
1586 {
1587   /* ### fix this?  */
1588   apr_pool_t *scratch_pool = result_pool;
1589   const struct editor_baton *eb = callback_baton;
1590   const struct change_node *change;
1591   const char *relpath;
1592   void *file_baton = NULL;
1593
1594   /* Typically, we are not creating new directory batons.  */
1595   *dir_baton = NULL;
1596
1597   relpath = svn_relpath_join(eb->base_relpath, ev1_relpath, scratch_pool);
1598   change = svn_hash_gets(eb->changes, relpath);
1599
1600   /* The callback should only be called for paths in CHANGES.  */
1601   SVN_ERR_ASSERT(change != NULL);
1602
1603   /* Are we editing the root of the tree?  */
1604   if (parent_baton == NULL)
1605     {
1606       /* The root was opened in start_edit_func()  */
1607       *dir_baton = eb->root.baton;
1608
1609       /* Only property edits are allowed on the root.  */
1610       SVN_ERR_ASSERT(change->action == RESTRUCTURE_NONE);
1611       SVN_ERR(drive_ev1_props(eb, relpath, change, *dir_baton, scratch_pool));
1612
1613       /* No further action possible for the root.  */
1614       return SVN_NO_ERROR;
1615     }
1616
1617   if (change->action == RESTRUCTURE_DELETE)
1618     {
1619       SVN_ERR(eb->deditor->delete_entry(ev1_relpath, change->deleting,
1620                                         parent_baton, scratch_pool));
1621
1622       /* No futher action possible for this node.  */
1623       return SVN_NO_ERROR;
1624     }
1625
1626   /* If we're not deleting this node, then we should know its kind.  */
1627   SVN_ERR_ASSERT(change->kind != svn_node_unknown);
1628
1629   if (change->action == RESTRUCTURE_ADD_ABSENT)
1630     {
1631       if (change->kind == svn_node_dir)
1632         SVN_ERR(eb->deditor->absent_directory(ev1_relpath, parent_baton,
1633                                               scratch_pool));
1634       else
1635         SVN_ERR(eb->deditor->absent_file(ev1_relpath, parent_baton,
1636                                          scratch_pool));
1637
1638       /* No further action possible for this node.  */
1639       return SVN_NO_ERROR;
1640     }
1641   /* RESTRUCTURE_NONE or RESTRUCTURE_ADD  */
1642
1643   if (change->action == RESTRUCTURE_ADD)
1644     {
1645       const char *copyfrom_url = NULL;
1646       svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
1647
1648       /* Do we have an old node to delete first?  */
1649       if (SVN_IS_VALID_REVNUM(change->deleting))
1650         SVN_ERR(eb->deditor->delete_entry(ev1_relpath, change->deleting,
1651                                           parent_baton, scratch_pool));
1652
1653       /* Are we copying the node from somewhere?  */
1654       if (change->copyfrom_path)
1655         {
1656           if (eb->repos_root)
1657             copyfrom_url = svn_path_url_add_component2(eb->repos_root,
1658                                                        change->copyfrom_path,
1659                                                        scratch_pool);
1660           else
1661             {
1662               copyfrom_url = change->copyfrom_path;
1663
1664               /* Make this an FS path by prepending "/" */
1665               if (copyfrom_url[0] != '/')
1666                 copyfrom_url = apr_pstrcat(scratch_pool, "/",
1667                                            copyfrom_url, SVN_VA_NULL);
1668             }
1669
1670           copyfrom_rev = change->copyfrom_rev;
1671         }
1672
1673       if (change->kind == svn_node_dir)
1674         SVN_ERR(eb->deditor->add_directory(ev1_relpath, parent_baton,
1675                                            copyfrom_url, copyfrom_rev,
1676                                            result_pool, dir_baton));
1677       else
1678         SVN_ERR(eb->deditor->add_file(ev1_relpath, parent_baton,
1679                                       copyfrom_url, copyfrom_rev,
1680                                       result_pool, &file_baton));
1681     }
1682   else
1683     {
1684       if (change->kind == svn_node_dir)
1685         SVN_ERR(eb->deditor->open_directory(ev1_relpath, parent_baton,
1686                                             change->changing,
1687                                             result_pool, dir_baton));
1688       else
1689         SVN_ERR(eb->deditor->open_file(ev1_relpath, parent_baton,
1690                                        change->changing,
1691                                        result_pool, &file_baton));
1692     }
1693
1694   /* Apply any properties in CHANGE to the node.  */
1695   if (change->kind == svn_node_dir)
1696     SVN_ERR(drive_ev1_props(eb, relpath, change, *dir_baton, scratch_pool));
1697   else
1698     SVN_ERR(drive_ev1_props(eb, relpath, change, file_baton, scratch_pool));
1699
1700   if (change->contents_changed && change->contents_abspath)
1701     {
1702       svn_txdelta_window_handler_t handler;
1703       void *handler_baton;
1704       svn_stream_t *contents;
1705
1706       /* ### would be nice to have a BASE_CHECKSUM, but hey: this is the
1707          ### shim code...  */
1708       SVN_ERR(eb->deditor->apply_textdelta(file_baton, NULL, scratch_pool,
1709                                            &handler, &handler_baton));
1710       SVN_ERR(svn_stream_open_readonly(&contents, change->contents_abspath,
1711                                        scratch_pool, scratch_pool));
1712       /* ### it would be nice to send a true txdelta here, but whatever.  */
1713       SVN_ERR(svn_txdelta_send_stream(contents, handler, handler_baton,
1714                                       NULL, scratch_pool));
1715       SVN_ERR(svn_stream_close(contents));
1716     }
1717
1718   if (file_baton)
1719     {
1720       const char *digest = svn_checksum_to_cstring(change->checksum,
1721                                                    scratch_pool);
1722
1723       SVN_ERR(eb->deditor->close_file(file_baton, digest, scratch_pool));
1724     }
1725
1726   return SVN_NO_ERROR;
1727 }
1728
1729
1730 static svn_error_t *
1731 drive_changes(const struct editor_baton *eb,
1732               apr_pool_t *scratch_pool)
1733 {
1734   struct change_node *change;
1735   const apr_array_header_t *paths;
1736
1737   /* If we never opened a root baton, then the caller aborted the editor
1738      before it even began. There is nothing to do. Bail.  */
1739   if (eb->root.baton == NULL)
1740     return SVN_NO_ERROR;
1741
1742   /* We need to make the path driver believe we want to make changes to
1743      the root. Otherwise, it will attempt an open_root(), which we already
1744      did in start_edit_func(). We can forge up a change record, if one
1745      does not already exist.  */
1746   change = insert_change(eb->base_relpath, eb->changes);
1747   change->kind = svn_node_dir;
1748   /* No property changes (tho they might exist from a real change).  */
1749
1750   /* Get a sorted list of Ev1-relative paths.  */
1751   paths = get_sorted_paths(eb->changes, eb->base_relpath, scratch_pool);
1752   SVN_ERR(svn_delta_path_driver2(eb->deditor, eb->dedit_baton, paths,
1753                                  FALSE, apply_change, (void *)eb,
1754                                  scratch_pool));
1755
1756   return SVN_NO_ERROR;
1757 }
1758
1759
1760 /* This implements svn_editor_cb_complete_t */
1761 static svn_error_t *
1762 complete_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 */
1784 static svn_error_t *
1785 abort_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
1811 static svn_error_t *
1812 start_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
1828 static svn_error_t *
1829 target_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
1841 static svn_error_t *
1842 do_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  */
1885 svn_error_t *
1886 svn_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
1963 svn_delta_shim_callbacks_t *
1964 svn_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
1975 svn_error_t *
1976 svn_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 }