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