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 *
file_rev_handler_wrapper(void * baton,const char * path,svn_revnum_t rev,apr_hash_t * rev_props,svn_boolean_t result_of_merge,svn_txdelta_window_handler_t * delta_handler,void ** delta_baton,apr_array_header_t * prop_diffs,apr_pool_t * pool)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
svn_compat_wrap_file_rev_handler(svn_file_rev_handler_t * handler2,void ** handler2_baton,svn_file_rev_handler_old_t handler,void * handler_baton,apr_pool_t * pool)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
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 *
locate_change(struct ev2_edit_baton * eb,const char * relpath)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 *
apply_propedit(struct ev2_edit_baton * eb,const char * relpath,svn_node_kind_t kind,svn_revnum_t base_revision,const char * name,const svn_string_t * value,apr_pool_t * scratch_pool)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 *
get_children(struct ev2_edit_baton * eb,const char * path,apr_pool_t * pool)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 *
process_actions(struct ev2_edit_baton * eb,const char * repos_relpath,const struct change_node * change,apr_pool_t * scratch_pool)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 *
run_ev2_actions(struct ev2_edit_baton * eb,apr_pool_t * scratch_pool)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 *
map_to_repos_relpath(struct ev2_edit_baton * eb,const char * path_or_url,apr_pool_t * result_pool)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 *
ev2_set_target_revision(void * edit_baton,svn_revnum_t target_revision,apr_pool_t * scratch_pool)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 *
ev2_open_root(void * edit_baton,svn_revnum_t base_revision,apr_pool_t * result_pool,void ** root_baton)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 *
ev2_delete_entry(const char * path,svn_revnum_t revision,void * parent_baton,apr_pool_t * scratch_pool)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 *
ev2_add_directory(const char * path,void * parent_baton,const char * copyfrom_path,svn_revnum_t copyfrom_revision,apr_pool_t * result_pool,void ** child_baton)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 *
ev2_open_directory(const char * path,void * parent_baton,svn_revnum_t base_revision,apr_pool_t * result_pool,void ** child_baton)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 *
ev2_change_dir_prop(void * dir_baton,const char * name,const svn_string_t * value,apr_pool_t * scratch_pool)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 *
ev2_close_directory(void * dir_baton,apr_pool_t * scratch_pool)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 *
ev2_absent_directory(const char * path,void * parent_baton,apr_pool_t * scratch_pool)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 *
ev2_add_file(const char * path,void * parent_baton,const char * copyfrom_path,svn_revnum_t copyfrom_revision,apr_pool_t * result_pool,void ** file_baton)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 *
ev2_open_file(const char * path,void * parent_baton,svn_revnum_t base_revision,apr_pool_t * result_pool,void ** file_baton)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 *
window_handler(svn_txdelta_window_t * window,void * baton)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 *
open_delta_base(svn_stream_t ** stream,void * baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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 *
open_delta_target(svn_stream_t ** stream,void * baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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 *
ev2_apply_textdelta(void * file_baton,const char * base_checksum,apr_pool_t * result_pool,svn_txdelta_window_handler_t * handler,void ** handler_baton)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 *
ev2_change_file_prop(void * file_baton,const char * name,const svn_string_t * value,apr_pool_t * scratch_pool)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 *
ev2_close_file(void * file_baton,const char * text_checksum,apr_pool_t * scratch_pool)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 *
ev2_absent_file(const char * path,void * parent_baton,apr_pool_t * scratch_pool)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 *
ev2_close_edit(void * edit_baton,apr_pool_t * scratch_pool)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 *
ev2_abort_edit(void * edit_baton,apr_pool_t * scratch_pool)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 *
svn_delta__delta_from_editor(const svn_delta_editor_t ** deditor,void ** dedit_baton,svn_editor_t * editor,svn_delta__unlock_func_t unlock_func,void * unlock_baton,svn_boolean_t * found_abs_paths,const char * repos_root,const char * base_relpath,svn_delta_fetch_props_func_t fetch_props_func,void * fetch_props_baton,svn_delta_fetch_base_func_t fetch_base_func,void * fetch_base_baton,struct svn_delta__extra_baton * exb,apr_pool_t * pool)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 *
insert_change(const char * relpath,apr_hash_t * changes)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 *
add_directory_cb(void * baton,const char * relpath,const apr_array_header_t * children,apr_hash_t * props,svn_revnum_t replaces_rev,apr_pool_t * scratch_pool)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 *
add_file_cb(void * baton,const char * relpath,const svn_checksum_t * checksum,svn_stream_t * contents,apr_hash_t * props,svn_revnum_t replaces_rev,apr_pool_t * scratch_pool)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 *
add_symlink_cb(void * baton,const char * relpath,const char * target,apr_hash_t * props,svn_revnum_t replaces_rev,apr_pool_t * scratch_pool)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 *
add_absent_cb(void * baton,const char * relpath,svn_node_kind_t kind,svn_revnum_t replaces_rev,apr_pool_t * scratch_pool)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 *
alter_directory_cb(void * baton,const char * relpath,svn_revnum_t revision,const apr_array_header_t * children,apr_hash_t * props,apr_pool_t * scratch_pool)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 *
alter_file_cb(void * baton,const char * relpath,svn_revnum_t revision,const svn_checksum_t * checksum,svn_stream_t * contents,apr_hash_t * props,apr_pool_t * scratch_pool)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 *
alter_symlink_cb(void * baton,const char * relpath,svn_revnum_t revision,const char * target,apr_hash_t * props,apr_pool_t * scratch_pool)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 *
delete_cb(void * baton,const char * relpath,svn_revnum_t revision,apr_pool_t * scratch_pool)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 *
copy_cb(void * baton,const char * src_relpath,svn_revnum_t src_revision,const char * dst_relpath,svn_revnum_t replaces_rev,apr_pool_t * scratch_pool)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 *
move_cb(void * baton,const char * src_relpath,svn_revnum_t src_revision,const char * dst_relpath,svn_revnum_t replaces_rev,apr_pool_t * scratch_pool)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
count_components(const char * relpath)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
sort_deletes_first(const svn_sort__item_t * item1,const svn_sort__item_t * item2)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 *
get_sorted_paths(apr_hash_t * changes,const char * base_relpath,apr_pool_t * scratch_pool)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 *
drive_ev1_props(const struct editor_baton * eb,const char * repos_relpath,const struct change_node * change,void * node_baton,apr_pool_t * scratch_pool)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 *
apply_change(void ** dir_baton,void * parent_baton,void * callback_baton,const char * ev1_relpath,apr_pool_t * result_pool)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 *
drive_changes(const struct editor_baton * eb,apr_pool_t * scratch_pool)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 *
complete_cb(void * baton,apr_pool_t * scratch_pool)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 *
abort_cb(void * baton,apr_pool_t * scratch_pool)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 *
start_edit_func(void * baton,svn_revnum_t base_revision)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 *
target_revision_func(void * baton,svn_revnum_t target_revision,apr_pool_t * scratch_pool)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 *
do_unlock(void * baton,const char * path,apr_pool_t * scratch_pool)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 *
svn_delta__editor_from_delta(svn_editor_t ** editor_p,struct svn_delta__extra_baton ** exb,svn_delta__unlock_func_t * unlock_func,void ** unlock_baton,const svn_delta_editor_t * deditor,void * dedit_baton,svn_boolean_t * send_abs_paths,const char * repos_root,const char * base_relpath,svn_cancel_func_t cancel_func,void * cancel_baton,svn_delta_fetch_kind_func_t fetch_kind_func,void * fetch_kind_baton,svn_delta_fetch_props_func_t fetch_props_func,void * fetch_props_baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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 *
svn_delta_shim_callbacks_default(apr_pool_t * result_pool)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 *
svn_editor__insert_shims(const svn_delta_editor_t ** deditor_out,void ** dedit_baton_out,const svn_delta_editor_t * deditor_in,void * dedit_baton_in,const char * repos_root,const char * base_relpath,svn_delta_shim_callbacks_t * shim_callbacks,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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 }
2038