2 * compat.c : Wrappers and callbacks for compatibility.
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
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"
33 #include "svn_props.h"
34 #include "svn_pools.h"
36 #include "svn_private_config.h"
38 #include "private/svn_delta_private.h"
39 #include "private/svn_sorts_private.h"
40 #include "svn_private_config.h"
43 struct file_rev_handler_wrapper_baton {
45 svn_file_rev_handler_old_t handler;
48 /* This implements svn_file_rev_handler_t. */
50 file_rev_handler_wrapper(void *baton,
53 apr_hash_t *rev_props,
54 svn_boolean_t result_of_merge,
55 svn_txdelta_window_handler_t *delta_handler,
57 apr_array_header_t *prop_diffs,
60 struct file_rev_handler_wrapper_baton *fwb = baton;
63 return fwb->handler(fwb->baton,
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,
82 struct file_rev_handler_wrapper_baton *fwb = apr_pcalloc(pool, sizeof(*fwb));
84 /* Set the user provided old format callback in the baton. */
85 fwb->baton = handler_baton;
86 fwb->handler = handler;
88 *handler2_baton = fwb;
89 *handler2 = file_rev_handler_wrapper;
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.
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.
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
115 struct ev2_edit_baton
117 svn_editor_t *editor;
119 apr_hash_t *changes; /* REPOS_RELPATH -> struct change_node */
121 apr_array_header_t *path_order;
124 /* For calculating relpaths from Ev1 copyfrom urls. */
125 const char *repos_root;
126 const char *base_relpath;
128 apr_pool_t *edit_pool;
129 struct svn_delta__extra_baton *exb;
130 svn_boolean_t closed;
132 svn_boolean_t *found_abs_paths; /* Did we strip an incoming '/' from the
135 svn_delta_fetch_props_func_t fetch_props_func;
136 void *fetch_props_baton;
138 svn_delta_fetch_base_func_t fetch_base_func;
139 void *fetch_base_baton;
141 svn_delta__unlock_func_t do_unlock;
147 struct ev2_edit_baton *eb;
149 svn_revnum_t base_revision;
151 const char *copyfrom_relpath;
152 svn_revnum_t copyfrom_rev;
155 struct ev2_file_baton
157 struct ev2_edit_baton *eb;
159 svn_revnum_t base_revision;
160 const char *delta_base;
163 enum restructure_action_t
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 */
171 /* Records everything about how this node is to be changed. */
174 /* what kind of (tree) restructure is occurring at this node? */
175 enum restructure_action_t action;
177 svn_node_kind_t kind; /* the NEW kind of this node */
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;
191 apr_hash_t *props; /* new/final set of props to apply */
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 */
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;
202 /* Record whether an incoming propchange unlocked this node. */
203 svn_boolean_t unlock;
207 static struct change_node *
208 locate_change(struct ev2_edit_baton *eb,
211 struct change_node *change = svn_hash_gets(eb->changes, relpath);
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;
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;
226 svn_hash_sets(eb->changes, relpath, change);
233 apply_propedit(struct ev2_edit_baton *eb,
235 svn_node_kind_t kind,
236 svn_revnum_t base_revision,
238 const svn_string_t *value,
239 apr_pool_t *scratch_pool)
241 struct change_node *change = locate_change(eb, relpath);
243 SVN_ERR_ASSERT(change->kind == svn_node_unknown || change->kind == kind);
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;
251 if (change->props == NULL)
253 /* Fetch the original set of properties. We'll apply edits to create
254 the new/target set of properties.
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. */
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);
269 SVN_ERR(eb->fetch_props_func(&change->props,
270 eb->fetch_props_baton,
271 relpath, base_revision,
272 eb->edit_pool, scratch_pool));
276 svn_hash_sets(change->props, name, NULL);
278 svn_hash_sets(change->props,
279 apr_pstrdup(eb->edit_pool, name),
280 svn_string_dup(value, eb->edit_pool));
286 /* Find all the paths which are immediate children of PATH and return their
287 basenames in a list. */
288 static apr_array_header_t *
289 get_children(struct ev2_edit_baton *eb,
293 apr_array_header_t *children = apr_array_make(pool, 1, sizeof(const char *));
294 apr_hash_index_t *hi;
296 for (hi = apr_hash_first(pool, eb->changes); hi; hi = apr_hash_next(hi))
298 const char *repos_relpath = apr_hash_this_key(hi);
301 /* Find potential children. */
302 child = svn_relpath_skip_ancestor(path, repos_relpath);
303 if (!child || !*child)
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)
311 APR_ARRAY_PUSH(children, const char *) = child;
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)
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;
329 SVN_ERR_ASSERT(change != NULL);
332 SVN_ERR(eb->do_unlock(eb->unlock_baton, repos_relpath, scratch_pool));
334 if (change->action == RESTRUCTURE_DELETE)
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,
341 /* No further work possible on this node. */
344 if (change->action == RESTRUCTURE_ADD_ABSENT)
346 SVN_ERR(svn_editor_add_absent(eb->editor, repos_relpath,
347 change->kind, change->deleting));
349 /* No further work possible on this node. */
353 if (change->contents_changed)
355 /* We can only set text on files. */
356 /* ### validate we aren't overwriting KIND? */
357 kind = svn_node_file;
359 if (change->contents_abspath)
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));
369 contents = svn_stream_empty(scratch_pool);
370 checksum = svn_checksum_empty_checksum(svn_checksum_sha1,
375 if (change->props != NULL)
377 /* ### validate we aren't overwriting KIND? */
379 props = change->props;
382 if (change->action == RESTRUCTURE_ADD)
384 /* An add might be a replace. Grab the revnum we're replacing. */
385 svn_revnum_t replaces_rev = change->deleting;
389 if (change->copyfrom_path != NULL)
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. */
398 /* If no properties were defined, then use an empty set. */
400 props = apr_hash_make(scratch_pool);
402 if (kind == svn_node_dir)
404 const apr_array_header_t *children;
406 children = get_children(eb, repos_relpath, scratch_pool);
407 SVN_ERR(svn_editor_add_directory(eb->editor, repos_relpath,
413 /* If this file was added, but apply_txdelta() was not
414 called (i.e., CONTENTS_CHANGED is FALSE), then we're adding
416 if (change->contents_abspath == NULL)
418 contents = svn_stream_empty(scratch_pool);
419 checksum = svn_checksum_empty_checksum(svn_checksum_sha1,
423 SVN_ERR(svn_editor_add_file(eb->editor, repos_relpath,
424 checksum, contents, props,
428 /* No further work possible on this node. */
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);
438 if (props || contents)
440 /* Changes to properties or content should have indicated the revision
441 it was intending to change.
443 Oop. Not true. The node may be locally-added. */
445 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(change->changing));
448 /* ### we need to gather up the target set of children */
450 if (kind == svn_node_dir)
451 SVN_ERR(svn_editor_alter_directory(eb->editor, repos_relpath,
452 change->changing, NULL, props));
454 SVN_ERR(svn_editor_alter_file(eb->editor, repos_relpath,
456 checksum, contents, props));
463 run_ev2_actions(struct ev2_edit_baton *eb,
464 apr_pool_t *scratch_pool)
466 apr_pool_t *iterpool;
468 iterpool = svn_pool_create(scratch_pool);
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)
474 const char *repos_relpath = APR_ARRAY_IDX(eb->path_order,
477 const struct change_node *change = svn_hash_gets(eb->changes,
480 svn_pool_clear(iterpool);
482 SVN_ERR(process_actions(eb, repos_relpath, change, iterpool));
484 svn_pool_destroy(iterpool);
491 map_to_repos_relpath(struct ev2_edit_baton *eb,
492 const char *path_or_url,
493 apr_pool_t *result_pool)
495 if (svn_path_is_url(path_or_url))
497 return svn_uri_skip_ancestor(eb->repos_root, path_or_url, result_pool);
501 return svn_relpath_join(eb->base_relpath,
502 path_or_url[0] == '/'
503 ? path_or_url + 1 : path_or_url,
510 ev2_set_target_revision(void *edit_baton,
511 svn_revnum_t target_revision,
512 apr_pool_t *scratch_pool)
514 struct ev2_edit_baton *eb = edit_baton;
516 if (eb->exb->target_revision)
517 SVN_ERR(eb->exb->target_revision(eb->exb->baton, target_revision,
524 ev2_open_root(void *edit_baton,
525 svn_revnum_t base_revision,
526 apr_pool_t *result_pool,
529 struct ev2_dir_baton *db = apr_pcalloc(result_pool, sizeof(*db));
530 struct ev2_edit_baton *eb = edit_baton;
533 db->path = apr_pstrdup(eb->edit_pool, eb->base_relpath);
534 db->base_revision = base_revision;
538 if (eb->exb->start_edit)
539 SVN_ERR(eb->exb->start_edit(eb->exb->baton, base_revision));
545 ev2_delete_entry(const char *path,
546 svn_revnum_t revision,
548 apr_pool_t *scratch_pool)
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);
555 if (SVN_IS_VALID_REVNUM(revision))
556 base_revision = revision;
558 base_revision = pb->base_revision;
560 SVN_ERR_ASSERT(change->action == RESTRUCTURE_NONE);
561 change->action = RESTRUCTURE_DELETE;
563 SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(change->deleting)
564 || change->deleting == base_revision);
565 change->deleting = base_revision;
571 ev2_add_directory(const char *path,
573 const char *copyfrom_path,
574 svn_revnum_t copyfrom_revision,
575 apr_pool_t *result_pool,
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);
585 /* ### assert that RESTRUCTURE is NONE or DELETE? */
586 change->action = RESTRUCTURE_ADD;
587 change->kind = svn_node_dir;
590 cb->path = apr_pstrdup(result_pool, relpath);
591 cb->base_revision = pb->base_revision;
596 if (pb->copyfrom_relpath)
598 const char *name = svn_relpath_basename(relpath, scratch_pool);
599 cb->copyfrom_relpath = svn_relpath_join(pb->copyfrom_relpath, name,
601 cb->copyfrom_rev = pb->copyfrom_rev;
608 change->copyfrom_path = map_to_repos_relpath(pb->eb, copyfrom_path,
610 change->copyfrom_rev = copyfrom_revision;
612 cb->copyfrom_relpath = change->copyfrom_path;
613 cb->copyfrom_rev = change->copyfrom_rev;
620 ev2_open_directory(const char *path,
622 svn_revnum_t base_revision,
623 apr_pool_t *result_pool,
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);
633 db->path = apr_pstrdup(result_pool, relpath);
634 db->base_revision = base_revision;
636 if (pb->copyfrom_relpath)
638 /* We are inside a copy. */
639 const char *name = svn_relpath_basename(relpath, scratch_pool);
641 db->copyfrom_relpath = svn_relpath_join(pb->copyfrom_relpath, name,
643 db->copyfrom_rev = pb->copyfrom_rev;
651 ev2_change_dir_prop(void *dir_baton,
653 const svn_string_t *value,
654 apr_pool_t *scratch_pool)
656 struct ev2_dir_baton *db = dir_baton;
658 SVN_ERR(apply_propedit(db->eb, db->path, svn_node_dir, db->base_revision,
659 name, value, scratch_pool));
665 ev2_close_directory(void *dir_baton,
666 apr_pool_t *scratch_pool)
672 ev2_absent_directory(const char *path,
674 apr_pool_t *scratch_pool)
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);
680 /* ### assert that RESTRUCTURE is NONE or DELETE? */
681 change->action = RESTRUCTURE_ADD_ABSENT;
682 change->kind = svn_node_dir;
688 ev2_add_file(const char *path,
690 const char *copyfrom_path,
691 svn_revnum_t copyfrom_revision,
692 apr_pool_t *result_pool,
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);
702 /* ### assert that RESTRUCTURE is NONE or DELETE? */
703 change->action = RESTRUCTURE_ADD;
704 change->kind = svn_node_file;
707 fb->path = apr_pstrdup(result_pool, relpath);
708 fb->base_revision = pb->base_revision;
713 /* Don't bother fetching the base, as in an add we don't have a base. */
714 fb->delta_base = NULL;
720 change->copyfrom_path = map_to_repos_relpath(fb->eb, copyfrom_path,
722 change->copyfrom_rev = copyfrom_revision;
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));
735 ev2_open_file(const char *path,
737 svn_revnum_t base_revision,
738 apr_pool_t *result_pool,
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);
748 fb->path = apr_pstrdup(result_pool, relpath);
749 fb->base_revision = base_revision;
751 if (pb->copyfrom_relpath)
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,
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));
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));
779 svn_txdelta_window_handler_t apply_handler;
782 svn_stream_t *source;
788 window_handler(svn_txdelta_window_t *window, void *baton)
790 struct handler_baton *hb = baton;
793 err = hb->apply_handler(window, hb->apply_baton);
794 if (window != NULL && !err)
797 SVN_ERR(svn_stream_close(hb->source));
799 svn_pool_destroy(hb->pool);
801 return svn_error_trace(err);
804 /* Lazy-open handler for getting a read-only stream of the delta base. */
806 open_delta_base(svn_stream_t **stream, void *baton,
807 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
809 const char *const delta_base = baton;
810 return svn_stream_open_readonly(stream, delta_base,
811 result_pool, scratch_pool);
814 /* Lazy-open handler for opening a stream for the delta result. */
816 open_delta_target(svn_stream_t **stream, void *baton,
817 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
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);
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)
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;
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;
845 if (! fb->delta_base)
846 hb->source = svn_stream_empty(handler_pool);
848 hb->source = svn_stream_lazyopen_create(open_delta_base,
849 (char*)fb->delta_base,
850 FALSE, handler_pool);
852 change->contents_changed = TRUE;
853 target = svn_stream_lazyopen_create(open_delta_target,
854 &change->contents_abspath,
855 FALSE, fb->eb->edit_pool);
857 svn_txdelta_apply(hb->source, target,
860 &hb->apply_handler, &hb->apply_baton);
862 hb->pool = handler_pool;
865 *handler = window_handler;
871 ev2_change_file_prop(void *file_baton,
873 const svn_string_t *value,
874 apr_pool_t *scratch_pool)
876 struct ev2_file_baton *fb = file_baton;
878 if (!strcmp(name, SVN_PROP_ENTRY_LOCK_TOKEN) && value == NULL)
880 /* We special case the lock token propery deletion, which is the
881 server's way of telling the client to unlock the path. */
883 /* ### this duplicates much of apply_propedit(). fix in future. */
884 const char *relpath = map_to_repos_relpath(fb->eb, fb->path,
886 struct change_node *change = locate_change(fb->eb, relpath);
888 change->unlock = TRUE;
891 SVN_ERR(apply_propedit(fb->eb, fb->path, svn_node_file, fb->base_revision,
892 name, value, scratch_pool));
898 ev2_close_file(void *file_baton,
899 const char *text_checksum,
900 apr_pool_t *scratch_pool)
906 ev2_absent_file(const char *path,
908 apr_pool_t *scratch_pool)
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);
914 /* ### assert that RESTRUCTURE is NONE or DELETE? */
915 change->action = RESTRUCTURE_ADD_ABSENT;
916 change->kind = svn_node_file;
922 ev2_close_edit(void *edit_baton,
923 apr_pool_t *scratch_pool)
925 struct ev2_edit_baton *eb = edit_baton;
927 SVN_ERR(run_ev2_actions(edit_baton, scratch_pool));
929 return svn_error_trace(svn_editor_complete(eb->editor));
933 ev2_abort_edit(void *edit_baton,
934 apr_pool_t *scratch_pool)
936 struct ev2_edit_baton *eb = edit_baton;
938 SVN_ERR(run_ev2_actions(edit_baton, scratch_pool));
940 return svn_error_trace(svn_editor_abort(eb->editor));
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.
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
966 svn_delta__delta_from_editor(const svn_delta_editor_t **deditor,
968 svn_editor_t *editor,
969 svn_delta__unlock_func_t unlock_func,
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,
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,
990 ev2_absent_directory,
994 ev2_change_file_prop,
1000 struct ev2_edit_baton *eb = apr_pcalloc(pool, sizeof(*eb));
1004 else if (base_relpath[0] == '/')
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;
1014 eb->repos_root = apr_pstrdup(pool, repos_root);
1015 eb->base_relpath = apr_pstrdup(pool, base_relpath);
1017 eb->fetch_props_func = fetch_props_func;
1018 eb->fetch_props_baton = fetch_props_baton;
1020 eb->fetch_base_func = fetch_base_func;
1021 eb->fetch_base_baton = fetch_base_baton;
1023 eb->do_unlock = unlock_func;
1024 eb->unlock_baton = unlock_baton;
1027 *deditor = &delta_editor;
1029 return SVN_NO_ERROR;
1033 /* ### note the similarity to struct change_node. these structures will
1034 ### be combined in the future. */
1036 /* ### leave these two here for now. still used. */
1037 svn_revnum_t base_revision;
1043 const svn_delta_editor_t *deditor;
1046 svn_delta_fetch_kind_func_t fetch_kind_func;
1047 void *fetch_kind_baton;
1049 svn_delta_fetch_props_func_t fetch_props_func;
1050 void *fetch_props_baton;
1052 struct operation root;
1053 svn_boolean_t *make_abs_paths;
1054 const char *repos_root;
1055 const char *base_relpath;
1057 /* REPOS_RELPATH -> struct change_node * */
1058 apr_hash_t *changes;
1060 apr_pool_t *edit_pool;
1064 /* Insert a new change for RELPATH, or return an existing one. */
1065 static struct change_node *
1066 insert_change(const char *relpath,
1067 apr_hash_t *changes)
1069 apr_pool_t *result_pool;
1070 struct change_node *change;
1072 change = svn_hash_gets(changes, relpath);
1076 result_pool = apr_hash_pool_get(changes);
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;
1083 svn_hash_sets(changes, apr_pstrdup(result_pool, relpath), change);
1089 /* This implements svn_editor_cb_add_directory_t */
1090 static svn_error_t *
1091 add_directory_cb(void *baton,
1092 const char *relpath,
1093 const apr_array_header_t *children,
1095 svn_revnum_t replaces_rev,
1096 apr_pool_t *scratch_pool)
1098 struct editor_baton *eb = baton;
1099 struct change_node *change = insert_change(relpath, eb->changes);
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);
1106 return SVN_NO_ERROR;
1109 /* This implements svn_editor_cb_add_file_t */
1110 static svn_error_t *
1111 add_file_cb(void *baton,
1112 const char *relpath,
1113 const svn_checksum_t *checksum,
1114 svn_stream_t *contents,
1116 svn_revnum_t replaces_rev,
1117 apr_pool_t *scratch_pool)
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);
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);
1130 md5_checksum = (svn_checksum_t *)checksum;
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));
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);
1146 return SVN_NO_ERROR;
1149 /* This implements svn_editor_cb_add_symlink_t */
1150 static svn_error_t *
1151 add_symlink_cb(void *baton,
1152 const char *relpath,
1155 svn_revnum_t replaces_rev,
1156 apr_pool_t *scratch_pool)
1159 struct editor_baton *eb = baton;
1160 struct change_node *change = insert_change(relpath, eb->changes);
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);
1169 SVN__NOT_IMPLEMENTED();
1172 /* This implements svn_editor_cb_add_absent_t */
1173 static svn_error_t *
1174 add_absent_cb(void *baton,
1175 const char *relpath,
1176 svn_node_kind_t kind,
1177 svn_revnum_t replaces_rev,
1178 apr_pool_t *scratch_pool)
1180 struct editor_baton *eb = baton;
1181 struct change_node *change = insert_change(relpath, eb->changes);
1183 change->action = RESTRUCTURE_ADD_ABSENT;
1184 change->kind = kind;
1185 change->deleting = replaces_rev;
1187 return SVN_NO_ERROR;
1190 /* This implements svn_editor_cb_alter_directory_t */
1191 static svn_error_t *
1192 alter_directory_cb(void *baton,
1193 const char *relpath,
1194 svn_revnum_t revision,
1195 const apr_array_header_t *children,
1197 apr_pool_t *scratch_pool)
1199 struct editor_baton *eb = baton;
1200 struct change_node *change = insert_change(relpath, eb->changes);
1202 /* ### should we verify the kind is truly a directory? */
1204 /* ### do we need to do anything with CHILDREN? */
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);
1212 return SVN_NO_ERROR;
1215 /* This implements svn_editor_cb_alter_file_t */
1216 static svn_error_t *
1217 alter_file_cb(void *baton,
1218 const char *relpath,
1219 svn_revnum_t revision,
1220 const svn_checksum_t *checksum,
1221 svn_stream_t *contents,
1223 apr_pool_t *scratch_pool)
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);
1231 /* ### should we verify the kind is truly a file? */
1235 /* We may need to re-checksum these contents */
1236 if (checksum && checksum->kind == svn_checksum_md5)
1237 md5_checksum = (svn_checksum_t *)checksum;
1239 contents = svn_stream_checksummed2(contents, &md5_checksum, NULL,
1240 svn_checksum_md5, TRUE,
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,
1251 /* Note: this node may already have information in CHANGE as a result
1252 of an earlier copy/move operation. */
1254 change->kind = svn_node_file;
1255 change->changing = revision;
1257 change->props = svn_prop_hash_dup(props, eb->edit_pool);
1258 if (contents != NULL)
1260 change->contents_changed = TRUE;
1261 change->contents_abspath = tmp_filename;
1262 change->checksum = svn_checksum_dup(md5_checksum, eb->edit_pool);
1265 return SVN_NO_ERROR;
1268 /* This implements svn_editor_cb_alter_symlink_t */
1269 static svn_error_t *
1270 alter_symlink_cb(void *baton,
1271 const char *relpath,
1272 svn_revnum_t revision,
1275 apr_pool_t *scratch_pool)
1277 /* ### should we verify the kind is truly a symlink? */
1279 /* ### do something */
1281 SVN__NOT_IMPLEMENTED();
1284 /* This implements svn_editor_cb_delete_t */
1285 static svn_error_t *
1286 delete_cb(void *baton,
1287 const char *relpath,
1288 svn_revnum_t revision,
1289 apr_pool_t *scratch_pool)
1291 struct editor_baton *eb = baton;
1292 struct change_node *change = insert_change(relpath, eb->changes);
1294 change->action = RESTRUCTURE_DELETE;
1295 /* change->kind = svn_node_unknown; */
1296 change->deleting = revision;
1298 return SVN_NO_ERROR;
1301 /* This implements svn_editor_cb_copy_t */
1302 static svn_error_t *
1303 copy_cb(void *baton,
1304 const char *src_relpath,
1305 svn_revnum_t src_revision,
1306 const char *dst_relpath,
1307 svn_revnum_t replaces_rev,
1308 apr_pool_t *scratch_pool)
1310 struct editor_baton *eb = baton;
1311 struct change_node *change = insert_change(dst_relpath, eb->changes);
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;
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,
1326 /* Note: this node may later have alter_*() called on it. */
1328 return SVN_NO_ERROR;
1331 /* This implements svn_editor_cb_move_t */
1332 static svn_error_t *
1333 move_cb(void *baton,
1334 const char *src_relpath,
1335 svn_revnum_t src_revision,
1336 const char *dst_relpath,
1337 svn_revnum_t replaces_rev,
1338 apr_pool_t *scratch_pool)
1340 struct editor_baton *eb = baton;
1341 struct change_node *change;
1343 /* Remap a move into a DELETE + COPY. */
1345 change = insert_change(src_relpath, eb->changes);
1346 change->action = RESTRUCTURE_DELETE;
1347 /* change->kind = svn_node_unknown; */
1348 change->deleting = src_revision;
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;
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,
1364 /* Note: this node may later have alter_*() called on it. */
1366 return SVN_NO_ERROR;
1370 count_components(const char *relpath)
1373 const char *slash = strchr(relpath, '/');
1375 while (slash != NULL)
1378 slash = strchr(slash + 1, '/');
1385 sort_deletes_first(const svn_sort__item_t *item1,
1386 const svn_sort__item_t *item2)
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;
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')
1402 if (*relpath2 == '\0')
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)
1414 && (len1 = slash1 - relpath1) == (len2 = slash2 - relpath2)
1415 && memcmp(relpath1, relpath2, len1) == 0))
1417 if (change1->action == RESTRUCTURE_DELETE)
1419 if (change2->action == RESTRUCTURE_DELETE)
1421 /* If both items are being deleted, then we don't care about
1422 the order. State they are equal. */
1426 /* ITEM1 is being deleted. Sort it before the surviving item. */
1429 if (change2->action == RESTRUCTURE_DELETE)
1430 /* ITEM2 is being deleted. Sort it before the surviving item. */
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.
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. */
1446 /* Paths-to-be-deleted with fewer components always sort earlier.
1448 For example, gamma will sort before E/alpha.
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.
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)
1459 int count1 = count_components(relpath1);
1460 int count2 = count_components(relpath2);
1462 if (count1 < count2 && change1->action == RESTRUCTURE_DELETE)
1464 if (count1 > count2 && change2->action == RESTRUCTURE_DELETE)
1468 /* Use svn_path_compare_paths() to get correct depth-based ordering. */
1469 return svn_path_compare_paths(relpath1, relpath2);
1473 static const apr_array_header_t *
1474 get_sorted_paths(apr_hash_t *changes,
1475 const char *base_relpath,
1476 apr_pool_t *scratch_pool)
1478 const apr_array_header_t *items;
1479 apr_array_header_t *paths;
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);
1486 /* Build a new array with just the paths, trimmed to relative paths for
1488 paths = apr_array_make(scratch_pool, items->nelts, sizeof(const char *));
1489 for (i = items->nelts; i--; )
1491 const svn_sort__item_t *item;
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);
1498 /* We didn't use PUSH, so set the proper number of elements. */
1499 paths->nelts = items->nelts;
1505 static svn_error_t *
1506 drive_ev1_props(const struct editor_baton *eb,
1507 const char *repos_relpath,
1508 const struct change_node *change,
1510 apr_pool_t *scratch_pool)
1512 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1513 apr_hash_t *old_props;
1514 apr_array_header_t *propdiffs;
1517 /* If there are no properties to install, then just exit. */
1518 if (change->props == NULL)
1519 return SVN_NO_ERROR;
1521 if (change->copyfrom_path)
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));
1529 else if (change->action == RESTRUCTURE_ADD)
1531 /* Locally-added nodes have no pristine properties.
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);
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));
1545 SVN_ERR(svn_prop_diffs(&propdiffs, change->props, old_props, scratch_pool));
1547 for (i = 0; i < propdiffs->nelts; i++)
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);
1553 svn_pool_clear(iterpool);
1555 if (change->kind == svn_node_dir)
1556 SVN_ERR(eb->deditor->change_dir_prop(node_baton,
1557 prop->name, prop->value,
1560 SVN_ERR(eb->deditor->change_file_prop(node_baton,
1561 prop->name, prop->value,
1565 /* Handle the funky unlock protocol. Note: only possibly on files. */
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,
1574 svn_pool_destroy(iterpool);
1575 return SVN_NO_ERROR;
1579 /* Conforms to svn_delta_path_driver_cb_func_t */
1580 static svn_error_t *
1581 apply_change(void **dir_baton,
1583 void *callback_baton,
1584 const char *ev1_relpath,
1585 apr_pool_t *result_pool)
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;
1594 /* Typically, we are not creating new directory batons. */
1597 relpath = svn_relpath_join(eb->base_relpath, ev1_relpath, scratch_pool);
1598 change = svn_hash_gets(eb->changes, relpath);
1600 /* The callback should only be called for paths in CHANGES. */
1601 SVN_ERR_ASSERT(change != NULL);
1603 /* Are we editing the root of the tree? */
1604 if (parent_baton == NULL)
1606 /* The root was opened in start_edit_func() */
1607 *dir_baton = eb->root.baton;
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));
1613 /* No further action possible for the root. */
1614 return SVN_NO_ERROR;
1617 if (change->action == RESTRUCTURE_DELETE)
1619 SVN_ERR(eb->deditor->delete_entry(ev1_relpath, change->deleting,
1620 parent_baton, scratch_pool));
1622 /* No futher action possible for this node. */
1623 return SVN_NO_ERROR;
1626 /* If we're not deleting this node, then we should know its kind. */
1627 SVN_ERR_ASSERT(change->kind != svn_node_unknown);
1629 if (change->action == RESTRUCTURE_ADD_ABSENT)
1631 if (change->kind == svn_node_dir)
1632 SVN_ERR(eb->deditor->absent_directory(ev1_relpath, parent_baton,
1635 SVN_ERR(eb->deditor->absent_file(ev1_relpath, parent_baton,
1638 /* No further action possible for this node. */
1639 return SVN_NO_ERROR;
1641 /* RESTRUCTURE_NONE or RESTRUCTURE_ADD */
1643 if (change->action == RESTRUCTURE_ADD)
1645 const char *copyfrom_url = NULL;
1646 svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
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));
1653 /* Are we copying the node from somewhere? */
1654 if (change->copyfrom_path)
1657 copyfrom_url = svn_path_url_add_component2(eb->repos_root,
1658 change->copyfrom_path,
1662 copyfrom_url = change->copyfrom_path;
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);
1670 copyfrom_rev = change->copyfrom_rev;
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));
1678 SVN_ERR(eb->deditor->add_file(ev1_relpath, parent_baton,
1679 copyfrom_url, copyfrom_rev,
1680 result_pool, &file_baton));
1684 if (change->kind == svn_node_dir)
1685 SVN_ERR(eb->deditor->open_directory(ev1_relpath, parent_baton,
1687 result_pool, dir_baton));
1689 SVN_ERR(eb->deditor->open_file(ev1_relpath, parent_baton,
1691 result_pool, &file_baton));
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));
1698 SVN_ERR(drive_ev1_props(eb, relpath, change, file_baton, scratch_pool));
1700 if (change->contents_changed && change->contents_abspath)
1702 svn_txdelta_window_handler_t handler;
1703 void *handler_baton;
1704 svn_stream_t *contents;
1706 /* ### would be nice to have a BASE_CHECKSUM, but hey: this is the
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));
1720 const char *digest = svn_checksum_to_cstring(change->checksum,
1723 SVN_ERR(eb->deditor->close_file(file_baton, digest, scratch_pool));
1726 return SVN_NO_ERROR;
1730 static svn_error_t *
1731 drive_changes(const struct editor_baton *eb,
1732 apr_pool_t *scratch_pool)
1734 struct change_node *change;
1735 const apr_array_header_t *paths;
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;
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). */
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,
1756 return SVN_NO_ERROR;
1760 /* This implements svn_editor_cb_complete_t */
1761 static svn_error_t *
1762 complete_cb(void *baton,
1763 apr_pool_t *scratch_pool)
1765 struct editor_baton *eb = baton;
1768 /* Drive the tree we've created. */
1769 err = drive_changes(eb, scratch_pool);
1772 err = svn_error_compose_create(err, eb->deditor->close_edit(
1778 svn_error_clear(eb->deditor->abort_edit(eb->dedit_baton, scratch_pool));
1780 return svn_error_trace(err);
1783 /* This implements svn_editor_cb_abort_t */
1784 static svn_error_t *
1785 abort_cb(void *baton,
1786 apr_pool_t *scratch_pool)
1788 struct editor_baton *eb = baton;
1792 /* We still need to drive anything we collected in the editor to this
1795 /* Drive the tree we've created. */
1796 err = drive_changes(eb, scratch_pool);
1798 err2 = eb->deditor->abort_edit(eb->dedit_baton, scratch_pool);
1803 svn_error_clear(err2);
1808 return svn_error_trace(err);
1811 static svn_error_t *
1812 start_edit_func(void *baton,
1813 svn_revnum_t base_revision)
1815 struct editor_baton *eb = baton;
1817 eb->root.base_revision = base_revision;
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));
1825 return SVN_NO_ERROR;
1828 static svn_error_t *
1829 target_revision_func(void *baton,
1830 svn_revnum_t target_revision,
1831 apr_pool_t *scratch_pool)
1833 struct editor_baton *eb = baton;
1835 SVN_ERR(eb->deditor->set_target_revision(eb->dedit_baton, target_revision,
1838 return SVN_NO_ERROR;
1841 static svn_error_t *
1842 do_unlock(void *baton,
1844 apr_pool_t *scratch_pool)
1846 struct editor_baton *eb = baton;
1849 /* PATH is REPOS_RELPATH */
1850 struct change_node *change = insert_change(path, eb->changes);
1852 /* We will need to propagate a deletion of SVN_PROP_ENTRY_LOCK_TOKEN */
1853 change->unlock = TRUE;
1856 return SVN_NO_ERROR;
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
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
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
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.
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,
1892 svn_boolean_t *send_abs_paths,
1893 const char *repos_root,
1894 const char *base_relpath,
1895 svn_cancel_func_t cancel_func,
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)
1904 svn_editor_t *editor;
1905 static const svn_editor_cb_many_t editor_cbs = {
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));
1925 else if (base_relpath[0] == '/')
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);
1934 eb->changes = apr_hash_make(result_pool);
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;
1941 eb->root.base_revision = SVN_INVALID_REVNUM;
1943 eb->make_abs_paths = send_abs_paths;
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));
1951 *unlock_func = do_unlock;
1954 extra_baton->start_edit = start_edit_func;
1955 extra_baton->target_revision = target_revision_func;
1956 extra_baton->baton = eb;
1960 return SVN_NO_ERROR;
1963 svn_delta_shim_callbacks_t *
1964 svn_delta_shim_callbacks_default(apr_pool_t *result_pool)
1966 svn_delta_shim_callbacks_t *shim_callbacks = apr_pcalloc(result_pool,
1967 sizeof(*shim_callbacks));
1968 return shim_callbacks;
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'. */
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)
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;
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;
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;
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));
2008 svn_delta__unlock_func_t unlock_func;
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);
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,
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,
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,
2036 return SVN_NO_ERROR;