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"
41 struct file_rev_handler_wrapper_baton {
43 svn_file_rev_handler_old_t handler;
46 /* This implements svn_file_rev_handler_t. */
48 file_rev_handler_wrapper(void *baton,
51 apr_hash_t *rev_props,
52 svn_boolean_t result_of_merge,
53 svn_txdelta_window_handler_t *delta_handler,
55 apr_array_header_t *prop_diffs,
58 struct file_rev_handler_wrapper_baton *fwb = baton;
61 return fwb->handler(fwb->baton,
74 svn_compat_wrap_file_rev_handler(svn_file_rev_handler_t *handler2,
75 void **handler2_baton,
76 svn_file_rev_handler_old_t handler,
80 struct file_rev_handler_wrapper_baton *fwb = apr_pcalloc(pool, sizeof(*fwb));
82 /* Set the user provided old format callback in the baton. */
83 fwb->baton = handler_baton;
84 fwb->handler = handler;
86 *handler2_baton = fwb;
87 *handler2 = file_rev_handler_wrapper;
91 /* The following code maps the calls to a traditional delta editor to an
92 * Editorv2 editor. It does this by keeping track of a lot of state, and
93 * then communicating that state to Ev2 upon closure of the file or dir (or
94 * edit). Note that Ev2 calls add_symlink() and alter_symlink() are not
95 * present in the delta editor paradigm, so we never call them.
97 * The general idea here is that we have to see *all* the actions on a node's
98 * parent before we can process that node, which means we need to buffer a
99 * large amount of information in the dir batons, and then process it in the
100 * close_directory() handler.
102 * There are a few ways we alter the callback stream. One is when unlocking
103 * paths. To tell a client a path should be unlocked, the server sends a
104 * prop-del for the SVN_PROP_ENTRY_LOCK_TOKEN property. This causes problems,
105 * since the client doesn't have this property in the first place, but the
106 * deletion has side effects (unlike deleting a non-existent regular property
107 * would). To solve this, we introduce *another* function into the API, not
108 * a part of the Ev2 callbacks, but a companion which is used to register
109 * the unlock of a path. See ev2_change_file_prop() for implemenation
113 struct ev2_edit_baton
115 svn_editor_t *editor;
117 apr_hash_t *changes; /* REPOS_RELPATH -> struct change_node */
119 apr_array_header_t *path_order;
122 /* For calculating relpaths from Ev1 copyfrom urls. */
123 const char *repos_root;
124 const char *base_relpath;
126 apr_pool_t *edit_pool;
127 struct svn_delta__extra_baton *exb;
128 svn_boolean_t closed;
130 svn_boolean_t *found_abs_paths; /* Did we strip an incoming '/' from the
133 svn_delta_fetch_props_func_t fetch_props_func;
134 void *fetch_props_baton;
136 svn_delta_fetch_base_func_t fetch_base_func;
137 void *fetch_base_baton;
139 svn_delta__unlock_func_t do_unlock;
145 struct ev2_edit_baton *eb;
147 svn_revnum_t base_revision;
149 const char *copyfrom_relpath;
150 svn_revnum_t copyfrom_rev;
153 struct ev2_file_baton
155 struct ev2_edit_baton *eb;
157 svn_revnum_t base_revision;
158 const char *delta_base;
161 enum restructure_action_t
163 RESTRUCTURE_NONE = 0,
164 RESTRUCTURE_ADD, /* add the node, maybe replacing. maybe copy */
165 RESTRUCTURE_ADD_ABSENT, /* add an absent node, possibly replacing */
166 RESTRUCTURE_DELETE /* delete this node */
169 /* Records everything about how this node is to be changed. */
172 /* what kind of (tree) restructure is occurring at this node? */
173 enum restructure_action_t action;
175 svn_node_kind_t kind; /* the NEW kind of this node */
177 /* We need two revisions: one to specify the revision we are altering,
178 and a second to specify the revision to delete/replace. These are
179 mutually exclusive, but they need to be separate to ensure we don't
180 confuse the operation on this node. For example, we may delete a
181 node and replace it we use DELETING for REPLACES_REV, and ignore
182 the value placed into CHANGING when properties were set/changed
183 on the new node. Or we simply change a node (setting CHANGING),
184 and DELETING remains SVN_INVALID_REVNUM, indicating we are not
185 attempting to replace a node. */
186 svn_revnum_t changing;
187 svn_revnum_t deleting;
189 apr_hash_t *props; /* new/final set of props to apply */
191 const char *contents_abspath; /* file containing new fulltext */
192 svn_checksum_t *checksum; /* checksum of new fulltext */
194 /* If COPYFROM_PATH is not NULL, then copy PATH@REV to this node.
195 RESTRUCTURE must be RESTRUCTURE_ADD. */
196 const char *copyfrom_path;
197 svn_revnum_t copyfrom_rev;
199 /* Record whether an incoming propchange unlocked this node. */
200 svn_boolean_t unlock;
204 static struct change_node *
205 locate_change(struct ev2_edit_baton *eb,
208 struct change_node *change = svn_hash_gets(eb->changes, relpath);
213 /* Shift RELPATH into the proper pool, and record the observed order. */
214 relpath = apr_pstrdup(eb->edit_pool, relpath);
215 APR_ARRAY_PUSH(eb->path_order, const char *) = relpath;
217 /* Return an empty change. Callers will tweak as needed. */
218 change = apr_pcalloc(eb->edit_pool, sizeof(*change));
219 change->changing = SVN_INVALID_REVNUM;
220 change->deleting = SVN_INVALID_REVNUM;
221 change->kind = svn_node_unknown;
223 svn_hash_sets(eb->changes, relpath, change);
230 apply_propedit(struct ev2_edit_baton *eb,
232 svn_node_kind_t kind,
233 svn_revnum_t base_revision,
235 const svn_string_t *value,
236 apr_pool_t *scratch_pool)
238 struct change_node *change = locate_change(eb, relpath);
240 SVN_ERR_ASSERT(change->kind == svn_node_unknown || change->kind == kind);
243 /* We're now changing the node. Record the revision. */
244 SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(change->changing)
245 || change->changing == base_revision);
246 change->changing = base_revision;
248 if (change->props == NULL)
250 /* Fetch the original set of properties. We'll apply edits to create
251 the new/target set of properties.
253 If this is a copied/moved now, then the original properties come
254 from there. If the node has been added, it starts with empty props.
255 Otherwise, we get the properties from BASE. */
257 if (change->copyfrom_path)
258 SVN_ERR(eb->fetch_props_func(&change->props,
259 eb->fetch_props_baton,
260 change->copyfrom_path,
261 change->copyfrom_rev,
262 eb->edit_pool, scratch_pool));
263 else if (change->action == RESTRUCTURE_ADD)
264 change->props = apr_hash_make(eb->edit_pool);
266 SVN_ERR(eb->fetch_props_func(&change->props,
267 eb->fetch_props_baton,
268 relpath, base_revision,
269 eb->edit_pool, scratch_pool));
273 svn_hash_sets(change->props, name, NULL);
275 svn_hash_sets(change->props,
276 apr_pstrdup(eb->edit_pool, name),
277 svn_string_dup(value, eb->edit_pool));
283 /* Find all the paths which are immediate children of PATH and return their
284 basenames in a list. */
285 static apr_array_header_t *
286 get_children(struct ev2_edit_baton *eb,
290 apr_array_header_t *children = apr_array_make(pool, 1, sizeof(const char *));
291 apr_hash_index_t *hi;
293 for (hi = apr_hash_first(pool, eb->changes); hi; hi = apr_hash_next(hi))
295 const char *repos_relpath = svn__apr_hash_index_key(hi);
298 /* Find potential children. */
299 child = svn_relpath_skip_ancestor(path, repos_relpath);
300 if (!child || !*child)
303 /* If we have a path separator, it's a deep child, so just ignore it.
304 ### Is there an API we should be using for this? */
305 if (strchr(child, '/') != NULL)
308 APR_ARRAY_PUSH(children, const char *) = child;
316 process_actions(struct ev2_edit_baton *eb,
317 const char *repos_relpath,
318 const struct change_node *change,
319 apr_pool_t *scratch_pool)
321 apr_hash_t *props = NULL;
322 svn_stream_t *contents = NULL;
323 svn_checksum_t *checksum = NULL;
324 svn_node_kind_t kind = svn_node_unknown;
326 SVN_ERR_ASSERT(change != NULL);
329 SVN_ERR(eb->do_unlock(eb->unlock_baton, repos_relpath, scratch_pool));
331 if (change->action == RESTRUCTURE_DELETE)
333 /* If the action was left as RESTRUCTURE_DELETE, then a
334 replacement is not occurring. Just do the delete and bail. */
335 SVN_ERR(svn_editor_delete(eb->editor, repos_relpath,
338 /* No further work possible on this node. */
341 if (change->action == RESTRUCTURE_ADD_ABSENT)
343 SVN_ERR(svn_editor_add_absent(eb->editor, repos_relpath,
344 change->kind, change->deleting));
346 /* No further work possible on this node. */
350 if (change->contents_abspath != NULL)
352 /* We can only set text on files. */
353 /* ### validate we aren't overwriting KIND? */
354 kind = svn_node_file;
356 /* ### the checksum might be in CHANGE->CHECKSUM */
357 SVN_ERR(svn_io_file_checksum2(&checksum, change->contents_abspath,
358 svn_checksum_sha1, scratch_pool));
359 SVN_ERR(svn_stream_open_readonly(&contents, change->contents_abspath,
360 scratch_pool, scratch_pool));
363 if (change->props != NULL)
365 /* ### validate we aren't overwriting KIND? */
367 props = change->props;
370 if (change->action == RESTRUCTURE_ADD)
372 /* An add might be a replace. Grab the revnum we're replacing. */
373 svn_revnum_t replaces_rev = change->deleting;
377 if (change->copyfrom_path != NULL)
379 SVN_ERR(svn_editor_copy(eb->editor, change->copyfrom_path,
380 change->copyfrom_rev,
381 repos_relpath, replaces_rev));
382 /* Fall through to possibly make changes post-copy. */
386 /* If no properties were defined, then use an empty set. */
388 props = apr_hash_make(scratch_pool);
390 if (kind == svn_node_dir)
392 const apr_array_header_t *children;
394 children = get_children(eb, repos_relpath, scratch_pool);
395 SVN_ERR(svn_editor_add_directory(eb->editor, repos_relpath,
401 /* If this file was added, but apply_txdelta() was not
402 called (ie. no CONTENTS_ABSPATH), then we're adding
404 if (change->contents_abspath == NULL)
406 contents = svn_stream_empty(scratch_pool);
407 checksum = svn_checksum_empty_checksum(svn_checksum_sha1,
411 SVN_ERR(svn_editor_add_file(eb->editor, repos_relpath,
412 checksum, contents, props,
416 /* No further work possible on this node. */
422 /* There *should* be work for this node. But it seems that isn't true
423 in some cases. Future investigation... */
424 SVN_ERR_ASSERT(props || contents);
426 if (props || contents)
428 /* Changes to properties or content should have indicated the revision
429 it was intending to change.
431 Oop. Not true. The node may be locally-added. */
433 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(change->changing));
436 /* ### we need to gather up the target set of children */
438 if (kind == svn_node_dir)
439 SVN_ERR(svn_editor_alter_directory(eb->editor, repos_relpath,
440 change->changing, NULL, props));
442 SVN_ERR(svn_editor_alter_file(eb->editor, repos_relpath,
443 change->changing, props,
444 checksum, contents));
451 run_ev2_actions(struct ev2_edit_baton *eb,
452 apr_pool_t *scratch_pool)
454 apr_pool_t *iterpool;
456 iterpool = svn_pool_create(scratch_pool);
458 /* Possibly pick up where we left off. Ocassionally, we do some of these
459 as part of close_edit() and then some more as part of abort_edit() */
460 for (; eb->paths_processed < eb->path_order->nelts; ++eb->paths_processed)
462 const char *repos_relpath = APR_ARRAY_IDX(eb->path_order,
465 const struct change_node *change = svn_hash_gets(eb->changes,
468 svn_pool_clear(iterpool);
470 SVN_ERR(process_actions(eb, repos_relpath, change, iterpool));
472 svn_pool_destroy(iterpool);
479 map_to_repos_relpath(struct ev2_edit_baton *eb,
480 const char *path_or_url,
481 apr_pool_t *result_pool)
483 if (svn_path_is_url(path_or_url))
485 return svn_uri_skip_ancestor(eb->repos_root, path_or_url, result_pool);
489 return svn_relpath_join(eb->base_relpath,
490 path_or_url[0] == '/'
491 ? path_or_url + 1 : path_or_url,
498 ev2_set_target_revision(void *edit_baton,
499 svn_revnum_t target_revision,
500 apr_pool_t *scratch_pool)
502 struct ev2_edit_baton *eb = edit_baton;
504 if (eb->exb->target_revision)
505 SVN_ERR(eb->exb->target_revision(eb->exb->baton, target_revision,
512 ev2_open_root(void *edit_baton,
513 svn_revnum_t base_revision,
514 apr_pool_t *result_pool,
517 struct ev2_dir_baton *db = apr_pcalloc(result_pool, sizeof(*db));
518 struct ev2_edit_baton *eb = edit_baton;
521 db->path = apr_pstrdup(eb->edit_pool, eb->base_relpath);
522 db->base_revision = base_revision;
526 if (eb->exb->start_edit)
527 SVN_ERR(eb->exb->start_edit(eb->exb->baton, base_revision));
533 ev2_delete_entry(const char *path,
534 svn_revnum_t revision,
536 apr_pool_t *scratch_pool)
538 struct ev2_dir_baton *pb = parent_baton;
539 svn_revnum_t base_revision;
540 const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool);
541 struct change_node *change = locate_change(pb->eb, relpath);
543 if (SVN_IS_VALID_REVNUM(revision))
544 base_revision = revision;
546 base_revision = pb->base_revision;
548 SVN_ERR_ASSERT(change->action == RESTRUCTURE_NONE);
549 change->action = RESTRUCTURE_DELETE;
551 SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(change->deleting)
552 || change->deleting == base_revision);
553 change->deleting = base_revision;
559 ev2_add_directory(const char *path,
561 const char *copyfrom_path,
562 svn_revnum_t copyfrom_revision,
563 apr_pool_t *result_pool,
567 apr_pool_t *scratch_pool = result_pool;
568 struct ev2_dir_baton *pb = parent_baton;
569 struct ev2_dir_baton *cb = apr_pcalloc(result_pool, sizeof(*cb));
570 const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool);
571 struct change_node *change = locate_change(pb->eb, relpath);
573 /* ### assert that RESTRUCTURE is NONE or DELETE? */
574 change->action = RESTRUCTURE_ADD;
575 change->kind = svn_node_dir;
578 cb->path = apr_pstrdup(result_pool, relpath);
579 cb->base_revision = pb->base_revision;
584 if (pb->copyfrom_relpath)
586 const char *name = svn_relpath_basename(relpath, scratch_pool);
587 cb->copyfrom_relpath = svn_relpath_join(pb->copyfrom_relpath, name,
589 cb->copyfrom_rev = pb->copyfrom_rev;
596 change->copyfrom_path = map_to_repos_relpath(pb->eb, copyfrom_path,
598 change->copyfrom_rev = copyfrom_revision;
600 cb->copyfrom_relpath = change->copyfrom_path;
601 cb->copyfrom_rev = change->copyfrom_rev;
608 ev2_open_directory(const char *path,
610 svn_revnum_t base_revision,
611 apr_pool_t *result_pool,
615 apr_pool_t *scratch_pool = result_pool;
616 struct ev2_dir_baton *pb = parent_baton;
617 struct ev2_dir_baton *db = apr_pcalloc(result_pool, sizeof(*db));
618 const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool);
621 db->path = apr_pstrdup(result_pool, relpath);
622 db->base_revision = base_revision;
624 if (pb->copyfrom_relpath)
626 /* We are inside a copy. */
627 const char *name = svn_relpath_basename(relpath, scratch_pool);
629 db->copyfrom_relpath = svn_relpath_join(pb->copyfrom_relpath, name,
631 db->copyfrom_rev = pb->copyfrom_rev;
639 ev2_change_dir_prop(void *dir_baton,
641 const svn_string_t *value,
642 apr_pool_t *scratch_pool)
644 struct ev2_dir_baton *db = dir_baton;
646 SVN_ERR(apply_propedit(db->eb, db->path, svn_node_dir, db->base_revision,
647 name, value, scratch_pool));
653 ev2_close_directory(void *dir_baton,
654 apr_pool_t *scratch_pool)
660 ev2_absent_directory(const char *path,
662 apr_pool_t *scratch_pool)
664 struct ev2_dir_baton *pb = parent_baton;
665 const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool);
666 struct change_node *change = locate_change(pb->eb, relpath);
668 /* ### assert that RESTRUCTURE is NONE or DELETE? */
669 change->action = RESTRUCTURE_ADD_ABSENT;
670 change->kind = svn_node_dir;
676 ev2_add_file(const char *path,
678 const char *copyfrom_path,
679 svn_revnum_t copyfrom_revision,
680 apr_pool_t *result_pool,
684 apr_pool_t *scratch_pool = result_pool;
685 struct ev2_file_baton *fb = apr_pcalloc(result_pool, sizeof(*fb));
686 struct ev2_dir_baton *pb = parent_baton;
687 const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool);
688 struct change_node *change = locate_change(pb->eb, relpath);
690 /* ### assert that RESTRUCTURE is NONE or DELETE? */
691 change->action = RESTRUCTURE_ADD;
692 change->kind = svn_node_file;
695 fb->path = apr_pstrdup(result_pool, relpath);
696 fb->base_revision = pb->base_revision;
701 /* Don't bother fetching the base, as in an add we don't have a base. */
702 fb->delta_base = NULL;
708 change->copyfrom_path = map_to_repos_relpath(fb->eb, copyfrom_path,
710 change->copyfrom_rev = copyfrom_revision;
712 SVN_ERR(fb->eb->fetch_base_func(&fb->delta_base,
713 fb->eb->fetch_base_baton,
714 change->copyfrom_path,
715 change->copyfrom_rev,
716 result_pool, scratch_pool));
723 ev2_open_file(const char *path,
725 svn_revnum_t base_revision,
726 apr_pool_t *result_pool,
730 apr_pool_t *scratch_pool = result_pool;
731 struct ev2_file_baton *fb = apr_pcalloc(result_pool, sizeof(*fb));
732 struct ev2_dir_baton *pb = parent_baton;
733 const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool);
736 fb->path = apr_pstrdup(result_pool, relpath);
737 fb->base_revision = base_revision;
739 if (pb->copyfrom_relpath)
741 /* We're in a copied directory, so the delta base is going to be
742 based up on the copy source. */
743 const char *name = svn_relpath_basename(relpath, scratch_pool);
744 const char *copyfrom_relpath = svn_relpath_join(pb->copyfrom_relpath,
748 SVN_ERR(fb->eb->fetch_base_func(&fb->delta_base,
749 fb->eb->fetch_base_baton,
750 copyfrom_relpath, pb->copyfrom_rev,
751 result_pool, scratch_pool));
755 SVN_ERR(fb->eb->fetch_base_func(&fb->delta_base,
756 fb->eb->fetch_base_baton,
757 relpath, base_revision,
758 result_pool, scratch_pool));
767 svn_txdelta_window_handler_t apply_handler;
770 svn_stream_t *source;
776 window_handler(svn_txdelta_window_t *window, void *baton)
778 struct handler_baton *hb = baton;
781 err = hb->apply_handler(window, hb->apply_baton);
782 if (window != NULL && !err)
785 SVN_ERR(svn_stream_close(hb->source));
787 svn_pool_destroy(hb->pool);
789 return svn_error_trace(err);
794 ev2_apply_textdelta(void *file_baton,
795 const char *base_checksum,
796 apr_pool_t *result_pool,
797 svn_txdelta_window_handler_t *handler,
798 void **handler_baton)
800 struct ev2_file_baton *fb = file_baton;
801 apr_pool_t *handler_pool = svn_pool_create(fb->eb->edit_pool);
802 struct handler_baton *hb = apr_pcalloc(handler_pool, sizeof(*hb));
803 struct change_node *change;
804 svn_stream_t *target;
805 /* ### fix this. for now, we know this has a "short" lifetime. */
806 apr_pool_t *scratch_pool = handler_pool;
808 change = locate_change(fb->eb, fb->path);
809 SVN_ERR_ASSERT(change->contents_abspath == NULL);
810 SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(change->changing)
811 || change->changing == fb->base_revision);
812 change->changing = fb->base_revision;
814 if (! fb->delta_base)
815 hb->source = svn_stream_empty(handler_pool);
817 SVN_ERR(svn_stream_open_readonly(&hb->source, fb->delta_base, handler_pool,
820 SVN_ERR(svn_stream_open_unique(&target, &change->contents_abspath, NULL,
821 svn_io_file_del_on_pool_cleanup,
822 fb->eb->edit_pool, scratch_pool));
824 svn_txdelta_apply(hb->source, target,
827 &hb->apply_handler, &hb->apply_baton);
829 hb->pool = handler_pool;
832 *handler = window_handler;
838 ev2_change_file_prop(void *file_baton,
840 const svn_string_t *value,
841 apr_pool_t *scratch_pool)
843 struct ev2_file_baton *fb = file_baton;
845 if (!strcmp(name, SVN_PROP_ENTRY_LOCK_TOKEN) && value == NULL)
847 /* We special case the lock token propery deletion, which is the
848 server's way of telling the client to unlock the path. */
850 /* ### this duplicates much of apply_propedit(). fix in future. */
851 const char *relpath = map_to_repos_relpath(fb->eb, fb->path,
853 struct change_node *change = locate_change(fb->eb, relpath);
855 change->unlock = TRUE;
858 SVN_ERR(apply_propedit(fb->eb, fb->path, svn_node_file, fb->base_revision,
859 name, value, scratch_pool));
865 ev2_close_file(void *file_baton,
866 const char *text_checksum,
867 apr_pool_t *scratch_pool)
873 ev2_absent_file(const char *path,
875 apr_pool_t *scratch_pool)
877 struct ev2_dir_baton *pb = parent_baton;
878 const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool);
879 struct change_node *change = locate_change(pb->eb, relpath);
881 /* ### assert that RESTRUCTURE is NONE or DELETE? */
882 change->action = RESTRUCTURE_ADD_ABSENT;
883 change->kind = svn_node_file;
889 ev2_close_edit(void *edit_baton,
890 apr_pool_t *scratch_pool)
892 struct ev2_edit_baton *eb = edit_baton;
894 SVN_ERR(run_ev2_actions(edit_baton, scratch_pool));
896 return svn_error_trace(svn_editor_complete(eb->editor));
900 ev2_abort_edit(void *edit_baton,
901 apr_pool_t *scratch_pool)
903 struct ev2_edit_baton *eb = edit_baton;
905 SVN_ERR(run_ev2_actions(edit_baton, scratch_pool));
907 return svn_error_trace(svn_editor_abort(eb->editor));
912 /* Return a svn_delta_editor_t * in DEDITOR, with an accompanying baton in
913 * DEDITOR_BATON, which will drive EDITOR. These will both be
914 * allocated in RESULT_POOL, which may become large and long-lived;
915 * SCRATCH_POOL is used for temporary allocations.
917 * The other parameters are as follows:
918 * - UNLOCK_FUNC / UNLOCK_BATON: A callback / baton which will be called
919 * when an unlocking action is received.
920 * - FOUND_ABS_PATHS: A pointer to a boolean flag which will be set if
921 * this shim determines that it is receiving absolute paths.
922 * - FETCH_PROPS_FUNC / FETCH_PROPS_BATON: A callback / baton pair which
923 * will be used by the shim handlers if they need to determine the
924 * existing properties on a path.
925 * - FETCH_BASE_FUNC / FETCH_BASE_BATON: A callback / baton pair which will
926 * be used by the shims handlers if they need to determine the base
927 * text of a path. It should only be invoked for files.
928 * - EXB: An 'extra baton' which is used to communicate between the shims.
929 * Its callbacks should be invoked at the appropriate time by this
933 svn_delta__delta_from_editor(const svn_delta_editor_t **deditor,
935 svn_editor_t *editor,
936 svn_delta__unlock_func_t unlock_func,
938 svn_boolean_t *found_abs_paths,
939 const char *repos_root,
940 const char *base_relpath,
941 svn_delta_fetch_props_func_t fetch_props_func,
942 void *fetch_props_baton,
943 svn_delta_fetch_base_func_t fetch_base_func,
944 void *fetch_base_baton,
945 struct svn_delta__extra_baton *exb,
948 /* Static 'cause we don't want it to be on the stack. */
949 static svn_delta_editor_t delta_editor = {
950 ev2_set_target_revision,
957 ev2_absent_directory,
961 ev2_change_file_prop,
967 struct ev2_edit_baton *eb = apr_pcalloc(pool, sizeof(*eb));
971 else if (base_relpath[0] == '/')
975 eb->changes = apr_hash_make(pool);
976 eb->path_order = apr_array_make(pool, 1, sizeof(const char *));
977 eb->edit_pool = pool;
978 eb->found_abs_paths = found_abs_paths;
979 *eb->found_abs_paths = FALSE;
981 eb->repos_root = apr_pstrdup(pool, repos_root);
982 eb->base_relpath = apr_pstrdup(pool, base_relpath);
984 eb->fetch_props_func = fetch_props_func;
985 eb->fetch_props_baton = fetch_props_baton;
987 eb->fetch_base_func = fetch_base_func;
988 eb->fetch_base_baton = fetch_base_baton;
990 eb->do_unlock = unlock_func;
991 eb->unlock_baton = unlock_baton;
994 *deditor = &delta_editor;
1000 /* ### note the similarity to struct change_node. these structures will
1001 ### be combined in the future. */
1003 /* ### leave these two here for now. still used. */
1004 svn_revnum_t base_revision;
1010 const svn_delta_editor_t *deditor;
1013 svn_delta_fetch_kind_func_t fetch_kind_func;
1014 void *fetch_kind_baton;
1016 svn_delta_fetch_props_func_t fetch_props_func;
1017 void *fetch_props_baton;
1019 struct operation root;
1020 svn_boolean_t *make_abs_paths;
1021 const char *repos_root;
1022 const char *base_relpath;
1024 /* REPOS_RELPATH -> struct change_node * */
1025 apr_hash_t *changes;
1027 apr_pool_t *edit_pool;
1031 /* Insert a new change for RELPATH, or return an existing one. */
1032 static struct change_node *
1033 insert_change(const char *relpath,
1034 apr_hash_t *changes)
1036 apr_pool_t *result_pool;
1037 struct change_node *change;
1039 change = svn_hash_gets(changes, relpath);
1043 result_pool = apr_hash_pool_get(changes);
1045 /* Return an empty change. Callers will tweak as needed. */
1046 change = apr_pcalloc(result_pool, sizeof(*change));
1047 change->changing = SVN_INVALID_REVNUM;
1048 change->deleting = SVN_INVALID_REVNUM;
1050 svn_hash_sets(changes, apr_pstrdup(result_pool, relpath), change);
1056 /* This implements svn_editor_cb_add_directory_t */
1057 static svn_error_t *
1058 add_directory_cb(void *baton,
1059 const char *relpath,
1060 const apr_array_header_t *children,
1062 svn_revnum_t replaces_rev,
1063 apr_pool_t *scratch_pool)
1065 struct editor_baton *eb = baton;
1066 struct change_node *change = insert_change(relpath, eb->changes);
1068 change->action = RESTRUCTURE_ADD;
1069 change->kind = svn_node_dir;
1070 change->deleting = replaces_rev;
1071 change->props = svn_prop_hash_dup(props, eb->edit_pool);
1073 return SVN_NO_ERROR;
1076 /* This implements svn_editor_cb_add_file_t */
1077 static svn_error_t *
1078 add_file_cb(void *baton,
1079 const char *relpath,
1080 const svn_checksum_t *checksum,
1081 svn_stream_t *contents,
1083 svn_revnum_t replaces_rev,
1084 apr_pool_t *scratch_pool)
1086 struct editor_baton *eb = baton;
1087 const char *tmp_filename;
1088 svn_stream_t *tmp_stream;
1089 svn_checksum_t *md5_checksum;
1090 struct change_node *change = insert_change(relpath, eb->changes);
1092 /* We may need to re-checksum these contents */
1093 if (!(checksum && checksum->kind == svn_checksum_md5))
1094 contents = svn_stream_checksummed2(contents, &md5_checksum, NULL,
1095 svn_checksum_md5, TRUE, scratch_pool);
1097 md5_checksum = (svn_checksum_t *)checksum;
1099 /* Spool the contents to a tempfile, and provide that to the driver. */
1100 SVN_ERR(svn_stream_open_unique(&tmp_stream, &tmp_filename, NULL,
1101 svn_io_file_del_on_pool_cleanup,
1102 eb->edit_pool, scratch_pool));
1103 SVN_ERR(svn_stream_copy3(contents, tmp_stream, NULL, NULL, scratch_pool));
1105 change->action = RESTRUCTURE_ADD;
1106 change->kind = svn_node_file;
1107 change->deleting = replaces_rev;
1108 change->props = svn_prop_hash_dup(props, eb->edit_pool);
1109 change->contents_abspath = tmp_filename;
1110 change->checksum = svn_checksum_dup(md5_checksum, eb->edit_pool);
1112 return SVN_NO_ERROR;
1115 /* This implements svn_editor_cb_add_symlink_t */
1116 static svn_error_t *
1117 add_symlink_cb(void *baton,
1118 const char *relpath,
1121 svn_revnum_t replaces_rev,
1122 apr_pool_t *scratch_pool)
1125 struct editor_baton *eb = baton;
1126 struct change_node *change = insert_change(relpath, eb->changes);
1128 change->action = RESTRUCTURE_ADD;
1129 change->kind = svn_node_symlink;
1130 change->deleting = replaces_rev;
1131 change->props = svn_prop_hash_dup(props, eb->edit_pool);
1135 SVN__NOT_IMPLEMENTED();
1138 /* This implements svn_editor_cb_add_absent_t */
1139 static svn_error_t *
1140 add_absent_cb(void *baton,
1141 const char *relpath,
1142 svn_node_kind_t kind,
1143 svn_revnum_t replaces_rev,
1144 apr_pool_t *scratch_pool)
1146 struct editor_baton *eb = baton;
1147 struct change_node *change = insert_change(relpath, eb->changes);
1149 change->action = RESTRUCTURE_ADD_ABSENT;
1150 change->kind = kind;
1151 change->deleting = replaces_rev;
1153 return SVN_NO_ERROR;
1156 /* This implements svn_editor_cb_alter_directory_t */
1157 static svn_error_t *
1158 alter_directory_cb(void *baton,
1159 const char *relpath,
1160 svn_revnum_t revision,
1161 const apr_array_header_t *children,
1163 apr_pool_t *scratch_pool)
1165 struct editor_baton *eb = baton;
1166 struct change_node *change = insert_change(relpath, eb->changes);
1168 /* ### should we verify the kind is truly a directory? */
1170 /* ### do we need to do anything with CHILDREN? */
1172 /* Note: this node may already have information in CHANGE as a result
1173 of an earlier copy/move operation. */
1174 change->kind = svn_node_dir;
1175 change->changing = revision;
1176 change->props = svn_prop_hash_dup(props, eb->edit_pool);
1178 return SVN_NO_ERROR;
1181 /* This implements svn_editor_cb_alter_file_t */
1182 static svn_error_t *
1183 alter_file_cb(void *baton,
1184 const char *relpath,
1185 svn_revnum_t revision,
1187 const svn_checksum_t *checksum,
1188 svn_stream_t *contents,
1189 apr_pool_t *scratch_pool)
1191 struct editor_baton *eb = baton;
1192 const char *tmp_filename;
1193 svn_stream_t *tmp_stream;
1194 svn_checksum_t *md5_checksum;
1195 struct change_node *change = insert_change(relpath, eb->changes);
1197 /* ### should we verify the kind is truly a file? */
1201 /* We may need to re-checksum these contents */
1202 if (!(checksum && checksum->kind == svn_checksum_md5))
1203 contents = svn_stream_checksummed2(contents, &md5_checksum, NULL,
1204 svn_checksum_md5, TRUE,
1207 md5_checksum = (svn_checksum_t *)checksum;
1209 /* Spool the contents to a tempfile, and provide that to the driver. */
1210 SVN_ERR(svn_stream_open_unique(&tmp_stream, &tmp_filename, NULL,
1211 svn_io_file_del_on_pool_cleanup,
1212 eb->edit_pool, scratch_pool));
1213 SVN_ERR(svn_stream_copy3(contents, tmp_stream, NULL, NULL,
1217 /* Note: this node may already have information in CHANGE as a result
1218 of an earlier copy/move operation. */
1220 change->kind = svn_node_file;
1221 change->changing = revision;
1223 change->props = svn_prop_hash_dup(props, eb->edit_pool);
1224 if (contents != NULL)
1226 change->contents_abspath = tmp_filename;
1227 change->checksum = svn_checksum_dup(md5_checksum, eb->edit_pool);
1230 return SVN_NO_ERROR;
1233 /* This implements svn_editor_cb_alter_symlink_t */
1234 static svn_error_t *
1235 alter_symlink_cb(void *baton,
1236 const char *relpath,
1237 svn_revnum_t revision,
1240 apr_pool_t *scratch_pool)
1242 /* ### should we verify the kind is truly a symlink? */
1244 /* ### do something */
1246 SVN__NOT_IMPLEMENTED();
1249 /* This implements svn_editor_cb_delete_t */
1250 static svn_error_t *
1251 delete_cb(void *baton,
1252 const char *relpath,
1253 svn_revnum_t revision,
1254 apr_pool_t *scratch_pool)
1256 struct editor_baton *eb = baton;
1257 struct change_node *change = insert_change(relpath, eb->changes);
1259 change->action = RESTRUCTURE_DELETE;
1260 /* change->kind = svn_node_unknown; */
1261 change->deleting = revision;
1263 return SVN_NO_ERROR;
1266 /* This implements svn_editor_cb_copy_t */
1267 static svn_error_t *
1268 copy_cb(void *baton,
1269 const char *src_relpath,
1270 svn_revnum_t src_revision,
1271 const char *dst_relpath,
1272 svn_revnum_t replaces_rev,
1273 apr_pool_t *scratch_pool)
1275 struct editor_baton *eb = baton;
1276 struct change_node *change = insert_change(dst_relpath, eb->changes);
1278 change->action = RESTRUCTURE_ADD;
1279 /* change->kind = svn_node_unknown; */
1280 change->deleting = replaces_rev;
1281 change->copyfrom_path = apr_pstrdup(eb->edit_pool, src_relpath);
1282 change->copyfrom_rev = src_revision;
1284 /* We need the source's kind to know whether to call add_directory()
1285 or add_file() later on. */
1286 SVN_ERR(eb->fetch_kind_func(&change->kind, eb->fetch_kind_baton,
1287 change->copyfrom_path,
1288 change->copyfrom_rev,
1291 /* Note: this node may later have alter_*() called on it. */
1293 return SVN_NO_ERROR;
1296 /* This implements svn_editor_cb_move_t */
1297 static svn_error_t *
1298 move_cb(void *baton,
1299 const char *src_relpath,
1300 svn_revnum_t src_revision,
1301 const char *dst_relpath,
1302 svn_revnum_t replaces_rev,
1303 apr_pool_t *scratch_pool)
1305 struct editor_baton *eb = baton;
1306 struct change_node *change;
1308 /* Remap a move into a DELETE + COPY. */
1310 change = insert_change(src_relpath, eb->changes);
1311 change->action = RESTRUCTURE_DELETE;
1312 /* change->kind = svn_node_unknown; */
1313 change->deleting = src_revision;
1315 change = insert_change(dst_relpath, eb->changes);
1316 change->action = RESTRUCTURE_ADD;
1317 /* change->kind = svn_node_unknown; */
1318 change->deleting = replaces_rev;
1319 change->copyfrom_path = apr_pstrdup(eb->edit_pool, src_relpath);
1320 change->copyfrom_rev = src_revision;
1322 /* We need the source's kind to know whether to call add_directory()
1323 or add_file() later on. */
1324 SVN_ERR(eb->fetch_kind_func(&change->kind, eb->fetch_kind_baton,
1325 change->copyfrom_path,
1326 change->copyfrom_rev,
1329 /* Note: this node may later have alter_*() called on it. */
1331 return SVN_NO_ERROR;
1334 /* This implements svn_editor_cb_rotate_t */
1335 static svn_error_t *
1336 rotate_cb(void *baton,
1337 const apr_array_header_t *relpaths,
1338 const apr_array_header_t *revisions,
1339 apr_pool_t *scratch_pool)
1341 SVN__NOT_IMPLEMENTED();
1346 count_components(const char *relpath)
1349 const char *slash = strchr(relpath, '/');
1351 while (slash != NULL)
1354 slash = strchr(slash + 1, '/');
1361 sort_deletes_first(const svn_sort__item_t *item1,
1362 const svn_sort__item_t *item2)
1364 const char *relpath1 = item1->key;
1365 const char *relpath2 = item2->key;
1366 const struct change_node *change1 = item1->value;
1367 const struct change_node *change2 = item2->value;
1373 /* Force the root to always sort first. Otherwise, it may look like a
1374 sibling of its children (no slashes), and could get sorted *after*
1375 any children that get deleted. */
1376 if (*relpath1 == '\0')
1378 if (*relpath2 == '\0')
1381 /* Are these two items siblings? The 'if' statement tests if they are
1382 siblings in the root directory, or that slashes were found in both
1383 paths, that the length of the paths to those slashes match, and that
1384 the path contents up to those slashes also match. */
1385 slash1 = strrchr(relpath1, '/');
1386 slash2 = strrchr(relpath2, '/');
1387 if ((slash1 == NULL && slash2 == NULL)
1390 && (len1 = slash1 - relpath1) == (len2 = slash2 - relpath2)
1391 && memcmp(relpath1, relpath2, len1) == 0))
1393 if (change1->action == RESTRUCTURE_DELETE)
1395 if (change2->action == RESTRUCTURE_DELETE)
1397 /* If both items are being deleted, then we don't care about
1398 the order. State they are equal. */
1402 /* ITEM1 is being deleted. Sort it before the surviving item. */
1405 if (change2->action == RESTRUCTURE_DELETE)
1406 /* ITEM2 is being deleted. Sort it before the surviving item. */
1409 /* Normally, we don't care about the ordering of two siblings. However,
1410 if these siblings are directories, then we need to provide an
1411 ordering so that the quicksort algorithm will further sort them
1412 relative to the maybe-directory's children.
1414 Without this additional ordering, we could see that A/B/E and A/B/F
1415 are equal. And then A/B/E/child is sorted before A/B/F. But since
1416 E and F are "equal", A/B/E could arrive *after* A/B/F and after the
1417 A/B/E/child node. */
1422 /* Paths-to-be-deleted with fewer components always sort earlier.
1424 For example, gamma will sort before E/alpha.
1426 Without this test, E/alpha lexicographically sorts before gamma,
1427 but gamma sorts before E when gamma is to be deleted. This kind of
1428 ordering would place E/alpha before E. Not good.
1430 With this test, gamma sorts before E/alpha. E and E/alpha are then
1431 sorted by svn_path_compare_paths() (which places E before E/alpha). */
1432 if (change1->action == RESTRUCTURE_DELETE
1433 || change2->action == RESTRUCTURE_DELETE)
1435 int count1 = count_components(relpath1);
1436 int count2 = count_components(relpath2);
1438 if (count1 < count2 && change1->action == RESTRUCTURE_DELETE)
1440 if (count1 > count2 && change2->action == RESTRUCTURE_DELETE)
1444 /* Use svn_path_compare_paths() to get correct depth-based ordering. */
1445 return svn_path_compare_paths(relpath1, relpath2);
1449 static const apr_array_header_t *
1450 get_sorted_paths(apr_hash_t *changes,
1451 const char *base_relpath,
1452 apr_pool_t *scratch_pool)
1454 const apr_array_header_t *items;
1455 apr_array_header_t *paths;
1458 /* Construct a sorted array of svn_sort__item_t structs. Within a given
1459 directory, nodes that are to be deleted will appear first. */
1460 items = svn_sort__hash(changes, sort_deletes_first, scratch_pool);
1462 /* Build a new array with just the paths, trimmed to relative paths for
1464 paths = apr_array_make(scratch_pool, items->nelts, sizeof(const char *));
1465 for (i = items->nelts; i--; )
1467 const svn_sort__item_t *item;
1469 item = &APR_ARRAY_IDX(items, i, const svn_sort__item_t);
1470 APR_ARRAY_IDX(paths, i, const char *)
1471 = svn_relpath_skip_ancestor(base_relpath, item->key);
1474 /* We didn't use PUSH, so set the proper number of elements. */
1475 paths->nelts = items->nelts;
1481 static svn_error_t *
1482 drive_ev1_props(const struct editor_baton *eb,
1483 const char *repos_relpath,
1484 const struct change_node *change,
1486 apr_pool_t *scratch_pool)
1488 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1489 apr_hash_t *old_props;
1490 apr_array_header_t *propdiffs;
1493 /* If there are no properties to install, then just exit. */
1494 if (change->props == NULL)
1495 return SVN_NO_ERROR;
1497 if (change->copyfrom_path)
1499 /* The pristine properties are from the copy/move source. */
1500 SVN_ERR(eb->fetch_props_func(&old_props, eb->fetch_props_baton,
1501 change->copyfrom_path,
1502 change->copyfrom_rev,
1503 scratch_pool, iterpool));
1505 else if (change->action == RESTRUCTURE_ADD)
1507 /* Locally-added nodes have no pristine properties.
1509 Note: we can use iterpool; this hash only needs to survive to
1510 the propdiffs call, and there are no contents to preserve. */
1511 old_props = apr_hash_make(iterpool);
1515 /* Fetch the pristine properties for whatever we're editing. */
1516 SVN_ERR(eb->fetch_props_func(&old_props, eb->fetch_props_baton,
1517 repos_relpath, change->changing,
1518 scratch_pool, iterpool));
1521 SVN_ERR(svn_prop_diffs(&propdiffs, change->props, old_props, scratch_pool));
1523 for (i = 0; i < propdiffs->nelts; i++)
1525 /* Note: the array returned by svn_prop_diffs() is an array of
1526 actual structures, not pointers to them. */
1527 const svn_prop_t *prop = &APR_ARRAY_IDX(propdiffs, i, svn_prop_t);
1529 svn_pool_clear(iterpool);
1531 if (change->kind == svn_node_dir)
1532 SVN_ERR(eb->deditor->change_dir_prop(node_baton,
1533 prop->name, prop->value,
1536 SVN_ERR(eb->deditor->change_file_prop(node_baton,
1537 prop->name, prop->value,
1541 /* Handle the funky unlock protocol. Note: only possibly on files. */
1544 SVN_ERR_ASSERT(change->kind == svn_node_file);
1545 SVN_ERR(eb->deditor->change_file_prop(node_baton,
1546 SVN_PROP_ENTRY_LOCK_TOKEN, NULL,
1550 svn_pool_destroy(iterpool);
1551 return SVN_NO_ERROR;
1555 /* Conforms to svn_delta_path_driver_cb_func_t */
1556 static svn_error_t *
1557 apply_change(void **dir_baton,
1559 void *callback_baton,
1560 const char *ev1_relpath,
1561 apr_pool_t *result_pool)
1564 apr_pool_t *scratch_pool = result_pool;
1565 const struct editor_baton *eb = callback_baton;
1566 const struct change_node *change;
1567 const char *relpath;
1568 void *file_baton = NULL;
1570 /* Typically, we are not creating new directory batons. */
1573 relpath = svn_relpath_join(eb->base_relpath, ev1_relpath, scratch_pool);
1574 change = svn_hash_gets(eb->changes, relpath);
1576 /* The callback should only be called for paths in CHANGES. */
1577 SVN_ERR_ASSERT(change != NULL);
1579 /* Are we editing the root of the tree? */
1580 if (parent_baton == NULL)
1582 /* The root was opened in start_edit_func() */
1583 *dir_baton = eb->root.baton;
1585 /* Only property edits are allowed on the root. */
1586 SVN_ERR_ASSERT(change->action == RESTRUCTURE_NONE);
1587 SVN_ERR(drive_ev1_props(eb, relpath, change, *dir_baton, scratch_pool));
1589 /* No further action possible for the root. */
1590 return SVN_NO_ERROR;
1593 if (change->action == RESTRUCTURE_DELETE)
1595 SVN_ERR(eb->deditor->delete_entry(ev1_relpath, change->deleting,
1596 parent_baton, scratch_pool));
1598 /* No futher action possible for this node. */
1599 return SVN_NO_ERROR;
1602 /* If we're not deleting this node, then we should know its kind. */
1603 SVN_ERR_ASSERT(change->kind != svn_node_unknown);
1605 if (change->action == RESTRUCTURE_ADD_ABSENT)
1607 if (change->kind == svn_node_dir)
1608 SVN_ERR(eb->deditor->absent_directory(ev1_relpath, parent_baton,
1611 SVN_ERR(eb->deditor->absent_file(ev1_relpath, parent_baton,
1614 /* No further action possible for this node. */
1615 return SVN_NO_ERROR;
1617 /* RESTRUCTURE_NONE or RESTRUCTURE_ADD */
1619 if (change->action == RESTRUCTURE_ADD)
1621 const char *copyfrom_url = NULL;
1622 svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
1624 /* Do we have an old node to delete first? */
1625 if (SVN_IS_VALID_REVNUM(change->deleting))
1626 SVN_ERR(eb->deditor->delete_entry(ev1_relpath, change->deleting,
1627 parent_baton, scratch_pool));
1629 /* Are we copying the node from somewhere? */
1630 if (change->copyfrom_path)
1633 copyfrom_url = svn_path_url_add_component2(eb->repos_root,
1634 change->copyfrom_path,
1638 copyfrom_url = change->copyfrom_path;
1640 /* Make this an FS path by prepending "/" */
1641 if (copyfrom_url[0] != '/')
1642 copyfrom_url = apr_pstrcat(scratch_pool, "/",
1643 copyfrom_url, NULL);
1646 copyfrom_rev = change->copyfrom_rev;
1649 if (change->kind == svn_node_dir)
1650 SVN_ERR(eb->deditor->add_directory(ev1_relpath, parent_baton,
1651 copyfrom_url, copyfrom_rev,
1652 result_pool, dir_baton));
1654 SVN_ERR(eb->deditor->add_file(ev1_relpath, parent_baton,
1655 copyfrom_url, copyfrom_rev,
1656 result_pool, &file_baton));
1660 if (change->kind == svn_node_dir)
1661 SVN_ERR(eb->deditor->open_directory(ev1_relpath, parent_baton,
1663 result_pool, dir_baton));
1665 SVN_ERR(eb->deditor->open_file(ev1_relpath, parent_baton,
1667 result_pool, &file_baton));
1670 /* Apply any properties in CHANGE to the node. */
1671 if (change->kind == svn_node_dir)
1672 SVN_ERR(drive_ev1_props(eb, relpath, change, *dir_baton, scratch_pool));
1674 SVN_ERR(drive_ev1_props(eb, relpath, change, file_baton, scratch_pool));
1676 if (change->contents_abspath)
1678 svn_txdelta_window_handler_t handler;
1679 void *handler_baton;
1680 svn_stream_t *contents;
1682 /* ### would be nice to have a BASE_CHECKSUM, but hey: this is the
1684 SVN_ERR(eb->deditor->apply_textdelta(file_baton, NULL, scratch_pool,
1685 &handler, &handler_baton));
1686 SVN_ERR(svn_stream_open_readonly(&contents, change->contents_abspath,
1687 scratch_pool, scratch_pool));
1688 /* ### it would be nice to send a true txdelta here, but whatever. */
1689 SVN_ERR(svn_txdelta_send_stream(contents, handler, handler_baton,
1690 NULL, scratch_pool));
1691 SVN_ERR(svn_stream_close(contents));
1696 const char *digest = svn_checksum_to_cstring(change->checksum,
1699 SVN_ERR(eb->deditor->close_file(file_baton, digest, scratch_pool));
1702 return SVN_NO_ERROR;
1706 static svn_error_t *
1707 drive_changes(const struct editor_baton *eb,
1708 apr_pool_t *scratch_pool)
1710 struct change_node *change;
1711 const apr_array_header_t *paths;
1713 /* If we never opened a root baton, then the caller aborted the editor
1714 before it even began. There is nothing to do. Bail. */
1715 if (eb->root.baton == NULL)
1716 return SVN_NO_ERROR;
1718 /* We need to make the path driver believe we want to make changes to
1719 the root. Otherwise, it will attempt an open_root(), which we already
1720 did in start_edit_func(). We can forge up a change record, if one
1721 does not already exist. */
1722 change = insert_change(eb->base_relpath, eb->changes);
1723 change->kind = svn_node_dir;
1724 /* No property changes (tho they might exist from a real change). */
1726 /* Get a sorted list of Ev1-relative paths. */
1727 paths = get_sorted_paths(eb->changes, eb->base_relpath, scratch_pool);
1728 SVN_ERR(svn_delta_path_driver2(eb->deditor, eb->dedit_baton, paths,
1729 FALSE, apply_change, (void *)eb,
1732 return SVN_NO_ERROR;
1736 /* This implements svn_editor_cb_complete_t */
1737 static svn_error_t *
1738 complete_cb(void *baton,
1739 apr_pool_t *scratch_pool)
1741 struct editor_baton *eb = baton;
1744 /* Drive the tree we've created. */
1745 err = drive_changes(eb, scratch_pool);
1748 err = svn_error_compose_create(err, eb->deditor->close_edit(
1754 svn_error_clear(eb->deditor->abort_edit(eb->dedit_baton, scratch_pool));
1756 return svn_error_trace(err);
1759 /* This implements svn_editor_cb_abort_t */
1760 static svn_error_t *
1761 abort_cb(void *baton,
1762 apr_pool_t *scratch_pool)
1764 struct editor_baton *eb = baton;
1768 /* We still need to drive anything we collected in the editor to this
1771 /* Drive the tree we've created. */
1772 err = drive_changes(eb, scratch_pool);
1774 err2 = eb->deditor->abort_edit(eb->dedit_baton, scratch_pool);
1779 svn_error_clear(err2);
1784 return svn_error_trace(err);
1787 static svn_error_t *
1788 start_edit_func(void *baton,
1789 svn_revnum_t base_revision)
1791 struct editor_baton *eb = baton;
1793 eb->root.base_revision = base_revision;
1795 /* For some Ev1 editors (such as the repos commit editor), the root must
1796 be open before can invoke any callbacks. The open_root() call sets up
1797 stuff (eg. open an FS txn) which will be needed. */
1798 SVN_ERR(eb->deditor->open_root(eb->dedit_baton, eb->root.base_revision,
1799 eb->edit_pool, &eb->root.baton));
1801 return SVN_NO_ERROR;
1804 static svn_error_t *
1805 target_revision_func(void *baton,
1806 svn_revnum_t target_revision,
1807 apr_pool_t *scratch_pool)
1809 struct editor_baton *eb = baton;
1811 SVN_ERR(eb->deditor->set_target_revision(eb->dedit_baton, target_revision,
1814 return SVN_NO_ERROR;
1817 static svn_error_t *
1818 do_unlock(void *baton,
1820 apr_pool_t *scratch_pool)
1822 struct editor_baton *eb = baton;
1825 /* PATH is REPOS_RELPATH */
1826 struct change_node *change = insert_change(path, eb->changes);
1828 /* We will need to propagate a deletion of SVN_PROP_ENTRY_LOCK_TOKEN */
1829 change->unlock = TRUE;
1832 return SVN_NO_ERROR;
1835 /* Return an svn_editor_t * in EDITOR_P which will drive
1836 * DEDITOR/DEDIT_BATON. EDITOR_P is allocated in RESULT_POOL, which may
1837 * become large and long-lived; SCRATCH_POOL is used for temporary
1840 * The other parameters are as follows:
1841 * - EXB: An 'extra_baton' used for passing information between the coupled
1842 * shims. This includes actions like 'start edit' and 'set target'.
1843 * As this shim receives these actions, it provides the extra baton
1845 * - UNLOCK_FUNC / UNLOCK_BATON: A callback / baton pair which a caller
1846 * can use to notify this shim that a path should be unlocked (in the
1847 * 'svn lock' sense). As this shim receives this action, it provides
1848 * this callback / baton to its caller.
1849 * - SEND_ABS_PATHS: A pointer which will be set prior to this edit (but
1850 * not necessarily at the invocation of editor_from_delta()),and
1851 * which indicates whether incoming paths should be expected to
1852 * be absolute or relative.
1853 * - CANCEL_FUNC / CANCEL_BATON: The usual; folded into the produced editor.
1854 * - FETCH_KIND_FUNC / FETCH_KIND_BATON: A callback / baton pair which will
1855 * be used by the shim handlers if they need to determine the kind of
1857 * - FETCH_PROPS_FUNC / FETCH_PROPS_BATON: A callback / baton pair which
1858 * will be used by the shim handlers if they need to determine the
1859 * existing properties on a path.
1862 svn_delta__editor_from_delta(svn_editor_t **editor_p,
1863 struct svn_delta__extra_baton **exb,
1864 svn_delta__unlock_func_t *unlock_func,
1865 void **unlock_baton,
1866 const svn_delta_editor_t *deditor,
1868 svn_boolean_t *send_abs_paths,
1869 const char *repos_root,
1870 const char *base_relpath,
1871 svn_cancel_func_t cancel_func,
1873 svn_delta_fetch_kind_func_t fetch_kind_func,
1874 void *fetch_kind_baton,
1875 svn_delta_fetch_props_func_t fetch_props_func,
1876 void *fetch_props_baton,
1877 apr_pool_t *result_pool,
1878 apr_pool_t *scratch_pool)
1880 svn_editor_t *editor;
1881 static const svn_editor_cb_many_t editor_cbs = {
1896 struct editor_baton *eb = apr_pcalloc(result_pool, sizeof(*eb));
1897 struct svn_delta__extra_baton *extra_baton = apr_pcalloc(result_pool,
1898 sizeof(*extra_baton));
1902 else if (base_relpath[0] == '/')
1905 eb->deditor = deditor;
1906 eb->dedit_baton = dedit_baton;
1907 eb->edit_pool = result_pool;
1908 eb->repos_root = apr_pstrdup(result_pool, repos_root);
1909 eb->base_relpath = apr_pstrdup(result_pool, base_relpath);
1911 eb->changes = apr_hash_make(result_pool);
1913 eb->fetch_kind_func = fetch_kind_func;
1914 eb->fetch_kind_baton = fetch_kind_baton;
1915 eb->fetch_props_func = fetch_props_func;
1916 eb->fetch_props_baton = fetch_props_baton;
1918 eb->root.base_revision = SVN_INVALID_REVNUM;
1920 eb->make_abs_paths = send_abs_paths;
1922 SVN_ERR(svn_editor_create(&editor, eb, cancel_func, cancel_baton,
1923 result_pool, scratch_pool));
1924 SVN_ERR(svn_editor_setcb_many(editor, &editor_cbs, scratch_pool));
1928 *unlock_func = do_unlock;
1931 extra_baton->start_edit = start_edit_func;
1932 extra_baton->target_revision = target_revision_func;
1933 extra_baton->baton = eb;
1937 return SVN_NO_ERROR;
1940 svn_delta_shim_callbacks_t *
1941 svn_delta_shim_callbacks_default(apr_pool_t *result_pool)
1943 svn_delta_shim_callbacks_t *shim_callbacks = apr_pcalloc(result_pool,
1944 sizeof(*shim_callbacks));
1945 return shim_callbacks;
1948 /* To enable editor shims throughout Subversion, ENABLE_EV2_SHIMS should be
1949 * defined. This can be done manually, or by providing `--enable-ev2-shims'
1950 * to `configure'. */
1953 svn_editor__insert_shims(const svn_delta_editor_t **deditor_out,
1954 void **dedit_baton_out,
1955 const svn_delta_editor_t *deditor_in,
1956 void *dedit_baton_in,
1957 const char *repos_root,
1958 const char *base_relpath,
1959 svn_delta_shim_callbacks_t *shim_callbacks,
1960 apr_pool_t *result_pool,
1961 apr_pool_t *scratch_pool)
1963 #ifndef ENABLE_EV2_SHIMS
1964 /* Shims disabled, just copy the editor and baton directly. */
1965 *deditor_out = deditor_in;
1966 *dedit_baton_out = dedit_baton_in;
1968 /* Use our shim APIs to create an intermediate svn_editor_t, and then
1969 wrap that again back into a svn_delta_editor_t. This introduces
1970 a lot of overhead. */
1971 svn_editor_t *editor;
1973 /* The "extra baton" is a set of functions and a baton which allows the
1974 shims to communicate additional events to each other.
1975 svn_delta__editor_from_delta() returns a pointer to this baton, which
1976 svn_delta__delta_from_editor() should then store. */
1977 struct svn_delta__extra_baton *exb;
1979 /* The reason this is a pointer is that we don't know the appropriate
1980 value until we start receiving paths. So process_actions() sets the
1981 flag, which drive_tree() later consumes. */
1982 svn_boolean_t *found_abs_paths = apr_palloc(result_pool,
1983 sizeof(*found_abs_paths));
1985 svn_delta__unlock_func_t unlock_func;
1988 SVN_ERR_ASSERT(shim_callbacks->fetch_kind_func != NULL);
1989 SVN_ERR_ASSERT(shim_callbacks->fetch_props_func != NULL);
1990 SVN_ERR_ASSERT(shim_callbacks->fetch_base_func != NULL);
1992 SVN_ERR(svn_delta__editor_from_delta(&editor, &exb,
1993 &unlock_func, &unlock_baton,
1994 deditor_in, dedit_baton_in,
1995 found_abs_paths, repos_root, base_relpath,
1997 shim_callbacks->fetch_kind_func,
1998 shim_callbacks->fetch_baton,
1999 shim_callbacks->fetch_props_func,
2000 shim_callbacks->fetch_baton,
2001 result_pool, scratch_pool));
2002 SVN_ERR(svn_delta__delta_from_editor(deditor_out, dedit_baton_out, editor,
2003 unlock_func, unlock_baton,
2005 repos_root, base_relpath,
2006 shim_callbacks->fetch_props_func,
2007 shim_callbacks->fetch_baton,
2008 shim_callbacks->fetch_base_func,
2009 shim_callbacks->fetch_baton,
2013 return SVN_NO_ERROR;