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
41struct 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. */
47static svn_error_t *
48file_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
73void
74svn_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
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
113struct 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
143struct 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
153struct 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
161enum 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.  */
170struct 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
204static struct change_node *
205locate_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
229static svn_error_t *
230apply_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. */
285static apr_array_header_t *
286get_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
315static svn_error_t *
316process_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
450static svn_error_t *
451run_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
478static const char *
479map_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
497static svn_error_t *
498ev2_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
511static svn_error_t *
512ev2_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
532static svn_error_t *
533ev2_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
558static svn_error_t *
559ev2_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
607static svn_error_t *
608ev2_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
638static svn_error_t *
639ev2_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
652static svn_error_t *
653ev2_close_directory(void *dir_baton,
654                    apr_pool_t *scratch_pool)
655{
656  return SVN_NO_ERROR;
657}
658
659static svn_error_t *
660ev2_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
675static svn_error_t *
676ev2_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
722static svn_error_t *
723ev2_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
765struct 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
775static svn_error_t *
776window_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
793static svn_error_t *
794ev2_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
837static svn_error_t *
838ev2_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
864static svn_error_t *
865ev2_close_file(void *file_baton,
866               const char *text_checksum,
867               apr_pool_t *scratch_pool)
868{
869  return SVN_NO_ERROR;
870}
871
872static svn_error_t *
873ev2_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
888static svn_error_t *
889ev2_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
899static svn_error_t *
900ev2_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 */
932svn_error_t *
933svn_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.  */
1002struct operation {
1003  /* ### leave these two here for now. still used.  */
1004  svn_revnum_t base_revision;
1005  void *baton;
1006};
1007
1008struct 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.  */
1032static struct change_node *
1033insert_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 */
1057static svn_error_t *
1058add_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 */
1077static svn_error_t *
1078add_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 */
1116static svn_error_t *
1117add_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 */
1139static svn_error_t *
1140add_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 */
1157static svn_error_t *
1158alter_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 */
1182static svn_error_t *
1183alter_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 */
1234static svn_error_t *
1235alter_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 */
1250static svn_error_t *
1251delete_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 */
1267static svn_error_t *
1268copy_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 */
1297static svn_error_t *
1298move_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 */
1335static svn_error_t *
1336rotate_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
1345static int
1346count_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
1360static int
1361sort_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
1449static const apr_array_header_t *
1450get_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
1481static svn_error_t *
1482drive_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  */
1556static svn_error_t *
1557apply_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
1706static svn_error_t *
1707drive_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 */
1737static svn_error_t *
1738complete_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 */
1760static svn_error_t *
1761abort_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
1787static svn_error_t *
1788start_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
1804static svn_error_t *
1805target_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
1817static svn_error_t *
1818do_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 */
1861svn_error_t *
1862svn_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
1940svn_delta_shim_callbacks_t *
1941svn_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
1952svn_error_t *
1953svn_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}
2015