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