externals.c revision 289166
1/* 2 * externals.c : routines dealing with (file) externals in the working copy 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 25 26#include <stdlib.h> 27#include <string.h> 28 29#include <apr_pools.h> 30#include <apr_hash.h> 31#include <apr_tables.h> 32#include <apr_general.h> 33#include <apr_uri.h> 34 35#include "svn_dirent_uri.h" 36#include "svn_path.h" 37#include "svn_error.h" 38#include "svn_hash.h" 39#include "svn_io.h" 40#include "svn_pools.h" 41#include "svn_props.h" 42#include "svn_string.h" 43#include "svn_time.h" 44#include "svn_types.h" 45#include "svn_wc.h" 46 47#include "private/svn_skel.h" 48#include "private/svn_subr_private.h" 49 50#include "wc.h" 51#include "adm_files.h" 52#include "props.h" 53#include "translate.h" 54#include "workqueue.h" 55#include "conflicts.h" 56 57#include "svn_private_config.h" 58 59/** Externals **/ 60 61/* 62 * Look for either 63 * 64 * -r N 65 * -rN 66 * 67 * in the LINE_PARTS array and update the revision field in ITEM with 68 * the revision if the revision is found. Set REV_IDX to the index in 69 * LINE_PARTS where the revision specification starts. Remove from 70 * LINE_PARTS the element(s) that specify the revision. 71 * PARENT_DIRECTORY_DISPLAY and LINE are given to return a nice error 72 * string. 73 * 74 * If this function returns successfully, then LINE_PARTS will have 75 * only two elements in it. 76 */ 77static svn_error_t * 78find_and_remove_externals_revision(int *rev_idx, 79 const char **line_parts, 80 int num_line_parts, 81 svn_wc_external_item2_t *item, 82 const char *parent_directory_display, 83 const char *line, 84 apr_pool_t *pool) 85{ 86 int i; 87 88 for (i = 0; i < 2; ++i) 89 { 90 const char *token = line_parts[i]; 91 92 if (token[0] == '-' && token[1] == 'r') 93 { 94 svn_opt_revision_t end_revision = { svn_opt_revision_unspecified }; 95 const char *digits_ptr; 96 int shift_count; 97 int j; 98 99 *rev_idx = i; 100 101 if (token[2] == '\0') 102 { 103 /* There must be a total of four elements in the line if 104 -r N is used. */ 105 if (num_line_parts != 4) 106 goto parse_error; 107 108 shift_count = 2; 109 digits_ptr = line_parts[i+1]; 110 } 111 else 112 { 113 /* There must be a total of three elements in the line 114 if -rN is used. */ 115 if (num_line_parts != 3) 116 goto parse_error; 117 118 shift_count = 1; 119 digits_ptr = token+2; 120 } 121 122 if (svn_opt_parse_revision(&item->revision, 123 &end_revision, 124 digits_ptr, pool) != 0) 125 goto parse_error; 126 /* We want a single revision, not a range. */ 127 if (end_revision.kind != svn_opt_revision_unspecified) 128 goto parse_error; 129 /* Allow only numbers and dates, not keywords. */ 130 if (item->revision.kind != svn_opt_revision_number 131 && item->revision.kind != svn_opt_revision_date) 132 goto parse_error; 133 134 /* Shift any line elements past the revision specification 135 down over the revision specification. */ 136 for (j = i; j < num_line_parts-shift_count; ++j) 137 line_parts[j] = line_parts[j+shift_count]; 138 line_parts[num_line_parts-shift_count] = NULL; 139 140 /* Found the revision, so leave the function immediately, do 141 * not continue looking for additional revisions. */ 142 return SVN_NO_ERROR; 143 } 144 } 145 146 /* No revision was found, so there must be exactly two items in the 147 line array. */ 148 if (num_line_parts == 2) 149 return SVN_NO_ERROR; 150 151 parse_error: 152 return svn_error_createf 153 (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL, 154 _("Error parsing %s property on '%s': '%s'"), 155 SVN_PROP_EXTERNALS, 156 parent_directory_display, 157 line); 158} 159 160svn_error_t * 161svn_wc_parse_externals_description3(apr_array_header_t **externals_p, 162 const char *parent_directory, 163 const char *desc, 164 svn_boolean_t canonicalize_url, 165 apr_pool_t *pool) 166{ 167 int i; 168 apr_array_header_t *externals = NULL; 169 apr_array_header_t *lines = svn_cstring_split(desc, "\n\r", TRUE, pool); 170 const char *parent_directory_display = svn_path_is_url(parent_directory) ? 171 parent_directory : svn_dirent_local_style(parent_directory, pool); 172 173 /* If an error occurs halfway through parsing, *externals_p should stay 174 * untouched. So, store the list in a local var first. */ 175 if (externals_p) 176 externals = apr_array_make(pool, 1, sizeof(svn_wc_external_item2_t *)); 177 178 for (i = 0; i < lines->nelts; i++) 179 { 180 const char *line = APR_ARRAY_IDX(lines, i, const char *); 181 apr_status_t status; 182 char **line_parts; 183 int num_line_parts; 184 svn_wc_external_item2_t *item; 185 const char *token0; 186 const char *token1; 187 svn_boolean_t token0_is_url; 188 svn_boolean_t token1_is_url; 189 190 /* Index into line_parts where the revision specification 191 started. */ 192 int rev_idx = -1; 193 194 if ((! line) || (line[0] == '#')) 195 continue; 196 197 /* else proceed */ 198 199 status = apr_tokenize_to_argv(line, &line_parts, pool); 200 if (status) 201 return svn_error_wrap_apr(status, 202 _("Can't split line into components: '%s'"), 203 line); 204 /* Count the number of tokens. */ 205 for (num_line_parts = 0; line_parts[num_line_parts]; num_line_parts++) 206 ; 207 208 SVN_ERR(svn_wc_external_item2_create(&item, pool)); 209 item->revision.kind = svn_opt_revision_unspecified; 210 item->peg_revision.kind = svn_opt_revision_unspecified; 211 212 /* 213 * There are six different formats of externals: 214 * 215 * 1) DIR URL 216 * 2) DIR -r N URL 217 * 3) DIR -rN URL 218 * 4) URL DIR 219 * 5) -r N URL DIR 220 * 6) -rN URL DIR 221 * 222 * The last three allow peg revisions in the URL. 223 * 224 * With relative URLs and no '-rN' or '-r N', there is no way to 225 * distinguish between 'DIR URL' and 'URL DIR' when URL is a 226 * relative URL like /svn/repos/trunk, so this case is taken as 227 * case 4). 228 */ 229 if (num_line_parts < 2 || num_line_parts > 4) 230 return svn_error_createf 231 (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL, 232 _("Error parsing %s property on '%s': '%s'"), 233 SVN_PROP_EXTERNALS, 234 parent_directory_display, 235 line); 236 237 /* To make it easy to check for the forms, find and remove -r N 238 or -rN from the line item array. If it is found, rev_idx 239 contains the index into line_parts where '-r' was found and 240 set item->revision to the parsed revision. */ 241 /* ### ugh. stupid cast. */ 242 SVN_ERR(find_and_remove_externals_revision(&rev_idx, 243 (const char **)line_parts, 244 num_line_parts, item, 245 parent_directory_display, 246 line, pool)); 247 248 token0 = line_parts[0]; 249 token1 = line_parts[1]; 250 251 token0_is_url = svn_path_is_url(token0); 252 token1_is_url = svn_path_is_url(token1); 253 254 if (token0_is_url && token1_is_url) 255 return svn_error_createf 256 (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL, 257 _("Invalid %s property on '%s': " 258 "cannot use two absolute URLs ('%s' and '%s') in an external; " 259 "one must be a path where an absolute or relative URL is " 260 "checked out to"), 261 SVN_PROP_EXTERNALS, parent_directory_display, token0, token1); 262 263 if (0 == rev_idx && token1_is_url) 264 return svn_error_createf 265 (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL, 266 _("Invalid %s property on '%s': " 267 "cannot use a URL '%s' as the target directory for an external " 268 "definition"), 269 SVN_PROP_EXTERNALS, parent_directory_display, token1); 270 271 if (1 == rev_idx && token0_is_url) 272 return svn_error_createf 273 (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL, 274 _("Invalid %s property on '%s': " 275 "cannot use a URL '%s' as the target directory for an external " 276 "definition"), 277 SVN_PROP_EXTERNALS, parent_directory_display, token0); 278 279 /* The appearence of -r N or -rN forces the type of external. 280 If -r is at the beginning of the line or the first token is 281 an absolute URL or if the second token is not an absolute 282 URL, then the URL supports peg revisions. */ 283 if (0 == rev_idx || 284 (-1 == rev_idx && (token0_is_url || ! token1_is_url))) 285 { 286 /* The URL is passed to svn_opt_parse_path in 287 uncanonicalized form so that the scheme relative URL 288 //hostname/foo is not collapsed to a server root relative 289 URL /hostname/foo. */ 290 SVN_ERR(svn_opt_parse_path(&item->peg_revision, &item->url, 291 token0, pool)); 292 item->target_dir = token1; 293 } 294 else 295 { 296 item->target_dir = token0; 297 item->url = token1; 298 item->peg_revision = item->revision; 299 } 300 301 SVN_ERR(svn_opt_resolve_revisions(&item->peg_revision, 302 &item->revision, TRUE, FALSE, 303 pool)); 304 305 item->target_dir = svn_dirent_internal_style(item->target_dir, pool); 306 307 if (item->target_dir[0] == '\0' 308 || svn_dirent_is_absolute(item->target_dir) 309 || svn_path_is_backpath_present(item->target_dir) 310 || !svn_dirent_skip_ancestor("dummy", 311 svn_dirent_join("dummy", 312 item->target_dir, 313 pool))) 314 return svn_error_createf 315 (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL, 316 _("Invalid %s property on '%s': " 317 "target '%s' is an absolute path or involves '..'"), 318 SVN_PROP_EXTERNALS, 319 parent_directory_display, 320 item->target_dir); 321 322 if (canonicalize_url) 323 { 324 /* Uh... this is stupid. But it's consistent with what our 325 code did before we split up the relpath/dirent/uri APIs. 326 Still, given this, it's no wonder that our own libraries 327 don't ask this function to canonicalize the results. */ 328 if (svn_path_is_url(item->url)) 329 item->url = svn_uri_canonicalize(item->url, pool); 330 else 331 item->url = svn_dirent_canonicalize(item->url, pool); 332 } 333 334 if (externals) 335 APR_ARRAY_PUSH(externals, svn_wc_external_item2_t *) = item; 336 } 337 338 if (externals_p) 339 *externals_p = externals; 340 341 return SVN_NO_ERROR; 342} 343 344svn_error_t * 345svn_wc__externals_find_target_dups(apr_array_header_t **duplicate_targets, 346 apr_array_header_t *externals, 347 apr_pool_t *pool, 348 apr_pool_t *scratch_pool) 349{ 350 int i; 351 unsigned int len; 352 unsigned int len2; 353 const char *target; 354 apr_hash_t *targets = apr_hash_make(scratch_pool); 355 apr_hash_t *targets2 = NULL; 356 *duplicate_targets = NULL; 357 358 for (i = 0; i < externals->nelts; i++) 359 { 360 target = APR_ARRAY_IDX(externals, i, 361 svn_wc_external_item2_t*)->target_dir; 362 len = apr_hash_count(targets); 363 svn_hash_sets(targets, target, ""); 364 if (len == apr_hash_count(targets)) 365 { 366 /* Hashtable length is unchanged. This must be a duplicate. */ 367 368 /* Collapse multiple duplicates of the same target by using a second 369 * hash layer. */ 370 if (! targets2) 371 targets2 = apr_hash_make(scratch_pool); 372 len2 = apr_hash_count(targets2); 373 svn_hash_sets(targets2, target, ""); 374 if (len2 < apr_hash_count(targets2)) 375 { 376 /* The second hash list just got bigger, i.e. this target has 377 * not been counted as duplicate before. */ 378 if (! *duplicate_targets) 379 { 380 *duplicate_targets = apr_array_make( 381 pool, 1, sizeof(svn_wc_external_item2_t*)); 382 } 383 APR_ARRAY_PUSH((*duplicate_targets), const char *) = target; 384 } 385 /* Else, this same target has already been recorded as a duplicate, 386 * don't count it again. */ 387 } 388 } 389 return SVN_NO_ERROR; 390} 391 392struct edit_baton 393{ 394 apr_pool_t *pool; 395 svn_wc__db_t *db; 396 397 /* We explicitly use wri_abspath and local_abspath here, because we 398 might want to install file externals in an obstructing working copy */ 399 const char *wri_abspath; /* The working defining the file external */ 400 const char *local_abspath; /* The file external itself */ 401 const char *name; /* The basename of the file external itself */ 402 403 /* Information from the caller */ 404 svn_boolean_t use_commit_times; 405 const apr_array_header_t *ext_patterns; 406 const char *diff3cmd; 407 408 const char *repos_root_url; 409 const char *repos_uuid; 410 const char *old_repos_relpath; 411 const char *new_repos_relpath; 412 413 const char *record_ancestor_abspath; 414 const char *recorded_repos_relpath; 415 svn_revnum_t recorded_peg_revision; 416 svn_revnum_t recorded_revision; 417 418 /* Introducing a new file external */ 419 svn_boolean_t added; 420 421 svn_wc_conflict_resolver_func2_t conflict_func; 422 void *conflict_baton; 423 svn_cancel_func_t cancel_func; 424 void *cancel_baton; 425 svn_wc_notify_func2_t notify_func; 426 void *notify_baton; 427 428 svn_revnum_t *target_revision; 429 430 /* What was there before the update */ 431 svn_revnum_t original_revision; 432 const svn_checksum_t *original_checksum; 433 434 /* What we are installing now */ 435 const char *new_pristine_abspath; 436 svn_checksum_t *new_sha1_checksum; 437 svn_checksum_t *new_md5_checksum; 438 439 /* List of incoming propchanges */ 440 apr_array_header_t *propchanges; 441 442 /* Array of svn_prop_inherited_item_t * structures representing the 443 properties inherited by the base node at LOCAL_ABSPATH. */ 444 apr_array_header_t *iprops; 445 446 /* The last change information */ 447 svn_revnum_t changed_rev; 448 apr_time_t changed_date; 449 const char *changed_author; 450 451 svn_boolean_t had_props; 452 453 svn_boolean_t file_closed; 454}; 455 456/* svn_delta_editor_t function for svn_wc__get_file_external_editor */ 457static svn_error_t * 458set_target_revision(void *edit_baton, 459 svn_revnum_t target_revision, 460 apr_pool_t *pool) 461{ 462 struct edit_baton *eb = edit_baton; 463 464 *eb->target_revision = target_revision; 465 466 return SVN_NO_ERROR; 467} 468 469/* svn_delta_editor_t function for svn_wc__get_file_external_editor */ 470static svn_error_t * 471open_root(void *edit_baton, 472 svn_revnum_t base_revision, 473 apr_pool_t *dir_pool, 474 void **root_baton) 475{ 476 *root_baton = edit_baton; 477 return SVN_NO_ERROR; 478} 479 480/* svn_delta_editor_t function for svn_wc__get_file_external_editor */ 481static svn_error_t * 482add_file(const char *path, 483 void *parent_baton, 484 const char *copyfrom_path, 485 svn_revnum_t copyfrom_revision, 486 apr_pool_t *file_pool, 487 void **file_baton) 488{ 489 struct edit_baton *eb = parent_baton; 490 if (strcmp(path, eb->name)) 491 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, 492 _("This editor can only update '%s'"), 493 svn_dirent_local_style(eb->local_abspath, 494 file_pool)); 495 496 *file_baton = eb; 497 eb->original_revision = SVN_INVALID_REVNUM; 498 eb->added = TRUE; 499 500 return SVN_NO_ERROR; 501} 502 503/* svn_delta_editor_t function for svn_wc__get_file_external_editor */ 504static svn_error_t * 505open_file(const char *path, 506 void *parent_baton, 507 svn_revnum_t base_revision, 508 apr_pool_t *file_pool, 509 void **file_baton) 510{ 511 struct edit_baton *eb = parent_baton; 512 svn_node_kind_t kind; 513 if (strcmp(path, eb->name)) 514 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, 515 _("This editor can only update '%s'"), 516 svn_dirent_local_style(eb->local_abspath, 517 file_pool)); 518 519 *file_baton = eb; 520 SVN_ERR(svn_wc__db_base_get_info(NULL, &kind, &eb->original_revision, 521 &eb->old_repos_relpath, NULL, NULL, 522 &eb->changed_rev, 523 &eb->changed_date, &eb->changed_author, 524 NULL, &eb->original_checksum, NULL, NULL, 525 &eb->had_props, NULL, NULL, 526 eb->db, eb->local_abspath, 527 eb->pool, file_pool)); 528 529 if (kind != svn_node_file) 530 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, 531 _("Node '%s' is no existing file external"), 532 svn_dirent_local_style(eb->local_abspath, 533 file_pool)); 534 return SVN_NO_ERROR; 535} 536 537/* svn_delta_editor_t function for svn_wc__get_file_external_editor */ 538static svn_error_t * 539apply_textdelta(void *file_baton, 540 const char *base_checksum_digest, 541 apr_pool_t *pool, 542 svn_txdelta_window_handler_t *handler, 543 void **handler_baton) 544{ 545 struct edit_baton *eb = file_baton; 546 svn_stream_t *src_stream; 547 svn_stream_t *dest_stream; 548 549 if (eb->original_checksum) 550 { 551 if (base_checksum_digest) 552 { 553 svn_checksum_t *expected_checksum; 554 const svn_checksum_t *original_md5; 555 556 SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5, 557 base_checksum_digest, pool)); 558 559 if (eb->original_checksum->kind != svn_checksum_md5) 560 SVN_ERR(svn_wc__db_pristine_get_md5(&original_md5, 561 eb->db, eb->wri_abspath, 562 eb->original_checksum, 563 pool, pool)); 564 else 565 original_md5 = eb->original_checksum; 566 567 if (!svn_checksum_match(expected_checksum, original_md5)) 568 return svn_error_trace(svn_checksum_mismatch_err( 569 expected_checksum, 570 original_md5, 571 pool, 572 _("Base checksum mismatch for '%s'"), 573 svn_dirent_local_style(eb->local_abspath, 574 pool))); 575 } 576 577 SVN_ERR(svn_wc__db_pristine_read(&src_stream, NULL, eb->db, 578 eb->wri_abspath, eb->original_checksum, 579 pool, pool)); 580 } 581 else 582 src_stream = svn_stream_empty(pool); 583 584 SVN_ERR(svn_wc__open_writable_base(&dest_stream, &eb->new_pristine_abspath, 585 &eb->new_md5_checksum, 586 &eb->new_sha1_checksum, 587 eb->db, eb->wri_abspath, 588 eb->pool, pool)); 589 590 svn_txdelta_apply(src_stream, dest_stream, NULL, eb->local_abspath, pool, 591 handler, handler_baton); 592 593 return SVN_NO_ERROR; 594} 595 596/* svn_delta_editor_t function for svn_wc__get_file_external_editor */ 597static svn_error_t * 598change_file_prop(void *file_baton, 599 const char *name, 600 const svn_string_t *value, 601 apr_pool_t *pool) 602{ 603 struct edit_baton *eb = file_baton; 604 svn_prop_t *propchange; 605 606 propchange = apr_array_push(eb->propchanges); 607 propchange->name = apr_pstrdup(eb->pool, name); 608 propchange->value = value ? svn_string_dup(value, eb->pool) : NULL; 609 610 return SVN_NO_ERROR; 611} 612 613/* svn_delta_editor_t function for svn_wc__get_file_external_editor */ 614static svn_error_t * 615close_file(void *file_baton, 616 const char *expected_md5_digest, 617 apr_pool_t *pool) 618{ 619 struct edit_baton *eb = file_baton; 620 svn_wc_notify_state_t prop_state = svn_wc_notify_state_unknown; 621 svn_wc_notify_state_t content_state = svn_wc_notify_state_unknown; 622 svn_boolean_t obstructed = FALSE; 623 624 eb->file_closed = TRUE; /* We bump the revision here */ 625 626 /* Check the checksum, if provided */ 627 if (expected_md5_digest) 628 { 629 svn_checksum_t *expected_md5_checksum; 630 const svn_checksum_t *actual_md5_checksum = eb->new_md5_checksum; 631 632 SVN_ERR(svn_checksum_parse_hex(&expected_md5_checksum, svn_checksum_md5, 633 expected_md5_digest, pool)); 634 635 if (actual_md5_checksum == NULL) 636 { 637 actual_md5_checksum = eb->original_checksum; 638 639 if (actual_md5_checksum != NULL 640 && actual_md5_checksum->kind != svn_checksum_md5) 641 { 642 SVN_ERR(svn_wc__db_pristine_get_md5(&actual_md5_checksum, 643 eb->db, eb->wri_abspath, 644 actual_md5_checksum, 645 pool, pool)); 646 } 647 } 648 649 if (! svn_checksum_match(expected_md5_checksum, actual_md5_checksum)) 650 return svn_checksum_mismatch_err( 651 expected_md5_checksum, 652 actual_md5_checksum, pool, 653 _("Checksum mismatch for '%s'"), 654 svn_dirent_local_style(eb->local_abspath, pool)); 655 } 656 657 /* First move the file in the pristine store; this hands over the cleanup 658 behavior to the pristine store. */ 659 if (eb->new_sha1_checksum) 660 { 661 SVN_ERR(svn_wc__db_pristine_install(eb->db, eb->new_pristine_abspath, 662 eb->new_sha1_checksum, 663 eb->new_md5_checksum, pool)); 664 665 eb->new_pristine_abspath = NULL; 666 } 667 668 /* Merge the changes */ 669 { 670 svn_skel_t *all_work_items = NULL; 671 svn_skel_t *conflict_skel = NULL; 672 svn_skel_t *work_item; 673 apr_hash_t *base_props = NULL; 674 apr_hash_t *actual_props = NULL; 675 apr_hash_t *new_pristine_props = NULL; 676 apr_hash_t *new_actual_props = NULL; 677 apr_hash_t *new_dav_props = NULL; 678 const svn_checksum_t *new_checksum = NULL; 679 const svn_checksum_t *original_checksum = NULL; 680 681 svn_boolean_t added = !SVN_IS_VALID_REVNUM(eb->original_revision); 682 683 if (! added) 684 { 685 new_checksum = eb->original_checksum; 686 687 if (eb->had_props) 688 SVN_ERR(svn_wc__db_base_get_props( 689 &base_props, eb->db, eb->local_abspath, pool, pool)); 690 691 SVN_ERR(svn_wc__db_read_props( 692 &actual_props, eb->db, eb->local_abspath, pool, pool)); 693 } 694 695 if (!base_props) 696 base_props = apr_hash_make(pool); 697 698 if (!actual_props) 699 actual_props = apr_hash_make(pool); 700 701 if (eb->new_sha1_checksum) 702 new_checksum = eb->new_sha1_checksum; 703 704 /* Merge the properties */ 705 { 706 apr_array_header_t *entry_prop_changes; 707 apr_array_header_t *dav_prop_changes; 708 apr_array_header_t *regular_prop_changes; 709 int i; 710 711 SVN_ERR(svn_categorize_props(eb->propchanges, &entry_prop_changes, 712 &dav_prop_changes, ®ular_prop_changes, 713 pool)); 714 715 /* Read the entry-prop changes to update the last-changed info. */ 716 for (i = 0; i < entry_prop_changes->nelts; i++) 717 { 718 const svn_prop_t *prop = &APR_ARRAY_IDX(entry_prop_changes, i, 719 svn_prop_t); 720 721 if (! prop->value) 722 continue; /* authz or something */ 723 724 if (! strcmp(prop->name, SVN_PROP_ENTRY_LAST_AUTHOR)) 725 eb->changed_author = apr_pstrdup(pool, prop->value->data); 726 else if (! strcmp(prop->name, SVN_PROP_ENTRY_COMMITTED_REV)) 727 { 728 apr_int64_t rev; 729 SVN_ERR(svn_cstring_atoi64(&rev, prop->value->data)); 730 eb->changed_rev = (svn_revnum_t)rev; 731 } 732 else if (! strcmp(prop->name, SVN_PROP_ENTRY_COMMITTED_DATE)) 733 SVN_ERR(svn_time_from_cstring(&eb->changed_date, prop->value->data, 734 pool)); 735 } 736 737 /* Store the DAV-prop (aka WC-prop) changes. (This treats a list 738 * of changes as a list of new props, but we only use this when 739 * adding a new file and it's equivalent in that case.) */ 740 if (dav_prop_changes->nelts > 0) 741 new_dav_props = svn_prop_array_to_hash(dav_prop_changes, pool); 742 743 /* Merge the regular prop changes. */ 744 if (regular_prop_changes->nelts > 0) 745 { 746 new_pristine_props = svn_prop__patch(base_props, regular_prop_changes, 747 pool); 748 SVN_ERR(svn_wc__merge_props(&conflict_skel, 749 &prop_state, 750 &new_actual_props, 751 eb->db, eb->local_abspath, 752 NULL /* server_baseprops*/, 753 base_props, 754 actual_props, 755 regular_prop_changes, 756 pool, pool)); 757 } 758 else 759 { 760 new_pristine_props = base_props; 761 new_actual_props = actual_props; 762 } 763 } 764 765 /* Merge the text */ 766 if (eb->new_sha1_checksum) 767 { 768 svn_node_kind_t disk_kind; 769 svn_boolean_t install_pristine = FALSE; 770 const char *install_from = NULL; 771 772 SVN_ERR(svn_io_check_path(eb->local_abspath, &disk_kind, pool)); 773 774 if (disk_kind == svn_node_none) 775 { 776 /* Just install the file */ 777 install_pristine = TRUE; 778 content_state = svn_wc_notify_state_changed; 779 } 780 else if (disk_kind != svn_node_file 781 || (eb->added && disk_kind == svn_node_file)) 782 { 783 /* The node is obstructed; we just change the DB */ 784 obstructed = TRUE; 785 content_state = svn_wc_notify_state_unchanged; 786 } 787 else 788 { 789 svn_boolean_t is_mod; 790 SVN_ERR(svn_wc__internal_file_modified_p(&is_mod, 791 eb->db, eb->local_abspath, 792 FALSE, pool)); 793 794 if (!is_mod) 795 { 796 install_pristine = TRUE; 797 content_state = svn_wc_notify_state_changed; 798 } 799 else 800 { 801 svn_boolean_t found_text_conflict; 802 803 /* Ok, we have to do some work to merge a local change */ 804 SVN_ERR(svn_wc__perform_file_merge(&work_item, 805 &conflict_skel, 806 &found_text_conflict, 807 eb->db, 808 eb->local_abspath, 809 eb->wri_abspath, 810 new_checksum, 811 original_checksum, 812 actual_props, 813 eb->ext_patterns, 814 eb->original_revision, 815 *eb->target_revision, 816 eb->propchanges, 817 eb->diff3cmd, 818 eb->cancel_func, 819 eb->cancel_baton, 820 pool, pool)); 821 822 all_work_items = svn_wc__wq_merge(all_work_items, work_item, 823 pool); 824 825 if (found_text_conflict) 826 content_state = svn_wc_notify_state_conflicted; 827 else 828 content_state = svn_wc_notify_state_merged; 829 } 830 } 831 if (install_pristine) 832 { 833 SVN_ERR(svn_wc__wq_build_file_install(&work_item, eb->db, 834 eb->local_abspath, 835 install_from, 836 eb->use_commit_times, TRUE, 837 pool, pool)); 838 839 all_work_items = svn_wc__wq_merge(all_work_items, work_item, pool); 840 } 841 } 842 else 843 { 844 content_state = svn_wc_notify_state_unchanged; 845 /* ### Retranslate on magic property changes, etc. */ 846 } 847 848 /* Generate a conflict description, if needed */ 849 if (conflict_skel) 850 { 851 SVN_ERR(svn_wc__conflict_skel_set_op_switch( 852 conflict_skel, 853 svn_wc_conflict_version_create2( 854 eb->repos_root_url, 855 eb->repos_uuid, 856 eb->old_repos_relpath, 857 eb->original_revision, 858 svn_node_file, 859 pool), 860 svn_wc_conflict_version_create2( 861 eb->repos_root_url, 862 eb->repos_uuid, 863 eb->new_repos_relpath, 864 *eb->target_revision, 865 svn_node_file, 866 pool), 867 pool, pool)); 868 SVN_ERR(svn_wc__conflict_create_markers(&work_item, 869 eb->db, eb->local_abspath, 870 conflict_skel, 871 pool, pool)); 872 all_work_items = svn_wc__wq_merge(all_work_items, work_item, 873 pool); 874 } 875 876 /* Install the file in the DB */ 877 SVN_ERR(svn_wc__db_external_add_file( 878 eb->db, 879 eb->local_abspath, 880 eb->wri_abspath, 881 eb->new_repos_relpath, 882 eb->repos_root_url, 883 eb->repos_uuid, 884 *eb->target_revision, 885 new_pristine_props, 886 eb->iprops, 887 eb->changed_rev, 888 eb->changed_date, 889 eb->changed_author, 890 new_checksum, 891 new_dav_props, 892 eb->record_ancestor_abspath, 893 eb->recorded_repos_relpath, 894 eb->recorded_peg_revision, 895 eb->recorded_revision, 896 TRUE, new_actual_props, 897 FALSE /* keep_recorded_info */, 898 conflict_skel, 899 all_work_items, 900 pool)); 901 902 /* close_edit may also update iprops for switched files, catching 903 those for which close_file is never called (e.g. an update of a 904 file external with no changes). So as a minor optimization we 905 clear the iprops so as not to set them again in close_edit. */ 906 eb->iprops = NULL; 907 908 /* Run the work queue to complete the installation */ 909 SVN_ERR(svn_wc__wq_run(eb->db, eb->wri_abspath, 910 eb->cancel_func, eb->cancel_baton, pool)); 911 } 912 913 /* Notify */ 914 if (eb->notify_func) 915 { 916 svn_wc_notify_action_t action; 917 svn_wc_notify_t *notify; 918 919 if (!eb->added) 920 action = obstructed ? svn_wc_notify_update_shadowed_update 921 : svn_wc_notify_update_update; 922 else 923 action = obstructed ? svn_wc_notify_update_shadowed_add 924 : svn_wc_notify_update_add; 925 926 notify = svn_wc_create_notify(eb->local_abspath, action, pool); 927 notify->kind = svn_node_file; 928 929 notify->revision = *eb->target_revision; 930 notify->prop_state = prop_state; 931 notify->content_state = content_state; 932 933 notify->old_revision = eb->original_revision; 934 935 eb->notify_func(eb->notify_baton, notify, pool); 936 } 937 938 return SVN_NO_ERROR; 939} 940 941/* svn_delta_editor_t function for svn_wc__get_file_external_editor */ 942static svn_error_t * 943close_edit(void *edit_baton, 944 apr_pool_t *pool) 945{ 946 struct edit_baton *eb = edit_baton; 947 948 if (!eb->file_closed) 949 { 950 apr_hash_t *wcroot_iprops = NULL; 951 /* The file wasn't updated, but its url or revision might have... 952 e.g. switch between branches for relative externals. 953 954 Just bump the information as that is just as expensive as 955 investigating when we should and shouldn't update it... 956 and avoid hard to debug edge cases */ 957 958 if (eb->iprops) 959 { 960 wcroot_iprops = apr_hash_make(pool); 961 svn_hash_sets(wcroot_iprops, eb->local_abspath, eb->iprops); 962 } 963 964 SVN_ERR(svn_wc__db_op_bump_revisions_post_update(eb->db, 965 eb->local_abspath, 966 svn_depth_infinity, 967 eb->new_repos_relpath, 968 eb->repos_root_url, 969 eb->repos_uuid, 970 *eb->target_revision, 971 apr_hash_make(pool) 972 /* exclude_relpaths */, 973 wcroot_iprops, 974 eb->notify_func, 975 eb->notify_baton, 976 pool)); 977 } 978 979 return SVN_NO_ERROR; 980} 981 982svn_error_t * 983svn_wc__get_file_external_editor(const svn_delta_editor_t **editor, 984 void **edit_baton, 985 svn_revnum_t *target_revision, 986 svn_wc_context_t *wc_ctx, 987 const char *local_abspath, 988 const char *wri_abspath, 989 const char *url, 990 const char *repos_root_url, 991 const char *repos_uuid, 992 apr_array_header_t *iprops, 993 svn_boolean_t use_commit_times, 994 const char *diff3_cmd, 995 const apr_array_header_t *preserved_exts, 996 const char *record_ancestor_abspath, 997 const char *recorded_url, 998 const svn_opt_revision_t *recorded_peg_rev, 999 const svn_opt_revision_t *recorded_rev, 1000 svn_wc_conflict_resolver_func2_t conflict_func, 1001 void *conflict_baton, 1002 svn_cancel_func_t cancel_func, 1003 void *cancel_baton, 1004 svn_wc_notify_func2_t notify_func, 1005 void *notify_baton, 1006 apr_pool_t *result_pool, 1007 apr_pool_t *scratch_pool) 1008{ 1009 svn_wc__db_t *db = wc_ctx->db; 1010 apr_pool_t *edit_pool = result_pool; 1011 struct edit_baton *eb = apr_pcalloc(edit_pool, sizeof(*eb)); 1012 svn_delta_editor_t *tree_editor = svn_delta_default_editor(edit_pool); 1013 1014 eb->pool = edit_pool; 1015 eb->db = db; 1016 eb->local_abspath = apr_pstrdup(edit_pool, local_abspath); 1017 if (wri_abspath) 1018 eb->wri_abspath = apr_pstrdup(edit_pool, wri_abspath); 1019 else 1020 eb->wri_abspath = svn_dirent_dirname(local_abspath, edit_pool); 1021 eb->name = svn_dirent_basename(eb->local_abspath, NULL); 1022 eb->target_revision = target_revision; 1023 1024 eb->repos_root_url = apr_pstrdup(edit_pool, repos_root_url); 1025 eb->repos_uuid = apr_pstrdup(edit_pool, repos_uuid); 1026 eb->new_repos_relpath = svn_uri_skip_ancestor(eb->repos_root_url, url, edit_pool); 1027 eb->old_repos_relpath = eb->new_repos_relpath; 1028 1029 eb->original_revision = SVN_INVALID_REVNUM; 1030 1031 eb->iprops = iprops; 1032 1033 eb->use_commit_times = use_commit_times; 1034 eb->ext_patterns = preserved_exts; 1035 eb->diff3cmd = diff3_cmd; 1036 1037 eb->record_ancestor_abspath = apr_pstrdup(edit_pool,record_ancestor_abspath); 1038 eb->recorded_repos_relpath = svn_uri_skip_ancestor(repos_root_url, recorded_url, 1039 edit_pool); 1040 1041 eb->changed_rev = SVN_INVALID_REVNUM; 1042 1043 if (recorded_peg_rev->kind == svn_opt_revision_number) 1044 eb->recorded_peg_revision = recorded_peg_rev->value.number; 1045 else 1046 eb->recorded_peg_revision = SVN_INVALID_REVNUM; /* Not fixed/HEAD */ 1047 1048 if (recorded_rev->kind == svn_opt_revision_number) 1049 eb->recorded_revision = recorded_rev->value.number; 1050 else 1051 eb->recorded_revision = SVN_INVALID_REVNUM; /* Not fixed/HEAD */ 1052 1053 eb->conflict_func = conflict_func; 1054 eb->conflict_baton = conflict_baton; 1055 eb->cancel_func = cancel_func; 1056 eb->cancel_baton = cancel_baton; 1057 eb->notify_func = notify_func; 1058 eb->notify_baton = notify_baton; 1059 1060 eb->propchanges = apr_array_make(edit_pool, 1, sizeof(svn_prop_t)); 1061 1062 tree_editor->open_root = open_root; 1063 tree_editor->set_target_revision = set_target_revision; 1064 tree_editor->add_file = add_file; 1065 tree_editor->open_file = open_file; 1066 tree_editor->apply_textdelta = apply_textdelta; 1067 tree_editor->change_file_prop = change_file_prop; 1068 tree_editor->close_file = close_file; 1069 tree_editor->close_edit = close_edit; 1070 1071 return svn_delta_get_cancellation_editor(cancel_func, cancel_baton, 1072 tree_editor, eb, 1073 editor, edit_baton, 1074 result_pool); 1075} 1076 1077svn_error_t * 1078svn_wc__crawl_file_external(svn_wc_context_t *wc_ctx, 1079 const char *local_abspath, 1080 const svn_ra_reporter3_t *reporter, 1081 void *report_baton, 1082 svn_boolean_t restore_files, 1083 svn_boolean_t use_commit_times, 1084 svn_cancel_func_t cancel_func, 1085 void *cancel_baton, 1086 svn_wc_notify_func2_t notify_func, 1087 void *notify_baton, 1088 apr_pool_t *scratch_pool) 1089{ 1090 svn_wc__db_t *db = wc_ctx->db; 1091 svn_error_t *err; 1092 svn_node_kind_t kind; 1093 svn_wc__db_lock_t *lock; 1094 svn_revnum_t revision; 1095 const char *repos_root_url; 1096 const char *repos_relpath; 1097 svn_boolean_t update_root; 1098 1099 err = svn_wc__db_base_get_info(NULL, &kind, &revision, 1100 &repos_relpath, &repos_root_url, NULL, NULL, 1101 NULL, NULL, NULL, NULL, NULL, &lock, 1102 NULL, NULL, &update_root, 1103 db, local_abspath, 1104 scratch_pool, scratch_pool); 1105 1106 if (err 1107 || kind == svn_node_dir 1108 || !update_root) 1109 { 1110 if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) 1111 return svn_error_trace(err); 1112 1113 svn_error_clear(err); 1114 1115 /* We don't know about this node, so all we have to do is tell 1116 the reporter that we don't know this node. 1117 1118 But first we have to start the report by sending some basic 1119 information for the root. */ 1120 1121 SVN_ERR(reporter->set_path(report_baton, "", 0, svn_depth_infinity, 1122 FALSE, NULL, scratch_pool)); 1123 SVN_ERR(reporter->delete_path(report_baton, "", scratch_pool)); 1124 1125 /* Finish the report, which causes the update editor to be 1126 driven. */ 1127 SVN_ERR(reporter->finish_report(report_baton, scratch_pool)); 1128 1129 return SVN_NO_ERROR; 1130 } 1131 else 1132 { 1133 if (restore_files) 1134 { 1135 svn_node_kind_t disk_kind; 1136 SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool)); 1137 1138 if (disk_kind == svn_node_none) 1139 { 1140 err = svn_wc_restore(wc_ctx, local_abspath, use_commit_times, 1141 scratch_pool); 1142 1143 if (err) 1144 { 1145 if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS) 1146 return svn_error_trace(err); 1147 1148 svn_error_clear(err); 1149 } 1150 } 1151 } 1152 1153 /* Report that we know the path */ 1154 SVN_ERR(reporter->set_path(report_baton, "", revision, 1155 svn_depth_infinity, FALSE, NULL, 1156 scratch_pool)); 1157 1158 /* For compatibility with the normal update editor report we report 1159 the target as switched. 1160 1161 ### We can probably report a parent url and unswitched later */ 1162 SVN_ERR(reporter->link_path(report_baton, "", 1163 svn_path_url_add_component2(repos_root_url, 1164 repos_relpath, 1165 scratch_pool), 1166 revision, 1167 svn_depth_infinity, 1168 FALSE /* start_empty*/, 1169 lock ? lock->token : NULL, 1170 scratch_pool)); 1171 } 1172 1173 return svn_error_trace(reporter->finish_report(report_baton, scratch_pool)); 1174} 1175 1176svn_error_t * 1177svn_wc__read_external_info(svn_node_kind_t *external_kind, 1178 const char **defining_abspath, 1179 const char **defining_url, 1180 svn_revnum_t *defining_operational_revision, 1181 svn_revnum_t *defining_revision, 1182 svn_wc_context_t *wc_ctx, 1183 const char *wri_abspath, 1184 const char *local_abspath, 1185 svn_boolean_t ignore_enoent, 1186 apr_pool_t *result_pool, 1187 apr_pool_t *scratch_pool) 1188{ 1189 const char *repos_root_url; 1190 svn_wc__db_status_t status; 1191 svn_node_kind_t kind; 1192 svn_error_t *err; 1193 1194 err = svn_wc__db_external_read(&status, &kind, defining_abspath, 1195 defining_url ? &repos_root_url : NULL, NULL, 1196 defining_url, defining_operational_revision, 1197 defining_revision, 1198 wc_ctx->db, local_abspath, wri_abspath, 1199 result_pool, scratch_pool); 1200 1201 if (err) 1202 { 1203 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND || !ignore_enoent) 1204 return svn_error_trace(err); 1205 1206 svn_error_clear(err); 1207 1208 if (external_kind) 1209 *external_kind = svn_node_none; 1210 1211 if (defining_abspath) 1212 *defining_abspath = NULL; 1213 1214 if (defining_url) 1215 *defining_url = NULL; 1216 1217 if (defining_operational_revision) 1218 *defining_operational_revision = SVN_INVALID_REVNUM; 1219 1220 if (defining_revision) 1221 *defining_revision = SVN_INVALID_REVNUM; 1222 1223 return SVN_NO_ERROR; 1224 } 1225 1226 if (external_kind) 1227 { 1228 if (status != svn_wc__db_status_normal) 1229 *external_kind = svn_node_unknown; 1230 else 1231 switch(kind) 1232 { 1233 case svn_node_file: 1234 case svn_node_symlink: 1235 *external_kind = svn_node_file; 1236 break; 1237 case svn_node_dir: 1238 *external_kind = svn_node_dir; 1239 break; 1240 default: 1241 *external_kind = svn_node_none; 1242 } 1243 } 1244 1245 if (defining_url && *defining_url) 1246 *defining_url = svn_path_url_add_component2(repos_root_url, *defining_url, 1247 result_pool); 1248 1249 return SVN_NO_ERROR; 1250} 1251 1252/* Return TRUE in *IS_ROLLED_OUT iff a node exists at XINFO->LOCAL_ABSPATH and 1253 * if that node's origin corresponds with XINFO->REPOS_ROOT_URL and 1254 * XINFO->REPOS_RELPATH. All allocations are made in SCRATCH_POOL. */ 1255static svn_error_t * 1256is_external_rolled_out(svn_boolean_t *is_rolled_out, 1257 svn_wc_context_t *wc_ctx, 1258 svn_wc__committable_external_info_t *xinfo, 1259 apr_pool_t *scratch_pool) 1260{ 1261 const char *repos_relpath; 1262 const char *repos_root_url; 1263 svn_error_t *err; 1264 1265 *is_rolled_out = FALSE; 1266 1267 err = svn_wc__db_base_get_info(NULL, NULL, NULL, &repos_relpath, 1268 &repos_root_url, NULL, NULL, NULL, NULL, 1269 NULL, NULL, NULL, NULL, NULL, NULL, NULL, 1270 wc_ctx->db, xinfo->local_abspath, 1271 scratch_pool, scratch_pool); 1272 1273 if (err) 1274 { 1275 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) 1276 { 1277 svn_error_clear(err); 1278 return SVN_NO_ERROR; 1279 } 1280 SVN_ERR(err); 1281 } 1282 1283 *is_rolled_out = (strcmp(xinfo->repos_root_url, repos_root_url) == 0 && 1284 strcmp(xinfo->repos_relpath, repos_relpath) == 0); 1285 return SVN_NO_ERROR; 1286} 1287 1288svn_error_t * 1289svn_wc__committable_externals_below(apr_array_header_t **externals, 1290 svn_wc_context_t *wc_ctx, 1291 const char *local_abspath, 1292 svn_depth_t depth, 1293 apr_pool_t *result_pool, 1294 apr_pool_t *scratch_pool) 1295{ 1296 apr_array_header_t *orig_externals; 1297 int i; 1298 apr_pool_t *iterpool; 1299 1300 /* For svn_depth_files, this also fetches dirs. They are filtered later. */ 1301 SVN_ERR(svn_wc__db_committable_externals_below(&orig_externals, 1302 wc_ctx->db, 1303 local_abspath, 1304 depth != svn_depth_infinity, 1305 result_pool, scratch_pool)); 1306 1307 if (orig_externals == NULL) 1308 return SVN_NO_ERROR; 1309 1310 iterpool = svn_pool_create(scratch_pool); 1311 1312 for (i = 0; i < orig_externals->nelts; i++) 1313 { 1314 svn_boolean_t is_rolled_out; 1315 1316 svn_wc__committable_external_info_t *xinfo = 1317 APR_ARRAY_IDX(orig_externals, i, 1318 svn_wc__committable_external_info_t *); 1319 1320 /* Discard dirs for svn_depth_files (s.a.). */ 1321 if (depth == svn_depth_files 1322 && xinfo->kind == svn_node_dir) 1323 continue; 1324 1325 svn_pool_clear(iterpool); 1326 1327 /* Discard those externals that are not currently checked out. */ 1328 SVN_ERR(is_external_rolled_out(&is_rolled_out, wc_ctx, xinfo, 1329 iterpool)); 1330 if (! is_rolled_out) 1331 continue; 1332 1333 if (*externals == NULL) 1334 *externals = apr_array_make( 1335 result_pool, 0, 1336 sizeof(svn_wc__committable_external_info_t *)); 1337 1338 APR_ARRAY_PUSH(*externals, 1339 svn_wc__committable_external_info_t *) = xinfo; 1340 1341 if (depth != svn_depth_infinity) 1342 continue; 1343 1344 /* Are there any nested externals? */ 1345 SVN_ERR(svn_wc__committable_externals_below(externals, wc_ctx, 1346 xinfo->local_abspath, 1347 svn_depth_infinity, 1348 result_pool, iterpool)); 1349 } 1350 1351 return SVN_NO_ERROR; 1352} 1353 1354svn_error_t * 1355svn_wc__externals_defined_below(apr_hash_t **externals, 1356 svn_wc_context_t *wc_ctx, 1357 const char *local_abspath, 1358 apr_pool_t *result_pool, 1359 apr_pool_t *scratch_pool) 1360{ 1361 return svn_error_trace( 1362 svn_wc__db_externals_defined_below(externals, 1363 wc_ctx->db, local_abspath, 1364 result_pool, scratch_pool)); 1365} 1366 1367svn_error_t * 1368svn_wc__external_register(svn_wc_context_t *wc_ctx, 1369 const char *defining_abspath, 1370 const char *local_abspath, 1371 svn_node_kind_t kind, 1372 const char *repos_root_url, 1373 const char *repos_uuid, 1374 const char *repos_relpath, 1375 svn_revnum_t operational_revision, 1376 svn_revnum_t revision, 1377 apr_pool_t *scratch_pool) 1378{ 1379 SVN_ERR_ASSERT(kind == svn_node_dir); 1380 return svn_error_trace( 1381 svn_wc__db_external_add_dir(wc_ctx->db, local_abspath, 1382 defining_abspath, 1383 repos_root_url, 1384 repos_uuid, 1385 defining_abspath, 1386 repos_relpath, 1387 operational_revision, 1388 revision, 1389 NULL, 1390 scratch_pool)); 1391} 1392 1393svn_error_t * 1394svn_wc__external_remove(svn_wc_context_t *wc_ctx, 1395 const char *wri_abspath, 1396 const char *local_abspath, 1397 svn_boolean_t declaration_only, 1398 svn_cancel_func_t cancel_func, 1399 void *cancel_baton, 1400 apr_pool_t *scratch_pool) 1401{ 1402 svn_wc__db_status_t status; 1403 svn_node_kind_t kind; 1404 1405 SVN_ERR(svn_wc__db_external_read(&status, &kind, NULL, NULL, NULL, NULL, 1406 NULL, NULL, 1407 wc_ctx->db, local_abspath, wri_abspath, 1408 scratch_pool, scratch_pool)); 1409 1410 SVN_ERR(svn_wc__db_external_remove(wc_ctx->db, local_abspath, wri_abspath, 1411 NULL, scratch_pool)); 1412 1413 if (declaration_only) 1414 return SVN_NO_ERROR; 1415 1416 if (kind == svn_node_dir) 1417 SVN_ERR(svn_wc_remove_from_revision_control2(wc_ctx, local_abspath, 1418 TRUE, TRUE, 1419 cancel_func, cancel_baton, 1420 scratch_pool)); 1421 else 1422 { 1423 SVN_ERR(svn_wc__db_base_remove(wc_ctx->db, local_abspath, 1424 FALSE /* keep_as_working */, 1425 TRUE /* queue_deletes */, 1426 FALSE /* remove_locks */, 1427 SVN_INVALID_REVNUM, 1428 NULL, NULL, scratch_pool)); 1429 SVN_ERR(svn_wc__wq_run(wc_ctx->db, local_abspath, 1430 cancel_func, cancel_baton, 1431 scratch_pool)); 1432 } 1433 1434 return SVN_NO_ERROR; 1435} 1436 1437svn_error_t * 1438svn_wc__externals_gather_definitions(apr_hash_t **externals, 1439 apr_hash_t **depths, 1440 svn_wc_context_t *wc_ctx, 1441 const char *local_abspath, 1442 svn_depth_t depth, 1443 apr_pool_t *result_pool, 1444 apr_pool_t *scratch_pool) 1445{ 1446 if (depth == svn_depth_infinity 1447 || depth == svn_depth_unknown) 1448 { 1449 return svn_error_trace( 1450 svn_wc__db_externals_gather_definitions(externals, depths, 1451 wc_ctx->db, local_abspath, 1452 result_pool, scratch_pool)); 1453 } 1454 else 1455 { 1456 const svn_string_t *value; 1457 svn_error_t *err; 1458 *externals = apr_hash_make(result_pool); 1459 1460 local_abspath = apr_pstrdup(result_pool, local_abspath); 1461 1462 err = svn_wc_prop_get2(&value, wc_ctx, local_abspath, 1463 SVN_PROP_EXTERNALS, result_pool, scratch_pool); 1464 1465 if (err) 1466 { 1467 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) 1468 return svn_error_trace(err); 1469 1470 svn_error_clear(err); 1471 value = NULL; 1472 } 1473 1474 if (value) 1475 svn_hash_sets(*externals, local_abspath, value->data); 1476 1477 if (value && depths) 1478 { 1479 svn_depth_t node_depth; 1480 *depths = apr_hash_make(result_pool); 1481 1482 SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL, 1483 NULL, NULL, NULL, &node_depth, NULL, 1484 NULL, NULL, NULL, NULL, NULL, NULL, 1485 NULL, NULL, NULL, NULL, NULL, NULL, 1486 NULL, NULL, NULL, NULL, 1487 wc_ctx->db, local_abspath, 1488 scratch_pool, scratch_pool)); 1489 1490 svn_hash_sets(*depths, local_abspath, svn_depth_to_word(node_depth)); 1491 } 1492 1493 return SVN_NO_ERROR; 1494 } 1495} 1496 1497svn_error_t * 1498svn_wc__close_db(const char *external_abspath, 1499 svn_wc_context_t *wc_ctx, 1500 apr_pool_t *scratch_pool) 1501{ 1502 SVN_ERR(svn_wc__db_drop_root(wc_ctx->db, external_abspath, 1503 scratch_pool)); 1504 return SVN_NO_ERROR; 1505} 1506 1507/* Return the scheme of @a uri in @a scheme allocated from @a pool. 1508 If @a uri does not appear to be a valid URI, then @a scheme will 1509 not be updated. */ 1510static svn_error_t * 1511uri_scheme(const char **scheme, const char *uri, apr_pool_t *pool) 1512{ 1513 apr_size_t i; 1514 1515 for (i = 0; uri[i] && uri[i] != ':'; ++i) 1516 if (uri[i] == '/') 1517 goto error; 1518 1519 if (i > 0 && uri[i] == ':' && uri[i+1] == '/' && uri[i+2] == '/') 1520 { 1521 *scheme = apr_pstrmemdup(pool, uri, i); 1522 return SVN_NO_ERROR; 1523 } 1524 1525error: 1526 return svn_error_createf(SVN_ERR_BAD_URL, 0, 1527 _("URL '%s' does not begin with a scheme"), 1528 uri); 1529} 1530 1531svn_error_t * 1532svn_wc__resolve_relative_external_url(const char **resolved_url, 1533 const svn_wc_external_item2_t *item, 1534 const char *repos_root_url, 1535 const char *parent_dir_url, 1536 apr_pool_t *result_pool, 1537 apr_pool_t *scratch_pool) 1538{ 1539 const char *url = item->url; 1540 apr_uri_t parent_dir_uri; 1541 apr_status_t status; 1542 1543 *resolved_url = item->url; 1544 1545 /* If the URL is already absolute, there is nothing to do. */ 1546 if (svn_path_is_url(url)) 1547 { 1548 /* "http://server/path" */ 1549 *resolved_url = svn_uri_canonicalize(url, result_pool); 1550 return SVN_NO_ERROR; 1551 } 1552 1553 if (url[0] == '/') 1554 { 1555 /* "/path", "//path", and "///path" */ 1556 int num_leading_slashes = 1; 1557 if (url[1] == '/') 1558 { 1559 num_leading_slashes++; 1560 if (url[2] == '/') 1561 num_leading_slashes++; 1562 } 1563 1564 /* "//schema-relative" and in some cases "///schema-relative". 1565 This last format is supported on file:// schema relative. */ 1566 url = apr_pstrcat(scratch_pool, 1567 apr_pstrndup(scratch_pool, url, num_leading_slashes), 1568 svn_relpath_canonicalize(url + num_leading_slashes, 1569 scratch_pool), 1570 (char*)NULL); 1571 } 1572 else 1573 { 1574 /* "^/path" and "../path" */ 1575 url = svn_relpath_canonicalize(url, scratch_pool); 1576 } 1577 1578 /* Parse the parent directory URL into its parts. */ 1579 status = apr_uri_parse(scratch_pool, parent_dir_url, &parent_dir_uri); 1580 if (status) 1581 return svn_error_createf(SVN_ERR_BAD_URL, 0, 1582 _("Illegal parent directory URL '%s'"), 1583 parent_dir_url); 1584 1585 /* If the parent directory URL is at the server root, then the URL 1586 may have no / after the hostname so apr_uri_parse() will leave 1587 the URL's path as NULL. */ 1588 if (! parent_dir_uri.path) 1589 parent_dir_uri.path = apr_pstrmemdup(scratch_pool, "/", 1); 1590 parent_dir_uri.query = NULL; 1591 parent_dir_uri.fragment = NULL; 1592 1593 /* Handle URLs relative to the current directory or to the 1594 repository root. The backpaths may only remove path elements, 1595 not the hostname. This allows an external to refer to another 1596 repository in the same server relative to the location of this 1597 repository, say using SVNParentPath. */ 1598 if ((0 == strncmp("../", url, 3)) || 1599 (0 == strncmp("^/", url, 2))) 1600 { 1601 apr_array_header_t *base_components; 1602 apr_array_header_t *relative_components; 1603 int i; 1604 1605 /* Decompose either the parent directory's URL path or the 1606 repository root's URL path into components. */ 1607 if (0 == strncmp("../", url, 3)) 1608 { 1609 base_components = svn_path_decompose(parent_dir_uri.path, 1610 scratch_pool); 1611 relative_components = svn_path_decompose(url, scratch_pool); 1612 } 1613 else 1614 { 1615 apr_uri_t repos_root_uri; 1616 1617 status = apr_uri_parse(scratch_pool, repos_root_url, 1618 &repos_root_uri); 1619 if (status) 1620 return svn_error_createf(SVN_ERR_BAD_URL, 0, 1621 _("Illegal repository root URL '%s'"), 1622 repos_root_url); 1623 1624 /* If the repository root URL is at the server root, then 1625 the URL may have no / after the hostname so 1626 apr_uri_parse() will leave the URL's path as NULL. */ 1627 if (! repos_root_uri.path) 1628 repos_root_uri.path = apr_pstrmemdup(scratch_pool, "/", 1); 1629 1630 base_components = svn_path_decompose(repos_root_uri.path, 1631 scratch_pool); 1632 relative_components = svn_path_decompose(url + 2, scratch_pool); 1633 } 1634 1635 for (i = 0; i < relative_components->nelts; ++i) 1636 { 1637 const char *component = APR_ARRAY_IDX(relative_components, 1638 i, 1639 const char *); 1640 if (0 == strcmp("..", component)) 1641 { 1642 /* Constructing the final absolute URL together with 1643 apr_uri_unparse() requires that the path be absolute, 1644 so only pop a component if the component being popped 1645 is not the component for the root directory. */ 1646 if (base_components->nelts > 1) 1647 apr_array_pop(base_components); 1648 } 1649 else 1650 APR_ARRAY_PUSH(base_components, const char *) = component; 1651 } 1652 1653 parent_dir_uri.path = (char *)svn_path_compose(base_components, 1654 scratch_pool); 1655 *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool, 1656 &parent_dir_uri, 0), 1657 result_pool); 1658 return SVN_NO_ERROR; 1659 } 1660 1661 /* The remaining URLs are relative to either the scheme or server root 1662 and can only refer to locations inside that scope, so backpaths are 1663 not allowed. */ 1664 if (svn_path_is_backpath_present(url)) 1665 return svn_error_createf(SVN_ERR_BAD_URL, 0, 1666 _("The external relative URL '%s' cannot have " 1667 "backpaths, i.e. '..'"), 1668 item->url); 1669 1670 /* Relative to the scheme: Build a new URL from the parts we know. */ 1671 if (0 == strncmp("//", url, 2)) 1672 { 1673 const char *scheme; 1674 1675 SVN_ERR(uri_scheme(&scheme, repos_root_url, scratch_pool)); 1676 *resolved_url = svn_uri_canonicalize(apr_pstrcat(scratch_pool, scheme, 1677 ":", url, (char *)NULL), 1678 result_pool); 1679 return SVN_NO_ERROR; 1680 } 1681 1682 /* Relative to the server root: Just replace the path portion of the 1683 parent's URL. */ 1684 if (url[0] == '/') 1685 { 1686 parent_dir_uri.path = (char *)url; 1687 *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool, 1688 &parent_dir_uri, 0), 1689 result_pool); 1690 return SVN_NO_ERROR; 1691 } 1692 1693 return svn_error_createf(SVN_ERR_BAD_URL, 0, 1694 _("Unrecognized format for the relative external " 1695 "URL '%s'"), 1696 item->url); 1697} 1698