1/* 2 * patch.c: patch application support 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 27 28/*** Includes. ***/ 29 30#include <apr_hash.h> 31#include <apr_fnmatch.h> 32#include "svn_client.h" 33#include "svn_dirent_uri.h" 34#include "svn_diff.h" 35#include "svn_hash.h" 36#include "svn_io.h" 37#include "svn_path.h" 38#include "svn_pools.h" 39#include "svn_props.h" 40#include "svn_sorts.h" 41#include "svn_subst.h" 42#include "svn_wc.h" 43#include "client.h" 44 45#include "svn_private_config.h" 46#include "private/svn_eol_private.h" 47#include "private/svn_wc_private.h" 48#include "private/svn_dep_compat.h" 49#include "private/svn_diff_private.h" 50#include "private/svn_string_private.h" 51#include "private/svn_subr_private.h" 52#include "private/svn_sorts_private.h" 53 54typedef struct hunk_info_t { 55 /* The hunk. */ 56 svn_diff_hunk_t *hunk; 57 58 /* The line where the hunk matched in the target file. */ 59 svn_linenum_t matched_line; 60 61 /* Whether this hunk has been rejected. */ 62 svn_boolean_t rejected; 63 64 /* Whether this hunk has already been applied (either manually 65 * or by an earlier run of patch). */ 66 svn_boolean_t already_applied; 67 68 /* The fuzz factor used when matching this hunk, i.e. how many 69 * lines of leading and trailing context to ignore during matching. */ 70 svn_linenum_t match_fuzz; 71 72 /* match_fuzz + the penalty caused by bad patch files */ 73 svn_linenum_t report_fuzz; 74} hunk_info_t; 75 76/* A struct carrying information related to the patched and unpatched 77 * content of a target, be it a property or the text of a file. */ 78typedef struct target_content_t { 79 /* Indicates whether unpatched content existed prior to patching. */ 80 svn_boolean_t existed; 81 82 /* The line last read from the unpatched content. */ 83 svn_linenum_t current_line; 84 85 /* The EOL-style of the unpatched content. Either 'none', 'fixed', 86 * or 'native'. See the documentation of svn_subst_eol_style_t. */ 87 svn_subst_eol_style_t eol_style; 88 89 /* If the EOL_STYLE above is not 'none', this is the EOL string 90 * corresponding to the EOL-style. Else, it is the EOL string the 91 * last line read from the target file was using. */ 92 const char *eol_str; 93 94 /* An array containing apr_off_t offsets marking the beginning of 95 * each line in the unpatched content. */ 96 apr_array_header_t *lines; 97 98 /* An array containing hunk_info_t structures for hunks already matched. */ 99 apr_array_header_t *hunks; 100 101 /* True if end-of-file was reached while reading from the unpatched 102 * content. */ 103 svn_boolean_t eof; 104 105 /* The keywords of the target. They will be contracted when reading 106 * unpatched content and expanded when writing patched content. 107 * When patching properties this hash is always empty. */ 108 apr_hash_t *keywords; 109 110 /* A callback, with an associated baton, to read a line of unpatched 111 * content. */ 112 svn_error_t *(*readline)(void *baton, svn_stringbuf_t **line, 113 const char **eol_str, svn_boolean_t *eof, 114 apr_pool_t *result_pool, apr_pool_t *scratch_pool); 115 void *read_baton; 116 117 /* A callback to get the current byte offset within the unpatched 118 * content. Uses the read baton. */ 119 svn_error_t * (*tell)(void *baton, apr_off_t *offset, 120 apr_pool_t *scratch_pool); 121 122 /* A callback to seek to an offset within the unpatched content. 123 * Uses the read baton. */ 124 svn_error_t * (*seek)(void *baton, apr_off_t offset, 125 apr_pool_t *scratch_pool); 126 127 /* A callback to write data to the patched content, with an 128 * associated baton. */ 129 svn_error_t * (*write)(void *baton, const char *buf, apr_size_t len, 130 apr_pool_t *scratch_pool); 131 void *write_baton; 132 133} target_content_t; 134 135typedef struct prop_patch_target_t { 136 137 /* The name of the property */ 138 const char *name; 139 140 /* The property value. This is NULL in case the property did not exist 141 * prior to patch application (see also CONTENT->existed). 142 * Note that the patch implementation does not support binary properties, 143 * so this string is not expected to contain embedded NUL characters. */ 144 const svn_string_t *value; 145 146 /* The patched property value. 147 * This is equivalent to the target, except that in appropriate 148 * places it contains the modified text as it appears in the patch file. */ 149 svn_stringbuf_t *patched_value; 150 151 /* All information that is specific to the content of the property. */ 152 target_content_t *content; 153 154 /* Represents the operation performed on the property. It can be added, 155 * deleted or modified. 156 * ### Should we use flags instead since we're not using all enum values? */ 157 svn_diff_operation_kind_t operation; 158 159 /* When true the property change won't be applied */ 160 svn_boolean_t skipped; 161 162 /* ### Here we'll add flags telling if the prop was added, deleted, 163 * ### had_rejects, had_local_mods prior to patching and so on. */ 164} prop_patch_target_t; 165 166typedef struct patch_target_t { 167 /* The target path as it appeared in the patch file, 168 * but in canonicalised form. */ 169 const char *canon_path_from_patchfile; 170 171 /* The target path, relative to the working copy directory the 172 * patch is being applied to. A patch strip count applies to this 173 * and only this path. This is never NULL. */ 174 const char *local_relpath; 175 176 /* The absolute path of the target on the filesystem. 177 * Any symlinks the path from the patch file may contain are resolved. 178 * Is not always known, so it may be NULL. */ 179 const char *local_abspath; 180 181 /* The target file, read-only. This is NULL in case the target 182 * file did not exist prior to patch application (see also 183 * CONTENT->existed). */ 184 apr_file_t *file; 185 186 /* The target file is a symlink */ 187 svn_boolean_t is_symlink; 188 189 /* The patched file. 190 * This is equivalent to the target, except that in appropriate 191 * places it contains the modified text as it appears in the patch file. 192 * The data in this file is written in repository-normal form. 193 * EOL transformation and keyword contraction is performed when the 194 * patched result is installed in the working copy. */ 195 apr_file_t *patched_file; 196 197 /* Path to the patched file. */ 198 const char *patched_path; 199 200 /* Hunks that are rejected will be written to this stream. */ 201 svn_stream_t *reject_stream; 202 203 /* Path to the reject file. */ 204 const char *reject_path; 205 206 /* The node kind of the target as found in WC-DB prior 207 * to patch application. */ 208 svn_node_kind_t db_kind; 209 210 /* The target's kind on disk prior to patch application. */ 211 svn_node_kind_t kind_on_disk; 212 213 /* True if the target was locally deleted prior to patching. */ 214 svn_boolean_t locally_deleted; 215 216 /* True if the target had to be skipped for some reason. */ 217 svn_boolean_t skipped; 218 219 /* True if the reason for skipping is a local obstruction */ 220 svn_boolean_t obstructed; 221 222 /* True if at least one hunk was rejected. */ 223 svn_boolean_t had_rejects; 224 225 /* True if at least one property hunk was rejected. */ 226 svn_boolean_t had_prop_rejects; 227 228 /* True if at least one hunk was handled as already applied */ 229 svn_boolean_t had_already_applied; 230 231 /* True if at least one property hunk was handled as already applied */ 232 svn_boolean_t had_prop_already_applied; 233 234 /* The operation on the target as set in the patch file */ 235 svn_diff_operation_kind_t operation; 236 237 /* True if the target was added by the patch, which means that it did 238 * not exist on disk before patching and has content after patching. */ 239 svn_boolean_t added; 240 241 /* True if the target ended up being deleted by the patch. */ 242 svn_boolean_t deleted; 243 244 /* Set if the target is supposed to be moved by the patch. 245 * This applies to --git diffs which carry "rename from/to" headers. */ 246 const char *move_target_abspath; 247 248 /* True if the target has the executable bit set. */ 249 svn_boolean_t executable; 250 251 /* True if the patch changed the text of the target. */ 252 svn_boolean_t has_text_changes; 253 254 /* True if the patch changed any of the properties of the target. */ 255 svn_boolean_t has_prop_changes; 256 257 /* True if the patch contained a svn:special property. */ 258 svn_boolean_t is_special; 259 260 /* All the information that is specific to the content of the target. */ 261 target_content_t *content; 262 263 /* A hash table of prop_patch_target_t objects keyed by property names. */ 264 apr_hash_t *prop_targets; 265 266 /* When TRUE, this patch uses the raw git symlink format instead of the 267 Subversion internal style format where links start with 'link '. */ 268 svn_boolean_t git_symlink_format; 269 270} patch_target_t; 271 272 273/* A smaller struct containing a subset of patch_target_t. 274 * Carries the minimal amount of information we still need for a 275 * target after we're done patching it so we can free other resources. */ 276typedef struct patch_target_info_t { 277 const char *local_abspath; 278 svn_boolean_t deleted; 279 svn_boolean_t added; 280} patch_target_info_t; 281 282/* Check if LOCAL_ABSPATH is recorded as added in TARGETS_INFO */ 283static svn_boolean_t 284target_is_added(const apr_array_header_t *targets_info, 285 const char *local_abspath, 286 apr_pool_t *scratch_pool) 287{ 288 int i; 289 290 for (i = targets_info->nelts - 1; i >= 0; i--) 291 { 292 const patch_target_info_t *target_info = 293 APR_ARRAY_IDX(targets_info, i, const patch_target_info_t *); 294 295 const char *info = svn_dirent_skip_ancestor(target_info->local_abspath, 296 local_abspath); 297 298 if (info && !*info) 299 return target_info->added; 300 else if (info) 301 return FALSE; 302 } 303 304 return FALSE; 305} 306 307/* Check if LOCAL_ABSPATH or an ancestor is recorded as deleted in 308 TARGETS_INFO */ 309static svn_boolean_t 310target_is_deleted(const apr_array_header_t *targets_info, 311 const char *local_abspath, 312 apr_pool_t *scratch_pool) 313{ 314 int i; 315 316 for (i = targets_info->nelts - 1; i >= 0; i--) 317 { 318 const patch_target_info_t *target_info = 319 APR_ARRAY_IDX(targets_info, i, const patch_target_info_t *); 320 321 const char *info = svn_dirent_skip_ancestor(target_info->local_abspath, 322 local_abspath); 323 324 if (info) 325 return target_info->deleted; 326 } 327 328 return FALSE; 329} 330 331 332/* Strip STRIP_COUNT components from the front of PATH, returning 333 * the result in *RESULT, allocated in RESULT_POOL. 334 * Do temporary allocations in SCRATCH_POOL. */ 335static svn_error_t * 336strip_path(const char **result, const char *path, int strip_count, 337 apr_pool_t *result_pool, apr_pool_t *scratch_pool) 338{ 339 int i; 340 apr_array_header_t *components; 341 apr_array_header_t *stripped; 342 343 components = svn_path_decompose(path, scratch_pool); 344 if (strip_count > components->nelts) 345 return svn_error_createf(SVN_ERR_CLIENT_PATCH_BAD_STRIP_COUNT, NULL, 346 Q_("Cannot strip %u component from '%s'", 347 "Cannot strip %u components from '%s'", 348 strip_count), 349 strip_count, 350 svn_dirent_local_style(path, scratch_pool)); 351 352 stripped = apr_array_make(scratch_pool, components->nelts - strip_count, 353 sizeof(const char *)); 354 for (i = strip_count; i < components->nelts; i++) 355 { 356 const char *component; 357 358 component = APR_ARRAY_IDX(components, i, const char *); 359 APR_ARRAY_PUSH(stripped, const char *) = component; 360 } 361 362 *result = svn_path_compose(stripped, result_pool); 363 364 return SVN_NO_ERROR; 365} 366 367/* Obtain KEYWORDS, EOL_STYLE and EOL_STR for LOCAL_ABSPATH. 368 * WC_CTX is a context for the working copy the patch is applied to. 369 * Use RESULT_POOL for allocations of fields in TARGET. 370 * Use SCRATCH_POOL for all other allocations. */ 371static svn_error_t * 372obtain_eol_and_keywords_for_file(apr_hash_t **keywords, 373 svn_subst_eol_style_t *eol_style, 374 const char **eol_str, 375 svn_wc_context_t *wc_ctx, 376 const char *local_abspath, 377 apr_pool_t *result_pool, 378 apr_pool_t *scratch_pool) 379{ 380 apr_hash_t *props; 381 svn_string_t *keywords_val, *eol_style_val; 382 383 SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, local_abspath, 384 scratch_pool, scratch_pool)); 385 keywords_val = svn_hash_gets(props, SVN_PROP_KEYWORDS); 386 if (keywords_val) 387 { 388 svn_revnum_t changed_rev; 389 apr_time_t changed_date; 390 const char *rev_str; 391 const char *author; 392 const char *url; 393 const char *repos_root_url; 394 const char *repos_relpath; 395 396 SVN_ERR(svn_wc__node_get_changed_info(&changed_rev, 397 &changed_date, 398 &author, wc_ctx, 399 local_abspath, 400 scratch_pool, 401 scratch_pool)); 402 rev_str = apr_psprintf(scratch_pool, "%ld", changed_rev); 403 SVN_ERR(svn_wc__node_get_repos_info(NULL, &repos_relpath, &repos_root_url, 404 NULL, 405 wc_ctx, local_abspath, 406 scratch_pool, scratch_pool)); 407 url = svn_path_url_add_component2(repos_root_url, repos_relpath, 408 scratch_pool); 409 410 SVN_ERR(svn_subst_build_keywords3(keywords, 411 keywords_val->data, 412 rev_str, url, repos_root_url, 413 changed_date, 414 author, result_pool)); 415 } 416 417 eol_style_val = svn_hash_gets(props, SVN_PROP_EOL_STYLE); 418 if (eol_style_val) 419 { 420 svn_subst_eol_style_from_value(eol_style, 421 eol_str, 422 eol_style_val->data); 423 } 424 425 return SVN_NO_ERROR; 426} 427 428/* Resolve the exact path for a patch TARGET at path PATH_FROM_PATCHFILE, 429 * which is the path of the target as it appeared in the patch file. 430 * Put a canonicalized version of PATH_FROM_PATCHFILE into 431 * TARGET->CANON_PATH_FROM_PATCHFILE. 432 * WC_CTX is a context for the working copy the patch is applied to. 433 * If possible, determine TARGET->WC_PATH, TARGET->ABS_PATH, TARGET->KIND, 434 * TARGET->ADDED, and TARGET->PARENT_DIR_EXISTS. 435 * Indicate in TARGET->SKIPPED whether the target should be skipped. 436 * STRIP_COUNT specifies the number of leading path components 437 * which should be stripped from target paths in the patch. 438 * HAS_TEXT_CHANGES specifies whether the target path will have some text 439 * changes applied, implying that the target should be a file and not a 440 * directory. 441 * Use RESULT_POOL for allocations of fields in TARGET. 442 * Use SCRATCH_POOL for all other allocations. */ 443static svn_error_t * 444resolve_target_path(patch_target_t *target, 445 const char *path_from_patchfile, 446 const char *root_abspath, 447 int strip_count, 448 svn_boolean_t has_text_changes, 449 svn_boolean_t follow_moves, 450 svn_wc_context_t *wc_ctx, 451 const apr_array_header_t *targets_info, 452 apr_pool_t *result_pool, 453 apr_pool_t *scratch_pool) 454{ 455 const char *stripped_path; 456 svn_wc_status3_t *status; 457 svn_error_t *err; 458 svn_boolean_t under_root; 459 460 target->canon_path_from_patchfile = svn_dirent_internal_style( 461 path_from_patchfile, result_pool); 462 463 /* We can't handle text changes on the patch root dir. */ 464 if (has_text_changes && target->canon_path_from_patchfile[0] == '\0') 465 { 466 /* An empty patch target path? What gives? Skip this. */ 467 target->skipped = TRUE; 468 target->local_abspath = NULL; 469 target->local_relpath = ""; 470 return SVN_NO_ERROR; 471 } 472 473 if (strip_count > 0) 474 SVN_ERR(strip_path(&stripped_path, target->canon_path_from_patchfile, 475 strip_count, result_pool, scratch_pool)); 476 else 477 stripped_path = target->canon_path_from_patchfile; 478 479 if (svn_dirent_is_absolute(stripped_path)) 480 { 481 target->local_relpath = svn_dirent_is_child(root_abspath, 482 stripped_path, 483 result_pool); 484 485 if (! target->local_relpath) 486 { 487 /* The target path is either outside of the working copy 488 * or it is the patch root itself. Skip it. */ 489 target->skipped = TRUE; 490 target->local_abspath = NULL; 491 target->local_relpath = stripped_path; 492 return SVN_NO_ERROR; 493 } 494 } 495 else 496 { 497 target->local_relpath = stripped_path; 498 } 499 500 /* Make sure the path is secure to use. We want the target to be inside 501 * the locked tree and not be fooled by symlinks it might contain. */ 502 SVN_ERR(svn_dirent_is_under_root(&under_root, 503 &target->local_abspath, root_abspath, 504 target->local_relpath, result_pool)); 505 506 if (! under_root) 507 { 508 /* The target path is outside of the working copy. Skip it. */ 509 target->skipped = TRUE; 510 target->local_abspath = NULL; 511 return SVN_NO_ERROR; 512 } 513 514 if (target_is_deleted(targets_info, target->local_abspath, scratch_pool)) 515 { 516 target->locally_deleted = TRUE; 517 target->db_kind = svn_node_none; 518 return SVN_NO_ERROR; 519 } 520 521 /* Skip things we should not be messing with. */ 522 err = svn_wc_status3(&status, wc_ctx, target->local_abspath, 523 result_pool, scratch_pool); 524 if (err) 525 { 526 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) 527 return svn_error_trace(err); 528 529 svn_error_clear(err); 530 531 target->locally_deleted = TRUE; 532 target->db_kind = svn_node_none; 533 status = NULL; 534 } 535 else if (status->node_status == svn_wc_status_ignored || 536 status->node_status == svn_wc_status_unversioned || 537 status->node_status == svn_wc_status_missing || 538 status->node_status == svn_wc_status_obstructed || 539 status->conflicted) 540 { 541 target->skipped = TRUE; 542 target->obstructed = TRUE; 543 return SVN_NO_ERROR; 544 } 545 else if (status->node_status == svn_wc_status_deleted) 546 { 547 target->locally_deleted = TRUE; 548 } 549 550 if (status && (status->kind != svn_node_unknown)) 551 target->db_kind = status->kind; 552 else 553 target->db_kind = svn_node_none; 554 555 SVN_ERR(svn_io_check_special_path(target->local_abspath, 556 &target->kind_on_disk, &target->is_symlink, 557 scratch_pool)); 558 559 if (target->locally_deleted) 560 { 561 const char *moved_to_abspath = NULL; 562 563 if (follow_moves 564 && !target_is_added(targets_info, target->local_abspath, 565 scratch_pool)) 566 { 567 SVN_ERR(svn_wc__node_was_moved_away(&moved_to_abspath, NULL, 568 wc_ctx, target->local_abspath, 569 result_pool, scratch_pool)); 570 } 571 572 if (moved_to_abspath) 573 { 574 target->local_abspath = moved_to_abspath; 575 target->local_relpath = svn_dirent_skip_ancestor(root_abspath, 576 moved_to_abspath); 577 578 if (!target->local_relpath || target->local_relpath[0] == '\0') 579 { 580 /* The target path is outside of the patch area. Skip it. */ 581 target->skipped = TRUE; 582 return SVN_NO_ERROR; 583 } 584 585 /* As far as we are concerned this target is not locally deleted. */ 586 target->locally_deleted = FALSE; 587 588 SVN_ERR(svn_io_check_special_path(target->local_abspath, 589 &target->kind_on_disk, 590 &target->is_symlink, 591 scratch_pool)); 592 } 593 else if (target->kind_on_disk != svn_node_none) 594 { 595 target->skipped = TRUE; 596 return SVN_NO_ERROR; 597 } 598 } 599 600#ifndef HAVE_SYMLINK 601 if (target->kind_on_disk == svn_node_file 602 && !target->is_symlink 603 && !target->locally_deleted 604 && status->prop_status != svn_wc_status_none) 605 { 606 const svn_string_t *value; 607 608 SVN_ERR(svn_wc_prop_get2(&value, wc_ctx, target->local_abspath, 609 SVN_PROP_SPECIAL, scratch_pool, scratch_pool)); 610 611 if (value) 612 target->is_symlink = TRUE; 613 } 614#endif 615 616 return SVN_NO_ERROR; 617} 618 619/* Baton for reading from properties. */ 620typedef struct prop_read_baton_t { 621 const svn_string_t *value; 622 apr_off_t offset; 623} prop_read_baton_t; 624 625/* Allocate *STRINGBUF in RESULT_POOL, and read into it one line from 626 * the unpatched property value accessed via BATON. 627 * Reading stops either after a line-terminator was found, or if 628 * the property value runs out in which case *EOF is set to TRUE. 629 * The line-terminator is not stored in *STRINGBUF. 630 * 631 * If the line is empty or could not be read, *line is set to NULL. 632 * 633 * The line-terminator is detected automatically and stored in *EOL 634 * if EOL is not NULL. If the end of the property value is reached 635 * and does not end with a newline character, and EOL is not NULL, 636 * *EOL is set to NULL. 637 * 638 * SCRATCH_POOL is used for temporary allocations. 639 */ 640static svn_error_t * 641readline_prop(void *baton, svn_stringbuf_t **line, const char **eol_str, 642 svn_boolean_t *eof, apr_pool_t *result_pool, 643 apr_pool_t *scratch_pool) 644{ 645 prop_read_baton_t *b = baton; 646 svn_stringbuf_t *str = NULL; 647 const char *c; 648 svn_boolean_t found_eof; 649 650 if ((apr_uint64_t)b->offset >= (apr_uint64_t)b->value->len) 651 { 652 *eol_str = NULL; 653 *eof = TRUE; 654 *line = NULL; 655 return SVN_NO_ERROR; 656 } 657 658 /* Read bytes into STR up to and including, but not storing, 659 * the next EOL sequence. */ 660 *eol_str = NULL; 661 found_eof = FALSE; 662 do 663 { 664 c = b->value->data + b->offset; 665 b->offset++; 666 667 if (*c == '\0') 668 { 669 found_eof = TRUE; 670 break; 671 } 672 else if (*c == '\n') 673 { 674 *eol_str = "\n"; 675 } 676 else if (*c == '\r') 677 { 678 *eol_str = "\r"; 679 if (*(c + 1) == '\n') 680 { 681 *eol_str = "\r\n"; 682 b->offset++; 683 } 684 } 685 else 686 { 687 if (str == NULL) 688 str = svn_stringbuf_create_ensure(80, result_pool); 689 svn_stringbuf_appendbyte(str, *c); 690 } 691 692 if (*eol_str) 693 break; 694 } 695 while (c < b->value->data + b->value->len); 696 697 if (eof) 698 *eof = found_eof && !(str && str->len > 0); 699 *line = str; 700 701 return SVN_NO_ERROR; 702} 703 704/* Return in *OFFSET the current byte offset for reading from the 705 * unpatched property value accessed via BATON. 706 * Use SCRATCH_POOL for temporary allocations. */ 707static svn_error_t * 708tell_prop(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool) 709{ 710 prop_read_baton_t *b = baton; 711 712 *offset = b->offset; 713 return SVN_NO_ERROR; 714} 715 716/* Seek to the specified by OFFSET in the unpatched property value accessed 717 * via BATON. Use SCRATCH_POOL for temporary allocations. */ 718static svn_error_t * 719seek_prop(void *baton, apr_off_t offset, apr_pool_t *scratch_pool) 720{ 721 prop_read_baton_t *b = baton; 722 723 b->offset = offset; 724 return SVN_NO_ERROR; 725} 726 727/* Write LEN bytes from BUF into the patched property value accessed 728 * via BATON. Use SCRATCH_POOL for temporary allocations. */ 729static svn_error_t * 730write_prop(void *baton, const char *buf, apr_size_t len, 731 apr_pool_t *scratch_pool) 732{ 733 svn_stringbuf_t *patched_value = baton; 734 735 svn_stringbuf_appendbytes(patched_value, buf, len); 736 return SVN_NO_ERROR; 737} 738 739/* Initialize a PROP_TARGET structure for PROP_NAME on the patch target 740 * at LOCAL_ABSPATH. OPERATION indicates the operation performed on the 741 * property. Use working copy context WC_CTX. 742 * Allocate results in RESULT_POOL. 743 * Use SCRATCH_POOL for temporary allocations. */ 744static svn_error_t * 745init_prop_target(prop_patch_target_t **prop_target, 746 const patch_target_t *target, 747 const char *prop_name, 748 svn_diff_operation_kind_t operation, 749 svn_wc_context_t *wc_ctx, 750 const char *local_abspath, 751 apr_pool_t *result_pool, apr_pool_t *scratch_pool) 752{ 753 prop_patch_target_t *new_prop_target; 754 target_content_t *content; 755 const svn_string_t *value; 756 prop_read_baton_t *prop_read_baton; 757 758 content = apr_pcalloc(result_pool, sizeof(*content)); 759 760 /* All other fields are FALSE or NULL due to apr_pcalloc(). */ 761 content->current_line = 1; 762 content->eol_style = svn_subst_eol_style_none; 763 content->lines = apr_array_make(result_pool, 0, sizeof(apr_off_t)); 764 content->hunks = apr_array_make(result_pool, 0, sizeof(hunk_info_t *)); 765 content->keywords = apr_hash_make(result_pool); 766 767 new_prop_target = apr_pcalloc(result_pool, sizeof(*new_prop_target)); 768 new_prop_target->name = apr_pstrdup(result_pool, prop_name); 769 new_prop_target->operation = operation; 770 new_prop_target->content = content; 771 772 if (!(target->deleted || target->db_kind == svn_node_none)) 773 SVN_ERR(svn_wc_prop_get2(&value, wc_ctx, local_abspath, prop_name, 774 result_pool, scratch_pool)); 775 else 776 value = NULL; 777 778 content->existed = (value != NULL); 779 new_prop_target->value = value; 780 new_prop_target->patched_value = svn_stringbuf_create_empty(result_pool); 781 782 783 /* Wire up the read and write callbacks. */ 784 prop_read_baton = apr_pcalloc(result_pool, sizeof(*prop_read_baton)); 785 prop_read_baton->value = value; 786 prop_read_baton->offset = 0; 787 content->readline = readline_prop; 788 content->tell = tell_prop; 789 content->seek = seek_prop; 790 content->read_baton = prop_read_baton; 791 content->write = write_prop; 792 content->write_baton = new_prop_target->patched_value; 793 794 *prop_target = new_prop_target; 795 796 return SVN_NO_ERROR; 797} 798 799/* Allocate *STRINGBUF in RESULT_POOL, and read into it one line from 800 * the unpatched file content accessed via BATON. 801 * Reading stops either after a line-terminator was found, 802 * or if EOF is reached in which case *EOF is set to TRUE. 803 * The line-terminator is not stored in *STRINGBUF. 804 * 805 * If the line is empty or could not be read, *line is set to NULL. 806 * 807 * The line-terminator is detected automatically and stored in *EOL 808 * if EOL is not NULL. If EOF is reached and FILE does not end 809 * with a newline character, and EOL is not NULL, *EOL is set to NULL. 810 * 811 * SCRATCH_POOL is used for temporary allocations. 812 */ 813static svn_error_t * 814readline_file(void *baton, svn_stringbuf_t **line, const char **eol_str, 815 svn_boolean_t *eof, apr_pool_t *result_pool, 816 apr_pool_t *scratch_pool) 817{ 818 apr_file_t *file = baton; 819 820 SVN_ERR(svn_io_file_readline(file, line, eol_str, eof, APR_SIZE_MAX, 821 result_pool, scratch_pool)); 822 823 if (!(*line)->len) 824 *line = NULL; 825 else 826 *eof = FALSE; 827 828 return SVN_NO_ERROR; 829} 830 831/* Return in *OFFSET the current byte offset for reading from the 832 * unpatched file content accessed via BATON. 833 * Use SCRATCH_POOL for temporary allocations. */ 834static svn_error_t * 835tell_file(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool) 836{ 837 apr_file_t *file = baton; 838 839 SVN_ERR(svn_io_file_get_offset(offset, file, scratch_pool)); 840 return SVN_NO_ERROR; 841} 842 843/* Seek to the specified by OFFSET in the unpatched file content accessed 844 * via BATON. Use SCRATCH_POOL for temporary allocations. */ 845static svn_error_t * 846seek_file(void *baton, apr_off_t offset, apr_pool_t *scratch_pool) 847{ 848 apr_file_t *file = baton; 849 850 SVN_ERR(svn_io_file_seek(file, APR_SET, &offset, scratch_pool)); 851 return SVN_NO_ERROR; 852} 853 854/* Write LEN bytes from BUF into the patched file content accessed 855 * via BATON. Use SCRATCH_POOL for temporary allocations. */ 856static svn_error_t * 857write_file(void *baton, const char *buf, apr_size_t len, 858 apr_pool_t *scratch_pool) 859{ 860 apr_file_t *file = baton; 861 862 SVN_ERR(svn_io_file_write_full(file, buf, len, &len, scratch_pool)); 863 return SVN_NO_ERROR; 864} 865 866/* Symlinks appear in patches in their repository normal form, abstracted by 867 * the svn_subst_* module. The functions below enable patches to change the 868 * targets of symlinks. 869 */ 870 871/* Baton for the (readline|tell|seek|write)_symlink functions. */ 872struct symlink_baton_t 873{ 874 /* The path to the symlink on disk (not the path to the target of the link) */ 875 const char *local_abspath; 876 877 /* Indicates whether the "normal form" of the symlink has been read. */ 878 svn_boolean_t at_eof; 879}; 880 881/* Allocate *STRINGBUF in RESULT_POOL, and store into it the "normal form" 882 * of the symlink accessed via BATON. 883 * 884 * Otherwise behaves like readline_file(), which see. 885 */ 886static svn_error_t * 887readline_symlink(void *baton, svn_stringbuf_t **line, const char **eol_str, 888 svn_boolean_t *eof, apr_pool_t *result_pool, 889 apr_pool_t *scratch_pool) 890{ 891 struct symlink_baton_t *sb = baton; 892 893 if (eof) 894 *eof = TRUE; 895 if (eol_str) 896 *eol_str = NULL; 897 898 if (sb->at_eof) 899 { 900 *line = NULL; 901 } 902 else 903 { 904 svn_stream_t *stream; 905 const apr_size_t len_hint = 64; /* arbitrary */ 906 907 SVN_ERR(svn_subst_read_specialfile(&stream, sb->local_abspath, 908 scratch_pool, scratch_pool)); 909 SVN_ERR(svn_stringbuf_from_stream(line, stream, len_hint, result_pool)); 910 *eof = FALSE; 911 sb->at_eof = TRUE; 912 } 913 914 return SVN_NO_ERROR; 915} 916 917/* Identical to readline_symlink(), but returns symlink in raw format to 918 * allow patching links in git-style. 919 */ 920static svn_error_t * 921readline_symlink_git(void *baton, svn_stringbuf_t **line, const char **eol_str, 922 svn_boolean_t *eof, apr_pool_t *result_pool, 923 apr_pool_t *scratch_pool) 924{ 925 SVN_ERR(readline_symlink(baton, line, eol_str, eof, 926 result_pool, scratch_pool)); 927 928 if (*line && (*line)->len > 5 && !strncmp((*line)->data, "link ", 5)) 929 svn_stringbuf_remove(*line, 0, 5); /* Skip "link " */ 930 931 return SVN_NO_ERROR; 932} 933 934/* Set *OFFSET to 1 or 0 depending on whether the "normal form" of 935 * the symlink has already been read. */ 936static svn_error_t * 937tell_symlink(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool) 938{ 939 struct symlink_baton_t *sb = baton; 940 941 *offset = sb->at_eof ? 1 : 0; 942 return SVN_NO_ERROR; 943} 944 945/* If offset is non-zero, mark the symlink as having been read in its 946 * "normal form". Else, mark the symlink as not having been read yet. */ 947static svn_error_t * 948seek_symlink(void *baton, apr_off_t offset, apr_pool_t *scratch_pool) 949{ 950 struct symlink_baton_t *sb = baton; 951 952 sb->at_eof = (offset != 0); 953 return SVN_NO_ERROR; 954} 955 956/* Return a suitable filename for the target of PATCH. 957 * Examine the ``old'' and ``new'' file names, and choose the file name 958 * with the fewest path components, the shortest basename, and the shortest 959 * total file name length (in that order). In case of a tie, return the new 960 * filename. This heuristic is also used by Larry Wall's UNIX patch (except 961 * that it prompts for a filename in case of a tie). 962 * Additionally, for compatibility with git, if one of the filenames 963 * is "/dev/null", use the other filename. */ 964static const char * 965choose_target_filename(const svn_patch_t *patch) 966{ 967 apr_size_t old; 968 apr_size_t new; 969 970 if (strcmp(patch->old_filename, "/dev/null") == 0) 971 return patch->new_filename; 972 if (strcmp(patch->new_filename, "/dev/null") == 0) 973 return patch->old_filename; 974 975 /* If the patch renames the target, use the old name while 976 * applying hunks. The target will be renamed to the new name 977 * after hunks have been applied. */ 978 if (patch->operation == svn_diff_op_moved) 979 return patch->old_filename; 980 981 old = svn_path_component_count(patch->old_filename); 982 new = svn_path_component_count(patch->new_filename); 983 984 if (old == new) 985 { 986 old = strlen(svn_dirent_basename(patch->old_filename, NULL)); 987 new = strlen(svn_dirent_basename(patch->new_filename, NULL)); 988 989 if (old == new) 990 { 991 old = strlen(patch->old_filename); 992 new = strlen(patch->new_filename); 993 } 994 } 995 996 return (old < new) ? patch->old_filename : patch->new_filename; 997} 998 999/* Attempt to initialize a *PATCH_TARGET structure for a target file 1000 * described by PATCH. Use working copy context WC_CTX. 1001 * STRIP_COUNT specifies the number of leading path components 1002 * which should be stripped from target paths in the patch. 1003 * The patch target structure is allocated in RESULT_POOL, but if the target 1004 * should be skipped, PATCH_TARGET->SKIPPED is set and the target should be 1005 * treated as not fully initialized, e.g. the caller should not not do any 1006 * further operations on the target if it is marked to be skipped. 1007 * If REMOVE_TEMPFILES is TRUE, set up temporary files to be removed as 1008 * soon as they are no longer needed. 1009 * Use SCRATCH_POOL for all other allocations. */ 1010static svn_error_t * 1011init_patch_target(patch_target_t **patch_target, 1012 const svn_patch_t *patch, 1013 const char *root_abspath, 1014 svn_wc_context_t *wc_ctx, int strip_count, 1015 svn_boolean_t remove_tempfiles, 1016 const apr_array_header_t *targets_info, 1017 apr_pool_t *result_pool, apr_pool_t *scratch_pool) 1018{ 1019 patch_target_t *target; 1020 target_content_t *content; 1021 svn_boolean_t has_text_changes = FALSE; 1022 svn_boolean_t follow_moves; 1023 const char *tempdir_abspath; 1024 1025 has_text_changes = ((patch->hunks && patch->hunks->nelts > 0) 1026 || patch->binary_patch); 1027 1028 content = apr_pcalloc(result_pool, sizeof(*content)); 1029 1030 /* All other fields in content are FALSE or NULL due to apr_pcalloc().*/ 1031 content->current_line = 1; 1032 content->eol_style = svn_subst_eol_style_none; 1033 content->lines = apr_array_make(result_pool, 0, sizeof(apr_off_t)); 1034 content->hunks = apr_array_make(result_pool, 0, sizeof(hunk_info_t *)); 1035 content->keywords = apr_hash_make(result_pool); 1036 1037 target = apr_pcalloc(result_pool, sizeof(*target)); 1038 1039 /* All other fields in target are FALSE or NULL due to apr_pcalloc(). */ 1040 target->db_kind = svn_node_none; 1041 target->kind_on_disk = svn_node_none; 1042 target->content = content; 1043 target->prop_targets = apr_hash_make(result_pool); 1044 target->operation = patch->operation; 1045 1046 if (patch->operation == svn_diff_op_added /* Allow replacing */ 1047 || patch->operation == svn_diff_op_moved) 1048 { 1049 follow_moves = FALSE; 1050 } 1051 else if (patch->operation == svn_diff_op_unchanged 1052 && patch->hunks && patch->hunks->nelts == 1) 1053 { 1054 svn_diff_hunk_t *hunk = APR_ARRAY_IDX(patch->hunks, 0, 1055 svn_diff_hunk_t *); 1056 1057 follow_moves = (svn_diff_hunk_get_original_start(hunk) != 0); 1058 } 1059 else 1060 follow_moves = TRUE; 1061 1062 SVN_ERR(resolve_target_path(target, choose_target_filename(patch), 1063 root_abspath, strip_count, has_text_changes, 1064 follow_moves, wc_ctx, targets_info, 1065 result_pool, scratch_pool)); 1066 *patch_target = target; 1067 if (! target->skipped) 1068 { 1069 if (patch->old_symlink_bit == svn_tristate_true 1070 || patch->new_symlink_bit == svn_tristate_true) 1071 { 1072 target->git_symlink_format = TRUE; 1073 } 1074 1075 /* ### Is it ok to set the operation of the target already here? Isn't 1076 * ### the target supposed to be marked with an operation after we have 1077 * ### determined that the changes will apply cleanly to the WC? Maybe 1078 * ### we should have kept the patch field in patch_target_t to be 1079 * ### able to distinguish between 'what the patch says we should do' 1080 * ### and 'what we can do with the given state of our WC'. */ 1081 if (patch->operation == svn_diff_op_added) 1082 target->added = TRUE; 1083 else if (patch->operation == svn_diff_op_deleted) 1084 target->deleted = TRUE; 1085 else if (patch->operation == svn_diff_op_moved) 1086 { 1087 const char *move_target_path; 1088 const char *move_target_relpath; 1089 svn_boolean_t under_root; 1090 svn_boolean_t is_special; 1091 svn_node_kind_t kind_on_disk; 1092 svn_node_kind_t wc_kind; 1093 1094 move_target_path = svn_dirent_internal_style(patch->new_filename, 1095 scratch_pool); 1096 1097 if (strip_count > 0) 1098 SVN_ERR(strip_path(&move_target_path, move_target_path, 1099 strip_count, scratch_pool, scratch_pool)); 1100 1101 if (svn_dirent_is_absolute(move_target_path)) 1102 { 1103 move_target_relpath = svn_dirent_is_child(root_abspath, 1104 move_target_path, 1105 scratch_pool); 1106 if (! move_target_relpath) 1107 { 1108 /* The move target path is either outside of the working 1109 * copy or it is the working copy itself. Skip it. */ 1110 target->skipped = TRUE; 1111 return SVN_NO_ERROR; 1112 } 1113 } 1114 else 1115 move_target_relpath = move_target_path; 1116 1117 /* Make sure the move target path is secure to use. */ 1118 SVN_ERR(svn_dirent_is_under_root(&under_root, 1119 &target->move_target_abspath, 1120 root_abspath, 1121 move_target_relpath, result_pool)); 1122 if (! under_root) 1123 { 1124 /* The target path is outside of the working copy. Skip it. */ 1125 target->skipped = TRUE; 1126 target->move_target_abspath = NULL; 1127 return SVN_NO_ERROR; 1128 } 1129 1130 SVN_ERR(svn_io_check_special_path(target->move_target_abspath, 1131 &kind_on_disk, &is_special, 1132 scratch_pool)); 1133 SVN_ERR(svn_wc_read_kind2(&wc_kind, wc_ctx, 1134 target->move_target_abspath, 1135 FALSE, FALSE, scratch_pool)); 1136 if (wc_kind == svn_node_file || wc_kind == svn_node_dir) 1137 { 1138 /* The move target path already exists on disk. */ 1139 svn_error_t *err; 1140 const char *moved_from_abspath; 1141 1142 err = svn_wc__node_was_moved_here(&moved_from_abspath, NULL, 1143 wc_ctx, 1144 target->move_target_abspath, 1145 scratch_pool, scratch_pool); 1146 1147 if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) 1148 { 1149 svn_error_clear(err); 1150 err = NULL; 1151 moved_from_abspath = NULL; 1152 } 1153 else 1154 SVN_ERR(err); 1155 1156 if (moved_from_abspath && (strcmp(moved_from_abspath, 1157 target->local_abspath) == 0)) 1158 { 1159 target->local_abspath = target->move_target_abspath; 1160 target->move_target_abspath = NULL; 1161 target->operation = svn_diff_op_modified; 1162 target->locally_deleted = FALSE; 1163 target->db_kind = wc_kind; 1164 target->kind_on_disk = kind_on_disk; 1165 target->is_special = is_special; 1166 1167 target->had_already_applied = TRUE; /* Make sure we notify */ 1168 } 1169 else 1170 { 1171 target->skipped = TRUE; 1172 target->move_target_abspath = NULL; 1173 return SVN_NO_ERROR; 1174 } 1175 1176 } 1177 else if (kind_on_disk != svn_node_none 1178 || target_is_added(targets_info, target->move_target_abspath, 1179 scratch_pool)) 1180 { 1181 target->skipped = TRUE; 1182 target->move_target_abspath = NULL; 1183 return SVN_NO_ERROR; 1184 } 1185 } 1186 1187 /* Create a temporary file to write the patched result to. 1188 * Also grab various bits of information about the file. */ 1189 if (target->is_symlink) 1190 { 1191 struct symlink_baton_t *sb = apr_pcalloc(result_pool, sizeof(*sb)); 1192 content->existed = TRUE; 1193 1194 sb->local_abspath = target->local_abspath; 1195 1196 /* Wire up the read callbacks. */ 1197 content->read_baton = sb; 1198 1199 content->readline = target->git_symlink_format ? readline_symlink_git 1200 : readline_symlink; 1201 content->seek = seek_symlink; 1202 content->tell = tell_symlink; 1203 } 1204 else if (target->kind_on_disk == svn_node_file) 1205 { 1206 SVN_ERR(svn_io_file_open(&target->file, target->local_abspath, 1207 APR_READ | APR_BUFFERED, 1208 APR_OS_DEFAULT, result_pool)); 1209 SVN_ERR(svn_io_is_file_executable(&target->executable, 1210 target->local_abspath, 1211 scratch_pool)); 1212 SVN_ERR(obtain_eol_and_keywords_for_file(&content->keywords, 1213 &content->eol_style, 1214 &content->eol_str, 1215 wc_ctx, 1216 target->local_abspath, 1217 result_pool, 1218 scratch_pool)); 1219 content->existed = TRUE; 1220 1221 /* Wire up the read callbacks. */ 1222 content->readline = readline_file; 1223 content->seek = seek_file; 1224 content->tell = tell_file; 1225 content->read_baton = target->file; 1226 } 1227 1228 /* Open a temporary file to write the patched result to. */ 1229 SVN_ERR(svn_wc__get_tmpdir(&tempdir_abspath, wc_ctx, 1230 target->local_abspath, scratch_pool, scratch_pool)); 1231 SVN_ERR(svn_io_open_unique_file3(&target->patched_file, 1232 &target->patched_path, tempdir_abspath, 1233 remove_tempfiles ? 1234 svn_io_file_del_on_pool_cleanup : 1235 svn_io_file_del_none, 1236 result_pool, scratch_pool)); 1237 1238 /* Put the write callback in place. */ 1239 content->write = write_file; 1240 content->write_baton = target->patched_file; 1241 1242 /* Open a temporary stream to write rejected hunks to. */ 1243 SVN_ERR(svn_stream_open_unique(&target->reject_stream, 1244 &target->reject_path, tempdir_abspath, 1245 remove_tempfiles ? 1246 svn_io_file_del_on_pool_cleanup : 1247 svn_io_file_del_none, 1248 result_pool, scratch_pool)); 1249 1250 /* Handle properties. */ 1251 if (! target->skipped) 1252 { 1253 apr_hash_index_t *hi; 1254 1255 for (hi = apr_hash_first(result_pool, patch->prop_patches); 1256 hi; 1257 hi = apr_hash_next(hi)) 1258 { 1259 const char *prop_name = apr_hash_this_key(hi); 1260 svn_prop_patch_t *prop_patch = apr_hash_this_val(hi); 1261 prop_patch_target_t *prop_target; 1262 1263 SVN_ERR(init_prop_target(&prop_target, 1264 target, prop_name, 1265 prop_patch->operation, 1266 wc_ctx, target->local_abspath, 1267 result_pool, scratch_pool)); 1268 svn_hash_sets(target->prop_targets, prop_name, prop_target); 1269 } 1270 1271 /* Now, check for out-of-band mode changes and convert these in 1272 their Subversion equivalent properties. */ 1273 if (patch->new_executable_bit != svn_tristate_unknown 1274 && patch->new_executable_bit != patch->old_executable_bit) 1275 { 1276 svn_diff_operation_kind_t operation; 1277 1278 if (patch->new_executable_bit == svn_tristate_true) 1279 operation = svn_diff_op_added; 1280 else if (patch->new_executable_bit == svn_tristate_false) 1281 { 1282 /* Made non-executable. */ 1283 if (patch->old_executable_bit == svn_tristate_true) 1284 operation = svn_diff_op_deleted; 1285 else 1286 operation = svn_diff_op_unchanged; 1287 } 1288 else 1289 operation = svn_diff_op_unchanged; 1290 1291 if (operation != svn_diff_op_unchanged) 1292 { 1293 prop_patch_target_t *prop_target; 1294 1295 prop_target = svn_hash_gets(target->prop_targets, 1296 SVN_PROP_EXECUTABLE); 1297 1298 if (prop_target && operation != prop_target->operation) 1299 { 1300 return svn_error_createf(SVN_ERR_INVALID_INPUT, NULL, 1301 _("Invalid patch: specifies " 1302 "contradicting mode changes and " 1303 "%s changes (for '%s')"), 1304 SVN_PROP_EXECUTABLE, 1305 target->local_abspath); 1306 } 1307 else if (!prop_target) 1308 { 1309 SVN_ERR(init_prop_target(&prop_target, 1310 target, SVN_PROP_EXECUTABLE, 1311 operation, 1312 wc_ctx, target->local_abspath, 1313 result_pool, scratch_pool)); 1314 svn_hash_sets(target->prop_targets, SVN_PROP_EXECUTABLE, 1315 prop_target); 1316 } 1317 } 1318 } 1319 1320 if (patch->new_symlink_bit != svn_tristate_unknown 1321 && patch->new_symlink_bit != patch->old_symlink_bit) 1322 { 1323 svn_diff_operation_kind_t operation; 1324 1325 if (patch->new_symlink_bit == svn_tristate_true) 1326 operation = svn_diff_op_added; 1327 else if (patch->new_symlink_bit == svn_tristate_false) 1328 { 1329 /* Made non-symlink. */ 1330 if (patch->old_symlink_bit == svn_tristate_true) 1331 operation = svn_diff_op_deleted; 1332 else 1333 operation = svn_diff_op_unchanged; 1334 } 1335 else 1336 operation = svn_diff_op_unchanged; 1337 1338 if (operation != svn_diff_op_unchanged) 1339 { 1340 prop_patch_target_t *prop_target; 1341 prop_target = svn_hash_gets(target->prop_targets, 1342 SVN_PROP_SPECIAL); 1343 1344 if (prop_target && operation != prop_target->operation) 1345 { 1346 return svn_error_createf(SVN_ERR_INVALID_INPUT, NULL, 1347 _("Invalid patch: specifies " 1348 "contradicting mode changes and " 1349 "%s changes (for '%s')"), 1350 SVN_PROP_SPECIAL, 1351 target->local_abspath); 1352 } 1353 else if (!prop_target) 1354 { 1355 SVN_ERR(init_prop_target(&prop_target, 1356 target, SVN_PROP_SPECIAL, 1357 operation, 1358 wc_ctx, target->local_abspath, 1359 result_pool, scratch_pool)); 1360 svn_hash_sets(target->prop_targets, SVN_PROP_SPECIAL, 1361 prop_target); 1362 } 1363 } 1364 } 1365 } 1366 } 1367 1368 if ((target->locally_deleted || target->db_kind == svn_node_none) 1369 && !target->added 1370 && target->operation == svn_diff_op_unchanged) 1371 { 1372 svn_boolean_t maybe_add = FALSE; 1373 1374 if (patch->hunks && patch->hunks->nelts == 1) 1375 { 1376 svn_diff_hunk_t *hunk = APR_ARRAY_IDX(patch->hunks, 0, 1377 svn_diff_hunk_t *); 1378 1379 if (svn_diff_hunk_get_original_start(hunk) == 0) 1380 maybe_add = TRUE; 1381 } 1382 else if (patch->prop_patches && apr_hash_count(patch->prop_patches)) 1383 { 1384 apr_hash_index_t *hi; 1385 svn_boolean_t all_add = TRUE; 1386 1387 for (hi = apr_hash_first(result_pool, patch->prop_patches); 1388 hi; 1389 hi = apr_hash_next(hi)) 1390 { 1391 svn_prop_patch_t *prop_patch = apr_hash_this_val(hi); 1392 1393 if (prop_patch->operation != svn_diff_op_added) 1394 { 1395 all_add = FALSE; 1396 break; 1397 } 1398 } 1399 1400 maybe_add = all_add; 1401 } 1402 /* Other implied types */ 1403 1404 if (maybe_add) 1405 target->added = TRUE; 1406 } 1407 else if (!target->deleted && !target->added 1408 && target->operation == svn_diff_op_unchanged) 1409 { 1410 svn_boolean_t maybe_delete = FALSE; 1411 1412 if (patch->hunks && patch->hunks->nelts == 1) 1413 { 1414 svn_diff_hunk_t *hunk = APR_ARRAY_IDX(patch->hunks, 0, 1415 svn_diff_hunk_t *); 1416 1417 if (svn_diff_hunk_get_modified_start(hunk) == 0) 1418 maybe_delete = TRUE; 1419 } 1420 1421 /* Other implied types */ 1422 1423 if (maybe_delete) 1424 target->deleted = TRUE; 1425 } 1426 1427 if (target->reject_stream != NULL) 1428 { 1429 /* The reject file needs a diff header. */ 1430 const char *left_src = target->canon_path_from_patchfile; 1431 const char *right_src = target->canon_path_from_patchfile; 1432 1433 /* Handle moves specifically? */ 1434 if (target->added) 1435 left_src = "/dev/null"; 1436 if (target->deleted) 1437 right_src = "/dev/null"; 1438 1439 SVN_ERR(svn_stream_printf(target->reject_stream, scratch_pool, 1440 "--- %s" APR_EOL_STR 1441 "+++ %s" APR_EOL_STR, 1442 left_src, right_src)); 1443 } 1444 1445 return SVN_NO_ERROR; 1446} 1447 1448/* Read a *LINE from CONTENT. If the line has not been read before 1449 * mark the line in CONTENT->LINES. 1450 * If a line could be read successfully, increase CONTENT->CURRENT_LINE, 1451 * and allocate *LINE in RESULT_POOL. 1452 * Do temporary allocations in SCRATCH_POOL. 1453 */ 1454static svn_error_t * 1455readline(target_content_t *content, 1456 const char **line, 1457 apr_pool_t *result_pool, 1458 apr_pool_t *scratch_pool) 1459{ 1460 svn_stringbuf_t *line_raw; 1461 const char *eol_str; 1462 svn_linenum_t max_line = (svn_linenum_t)content->lines->nelts + 1; 1463 1464 if (content->eof || content->readline == NULL) 1465 { 1466 *line = ""; 1467 return SVN_NO_ERROR; 1468 } 1469 1470 SVN_ERR_ASSERT(content->current_line <= max_line); 1471 if (content->current_line == max_line) 1472 { 1473 apr_off_t offset; 1474 1475 SVN_ERR(content->tell(content->read_baton, &offset, 1476 scratch_pool)); 1477 APR_ARRAY_PUSH(content->lines, apr_off_t) = offset; 1478 } 1479 1480 SVN_ERR(content->readline(content->read_baton, &line_raw, 1481 &eol_str, &content->eof, 1482 result_pool, scratch_pool)); 1483 if (content->eol_style == svn_subst_eol_style_none) 1484 content->eol_str = eol_str; 1485 1486 if (line_raw) 1487 { 1488 /* Contract keywords. */ 1489 SVN_ERR(svn_subst_translate_cstring2(line_raw->data, line, 1490 NULL, FALSE, 1491 content->keywords, FALSE, 1492 result_pool)); 1493 } 1494 else 1495 *line = ""; 1496 1497 if ((line_raw && line_raw->len > 0) || eol_str) 1498 content->current_line++; 1499 1500 SVN_ERR_ASSERT(content->current_line > 0); 1501 1502 return SVN_NO_ERROR; 1503} 1504 1505/* Seek to the specified LINE in CONTENT. 1506 * Mark any lines not read before in CONTENT->LINES. 1507 * Do temporary allocations in SCRATCH_POOL. 1508 */ 1509static svn_error_t * 1510seek_to_line(target_content_t *content, svn_linenum_t line, 1511 apr_pool_t *scratch_pool) 1512{ 1513 svn_linenum_t saved_line; 1514 svn_boolean_t saved_eof; 1515 1516 SVN_ERR_ASSERT(line > 0); 1517 1518 if (line == content->current_line) 1519 return SVN_NO_ERROR; 1520 1521 saved_line = content->current_line; 1522 saved_eof = content->eof; 1523 1524 if (line <= (svn_linenum_t)content->lines->nelts) 1525 { 1526 apr_off_t offset; 1527 1528 offset = APR_ARRAY_IDX(content->lines, line - 1, apr_off_t); 1529 SVN_ERR(content->seek(content->read_baton, offset, 1530 scratch_pool)); 1531 content->current_line = line; 1532 } 1533 else 1534 { 1535 const char *dummy; 1536 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1537 1538 while (! content->eof && content->current_line < line) 1539 { 1540 svn_pool_clear(iterpool); 1541 SVN_ERR(readline(content, &dummy, iterpool, iterpool)); 1542 } 1543 svn_pool_destroy(iterpool); 1544 } 1545 1546 /* After seeking backwards from EOF position clear EOF indicator. */ 1547 if (saved_eof && saved_line > content->current_line) 1548 content->eof = FALSE; 1549 1550 return SVN_NO_ERROR; 1551} 1552 1553/* Indicate in *MATCHED whether the original text of HUNK matches the patch 1554 * CONTENT at its current line. Lines within FUZZ lines of the start or 1555 * end of HUNK will always match. If IGNORE_WHITESPACE is set, we ignore 1556 * whitespace when doing the matching. When this function returns, neither 1557 * CONTENT->CURRENT_LINE nor the file offset in the target file will 1558 * have changed. If MATCH_MODIFIED is TRUE, match the modified hunk text, 1559 * rather than the original hunk text. 1560 * Do temporary allocations in POOL. */ 1561static svn_error_t * 1562match_hunk(svn_boolean_t *matched, target_content_t *content, 1563 svn_diff_hunk_t *hunk, svn_linenum_t fuzz, 1564 svn_boolean_t ignore_whitespace, 1565 svn_boolean_t match_modified, apr_pool_t *pool) 1566{ 1567 svn_stringbuf_t *hunk_line; 1568 const char *target_line; 1569 svn_linenum_t lines_read; 1570 svn_linenum_t saved_line; 1571 svn_boolean_t hunk_eof; 1572 svn_boolean_t lines_matched; 1573 apr_pool_t *iterpool; 1574 svn_linenum_t hunk_length; 1575 svn_linenum_t leading_context; 1576 svn_linenum_t trailing_context; 1577 svn_linenum_t fuzz_penalty; 1578 1579 *matched = FALSE; 1580 1581 if (content->eof) 1582 return SVN_NO_ERROR; 1583 1584 fuzz_penalty = svn_diff_hunk__get_fuzz_penalty(hunk); 1585 1586 if (fuzz_penalty > fuzz) 1587 return SVN_NO_ERROR; 1588 else 1589 fuzz -= fuzz_penalty; 1590 1591 saved_line = content->current_line; 1592 lines_read = 0; 1593 lines_matched = FALSE; 1594 leading_context = svn_diff_hunk_get_leading_context(hunk); 1595 trailing_context = svn_diff_hunk_get_trailing_context(hunk); 1596 if (match_modified) 1597 { 1598 svn_diff_hunk_reset_modified_text(hunk); 1599 hunk_length = svn_diff_hunk_get_modified_length(hunk); 1600 } 1601 else 1602 { 1603 svn_diff_hunk_reset_original_text(hunk); 1604 hunk_length = svn_diff_hunk_get_original_length(hunk); 1605 } 1606 iterpool = svn_pool_create(pool); 1607 do 1608 { 1609 const char *hunk_line_translated; 1610 1611 svn_pool_clear(iterpool); 1612 1613 if (match_modified) 1614 SVN_ERR(svn_diff_hunk_readline_modified_text(hunk, &hunk_line, 1615 NULL, &hunk_eof, 1616 iterpool, iterpool)); 1617 else 1618 SVN_ERR(svn_diff_hunk_readline_original_text(hunk, &hunk_line, 1619 NULL, &hunk_eof, 1620 iterpool, iterpool)); 1621 1622 /* Contract keywords, if any, before matching. */ 1623 SVN_ERR(svn_subst_translate_cstring2(hunk_line->data, 1624 &hunk_line_translated, 1625 NULL, FALSE, 1626 content->keywords, FALSE, 1627 iterpool)); 1628 SVN_ERR(readline(content, &target_line, iterpool, iterpool)); 1629 1630 lines_read++; 1631 1632 /* If the last line doesn't have a newline, we get EOF but still 1633 * have a non-empty line to compare. */ 1634 if ((hunk_eof && hunk_line->len == 0) || 1635 (content->eof && *target_line == 0)) 1636 break; 1637 1638 /* Leading/trailing fuzzy lines always match. */ 1639 if ((lines_read <= fuzz && leading_context > fuzz) || 1640 (lines_read > hunk_length - fuzz && trailing_context > fuzz)) 1641 lines_matched = TRUE; 1642 else 1643 { 1644 if (ignore_whitespace) 1645 { 1646 char *hunk_line_trimmed; 1647 char *target_line_trimmed; 1648 1649 hunk_line_trimmed = apr_pstrdup(iterpool, hunk_line_translated); 1650 target_line_trimmed = apr_pstrdup(iterpool, target_line); 1651 apr_collapse_spaces(hunk_line_trimmed, hunk_line_trimmed); 1652 apr_collapse_spaces(target_line_trimmed, target_line_trimmed); 1653 lines_matched = ! strcmp(hunk_line_trimmed, target_line_trimmed); 1654 } 1655 else 1656 lines_matched = ! strcmp(hunk_line_translated, target_line); 1657 } 1658 } 1659 while (lines_matched); 1660 1661 *matched = lines_matched && hunk_eof && hunk_line->len == 0; 1662 SVN_ERR(seek_to_line(content, saved_line, iterpool)); 1663 svn_pool_destroy(iterpool); 1664 1665 return SVN_NO_ERROR; 1666} 1667 1668/* Scan lines of CONTENT for a match of the original text of HUNK, 1669 * up to but not including the specified UPPER_LINE. Use fuzz factor FUZZ. 1670 * If UPPER_LINE is zero scan until EOF occurs when reading from TARGET. 1671 * Return the line at which HUNK was matched in *MATCHED_LINE. 1672 * If the hunk did not match at all, set *MATCHED_LINE to zero. 1673 * If the hunk matched multiple times, and MATCH_FIRST is TRUE, 1674 * return the line number at which the first match occurred in *MATCHED_LINE. 1675 * If the hunk matched multiple times, and MATCH_FIRST is FALSE, 1676 * return the line number at which the last match occurred in *MATCHED_LINE. 1677 * If IGNORE_WHITESPACE is set, ignore whitespace during the matching. 1678 * If MATCH_MODIFIED is TRUE, match the modified hunk text, 1679 * rather than the original hunk text. 1680 * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation. 1681 * Do all allocations in POOL. */ 1682static svn_error_t * 1683scan_for_match(svn_linenum_t *matched_line, 1684 target_content_t *content, 1685 svn_diff_hunk_t *hunk, svn_boolean_t match_first, 1686 svn_linenum_t upper_line, svn_linenum_t fuzz, 1687 svn_boolean_t ignore_whitespace, 1688 svn_boolean_t match_modified, 1689 svn_cancel_func_t cancel_func, void *cancel_baton, 1690 apr_pool_t *pool) 1691{ 1692 apr_pool_t *iterpool; 1693 1694 *matched_line = 0; 1695 iterpool = svn_pool_create(pool); 1696 while ((content->current_line < upper_line || upper_line == 0) && 1697 ! content->eof) 1698 { 1699 svn_boolean_t matched; 1700 1701 svn_pool_clear(iterpool); 1702 1703 if (cancel_func) 1704 SVN_ERR(cancel_func(cancel_baton)); 1705 1706 SVN_ERR(match_hunk(&matched, content, hunk, fuzz, ignore_whitespace, 1707 match_modified, iterpool)); 1708 if (matched) 1709 { 1710 svn_boolean_t taken = FALSE; 1711 int i; 1712 1713 /* Don't allow hunks to match at overlapping locations. */ 1714 for (i = 0; i < content->hunks->nelts; i++) 1715 { 1716 const hunk_info_t *hi; 1717 svn_linenum_t length; 1718 1719 hi = APR_ARRAY_IDX(content->hunks, i, const hunk_info_t *); 1720 1721 if (match_modified) 1722 length = svn_diff_hunk_get_modified_length(hi->hunk); 1723 else 1724 length = svn_diff_hunk_get_original_length(hi->hunk); 1725 1726 taken = (! hi->rejected && 1727 content->current_line >= hi->matched_line && 1728 content->current_line < (hi->matched_line + length)); 1729 if (taken) 1730 break; 1731 } 1732 1733 if (! taken) 1734 { 1735 *matched_line = content->current_line; 1736 if (match_first) 1737 break; 1738 } 1739 } 1740 1741 if (! content->eof) 1742 SVN_ERR(seek_to_line(content, content->current_line + 1, 1743 iterpool)); 1744 } 1745 svn_pool_destroy(iterpool); 1746 1747 return SVN_NO_ERROR; 1748} 1749 1750/* Indicate in *MATCH whether the content described by CONTENT 1751 * matches the modified text of HUNK. 1752 * Use SCRATCH_POOL for temporary allocations. */ 1753static svn_error_t * 1754match_existing_target(svn_boolean_t *match, 1755 target_content_t *content, 1756 svn_diff_hunk_t *hunk, 1757 apr_pool_t *scratch_pool) 1758{ 1759 svn_boolean_t lines_matched; 1760 apr_pool_t *iterpool; 1761 svn_boolean_t hunk_eof; 1762 svn_linenum_t saved_line; 1763 1764 svn_diff_hunk_reset_modified_text(hunk); 1765 1766 saved_line = content->current_line; 1767 1768 iterpool = svn_pool_create(scratch_pool); 1769 do 1770 { 1771 const char *line; 1772 svn_stringbuf_t *hunk_line; 1773 const char *line_translated; 1774 const char *hunk_line_translated; 1775 1776 svn_pool_clear(iterpool); 1777 1778 SVN_ERR(readline(content, &line, iterpool, iterpool)); 1779 SVN_ERR(svn_diff_hunk_readline_modified_text(hunk, &hunk_line, 1780 NULL, &hunk_eof, 1781 iterpool, iterpool)); 1782 /* Contract keywords. */ 1783 SVN_ERR(svn_subst_translate_cstring2(line, &line_translated, 1784 NULL, FALSE, 1785 content->keywords, 1786 FALSE, iterpool)); 1787 SVN_ERR(svn_subst_translate_cstring2(hunk_line->data, 1788 &hunk_line_translated, 1789 NULL, FALSE, 1790 content->keywords, 1791 FALSE, iterpool)); 1792 lines_matched = ! strcmp(line_translated, hunk_line_translated); 1793 if (content->eof != hunk_eof) 1794 { 1795 svn_pool_destroy(iterpool); 1796 *match = FALSE; 1797 return SVN_NO_ERROR; 1798 } 1799 } 1800 while (lines_matched && ! content->eof && ! hunk_eof); 1801 svn_pool_destroy(iterpool); 1802 1803 *match = (lines_matched && content->eof == hunk_eof); 1804 SVN_ERR(seek_to_line(content, saved_line, scratch_pool)); 1805 1806 return SVN_NO_ERROR; 1807} 1808 1809/* Determine the line at which a HUNK applies to CONTENT of the TARGET 1810 * file, and return an appropriate hunk_info object in *HI, allocated from 1811 * RESULT_POOL. Use fuzz factor FUZZ. Set HI->FUZZ to FUZZ. If no correct 1812 * line can be determined, set HI->REJECTED to TRUE. PREVIOUS_OFFSET 1813 * is the offset at which the previous matching hunk was applied, or zero. 1814 * IGNORE_WHITESPACE tells whether whitespace should be considered when 1815 * matching. IS_PROP_HUNK indicates whether the hunk patches file content 1816 * or a property. 1817 * When this function returns, neither CONTENT->CURRENT_LINE nor 1818 * the file offset in the target file will have changed. 1819 * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation. 1820 * Do temporary allocations in POOL. */ 1821static svn_error_t * 1822get_hunk_info(hunk_info_t **hi, patch_target_t *target, 1823 target_content_t *content, 1824 svn_diff_hunk_t *hunk, svn_linenum_t fuzz, 1825 svn_linenum_t previous_offset, 1826 svn_boolean_t ignore_whitespace, 1827 svn_boolean_t is_prop_hunk, 1828 svn_cancel_func_t cancel_func, void *cancel_baton, 1829 apr_pool_t *result_pool, apr_pool_t *scratch_pool) 1830{ 1831 svn_linenum_t matched_line; 1832 svn_linenum_t original_start; 1833 svn_boolean_t already_applied; 1834 1835 original_start = svn_diff_hunk_get_original_start(hunk) + previous_offset; 1836 already_applied = FALSE; 1837 1838 /* An original offset of zero means that this hunk wants to create 1839 * a new file. Don't bother matching hunks in that case, since 1840 * the hunk applies at line 1. If the file already exists, the hunk 1841 * is rejected, unless the file is versioned and its content matches 1842 * the file the patch wants to create. */ 1843 if (original_start == 0 && fuzz > 0) 1844 { 1845 matched_line = 0; /* reject any fuzz for new files */ 1846 } 1847 else if (original_start == 0 && ! is_prop_hunk) 1848 { 1849 if (target->kind_on_disk == svn_node_file) 1850 { 1851 const svn_io_dirent2_t *dirent; 1852 SVN_ERR(svn_io_stat_dirent2(&dirent, target->local_abspath, FALSE, 1853 TRUE, scratch_pool, scratch_pool)); 1854 1855 if (dirent->kind == svn_node_file 1856 && !dirent->special 1857 && dirent->filesize == 0) 1858 { 1859 matched_line = 1; /* Matched an on-disk empty file */ 1860 } 1861 else 1862 { 1863 if (target->db_kind == svn_node_file) 1864 { 1865 svn_boolean_t file_matches; 1866 1867 SVN_ERR(match_existing_target(&file_matches, content, hunk, 1868 scratch_pool)); 1869 if (file_matches) 1870 { 1871 matched_line = 1; 1872 already_applied = TRUE; 1873 } 1874 else 1875 matched_line = 0; /* reject */ 1876 } 1877 else 1878 matched_line = 0; /* reject */ 1879 } 1880 } 1881 else 1882 matched_line = 1; 1883 } 1884 /* Same conditions apply as for the file case above. 1885 * 1886 * ### Since the hunk says the prop should be added we just assume so for 1887 * ### now and don't bother with storing the previous lines and such. When 1888 * ### we have the diff operation available we can just check for adds. */ 1889 else if (original_start == 0 && is_prop_hunk) 1890 { 1891 if (content->existed) 1892 { 1893 svn_boolean_t prop_matches; 1894 1895 SVN_ERR(match_existing_target(&prop_matches, content, hunk, 1896 scratch_pool)); 1897 1898 if (prop_matches) 1899 { 1900 matched_line = 1; 1901 already_applied = TRUE; 1902 } 1903 else 1904 matched_line = 0; /* reject */ 1905 } 1906 else 1907 matched_line = 1; 1908 } 1909 else if (original_start > 0 && content->existed) 1910 { 1911 svn_linenum_t modified_start; 1912 svn_linenum_t saved_line = content->current_line; 1913 1914 modified_start = svn_diff_hunk_get_modified_start(hunk); 1915 1916 /* Scan for a match at the line where the hunk thinks it 1917 * should be going. */ 1918 SVN_ERR(seek_to_line(content, original_start, scratch_pool)); 1919 if (content->current_line != original_start) 1920 { 1921 /* Seek failed. */ 1922 matched_line = 0; 1923 } 1924 else 1925 SVN_ERR(scan_for_match(&matched_line, content, hunk, TRUE, 1926 original_start + 1, fuzz, 1927 ignore_whitespace, FALSE, 1928 cancel_func, cancel_baton, 1929 scratch_pool)); 1930 1931 if (matched_line != original_start) 1932 { 1933 /* Check if the hunk is already applied. 1934 * We only check for an exact match here, and don't bother checking 1935 * for already applied patches with offset/fuzz, because such a 1936 * check would be ambiguous. */ 1937 if (fuzz == 0) 1938 { 1939 if (modified_start == 0 1940 && (target->operation == svn_diff_op_unchanged 1941 || target->operation == svn_diff_op_deleted)) 1942 { 1943 /* Patch wants to delete the file. */ 1944 1945 already_applied = target->locally_deleted; 1946 } 1947 else 1948 { 1949 svn_linenum_t seek_to; 1950 1951 if (modified_start == 0) 1952 seek_to = 1; /* Empty file case */ 1953 else 1954 seek_to = modified_start; 1955 1956 SVN_ERR(seek_to_line(content, seek_to, scratch_pool)); 1957 SVN_ERR(scan_for_match(&matched_line, content, 1958 hunk, TRUE, 1959 modified_start + 1, 1960 fuzz, ignore_whitespace, TRUE, 1961 cancel_func, cancel_baton, 1962 scratch_pool)); 1963 already_applied = (matched_line == modified_start); 1964 } 1965 } 1966 else 1967 already_applied = FALSE; 1968 1969 if (! already_applied) 1970 { 1971 int i; 1972 svn_linenum_t search_start = 1, search_end = 0; 1973 svn_linenum_t matched_line2; 1974 1975 /* Search for closest match before or after original 1976 start. We have no backward search so search forwards 1977 from the previous match (or start of file) to the 1978 original start looking for the last match. Then 1979 search forwards from the original start looking for a 1980 better match. Finally search forwards from the start 1981 of file to the previous hunk if that could result in 1982 a better match. */ 1983 1984 for (i = content->hunks->nelts; i > 0; --i) 1985 { 1986 const hunk_info_t *prev 1987 = APR_ARRAY_IDX(content->hunks, i - 1, const hunk_info_t *); 1988 if (!prev->rejected) 1989 { 1990 svn_linenum_t length; 1991 1992 length = svn_diff_hunk_get_original_length(prev->hunk); 1993 search_start = prev->matched_line + length; 1994 break; 1995 } 1996 } 1997 1998 /* Search from the previous match, or start of file, 1999 towards the original location. */ 2000 SVN_ERR(seek_to_line(content, search_start, scratch_pool)); 2001 SVN_ERR(scan_for_match(&matched_line, content, hunk, FALSE, 2002 original_start, fuzz, 2003 ignore_whitespace, FALSE, 2004 cancel_func, cancel_baton, 2005 scratch_pool)); 2006 2007 /* If a match we only need to search forwards for a 2008 better match, otherwise to the end of the file. */ 2009 if (matched_line) 2010 search_end = original_start + (original_start - matched_line); 2011 2012 /* Search from original location, towards the end. */ 2013 SVN_ERR(seek_to_line(content, original_start + 1, scratch_pool)); 2014 SVN_ERR(scan_for_match(&matched_line2, content, hunk, 2015 TRUE, search_end, fuzz, ignore_whitespace, 2016 FALSE, cancel_func, cancel_baton, 2017 scratch_pool)); 2018 2019 /* Chose the forward match if it is closer than the 2020 backward match or if there is no backward match. */ 2021 if (matched_line2 2022 && (!matched_line 2023 || (matched_line2 - original_start 2024 < original_start - matched_line))) 2025 matched_line = matched_line2; 2026 2027 /* Search from before previous hunk if there could be a 2028 better match. */ 2029 if (search_start > 1 2030 && (!matched_line 2031 || (matched_line > original_start 2032 && (matched_line - original_start 2033 > original_start - search_start)))) 2034 { 2035 svn_linenum_t search_start2 = 1; 2036 2037 if (matched_line 2038 && matched_line - original_start < original_start) 2039 search_start2 2040 = original_start - (matched_line - original_start) + 1; 2041 2042 SVN_ERR(seek_to_line(content, search_start2, scratch_pool)); 2043 SVN_ERR(scan_for_match(&matched_line2, content, hunk, FALSE, 2044 search_start - 1, fuzz, 2045 ignore_whitespace, FALSE, 2046 cancel_func, cancel_baton, 2047 scratch_pool)); 2048 if (matched_line2) 2049 matched_line = matched_line2; 2050 } 2051 } 2052 } 2053 else if (matched_line > 0 2054 && fuzz == 0 2055 && (svn_diff_hunk_get_leading_context(hunk) == 0 2056 || svn_diff_hunk_get_trailing_context(hunk) == 0) 2057 && (svn_diff_hunk_get_modified_length(hunk) > 2058 svn_diff_hunk_get_original_length(hunk))) 2059 { 2060 /* Check that we are not applying the same change that just adds some 2061 lines again, when we don't have enough context to see the 2062 difference */ 2063 svn_linenum_t reverse_matched_line; 2064 2065 SVN_ERR(seek_to_line(content, modified_start, scratch_pool)); 2066 SVN_ERR(scan_for_match(&reverse_matched_line, content, 2067 hunk, TRUE, 2068 modified_start + 1, 2069 fuzz, ignore_whitespace, TRUE, 2070 cancel_func, cancel_baton, 2071 scratch_pool)); 2072 2073 /* We might want to check that we are actually at the start or the 2074 end of the file. Having no context implies that we should be. */ 2075 already_applied = (reverse_matched_line == modified_start); 2076 } 2077 2078 SVN_ERR(seek_to_line(content, saved_line, scratch_pool)); 2079 } 2080 else if (!content->existed && svn_diff_hunk_get_modified_start(hunk) == 0) 2081 { 2082 /* The hunk wants to delete a file or property which doesn't exist. */ 2083 matched_line = 0; 2084 already_applied = TRUE; 2085 } 2086 else 2087 { 2088 /* The hunk wants to modify a file or property which doesn't exist. */ 2089 matched_line = 0; 2090 } 2091 2092 (*hi) = apr_pcalloc(result_pool, sizeof(hunk_info_t)); 2093 (*hi)->hunk = hunk; 2094 (*hi)->matched_line = matched_line; 2095 (*hi)->rejected = (matched_line == 0); 2096 (*hi)->already_applied = already_applied; 2097 (*hi)->report_fuzz = fuzz; 2098 (*hi)->match_fuzz = fuzz - svn_diff_hunk__get_fuzz_penalty(hunk); 2099 2100 return SVN_NO_ERROR; 2101} 2102 2103/* Copy lines to the patched content until the specified LINE has been 2104 * reached. Indicate in *EOF whether end-of-file was encountered while 2105 * reading from the target. 2106 * If LINE is zero, copy lines until end-of-file has been reached. 2107 * Do all allocations in POOL. */ 2108static svn_error_t * 2109copy_lines_to_target(target_content_t *content, svn_linenum_t line, 2110 apr_pool_t *pool) 2111{ 2112 apr_pool_t *iterpool; 2113 2114 iterpool = svn_pool_create(pool); 2115 while ((content->current_line < line || line == 0) && ! content->eof) 2116 { 2117 const char *target_line; 2118 apr_size_t len; 2119 2120 svn_pool_clear(iterpool); 2121 2122 SVN_ERR(readline(content, &target_line, iterpool, iterpool)); 2123 if (! content->eof) 2124 target_line = apr_pstrcat(iterpool, target_line, content->eol_str, 2125 SVN_VA_NULL); 2126 len = strlen(target_line); 2127 SVN_ERR(content->write(content->write_baton, target_line, 2128 len, iterpool)); 2129 } 2130 svn_pool_destroy(iterpool); 2131 2132 return SVN_NO_ERROR; 2133} 2134 2135/* Write the diff text of HUNK to TARGET's reject file, 2136 * and mark TARGET as having had rejects. 2137 * We don't expand keywords, nor normalise line-endings, in reject files. 2138 * Do temporary allocations in SCRATCH_POOL. */ 2139static svn_error_t * 2140reject_hunk(patch_target_t *target, target_content_t *content, 2141 svn_diff_hunk_t *hunk, const char *prop_name, 2142 apr_pool_t *pool) 2143{ 2144 svn_boolean_t eof; 2145 static const char * const text_atat = "@@"; 2146 static const char * const prop_atat = "##"; 2147 const char *atat; 2148 apr_pool_t *iterpool; 2149 2150 if (prop_name) 2151 { 2152 /* ### Print 'Added', 'Deleted' or 'Modified' instead of 'Property'. */ 2153 SVN_ERR(svn_stream_printf(target->reject_stream, 2154 pool, "Property: %s" APR_EOL_STR, prop_name)); 2155 atat = prop_atat; 2156 } 2157 else 2158 { 2159 atat = text_atat; 2160 } 2161 2162 SVN_ERR(svn_stream_printf(target->reject_stream, pool, 2163 "%s -%lu,%lu +%lu,%lu %s" APR_EOL_STR, 2164 atat, 2165 svn_diff_hunk_get_original_start(hunk), 2166 svn_diff_hunk_get_original_length(hunk), 2167 svn_diff_hunk_get_modified_start(hunk), 2168 svn_diff_hunk_get_modified_length(hunk), 2169 atat)); 2170 2171 iterpool = svn_pool_create(pool); 2172 do 2173 { 2174 svn_stringbuf_t *hunk_line; 2175 const char *eol_str; 2176 2177 svn_pool_clear(iterpool); 2178 2179 SVN_ERR(svn_diff_hunk_readline_diff_text(hunk, &hunk_line, &eol_str, 2180 &eof, iterpool, iterpool)); 2181 if (! eof) 2182 { 2183 if (hunk_line->len >= 1) 2184 { 2185 apr_size_t len = hunk_line->len; 2186 2187 SVN_ERR(svn_stream_write(target->reject_stream, 2188 hunk_line->data, &len)); 2189 } 2190 2191 if (eol_str) 2192 { 2193 SVN_ERR(svn_stream_puts(target->reject_stream, eol_str)); 2194 } 2195 } 2196 } 2197 while (! eof); 2198 svn_pool_destroy(iterpool); 2199 2200 if (prop_name) 2201 target->had_prop_rejects = TRUE; 2202 else 2203 target->had_rejects = TRUE; 2204 2205 return SVN_NO_ERROR; 2206} 2207 2208/* Write the modified text of the hunk described by HI to the patched 2209 * CONTENT. TARGET is the patch target. 2210 * If PROP_NAME is not NULL, the hunk is assumed to be targeted for 2211 * a property with the given name. 2212 * Do temporary allocations in POOL. */ 2213static svn_error_t * 2214apply_hunk(patch_target_t *target, target_content_t *content, 2215 hunk_info_t *hi, const char *prop_name, apr_pool_t *pool) 2216{ 2217 svn_linenum_t lines_read; 2218 svn_boolean_t eof; 2219 apr_pool_t *iterpool; 2220 svn_linenum_t fuzz = hi->match_fuzz; 2221 2222 /* ### Is there a cleaner way to describe if we have an existing target? 2223 */ 2224 if (target->kind_on_disk == svn_node_file || prop_name) 2225 { 2226 svn_linenum_t line; 2227 2228 /* Move forward to the hunk's line, copying data as we go. 2229 * Also copy leading lines of context which matched with fuzz. 2230 * The target has changed on the fuzzy-matched lines, 2231 * so we should retain the target's version of those lines. */ 2232 SVN_ERR(copy_lines_to_target(content, hi->matched_line + fuzz, 2233 pool)); 2234 2235 /* Skip the target's version of the hunk. 2236 * Don't skip trailing lines which matched with fuzz. */ 2237 line = content->current_line + 2238 svn_diff_hunk_get_original_length(hi->hunk) - (2 * fuzz); 2239 SVN_ERR(seek_to_line(content, line, pool)); 2240 if (content->current_line != line && ! content->eof) 2241 { 2242 /* Seek failed, reject this hunk. */ 2243 hi->rejected = TRUE; 2244 SVN_ERR(reject_hunk(target, content, hi->hunk, prop_name, pool)); 2245 return SVN_NO_ERROR; 2246 } 2247 } 2248 2249 /* Write the hunk's version to the patched result. 2250 * Don't write the lines which matched with fuzz. */ 2251 lines_read = 0; 2252 svn_diff_hunk_reset_modified_text(hi->hunk); 2253 iterpool = svn_pool_create(pool); 2254 do 2255 { 2256 svn_stringbuf_t *hunk_line; 2257 const char *eol_str; 2258 2259 svn_pool_clear(iterpool); 2260 2261 SVN_ERR(svn_diff_hunk_readline_modified_text(hi->hunk, &hunk_line, 2262 &eol_str, &eof, 2263 iterpool, iterpool)); 2264 lines_read++; 2265 if (lines_read > fuzz && 2266 lines_read <= svn_diff_hunk_get_modified_length(hi->hunk) - fuzz) 2267 { 2268 apr_size_t len; 2269 2270 if (hunk_line->len >= 1) 2271 { 2272 len = hunk_line->len; 2273 SVN_ERR(content->write(content->write_baton, 2274 hunk_line->data, len, iterpool)); 2275 } 2276 2277 if (eol_str) 2278 { 2279 /* Use the EOL as it was read from the patch file, 2280 * unless the target's EOL style is set by svn:eol-style */ 2281 if (content->eol_style != svn_subst_eol_style_none) 2282 eol_str = content->eol_str; 2283 2284 len = strlen(eol_str); 2285 SVN_ERR(content->write(content->write_baton, 2286 eol_str, len, iterpool)); 2287 } 2288 } 2289 } 2290 while (! eof); 2291 svn_pool_destroy(iterpool); 2292 2293 if (prop_name) 2294 target->has_prop_changes = TRUE; 2295 else 2296 target->has_text_changes = TRUE; 2297 2298 return SVN_NO_ERROR; 2299} 2300 2301/* Use client context CTX to send a suitable notification for hunk HI, 2302 * using TARGET to determine the path. If the hunk is a property hunk, 2303 * PROP_NAME must be the name of the property, else NULL. 2304 * Use POOL for temporary allocations. */ 2305static svn_error_t * 2306send_hunk_notification(const hunk_info_t *hi, 2307 const patch_target_t *target, 2308 const char *prop_name, 2309 const svn_client_ctx_t *ctx, 2310 apr_pool_t *pool) 2311{ 2312 svn_wc_notify_t *notify; 2313 svn_wc_notify_action_t action; 2314 2315 if (hi->already_applied) 2316 action = svn_wc_notify_patch_hunk_already_applied; 2317 else if (hi->rejected) 2318 action = svn_wc_notify_patch_rejected_hunk; 2319 else 2320 action = svn_wc_notify_patch_applied_hunk; 2321 2322 notify = svn_wc_create_notify(target->local_abspath 2323 ? target->local_abspath 2324 : target->local_relpath, 2325 action, pool); 2326 notify->hunk_original_start = 2327 svn_diff_hunk_get_original_start(hi->hunk); 2328 notify->hunk_original_length = 2329 svn_diff_hunk_get_original_length(hi->hunk); 2330 notify->hunk_modified_start = 2331 svn_diff_hunk_get_modified_start(hi->hunk); 2332 notify->hunk_modified_length = 2333 svn_diff_hunk_get_modified_length(hi->hunk); 2334 notify->hunk_matched_line = hi->matched_line; 2335 notify->hunk_fuzz = hi->report_fuzz; 2336 notify->prop_name = prop_name; 2337 2338 ctx->notify_func2(ctx->notify_baton2, notify, pool); 2339 2340 return SVN_NO_ERROR; 2341} 2342 2343/* Use client context CTX to send a suitable notification for a patch TARGET. 2344 * Use POOL for temporary allocations. */ 2345static svn_error_t * 2346send_patch_notification(const patch_target_t *target, 2347 const svn_client_ctx_t *ctx, 2348 apr_pool_t *scratch_pool) 2349{ 2350 svn_wc_notify_t *notify; 2351 svn_wc_notify_action_t action; 2352 const char *notify_path; 2353 2354 if (! ctx->notify_func2) 2355 return SVN_NO_ERROR; 2356 2357 if (target->skipped) 2358 action = svn_wc_notify_skip; 2359 else if (target->deleted) 2360 action = svn_wc_notify_delete; 2361 else if (target->added || target->move_target_abspath) 2362 action = svn_wc_notify_add; 2363 else 2364 action = svn_wc_notify_patch; 2365 2366 if (target->move_target_abspath) 2367 notify_path = target->move_target_abspath; 2368 else 2369 notify_path = target->local_abspath ? target->local_abspath 2370 : target->local_relpath; 2371 2372 notify = svn_wc_create_notify(notify_path, action, scratch_pool); 2373 notify->kind = (target->db_kind == svn_node_dir) ? svn_node_dir 2374 : svn_node_file; 2375 2376 if (action == svn_wc_notify_skip) 2377 { 2378 if (target->obstructed) 2379 notify->content_state = svn_wc_notify_state_obstructed; 2380 else if (target->db_kind == svn_node_none || 2381 target->db_kind == svn_node_unknown) 2382 notify->content_state = svn_wc_notify_state_missing; 2383 else 2384 notify->content_state = svn_wc_notify_state_unknown; 2385 } 2386 else 2387 { 2388 if (target->had_rejects) 2389 notify->content_state = svn_wc_notify_state_conflicted; 2390 else if (target->has_text_changes) 2391 notify->content_state = svn_wc_notify_state_changed; 2392 else if (target->had_already_applied) 2393 notify->content_state = svn_wc_notify_state_merged; 2394 else 2395 notify->content_state = svn_wc_notify_state_unchanged; 2396 2397 if (target->had_prop_rejects) 2398 notify->prop_state = svn_wc_notify_state_conflicted; 2399 else if (target->has_prop_changes) 2400 notify->prop_state = svn_wc_notify_state_changed; 2401 else if (target->had_prop_already_applied) 2402 notify->prop_state = svn_wc_notify_state_merged; 2403 else 2404 notify->prop_state = svn_wc_notify_state_unchanged; 2405 } 2406 2407 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 2408 2409 if (action == svn_wc_notify_patch) 2410 { 2411 int i; 2412 apr_pool_t *iterpool; 2413 apr_array_header_t *prop_targets; 2414 2415 iterpool = svn_pool_create(scratch_pool); 2416 for (i = 0; i < target->content->hunks->nelts; i++) 2417 { 2418 const hunk_info_t *hi; 2419 2420 svn_pool_clear(iterpool); 2421 2422 hi = APR_ARRAY_IDX(target->content->hunks, i, hunk_info_t *); 2423 2424 SVN_ERR(send_hunk_notification(hi, target, NULL /* prop_name */, 2425 ctx, iterpool)); 2426 } 2427 2428 prop_targets = svn_sort__hash(target->prop_targets, 2429 svn_sort_compare_items_lexically, 2430 scratch_pool); 2431 for (i = 0; i < prop_targets->nelts; i++) 2432 { 2433 int j; 2434 svn_sort__item_t item = APR_ARRAY_IDX(prop_targets, i, 2435 svn_sort__item_t); 2436 2437 prop_patch_target_t *prop_target = item.value; 2438 2439 for (j = 0; j < prop_target->content->hunks->nelts; j++) 2440 { 2441 const hunk_info_t *hi; 2442 2443 svn_pool_clear(iterpool); 2444 2445 hi = APR_ARRAY_IDX(prop_target->content->hunks, j, 2446 hunk_info_t *); 2447 2448 /* Don't notify on the hunk level for added or deleted props. */ 2449 if ((prop_target->operation != svn_diff_op_added && 2450 prop_target->operation != svn_diff_op_deleted) 2451 || hi->rejected || hi->already_applied) 2452 SVN_ERR(send_hunk_notification(hi, target, prop_target->name, 2453 ctx, iterpool)); 2454 } 2455 } 2456 svn_pool_destroy(iterpool); 2457 } 2458 2459 if (!target->skipped && target->move_target_abspath) 2460 { 2461 /* Notify about deletion of move source. */ 2462 notify = svn_wc_create_notify(target->local_abspath, 2463 svn_wc_notify_delete, scratch_pool); 2464 notify->kind = svn_node_file; 2465 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 2466 } 2467 2468 return SVN_NO_ERROR; 2469} 2470 2471/* Implements the callback for svn_sort__array. Puts hunks that match 2472 before hunks that do not match, puts hunks that match in order 2473 based on postion matched, puts hunks that do not match in order 2474 based on original position. */ 2475static int 2476sort_matched_hunks(const void *a, const void *b) 2477{ 2478 const hunk_info_t *item1 = *((const hunk_info_t * const *)a); 2479 const hunk_info_t *item2 = *((const hunk_info_t * const *)b); 2480 svn_boolean_t matched1 = !item1->rejected && !item1->already_applied; 2481 svn_boolean_t matched2 = !item2->rejected && !item2->already_applied; 2482 svn_linenum_t original1, original2; 2483 2484 if (matched1 && matched2) 2485 { 2486 /* Both match so use order matched in file. */ 2487 if (item1->matched_line > item2->matched_line) 2488 return 1; 2489 else if (item1->matched_line == item2->matched_line) 2490 return 0; 2491 else 2492 return -1; 2493 } 2494 else if (matched2) 2495 /* Only second matches, put it before first. */ 2496 return 1; 2497 else if (matched1) 2498 /* Only first matches, put it before second. */ 2499 return -1; 2500 2501 /* Neither matches, sort by original_start. */ 2502 original1 = svn_diff_hunk_get_original_start(item1->hunk); 2503 original2 = svn_diff_hunk_get_original_start(item2->hunk); 2504 if (original1 > original2) 2505 return 1; 2506 else if (original1 == original2) 2507 return 0; 2508 else 2509 return -1; 2510} 2511 2512 2513/* Apply a PATCH to a working copy at ABS_WC_PATH and put the result 2514 * into temporary files, to be installed in the working copy later. 2515 * Return information about the patch target in *PATCH_TARGET, allocated 2516 * in RESULT_POOL. Use WC_CTX as the working copy context. 2517 * STRIP_COUNT specifies the number of leading path components 2518 * which should be stripped from target paths in the patch. 2519 * REMOVE_TEMPFILES is as in svn_client_patch(). 2520 * TARGETS_INFO is for preserving info across calls. 2521 * IGNORE_WHITESPACE tells whether whitespace should be considered when 2522 * doing the matching. 2523 * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation. 2524 * Do temporary allocations in SCRATCH_POOL. */ 2525static svn_error_t * 2526apply_one_patch(patch_target_t **patch_target, svn_patch_t *patch, 2527 const char *abs_wc_path, svn_wc_context_t *wc_ctx, 2528 int strip_count, 2529 svn_boolean_t ignore_whitespace, 2530 svn_boolean_t remove_tempfiles, 2531 const apr_array_header_t *targets_info, 2532 svn_cancel_func_t cancel_func, 2533 void *cancel_baton, 2534 apr_pool_t *result_pool, apr_pool_t *scratch_pool) 2535{ 2536 patch_target_t *target; 2537 apr_pool_t *iterpool; 2538 int i; 2539 static const svn_linenum_t MAX_FUZZ = 2; 2540 apr_hash_index_t *hash_index; 2541 svn_linenum_t previous_offset = 0; 2542 apr_array_header_t *prop_targets; 2543 2544 SVN_ERR(init_patch_target(&target, patch, abs_wc_path, wc_ctx, strip_count, 2545 remove_tempfiles, targets_info, 2546 result_pool, scratch_pool)); 2547 if (target->skipped) 2548 { 2549 *patch_target = target; 2550 return SVN_NO_ERROR; 2551 } 2552 2553 iterpool = svn_pool_create(scratch_pool); 2554 2555 if (patch->hunks && patch->hunks->nelts) 2556 { 2557 /* Match hunks. */ 2558 for (i = 0; i < patch->hunks->nelts; i++) 2559 { 2560 svn_diff_hunk_t *hunk; 2561 hunk_info_t *hi; 2562 svn_linenum_t fuzz = 0; 2563 2564 svn_pool_clear(iterpool); 2565 2566 if (cancel_func) 2567 SVN_ERR(cancel_func(cancel_baton)); 2568 2569 hunk = APR_ARRAY_IDX(patch->hunks, i, svn_diff_hunk_t *); 2570 2571 /* Determine the line the hunk should be applied at. 2572 * If no match is found initially, try with fuzz. */ 2573 do 2574 { 2575 SVN_ERR(get_hunk_info(&hi, target, target->content, hunk, fuzz, 2576 previous_offset, 2577 ignore_whitespace, 2578 FALSE /* is_prop_hunk */, 2579 cancel_func, cancel_baton, 2580 result_pool, iterpool)); 2581 fuzz++; 2582 } 2583 while (hi->rejected && fuzz <= MAX_FUZZ && ! hi->already_applied); 2584 2585 if (hi->matched_line) 2586 previous_offset 2587 = hi->matched_line - svn_diff_hunk_get_original_start(hunk); 2588 2589 APR_ARRAY_PUSH(target->content->hunks, hunk_info_t *) = hi; 2590 } 2591 2592 /* Hunks are applied in the order determined by the matched line and 2593 this may be different from the order of the original lines. */ 2594 svn_sort__array(target->content->hunks, sort_matched_hunks); 2595 2596 /* Apply or reject hunks. */ 2597 for (i = 0; i < target->content->hunks->nelts; i++) 2598 { 2599 hunk_info_t *hi; 2600 2601 svn_pool_clear(iterpool); 2602 2603 if (cancel_func) 2604 SVN_ERR(cancel_func(cancel_baton)); 2605 2606 hi = APR_ARRAY_IDX(target->content->hunks, i, hunk_info_t *); 2607 if (hi->already_applied) 2608 { 2609 target->had_already_applied = TRUE; 2610 continue; 2611 } 2612 else if (hi->rejected) 2613 SVN_ERR(reject_hunk(target, target->content, hi->hunk, 2614 NULL /* prop_name */, 2615 iterpool)); 2616 else 2617 SVN_ERR(apply_hunk(target, target->content, hi, 2618 NULL /* prop_name */, iterpool)); 2619 } 2620 2621 if (target->kind_on_disk == svn_node_file) 2622 { 2623 /* Copy any remaining lines to target. */ 2624 SVN_ERR(copy_lines_to_target(target->content, 0, scratch_pool)); 2625 if (! target->content->eof) 2626 { 2627 /* We could not copy the entire target file to the temporary 2628 * file, and would truncate the target if we copied the 2629 * temporary file on top of it. Skip this target. */ 2630 target->skipped = TRUE; 2631 } 2632 } 2633 } 2634 else if (patch->binary_patch) 2635 { 2636 svn_stream_t *orig_stream; 2637 svn_boolean_t same; 2638 2639 if (target->file) 2640 orig_stream = svn_stream_from_aprfile2(target->file, TRUE, iterpool); 2641 else 2642 orig_stream = svn_stream_empty(iterpool); 2643 2644 SVN_ERR(svn_stream_contents_same2( 2645 &same, orig_stream, 2646 svn_diff_get_binary_diff_original_stream(patch->binary_patch, 2647 iterpool), 2648 iterpool)); 2649 svn_pool_clear(iterpool); 2650 2651 if (same) 2652 { 2653 /* The file in the working copy is identical to the one expected by 2654 the patch... So we can write the result stream; no fuzz, 2655 just a 100% match */ 2656 2657 target->has_text_changes = TRUE; 2658 } 2659 else 2660 { 2661 /* Perhaps the file is identical to the resulting version, implying 2662 that the patch has already been applied */ 2663 if (target->file) 2664 { 2665 apr_off_t start = 0; 2666 2667 SVN_ERR(svn_io_file_seek(target->file, APR_SET, &start, iterpool)); 2668 2669 orig_stream = svn_stream_from_aprfile2(target->file, TRUE, iterpool); 2670 } 2671 else 2672 orig_stream = svn_stream_empty(iterpool); 2673 2674 SVN_ERR(svn_stream_contents_same2( 2675 &same, orig_stream, 2676 svn_diff_get_binary_diff_result_stream(patch->binary_patch, 2677 iterpool), 2678 iterpool)); 2679 svn_pool_clear(iterpool); 2680 2681 if (same) 2682 target->had_already_applied = TRUE; 2683 } 2684 2685 if (same) 2686 { 2687 SVN_ERR(svn_stream_copy3( 2688 svn_diff_get_binary_diff_result_stream(patch->binary_patch, 2689 iterpool), 2690 svn_stream_from_aprfile2(target->patched_file, TRUE, 2691 iterpool), 2692 cancel_func, cancel_baton, 2693 iterpool)); 2694 } 2695 else 2696 { 2697 /* ### TODO: Implement a proper reject of a binary patch 2698 2699 This should at least setup things for a proper notification, 2700 and perhaps install a normal text conflict. Unlike normal unified 2701 diff based patches we have all the versions we would need for 2702 that in a much easier format than can be obtained from the patch 2703 file. */ 2704 target->skipped = TRUE; 2705 } 2706 } 2707 else if (target->move_target_abspath) 2708 { 2709 /* ### Why do we do this? 2710 BH: I don't know, but if we don't do this some tests 2711 on git style patches break. 2712 2713 ### It would be much better to really move the actual file instead 2714 of copying to a temporary file; move that to target and then 2715 delete the original file 2716 2717 ### BH: I have absolutely no idea if moving directories would work. 2718 */ 2719 if (target->kind_on_disk == svn_node_file) 2720 { 2721 /* Copy any remaining lines to target. (read: all lines) */ 2722 SVN_ERR(copy_lines_to_target(target->content, 0, scratch_pool)); 2723 if (!target->content->eof) 2724 { 2725 /* We could not copy the entire target file to the temporary 2726 * file, and would truncate the target if we copied the 2727 * temporary file on top of it. Skip this target. */ 2728 target->skipped = TRUE; 2729 } 2730 } 2731 } 2732 2733 if (target->had_rejects || target->locally_deleted) 2734 target->deleted = FALSE; 2735 2736 if (target->added 2737 && !(target->locally_deleted || target->db_kind == svn_node_none)) 2738 { 2739 target->added = FALSE; 2740 } 2741 2742 /* Assume nothing changed. Will be updated via property hunks */ 2743 target->is_special = target->is_symlink; 2744 2745 /* Match property hunks. */ 2746 for (hash_index = apr_hash_first(scratch_pool, patch->prop_patches); 2747 hash_index; 2748 hash_index = apr_hash_next(hash_index)) 2749 { 2750 svn_prop_patch_t *prop_patch; 2751 const char *prop_name; 2752 prop_patch_target_t *prop_target; 2753 2754 prop_name = apr_hash_this_key(hash_index); 2755 prop_patch = apr_hash_this_val(hash_index); 2756 2757 if (!strcmp(prop_name, SVN_PROP_SPECIAL)) 2758 target->is_special = (prop_patch->operation != svn_diff_op_deleted); 2759 2760 /* We'll store matched hunks in prop_content. */ 2761 prop_target = svn_hash_gets(target->prop_targets, prop_name); 2762 2763 for (i = 0; i < prop_patch->hunks->nelts; i++) 2764 { 2765 svn_diff_hunk_t *hunk; 2766 hunk_info_t *hi; 2767 svn_linenum_t fuzz = 0; 2768 2769 svn_pool_clear(iterpool); 2770 2771 if (cancel_func) 2772 SVN_ERR(cancel_func(cancel_baton)); 2773 2774 hunk = APR_ARRAY_IDX(prop_patch->hunks, i, svn_diff_hunk_t *); 2775 2776 /* Determine the line the hunk should be applied at. 2777 * If no match is found initially, try with fuzz. */ 2778 do 2779 { 2780 SVN_ERR(get_hunk_info(&hi, target, prop_target->content, 2781 hunk, fuzz, 0, 2782 ignore_whitespace, 2783 TRUE /* is_prop_hunk */, 2784 cancel_func, cancel_baton, 2785 result_pool, iterpool)); 2786 fuzz++; 2787 } 2788 while (hi->rejected && fuzz <= MAX_FUZZ && ! hi->already_applied); 2789 2790 APR_ARRAY_PUSH(prop_target->content->hunks, hunk_info_t *) = hi; 2791 } 2792 } 2793 2794 /* Match implied property hunks. */ 2795 if (patch->new_executable_bit != svn_tristate_unknown 2796 && patch->new_executable_bit != patch->old_executable_bit 2797 && svn_hash_gets(target->prop_targets, SVN_PROP_EXECUTABLE) 2798 && !svn_hash_gets(patch->prop_patches, SVN_PROP_EXECUTABLE)) 2799 { 2800 hunk_info_t *hi; 2801 svn_diff_hunk_t *hunk; 2802 prop_patch_target_t *prop_target = svn_hash_gets(target->prop_targets, 2803 SVN_PROP_EXECUTABLE); 2804 2805 if (patch->new_executable_bit == svn_tristate_true) 2806 SVN_ERR(svn_diff_hunk__create_adds_single_line( 2807 &hunk, 2808 SVN_PROP_EXECUTABLE_VALUE, 2809 patch, 2810 result_pool, 2811 iterpool)); 2812 else 2813 SVN_ERR(svn_diff_hunk__create_deletes_single_line( 2814 &hunk, 2815 SVN_PROP_EXECUTABLE_VALUE, 2816 patch, 2817 result_pool, 2818 iterpool)); 2819 2820 /* Derive a hunk_info from hunk. */ 2821 SVN_ERR(get_hunk_info(&hi, target, prop_target->content, 2822 hunk, 0 /* fuzz */, 0 /* previous_offset */, 2823 ignore_whitespace, 2824 TRUE /* is_prop_hunk */, 2825 cancel_func, cancel_baton, 2826 result_pool, iterpool)); 2827 APR_ARRAY_PUSH(prop_target->content->hunks, hunk_info_t *) = hi; 2828 } 2829 2830 if (patch->new_symlink_bit != svn_tristate_unknown 2831 && patch->new_symlink_bit != patch->old_symlink_bit 2832 && svn_hash_gets(target->prop_targets, SVN_PROP_SPECIAL) 2833 && !svn_hash_gets(patch->prop_patches, SVN_PROP_SPECIAL)) 2834 { 2835 hunk_info_t *hi; 2836 svn_diff_hunk_t *hunk; 2837 2838 prop_patch_target_t *prop_target = svn_hash_gets(target->prop_targets, 2839 SVN_PROP_SPECIAL); 2840 2841 if (patch->new_symlink_bit == svn_tristate_true) 2842 { 2843 SVN_ERR(svn_diff_hunk__create_adds_single_line( 2844 &hunk, 2845 SVN_PROP_SPECIAL_VALUE, 2846 patch, 2847 result_pool, 2848 iterpool)); 2849 target->is_special = TRUE; 2850 } 2851 else 2852 { 2853 SVN_ERR(svn_diff_hunk__create_deletes_single_line( 2854 &hunk, 2855 SVN_PROP_SPECIAL_VALUE, 2856 patch, 2857 result_pool, 2858 iterpool)); 2859 target->is_special = FALSE; 2860 } 2861 2862 /* Derive a hunk_info from hunk. */ 2863 SVN_ERR(get_hunk_info(&hi, target, prop_target->content, 2864 hunk, 0 /* fuzz */, 0 /* previous_offset */, 2865 ignore_whitespace, 2866 TRUE /* is_prop_hunk */, 2867 cancel_func, cancel_baton, 2868 result_pool, iterpool)); 2869 APR_ARRAY_PUSH(prop_target->content->hunks, hunk_info_t *) = hi; 2870 } 2871 2872 /* When the node is deleted or does not exist after the patch is applied 2873 we should reject a few more property hunks that can't be applied even 2874 though the source matched */ 2875 if (target->deleted 2876 || (!target->added && 2877 (target->locally_deleted || target->db_kind == svn_node_none))) 2878 { 2879 for (hash_index = apr_hash_first(scratch_pool, target->prop_targets); 2880 hash_index; 2881 hash_index = apr_hash_next(hash_index)) 2882 { 2883 prop_patch_target_t *prop_target = apr_hash_this_val(hash_index); 2884 2885 if (prop_target->operation == svn_diff_op_deleted) 2886 continue; 2887 2888 for (i = 0; i < prop_target->content->hunks->nelts; i++) 2889 { 2890 hunk_info_t *hi; 2891 2892 hi = APR_ARRAY_IDX(prop_target->content->hunks, i, hunk_info_t*); 2893 2894 if (hi->already_applied || hi->rejected) 2895 continue; 2896 else 2897 { 2898 hi->rejected = TRUE; 2899 prop_target->skipped = TRUE; 2900 2901 if (!target->deleted && !target->added) 2902 target->skipped = TRUE; 2903 } 2904 } 2905 } 2906 } 2907 2908 /* Apply or reject property hunks. */ 2909 2910 prop_targets = svn_sort__hash(target->prop_targets, 2911 svn_sort_compare_items_lexically, 2912 scratch_pool); 2913 for (i = 0; i < prop_targets->nelts; i++) 2914 { 2915 svn_sort__item_t item = APR_ARRAY_IDX(prop_targets, i, svn_sort__item_t); 2916 prop_patch_target_t *prop_target = item.value; 2917 svn_boolean_t applied_one = FALSE; 2918 int j; 2919 2920 for (j = 0; j < prop_target->content->hunks->nelts; j++) 2921 { 2922 hunk_info_t *hi; 2923 2924 svn_pool_clear(iterpool); 2925 2926 hi = APR_ARRAY_IDX(prop_target->content->hunks, j, 2927 hunk_info_t *); 2928 if (hi->already_applied) 2929 { 2930 target->had_prop_already_applied = TRUE; 2931 continue; 2932 } 2933 else if (hi->rejected) 2934 SVN_ERR(reject_hunk(target, prop_target->content, hi->hunk, 2935 prop_target->name, 2936 iterpool)); 2937 else 2938 { 2939 SVN_ERR(apply_hunk(target, prop_target->content, hi, 2940 prop_target->name, 2941 iterpool)); 2942 applied_one = TRUE; 2943 } 2944 } 2945 2946 if (!applied_one) 2947 prop_target->skipped = TRUE; 2948 2949 if (applied_one && prop_target->content->existed) 2950 { 2951 /* Copy any remaining lines to target. */ 2952 SVN_ERR(copy_lines_to_target(prop_target->content, 0, 2953 scratch_pool)); 2954 if (! prop_target->content->eof) 2955 { 2956 /* We could not copy the entire target property to the 2957 * temporary stream, and would truncate the target if we 2958 * copied the temporary stream on top of it. Skip this target. */ 2959 prop_target->skipped = TRUE; 2960 } 2961 } 2962 } 2963 2964 svn_pool_destroy(iterpool); 2965 2966 if (!target->is_symlink) 2967 { 2968 /* Now close files we don't need any longer to get their contents 2969 * flushed to disk. 2970 * But we're not closing the reject file -- it still needed and 2971 * will be closed later in write_out_rejected_hunks(). */ 2972 if (target->kind_on_disk == svn_node_file) 2973 SVN_ERR(svn_io_file_close(target->file, scratch_pool)); 2974 } 2975 2976 SVN_ERR(svn_io_file_close(target->patched_file, scratch_pool)); 2977 2978 *patch_target = target; 2979 2980 return SVN_NO_ERROR; 2981} 2982 2983/* Try to create missing parent directories for TARGET in the working copy 2984 * rooted at ABS_WC_PATH, and add the parents to version control. 2985 * If the parents cannot be created, mark the target as skipped. 2986 * 2987 * In dry run mode record missing parents in ALREADY_ADDED 2988 * 2989 * Use client context CTX. If DRY_RUN is true, do not create missing 2990 * parents but issue notifications only. 2991 * Use SCRATCH_POOL for temporary allocations. */ 2992static svn_error_t * 2993create_missing_parents(patch_target_t *target, 2994 const char *abs_wc_path, 2995 svn_client_ctx_t *ctx, 2996 svn_boolean_t dry_run, 2997 apr_array_header_t *targets_info, 2998 apr_pool_t *scratch_pool) 2999{ 3000 const char *local_abspath; 3001 apr_array_header_t *components; 3002 int present_components; 3003 int i; 3004 apr_pool_t *iterpool; 3005 3006 /* Check if we can safely create the target's parent. */ 3007 local_abspath = abs_wc_path; 3008 components = svn_path_decompose(target->local_relpath, scratch_pool); 3009 present_components = 0; 3010 iterpool = svn_pool_create(scratch_pool); 3011 for (i = 0; i < components->nelts - 1; i++) 3012 { 3013 const char *component; 3014 svn_node_kind_t wc_kind, disk_kind; 3015 3016 svn_pool_clear(iterpool); 3017 3018 component = APR_ARRAY_IDX(components, i, const char *); 3019 local_abspath = svn_dirent_join(local_abspath, component, scratch_pool); 3020 3021 SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, local_abspath, 3022 FALSE, TRUE, iterpool)); 3023 3024 SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, iterpool)); 3025 3026 if (disk_kind == svn_node_file || wc_kind == svn_node_file) 3027 { 3028 /* on-disk files and missing files are obstructions */ 3029 target->skipped = TRUE; 3030 break; 3031 } 3032 else if (disk_kind == svn_node_dir) 3033 { 3034 if (wc_kind == svn_node_dir) 3035 present_components++; 3036 else 3037 { 3038 target->skipped = TRUE; 3039 break; 3040 } 3041 } 3042 else if (wc_kind != svn_node_none) 3043 { 3044 /* Node is missing */ 3045 target->skipped = TRUE; 3046 break; 3047 } 3048 else 3049 { 3050 /* It's not a file, it's not a dir... 3051 Let's add a dir */ 3052 break; 3053 } 3054 } 3055 if (! target->skipped) 3056 { 3057 local_abspath = abs_wc_path; 3058 for (i = 0; i < present_components; i++) 3059 { 3060 const char *component; 3061 component = APR_ARRAY_IDX(components, i, const char *); 3062 local_abspath = svn_dirent_join(local_abspath, 3063 component, scratch_pool); 3064 } 3065 3066 if (!dry_run && present_components < components->nelts - 1) 3067 SVN_ERR(svn_io_make_dir_recursively( 3068 svn_dirent_join( 3069 abs_wc_path, 3070 svn_relpath_dirname(target->local_relpath, 3071 scratch_pool), 3072 scratch_pool), 3073 scratch_pool)); 3074 3075 for (i = present_components; i < components->nelts - 1; i++) 3076 { 3077 const char *component; 3078 patch_target_info_t *pti; 3079 3080 svn_pool_clear(iterpool); 3081 3082 if (ctx->cancel_func) 3083 SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 3084 3085 component = APR_ARRAY_IDX(components, i, const char *); 3086 local_abspath = svn_dirent_join(local_abspath, component, 3087 scratch_pool); 3088 3089 if (target_is_added(targets_info, local_abspath, iterpool)) 3090 continue; 3091 3092 pti = apr_pcalloc(targets_info->pool, sizeof(*pti)); 3093 3094 pti->local_abspath = apr_pstrdup(targets_info->pool, 3095 local_abspath); 3096 pti->added = TRUE; 3097 3098 APR_ARRAY_PUSH(targets_info, patch_target_info_t *) = pti; 3099 3100 if (dry_run) 3101 { 3102 if (ctx->notify_func2) 3103 { 3104 /* Just do notification. */ 3105 svn_wc_notify_t *notify; 3106 notify = svn_wc_create_notify(local_abspath, 3107 svn_wc_notify_add, 3108 iterpool); 3109 notify->kind = svn_node_dir; 3110 ctx->notify_func2(ctx->notify_baton2, notify, 3111 iterpool); 3112 } 3113 } 3114 else 3115 { 3116 /* Create the missing component and add it 3117 * to version control. Allow cancellation since we 3118 * have not modified the working copy yet for this 3119 * target. */ 3120 SVN_ERR(svn_wc_add_from_disk3(ctx->wc_ctx, local_abspath, 3121 NULL /*props*/, 3122 FALSE /* skip checks */, 3123 ctx->notify_func2, ctx->notify_baton2, 3124 iterpool)); 3125 } 3126 } 3127 } 3128 3129 svn_pool_destroy(iterpool); 3130 return SVN_NO_ERROR; 3131} 3132 3133/* Install a patched TARGET into the working copy at ABS_WC_PATH. 3134 * Use client context CTX to retrieve WC_CTX, and possibly doing 3135 * notifications. 3136 * 3137 * Pass on ALREADY_ADDED to allow recording already added ancestors 3138 * in dry-run mode. 3139 * 3140 * If DRY_RUN is TRUE, don't modify the working copy. 3141 * Do temporary allocations in POOL. */ 3142static svn_error_t * 3143install_patched_target(patch_target_t *target, const char *abs_wc_path, 3144 svn_client_ctx_t *ctx, svn_boolean_t dry_run, 3145 apr_array_header_t *targets_info, 3146 apr_pool_t *pool) 3147{ 3148 if (target->deleted) 3149 { 3150 if (! dry_run) 3151 { 3152 /* Schedule the target for deletion. Suppress 3153 * notification, we'll do it manually in a minute 3154 * because we also need to notify during dry-run. 3155 * Also suppress cancellation, because we'd rather 3156 * notify about what we did before aborting. */ 3157 SVN_ERR(svn_wc_delete4(ctx->wc_ctx, target->local_abspath, 3158 FALSE /* keep_local */, FALSE, 3159 ctx->cancel_func, ctx->cancel_baton, 3160 NULL, NULL /* notify */, 3161 pool)); 3162 } 3163 } 3164 else 3165 { 3166 svn_node_kind_t parent_db_kind; 3167 if (target->added) 3168 { 3169 const char *parent_abspath; 3170 3171 parent_abspath = svn_dirent_dirname(target->local_abspath, 3172 pool); 3173 /* If the target's parent directory does not yet exist 3174 * we need to create it before we can copy the patched 3175 * result in place. */ 3176 SVN_ERR(svn_wc_read_kind2(&parent_db_kind, ctx->wc_ctx, 3177 parent_abspath, FALSE, FALSE, pool)); 3178 3179 /* We can't add targets under nodes scheduled for delete, so add 3180 a new directory if needed. */ 3181 if (parent_db_kind == svn_node_dir 3182 || parent_db_kind == svn_node_file) 3183 { 3184 if (parent_db_kind != svn_node_dir) 3185 target->skipped = TRUE; 3186 else 3187 { 3188 svn_node_kind_t disk_kind; 3189 3190 SVN_ERR(svn_io_check_path(parent_abspath, &disk_kind, pool)); 3191 if (disk_kind != svn_node_dir) 3192 target->skipped = TRUE; 3193 } 3194 } 3195 else 3196 SVN_ERR(create_missing_parents(target, abs_wc_path, ctx, 3197 dry_run, targets_info, pool)); 3198 3199 } 3200 else 3201 { 3202 svn_node_kind_t wc_kind; 3203 3204 /* The target should exist */ 3205 SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, 3206 target->local_abspath, 3207 FALSE, FALSE, pool)); 3208 3209 if (target->kind_on_disk == svn_node_none 3210 || wc_kind != target->kind_on_disk) 3211 { 3212 target->skipped = TRUE; 3213 if (wc_kind != target->kind_on_disk) 3214 target->obstructed = TRUE; 3215 } 3216 } 3217 3218 if (! dry_run && ! target->skipped) 3219 { 3220 if (target->is_special) 3221 { 3222 svn_stream_t *stream; 3223 svn_stream_t *patched_stream; 3224 3225 SVN_ERR(svn_stream_open_readonly(&patched_stream, 3226 target->patched_path, 3227 pool, pool)); 3228 SVN_ERR(svn_subst_create_specialfile(&stream, 3229 target->local_abspath, 3230 pool, pool)); 3231 if (target->git_symlink_format) 3232 SVN_ERR(svn_stream_puts(stream, "link ")); 3233 SVN_ERR(svn_stream_copy3(patched_stream, stream, 3234 ctx->cancel_func, ctx->cancel_baton, 3235 pool)); 3236 } 3237 else 3238 { 3239 svn_boolean_t repair_eol; 3240 3241 /* Copy the patched file on top of the target file. 3242 * Always expand keywords in the patched file, but repair EOL 3243 * only if svn:eol-style dictates a particular style. */ 3244 repair_eol = (target->content->eol_style == 3245 svn_subst_eol_style_fixed || 3246 target->content->eol_style == 3247 svn_subst_eol_style_native); 3248 3249 SVN_ERR(svn_subst_copy_and_translate4( 3250 target->patched_path, 3251 target->move_target_abspath 3252 ? target->move_target_abspath 3253 : target->local_abspath, 3254 target->content->eol_str, repair_eol, 3255 target->content->keywords, 3256 TRUE /* expand */, FALSE /* special */, 3257 ctx->cancel_func, ctx->cancel_baton, pool)); 3258 } 3259 3260 if (target->added) 3261 { 3262 /* The target file didn't exist previously, 3263 * so add it to version control. 3264 * Suppress notification, we'll do that later (and also 3265 * during dry-run). Don't allow cancellation because 3266 * we'd rather notify about what we did before aborting. */ 3267 SVN_ERR(svn_wc_add_from_disk3(ctx->wc_ctx, target->local_abspath, 3268 NULL /*props*/, 3269 FALSE /* skip checks */, 3270 NULL, NULL, pool)); 3271 } 3272 3273 /* Restore the target's executable bit if necessary. */ 3274 SVN_ERR(svn_io_set_file_executable(target->move_target_abspath 3275 ? target->move_target_abspath 3276 : target->local_abspath, 3277 target->executable, 3278 FALSE, pool)); 3279 3280 if (target->move_target_abspath) 3281 { 3282 /* ### Copying the patched content to the move target location, 3283 * performing the move in meta-data, and removing the file at 3284 * the move source should be one atomic operation. */ 3285 3286 /* ### Create missing parents. */ 3287 3288 /* Perform the move in meta-data. */ 3289 SVN_ERR(svn_wc__move2(ctx->wc_ctx, 3290 target->local_abspath, 3291 target->move_target_abspath, 3292 TRUE, /* metadata_only */ 3293 FALSE, /* allow_mixed_revisions */ 3294 ctx->cancel_func, ctx->cancel_baton, 3295 NULL, NULL, 3296 pool)); 3297 3298 /* Delete the patch target's old location from disk. */ 3299 SVN_ERR(svn_io_remove_file2(target->local_abspath, FALSE, pool)); 3300 } 3301 } 3302 } 3303 3304 return SVN_NO_ERROR; 3305} 3306 3307/* Write out rejected hunks, if any, to TARGET->REJECT_PATH. If DRY_RUN is 3308 * TRUE, don't modify the working copy. 3309 * Do temporary allocations in POOL. 3310 */ 3311static svn_error_t * 3312write_out_rejected_hunks(patch_target_t *target, 3313 const char *root_abspath, 3314 svn_boolean_t dry_run, 3315 apr_pool_t *scratch_pool) 3316{ 3317 if (! dry_run && (target->had_rejects || target->had_prop_rejects)) 3318 { 3319 /* Write out rejected hunks, if any. */ 3320 apr_file_t *reject_file; 3321 svn_error_t *err; 3322 3323 err = svn_io_open_uniquely_named(&reject_file, NULL, 3324 svn_dirent_dirname(target->local_abspath, 3325 scratch_pool), 3326 svn_dirent_basename( 3327 target->local_abspath, 3328 NULL), 3329 ".svnpatch.rej", 3330 svn_io_file_del_none, 3331 scratch_pool, scratch_pool); 3332 if (err && APR_STATUS_IS_ENOENT(err->apr_err)) 3333 { 3334 /* The hunk applies to a file in a directory which does not exist. 3335 * Put the reject file into the working copy root instead. */ 3336 svn_error_clear(err); 3337 SVN_ERR(svn_io_open_uniquely_named(&reject_file, NULL, 3338 root_abspath, 3339 svn_dirent_basename( 3340 target->local_abspath, 3341 NULL), 3342 ".svnpatch.rej", 3343 svn_io_file_del_none, 3344 scratch_pool, scratch_pool)); 3345 } 3346 else 3347 SVN_ERR(err); 3348 3349 SVN_ERR(svn_stream_reset(target->reject_stream)); 3350 3351 /* svn_stream_copy3() closes the files for us */ 3352 SVN_ERR(svn_stream_copy3(target->reject_stream, 3353 svn_stream_from_aprfile2(reject_file, FALSE, 3354 scratch_pool), 3355 NULL, NULL, scratch_pool)); 3356 /* ### TODO mark file as conflicted. */ 3357 } 3358 else 3359 SVN_ERR(svn_stream_close(target->reject_stream)); 3360 3361 return SVN_NO_ERROR; 3362} 3363 3364/* Install the patched properties for TARGET. Use client context CTX to 3365 * retrieve WC_CTX. If DRY_RUN is TRUE, don't modify the working copy. 3366 * Do temporary allocations in SCRATCH_POOL. */ 3367static svn_error_t * 3368install_patched_prop_targets(patch_target_t *target, 3369 svn_client_ctx_t *ctx, svn_boolean_t dry_run, 3370 apr_pool_t *scratch_pool) 3371{ 3372 apr_hash_index_t *hi; 3373 apr_pool_t *iterpool; 3374 const char *local_abspath; 3375 3376 /* Apply properties to a move target if there is one */ 3377 if (target->move_target_abspath) 3378 local_abspath = target->move_target_abspath; 3379 else 3380 local_abspath = target->local_abspath; 3381 3382 iterpool = svn_pool_create(scratch_pool); 3383 3384 for (hi = apr_hash_first(scratch_pool, target->prop_targets); 3385 hi; 3386 hi = apr_hash_next(hi)) 3387 { 3388 prop_patch_target_t *prop_target = apr_hash_this_val(hi); 3389 const svn_string_t *prop_val; 3390 svn_error_t *err; 3391 3392 svn_pool_clear(iterpool); 3393 3394 if (ctx->cancel_func) 3395 SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 3396 3397 if (prop_target->skipped) 3398 continue; 3399 3400 /* For a deleted prop we only set the value to NULL. */ 3401 if (prop_target->operation == svn_diff_op_deleted) 3402 { 3403 if (! dry_run) 3404 SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath, 3405 prop_target->name, NULL, svn_depth_empty, 3406 TRUE /* skip_checks */, 3407 NULL /* changelist_filter */, 3408 NULL, NULL /* cancellation */, 3409 NULL, NULL /* notification */, 3410 iterpool)); 3411 continue; 3412 } 3413 3414 /* Attempt to set the property, and reject all hunks if this 3415 fails. If the property had a non-empty value, but now has 3416 an empty one, we'll just delete the property altogether. */ 3417 if (prop_target->value && prop_target->value->len 3418 && prop_target->patched_value && !prop_target->patched_value->len) 3419 prop_val = NULL; 3420 else 3421 prop_val = svn_stringbuf__morph_into_string(prop_target->patched_value); 3422 3423 if (dry_run) 3424 { 3425 const svn_string_t *canon_propval; 3426 3427 err = svn_wc_canonicalize_svn_prop(&canon_propval, 3428 prop_target->name, 3429 prop_val, local_abspath, 3430 target->db_kind, 3431 TRUE, /* ### Skipping checks */ 3432 NULL, NULL, 3433 iterpool); 3434 } 3435 else 3436 { 3437 err = svn_wc_prop_set4(ctx->wc_ctx, local_abspath, 3438 prop_target->name, prop_val, svn_depth_empty, 3439 TRUE /* skip_checks */, 3440 NULL /* changelist_filter */, 3441 NULL, NULL /* cancellation */, 3442 NULL, NULL /* notification */, 3443 iterpool); 3444 } 3445 3446 if (err) 3447 { 3448 /* ### The errors which svn_wc_canonicalize_svn_prop() will 3449 * ### return aren't documented. */ 3450 if (err->apr_err == SVN_ERR_ILLEGAL_TARGET || 3451 err->apr_err == SVN_ERR_NODE_UNEXPECTED_KIND || 3452 err->apr_err == SVN_ERR_IO_UNKNOWN_EOL || 3453 err->apr_err == SVN_ERR_BAD_MIME_TYPE || 3454 err->apr_err == SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION) 3455 { 3456 int i; 3457 3458 svn_error_clear(err); 3459 3460 for (i = 0; i < prop_target->content->hunks->nelts; i++) 3461 { 3462 hunk_info_t *hunk_info; 3463 3464 hunk_info = APR_ARRAY_IDX(prop_target->content->hunks, 3465 i, hunk_info_t *); 3466 hunk_info->rejected = TRUE; 3467 SVN_ERR(reject_hunk(target, prop_target->content, 3468 hunk_info->hunk, prop_target->name, 3469 iterpool)); 3470 } 3471 } 3472 else 3473 return svn_error_trace(err); 3474 } 3475 3476 } 3477 3478 svn_pool_destroy(iterpool); 3479 3480 return SVN_NO_ERROR; 3481} 3482 3483/* Baton for can_delete_callback */ 3484struct can_delete_baton_t 3485{ 3486 svn_boolean_t must_keep; 3487 const apr_array_header_t *targets_info; 3488 const char *local_abspath; 3489}; 3490 3491/* Implements svn_wc_status_func4_t. */ 3492static svn_error_t * 3493can_delete_callback(void *baton, 3494 const char *abspath, 3495 const svn_wc_status3_t *status, 3496 apr_pool_t *pool) 3497{ 3498 struct can_delete_baton_t *cb = baton; 3499 int i; 3500 3501 switch(status->node_status) 3502 { 3503 case svn_wc_status_none: 3504 case svn_wc_status_deleted: 3505 return SVN_NO_ERROR; 3506 3507 default: 3508 if (! strcmp(cb->local_abspath, abspath)) 3509 return SVN_NO_ERROR; /* Only interested in descendants */ 3510 3511 for (i = 0; i < cb->targets_info->nelts; i++) 3512 { 3513 const patch_target_info_t *target_info = 3514 APR_ARRAY_IDX(cb->targets_info, i, const patch_target_info_t *); 3515 3516 if (! strcmp(target_info->local_abspath, abspath)) 3517 { 3518 if (target_info->deleted) 3519 return SVN_NO_ERROR; 3520 3521 break; /* Cease invocation; must keep */ 3522 } 3523 } 3524 3525 cb->must_keep = TRUE; 3526 3527 return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL); 3528 } 3529} 3530 3531static svn_error_t * 3532check_ancestor_delete(const char *deleted_target, 3533 apr_array_header_t *targets_info, 3534 const char *apply_root, 3535 svn_boolean_t dry_run, 3536 svn_client_ctx_t *ctx, 3537 apr_pool_t *result_pool, 3538 apr_pool_t *scratch_pool) 3539{ 3540 struct can_delete_baton_t cb; 3541 svn_error_t *err; 3542 apr_array_header_t *ignores; 3543 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 3544 3545 const char *dir_abspath = svn_dirent_dirname(deleted_target, scratch_pool); 3546 3547 SVN_ERR(svn_wc_get_default_ignores(&ignores, ctx->config, scratch_pool)); 3548 3549 while (svn_dirent_is_child(apply_root, dir_abspath, iterpool)) 3550 { 3551 svn_pool_clear(iterpool); 3552 3553 cb.local_abspath = dir_abspath; 3554 cb.must_keep = FALSE; 3555 cb.targets_info = targets_info; 3556 3557 err = svn_wc_walk_status(ctx->wc_ctx, dir_abspath, svn_depth_infinity, 3558 TRUE, FALSE, FALSE, ignores, 3559 can_delete_callback, &cb, 3560 ctx->cancel_func, ctx->cancel_baton, 3561 iterpool); 3562 3563 if (err) 3564 { 3565 if (err->apr_err != SVN_ERR_CEASE_INVOCATION) 3566 return svn_error_trace(err); 3567 3568 svn_error_clear(err); 3569 } 3570 3571 if (cb.must_keep) 3572 { 3573 break; 3574 } 3575 3576 if (! dry_run) 3577 { 3578 SVN_ERR(svn_wc_delete4(ctx->wc_ctx, dir_abspath, FALSE, FALSE, 3579 ctx->cancel_func, ctx->cancel_baton, 3580 NULL, NULL, 3581 scratch_pool)); 3582 } 3583 3584 { 3585 patch_target_info_t *pti = apr_pcalloc(result_pool, sizeof(*pti)); 3586 3587 pti->local_abspath = apr_pstrdup(result_pool, dir_abspath); 3588 pti->deleted = TRUE; 3589 3590 APR_ARRAY_PUSH(targets_info, patch_target_info_t *) = pti; 3591 } 3592 3593 3594 if (ctx->notify_func2) 3595 { 3596 svn_wc_notify_t *notify; 3597 3598 notify = svn_wc_create_notify(dir_abspath, svn_wc_notify_delete, 3599 iterpool); 3600 notify->kind = svn_node_dir; 3601 3602 ctx->notify_func2(ctx->notify_baton2, notify, iterpool); 3603 } 3604 3605 /* And check if we must also delete the parent */ 3606 dir_abspath = svn_dirent_dirname(dir_abspath, scratch_pool); 3607 } 3608 3609 svn_pool_destroy(iterpool); 3610 3611 return SVN_NO_ERROR; 3612} 3613 3614/* This function is the main entry point into the patch code. */ 3615static svn_error_t * 3616apply_patches(/* The path to the patch file. */ 3617 const char *patch_abspath, 3618 /* The abspath to the working copy the patch should be applied to. */ 3619 const char *root_abspath, 3620 /* Indicates whether we're doing a dry run. */ 3621 svn_boolean_t dry_run, 3622 /* Number of leading components to strip from patch target paths. */ 3623 int strip_count, 3624 /* Whether to apply the patch in reverse. */ 3625 svn_boolean_t reverse, 3626 /* Whether to ignore whitespace when matching context lines. */ 3627 svn_boolean_t ignore_whitespace, 3628 /* As in svn_client_patch(). */ 3629 svn_boolean_t remove_tempfiles, 3630 /* As in svn_client_patch(). */ 3631 svn_client_patch_func_t patch_func, 3632 void *patch_baton, 3633 /* The client context. */ 3634 svn_client_ctx_t *ctx, 3635 apr_pool_t *scratch_pool) 3636{ 3637 svn_patch_t *patch; 3638 apr_pool_t *iterpool; 3639 svn_patch_file_t *patch_file; 3640 apr_array_header_t *targets_info; 3641 3642 /* Try to open the patch file. */ 3643 SVN_ERR(svn_diff_open_patch_file(&patch_file, patch_abspath, scratch_pool)); 3644 3645 /* Apply patches. */ 3646 targets_info = apr_array_make(scratch_pool, 0, 3647 sizeof(patch_target_info_t *)); 3648 iterpool = svn_pool_create(scratch_pool); 3649 do 3650 { 3651 svn_pool_clear(iterpool); 3652 3653 if (ctx->cancel_func) 3654 SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 3655 3656 SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file, 3657 reverse, ignore_whitespace, 3658 iterpool, iterpool)); 3659 if (patch) 3660 { 3661 patch_target_t *target; 3662 svn_boolean_t filtered = FALSE; 3663 3664 SVN_ERR(apply_one_patch(&target, patch, root_abspath, 3665 ctx->wc_ctx, strip_count, 3666 ignore_whitespace, remove_tempfiles, 3667 targets_info, 3668 ctx->cancel_func, ctx->cancel_baton, 3669 iterpool, iterpool)); 3670 3671 if (!target->skipped && patch_func) 3672 { 3673 SVN_ERR(patch_func(patch_baton, &filtered, 3674 target->canon_path_from_patchfile, 3675 target->patched_path, target->reject_path, 3676 iterpool)); 3677 } 3678 3679 if (! filtered) 3680 { 3681 /* Save info we'll still need when we're done patching. */ 3682 patch_target_info_t *target_info = 3683 apr_pcalloc(scratch_pool, sizeof(patch_target_info_t)); 3684 target_info->local_abspath = apr_pstrdup(scratch_pool, 3685 target->local_abspath); 3686 target_info->deleted = target->deleted; 3687 target_info->added = target->added; 3688 3689 if (! target->skipped) 3690 { 3691 if (target->has_text_changes 3692 || target->added 3693 || target->move_target_abspath 3694 || target->deleted) 3695 SVN_ERR(install_patched_target(target, root_abspath, 3696 ctx, dry_run, 3697 targets_info, iterpool)); 3698 3699 if (target->has_prop_changes && (!target->deleted)) 3700 SVN_ERR(install_patched_prop_targets(target, ctx, 3701 dry_run, iterpool)); 3702 3703 SVN_ERR(write_out_rejected_hunks(target, root_abspath, 3704 dry_run, iterpool)); 3705 3706 APR_ARRAY_PUSH(targets_info, 3707 patch_target_info_t *) = target_info; 3708 } 3709 SVN_ERR(send_patch_notification(target, ctx, iterpool)); 3710 3711 if (target->deleted && !target->skipped) 3712 { 3713 SVN_ERR(check_ancestor_delete(target_info->local_abspath, 3714 targets_info, root_abspath, 3715 dry_run, ctx, 3716 scratch_pool, iterpool)); 3717 } 3718 } 3719 } 3720 } 3721 while (patch); 3722 3723 SVN_ERR(svn_diff_close_patch_file(patch_file, iterpool)); 3724 svn_pool_destroy(iterpool); 3725 3726 return SVN_NO_ERROR; 3727} 3728 3729svn_error_t * 3730svn_client_patch(const char *patch_abspath, 3731 const char *wc_dir_abspath, 3732 svn_boolean_t dry_run, 3733 int strip_count, 3734 svn_boolean_t reverse, 3735 svn_boolean_t ignore_whitespace, 3736 svn_boolean_t remove_tempfiles, 3737 svn_client_patch_func_t patch_func, 3738 void *patch_baton, 3739 svn_client_ctx_t *ctx, 3740 apr_pool_t *scratch_pool) 3741{ 3742 svn_node_kind_t kind; 3743 3744 if (strip_count < 0) 3745 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, 3746 _("strip count must be positive")); 3747 3748 if (svn_path_is_url(wc_dir_abspath)) 3749 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 3750 _("'%s' is not a local path"), 3751 svn_dirent_local_style(wc_dir_abspath, 3752 scratch_pool)); 3753 3754 SVN_ERR(svn_io_check_path(patch_abspath, &kind, scratch_pool)); 3755 if (kind == svn_node_none) 3756 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 3757 _("'%s' does not exist"), 3758 svn_dirent_local_style(patch_abspath, 3759 scratch_pool)); 3760 if (kind != svn_node_file) 3761 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 3762 _("'%s' is not a file"), 3763 svn_dirent_local_style(patch_abspath, 3764 scratch_pool)); 3765 3766 SVN_ERR(svn_io_check_path(wc_dir_abspath, &kind, scratch_pool)); 3767 if (kind == svn_node_none) 3768 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 3769 _("'%s' does not exist"), 3770 svn_dirent_local_style(wc_dir_abspath, 3771 scratch_pool)); 3772 if (kind != svn_node_dir) 3773 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 3774 _("'%s' is not a directory"), 3775 svn_dirent_local_style(wc_dir_abspath, 3776 scratch_pool)); 3777 3778 SVN_WC__CALL_WITH_WRITE_LOCK( 3779 apply_patches(patch_abspath, wc_dir_abspath, dry_run, strip_count, 3780 reverse, ignore_whitespace, remove_tempfiles, 3781 patch_func, patch_baton, ctx, scratch_pool), 3782 ctx->wc_ctx, wc_dir_abspath, FALSE /* lock_anchor */, scratch_pool); 3783 return SVN_NO_ERROR; 3784} 3785