compat.c revision 299742
1/* 2 * compat.c : Wrappers and callbacks for compatibility. 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24#include <stddef.h> 25 26#include "svn_types.h" 27#include "svn_error.h" 28#include "svn_delta.h" 29#include "svn_sorts.h" 30#include "svn_dirent_uri.h" 31#include "svn_path.h" 32#include "svn_hash.h" 33#include "svn_props.h" 34#include "svn_pools.h" 35 36#include "svn_private_config.h" 37 38#include "private/svn_delta_private.h" 39#include "private/svn_sorts_private.h" 40#include "svn_private_config.h" 41 42 43struct file_rev_handler_wrapper_baton { 44 void *baton; 45 svn_file_rev_handler_old_t handler; 46}; 47 48/* This implements svn_file_rev_handler_t. */ 49static svn_error_t * 50file_rev_handler_wrapper(void *baton, 51 const char *path, 52 svn_revnum_t rev, 53 apr_hash_t *rev_props, 54 svn_boolean_t result_of_merge, 55 svn_txdelta_window_handler_t *delta_handler, 56 void **delta_baton, 57 apr_array_header_t *prop_diffs, 58 apr_pool_t *pool) 59{ 60 struct file_rev_handler_wrapper_baton *fwb = baton; 61 62 if (fwb->handler) 63 return fwb->handler(fwb->baton, 64 path, 65 rev, 66 rev_props, 67 delta_handler, 68 delta_baton, 69 prop_diffs, 70 pool); 71 72 return SVN_NO_ERROR; 73} 74 75void 76svn_compat_wrap_file_rev_handler(svn_file_rev_handler_t *handler2, 77 void **handler2_baton, 78 svn_file_rev_handler_old_t handler, 79 void *handler_baton, 80 apr_pool_t *pool) 81{ 82 struct file_rev_handler_wrapper_baton *fwb = apr_pcalloc(pool, sizeof(*fwb)); 83 84 /* Set the user provided old format callback in the baton. */ 85 fwb->baton = handler_baton; 86 fwb->handler = handler; 87 88 *handler2_baton = fwb; 89 *handler2 = file_rev_handler_wrapper; 90} 91 92 93/* The following code maps the calls to a traditional delta editor to an 94 * Editorv2 editor. It does this by keeping track of a lot of state, and 95 * then communicating that state to Ev2 upon closure of the file or dir (or 96 * edit). Note that Ev2 calls add_symlink() and alter_symlink() are not 97 * present in the delta editor paradigm, so we never call them. 98 * 99 * The general idea here is that we have to see *all* the actions on a node's 100 * parent before we can process that node, which means we need to buffer a 101 * large amount of information in the dir batons, and then process it in the 102 * close_directory() handler. 103 * 104 * There are a few ways we alter the callback stream. One is when unlocking 105 * paths. To tell a client a path should be unlocked, the server sends a 106 * prop-del for the SVN_PROP_ENTRY_LOCK_TOKEN property. This causes problems, 107 * since the client doesn't have this property in the first place, but the 108 * deletion has side effects (unlike deleting a non-existent regular property 109 * would). To solve this, we introduce *another* function into the API, not 110 * a part of the Ev2 callbacks, but a companion which is used to register 111 * the unlock of a path. See ev2_change_file_prop() for implemenation 112 * details. 113 */ 114 115struct ev2_edit_baton 116{ 117 svn_editor_t *editor; 118 119 apr_hash_t *changes; /* REPOS_RELPATH -> struct change_node */ 120 121 apr_array_header_t *path_order; 122 int paths_processed; 123 124 /* For calculating relpaths from Ev1 copyfrom urls. */ 125 const char *repos_root; 126 const char *base_relpath; 127 128 apr_pool_t *edit_pool; 129 struct svn_delta__extra_baton *exb; 130 svn_boolean_t closed; 131 132 svn_boolean_t *found_abs_paths; /* Did we strip an incoming '/' from the 133 paths? */ 134 135 svn_delta_fetch_props_func_t fetch_props_func; 136 void *fetch_props_baton; 137 138 svn_delta_fetch_base_func_t fetch_base_func; 139 void *fetch_base_baton; 140 141 svn_delta__unlock_func_t do_unlock; 142 void *unlock_baton; 143}; 144 145struct ev2_dir_baton 146{ 147 struct ev2_edit_baton *eb; 148 const char *path; 149 svn_revnum_t base_revision; 150 151 const char *copyfrom_relpath; 152 svn_revnum_t copyfrom_rev; 153}; 154 155struct ev2_file_baton 156{ 157 struct ev2_edit_baton *eb; 158 const char *path; 159 svn_revnum_t base_revision; 160 const char *delta_base; 161}; 162 163enum restructure_action_t 164{ 165 RESTRUCTURE_NONE = 0, 166 RESTRUCTURE_ADD, /* add the node, maybe replacing. maybe copy */ 167 RESTRUCTURE_ADD_ABSENT, /* add an absent node, possibly replacing */ 168 RESTRUCTURE_DELETE /* delete this node */ 169}; 170 171/* Records everything about how this node is to be changed. */ 172struct change_node 173{ 174 /* what kind of (tree) restructure is occurring at this node? */ 175 enum restructure_action_t action; 176 177 svn_node_kind_t kind; /* the NEW kind of this node */ 178 179 /* We need two revisions: one to specify the revision we are altering, 180 and a second to specify the revision to delete/replace. These are 181 mutually exclusive, but they need to be separate to ensure we don't 182 confuse the operation on this node. For example, we may delete a 183 node and replace it we use DELETING for REPLACES_REV, and ignore 184 the value placed into CHANGING when properties were set/changed 185 on the new node. Or we simply change a node (setting CHANGING), 186 and DELETING remains SVN_INVALID_REVNUM, indicating we are not 187 attempting to replace a node. */ 188 svn_revnum_t changing; 189 svn_revnum_t deleting; 190 191 apr_hash_t *props; /* new/final set of props to apply */ 192 193 svn_boolean_t contents_changed; /* the file contents changed */ 194 const char *contents_abspath; /* file containing new fulltext */ 195 svn_checksum_t *checksum; /* checksum of new fulltext */ 196 197 /* If COPYFROM_PATH is not NULL, then copy PATH@REV to this node. 198 RESTRUCTURE must be RESTRUCTURE_ADD. */ 199 const char *copyfrom_path; 200 svn_revnum_t copyfrom_rev; 201 202 /* Record whether an incoming propchange unlocked this node. */ 203 svn_boolean_t unlock; 204}; 205 206 207static struct change_node * 208locate_change(struct ev2_edit_baton *eb, 209 const char *relpath) 210{ 211 struct change_node *change = svn_hash_gets(eb->changes, relpath); 212 213 if (change != NULL) 214 return change; 215 216 /* Shift RELPATH into the proper pool, and record the observed order. */ 217 relpath = apr_pstrdup(eb->edit_pool, relpath); 218 APR_ARRAY_PUSH(eb->path_order, const char *) = relpath; 219 220 /* Return an empty change. Callers will tweak as needed. */ 221 change = apr_pcalloc(eb->edit_pool, sizeof(*change)); 222 change->changing = SVN_INVALID_REVNUM; 223 change->deleting = SVN_INVALID_REVNUM; 224 change->kind = svn_node_unknown; 225 226 svn_hash_sets(eb->changes, relpath, change); 227 228 return change; 229} 230 231 232static svn_error_t * 233apply_propedit(struct ev2_edit_baton *eb, 234 const char *relpath, 235 svn_node_kind_t kind, 236 svn_revnum_t base_revision, 237 const char *name, 238 const svn_string_t *value, 239 apr_pool_t *scratch_pool) 240{ 241 struct change_node *change = locate_change(eb, relpath); 242 243 SVN_ERR_ASSERT(change->kind == svn_node_unknown || change->kind == kind); 244 change->kind = kind; 245 246 /* We're now changing the node. Record the revision. */ 247 SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(change->changing) 248 || change->changing == base_revision); 249 change->changing = base_revision; 250 251 if (change->props == NULL) 252 { 253 /* Fetch the original set of properties. We'll apply edits to create 254 the new/target set of properties. 255 256 If this is a copied/moved now, then the original properties come 257 from there. If the node has been added, it starts with empty props. 258 Otherwise, we get the properties from BASE. */ 259 260 if (change->copyfrom_path) 261 SVN_ERR(eb->fetch_props_func(&change->props, 262 eb->fetch_props_baton, 263 change->copyfrom_path, 264 change->copyfrom_rev, 265 eb->edit_pool, scratch_pool)); 266 else if (change->action == RESTRUCTURE_ADD) 267 change->props = apr_hash_make(eb->edit_pool); 268 else 269 SVN_ERR(eb->fetch_props_func(&change->props, 270 eb->fetch_props_baton, 271 relpath, base_revision, 272 eb->edit_pool, scratch_pool)); 273 } 274 275 if (value == NULL) 276 svn_hash_sets(change->props, name, NULL); 277 else 278 svn_hash_sets(change->props, 279 apr_pstrdup(eb->edit_pool, name), 280 svn_string_dup(value, eb->edit_pool)); 281 282 return SVN_NO_ERROR; 283} 284 285 286/* Find all the paths which are immediate children of PATH and return their 287 basenames in a list. */ 288static apr_array_header_t * 289get_children(struct ev2_edit_baton *eb, 290 const char *path, 291 apr_pool_t *pool) 292{ 293 apr_array_header_t *children = apr_array_make(pool, 1, sizeof(const char *)); 294 apr_hash_index_t *hi; 295 296 for (hi = apr_hash_first(pool, eb->changes); hi; hi = apr_hash_next(hi)) 297 { 298 const char *repos_relpath = apr_hash_this_key(hi); 299 const char *child; 300 301 /* Find potential children. */ 302 child = svn_relpath_skip_ancestor(path, repos_relpath); 303 if (!child || !*child) 304 continue; 305 306 /* If we have a path separator, it's a deep child, so just ignore it. 307 ### Is there an API we should be using for this? */ 308 if (strchr(child, '/') != NULL) 309 continue; 310 311 APR_ARRAY_PUSH(children, const char *) = child; 312 } 313 314 return children; 315} 316 317 318static svn_error_t * 319process_actions(struct ev2_edit_baton *eb, 320 const char *repos_relpath, 321 const struct change_node *change, 322 apr_pool_t *scratch_pool) 323{ 324 apr_hash_t *props = NULL; 325 svn_stream_t *contents = NULL; 326 svn_checksum_t *checksum = NULL; 327 svn_node_kind_t kind = svn_node_unknown; 328 329 SVN_ERR_ASSERT(change != NULL); 330 331 if (change->unlock) 332 SVN_ERR(eb->do_unlock(eb->unlock_baton, repos_relpath, scratch_pool)); 333 334 if (change->action == RESTRUCTURE_DELETE) 335 { 336 /* If the action was left as RESTRUCTURE_DELETE, then a 337 replacement is not occurring. Just do the delete and bail. */ 338 SVN_ERR(svn_editor_delete(eb->editor, repos_relpath, 339 change->deleting)); 340 341 /* No further work possible on this node. */ 342 return SVN_NO_ERROR; 343 } 344 if (change->action == RESTRUCTURE_ADD_ABSENT) 345 { 346 SVN_ERR(svn_editor_add_absent(eb->editor, repos_relpath, 347 change->kind, change->deleting)); 348 349 /* No further work possible on this node. */ 350 return SVN_NO_ERROR; 351 } 352 353 if (change->contents_changed) 354 { 355 /* We can only set text on files. */ 356 /* ### validate we aren't overwriting KIND? */ 357 kind = svn_node_file; 358 359 if (change->contents_abspath) 360 { 361 /* ### the checksum might be in CHANGE->CHECKSUM */ 362 SVN_ERR(svn_io_file_checksum2(&checksum, change->contents_abspath, 363 svn_checksum_sha1, scratch_pool)); 364 SVN_ERR(svn_stream_open_readonly(&contents, change->contents_abspath, 365 scratch_pool, scratch_pool)); 366 } 367 else 368 { 369 contents = svn_stream_empty(scratch_pool); 370 checksum = svn_checksum_empty_checksum(svn_checksum_sha1, 371 scratch_pool); 372 } 373 } 374 375 if (change->props != NULL) 376 { 377 /* ### validate we aren't overwriting KIND? */ 378 kind = change->kind; 379 props = change->props; 380 } 381 382 if (change->action == RESTRUCTURE_ADD) 383 { 384 /* An add might be a replace. Grab the revnum we're replacing. */ 385 svn_revnum_t replaces_rev = change->deleting; 386 387 kind = change->kind; 388 389 if (change->copyfrom_path != NULL) 390 { 391 SVN_ERR(svn_editor_copy(eb->editor, change->copyfrom_path, 392 change->copyfrom_rev, 393 repos_relpath, replaces_rev)); 394 /* Fall through to possibly make changes post-copy. */ 395 } 396 else 397 { 398 /* If no properties were defined, then use an empty set. */ 399 if (props == NULL) 400 props = apr_hash_make(scratch_pool); 401 402 if (kind == svn_node_dir) 403 { 404 const apr_array_header_t *children; 405 406 children = get_children(eb, repos_relpath, scratch_pool); 407 SVN_ERR(svn_editor_add_directory(eb->editor, repos_relpath, 408 children, props, 409 replaces_rev)); 410 } 411 else 412 { 413 /* If this file was added, but apply_txdelta() was not 414 called (i.e., CONTENTS_CHANGED is FALSE), then we're adding 415 an empty file. */ 416 if (change->contents_abspath == NULL) 417 { 418 contents = svn_stream_empty(scratch_pool); 419 checksum = svn_checksum_empty_checksum(svn_checksum_sha1, 420 scratch_pool); 421 } 422 423 SVN_ERR(svn_editor_add_file(eb->editor, repos_relpath, 424 checksum, contents, props, 425 replaces_rev)); 426 } 427 428 /* No further work possible on this node. */ 429 return SVN_NO_ERROR; 430 } 431 } 432 433#if 0 434 /* There *should* be work for this node. But it seems that isn't true 435 in some cases. Future investigation... */ 436 SVN_ERR_ASSERT(props || contents); 437#endif 438 if (props || contents) 439 { 440 /* Changes to properties or content should have indicated the revision 441 it was intending to change. 442 443 Oop. Not true. The node may be locally-added. */ 444#if 0 445 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(change->changing)); 446#endif 447 448 /* ### we need to gather up the target set of children */ 449 450 if (kind == svn_node_dir) 451 SVN_ERR(svn_editor_alter_directory(eb->editor, repos_relpath, 452 change->changing, NULL, props)); 453 else 454 SVN_ERR(svn_editor_alter_file(eb->editor, repos_relpath, 455 change->changing, 456 checksum, contents, props)); 457 } 458 459 return SVN_NO_ERROR; 460} 461 462static svn_error_t * 463run_ev2_actions(struct ev2_edit_baton *eb, 464 apr_pool_t *scratch_pool) 465{ 466 apr_pool_t *iterpool; 467 468 iterpool = svn_pool_create(scratch_pool); 469 470 /* Possibly pick up where we left off. Ocassionally, we do some of these 471 as part of close_edit() and then some more as part of abort_edit() */ 472 for (; eb->paths_processed < eb->path_order->nelts; ++eb->paths_processed) 473 { 474 const char *repos_relpath = APR_ARRAY_IDX(eb->path_order, 475 eb->paths_processed, 476 const char *); 477 const struct change_node *change = svn_hash_gets(eb->changes, 478 repos_relpath); 479 480 svn_pool_clear(iterpool); 481 482 SVN_ERR(process_actions(eb, repos_relpath, change, iterpool)); 483 } 484 svn_pool_destroy(iterpool); 485 486 return SVN_NO_ERROR; 487} 488 489 490static const char * 491map_to_repos_relpath(struct ev2_edit_baton *eb, 492 const char *path_or_url, 493 apr_pool_t *result_pool) 494{ 495 if (svn_path_is_url(path_or_url)) 496 { 497 return svn_uri_skip_ancestor(eb->repos_root, path_or_url, result_pool); 498 } 499 else 500 { 501 return svn_relpath_join(eb->base_relpath, 502 path_or_url[0] == '/' 503 ? path_or_url + 1 : path_or_url, 504 result_pool); 505 } 506} 507 508 509static svn_error_t * 510ev2_set_target_revision(void *edit_baton, 511 svn_revnum_t target_revision, 512 apr_pool_t *scratch_pool) 513{ 514 struct ev2_edit_baton *eb = edit_baton; 515 516 if (eb->exb->target_revision) 517 SVN_ERR(eb->exb->target_revision(eb->exb->baton, target_revision, 518 scratch_pool)); 519 520 return SVN_NO_ERROR; 521} 522 523static svn_error_t * 524ev2_open_root(void *edit_baton, 525 svn_revnum_t base_revision, 526 apr_pool_t *result_pool, 527 void **root_baton) 528{ 529 struct ev2_dir_baton *db = apr_pcalloc(result_pool, sizeof(*db)); 530 struct ev2_edit_baton *eb = edit_baton; 531 532 db->eb = eb; 533 db->path = apr_pstrdup(eb->edit_pool, eb->base_relpath); 534 db->base_revision = base_revision; 535 536 *root_baton = db; 537 538 if (eb->exb->start_edit) 539 SVN_ERR(eb->exb->start_edit(eb->exb->baton, base_revision)); 540 541 return SVN_NO_ERROR; 542} 543 544static svn_error_t * 545ev2_delete_entry(const char *path, 546 svn_revnum_t revision, 547 void *parent_baton, 548 apr_pool_t *scratch_pool) 549{ 550 struct ev2_dir_baton *pb = parent_baton; 551 svn_revnum_t base_revision; 552 const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool); 553 struct change_node *change = locate_change(pb->eb, relpath); 554 555 if (SVN_IS_VALID_REVNUM(revision)) 556 base_revision = revision; 557 else 558 base_revision = pb->base_revision; 559 560 SVN_ERR_ASSERT(change->action == RESTRUCTURE_NONE); 561 change->action = RESTRUCTURE_DELETE; 562 563 SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(change->deleting) 564 || change->deleting == base_revision); 565 change->deleting = base_revision; 566 567 return SVN_NO_ERROR; 568} 569 570static svn_error_t * 571ev2_add_directory(const char *path, 572 void *parent_baton, 573 const char *copyfrom_path, 574 svn_revnum_t copyfrom_revision, 575 apr_pool_t *result_pool, 576 void **child_baton) 577{ 578 /* ### fix this? */ 579 apr_pool_t *scratch_pool = result_pool; 580 struct ev2_dir_baton *pb = parent_baton; 581 struct ev2_dir_baton *cb = apr_pcalloc(result_pool, sizeof(*cb)); 582 const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool); 583 struct change_node *change = locate_change(pb->eb, relpath); 584 585 /* ### assert that RESTRUCTURE is NONE or DELETE? */ 586 change->action = RESTRUCTURE_ADD; 587 change->kind = svn_node_dir; 588 589 cb->eb = pb->eb; 590 cb->path = apr_pstrdup(result_pool, relpath); 591 cb->base_revision = pb->base_revision; 592 *child_baton = cb; 593 594 if (!copyfrom_path) 595 { 596 if (pb->copyfrom_relpath) 597 { 598 const char *name = svn_relpath_basename(relpath, scratch_pool); 599 cb->copyfrom_relpath = svn_relpath_join(pb->copyfrom_relpath, name, 600 result_pool); 601 cb->copyfrom_rev = pb->copyfrom_rev; 602 } 603 } 604 else 605 { 606 /* A copy */ 607 608 change->copyfrom_path = map_to_repos_relpath(pb->eb, copyfrom_path, 609 pb->eb->edit_pool); 610 change->copyfrom_rev = copyfrom_revision; 611 612 cb->copyfrom_relpath = change->copyfrom_path; 613 cb->copyfrom_rev = change->copyfrom_rev; 614 } 615 616 return SVN_NO_ERROR; 617} 618 619static svn_error_t * 620ev2_open_directory(const char *path, 621 void *parent_baton, 622 svn_revnum_t base_revision, 623 apr_pool_t *result_pool, 624 void **child_baton) 625{ 626 /* ### fix this? */ 627 apr_pool_t *scratch_pool = result_pool; 628 struct ev2_dir_baton *pb = parent_baton; 629 struct ev2_dir_baton *db = apr_pcalloc(result_pool, sizeof(*db)); 630 const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool); 631 632 db->eb = pb->eb; 633 db->path = apr_pstrdup(result_pool, relpath); 634 db->base_revision = base_revision; 635 636 if (pb->copyfrom_relpath) 637 { 638 /* We are inside a copy. */ 639 const char *name = svn_relpath_basename(relpath, scratch_pool); 640 641 db->copyfrom_relpath = svn_relpath_join(pb->copyfrom_relpath, name, 642 result_pool); 643 db->copyfrom_rev = pb->copyfrom_rev; 644 } 645 646 *child_baton = db; 647 return SVN_NO_ERROR; 648} 649 650static svn_error_t * 651ev2_change_dir_prop(void *dir_baton, 652 const char *name, 653 const svn_string_t *value, 654 apr_pool_t *scratch_pool) 655{ 656 struct ev2_dir_baton *db = dir_baton; 657 658 SVN_ERR(apply_propedit(db->eb, db->path, svn_node_dir, db->base_revision, 659 name, value, scratch_pool)); 660 661 return SVN_NO_ERROR; 662} 663 664static svn_error_t * 665ev2_close_directory(void *dir_baton, 666 apr_pool_t *scratch_pool) 667{ 668 return SVN_NO_ERROR; 669} 670 671static svn_error_t * 672ev2_absent_directory(const char *path, 673 void *parent_baton, 674 apr_pool_t *scratch_pool) 675{ 676 struct ev2_dir_baton *pb = parent_baton; 677 const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool); 678 struct change_node *change = locate_change(pb->eb, relpath); 679 680 /* ### assert that RESTRUCTURE is NONE or DELETE? */ 681 change->action = RESTRUCTURE_ADD_ABSENT; 682 change->kind = svn_node_dir; 683 684 return SVN_NO_ERROR; 685} 686 687static svn_error_t * 688ev2_add_file(const char *path, 689 void *parent_baton, 690 const char *copyfrom_path, 691 svn_revnum_t copyfrom_revision, 692 apr_pool_t *result_pool, 693 void **file_baton) 694{ 695 /* ### fix this? */ 696 apr_pool_t *scratch_pool = result_pool; 697 struct ev2_file_baton *fb = apr_pcalloc(result_pool, sizeof(*fb)); 698 struct ev2_dir_baton *pb = parent_baton; 699 const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool); 700 struct change_node *change = locate_change(pb->eb, relpath); 701 702 /* ### assert that RESTRUCTURE is NONE or DELETE? */ 703 change->action = RESTRUCTURE_ADD; 704 change->kind = svn_node_file; 705 706 fb->eb = pb->eb; 707 fb->path = apr_pstrdup(result_pool, relpath); 708 fb->base_revision = pb->base_revision; 709 *file_baton = fb; 710 711 if (!copyfrom_path) 712 { 713 /* Don't bother fetching the base, as in an add we don't have a base. */ 714 fb->delta_base = NULL; 715 } 716 else 717 { 718 /* A copy */ 719 720 change->copyfrom_path = map_to_repos_relpath(fb->eb, copyfrom_path, 721 fb->eb->edit_pool); 722 change->copyfrom_rev = copyfrom_revision; 723 724 SVN_ERR(fb->eb->fetch_base_func(&fb->delta_base, 725 fb->eb->fetch_base_baton, 726 change->copyfrom_path, 727 change->copyfrom_rev, 728 result_pool, scratch_pool)); 729 } 730 731 return SVN_NO_ERROR; 732} 733 734static svn_error_t * 735ev2_open_file(const char *path, 736 void *parent_baton, 737 svn_revnum_t base_revision, 738 apr_pool_t *result_pool, 739 void **file_baton) 740{ 741 /* ### fix this? */ 742 apr_pool_t *scratch_pool = result_pool; 743 struct ev2_file_baton *fb = apr_pcalloc(result_pool, sizeof(*fb)); 744 struct ev2_dir_baton *pb = parent_baton; 745 const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool); 746 747 fb->eb = pb->eb; 748 fb->path = apr_pstrdup(result_pool, relpath); 749 fb->base_revision = base_revision; 750 751 if (pb->copyfrom_relpath) 752 { 753 /* We're in a copied directory, so the delta base is going to be 754 based up on the copy source. */ 755 const char *name = svn_relpath_basename(relpath, scratch_pool); 756 const char *copyfrom_relpath = svn_relpath_join(pb->copyfrom_relpath, 757 name, 758 scratch_pool); 759 760 SVN_ERR(fb->eb->fetch_base_func(&fb->delta_base, 761 fb->eb->fetch_base_baton, 762 copyfrom_relpath, pb->copyfrom_rev, 763 result_pool, scratch_pool)); 764 } 765 else 766 { 767 SVN_ERR(fb->eb->fetch_base_func(&fb->delta_base, 768 fb->eb->fetch_base_baton, 769 relpath, base_revision, 770 result_pool, scratch_pool)); 771 } 772 773 *file_baton = fb; 774 return SVN_NO_ERROR; 775} 776 777struct handler_baton 778{ 779 svn_txdelta_window_handler_t apply_handler; 780 void *apply_baton; 781 782 svn_stream_t *source; 783 784 apr_pool_t *pool; 785}; 786 787static svn_error_t * 788window_handler(svn_txdelta_window_t *window, void *baton) 789{ 790 struct handler_baton *hb = baton; 791 svn_error_t *err; 792 793 err = hb->apply_handler(window, hb->apply_baton); 794 if (window != NULL && !err) 795 return SVN_NO_ERROR; 796 797 SVN_ERR(svn_stream_close(hb->source)); 798 799 svn_pool_destroy(hb->pool); 800 801 return svn_error_trace(err); 802} 803 804/* Lazy-open handler for getting a read-only stream of the delta base. */ 805static svn_error_t * 806open_delta_base(svn_stream_t **stream, void *baton, 807 apr_pool_t *result_pool, apr_pool_t *scratch_pool) 808{ 809 const char *const delta_base = baton; 810 return svn_stream_open_readonly(stream, delta_base, 811 result_pool, scratch_pool); 812} 813 814/* Lazy-open handler for opening a stream for the delta result. */ 815static svn_error_t * 816open_delta_target(svn_stream_t **stream, void *baton, 817 apr_pool_t *result_pool, apr_pool_t *scratch_pool) 818{ 819 const char **delta_target = baton; 820 return svn_stream_open_unique(stream, delta_target, NULL, 821 svn_io_file_del_on_pool_cleanup, 822 result_pool, scratch_pool); 823} 824 825static svn_error_t * 826ev2_apply_textdelta(void *file_baton, 827 const char *base_checksum, 828 apr_pool_t *result_pool, 829 svn_txdelta_window_handler_t *handler, 830 void **handler_baton) 831{ 832 struct ev2_file_baton *fb = file_baton; 833 apr_pool_t *handler_pool = svn_pool_create(fb->eb->edit_pool); 834 struct handler_baton *hb = apr_pcalloc(handler_pool, sizeof(*hb)); 835 struct change_node *change; 836 svn_stream_t *target; 837 838 change = locate_change(fb->eb, fb->path); 839 SVN_ERR_ASSERT(!change->contents_changed); 840 SVN_ERR_ASSERT(change->contents_abspath == NULL); 841 SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(change->changing) 842 || change->changing == fb->base_revision); 843 change->changing = fb->base_revision; 844 845 if (! fb->delta_base) 846 hb->source = svn_stream_empty(handler_pool); 847 else 848 hb->source = svn_stream_lazyopen_create(open_delta_base, 849 (char*)fb->delta_base, 850 FALSE, handler_pool); 851 852 change->contents_changed = TRUE; 853 target = svn_stream_lazyopen_create(open_delta_target, 854 &change->contents_abspath, 855 FALSE, fb->eb->edit_pool); 856 857 svn_txdelta_apply(hb->source, target, 858 NULL, NULL, 859 handler_pool, 860 &hb->apply_handler, &hb->apply_baton); 861 862 hb->pool = handler_pool; 863 864 *handler_baton = hb; 865 *handler = window_handler; 866 867 return SVN_NO_ERROR; 868} 869 870static svn_error_t * 871ev2_change_file_prop(void *file_baton, 872 const char *name, 873 const svn_string_t *value, 874 apr_pool_t *scratch_pool) 875{ 876 struct ev2_file_baton *fb = file_baton; 877 878 if (!strcmp(name, SVN_PROP_ENTRY_LOCK_TOKEN) && value == NULL) 879 { 880 /* We special case the lock token propery deletion, which is the 881 server's way of telling the client to unlock the path. */ 882 883 /* ### this duplicates much of apply_propedit(). fix in future. */ 884 const char *relpath = map_to_repos_relpath(fb->eb, fb->path, 885 scratch_pool); 886 struct change_node *change = locate_change(fb->eb, relpath); 887 888 change->unlock = TRUE; 889 } 890 891 SVN_ERR(apply_propedit(fb->eb, fb->path, svn_node_file, fb->base_revision, 892 name, value, scratch_pool)); 893 894 return SVN_NO_ERROR; 895} 896 897static svn_error_t * 898ev2_close_file(void *file_baton, 899 const char *text_checksum, 900 apr_pool_t *scratch_pool) 901{ 902 return SVN_NO_ERROR; 903} 904 905static svn_error_t * 906ev2_absent_file(const char *path, 907 void *parent_baton, 908 apr_pool_t *scratch_pool) 909{ 910 struct ev2_dir_baton *pb = parent_baton; 911 const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool); 912 struct change_node *change = locate_change(pb->eb, relpath); 913 914 /* ### assert that RESTRUCTURE is NONE or DELETE? */ 915 change->action = RESTRUCTURE_ADD_ABSENT; 916 change->kind = svn_node_file; 917 918 return SVN_NO_ERROR; 919} 920 921static svn_error_t * 922ev2_close_edit(void *edit_baton, 923 apr_pool_t *scratch_pool) 924{ 925 struct ev2_edit_baton *eb = edit_baton; 926 927 SVN_ERR(run_ev2_actions(edit_baton, scratch_pool)); 928 eb->closed = TRUE; 929 return svn_error_trace(svn_editor_complete(eb->editor)); 930} 931 932static svn_error_t * 933ev2_abort_edit(void *edit_baton, 934 apr_pool_t *scratch_pool) 935{ 936 struct ev2_edit_baton *eb = edit_baton; 937 938 SVN_ERR(run_ev2_actions(edit_baton, scratch_pool)); 939 if (!eb->closed) 940 return svn_error_trace(svn_editor_abort(eb->editor)); 941 else 942 return SVN_NO_ERROR; 943} 944 945/* Return a svn_delta_editor_t * in DEDITOR, with an accompanying baton in 946 * DEDITOR_BATON, which will drive EDITOR. These will both be 947 * allocated in RESULT_POOL, which may become large and long-lived; 948 * SCRATCH_POOL is used for temporary allocations. 949 * 950 * The other parameters are as follows: 951 * - UNLOCK_FUNC / UNLOCK_BATON: A callback / baton which will be called 952 * when an unlocking action is received. 953 * - FOUND_ABS_PATHS: A pointer to a boolean flag which will be set if 954 * this shim determines that it is receiving absolute paths. 955 * - FETCH_PROPS_FUNC / FETCH_PROPS_BATON: A callback / baton pair which 956 * will be used by the shim handlers if they need to determine the 957 * existing properties on a path. 958 * - FETCH_BASE_FUNC / FETCH_BASE_BATON: A callback / baton pair which will 959 * be used by the shims handlers if they need to determine the base 960 * text of a path. It should only be invoked for files. 961 * - EXB: An 'extra baton' which is used to communicate between the shims. 962 * Its callbacks should be invoked at the appropriate time by this 963 * shim. 964 */ 965svn_error_t * 966svn_delta__delta_from_editor(const svn_delta_editor_t **deditor, 967 void **dedit_baton, 968 svn_editor_t *editor, 969 svn_delta__unlock_func_t unlock_func, 970 void *unlock_baton, 971 svn_boolean_t *found_abs_paths, 972 const char *repos_root, 973 const char *base_relpath, 974 svn_delta_fetch_props_func_t fetch_props_func, 975 void *fetch_props_baton, 976 svn_delta_fetch_base_func_t fetch_base_func, 977 void *fetch_base_baton, 978 struct svn_delta__extra_baton *exb, 979 apr_pool_t *pool) 980{ 981 /* Static 'cause we don't want it to be on the stack. */ 982 static svn_delta_editor_t delta_editor = { 983 ev2_set_target_revision, 984 ev2_open_root, 985 ev2_delete_entry, 986 ev2_add_directory, 987 ev2_open_directory, 988 ev2_change_dir_prop, 989 ev2_close_directory, 990 ev2_absent_directory, 991 ev2_add_file, 992 ev2_open_file, 993 ev2_apply_textdelta, 994 ev2_change_file_prop, 995 ev2_close_file, 996 ev2_absent_file, 997 ev2_close_edit, 998 ev2_abort_edit 999 }; 1000 struct ev2_edit_baton *eb = apr_pcalloc(pool, sizeof(*eb)); 1001 1002 if (!base_relpath) 1003 base_relpath = ""; 1004 else if (base_relpath[0] == '/') 1005 base_relpath += 1; 1006 1007 eb->editor = editor; 1008 eb->changes = apr_hash_make(pool); 1009 eb->path_order = apr_array_make(pool, 1, sizeof(const char *)); 1010 eb->edit_pool = pool; 1011 eb->found_abs_paths = found_abs_paths; 1012 *eb->found_abs_paths = FALSE; 1013 eb->exb = exb; 1014 eb->repos_root = apr_pstrdup(pool, repos_root); 1015 eb->base_relpath = apr_pstrdup(pool, base_relpath); 1016 1017 eb->fetch_props_func = fetch_props_func; 1018 eb->fetch_props_baton = fetch_props_baton; 1019 1020 eb->fetch_base_func = fetch_base_func; 1021 eb->fetch_base_baton = fetch_base_baton; 1022 1023 eb->do_unlock = unlock_func; 1024 eb->unlock_baton = unlock_baton; 1025 1026 *dedit_baton = eb; 1027 *deditor = &delta_editor; 1028 1029 return SVN_NO_ERROR; 1030} 1031 1032 1033/* ### note the similarity to struct change_node. these structures will 1034 ### be combined in the future. */ 1035struct operation { 1036 /* ### leave these two here for now. still used. */ 1037 svn_revnum_t base_revision; 1038 void *baton; 1039}; 1040 1041struct editor_baton 1042{ 1043 const svn_delta_editor_t *deditor; 1044 void *dedit_baton; 1045 1046 svn_delta_fetch_kind_func_t fetch_kind_func; 1047 void *fetch_kind_baton; 1048 1049 svn_delta_fetch_props_func_t fetch_props_func; 1050 void *fetch_props_baton; 1051 1052 struct operation root; 1053 svn_boolean_t *make_abs_paths; 1054 const char *repos_root; 1055 const char *base_relpath; 1056 1057 /* REPOS_RELPATH -> struct change_node * */ 1058 apr_hash_t *changes; 1059 1060 apr_pool_t *edit_pool; 1061}; 1062 1063 1064/* Insert a new change for RELPATH, or return an existing one. */ 1065static struct change_node * 1066insert_change(const char *relpath, 1067 apr_hash_t *changes) 1068{ 1069 apr_pool_t *result_pool; 1070 struct change_node *change; 1071 1072 change = svn_hash_gets(changes, relpath); 1073 if (change != NULL) 1074 return change; 1075 1076 result_pool = apr_hash_pool_get(changes); 1077 1078 /* Return an empty change. Callers will tweak as needed. */ 1079 change = apr_pcalloc(result_pool, sizeof(*change)); 1080 change->changing = SVN_INVALID_REVNUM; 1081 change->deleting = SVN_INVALID_REVNUM; 1082 1083 svn_hash_sets(changes, apr_pstrdup(result_pool, relpath), change); 1084 1085 return change; 1086} 1087 1088 1089/* This implements svn_editor_cb_add_directory_t */ 1090static svn_error_t * 1091add_directory_cb(void *baton, 1092 const char *relpath, 1093 const apr_array_header_t *children, 1094 apr_hash_t *props, 1095 svn_revnum_t replaces_rev, 1096 apr_pool_t *scratch_pool) 1097{ 1098 struct editor_baton *eb = baton; 1099 struct change_node *change = insert_change(relpath, eb->changes); 1100 1101 change->action = RESTRUCTURE_ADD; 1102 change->kind = svn_node_dir; 1103 change->deleting = replaces_rev; 1104 change->props = svn_prop_hash_dup(props, eb->edit_pool); 1105 1106 return SVN_NO_ERROR; 1107} 1108 1109/* This implements svn_editor_cb_add_file_t */ 1110static svn_error_t * 1111add_file_cb(void *baton, 1112 const char *relpath, 1113 const svn_checksum_t *checksum, 1114 svn_stream_t *contents, 1115 apr_hash_t *props, 1116 svn_revnum_t replaces_rev, 1117 apr_pool_t *scratch_pool) 1118{ 1119 struct editor_baton *eb = baton; 1120 const char *tmp_filename; 1121 svn_stream_t *tmp_stream; 1122 svn_checksum_t *md5_checksum; 1123 struct change_node *change = insert_change(relpath, eb->changes); 1124 1125 /* We may need to re-checksum these contents */ 1126 if (!(checksum && checksum->kind == svn_checksum_md5)) 1127 contents = svn_stream_checksummed2(contents, &md5_checksum, NULL, 1128 svn_checksum_md5, TRUE, scratch_pool); 1129 else 1130 md5_checksum = (svn_checksum_t *)checksum; 1131 1132 /* Spool the contents to a tempfile, and provide that to the driver. */ 1133 SVN_ERR(svn_stream_open_unique(&tmp_stream, &tmp_filename, NULL, 1134 svn_io_file_del_on_pool_cleanup, 1135 eb->edit_pool, scratch_pool)); 1136 SVN_ERR(svn_stream_copy3(contents, tmp_stream, NULL, NULL, scratch_pool)); 1137 1138 change->action = RESTRUCTURE_ADD; 1139 change->kind = svn_node_file; 1140 change->deleting = replaces_rev; 1141 change->props = svn_prop_hash_dup(props, eb->edit_pool); 1142 change->contents_changed = TRUE; 1143 change->contents_abspath = tmp_filename; 1144 change->checksum = svn_checksum_dup(md5_checksum, eb->edit_pool); 1145 1146 return SVN_NO_ERROR; 1147} 1148 1149/* This implements svn_editor_cb_add_symlink_t */ 1150static svn_error_t * 1151add_symlink_cb(void *baton, 1152 const char *relpath, 1153 const char *target, 1154 apr_hash_t *props, 1155 svn_revnum_t replaces_rev, 1156 apr_pool_t *scratch_pool) 1157{ 1158#if 0 1159 struct editor_baton *eb = baton; 1160 struct change_node *change = insert_change(relpath, eb->changes); 1161 1162 change->action = RESTRUCTURE_ADD; 1163 change->kind = svn_node_symlink; 1164 change->deleting = replaces_rev; 1165 change->props = svn_prop_hash_dup(props, eb->edit_pool); 1166 /* ### target */ 1167#endif 1168 1169 SVN__NOT_IMPLEMENTED(); 1170} 1171 1172/* This implements svn_editor_cb_add_absent_t */ 1173static svn_error_t * 1174add_absent_cb(void *baton, 1175 const char *relpath, 1176 svn_node_kind_t kind, 1177 svn_revnum_t replaces_rev, 1178 apr_pool_t *scratch_pool) 1179{ 1180 struct editor_baton *eb = baton; 1181 struct change_node *change = insert_change(relpath, eb->changes); 1182 1183 change->action = RESTRUCTURE_ADD_ABSENT; 1184 change->kind = kind; 1185 change->deleting = replaces_rev; 1186 1187 return SVN_NO_ERROR; 1188} 1189 1190/* This implements svn_editor_cb_alter_directory_t */ 1191static svn_error_t * 1192alter_directory_cb(void *baton, 1193 const char *relpath, 1194 svn_revnum_t revision, 1195 const apr_array_header_t *children, 1196 apr_hash_t *props, 1197 apr_pool_t *scratch_pool) 1198{ 1199 struct editor_baton *eb = baton; 1200 struct change_node *change = insert_change(relpath, eb->changes); 1201 1202 /* ### should we verify the kind is truly a directory? */ 1203 1204 /* ### do we need to do anything with CHILDREN? */ 1205 1206 /* Note: this node may already have information in CHANGE as a result 1207 of an earlier copy/move operation. */ 1208 change->kind = svn_node_dir; 1209 change->changing = revision; 1210 change->props = svn_prop_hash_dup(props, eb->edit_pool); 1211 1212 return SVN_NO_ERROR; 1213} 1214 1215/* This implements svn_editor_cb_alter_file_t */ 1216static svn_error_t * 1217alter_file_cb(void *baton, 1218 const char *relpath, 1219 svn_revnum_t revision, 1220 const svn_checksum_t *checksum, 1221 svn_stream_t *contents, 1222 apr_hash_t *props, 1223 apr_pool_t *scratch_pool) 1224{ 1225 struct editor_baton *eb = baton; 1226 const char *tmp_filename; 1227 svn_stream_t *tmp_stream; 1228 svn_checksum_t *md5_checksum; 1229 struct change_node *change = insert_change(relpath, eb->changes); 1230 1231 /* ### should we verify the kind is truly a file? */ 1232 1233 if (contents) 1234 { 1235 /* We may need to re-checksum these contents */ 1236 if (checksum && checksum->kind == svn_checksum_md5) 1237 md5_checksum = (svn_checksum_t *)checksum; 1238 else 1239 contents = svn_stream_checksummed2(contents, &md5_checksum, NULL, 1240 svn_checksum_md5, TRUE, 1241 scratch_pool); 1242 1243 /* Spool the contents to a tempfile, and provide that to the driver. */ 1244 SVN_ERR(svn_stream_open_unique(&tmp_stream, &tmp_filename, NULL, 1245 svn_io_file_del_on_pool_cleanup, 1246 eb->edit_pool, scratch_pool)); 1247 SVN_ERR(svn_stream_copy3(contents, tmp_stream, NULL, NULL, 1248 scratch_pool)); 1249 } 1250 1251 /* Note: this node may already have information in CHANGE as a result 1252 of an earlier copy/move operation. */ 1253 1254 change->kind = svn_node_file; 1255 change->changing = revision; 1256 if (props != NULL) 1257 change->props = svn_prop_hash_dup(props, eb->edit_pool); 1258 if (contents != NULL) 1259 { 1260 change->contents_changed = TRUE; 1261 change->contents_abspath = tmp_filename; 1262 change->checksum = svn_checksum_dup(md5_checksum, eb->edit_pool); 1263 } 1264 1265 return SVN_NO_ERROR; 1266} 1267 1268/* This implements svn_editor_cb_alter_symlink_t */ 1269static svn_error_t * 1270alter_symlink_cb(void *baton, 1271 const char *relpath, 1272 svn_revnum_t revision, 1273 const char *target, 1274 apr_hash_t *props, 1275 apr_pool_t *scratch_pool) 1276{ 1277 /* ### should we verify the kind is truly a symlink? */ 1278 1279 /* ### do something */ 1280 1281 SVN__NOT_IMPLEMENTED(); 1282} 1283 1284/* This implements svn_editor_cb_delete_t */ 1285static svn_error_t * 1286delete_cb(void *baton, 1287 const char *relpath, 1288 svn_revnum_t revision, 1289 apr_pool_t *scratch_pool) 1290{ 1291 struct editor_baton *eb = baton; 1292 struct change_node *change = insert_change(relpath, eb->changes); 1293 1294 change->action = RESTRUCTURE_DELETE; 1295 /* change->kind = svn_node_unknown; */ 1296 change->deleting = revision; 1297 1298 return SVN_NO_ERROR; 1299} 1300 1301/* This implements svn_editor_cb_copy_t */ 1302static svn_error_t * 1303copy_cb(void *baton, 1304 const char *src_relpath, 1305 svn_revnum_t src_revision, 1306 const char *dst_relpath, 1307 svn_revnum_t replaces_rev, 1308 apr_pool_t *scratch_pool) 1309{ 1310 struct editor_baton *eb = baton; 1311 struct change_node *change = insert_change(dst_relpath, eb->changes); 1312 1313 change->action = RESTRUCTURE_ADD; 1314 /* change->kind = svn_node_unknown; */ 1315 change->deleting = replaces_rev; 1316 change->copyfrom_path = apr_pstrdup(eb->edit_pool, src_relpath); 1317 change->copyfrom_rev = src_revision; 1318 1319 /* We need the source's kind to know whether to call add_directory() 1320 or add_file() later on. */ 1321 SVN_ERR(eb->fetch_kind_func(&change->kind, eb->fetch_kind_baton, 1322 change->copyfrom_path, 1323 change->copyfrom_rev, 1324 scratch_pool)); 1325 1326 /* Note: this node may later have alter_*() called on it. */ 1327 1328 return SVN_NO_ERROR; 1329} 1330 1331/* This implements svn_editor_cb_move_t */ 1332static svn_error_t * 1333move_cb(void *baton, 1334 const char *src_relpath, 1335 svn_revnum_t src_revision, 1336 const char *dst_relpath, 1337 svn_revnum_t replaces_rev, 1338 apr_pool_t *scratch_pool) 1339{ 1340 struct editor_baton *eb = baton; 1341 struct change_node *change; 1342 1343 /* Remap a move into a DELETE + COPY. */ 1344 1345 change = insert_change(src_relpath, eb->changes); 1346 change->action = RESTRUCTURE_DELETE; 1347 /* change->kind = svn_node_unknown; */ 1348 change->deleting = src_revision; 1349 1350 change = insert_change(dst_relpath, eb->changes); 1351 change->action = RESTRUCTURE_ADD; 1352 /* change->kind = svn_node_unknown; */ 1353 change->deleting = replaces_rev; 1354 change->copyfrom_path = apr_pstrdup(eb->edit_pool, src_relpath); 1355 change->copyfrom_rev = src_revision; 1356 1357 /* We need the source's kind to know whether to call add_directory() 1358 or add_file() later on. */ 1359 SVN_ERR(eb->fetch_kind_func(&change->kind, eb->fetch_kind_baton, 1360 change->copyfrom_path, 1361 change->copyfrom_rev, 1362 scratch_pool)); 1363 1364 /* Note: this node may later have alter_*() called on it. */ 1365 1366 return SVN_NO_ERROR; 1367} 1368 1369static int 1370count_components(const char *relpath) 1371{ 1372 int count = 1; 1373 const char *slash = strchr(relpath, '/'); 1374 1375 while (slash != NULL) 1376 { 1377 ++count; 1378 slash = strchr(slash + 1, '/'); 1379 } 1380 return count; 1381} 1382 1383 1384static int 1385sort_deletes_first(const svn_sort__item_t *item1, 1386 const svn_sort__item_t *item2) 1387{ 1388 const char *relpath1 = item1->key; 1389 const char *relpath2 = item2->key; 1390 const struct change_node *change1 = item1->value; 1391 const struct change_node *change2 = item2->value; 1392 const char *slash1; 1393 const char *slash2; 1394 ptrdiff_t len1; 1395 ptrdiff_t len2; 1396 1397 /* Force the root to always sort first. Otherwise, it may look like a 1398 sibling of its children (no slashes), and could get sorted *after* 1399 any children that get deleted. */ 1400 if (*relpath1 == '\0') 1401 return -1; 1402 if (*relpath2 == '\0') 1403 return 1; 1404 1405 /* Are these two items siblings? The 'if' statement tests if they are 1406 siblings in the root directory, or that slashes were found in both 1407 paths, that the length of the paths to those slashes match, and that 1408 the path contents up to those slashes also match. */ 1409 slash1 = strrchr(relpath1, '/'); 1410 slash2 = strrchr(relpath2, '/'); 1411 if ((slash1 == NULL && slash2 == NULL) 1412 || (slash1 != NULL 1413 && slash2 != NULL 1414 && (len1 = slash1 - relpath1) == (len2 = slash2 - relpath2) 1415 && memcmp(relpath1, relpath2, len1) == 0)) 1416 { 1417 if (change1->action == RESTRUCTURE_DELETE) 1418 { 1419 if (change2->action == RESTRUCTURE_DELETE) 1420 { 1421 /* If both items are being deleted, then we don't care about 1422 the order. State they are equal. */ 1423 return 0; 1424 } 1425 1426 /* ITEM1 is being deleted. Sort it before the surviving item. */ 1427 return -1; 1428 } 1429 if (change2->action == RESTRUCTURE_DELETE) 1430 /* ITEM2 is being deleted. Sort it before the surviving item. */ 1431 return 1; 1432 1433 /* Normally, we don't care about the ordering of two siblings. However, 1434 if these siblings are directories, then we need to provide an 1435 ordering so that the quicksort algorithm will further sort them 1436 relative to the maybe-directory's children. 1437 1438 Without this additional ordering, we could see that A/B/E and A/B/F 1439 are equal. And then A/B/E/child is sorted before A/B/F. But since 1440 E and F are "equal", A/B/E could arrive *after* A/B/F and after the 1441 A/B/E/child node. */ 1442 1443 /* FALLTHROUGH */ 1444 } 1445 1446 /* Paths-to-be-deleted with fewer components always sort earlier. 1447 1448 For example, gamma will sort before E/alpha. 1449 1450 Without this test, E/alpha lexicographically sorts before gamma, 1451 but gamma sorts before E when gamma is to be deleted. This kind of 1452 ordering would place E/alpha before E. Not good. 1453 1454 With this test, gamma sorts before E/alpha. E and E/alpha are then 1455 sorted by svn_path_compare_paths() (which places E before E/alpha). */ 1456 if (change1->action == RESTRUCTURE_DELETE 1457 || change2->action == RESTRUCTURE_DELETE) 1458 { 1459 int count1 = count_components(relpath1); 1460 int count2 = count_components(relpath2); 1461 1462 if (count1 < count2 && change1->action == RESTRUCTURE_DELETE) 1463 return -1; 1464 if (count1 > count2 && change2->action == RESTRUCTURE_DELETE) 1465 return 1; 1466 } 1467 1468 /* Use svn_path_compare_paths() to get correct depth-based ordering. */ 1469 return svn_path_compare_paths(relpath1, relpath2); 1470} 1471 1472 1473static const apr_array_header_t * 1474get_sorted_paths(apr_hash_t *changes, 1475 const char *base_relpath, 1476 apr_pool_t *scratch_pool) 1477{ 1478 const apr_array_header_t *items; 1479 apr_array_header_t *paths; 1480 int i; 1481 1482 /* Construct a sorted array of svn_sort__item_t structs. Within a given 1483 directory, nodes that are to be deleted will appear first. */ 1484 items = svn_sort__hash(changes, sort_deletes_first, scratch_pool); 1485 1486 /* Build a new array with just the paths, trimmed to relative paths for 1487 the Ev1 drive. */ 1488 paths = apr_array_make(scratch_pool, items->nelts, sizeof(const char *)); 1489 for (i = items->nelts; i--; ) 1490 { 1491 const svn_sort__item_t *item; 1492 1493 item = &APR_ARRAY_IDX(items, i, const svn_sort__item_t); 1494 APR_ARRAY_IDX(paths, i, const char *) 1495 = svn_relpath_skip_ancestor(base_relpath, item->key); 1496 } 1497 1498 /* We didn't use PUSH, so set the proper number of elements. */ 1499 paths->nelts = items->nelts; 1500 1501 return paths; 1502} 1503 1504 1505static svn_error_t * 1506drive_ev1_props(const struct editor_baton *eb, 1507 const char *repos_relpath, 1508 const struct change_node *change, 1509 void *node_baton, 1510 apr_pool_t *scratch_pool) 1511{ 1512 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1513 apr_hash_t *old_props; 1514 apr_array_header_t *propdiffs; 1515 int i; 1516 1517 /* If there are no properties to install, then just exit. */ 1518 if (change->props == NULL) 1519 return SVN_NO_ERROR; 1520 1521 if (change->copyfrom_path) 1522 { 1523 /* The pristine properties are from the copy/move source. */ 1524 SVN_ERR(eb->fetch_props_func(&old_props, eb->fetch_props_baton, 1525 change->copyfrom_path, 1526 change->copyfrom_rev, 1527 scratch_pool, iterpool)); 1528 } 1529 else if (change->action == RESTRUCTURE_ADD) 1530 { 1531 /* Locally-added nodes have no pristine properties. 1532 1533 Note: we can use iterpool; this hash only needs to survive to 1534 the propdiffs call, and there are no contents to preserve. */ 1535 old_props = apr_hash_make(iterpool); 1536 } 1537 else 1538 { 1539 /* Fetch the pristine properties for whatever we're editing. */ 1540 SVN_ERR(eb->fetch_props_func(&old_props, eb->fetch_props_baton, 1541 repos_relpath, change->changing, 1542 scratch_pool, iterpool)); 1543 } 1544 1545 SVN_ERR(svn_prop_diffs(&propdiffs, change->props, old_props, scratch_pool)); 1546 1547 for (i = 0; i < propdiffs->nelts; i++) 1548 { 1549 /* Note: the array returned by svn_prop_diffs() is an array of 1550 actual structures, not pointers to them. */ 1551 const svn_prop_t *prop = &APR_ARRAY_IDX(propdiffs, i, svn_prop_t); 1552 1553 svn_pool_clear(iterpool); 1554 1555 if (change->kind == svn_node_dir) 1556 SVN_ERR(eb->deditor->change_dir_prop(node_baton, 1557 prop->name, prop->value, 1558 iterpool)); 1559 else 1560 SVN_ERR(eb->deditor->change_file_prop(node_baton, 1561 prop->name, prop->value, 1562 iterpool)); 1563 } 1564 1565 /* Handle the funky unlock protocol. Note: only possibly on files. */ 1566 if (change->unlock) 1567 { 1568 SVN_ERR_ASSERT(change->kind == svn_node_file); 1569 SVN_ERR(eb->deditor->change_file_prop(node_baton, 1570 SVN_PROP_ENTRY_LOCK_TOKEN, NULL, 1571 iterpool)); 1572 } 1573 1574 svn_pool_destroy(iterpool); 1575 return SVN_NO_ERROR; 1576} 1577 1578 1579/* Conforms to svn_delta_path_driver_cb_func_t */ 1580static svn_error_t * 1581apply_change(void **dir_baton, 1582 void *parent_baton, 1583 void *callback_baton, 1584 const char *ev1_relpath, 1585 apr_pool_t *result_pool) 1586{ 1587 /* ### fix this? */ 1588 apr_pool_t *scratch_pool = result_pool; 1589 const struct editor_baton *eb = callback_baton; 1590 const struct change_node *change; 1591 const char *relpath; 1592 void *file_baton = NULL; 1593 1594 /* Typically, we are not creating new directory batons. */ 1595 *dir_baton = NULL; 1596 1597 relpath = svn_relpath_join(eb->base_relpath, ev1_relpath, scratch_pool); 1598 change = svn_hash_gets(eb->changes, relpath); 1599 1600 /* The callback should only be called for paths in CHANGES. */ 1601 SVN_ERR_ASSERT(change != NULL); 1602 1603 /* Are we editing the root of the tree? */ 1604 if (parent_baton == NULL) 1605 { 1606 /* The root was opened in start_edit_func() */ 1607 *dir_baton = eb->root.baton; 1608 1609 /* Only property edits are allowed on the root. */ 1610 SVN_ERR_ASSERT(change->action == RESTRUCTURE_NONE); 1611 SVN_ERR(drive_ev1_props(eb, relpath, change, *dir_baton, scratch_pool)); 1612 1613 /* No further action possible for the root. */ 1614 return SVN_NO_ERROR; 1615 } 1616 1617 if (change->action == RESTRUCTURE_DELETE) 1618 { 1619 SVN_ERR(eb->deditor->delete_entry(ev1_relpath, change->deleting, 1620 parent_baton, scratch_pool)); 1621 1622 /* No futher action possible for this node. */ 1623 return SVN_NO_ERROR; 1624 } 1625 1626 /* If we're not deleting this node, then we should know its kind. */ 1627 SVN_ERR_ASSERT(change->kind != svn_node_unknown); 1628 1629 if (change->action == RESTRUCTURE_ADD_ABSENT) 1630 { 1631 if (change->kind == svn_node_dir) 1632 SVN_ERR(eb->deditor->absent_directory(ev1_relpath, parent_baton, 1633 scratch_pool)); 1634 else 1635 SVN_ERR(eb->deditor->absent_file(ev1_relpath, parent_baton, 1636 scratch_pool)); 1637 1638 /* No further action possible for this node. */ 1639 return SVN_NO_ERROR; 1640 } 1641 /* RESTRUCTURE_NONE or RESTRUCTURE_ADD */ 1642 1643 if (change->action == RESTRUCTURE_ADD) 1644 { 1645 const char *copyfrom_url = NULL; 1646 svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM; 1647 1648 /* Do we have an old node to delete first? */ 1649 if (SVN_IS_VALID_REVNUM(change->deleting)) 1650 SVN_ERR(eb->deditor->delete_entry(ev1_relpath, change->deleting, 1651 parent_baton, scratch_pool)); 1652 1653 /* Are we copying the node from somewhere? */ 1654 if (change->copyfrom_path) 1655 { 1656 if (eb->repos_root) 1657 copyfrom_url = svn_path_url_add_component2(eb->repos_root, 1658 change->copyfrom_path, 1659 scratch_pool); 1660 else 1661 { 1662 copyfrom_url = change->copyfrom_path; 1663 1664 /* Make this an FS path by prepending "/" */ 1665 if (copyfrom_url[0] != '/') 1666 copyfrom_url = apr_pstrcat(scratch_pool, "/", 1667 copyfrom_url, SVN_VA_NULL); 1668 } 1669 1670 copyfrom_rev = change->copyfrom_rev; 1671 } 1672 1673 if (change->kind == svn_node_dir) 1674 SVN_ERR(eb->deditor->add_directory(ev1_relpath, parent_baton, 1675 copyfrom_url, copyfrom_rev, 1676 result_pool, dir_baton)); 1677 else 1678 SVN_ERR(eb->deditor->add_file(ev1_relpath, parent_baton, 1679 copyfrom_url, copyfrom_rev, 1680 result_pool, &file_baton)); 1681 } 1682 else 1683 { 1684 if (change->kind == svn_node_dir) 1685 SVN_ERR(eb->deditor->open_directory(ev1_relpath, parent_baton, 1686 change->changing, 1687 result_pool, dir_baton)); 1688 else 1689 SVN_ERR(eb->deditor->open_file(ev1_relpath, parent_baton, 1690 change->changing, 1691 result_pool, &file_baton)); 1692 } 1693 1694 /* Apply any properties in CHANGE to the node. */ 1695 if (change->kind == svn_node_dir) 1696 SVN_ERR(drive_ev1_props(eb, relpath, change, *dir_baton, scratch_pool)); 1697 else 1698 SVN_ERR(drive_ev1_props(eb, relpath, change, file_baton, scratch_pool)); 1699 1700 if (change->contents_changed && change->contents_abspath) 1701 { 1702 svn_txdelta_window_handler_t handler; 1703 void *handler_baton; 1704 svn_stream_t *contents; 1705 1706 /* ### would be nice to have a BASE_CHECKSUM, but hey: this is the 1707 ### shim code... */ 1708 SVN_ERR(eb->deditor->apply_textdelta(file_baton, NULL, scratch_pool, 1709 &handler, &handler_baton)); 1710 SVN_ERR(svn_stream_open_readonly(&contents, change->contents_abspath, 1711 scratch_pool, scratch_pool)); 1712 /* ### it would be nice to send a true txdelta here, but whatever. */ 1713 SVN_ERR(svn_txdelta_send_stream(contents, handler, handler_baton, 1714 NULL, scratch_pool)); 1715 SVN_ERR(svn_stream_close(contents)); 1716 } 1717 1718 if (file_baton) 1719 { 1720 const char *digest = svn_checksum_to_cstring(change->checksum, 1721 scratch_pool); 1722 1723 SVN_ERR(eb->deditor->close_file(file_baton, digest, scratch_pool)); 1724 } 1725 1726 return SVN_NO_ERROR; 1727} 1728 1729 1730static svn_error_t * 1731drive_changes(const struct editor_baton *eb, 1732 apr_pool_t *scratch_pool) 1733{ 1734 struct change_node *change; 1735 const apr_array_header_t *paths; 1736 1737 /* If we never opened a root baton, then the caller aborted the editor 1738 before it even began. There is nothing to do. Bail. */ 1739 if (eb->root.baton == NULL) 1740 return SVN_NO_ERROR; 1741 1742 /* We need to make the path driver believe we want to make changes to 1743 the root. Otherwise, it will attempt an open_root(), which we already 1744 did in start_edit_func(). We can forge up a change record, if one 1745 does not already exist. */ 1746 change = insert_change(eb->base_relpath, eb->changes); 1747 change->kind = svn_node_dir; 1748 /* No property changes (tho they might exist from a real change). */ 1749 1750 /* Get a sorted list of Ev1-relative paths. */ 1751 paths = get_sorted_paths(eb->changes, eb->base_relpath, scratch_pool); 1752 SVN_ERR(svn_delta_path_driver2(eb->deditor, eb->dedit_baton, paths, 1753 FALSE, apply_change, (void *)eb, 1754 scratch_pool)); 1755 1756 return SVN_NO_ERROR; 1757} 1758 1759 1760/* This implements svn_editor_cb_complete_t */ 1761static svn_error_t * 1762complete_cb(void *baton, 1763 apr_pool_t *scratch_pool) 1764{ 1765 struct editor_baton *eb = baton; 1766 svn_error_t *err; 1767 1768 /* Drive the tree we've created. */ 1769 err = drive_changes(eb, scratch_pool); 1770 if (!err) 1771 { 1772 err = svn_error_compose_create(err, eb->deditor->close_edit( 1773 eb->dedit_baton, 1774 scratch_pool)); 1775 } 1776 1777 if (err) 1778 svn_error_clear(eb->deditor->abort_edit(eb->dedit_baton, scratch_pool)); 1779 1780 return svn_error_trace(err); 1781} 1782 1783/* This implements svn_editor_cb_abort_t */ 1784static svn_error_t * 1785abort_cb(void *baton, 1786 apr_pool_t *scratch_pool) 1787{ 1788 struct editor_baton *eb = baton; 1789 svn_error_t *err; 1790 svn_error_t *err2; 1791 1792 /* We still need to drive anything we collected in the editor to this 1793 point. */ 1794 1795 /* Drive the tree we've created. */ 1796 err = drive_changes(eb, scratch_pool); 1797 1798 err2 = eb->deditor->abort_edit(eb->dedit_baton, scratch_pool); 1799 1800 if (err2) 1801 { 1802 if (err) 1803 svn_error_clear(err2); 1804 else 1805 err = err2; 1806 } 1807 1808 return svn_error_trace(err); 1809} 1810 1811static svn_error_t * 1812start_edit_func(void *baton, 1813 svn_revnum_t base_revision) 1814{ 1815 struct editor_baton *eb = baton; 1816 1817 eb->root.base_revision = base_revision; 1818 1819 /* For some Ev1 editors (such as the repos commit editor), the root must 1820 be open before can invoke any callbacks. The open_root() call sets up 1821 stuff (eg. open an FS txn) which will be needed. */ 1822 SVN_ERR(eb->deditor->open_root(eb->dedit_baton, eb->root.base_revision, 1823 eb->edit_pool, &eb->root.baton)); 1824 1825 return SVN_NO_ERROR; 1826} 1827 1828static svn_error_t * 1829target_revision_func(void *baton, 1830 svn_revnum_t target_revision, 1831 apr_pool_t *scratch_pool) 1832{ 1833 struct editor_baton *eb = baton; 1834 1835 SVN_ERR(eb->deditor->set_target_revision(eb->dedit_baton, target_revision, 1836 scratch_pool)); 1837 1838 return SVN_NO_ERROR; 1839} 1840 1841static svn_error_t * 1842do_unlock(void *baton, 1843 const char *path, 1844 apr_pool_t *scratch_pool) 1845{ 1846 struct editor_baton *eb = baton; 1847 1848 { 1849 /* PATH is REPOS_RELPATH */ 1850 struct change_node *change = insert_change(path, eb->changes); 1851 1852 /* We will need to propagate a deletion of SVN_PROP_ENTRY_LOCK_TOKEN */ 1853 change->unlock = TRUE; 1854 } 1855 1856 return SVN_NO_ERROR; 1857} 1858 1859/* Return an svn_editor_t * in EDITOR_P which will drive 1860 * DEDITOR/DEDIT_BATON. EDITOR_P is allocated in RESULT_POOL, which may 1861 * become large and long-lived; SCRATCH_POOL is used for temporary 1862 * allocations. 1863 * 1864 * The other parameters are as follows: 1865 * - EXB: An 'extra_baton' used for passing information between the coupled 1866 * shims. This includes actions like 'start edit' and 'set target'. 1867 * As this shim receives these actions, it provides the extra baton 1868 * to its caller. 1869 * - UNLOCK_FUNC / UNLOCK_BATON: A callback / baton pair which a caller 1870 * can use to notify this shim that a path should be unlocked (in the 1871 * 'svn lock' sense). As this shim receives this action, it provides 1872 * this callback / baton to its caller. 1873 * - SEND_ABS_PATHS: A pointer which will be set prior to this edit (but 1874 * not necessarily at the invocation of editor_from_delta()),and 1875 * which indicates whether incoming paths should be expected to 1876 * be absolute or relative. 1877 * - CANCEL_FUNC / CANCEL_BATON: The usual; folded into the produced editor. 1878 * - FETCH_KIND_FUNC / FETCH_KIND_BATON: A callback / baton pair which will 1879 * be used by the shim handlers if they need to determine the kind of 1880 * a path. 1881 * - FETCH_PROPS_FUNC / FETCH_PROPS_BATON: A callback / baton pair which 1882 * will be used by the shim handlers if they need to determine the 1883 * existing properties on a path. 1884 */ 1885svn_error_t * 1886svn_delta__editor_from_delta(svn_editor_t **editor_p, 1887 struct svn_delta__extra_baton **exb, 1888 svn_delta__unlock_func_t *unlock_func, 1889 void **unlock_baton, 1890 const svn_delta_editor_t *deditor, 1891 void *dedit_baton, 1892 svn_boolean_t *send_abs_paths, 1893 const char *repos_root, 1894 const char *base_relpath, 1895 svn_cancel_func_t cancel_func, 1896 void *cancel_baton, 1897 svn_delta_fetch_kind_func_t fetch_kind_func, 1898 void *fetch_kind_baton, 1899 svn_delta_fetch_props_func_t fetch_props_func, 1900 void *fetch_props_baton, 1901 apr_pool_t *result_pool, 1902 apr_pool_t *scratch_pool) 1903{ 1904 svn_editor_t *editor; 1905 static const svn_editor_cb_many_t editor_cbs = { 1906 add_directory_cb, 1907 add_file_cb, 1908 add_symlink_cb, 1909 add_absent_cb, 1910 alter_directory_cb, 1911 alter_file_cb, 1912 alter_symlink_cb, 1913 delete_cb, 1914 copy_cb, 1915 move_cb, 1916 complete_cb, 1917 abort_cb 1918 }; 1919 struct editor_baton *eb = apr_pcalloc(result_pool, sizeof(*eb)); 1920 struct svn_delta__extra_baton *extra_baton = apr_pcalloc(result_pool, 1921 sizeof(*extra_baton)); 1922 1923 if (!base_relpath) 1924 base_relpath = ""; 1925 else if (base_relpath[0] == '/') 1926 base_relpath += 1; 1927 1928 eb->deditor = deditor; 1929 eb->dedit_baton = dedit_baton; 1930 eb->edit_pool = result_pool; 1931 eb->repos_root = apr_pstrdup(result_pool, repos_root); 1932 eb->base_relpath = apr_pstrdup(result_pool, base_relpath); 1933 1934 eb->changes = apr_hash_make(result_pool); 1935 1936 eb->fetch_kind_func = fetch_kind_func; 1937 eb->fetch_kind_baton = fetch_kind_baton; 1938 eb->fetch_props_func = fetch_props_func; 1939 eb->fetch_props_baton = fetch_props_baton; 1940 1941 eb->root.base_revision = SVN_INVALID_REVNUM; 1942 1943 eb->make_abs_paths = send_abs_paths; 1944 1945 SVN_ERR(svn_editor_create(&editor, eb, cancel_func, cancel_baton, 1946 result_pool, scratch_pool)); 1947 SVN_ERR(svn_editor_setcb_many(editor, &editor_cbs, scratch_pool)); 1948 1949 *editor_p = editor; 1950 1951 *unlock_func = do_unlock; 1952 *unlock_baton = eb; 1953 1954 extra_baton->start_edit = start_edit_func; 1955 extra_baton->target_revision = target_revision_func; 1956 extra_baton->baton = eb; 1957 1958 *exb = extra_baton; 1959 1960 return SVN_NO_ERROR; 1961} 1962 1963svn_delta_shim_callbacks_t * 1964svn_delta_shim_callbacks_default(apr_pool_t *result_pool) 1965{ 1966 svn_delta_shim_callbacks_t *shim_callbacks = apr_pcalloc(result_pool, 1967 sizeof(*shim_callbacks)); 1968 return shim_callbacks; 1969} 1970 1971/* To enable editor shims throughout Subversion, ENABLE_EV2_SHIMS should be 1972 * defined. This can be done manually, or by providing `--enable-ev2-shims' 1973 * to `configure'. */ 1974 1975svn_error_t * 1976svn_editor__insert_shims(const svn_delta_editor_t **deditor_out, 1977 void **dedit_baton_out, 1978 const svn_delta_editor_t *deditor_in, 1979 void *dedit_baton_in, 1980 const char *repos_root, 1981 const char *base_relpath, 1982 svn_delta_shim_callbacks_t *shim_callbacks, 1983 apr_pool_t *result_pool, 1984 apr_pool_t *scratch_pool) 1985{ 1986#ifndef ENABLE_EV2_SHIMS 1987 /* Shims disabled, just copy the editor and baton directly. */ 1988 *deditor_out = deditor_in; 1989 *dedit_baton_out = dedit_baton_in; 1990#else 1991 /* Use our shim APIs to create an intermediate svn_editor_t, and then 1992 wrap that again back into a svn_delta_editor_t. This introduces 1993 a lot of overhead. */ 1994 svn_editor_t *editor; 1995 1996 /* The "extra baton" is a set of functions and a baton which allows the 1997 shims to communicate additional events to each other. 1998 svn_delta__editor_from_delta() returns a pointer to this baton, which 1999 svn_delta__delta_from_editor() should then store. */ 2000 struct svn_delta__extra_baton *exb; 2001 2002 /* The reason this is a pointer is that we don't know the appropriate 2003 value until we start receiving paths. So process_actions() sets the 2004 flag, which drive_tree() later consumes. */ 2005 svn_boolean_t *found_abs_paths = apr_palloc(result_pool, 2006 sizeof(*found_abs_paths)); 2007 2008 svn_delta__unlock_func_t unlock_func; 2009 void *unlock_baton; 2010 2011 SVN_ERR_ASSERT(shim_callbacks->fetch_kind_func != NULL); 2012 SVN_ERR_ASSERT(shim_callbacks->fetch_props_func != NULL); 2013 SVN_ERR_ASSERT(shim_callbacks->fetch_base_func != NULL); 2014 2015 SVN_ERR(svn_delta__editor_from_delta(&editor, &exb, 2016 &unlock_func, &unlock_baton, 2017 deditor_in, dedit_baton_in, 2018 found_abs_paths, repos_root, base_relpath, 2019 NULL, NULL, 2020 shim_callbacks->fetch_kind_func, 2021 shim_callbacks->fetch_baton, 2022 shim_callbacks->fetch_props_func, 2023 shim_callbacks->fetch_baton, 2024 result_pool, scratch_pool)); 2025 SVN_ERR(svn_delta__delta_from_editor(deditor_out, dedit_baton_out, editor, 2026 unlock_func, unlock_baton, 2027 found_abs_paths, 2028 repos_root, base_relpath, 2029 shim_callbacks->fetch_props_func, 2030 shim_callbacks->fetch_baton, 2031 shim_callbacks->fetch_base_func, 2032 shim_callbacks->fetch_baton, 2033 exb, result_pool)); 2034 2035#endif 2036 return SVN_NO_ERROR; 2037} 2038