patch.c revision 289166
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_string_private.h" 50#include "private/svn_subr_private.h" 51 52typedef struct hunk_info_t { 53 /* The hunk. */ 54 svn_diff_hunk_t *hunk; 55 56 /* The line where the hunk matched in the target file. */ 57 svn_linenum_t matched_line; 58 59 /* Whether this hunk has been rejected. */ 60 svn_boolean_t rejected; 61 62 /* Whether this hunk has already been applied (either manually 63 * or by an earlier run of patch). */ 64 svn_boolean_t already_applied; 65 66 /* The fuzz factor used when matching this hunk, i.e. how many 67 * lines of leading and trailing context to ignore during matching. */ 68 svn_linenum_t fuzz; 69} hunk_info_t; 70 71/* A struct carrying information related to the patched and unpatched 72 * content of a target, be it a property or the text of a file. */ 73typedef struct target_content_t { 74 /* Indicates whether unpatched content existed prior to patching. */ 75 svn_boolean_t existed; 76 77 /* The line last read from the unpatched content. */ 78 svn_linenum_t current_line; 79 80 /* The EOL-style of the unpatched content. Either 'none', 'fixed', 81 * or 'native'. See the documentation of svn_subst_eol_style_t. */ 82 svn_subst_eol_style_t eol_style; 83 84 /* If the EOL_STYLE above is not 'none', this is the EOL string 85 * corresponding to the EOL-style. Else, it is the EOL string the 86 * last line read from the target file was using. */ 87 const char *eol_str; 88 89 /* An array containing apr_off_t offsets marking the beginning of 90 * each line in the unpatched content. */ 91 apr_array_header_t *lines; 92 93 /* An array containing hunk_info_t structures for hunks already matched. */ 94 apr_array_header_t *hunks; 95 96 /* True if end-of-file was reached while reading from the unpatched 97 * content. */ 98 svn_boolean_t eof; 99 100 /* The keywords of the target. They will be contracted when reading 101 * unpatched content and expanded when writing patched content. 102 * When patching properties this hash is always empty. */ 103 apr_hash_t *keywords; 104 105 /* A callback, with an associated baton, to read a line of unpatched 106 * content. */ 107 svn_error_t *(*readline)(void *baton, svn_stringbuf_t **line, 108 const char **eol_str, svn_boolean_t *eof, 109 apr_pool_t *result_pool, apr_pool_t *scratch_pool); 110 void *read_baton; 111 112 /* A callback to get the current byte offset within the unpatched 113 * content. Uses the read baton. */ 114 svn_error_t * (*tell)(void *baton, apr_off_t *offset, 115 apr_pool_t *scratch_pool); 116 117 /* A callback to seek to an offset within the unpatched content. 118 * Uses the read baton. */ 119 svn_error_t * (*seek)(void *baton, apr_off_t offset, 120 apr_pool_t *scratch_pool); 121 122 /* A callback to write data to the patched content, with an 123 * associated baton. */ 124 svn_error_t * (*write)(void *baton, const char *buf, apr_size_t len, 125 apr_pool_t *scratch_pool); 126 void *write_baton; 127 128} target_content_t; 129 130typedef struct prop_patch_target_t { 131 132 /* The name of the property */ 133 const char *name; 134 135 /* The property value. This is NULL in case the property did not exist 136 * prior to patch application (see also CONTENT->existed). 137 * Note that the patch implementation does not support binary properties, 138 * so this string is not expected to contain embedded NUL characters. */ 139 const svn_string_t *value; 140 141 /* The patched property value. 142 * This is equivalent to the target, except that in appropriate 143 * places it contains the modified text as it appears in the patch file. */ 144 svn_stringbuf_t *patched_value; 145 146 /* All information that is specific to the content of the property. */ 147 target_content_t *content; 148 149 /* Represents the operation performed on the property. It can be added, 150 * deleted or modified. 151 * ### Should we use flags instead since we're not using all enum values? */ 152 svn_diff_operation_kind_t operation; 153 154 /* ### Here we'll add flags telling if the prop was added, deleted, 155 * ### had_rejects, had_local_mods prior to patching and so on. */ 156} prop_patch_target_t; 157 158typedef struct patch_target_t { 159 /* The target path as it appeared in the patch file, 160 * but in canonicalised form. */ 161 const char *canon_path_from_patchfile; 162 163 /* The target path, relative to the working copy directory the 164 * patch is being applied to. A patch strip count applies to this 165 * and only this path. This is never NULL. */ 166 const char *local_relpath; 167 168 /* The absolute path of the target on the filesystem. 169 * Any symlinks the path from the patch file may contain are resolved. 170 * Is not always known, so it may be NULL. */ 171 const char *local_abspath; 172 173 /* The target file, read-only. This is NULL in case the target 174 * file did not exist prior to patch application (see also 175 * CONTENT->existed). */ 176 apr_file_t *file; 177 178 /* The target file is a symlink */ 179 svn_boolean_t is_symlink; 180 181 /* The patched file. 182 * This is equivalent to the target, except that in appropriate 183 * places it contains the modified text as it appears in the patch file. 184 * The data in this file is written in repository-normal form. 185 * EOL transformation and keyword contraction is performed when the 186 * patched result is installed in the working copy. */ 187 apr_file_t *patched_file; 188 189 /* Path to the patched file. */ 190 const char *patched_path; 191 192 /* Hunks that are rejected will be written to this file. */ 193 apr_file_t *reject_file; 194 195 /* Path to the reject file. */ 196 const char *reject_path; 197 198 /* The node kind of the target as found in WC-DB prior 199 * to patch application. */ 200 svn_node_kind_t db_kind; 201 202 /* The target's kind on disk prior to patch application. */ 203 svn_node_kind_t kind_on_disk; 204 205 /* True if the target was locally deleted prior to patching. */ 206 svn_boolean_t locally_deleted; 207 208 /* True if the target had to be skipped for some reason. */ 209 svn_boolean_t skipped; 210 211 /* True if the target has been filtered by the patch callback. */ 212 svn_boolean_t filtered; 213 214 /* True if at least one hunk was rejected. */ 215 svn_boolean_t had_rejects; 216 217 /* True if at least one property hunk was rejected. */ 218 svn_boolean_t had_prop_rejects; 219 220 /* True if the target file had local modifications before the 221 * patch was applied to it. */ 222 svn_boolean_t local_mods; 223 224 /* True if the target was added by the patch, which means that it did 225 * not exist on disk before patching and has content after patching. */ 226 svn_boolean_t added; 227 228 /* True if the target ended up being deleted by the patch. */ 229 svn_boolean_t deleted; 230 231 /* True if the target ended up being replaced by the patch 232 * (i.e. a new file was added on top locally deleted node). */ 233 svn_boolean_t replaced; 234 235 /* True if the target has the executable bit set. */ 236 svn_boolean_t executable; 237 238 /* True if the patch changed the text of the target. */ 239 svn_boolean_t has_text_changes; 240 241 /* True if the patch changed any of the properties of the target. */ 242 svn_boolean_t has_prop_changes; 243 244 /* True if the patch contained a svn:special property. */ 245 svn_boolean_t is_special; 246 247 /* All the information that is specific to the content of the target. */ 248 target_content_t *content; 249 250 /* A hash table of prop_patch_target_t objects keyed by property names. */ 251 apr_hash_t *prop_targets; 252 253} patch_target_t; 254 255 256/* A smaller struct containing a subset of patch_target_t. 257 * Carries the minimal amount of information we still need for a 258 * target after we're done patching it so we can free other resources. */ 259typedef struct patch_target_info_t { 260 const char *local_abspath; 261 svn_boolean_t deleted; 262} patch_target_info_t; 263 264 265/* Strip STRIP_COUNT components from the front of PATH, returning 266 * the result in *RESULT, allocated in RESULT_POOL. 267 * Do temporary allocations in SCRATCH_POOL. */ 268static svn_error_t * 269strip_path(const char **result, const char *path, int strip_count, 270 apr_pool_t *result_pool, apr_pool_t *scratch_pool) 271{ 272 int i; 273 apr_array_header_t *components; 274 apr_array_header_t *stripped; 275 276 components = svn_path_decompose(path, scratch_pool); 277 if (strip_count > components->nelts) 278 return svn_error_createf(SVN_ERR_CLIENT_PATCH_BAD_STRIP_COUNT, NULL, 279 _("Cannot strip %u components from '%s'"), 280 strip_count, 281 svn_dirent_local_style(path, scratch_pool)); 282 283 stripped = apr_array_make(scratch_pool, components->nelts - strip_count, 284 sizeof(const char *)); 285 for (i = strip_count; i < components->nelts; i++) 286 { 287 const char *component; 288 289 component = APR_ARRAY_IDX(components, i, const char *); 290 APR_ARRAY_PUSH(stripped, const char *) = component; 291 } 292 293 *result = svn_path_compose(stripped, result_pool); 294 295 return SVN_NO_ERROR; 296} 297 298/* Obtain KEYWORDS, EOL_STYLE and EOL_STR for LOCAL_ABSPATH. 299 * WC_CTX is a context for the working copy the patch is applied to. 300 * Use RESULT_POOL for allocations of fields in TARGET. 301 * Use SCRATCH_POOL for all other allocations. */ 302static svn_error_t * 303obtain_eol_and_keywords_for_file(apr_hash_t **keywords, 304 svn_subst_eol_style_t *eol_style, 305 const char **eol_str, 306 svn_wc_context_t *wc_ctx, 307 const char *local_abspath, 308 apr_pool_t *result_pool, 309 apr_pool_t *scratch_pool) 310{ 311 apr_hash_t *props; 312 svn_string_t *keywords_val, *eol_style_val; 313 314 SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, local_abspath, 315 scratch_pool, scratch_pool)); 316 keywords_val = svn_hash_gets(props, SVN_PROP_KEYWORDS); 317 if (keywords_val) 318 { 319 svn_revnum_t changed_rev; 320 apr_time_t changed_date; 321 const char *rev_str; 322 const char *author; 323 const char *url; 324 const char *root_url; 325 326 SVN_ERR(svn_wc__node_get_changed_info(&changed_rev, 327 &changed_date, 328 &author, wc_ctx, 329 local_abspath, 330 scratch_pool, 331 scratch_pool)); 332 rev_str = apr_psprintf(scratch_pool, "%ld", changed_rev); 333 SVN_ERR(svn_wc__node_get_url(&url, wc_ctx, 334 local_abspath, 335 scratch_pool, scratch_pool)); 336 SVN_ERR(svn_wc__node_get_repos_info(NULL, NULL, &root_url, NULL, 337 wc_ctx, local_abspath, 338 scratch_pool, scratch_pool)); 339 SVN_ERR(svn_subst_build_keywords3(keywords, 340 keywords_val->data, 341 rev_str, url, root_url, changed_date, 342 author, result_pool)); 343 } 344 345 eol_style_val = svn_hash_gets(props, SVN_PROP_EOL_STYLE); 346 if (eol_style_val) 347 { 348 svn_subst_eol_style_from_value(eol_style, 349 eol_str, 350 eol_style_val->data); 351 } 352 353 return SVN_NO_ERROR; 354} 355 356/* Resolve the exact path for a patch TARGET at path PATH_FROM_PATCHFILE, 357 * which is the path of the target as it appeared in the patch file. 358 * Put a canonicalized version of PATH_FROM_PATCHFILE into 359 * TARGET->CANON_PATH_FROM_PATCHFILE. 360 * WC_CTX is a context for the working copy the patch is applied to. 361 * If possible, determine TARGET->WC_PATH, TARGET->ABS_PATH, TARGET->KIND, 362 * TARGET->ADDED, and TARGET->PARENT_DIR_EXISTS. 363 * Indicate in TARGET->SKIPPED whether the target should be skipped. 364 * STRIP_COUNT specifies the number of leading path components 365 * which should be stripped from target paths in the patch. 366 * PROP_CHANGES_ONLY specifies whether the target path is allowed to have 367 * only property changes, and no content changes (in which case the target 368 * must be a directory). 369 * Use RESULT_POOL for allocations of fields in TARGET. 370 * Use SCRATCH_POOL for all other allocations. */ 371static svn_error_t * 372resolve_target_path(patch_target_t *target, 373 const char *path_from_patchfile, 374 const char *wcroot_abspath, 375 int strip_count, 376 svn_boolean_t prop_changes_only, 377 svn_wc_context_t *wc_ctx, 378 apr_pool_t *result_pool, 379 apr_pool_t *scratch_pool) 380{ 381 const char *stripped_path; 382 svn_wc_status3_t *status; 383 svn_error_t *err; 384 svn_boolean_t under_root; 385 386 target->canon_path_from_patchfile = svn_dirent_internal_style( 387 path_from_patchfile, result_pool); 388 389 /* We allow properties to be set on the wc root dir. */ 390 if (! prop_changes_only && target->canon_path_from_patchfile[0] == '\0') 391 { 392 /* An empty patch target path? What gives? Skip this. */ 393 target->skipped = TRUE; 394 target->local_abspath = NULL; 395 target->local_relpath = ""; 396 return SVN_NO_ERROR; 397 } 398 399 if (strip_count > 0) 400 SVN_ERR(strip_path(&stripped_path, target->canon_path_from_patchfile, 401 strip_count, result_pool, scratch_pool)); 402 else 403 stripped_path = target->canon_path_from_patchfile; 404 405 if (svn_dirent_is_absolute(stripped_path)) 406 { 407 target->local_relpath = svn_dirent_is_child(wcroot_abspath, 408 stripped_path, 409 result_pool); 410 411 if (! target->local_relpath) 412 { 413 /* The target path is either outside of the working copy 414 * or it is the working copy itself. Skip it. */ 415 target->skipped = TRUE; 416 target->local_abspath = NULL; 417 target->local_relpath = stripped_path; 418 return SVN_NO_ERROR; 419 } 420 } 421 else 422 { 423 target->local_relpath = stripped_path; 424 } 425 426 /* Make sure the path is secure to use. We want the target to be inside 427 * of the working copy and not be fooled by symlinks it might contain. */ 428 SVN_ERR(svn_dirent_is_under_root(&under_root, 429 &target->local_abspath, wcroot_abspath, 430 target->local_relpath, result_pool)); 431 432 if (! under_root) 433 { 434 /* The target path is outside of the working copy. Skip it. */ 435 target->skipped = TRUE; 436 target->local_abspath = NULL; 437 return SVN_NO_ERROR; 438 } 439 440 /* Skip things we should not be messing with. */ 441 err = svn_wc_status3(&status, wc_ctx, target->local_abspath, 442 result_pool, scratch_pool); 443 if (err) 444 { 445 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) 446 return svn_error_trace(err); 447 448 svn_error_clear(err); 449 450 target->locally_deleted = TRUE; 451 target->db_kind = svn_node_none; 452 status = NULL; 453 } 454 else if (status->node_status == svn_wc_status_ignored || 455 status->node_status == svn_wc_status_unversioned || 456 status->node_status == svn_wc_status_missing || 457 status->node_status == svn_wc_status_obstructed || 458 status->conflicted) 459 { 460 target->skipped = TRUE; 461 return SVN_NO_ERROR; 462 } 463 else if (status->node_status == svn_wc_status_deleted) 464 { 465 target->locally_deleted = TRUE; 466 } 467 468 if (status && (status->kind != svn_node_unknown)) 469 target->db_kind = status->kind; 470 else 471 target->db_kind = svn_node_none; 472 473 SVN_ERR(svn_io_check_special_path(target->local_abspath, 474 &target->kind_on_disk, &target->is_symlink, 475 scratch_pool)); 476 477 if (target->locally_deleted) 478 { 479 const char *moved_to_abspath; 480 481 SVN_ERR(svn_wc__node_was_moved_away(&moved_to_abspath, NULL, 482 wc_ctx, target->local_abspath, 483 result_pool, scratch_pool)); 484 if (moved_to_abspath) 485 { 486 target->local_abspath = moved_to_abspath; 487 target->local_relpath = svn_dirent_skip_ancestor(wcroot_abspath, 488 moved_to_abspath); 489 SVN_ERR_ASSERT(target->local_relpath && 490 target->local_relpath[0] != '\0'); 491 492 /* As far as we are concerned this target is not locally deleted. */ 493 target->locally_deleted = FALSE; 494 495 SVN_ERR(svn_io_check_special_path(target->local_abspath, 496 &target->kind_on_disk, 497 &target->is_symlink, 498 scratch_pool)); 499 } 500 else if (target->kind_on_disk != svn_node_none) 501 { 502 target->skipped = TRUE; 503 return SVN_NO_ERROR; 504 } 505 } 506 507 return SVN_NO_ERROR; 508} 509 510/* Baton for reading from properties. */ 511typedef struct prop_read_baton_t { 512 const svn_string_t *value; 513 apr_off_t offset; 514} prop_read_baton_t; 515 516/* Allocate *STRINGBUF in RESULT_POOL, and read into it one line from 517 * the unpatched property value accessed via BATON. 518 * Reading stops either after a line-terminator was found, or if 519 * the property value runs out in which case *EOF is set to TRUE. 520 * The line-terminator is not stored in *STRINGBUF. 521 * 522 * If the line is empty or could not be read, *line is set to NULL. 523 * 524 * The line-terminator is detected automatically and stored in *EOL 525 * if EOL is not NULL. If the end of the property value is reached 526 * and does not end with a newline character, and EOL is not NULL, 527 * *EOL is set to NULL. 528 * 529 * SCRATCH_POOL is used for temporary allocations. 530 */ 531static svn_error_t * 532readline_prop(void *baton, svn_stringbuf_t **line, const char **eol_str, 533 svn_boolean_t *eof, apr_pool_t *result_pool, 534 apr_pool_t *scratch_pool) 535{ 536 prop_read_baton_t *b = (prop_read_baton_t *)baton; 537 svn_stringbuf_t *str = NULL; 538 const char *c; 539 svn_boolean_t found_eof; 540 541 if ((apr_uint64_t)b->offset >= (apr_uint64_t)b->value->len) 542 { 543 *eol_str = NULL; 544 *eof = TRUE; 545 *line = NULL; 546 return SVN_NO_ERROR; 547 } 548 549 /* Read bytes into STR up to and including, but not storing, 550 * the next EOL sequence. */ 551 *eol_str = NULL; 552 found_eof = FALSE; 553 do 554 { 555 c = b->value->data + b->offset; 556 b->offset++; 557 558 if (*c == '\0') 559 { 560 found_eof = TRUE; 561 break; 562 } 563 else if (*c == '\n') 564 { 565 *eol_str = "\n"; 566 } 567 else if (*c == '\r') 568 { 569 *eol_str = "\r"; 570 if (*(c + 1) == '\n') 571 { 572 *eol_str = "\r\n"; 573 b->offset++; 574 } 575 } 576 else 577 { 578 if (str == NULL) 579 str = svn_stringbuf_create_ensure(80, result_pool); 580 svn_stringbuf_appendbyte(str, *c); 581 } 582 583 if (*eol_str) 584 break; 585 } 586 while (c < b->value->data + b->value->len); 587 588 if (eof) 589 *eof = found_eof; 590 *line = str; 591 592 return SVN_NO_ERROR; 593} 594 595/* Return in *OFFSET the current byte offset for reading from the 596 * unpatched property value accessed via BATON. 597 * Use SCRATCH_POOL for temporary allocations. */ 598static svn_error_t * 599tell_prop(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool) 600{ 601 prop_read_baton_t *b = (prop_read_baton_t *)baton; 602 *offset = b->offset; 603 return SVN_NO_ERROR; 604} 605 606/* Seek to the specified by OFFSET in the unpatched property value accessed 607 * via BATON. Use SCRATCH_POOL for temporary allocations. */ 608static svn_error_t * 609seek_prop(void *baton, apr_off_t offset, apr_pool_t *scratch_pool) 610{ 611 prop_read_baton_t *b = (prop_read_baton_t *)baton; 612 b->offset = offset; 613 return SVN_NO_ERROR; 614} 615 616/* Write LEN bytes from BUF into the patched property value accessed 617 * via BATON. Use SCRATCH_POOL for temporary allocations. */ 618static svn_error_t * 619write_prop(void *baton, const char *buf, apr_size_t len, 620 apr_pool_t *scratch_pool) 621{ 622 svn_stringbuf_t *patched_value = (svn_stringbuf_t *)baton; 623 svn_stringbuf_appendbytes(patched_value, buf, len); 624 return SVN_NO_ERROR; 625} 626 627/* Initialize a PROP_TARGET structure for PROP_NAME on the patch target 628 * at LOCAL_ABSPATH. OPERATION indicates the operation performed on the 629 * property. Use working copy context WC_CTX. 630 * Allocate results in RESULT_POOL. 631 * Use SCRATCH_POOL for temporary allocations. */ 632static svn_error_t * 633init_prop_target(prop_patch_target_t **prop_target, 634 const char *prop_name, 635 svn_diff_operation_kind_t operation, 636 svn_wc_context_t *wc_ctx, 637 const char *local_abspath, 638 apr_pool_t *result_pool, apr_pool_t *scratch_pool) 639{ 640 prop_patch_target_t *new_prop_target; 641 target_content_t *content; 642 const svn_string_t *value; 643 svn_error_t *err; 644 prop_read_baton_t *prop_read_baton; 645 646 content = apr_pcalloc(result_pool, sizeof(*content)); 647 648 /* All other fields are FALSE or NULL due to apr_pcalloc(). */ 649 content->current_line = 1; 650 content->eol_style = svn_subst_eol_style_none; 651 content->lines = apr_array_make(result_pool, 0, sizeof(apr_off_t)); 652 content->hunks = apr_array_make(result_pool, 0, sizeof(hunk_info_t *)); 653 content->keywords = apr_hash_make(result_pool); 654 655 new_prop_target = apr_pcalloc(result_pool, sizeof(*new_prop_target)); 656 new_prop_target->name = apr_pstrdup(result_pool, prop_name); 657 new_prop_target->operation = operation; 658 new_prop_target->content = content; 659 660 err = svn_wc_prop_get2(&value, wc_ctx, local_abspath, prop_name, 661 result_pool, scratch_pool); 662 if (err) 663 { 664 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) 665 { 666 svn_error_clear(err); 667 value = NULL; 668 } 669 else 670 return svn_error_trace(err); 671 } 672 content->existed = (value != NULL); 673 new_prop_target->value = value; 674 new_prop_target->patched_value = svn_stringbuf_create_empty(result_pool); 675 676 677 /* Wire up the read and write callbacks. */ 678 prop_read_baton = apr_pcalloc(result_pool, sizeof(*prop_read_baton)); 679 prop_read_baton->value = value; 680 prop_read_baton->offset = 0; 681 content->readline = readline_prop; 682 content->tell = tell_prop; 683 content->seek = seek_prop; 684 content->read_baton = prop_read_baton; 685 content->write = write_prop; 686 content->write_baton = new_prop_target->patched_value; 687 688 *prop_target = new_prop_target; 689 690 return SVN_NO_ERROR; 691} 692 693/* Allocate *STRINGBUF in RESULT_POOL, and read into it one line from 694 * the unpatched file content accessed via BATON. 695 * Reading stops either after a line-terminator was found, 696 * or if EOF is reached in which case *EOF is set to TRUE. 697 * The line-terminator is not stored in *STRINGBUF. 698 * 699 * If the line is empty or could not be read, *line is set to NULL. 700 * 701 * The line-terminator is detected automatically and stored in *EOL 702 * if EOL is not NULL. If EOF is reached and FILE does not end 703 * with a newline character, and EOL is not NULL, *EOL is set to NULL. 704 * 705 * SCRATCH_POOL is used for temporary allocations. 706 */ 707static svn_error_t * 708readline_file(void *baton, svn_stringbuf_t **line, const char **eol_str, 709 svn_boolean_t *eof, apr_pool_t *result_pool, 710 apr_pool_t *scratch_pool) 711{ 712 apr_file_t *file = (apr_file_t *)baton; 713 svn_stringbuf_t *str = NULL; 714 apr_size_t numbytes; 715 char c; 716 svn_boolean_t found_eof; 717 718 /* Read bytes into STR up to and including, but not storing, 719 * the next EOL sequence. */ 720 *eol_str = NULL; 721 numbytes = 1; 722 found_eof = FALSE; 723 while (!found_eof) 724 { 725 SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes, 726 &found_eof, scratch_pool)); 727 if (numbytes != 1) 728 { 729 found_eof = TRUE; 730 break; 731 } 732 733 if (c == '\n') 734 { 735 *eol_str = "\n"; 736 } 737 else if (c == '\r') 738 { 739 *eol_str = "\r"; 740 741 if (!found_eof) 742 { 743 apr_off_t pos; 744 745 /* Check for "\r\n" by peeking at the next byte. */ 746 pos = 0; 747 SVN_ERR(svn_io_file_seek(file, APR_CUR, &pos, scratch_pool)); 748 SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes, 749 &found_eof, scratch_pool)); 750 if (numbytes == 1 && c == '\n') 751 { 752 *eol_str = "\r\n"; 753 } 754 else 755 { 756 /* Pretend we never peeked. */ 757 SVN_ERR(svn_io_file_seek(file, APR_SET, &pos, scratch_pool)); 758 found_eof = FALSE; 759 numbytes = 1; 760 } 761 } 762 } 763 else 764 { 765 if (str == NULL) 766 str = svn_stringbuf_create_ensure(80, result_pool); 767 svn_stringbuf_appendbyte(str, c); 768 } 769 770 if (*eol_str) 771 break; 772 } 773 774 if (eof) 775 *eof = found_eof; 776 *line = str; 777 778 return SVN_NO_ERROR; 779} 780 781/* Return in *OFFSET the current byte offset for reading from the 782 * unpatched file content accessed via BATON. 783 * Use SCRATCH_POOL for temporary allocations. */ 784static svn_error_t * 785tell_file(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool) 786{ 787 apr_file_t *file = (apr_file_t *)baton; 788 *offset = 0; 789 SVN_ERR(svn_io_file_seek(file, APR_CUR, offset, scratch_pool)); 790 return SVN_NO_ERROR; 791} 792 793/* Seek to the specified by OFFSET in the unpatched file content accessed 794 * via BATON. Use SCRATCH_POOL for temporary allocations. */ 795static svn_error_t * 796seek_file(void *baton, apr_off_t offset, apr_pool_t *scratch_pool) 797{ 798 apr_file_t *file = (apr_file_t *)baton; 799 SVN_ERR(svn_io_file_seek(file, APR_SET, &offset, scratch_pool)); 800 return SVN_NO_ERROR; 801} 802 803/* Write LEN bytes from BUF into the patched file content accessed 804 * via BATON. Use SCRATCH_POOL for temporary allocations. */ 805static svn_error_t * 806write_file(void *baton, const char *buf, apr_size_t len, 807 apr_pool_t *scratch_pool) 808{ 809 apr_file_t *file = (apr_file_t *)baton; 810 SVN_ERR(svn_io_file_write_full(file, buf, len, &len, scratch_pool)); 811 return SVN_NO_ERROR; 812} 813 814/* Handling symbolic links: 815 * 816 * In Subversion, symlinks can be represented on disk in two distinct ways. 817 * On systems which support symlinks, a symlink is created on disk. 818 * On systems which do not support symlink, a file is created on disk 819 * which contains the "normal form" of the symlink, which looks like: 820 * link TARGET 821 * where TARGET is the file the symlink points to. 822 * 823 * When reading symlinks (i.e. the link itself, not the file the symlink 824 * is pointing to) through the svn_subst_create_specialfile() function 825 * into a buffer, the buffer always contains the "normal form" of the symlink. 826 * Due to this representation symlinks always contain a single line of text. 827 * 828 * The functions below are needed to deal with the case where a patch 829 * wants to change the TARGET that a symlink points to. 830 */ 831 832/* Baton for the (readline|tell|seek|write)_symlink functions. */ 833struct symlink_baton_t 834{ 835 /* The path to the symlink on disk (not the path to the target of the link) */ 836 const char *local_abspath; 837 838 /* Indicates whether the "normal form" of the symlink has been read. */ 839 svn_boolean_t at_eof; 840}; 841 842/* Allocate *STRINGBUF in RESULT_POOL, and store into it the "normal form" 843 * of the symlink accessed via BATON. 844 * 845 * Otherwise behaves like readline_file(), which see. 846 */ 847static svn_error_t * 848readline_symlink(void *baton, svn_stringbuf_t **line, const char **eol_str, 849 svn_boolean_t *eof, apr_pool_t *result_pool, 850 apr_pool_t *scratch_pool) 851{ 852 struct symlink_baton_t *sb = baton; 853 854 if (eof) 855 *eof = TRUE; 856 if (eol_str) 857 *eol_str = NULL; 858 859 if (sb->at_eof) 860 { 861 *line = NULL; 862 } 863 else 864 { 865 svn_string_t *dest; 866 867 SVN_ERR(svn_io_read_link(&dest, sb->local_abspath, scratch_pool)); 868 *line = svn_stringbuf_createf(result_pool, "link %s", dest->data); 869 sb->at_eof = TRUE; 870 } 871 872 return SVN_NO_ERROR; 873} 874 875/* Set *OFFSET to 1 or 0 depending on whether the "normal form" of 876 * the symlink has already been read. */ 877static svn_error_t * 878tell_symlink(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool) 879{ 880 struct symlink_baton_t *sb = baton; 881 882 *offset = sb->at_eof ? 1 : 0; 883 return SVN_NO_ERROR; 884} 885 886/* If offset is non-zero, mark the symlink as having been read in its 887 * "normal form". Else, mark the symlink as not having been read yet. */ 888static svn_error_t * 889seek_symlink(void *baton, apr_off_t offset, apr_pool_t *scratch_pool) 890{ 891 struct symlink_baton_t *sb = baton; 892 893 sb->at_eof = (offset != 0); 894 return SVN_NO_ERROR; 895} 896 897 898/* Set the target of the symlink accessed via BATON. 899 * The contents of BUF must be a valid "normal form" of a symlink. */ 900static svn_error_t * 901write_symlink(void *baton, const char *buf, apr_size_t len, 902 apr_pool_t *scratch_pool) 903{ 904 const char *target_abspath = baton; 905 const char *new_name; 906 const char *link = apr_pstrndup(scratch_pool, buf, len); 907 908 if (strncmp(link, "link ", 5) != 0) 909 return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, 910 _("Invalid link representation")); 911 912 link += 5; /* Skip "link " */ 913 914 /* We assume the entire symlink is written at once, as the patch 915 format is line based */ 916 917 SVN_ERR(svn_io_create_unique_link(&new_name, target_abspath, link, 918 ".tmp", scratch_pool)); 919 920 SVN_ERR(svn_io_file_rename(new_name, target_abspath, scratch_pool)); 921 922 return SVN_NO_ERROR; 923} 924 925 926/* Return a suitable filename for the target of PATCH. 927 * Examine the ``old'' and ``new'' file names, and choose the file name 928 * with the fewest path components, the shortest basename, and the shortest 929 * total file name length (in that order). In case of a tie, return the new 930 * filename. This heuristic is also used by Larry Wall's UNIX patch (except 931 * that it prompts for a filename in case of a tie). 932 * Additionally, for compatibility with git, if one of the filenames 933 * is "/dev/null", use the other filename. */ 934static const char * 935choose_target_filename(const svn_patch_t *patch) 936{ 937 apr_size_t old; 938 apr_size_t new; 939 940 if (strcmp(patch->old_filename, "/dev/null") == 0) 941 return patch->new_filename; 942 if (strcmp(patch->new_filename, "/dev/null") == 0) 943 return patch->old_filename; 944 945 old = svn_path_component_count(patch->old_filename); 946 new = svn_path_component_count(patch->new_filename); 947 948 if (old == new) 949 { 950 old = strlen(svn_dirent_basename(patch->old_filename, NULL)); 951 new = strlen(svn_dirent_basename(patch->new_filename, NULL)); 952 953 if (old == new) 954 { 955 old = strlen(patch->old_filename); 956 new = strlen(patch->new_filename); 957 } 958 } 959 960 return (old < new) ? patch->old_filename : patch->new_filename; 961} 962 963/* Attempt to initialize a *PATCH_TARGET structure for a target file 964 * described by PATCH. Use working copy context WC_CTX. 965 * STRIP_COUNT specifies the number of leading path components 966 * which should be stripped from target paths in the patch. 967 * The patch target structure is allocated in RESULT_POOL, but if the target 968 * should be skipped, PATCH_TARGET->SKIPPED is set and the target should be 969 * treated as not fully initialized, e.g. the caller should not not do any 970 * further operations on the target if it is marked to be skipped. 971 * If REMOVE_TEMPFILES is TRUE, set up temporary files to be removed as 972 * soon as they are no longer needed. 973 * Use SCRATCH_POOL for all other allocations. */ 974static svn_error_t * 975init_patch_target(patch_target_t **patch_target, 976 const svn_patch_t *patch, 977 const char *wcroot_abspath, 978 svn_wc_context_t *wc_ctx, int strip_count, 979 svn_boolean_t remove_tempfiles, 980 apr_pool_t *result_pool, apr_pool_t *scratch_pool) 981{ 982 patch_target_t *target; 983 target_content_t *content; 984 svn_boolean_t has_prop_changes = FALSE; 985 svn_boolean_t prop_changes_only = FALSE; 986 987 { 988 apr_hash_index_t *hi; 989 990 for (hi = apr_hash_first(scratch_pool, patch->prop_patches); 991 hi; 992 hi = apr_hash_next(hi)) 993 { 994 svn_prop_patch_t *prop_patch = svn__apr_hash_index_val(hi); 995 if (! has_prop_changes) 996 has_prop_changes = prop_patch->hunks->nelts > 0; 997 else 998 break; 999 } 1000 } 1001 1002 prop_changes_only = has_prop_changes && patch->hunks->nelts == 0; 1003 1004 content = apr_pcalloc(result_pool, sizeof(*content)); 1005 1006 /* All other fields in content are FALSE or NULL due to apr_pcalloc().*/ 1007 content->current_line = 1; 1008 content->eol_style = svn_subst_eol_style_none; 1009 content->lines = apr_array_make(result_pool, 0, sizeof(apr_off_t)); 1010 content->hunks = apr_array_make(result_pool, 0, sizeof(hunk_info_t *)); 1011 content->keywords = apr_hash_make(result_pool); 1012 1013 target = apr_pcalloc(result_pool, sizeof(*target)); 1014 1015 /* All other fields in target are FALSE or NULL due to apr_pcalloc(). */ 1016 target->db_kind = svn_node_none; 1017 target->kind_on_disk = svn_node_none; 1018 target->content = content; 1019 target->prop_targets = apr_hash_make(result_pool); 1020 1021 SVN_ERR(resolve_target_path(target, choose_target_filename(patch), 1022 wcroot_abspath, strip_count, prop_changes_only, 1023 wc_ctx, result_pool, scratch_pool)); 1024 if (! target->skipped) 1025 { 1026 const char *diff_header; 1027 apr_size_t len; 1028 1029 /* Create a temporary file to write the patched result to. 1030 * Also grab various bits of information about the file. */ 1031 if (target->is_symlink) 1032 { 1033 struct symlink_baton_t *sb = apr_pcalloc(result_pool, sizeof(*sb)); 1034 content->existed = TRUE; 1035 1036 sb->local_abspath = target->local_abspath; 1037 1038 /* Wire up the read callbacks. */ 1039 content->read_baton = sb; 1040 1041 content->readline = readline_symlink; 1042 content->seek = seek_symlink; 1043 content->tell = tell_symlink; 1044 } 1045 else if (target->kind_on_disk == svn_node_file) 1046 { 1047 SVN_ERR(svn_io_file_open(&target->file, target->local_abspath, 1048 APR_READ | APR_BUFFERED, 1049 APR_OS_DEFAULT, result_pool)); 1050 SVN_ERR(svn_wc_text_modified_p2(&target->local_mods, wc_ctx, 1051 target->local_abspath, FALSE, 1052 scratch_pool)); 1053 SVN_ERR(svn_io_is_file_executable(&target->executable, 1054 target->local_abspath, 1055 scratch_pool)); 1056 SVN_ERR(obtain_eol_and_keywords_for_file(&content->keywords, 1057 &content->eol_style, 1058 &content->eol_str, 1059 wc_ctx, 1060 target->local_abspath, 1061 result_pool, 1062 scratch_pool)); 1063 content->existed = TRUE; 1064 1065 /* Wire up the read callbacks. */ 1066 content->readline = readline_file; 1067 content->seek = seek_file; 1068 content->tell = tell_file; 1069 content->read_baton = target->file; 1070 } 1071 1072 /* ### Is it ok to set the operation of the target already here? Isn't 1073 * ### the target supposed to be marked with an operation after we have 1074 * ### determined that the changes will apply cleanly to the WC? Maybe 1075 * ### we should have kept the patch field in patch_target_t to be 1076 * ### able to distinguish between 'what the patch says we should do' 1077 * ### and 'what we can do with the given state of our WC'. */ 1078 if (patch->operation == svn_diff_op_added) 1079 target->added = TRUE; 1080 else if (patch->operation == svn_diff_op_deleted) 1081 target->deleted = TRUE; 1082 1083 if (! target->is_symlink) 1084 { 1085 /* Open a temporary file to write the patched result to. */ 1086 SVN_ERR(svn_io_open_unique_file3(&target->patched_file, 1087 &target->patched_path, NULL, 1088 remove_tempfiles ? 1089 svn_io_file_del_on_pool_cleanup : 1090 svn_io_file_del_none, 1091 result_pool, scratch_pool)); 1092 1093 /* Put the write callback in place. */ 1094 content->write = write_file; 1095 content->write_baton = target->patched_file; 1096 } 1097 else 1098 { 1099 /* Put the write callback in place. */ 1100 SVN_ERR(svn_io_open_unique_file3(NULL, 1101 &target->patched_path, NULL, 1102 remove_tempfiles ? 1103 svn_io_file_del_on_pool_cleanup : 1104 svn_io_file_del_none, 1105 result_pool, scratch_pool)); 1106 1107 content->write_baton = (void*)target->patched_path; 1108 1109 content->write = write_symlink; 1110 } 1111 1112 /* Open a temporary file to write rejected hunks to. */ 1113 SVN_ERR(svn_io_open_unique_file3(&target->reject_file, 1114 &target->reject_path, NULL, 1115 remove_tempfiles ? 1116 svn_io_file_del_on_pool_cleanup : 1117 svn_io_file_del_none, 1118 result_pool, scratch_pool)); 1119 1120 /* The reject file needs a diff header. */ 1121 diff_header = apr_psprintf(scratch_pool, "--- %s%s+++ %s%s", 1122 target->canon_path_from_patchfile, 1123 APR_EOL_STR, 1124 target->canon_path_from_patchfile, 1125 APR_EOL_STR); 1126 len = strlen(diff_header); 1127 SVN_ERR(svn_io_file_write_full(target->reject_file, diff_header, len, 1128 &len, scratch_pool)); 1129 1130 /* Handle properties. */ 1131 if (! target->skipped) 1132 { 1133 apr_hash_index_t *hi; 1134 1135 for (hi = apr_hash_first(result_pool, patch->prop_patches); 1136 hi; 1137 hi = apr_hash_next(hi)) 1138 { 1139 const char *prop_name = svn__apr_hash_index_key(hi); 1140 svn_prop_patch_t *prop_patch = svn__apr_hash_index_val(hi); 1141 prop_patch_target_t *prop_target; 1142 1143 SVN_ERR(init_prop_target(&prop_target, 1144 prop_name, 1145 prop_patch->operation, 1146 wc_ctx, target->local_abspath, 1147 result_pool, scratch_pool)); 1148 svn_hash_sets(target->prop_targets, prop_name, prop_target); 1149 } 1150 } 1151 } 1152 1153 *patch_target = target; 1154 return SVN_NO_ERROR; 1155} 1156 1157/* Read a *LINE from CONTENT. If the line has not been read before 1158 * mark the line in CONTENT->LINES. 1159 * If a line could be read successfully, increase CONTENT->CURRENT_LINE, 1160 * and allocate *LINE in RESULT_POOL. 1161 * Do temporary allocations in SCRATCH_POOL. 1162 */ 1163static svn_error_t * 1164readline(target_content_t *content, 1165 const char **line, 1166 apr_pool_t *result_pool, 1167 apr_pool_t *scratch_pool) 1168{ 1169 svn_stringbuf_t *line_raw; 1170 const char *eol_str; 1171 svn_linenum_t max_line = (svn_linenum_t)content->lines->nelts + 1; 1172 1173 if (content->eof || content->readline == NULL) 1174 { 1175 *line = ""; 1176 return SVN_NO_ERROR; 1177 } 1178 1179 SVN_ERR_ASSERT(content->current_line <= max_line); 1180 if (content->current_line == max_line) 1181 { 1182 apr_off_t offset; 1183 1184 SVN_ERR(content->tell(content->read_baton, &offset, 1185 scratch_pool)); 1186 APR_ARRAY_PUSH(content->lines, apr_off_t) = offset; 1187 } 1188 1189 SVN_ERR(content->readline(content->read_baton, &line_raw, 1190 &eol_str, &content->eof, 1191 result_pool, scratch_pool)); 1192 if (content->eol_style == svn_subst_eol_style_none) 1193 content->eol_str = eol_str; 1194 1195 if (line_raw) 1196 { 1197 /* Contract keywords. */ 1198 SVN_ERR(svn_subst_translate_cstring2(line_raw->data, line, 1199 NULL, FALSE, 1200 content->keywords, FALSE, 1201 result_pool)); 1202 } 1203 else 1204 *line = ""; 1205 1206 if ((line_raw && line_raw->len > 0) || eol_str) 1207 content->current_line++; 1208 1209 SVN_ERR_ASSERT(content->current_line > 0); 1210 1211 return SVN_NO_ERROR; 1212} 1213 1214/* Seek to the specified LINE in CONTENT. 1215 * Mark any lines not read before in CONTENT->LINES. 1216 * Do temporary allocations in SCRATCH_POOL. 1217 */ 1218static svn_error_t * 1219seek_to_line(target_content_t *content, svn_linenum_t line, 1220 apr_pool_t *scratch_pool) 1221{ 1222 svn_linenum_t saved_line; 1223 svn_boolean_t saved_eof; 1224 1225 SVN_ERR_ASSERT(line > 0); 1226 1227 if (line == content->current_line) 1228 return SVN_NO_ERROR; 1229 1230 saved_line = content->current_line; 1231 saved_eof = content->eof; 1232 1233 if (line <= (svn_linenum_t)content->lines->nelts) 1234 { 1235 apr_off_t offset; 1236 1237 offset = APR_ARRAY_IDX(content->lines, line - 1, apr_off_t); 1238 SVN_ERR(content->seek(content->read_baton, offset, 1239 scratch_pool)); 1240 content->current_line = line; 1241 } 1242 else 1243 { 1244 const char *dummy; 1245 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1246 1247 while (! content->eof && content->current_line < line) 1248 { 1249 svn_pool_clear(iterpool); 1250 SVN_ERR(readline(content, &dummy, iterpool, iterpool)); 1251 } 1252 svn_pool_destroy(iterpool); 1253 } 1254 1255 /* After seeking backwards from EOF position clear EOF indicator. */ 1256 if (saved_eof && saved_line > content->current_line) 1257 content->eof = FALSE; 1258 1259 return SVN_NO_ERROR; 1260} 1261 1262/* Indicate in *MATCHED whether the original text of HUNK matches the patch 1263 * CONTENT at its current line. Lines within FUZZ lines of the start or 1264 * end of HUNK will always match. If IGNORE_WHITESPACE is set, we ignore 1265 * whitespace when doing the matching. When this function returns, neither 1266 * CONTENT->CURRENT_LINE nor the file offset in the target file will 1267 * have changed. If MATCH_MODIFIED is TRUE, match the modified hunk text, 1268 * rather than the original hunk text. 1269 * Do temporary allocations in POOL. */ 1270static svn_error_t * 1271match_hunk(svn_boolean_t *matched, target_content_t *content, 1272 svn_diff_hunk_t *hunk, svn_linenum_t fuzz, 1273 svn_boolean_t ignore_whitespace, 1274 svn_boolean_t match_modified, apr_pool_t *pool) 1275{ 1276 svn_stringbuf_t *hunk_line; 1277 const char *target_line; 1278 svn_linenum_t lines_read; 1279 svn_linenum_t saved_line; 1280 svn_boolean_t hunk_eof; 1281 svn_boolean_t lines_matched; 1282 apr_pool_t *iterpool; 1283 svn_linenum_t hunk_length; 1284 svn_linenum_t leading_context; 1285 svn_linenum_t trailing_context; 1286 1287 *matched = FALSE; 1288 1289 if (content->eof) 1290 return SVN_NO_ERROR; 1291 1292 saved_line = content->current_line; 1293 lines_read = 0; 1294 lines_matched = FALSE; 1295 leading_context = svn_diff_hunk_get_leading_context(hunk); 1296 trailing_context = svn_diff_hunk_get_trailing_context(hunk); 1297 if (match_modified) 1298 { 1299 svn_diff_hunk_reset_modified_text(hunk); 1300 hunk_length = svn_diff_hunk_get_modified_length(hunk); 1301 } 1302 else 1303 { 1304 svn_diff_hunk_reset_original_text(hunk); 1305 hunk_length = svn_diff_hunk_get_original_length(hunk); 1306 } 1307 iterpool = svn_pool_create(pool); 1308 do 1309 { 1310 const char *hunk_line_translated; 1311 1312 svn_pool_clear(iterpool); 1313 1314 if (match_modified) 1315 SVN_ERR(svn_diff_hunk_readline_modified_text(hunk, &hunk_line, 1316 NULL, &hunk_eof, 1317 iterpool, iterpool)); 1318 else 1319 SVN_ERR(svn_diff_hunk_readline_original_text(hunk, &hunk_line, 1320 NULL, &hunk_eof, 1321 iterpool, iterpool)); 1322 1323 /* Contract keywords, if any, before matching. */ 1324 SVN_ERR(svn_subst_translate_cstring2(hunk_line->data, 1325 &hunk_line_translated, 1326 NULL, FALSE, 1327 content->keywords, FALSE, 1328 iterpool)); 1329 SVN_ERR(readline(content, &target_line, iterpool, iterpool)); 1330 1331 lines_read++; 1332 1333 /* If the last line doesn't have a newline, we get EOF but still 1334 * have a non-empty line to compare. */ 1335 if ((hunk_eof && hunk_line->len == 0) || 1336 (content->eof && *target_line == 0)) 1337 break; 1338 1339 /* Leading/trailing fuzzy lines always match. */ 1340 if ((lines_read <= fuzz && leading_context > fuzz) || 1341 (lines_read > hunk_length - fuzz && trailing_context > fuzz)) 1342 lines_matched = TRUE; 1343 else 1344 { 1345 if (ignore_whitespace) 1346 { 1347 char *hunk_line_trimmed; 1348 char *target_line_trimmed; 1349 1350 hunk_line_trimmed = apr_pstrdup(iterpool, hunk_line_translated); 1351 target_line_trimmed = apr_pstrdup(iterpool, target_line); 1352 apr_collapse_spaces(hunk_line_trimmed, hunk_line_trimmed); 1353 apr_collapse_spaces(target_line_trimmed, target_line_trimmed); 1354 lines_matched = ! strcmp(hunk_line_trimmed, target_line_trimmed); 1355 } 1356 else 1357 lines_matched = ! strcmp(hunk_line_translated, target_line); 1358 } 1359 } 1360 while (lines_matched); 1361 1362 *matched = lines_matched && hunk_eof && hunk_line->len == 0; 1363 SVN_ERR(seek_to_line(content, saved_line, iterpool)); 1364 svn_pool_destroy(iterpool); 1365 1366 return SVN_NO_ERROR; 1367} 1368 1369/* Scan lines of CONTENT for a match of the original text of HUNK, 1370 * up to but not including the specified UPPER_LINE. Use fuzz factor FUZZ. 1371 * If UPPER_LINE is zero scan until EOF occurs when reading from TARGET. 1372 * Return the line at which HUNK was matched in *MATCHED_LINE. 1373 * If the hunk did not match at all, set *MATCHED_LINE to zero. 1374 * If the hunk matched multiple times, and MATCH_FIRST is TRUE, 1375 * return the line number at which the first match occurred in *MATCHED_LINE. 1376 * If the hunk matched multiple times, and MATCH_FIRST is FALSE, 1377 * return the line number at which the last match occurred in *MATCHED_LINE. 1378 * If IGNORE_WHITESPACE is set, ignore whitespace during the matching. 1379 * If MATCH_MODIFIED is TRUE, match the modified hunk text, 1380 * rather than the original hunk text. 1381 * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation. 1382 * Do all allocations in POOL. */ 1383static svn_error_t * 1384scan_for_match(svn_linenum_t *matched_line, 1385 target_content_t *content, 1386 svn_diff_hunk_t *hunk, svn_boolean_t match_first, 1387 svn_linenum_t upper_line, svn_linenum_t fuzz, 1388 svn_boolean_t ignore_whitespace, 1389 svn_boolean_t match_modified, 1390 svn_cancel_func_t cancel_func, void *cancel_baton, 1391 apr_pool_t *pool) 1392{ 1393 apr_pool_t *iterpool; 1394 1395 *matched_line = 0; 1396 iterpool = svn_pool_create(pool); 1397 while ((content->current_line < upper_line || upper_line == 0) && 1398 ! content->eof) 1399 { 1400 svn_boolean_t matched; 1401 1402 svn_pool_clear(iterpool); 1403 1404 if (cancel_func) 1405 SVN_ERR(cancel_func(cancel_baton)); 1406 1407 SVN_ERR(match_hunk(&matched, content, hunk, fuzz, ignore_whitespace, 1408 match_modified, iterpool)); 1409 if (matched) 1410 { 1411 svn_boolean_t taken = FALSE; 1412 int i; 1413 1414 /* Don't allow hunks to match at overlapping locations. */ 1415 for (i = 0; i < content->hunks->nelts; i++) 1416 { 1417 const hunk_info_t *hi; 1418 svn_linenum_t length; 1419 1420 hi = APR_ARRAY_IDX(content->hunks, i, const hunk_info_t *); 1421 1422 if (match_modified) 1423 length = svn_diff_hunk_get_modified_length(hi->hunk); 1424 else 1425 length = svn_diff_hunk_get_original_length(hi->hunk); 1426 1427 taken = (! hi->rejected && 1428 content->current_line >= hi->matched_line && 1429 content->current_line < (hi->matched_line + length)); 1430 if (taken) 1431 break; 1432 } 1433 1434 if (! taken) 1435 { 1436 *matched_line = content->current_line; 1437 if (match_first) 1438 break; 1439 } 1440 } 1441 1442 if (! content->eof) 1443 SVN_ERR(seek_to_line(content, content->current_line + 1, 1444 iterpool)); 1445 } 1446 svn_pool_destroy(iterpool); 1447 1448 return SVN_NO_ERROR; 1449} 1450 1451/* Indicate in *MATCH whether the content described by CONTENT 1452 * matches the modified text of HUNK. 1453 * Use SCRATCH_POOL for temporary allocations. */ 1454static svn_error_t * 1455match_existing_target(svn_boolean_t *match, 1456 target_content_t *content, 1457 svn_diff_hunk_t *hunk, 1458 apr_pool_t *scratch_pool) 1459{ 1460 svn_boolean_t lines_matched; 1461 apr_pool_t *iterpool; 1462 svn_boolean_t hunk_eof; 1463 svn_linenum_t saved_line; 1464 1465 svn_diff_hunk_reset_modified_text(hunk); 1466 1467 saved_line = content->current_line; 1468 1469 iterpool = svn_pool_create(scratch_pool); 1470 do 1471 { 1472 const char *line; 1473 svn_stringbuf_t *hunk_line; 1474 const char *line_translated; 1475 const char *hunk_line_translated; 1476 1477 svn_pool_clear(iterpool); 1478 1479 SVN_ERR(readline(content, &line, iterpool, iterpool)); 1480 SVN_ERR(svn_diff_hunk_readline_modified_text(hunk, &hunk_line, 1481 NULL, &hunk_eof, 1482 iterpool, iterpool)); 1483 /* Contract keywords. */ 1484 SVN_ERR(svn_subst_translate_cstring2(line, &line_translated, 1485 NULL, FALSE, 1486 content->keywords, 1487 FALSE, iterpool)); 1488 SVN_ERR(svn_subst_translate_cstring2(hunk_line->data, 1489 &hunk_line_translated, 1490 NULL, FALSE, 1491 content->keywords, 1492 FALSE, iterpool)); 1493 lines_matched = ! strcmp(line_translated, hunk_line_translated); 1494 if (content->eof != hunk_eof) 1495 { 1496 svn_pool_destroy(iterpool); 1497 *match = FALSE; 1498 return SVN_NO_ERROR; 1499 } 1500 } 1501 while (lines_matched && ! content->eof && ! hunk_eof); 1502 svn_pool_destroy(iterpool); 1503 1504 *match = (lines_matched && content->eof == hunk_eof); 1505 SVN_ERR(seek_to_line(content, saved_line, scratch_pool)); 1506 1507 return SVN_NO_ERROR; 1508} 1509 1510/* Determine the line at which a HUNK applies to CONTENT of the TARGET 1511 * file, and return an appropriate hunk_info object in *HI, allocated from 1512 * RESULT_POOL. Use fuzz factor FUZZ. Set HI->FUZZ to FUZZ. If no correct 1513 * line can be determined, set HI->REJECTED to TRUE. 1514 * IGNORE_WHITESPACE tells whether whitespace should be considered when 1515 * matching. IS_PROP_HUNK indicates whether the hunk patches file content 1516 * or a property. 1517 * When this function returns, neither CONTENT->CURRENT_LINE nor 1518 * the file offset in the target file will have changed. 1519 * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation. 1520 * Do temporary allocations in POOL. */ 1521static svn_error_t * 1522get_hunk_info(hunk_info_t **hi, patch_target_t *target, 1523 target_content_t *content, 1524 svn_diff_hunk_t *hunk, svn_linenum_t fuzz, 1525 svn_boolean_t ignore_whitespace, 1526 svn_boolean_t is_prop_hunk, 1527 svn_cancel_func_t cancel_func, void *cancel_baton, 1528 apr_pool_t *result_pool, apr_pool_t *scratch_pool) 1529{ 1530 svn_linenum_t matched_line; 1531 svn_linenum_t original_start; 1532 svn_boolean_t already_applied; 1533 1534 original_start = svn_diff_hunk_get_original_start(hunk); 1535 already_applied = FALSE; 1536 1537 /* An original offset of zero means that this hunk wants to create 1538 * a new file. Don't bother matching hunks in that case, since 1539 * the hunk applies at line 1. If the file already exists, the hunk 1540 * is rejected, unless the file is versioned and its content matches 1541 * the file the patch wants to create. */ 1542 if (original_start == 0 && fuzz > 0) 1543 { 1544 matched_line = 0; /* reject any fuzz for new files */ 1545 } 1546 else if (original_start == 0 && ! is_prop_hunk) 1547 { 1548 if (target->kind_on_disk == svn_node_file) 1549 { 1550 const svn_io_dirent2_t *dirent; 1551 SVN_ERR(svn_io_stat_dirent2(&dirent, target->local_abspath, FALSE, 1552 TRUE, scratch_pool, scratch_pool)); 1553 1554 if (dirent->kind == svn_node_file 1555 && !dirent->special 1556 && dirent->filesize == 0) 1557 { 1558 matched_line = 1; /* Matched an on-disk empty file */ 1559 } 1560 else 1561 { 1562 if (target->db_kind == svn_node_file) 1563 { 1564 svn_boolean_t file_matches; 1565 1566 /* ### I can't reproduce anything but a no-match here. 1567 The content is already at eof, so any hunk fails */ 1568 SVN_ERR(match_existing_target(&file_matches, content, hunk, 1569 scratch_pool)); 1570 if (file_matches) 1571 { 1572 matched_line = 1; 1573 already_applied = TRUE; 1574 } 1575 else 1576 matched_line = 0; /* reject */ 1577 } 1578 else 1579 matched_line = 0; /* reject */ 1580 } 1581 } 1582 else 1583 matched_line = 1; 1584 } 1585 /* Same conditions apply as for the file case above. 1586 * 1587 * ### Since the hunk says the prop should be added we just assume so for 1588 * ### now and don't bother with storing the previous lines and such. When 1589 * ### we have the diff operation available we can just check for adds. */ 1590 else if (original_start == 0 && is_prop_hunk) 1591 { 1592 if (content->existed) 1593 { 1594 svn_boolean_t prop_matches; 1595 1596 SVN_ERR(match_existing_target(&prop_matches, content, hunk, 1597 scratch_pool)); 1598 1599 if (prop_matches) 1600 { 1601 matched_line = 1; 1602 already_applied = TRUE; 1603 } 1604 else 1605 matched_line = 0; /* reject */ 1606 } 1607 else 1608 matched_line = 1; 1609 } 1610 else if (original_start > 0 && content->existed) 1611 { 1612 svn_linenum_t saved_line = content->current_line; 1613 1614 /* Scan for a match at the line where the hunk thinks it 1615 * should be going. */ 1616 SVN_ERR(seek_to_line(content, original_start, scratch_pool)); 1617 if (content->current_line != original_start) 1618 { 1619 /* Seek failed. */ 1620 matched_line = 0; 1621 } 1622 else 1623 SVN_ERR(scan_for_match(&matched_line, content, hunk, TRUE, 1624 original_start + 1, fuzz, 1625 ignore_whitespace, FALSE, 1626 cancel_func, cancel_baton, 1627 scratch_pool)); 1628 1629 if (matched_line != original_start) 1630 { 1631 /* Check if the hunk is already applied. 1632 * We only check for an exact match here, and don't bother checking 1633 * for already applied patches with offset/fuzz, because such a 1634 * check would be ambiguous. */ 1635 if (fuzz == 0) 1636 { 1637 svn_linenum_t modified_start; 1638 1639 modified_start = svn_diff_hunk_get_modified_start(hunk); 1640 if (modified_start == 0) 1641 { 1642 /* Patch wants to delete the file. */ 1643 already_applied = target->locally_deleted; 1644 } 1645 else 1646 { 1647 SVN_ERR(seek_to_line(content, modified_start, 1648 scratch_pool)); 1649 SVN_ERR(scan_for_match(&matched_line, content, 1650 hunk, TRUE, 1651 modified_start + 1, 1652 fuzz, ignore_whitespace, TRUE, 1653 cancel_func, cancel_baton, 1654 scratch_pool)); 1655 already_applied = (matched_line == modified_start); 1656 } 1657 } 1658 else 1659 already_applied = FALSE; 1660 1661 if (! already_applied) 1662 { 1663 /* Scan the whole file again from the start. */ 1664 SVN_ERR(seek_to_line(content, 1, scratch_pool)); 1665 1666 /* Scan forward towards the hunk's line and look for a line 1667 * where the hunk matches. */ 1668 SVN_ERR(scan_for_match(&matched_line, content, hunk, FALSE, 1669 original_start, fuzz, 1670 ignore_whitespace, FALSE, 1671 cancel_func, cancel_baton, 1672 scratch_pool)); 1673 1674 /* In tie-break situations, we arbitrarily prefer early matches 1675 * to save us from scanning the rest of the file. */ 1676 if (matched_line == 0) 1677 { 1678 /* Scan forward towards the end of the file and look 1679 * for a line where the hunk matches. */ 1680 SVN_ERR(scan_for_match(&matched_line, content, hunk, 1681 TRUE, 0, fuzz, ignore_whitespace, 1682 FALSE, cancel_func, cancel_baton, 1683 scratch_pool)); 1684 } 1685 } 1686 } 1687 1688 SVN_ERR(seek_to_line(content, saved_line, scratch_pool)); 1689 } 1690 else 1691 { 1692 /* The hunk wants to modify a file which doesn't exist. */ 1693 matched_line = 0; 1694 } 1695 1696 (*hi) = apr_pcalloc(result_pool, sizeof(hunk_info_t)); 1697 (*hi)->hunk = hunk; 1698 (*hi)->matched_line = matched_line; 1699 (*hi)->rejected = (matched_line == 0); 1700 (*hi)->already_applied = already_applied; 1701 (*hi)->fuzz = fuzz; 1702 1703 return SVN_NO_ERROR; 1704} 1705 1706/* Copy lines to the patched content until the specified LINE has been 1707 * reached. Indicate in *EOF whether end-of-file was encountered while 1708 * reading from the target. 1709 * If LINE is zero, copy lines until end-of-file has been reached. 1710 * Do all allocations in POOL. */ 1711static svn_error_t * 1712copy_lines_to_target(target_content_t *content, svn_linenum_t line, 1713 apr_pool_t *pool) 1714{ 1715 apr_pool_t *iterpool; 1716 1717 iterpool = svn_pool_create(pool); 1718 while ((content->current_line < line || line == 0) && ! content->eof) 1719 { 1720 const char *target_line; 1721 apr_size_t len; 1722 1723 svn_pool_clear(iterpool); 1724 1725 SVN_ERR(readline(content, &target_line, iterpool, iterpool)); 1726 if (! content->eof) 1727 target_line = apr_pstrcat(iterpool, target_line, content->eol_str, 1728 (char *)NULL); 1729 len = strlen(target_line); 1730 SVN_ERR(content->write(content->write_baton, target_line, 1731 len, iterpool)); 1732 } 1733 svn_pool_destroy(iterpool); 1734 1735 return SVN_NO_ERROR; 1736} 1737 1738/* Write the diff text of HUNK to TARGET's reject file, 1739 * and mark TARGET as having had rejects. 1740 * We don't expand keywords, nor normalise line-endings, in reject files. 1741 * Do temporary allocations in SCRATCH_POOL. */ 1742static svn_error_t * 1743reject_hunk(patch_target_t *target, target_content_t *content, 1744 svn_diff_hunk_t *hunk, const char *prop_name, 1745 apr_pool_t *pool) 1746{ 1747 const char *hunk_header; 1748 apr_size_t len; 1749 svn_boolean_t eof; 1750 static const char * const text_atat = "@@"; 1751 static const char * const prop_atat = "##"; 1752 const char *atat; 1753 apr_pool_t *iterpool; 1754 1755 if (prop_name) 1756 { 1757 const char *prop_header; 1758 1759 /* ### Print 'Added', 'Deleted' or 'Modified' instead of 'Property'. 1760 */ 1761 prop_header = apr_psprintf(pool, "Property: %s\n", prop_name); 1762 len = strlen(prop_header); 1763 SVN_ERR(svn_io_file_write_full(target->reject_file, prop_header, 1764 len, &len, pool)); 1765 atat = prop_atat; 1766 } 1767 else 1768 { 1769 atat = text_atat; 1770 } 1771 1772 hunk_header = apr_psprintf(pool, "%s -%lu,%lu +%lu,%lu %s%s", 1773 atat, 1774 svn_diff_hunk_get_original_start(hunk), 1775 svn_diff_hunk_get_original_length(hunk), 1776 svn_diff_hunk_get_modified_start(hunk), 1777 svn_diff_hunk_get_modified_length(hunk), 1778 atat, 1779 APR_EOL_STR); 1780 len = strlen(hunk_header); 1781 SVN_ERR(svn_io_file_write_full(target->reject_file, hunk_header, len, 1782 &len, pool)); 1783 1784 iterpool = svn_pool_create(pool); 1785 do 1786 { 1787 svn_stringbuf_t *hunk_line; 1788 const char *eol_str; 1789 1790 svn_pool_clear(iterpool); 1791 1792 SVN_ERR(svn_diff_hunk_readline_diff_text(hunk, &hunk_line, &eol_str, 1793 &eof, iterpool, iterpool)); 1794 if (! eof) 1795 { 1796 if (hunk_line->len >= 1) 1797 { 1798 len = hunk_line->len; 1799 SVN_ERR(svn_io_file_write_full(target->reject_file, 1800 hunk_line->data, len, &len, 1801 iterpool)); 1802 } 1803 1804 if (eol_str) 1805 { 1806 len = strlen(eol_str); 1807 SVN_ERR(svn_io_file_write_full(target->reject_file, eol_str, 1808 len, &len, iterpool)); 1809 } 1810 } 1811 } 1812 while (! eof); 1813 svn_pool_destroy(iterpool); 1814 1815 if (prop_name) 1816 target->had_prop_rejects = TRUE; 1817 else 1818 target->had_rejects = TRUE; 1819 1820 return SVN_NO_ERROR; 1821} 1822 1823/* Write the modified text of the hunk described by HI to the patched 1824 * CONTENT. TARGET is the patch target. 1825 * If PROP_NAME is not NULL, the hunk is assumed to be targeted for 1826 * a property with the given name. 1827 * Do temporary allocations in POOL. */ 1828static svn_error_t * 1829apply_hunk(patch_target_t *target, target_content_t *content, 1830 hunk_info_t *hi, const char *prop_name, apr_pool_t *pool) 1831{ 1832 svn_linenum_t lines_read; 1833 svn_boolean_t eof; 1834 apr_pool_t *iterpool; 1835 1836 /* ### Is there a cleaner way to describe if we have an existing target? 1837 */ 1838 if (target->kind_on_disk == svn_node_file || prop_name) 1839 { 1840 svn_linenum_t line; 1841 1842 /* Move forward to the hunk's line, copying data as we go. 1843 * Also copy leading lines of context which matched with fuzz. 1844 * The target has changed on the fuzzy-matched lines, 1845 * so we should retain the target's version of those lines. */ 1846 SVN_ERR(copy_lines_to_target(content, hi->matched_line + hi->fuzz, 1847 pool)); 1848 1849 /* Skip the target's version of the hunk. 1850 * Don't skip trailing lines which matched with fuzz. */ 1851 line = content->current_line + 1852 svn_diff_hunk_get_original_length(hi->hunk) - (2 * hi->fuzz); 1853 SVN_ERR(seek_to_line(content, line, pool)); 1854 if (content->current_line != line && ! content->eof) 1855 { 1856 /* Seek failed, reject this hunk. */ 1857 hi->rejected = TRUE; 1858 SVN_ERR(reject_hunk(target, content, hi->hunk, prop_name, pool)); 1859 return SVN_NO_ERROR; 1860 } 1861 } 1862 1863 /* Write the hunk's version to the patched result. 1864 * Don't write the lines which matched with fuzz. */ 1865 lines_read = 0; 1866 svn_diff_hunk_reset_modified_text(hi->hunk); 1867 iterpool = svn_pool_create(pool); 1868 do 1869 { 1870 svn_stringbuf_t *hunk_line; 1871 const char *eol_str; 1872 1873 svn_pool_clear(iterpool); 1874 1875 SVN_ERR(svn_diff_hunk_readline_modified_text(hi->hunk, &hunk_line, 1876 &eol_str, &eof, 1877 iterpool, iterpool)); 1878 lines_read++; 1879 if (lines_read > hi->fuzz && 1880 lines_read <= svn_diff_hunk_get_modified_length(hi->hunk) - hi->fuzz) 1881 { 1882 apr_size_t len; 1883 1884 if (hunk_line->len >= 1) 1885 { 1886 len = hunk_line->len; 1887 SVN_ERR(content->write(content->write_baton, 1888 hunk_line->data, len, iterpool)); 1889 } 1890 1891 if (eol_str) 1892 { 1893 /* Use the EOL as it was read from the patch file, 1894 * unless the target's EOL style is set by svn:eol-style */ 1895 if (content->eol_style != svn_subst_eol_style_none) 1896 eol_str = content->eol_str; 1897 1898 len = strlen(eol_str); 1899 SVN_ERR(content->write(content->write_baton, 1900 eol_str, len, iterpool)); 1901 } 1902 } 1903 } 1904 while (! eof); 1905 svn_pool_destroy(iterpool); 1906 1907 if (prop_name) 1908 target->has_prop_changes = TRUE; 1909 else 1910 target->has_text_changes = TRUE; 1911 1912 return SVN_NO_ERROR; 1913} 1914 1915/* Use client context CTX to send a suitable notification for hunk HI, 1916 * using TARGET to determine the path. If the hunk is a property hunk, 1917 * PROP_NAME must be the name of the property, else NULL. 1918 * Use POOL for temporary allocations. */ 1919static svn_error_t * 1920send_hunk_notification(const hunk_info_t *hi, 1921 const patch_target_t *target, 1922 const char *prop_name, 1923 const svn_client_ctx_t *ctx, 1924 apr_pool_t *pool) 1925{ 1926 svn_wc_notify_t *notify; 1927 svn_wc_notify_action_t action; 1928 1929 if (hi->already_applied) 1930 action = svn_wc_notify_patch_hunk_already_applied; 1931 else if (hi->rejected) 1932 action = svn_wc_notify_patch_rejected_hunk; 1933 else 1934 action = svn_wc_notify_patch_applied_hunk; 1935 1936 notify = svn_wc_create_notify(target->local_abspath 1937 ? target->local_abspath 1938 : target->local_relpath, 1939 action, pool); 1940 notify->hunk_original_start = 1941 svn_diff_hunk_get_original_start(hi->hunk); 1942 notify->hunk_original_length = 1943 svn_diff_hunk_get_original_length(hi->hunk); 1944 notify->hunk_modified_start = 1945 svn_diff_hunk_get_modified_start(hi->hunk); 1946 notify->hunk_modified_length = 1947 svn_diff_hunk_get_modified_length(hi->hunk); 1948 notify->hunk_matched_line = hi->matched_line; 1949 notify->hunk_fuzz = hi->fuzz; 1950 notify->prop_name = prop_name; 1951 1952 (*ctx->notify_func2)(ctx->notify_baton2, notify, pool); 1953 1954 return SVN_NO_ERROR; 1955} 1956 1957/* Use client context CTX to send a suitable notification for a patch TARGET. 1958 * Use POOL for temporary allocations. */ 1959static svn_error_t * 1960send_patch_notification(const patch_target_t *target, 1961 const svn_client_ctx_t *ctx, 1962 apr_pool_t *pool) 1963{ 1964 svn_wc_notify_t *notify; 1965 svn_wc_notify_action_t action; 1966 1967 if (! ctx->notify_func2) 1968 return SVN_NO_ERROR; 1969 1970 if (target->skipped) 1971 action = svn_wc_notify_skip; 1972 else if (target->deleted) 1973 action = svn_wc_notify_delete; 1974 else if (target->added || target->replaced) 1975 action = svn_wc_notify_add; 1976 else 1977 action = svn_wc_notify_patch; 1978 1979 notify = svn_wc_create_notify(target->local_abspath ? target->local_abspath 1980 : target->local_relpath, 1981 action, pool); 1982 notify->kind = svn_node_file; 1983 1984 if (action == svn_wc_notify_skip) 1985 { 1986 if (target->db_kind == svn_node_none || 1987 target->db_kind == svn_node_unknown) 1988 notify->content_state = svn_wc_notify_state_missing; 1989 else if (target->db_kind == svn_node_dir) 1990 notify->content_state = svn_wc_notify_state_obstructed; 1991 else 1992 notify->content_state = svn_wc_notify_state_unknown; 1993 } 1994 else 1995 { 1996 if (target->had_rejects) 1997 notify->content_state = svn_wc_notify_state_conflicted; 1998 else if (target->local_mods) 1999 notify->content_state = svn_wc_notify_state_merged; 2000 else if (target->has_text_changes) 2001 notify->content_state = svn_wc_notify_state_changed; 2002 2003 if (target->had_prop_rejects) 2004 notify->prop_state = svn_wc_notify_state_conflicted; 2005 else if (target->has_prop_changes) 2006 notify->prop_state = svn_wc_notify_state_changed; 2007 } 2008 2009 (*ctx->notify_func2)(ctx->notify_baton2, notify, pool); 2010 2011 if (action == svn_wc_notify_patch) 2012 { 2013 int i; 2014 apr_pool_t *iterpool; 2015 apr_hash_index_t *hash_index; 2016 2017 iterpool = svn_pool_create(pool); 2018 for (i = 0; i < target->content->hunks->nelts; i++) 2019 { 2020 const hunk_info_t *hi; 2021 2022 svn_pool_clear(iterpool); 2023 2024 hi = APR_ARRAY_IDX(target->content->hunks, i, hunk_info_t *); 2025 2026 SVN_ERR(send_hunk_notification(hi, target, NULL /* prop_name */, 2027 ctx, iterpool)); 2028 } 2029 2030 for (hash_index = apr_hash_first(pool, target->prop_targets); 2031 hash_index; 2032 hash_index = apr_hash_next(hash_index)) 2033 { 2034 prop_patch_target_t *prop_target; 2035 2036 prop_target = svn__apr_hash_index_val(hash_index); 2037 2038 for (i = 0; i < prop_target->content->hunks->nelts; i++) 2039 { 2040 const hunk_info_t *hi; 2041 2042 svn_pool_clear(iterpool); 2043 2044 hi = APR_ARRAY_IDX(prop_target->content->hunks, i, 2045 hunk_info_t *); 2046 2047 /* Don't notify on the hunk level for added or deleted props. */ 2048 if (prop_target->operation != svn_diff_op_added && 2049 prop_target->operation != svn_diff_op_deleted) 2050 SVN_ERR(send_hunk_notification(hi, target, prop_target->name, 2051 ctx, iterpool)); 2052 } 2053 } 2054 svn_pool_destroy(iterpool); 2055 } 2056 2057 return SVN_NO_ERROR; 2058} 2059 2060static void 2061svn_sort__array(apr_array_header_t *array, 2062 int (*comparison_func)(const void *, 2063 const void *)) 2064{ 2065 qsort(array->elts, array->nelts, array->elt_size, comparison_func); 2066} 2067 2068/* Implements the callback for svn_sort__array. Puts hunks that match 2069 before hunks that do not match, puts hunks that match in order 2070 based on postion matched, puts hunks that do not match in order 2071 based on original position. */ 2072static int 2073sort_matched_hunks(const void *a, const void *b) 2074{ 2075 const hunk_info_t *item1 = *((const hunk_info_t * const *)a); 2076 const hunk_info_t *item2 = *((const hunk_info_t * const *)b); 2077 svn_boolean_t matched1 = !item1->rejected && !item1->already_applied; 2078 svn_boolean_t matched2 = !item2->rejected && !item2->already_applied; 2079 svn_linenum_t original1, original2; 2080 2081 if (matched1 && matched2) 2082 { 2083 /* Both match so use order matched in file. */ 2084 if (item1->matched_line > item2->matched_line) 2085 return 1; 2086 else if (item1->matched_line == item2->matched_line) 2087 return 0; 2088 else 2089 return -1; 2090 } 2091 else if (matched2) 2092 /* Only second matches, put it before first. */ 2093 return 1; 2094 else if (matched1) 2095 /* Only first matches, put it before second. */ 2096 return -1; 2097 2098 /* Neither matches, sort by original_start. */ 2099 original1 = svn_diff_hunk_get_original_start(item1->hunk); 2100 original2 = svn_diff_hunk_get_original_start(item2->hunk); 2101 if (original1 > original2) 2102 return 1; 2103 else if (original1 == original2) 2104 return 0; 2105 else 2106 return -1; 2107} 2108 2109 2110/* Apply a PATCH to a working copy at ABS_WC_PATH and put the result 2111 * into temporary files, to be installed in the working copy later. 2112 * Return information about the patch target in *PATCH_TARGET, allocated 2113 * in RESULT_POOL. Use WC_CTX as the working copy context. 2114 * STRIP_COUNT specifies the number of leading path components 2115 * which should be stripped from target paths in the patch. 2116 * REMOVE_TEMPFILES, PATCH_FUNC, and PATCH_BATON as in svn_client_patch(). 2117 * IGNORE_WHITESPACE tells whether whitespace should be considered when 2118 * doing the matching. 2119 * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation. 2120 * Do temporary allocations in SCRATCH_POOL. */ 2121static svn_error_t * 2122apply_one_patch(patch_target_t **patch_target, svn_patch_t *patch, 2123 const char *abs_wc_path, svn_wc_context_t *wc_ctx, 2124 int strip_count, 2125 svn_boolean_t ignore_whitespace, 2126 svn_boolean_t remove_tempfiles, 2127 svn_client_patch_func_t patch_func, 2128 void *patch_baton, 2129 svn_cancel_func_t cancel_func, 2130 void *cancel_baton, 2131 apr_pool_t *result_pool, apr_pool_t *scratch_pool) 2132{ 2133 patch_target_t *target; 2134 apr_pool_t *iterpool; 2135 int i; 2136 static const svn_linenum_t MAX_FUZZ = 2; 2137 apr_hash_index_t *hash_index; 2138 2139 SVN_ERR(init_patch_target(&target, patch, abs_wc_path, wc_ctx, strip_count, 2140 remove_tempfiles, result_pool, scratch_pool)); 2141 if (target->skipped) 2142 { 2143 *patch_target = target; 2144 return SVN_NO_ERROR; 2145 } 2146 2147 if (patch_func) 2148 { 2149 SVN_ERR(patch_func(patch_baton, &target->filtered, 2150 target->canon_path_from_patchfile, 2151 target->patched_path, target->reject_path, 2152 scratch_pool)); 2153 if (target->filtered) 2154 { 2155 *patch_target = target; 2156 return SVN_NO_ERROR; 2157 } 2158 } 2159 2160 iterpool = svn_pool_create(scratch_pool); 2161 /* Match hunks. */ 2162 for (i = 0; i < patch->hunks->nelts; i++) 2163 { 2164 svn_diff_hunk_t *hunk; 2165 hunk_info_t *hi; 2166 svn_linenum_t fuzz = 0; 2167 2168 svn_pool_clear(iterpool); 2169 2170 if (cancel_func) 2171 SVN_ERR(cancel_func(cancel_baton)); 2172 2173 hunk = APR_ARRAY_IDX(patch->hunks, i, svn_diff_hunk_t *); 2174 2175 /* Determine the line the hunk should be applied at. 2176 * If no match is found initially, try with fuzz. */ 2177 do 2178 { 2179 SVN_ERR(get_hunk_info(&hi, target, target->content, hunk, fuzz, 2180 ignore_whitespace, 2181 FALSE /* is_prop_hunk */, 2182 cancel_func, cancel_baton, 2183 result_pool, iterpool)); 2184 fuzz++; 2185 } 2186 while (hi->rejected && fuzz <= MAX_FUZZ && ! hi->already_applied); 2187 2188 APR_ARRAY_PUSH(target->content->hunks, hunk_info_t *) = hi; 2189 } 2190 2191 /* Hunks are applied in the order determined by the matched line and 2192 this may be different from the order of the original lines. */ 2193 svn_sort__array(target->content->hunks, sort_matched_hunks); 2194 2195 /* Apply or reject hunks. */ 2196 for (i = 0; i < target->content->hunks->nelts; i++) 2197 { 2198 hunk_info_t *hi; 2199 2200 svn_pool_clear(iterpool); 2201 2202 if (cancel_func) 2203 SVN_ERR(cancel_func(cancel_baton)); 2204 2205 hi = APR_ARRAY_IDX(target->content->hunks, i, hunk_info_t *); 2206 if (hi->already_applied) 2207 continue; 2208 else if (hi->rejected) 2209 SVN_ERR(reject_hunk(target, target->content, hi->hunk, 2210 NULL /* prop_name */, 2211 iterpool)); 2212 else 2213 SVN_ERR(apply_hunk(target, target->content, hi, 2214 NULL /* prop_name */, iterpool)); 2215 } 2216 2217 if (target->kind_on_disk == svn_node_file) 2218 { 2219 /* Copy any remaining lines to target. */ 2220 SVN_ERR(copy_lines_to_target(target->content, 0, scratch_pool)); 2221 if (! target->content->eof) 2222 { 2223 /* We could not copy the entire target file to the temporary file, 2224 * and would truncate the target if we copied the temporary file 2225 * on top of it. Skip this target. */ 2226 target->skipped = TRUE; 2227 } 2228 } 2229 2230 /* Match property hunks. */ 2231 for (hash_index = apr_hash_first(scratch_pool, patch->prop_patches); 2232 hash_index; 2233 hash_index = apr_hash_next(hash_index)) 2234 { 2235 svn_prop_patch_t *prop_patch; 2236 const char *prop_name; 2237 prop_patch_target_t *prop_target; 2238 2239 prop_name = svn__apr_hash_index_key(hash_index); 2240 prop_patch = svn__apr_hash_index_val(hash_index); 2241 2242 if (! strcmp(prop_name, SVN_PROP_SPECIAL)) 2243 target->is_special = TRUE; 2244 2245 /* We'll store matched hunks in prop_content. */ 2246 prop_target = svn_hash_gets(target->prop_targets, prop_name); 2247 2248 for (i = 0; i < prop_patch->hunks->nelts; i++) 2249 { 2250 svn_diff_hunk_t *hunk; 2251 hunk_info_t *hi; 2252 svn_linenum_t fuzz = 0; 2253 2254 svn_pool_clear(iterpool); 2255 2256 if (cancel_func) 2257 SVN_ERR(cancel_func(cancel_baton)); 2258 2259 hunk = APR_ARRAY_IDX(prop_patch->hunks, i, svn_diff_hunk_t *); 2260 2261 /* Determine the line the hunk should be applied at. 2262 * If no match is found initially, try with fuzz. */ 2263 do 2264 { 2265 SVN_ERR(get_hunk_info(&hi, target, prop_target->content, 2266 hunk, fuzz, 2267 ignore_whitespace, 2268 TRUE /* is_prop_hunk */, 2269 cancel_func, cancel_baton, 2270 result_pool, iterpool)); 2271 fuzz++; 2272 } 2273 while (hi->rejected && fuzz <= MAX_FUZZ && ! hi->already_applied); 2274 2275 APR_ARRAY_PUSH(prop_target->content->hunks, hunk_info_t *) = hi; 2276 } 2277 } 2278 2279 /* Apply or reject property hunks. */ 2280 for (hash_index = apr_hash_first(scratch_pool, target->prop_targets); 2281 hash_index; 2282 hash_index = apr_hash_next(hash_index)) 2283 { 2284 prop_patch_target_t *prop_target; 2285 2286 prop_target = svn__apr_hash_index_val(hash_index); 2287 2288 for (i = 0; i < prop_target->content->hunks->nelts; i++) 2289 { 2290 hunk_info_t *hi; 2291 2292 svn_pool_clear(iterpool); 2293 2294 hi = APR_ARRAY_IDX(prop_target->content->hunks, i, 2295 hunk_info_t *); 2296 if (hi->already_applied) 2297 continue; 2298 else if (hi->rejected) 2299 SVN_ERR(reject_hunk(target, prop_target->content, hi->hunk, 2300 prop_target->name, 2301 iterpool)); 2302 else 2303 SVN_ERR(apply_hunk(target, prop_target->content, hi, 2304 prop_target->name, 2305 iterpool)); 2306 } 2307 2308 if (prop_target->content->existed) 2309 { 2310 /* Copy any remaining lines to target. */ 2311 SVN_ERR(copy_lines_to_target(prop_target->content, 0, 2312 scratch_pool)); 2313 if (! prop_target->content->eof) 2314 { 2315 /* We could not copy the entire target property to the 2316 * temporary file, and would truncate the target if we 2317 * copied the temporary file on top of it. Skip this target. */ 2318 target->skipped = TRUE; 2319 } 2320 } 2321 } 2322 2323 svn_pool_destroy(iterpool); 2324 2325 if (!target->is_symlink) 2326 { 2327 /* Now close files we don't need any longer to get their contents 2328 * flushed to disk. 2329 * But we're not closing the reject file -- it still needed and 2330 * will be closed later in write_out_rejected_hunks(). */ 2331 if (target->kind_on_disk == svn_node_file) 2332 SVN_ERR(svn_io_file_close(target->file, scratch_pool)); 2333 2334 SVN_ERR(svn_io_file_close(target->patched_file, scratch_pool)); 2335 } 2336 2337 if (! target->skipped) 2338 { 2339 apr_finfo_t working_file; 2340 apr_finfo_t patched_file; 2341 2342 /* Get sizes of the patched temporary file and the working file. 2343 * We'll need those to figure out whether we should delete the 2344 * patched file. */ 2345 SVN_ERR(svn_io_stat(&patched_file, target->patched_path, 2346 APR_FINFO_SIZE | APR_FINFO_LINK, scratch_pool)); 2347 if (target->kind_on_disk == svn_node_file) 2348 SVN_ERR(svn_io_stat(&working_file, target->local_abspath, 2349 APR_FINFO_SIZE | APR_FINFO_LINK, scratch_pool)); 2350 else 2351 working_file.size = 0; 2352 2353 if (patched_file.size == 0 && working_file.size > 0) 2354 { 2355 /* If a unidiff removes all lines from a file, that usually 2356 * means deletion, so we can confidently schedule the target 2357 * for deletion. In the rare case where the unidiff was really 2358 * meant to replace a file with an empty one, this may not 2359 * be desirable. But the deletion can easily be reverted and 2360 * creating an empty file manually is not exactly hard either. */ 2361 target->deleted = (target->db_kind == svn_node_file); 2362 } 2363 else if (patched_file.size == 0 && working_file.size == 0) 2364 { 2365 /* The target was empty or non-existent to begin with 2366 * and no content was changed by patching. 2367 * Report this as skipped if it didn't exist, unless in the special 2368 * case of adding an empty file which has properties set on it or 2369 * adding an empty file with a 'git diff' */ 2370 if (target->kind_on_disk == svn_node_none 2371 && ! target->has_prop_changes 2372 && ! target->added) 2373 target->skipped = TRUE; 2374 } 2375 else if (patched_file.size > 0 && working_file.size == 0) 2376 { 2377 /* The patch has created a file. */ 2378 if (target->locally_deleted) 2379 target->replaced = TRUE; 2380 else if (target->db_kind == svn_node_none) 2381 target->added = TRUE; 2382 } 2383 } 2384 2385 *patch_target = target; 2386 2387 return SVN_NO_ERROR; 2388} 2389 2390/* Try to create missing parent directories for TARGET in the working copy 2391 * rooted at ABS_WC_PATH, and add the parents to version control. 2392 * If the parents cannot be created, mark the target as skipped. 2393 * Use client context CTX. If DRY_RUN is true, do not create missing 2394 * parents but issue notifications only. 2395 * Use SCRATCH_POOL for temporary allocations. */ 2396static svn_error_t * 2397create_missing_parents(patch_target_t *target, 2398 const char *abs_wc_path, 2399 svn_client_ctx_t *ctx, 2400 svn_boolean_t dry_run, 2401 apr_pool_t *scratch_pool) 2402{ 2403 const char *local_abspath; 2404 apr_array_header_t *components; 2405 int present_components; 2406 int i; 2407 apr_pool_t *iterpool; 2408 2409 /* Check if we can safely create the target's parent. */ 2410 local_abspath = abs_wc_path; 2411 components = svn_path_decompose(target->local_relpath, scratch_pool); 2412 present_components = 0; 2413 iterpool = svn_pool_create(scratch_pool); 2414 for (i = 0; i < components->nelts - 1; i++) 2415 { 2416 const char *component; 2417 svn_node_kind_t wc_kind, disk_kind; 2418 2419 svn_pool_clear(iterpool); 2420 2421 component = APR_ARRAY_IDX(components, i, const char *); 2422 local_abspath = svn_dirent_join(local_abspath, component, scratch_pool); 2423 2424 SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, local_abspath, 2425 FALSE, TRUE, iterpool)); 2426 2427 SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, iterpool)); 2428 2429 if (disk_kind == svn_node_file || wc_kind == svn_node_file) 2430 { 2431 /* on-disk files and missing files are obstructions */ 2432 target->skipped = TRUE; 2433 break; 2434 } 2435 else if (disk_kind == svn_node_dir) 2436 { 2437 if (wc_kind == svn_node_dir) 2438 present_components++; 2439 else 2440 { 2441 target->skipped = TRUE; 2442 break; 2443 } 2444 } 2445 else if (wc_kind != svn_node_none) 2446 { 2447 /* Node is missing */ 2448 target->skipped = TRUE; 2449 break; 2450 } 2451 else 2452 { 2453 /* It's not a file, it's not a dir... 2454 Let's add a dir */ 2455 break; 2456 } 2457 } 2458 if (! target->skipped) 2459 { 2460 local_abspath = abs_wc_path; 2461 for (i = 0; i < present_components; i++) 2462 { 2463 const char *component; 2464 component = APR_ARRAY_IDX(components, i, const char *); 2465 local_abspath = svn_dirent_join(local_abspath, 2466 component, scratch_pool); 2467 } 2468 2469 if (!dry_run && present_components < components->nelts - 1) 2470 SVN_ERR(svn_io_make_dir_recursively( 2471 svn_dirent_join( 2472 abs_wc_path, 2473 svn_relpath_dirname(target->local_relpath, 2474 scratch_pool), 2475 scratch_pool), 2476 scratch_pool)); 2477 2478 for (i = present_components; i < components->nelts - 1; i++) 2479 { 2480 const char *component; 2481 2482 svn_pool_clear(iterpool); 2483 2484 component = APR_ARRAY_IDX(components, i, const char *); 2485 local_abspath = svn_dirent_join(local_abspath, component, 2486 scratch_pool); 2487 if (dry_run) 2488 { 2489 if (ctx->notify_func2) 2490 { 2491 /* Just do notification. */ 2492 svn_wc_notify_t *notify; 2493 notify = svn_wc_create_notify(local_abspath, 2494 svn_wc_notify_add, 2495 iterpool); 2496 notify->kind = svn_node_dir; 2497 ctx->notify_func2(ctx->notify_baton2, notify, 2498 iterpool); 2499 } 2500 } 2501 else 2502 { 2503 /* Create the missing component and add it 2504 * to version control. Allow cancellation since we 2505 * have not modified the working copy yet for this 2506 * target. */ 2507 2508 if (ctx->cancel_func) 2509 SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 2510 2511 SVN_ERR(svn_wc_add_from_disk2(ctx->wc_ctx, local_abspath, 2512 NULL /*props*/, 2513 ctx->notify_func2, ctx->notify_baton2, 2514 iterpool)); 2515 } 2516 } 2517 } 2518 2519 svn_pool_destroy(iterpool); 2520 return SVN_NO_ERROR; 2521} 2522 2523/* Install a patched TARGET into the working copy at ABS_WC_PATH. 2524 * Use client context CTX to retrieve WC_CTX, and possibly doing 2525 * notifications. If DRY_RUN is TRUE, don't modify the working copy. 2526 * Do temporary allocations in POOL. */ 2527static svn_error_t * 2528install_patched_target(patch_target_t *target, const char *abs_wc_path, 2529 svn_client_ctx_t *ctx, svn_boolean_t dry_run, 2530 apr_pool_t *pool) 2531{ 2532 if (target->deleted) 2533 { 2534 if (! dry_run) 2535 { 2536 /* Schedule the target for deletion. Suppress 2537 * notification, we'll do it manually in a minute 2538 * because we also need to notify during dry-run. 2539 * Also suppress cancellation, because we'd rather 2540 * notify about what we did before aborting. */ 2541 SVN_ERR(svn_wc_delete4(ctx->wc_ctx, target->local_abspath, 2542 FALSE /* keep_local */, FALSE, 2543 NULL, NULL, NULL, NULL, pool)); 2544 } 2545 } 2546 else 2547 { 2548 svn_node_kind_t parent_db_kind; 2549 if (target->added || target->replaced) 2550 { 2551 const char *parent_abspath; 2552 2553 parent_abspath = svn_dirent_dirname(target->local_abspath, 2554 pool); 2555 /* If the target's parent directory does not yet exist 2556 * we need to create it before we can copy the patched 2557 * result in place. */ 2558 SVN_ERR(svn_wc_read_kind2(&parent_db_kind, ctx->wc_ctx, 2559 parent_abspath, FALSE, FALSE, pool)); 2560 2561 /* We can't add targets under nodes scheduled for delete, so add 2562 a new directory if needed. */ 2563 if (parent_db_kind == svn_node_dir 2564 || parent_db_kind == svn_node_file) 2565 { 2566 if (parent_db_kind != svn_node_dir) 2567 target->skipped = TRUE; 2568 else 2569 { 2570 svn_node_kind_t disk_kind; 2571 2572 SVN_ERR(svn_io_check_path(parent_abspath, &disk_kind, pool)); 2573 if (disk_kind != svn_node_dir) 2574 target->skipped = TRUE; 2575 } 2576 } 2577 else 2578 SVN_ERR(create_missing_parents(target, abs_wc_path, ctx, 2579 dry_run, pool)); 2580 2581 } 2582 else 2583 { 2584 svn_node_kind_t wc_kind; 2585 2586 /* The target should exist */ 2587 SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, 2588 target->local_abspath, 2589 FALSE, FALSE, pool)); 2590 2591 if (target->kind_on_disk == svn_node_none 2592 || wc_kind != target->kind_on_disk) 2593 { 2594 target->skipped = TRUE; 2595 } 2596 } 2597 2598 if (! dry_run && ! target->skipped) 2599 { 2600 if (target->is_special) 2601 { 2602 svn_stream_t *stream; 2603 svn_stream_t *patched_stream; 2604 2605 SVN_ERR(svn_stream_open_readonly(&patched_stream, 2606 target->patched_path, 2607 pool, pool)); 2608 SVN_ERR(svn_subst_create_specialfile(&stream, 2609 target->local_abspath, 2610 pool, pool)); 2611 SVN_ERR(svn_stream_copy3(patched_stream, stream, 2612 ctx->cancel_func, ctx->cancel_baton, 2613 pool)); 2614 } 2615 else 2616 { 2617 svn_boolean_t repair_eol; 2618 2619 /* Copy the patched file on top of the target file. 2620 * Always expand keywords in the patched file, but repair EOL 2621 * only if svn:eol-style dictates a particular style. */ 2622 repair_eol = (target->content->eol_style == 2623 svn_subst_eol_style_fixed || 2624 target->content->eol_style == 2625 svn_subst_eol_style_native); 2626 2627 SVN_ERR(svn_subst_copy_and_translate4( 2628 target->patched_path, target->local_abspath, 2629 target->content->eol_str, repair_eol, 2630 target->content->keywords, 2631 TRUE /* expand */, FALSE /* special */, 2632 ctx->cancel_func, ctx->cancel_baton, pool)); 2633 } 2634 2635 if (target->added || target->replaced) 2636 { 2637 /* The target file didn't exist previously, 2638 * so add it to version control. 2639 * Suppress notification, we'll do that later (and also 2640 * during dry-run). Don't allow cancellation because 2641 * we'd rather notify about what we did before aborting. */ 2642 SVN_ERR(svn_wc_add_from_disk2(ctx->wc_ctx, target->local_abspath, 2643 NULL /*props*/, 2644 NULL, NULL, pool)); 2645 } 2646 2647 /* Restore the target's executable bit if necessary. */ 2648 SVN_ERR(svn_io_set_file_executable(target->local_abspath, 2649 target->executable, 2650 FALSE, pool)); 2651 } 2652 } 2653 2654 return SVN_NO_ERROR; 2655} 2656 2657/* Write out rejected hunks, if any, to TARGET->REJECT_PATH. If DRY_RUN is 2658 * TRUE, don't modify the working copy. 2659 * Do temporary allocations in POOL. 2660 */ 2661static svn_error_t * 2662write_out_rejected_hunks(patch_target_t *target, 2663 svn_boolean_t dry_run, 2664 apr_pool_t *pool) 2665{ 2666 SVN_ERR(svn_io_file_close(target->reject_file, pool)); 2667 2668 if (! dry_run && (target->had_rejects || target->had_prop_rejects)) 2669 { 2670 /* Write out rejected hunks, if any. */ 2671 SVN_ERR(svn_io_copy_file(target->reject_path, 2672 apr_psprintf(pool, "%s.svnpatch.rej", 2673 target->local_abspath), 2674 FALSE, pool)); 2675 /* ### TODO mark file as conflicted. */ 2676 } 2677 return SVN_NO_ERROR; 2678} 2679 2680/* Install the patched properties for TARGET. Use client context CTX to 2681 * retrieve WC_CTX. If DRY_RUN is TRUE, don't modify the working copy. 2682 * Do temporary allocations in SCRATCH_POOL. */ 2683static svn_error_t * 2684install_patched_prop_targets(patch_target_t *target, 2685 svn_client_ctx_t *ctx, svn_boolean_t dry_run, 2686 apr_pool_t *scratch_pool) 2687{ 2688 apr_hash_index_t *hi; 2689 apr_pool_t *iterpool; 2690 2691 iterpool = svn_pool_create(scratch_pool); 2692 2693 for (hi = apr_hash_first(scratch_pool, target->prop_targets); 2694 hi; 2695 hi = apr_hash_next(hi)) 2696 { 2697 prop_patch_target_t *prop_target = svn__apr_hash_index_val(hi); 2698 const svn_string_t *prop_val; 2699 svn_error_t *err; 2700 2701 svn_pool_clear(iterpool); 2702 2703 if (ctx->cancel_func) 2704 SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 2705 2706 /* For a deleted prop we only set the value to NULL. */ 2707 if (prop_target->operation == svn_diff_op_deleted) 2708 { 2709 if (! dry_run) 2710 SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, target->local_abspath, 2711 prop_target->name, NULL, svn_depth_empty, 2712 TRUE /* skip_checks */, 2713 NULL /* changelist_filter */, 2714 NULL, NULL /* cancellation */, 2715 NULL, NULL /* notification */, 2716 iterpool)); 2717 continue; 2718 } 2719 2720 /* If the patch target doesn't exist yet, the patch wants to add an 2721 * empty file with properties set on it. So create an empty file and 2722 * add it to version control. But if the patch was in the 'git format' 2723 * then the file has already been added. 2724 * 2725 * ### How can we tell whether the patch really wanted to create 2726 * ### an empty directory? */ 2727 if (! target->has_text_changes 2728 && target->kind_on_disk == svn_node_none 2729 && ! target->added) 2730 { 2731 if (! dry_run) 2732 { 2733 SVN_ERR(svn_io_file_create(target->local_abspath, "", 2734 scratch_pool)); 2735 SVN_ERR(svn_wc_add_from_disk2(ctx->wc_ctx, target->local_abspath, 2736 NULL /*props*/, 2737 /* suppress notification */ 2738 NULL, NULL, 2739 iterpool)); 2740 } 2741 target->added = TRUE; 2742 } 2743 2744 /* Attempt to set the property, and reject all hunks if this 2745 fails. If the property had a non-empty value, but now has 2746 an empty one, we'll just delete the property altogether. */ 2747 if (prop_target->value && prop_target->value->len 2748 && prop_target->patched_value && !prop_target->patched_value->len) 2749 prop_val = NULL; 2750 else 2751 prop_val = svn_stringbuf__morph_into_string(prop_target->patched_value); 2752 2753 if (dry_run) 2754 { 2755 const svn_string_t *canon_propval; 2756 2757 err = svn_wc_canonicalize_svn_prop(&canon_propval, 2758 prop_target->name, 2759 prop_val, target->local_abspath, 2760 target->db_kind, 2761 TRUE, /* ### Skipping checks */ 2762 NULL, NULL, 2763 iterpool); 2764 } 2765 else 2766 { 2767 err = svn_wc_prop_set4(ctx->wc_ctx, target->local_abspath, 2768 prop_target->name, prop_val, svn_depth_empty, 2769 TRUE /* skip_checks */, 2770 NULL /* changelist_filter */, 2771 NULL, NULL /* cancellation */, 2772 NULL, NULL /* notification */, 2773 iterpool); 2774 } 2775 2776 if (err) 2777 { 2778 /* ### The errors which svn_wc_canonicalize_svn_prop() will 2779 * ### return aren't documented. */ 2780 if (err->apr_err == SVN_ERR_ILLEGAL_TARGET || 2781 err->apr_err == SVN_ERR_NODE_UNEXPECTED_KIND || 2782 err->apr_err == SVN_ERR_IO_UNKNOWN_EOL || 2783 err->apr_err == SVN_ERR_BAD_MIME_TYPE || 2784 err->apr_err == SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION) 2785 { 2786 int i; 2787 2788 svn_error_clear(err); 2789 2790 for (i = 0; i < prop_target->content->hunks->nelts; i++) 2791 { 2792 hunk_info_t *hunk_info; 2793 2794 hunk_info = APR_ARRAY_IDX(prop_target->content->hunks, 2795 i, hunk_info_t *); 2796 hunk_info->rejected = TRUE; 2797 SVN_ERR(reject_hunk(target, prop_target->content, 2798 hunk_info->hunk, prop_target->name, 2799 iterpool)); 2800 } 2801 } 2802 else 2803 return svn_error_trace(err); 2804 } 2805 2806 } 2807 2808 svn_pool_destroy(iterpool); 2809 2810 return SVN_NO_ERROR; 2811} 2812 2813/* Baton for can_delete_callback */ 2814struct can_delete_baton_t 2815{ 2816 svn_boolean_t must_keep; 2817 const apr_array_header_t *targets_info; 2818 const char *local_abspath; 2819}; 2820 2821/* Implements svn_wc_status_func4_t. */ 2822static svn_error_t * 2823can_delete_callback(void *baton, 2824 const char *abspath, 2825 const svn_wc_status3_t *status, 2826 apr_pool_t *pool) 2827{ 2828 struct can_delete_baton_t *cb = baton; 2829 int i; 2830 2831 switch(status->node_status) 2832 { 2833 case svn_wc_status_none: 2834 case svn_wc_status_deleted: 2835 return SVN_NO_ERROR; 2836 2837 default: 2838 if (! strcmp(cb->local_abspath, abspath)) 2839 return SVN_NO_ERROR; /* Only interested in descendants */ 2840 2841 for (i = 0; i < cb->targets_info->nelts; i++) 2842 { 2843 const patch_target_info_t *target_info = 2844 APR_ARRAY_IDX(cb->targets_info, i, const patch_target_info_t *); 2845 2846 if (! strcmp(target_info->local_abspath, abspath)) 2847 { 2848 if (target_info->deleted) 2849 return SVN_NO_ERROR; 2850 2851 break; /* Cease invocation; must keep */ 2852 } 2853 } 2854 2855 cb->must_keep = TRUE; 2856 2857 return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL); 2858 } 2859} 2860 2861static svn_error_t * 2862check_ancestor_delete(const char *deleted_target, 2863 apr_array_header_t *targets_info, 2864 const char *apply_root, 2865 svn_boolean_t dry_run, 2866 svn_client_ctx_t *ctx, 2867 apr_pool_t *result_pool, 2868 apr_pool_t *scratch_pool) 2869{ 2870 struct can_delete_baton_t cb; 2871 svn_error_t *err; 2872 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 2873 2874 const char *dir_abspath = svn_dirent_dirname(deleted_target, scratch_pool); 2875 2876 while (svn_dirent_is_child(apply_root, dir_abspath, iterpool)) 2877 { 2878 svn_pool_clear(iterpool); 2879 2880 cb.local_abspath = dir_abspath; 2881 cb.must_keep = FALSE; 2882 cb.targets_info = targets_info; 2883 2884 err = svn_wc_walk_status(ctx->wc_ctx, dir_abspath, svn_depth_infinity, 2885 TRUE, FALSE, FALSE, NULL, 2886 can_delete_callback, &cb, 2887 ctx->cancel_func, ctx->cancel_baton, 2888 iterpool); 2889 2890 if (err) 2891 { 2892 if (err->apr_err != SVN_ERR_CEASE_INVOCATION) 2893 return svn_error_trace(err); 2894 2895 svn_error_clear(err); 2896 } 2897 2898 if (cb.must_keep) 2899 { 2900 break; 2901 } 2902 2903 if (! dry_run) 2904 { 2905 SVN_ERR(svn_wc_delete4(ctx->wc_ctx, dir_abspath, FALSE, FALSE, 2906 ctx->cancel_func, ctx->cancel_baton, 2907 NULL, NULL, 2908 scratch_pool)); 2909 } 2910 2911 { 2912 patch_target_info_t *pti = apr_pcalloc(result_pool, sizeof(*pti)); 2913 2914 pti->local_abspath = apr_pstrdup(result_pool, dir_abspath); 2915 pti->deleted = TRUE; 2916 2917 APR_ARRAY_PUSH(targets_info, patch_target_info_t *) = pti; 2918 } 2919 2920 2921 if (ctx->notify_func2) 2922 { 2923 svn_wc_notify_t *notify; 2924 2925 notify = svn_wc_create_notify(dir_abspath, svn_wc_notify_delete, 2926 iterpool); 2927 notify->kind = svn_node_dir; 2928 2929 ctx->notify_func2(ctx->notify_baton2, notify, iterpool); 2930 } 2931 2932 /* And check if we must also delete the parent */ 2933 dir_abspath = svn_dirent_dirname(dir_abspath, scratch_pool); 2934 } 2935 2936 svn_pool_destroy(iterpool); 2937 2938 return SVN_NO_ERROR; 2939} 2940 2941/* This function is the main entry point into the patch code. */ 2942static svn_error_t * 2943apply_patches(/* The path to the patch file. */ 2944 const char *patch_abspath, 2945 /* The abspath to the working copy the patch should be applied to. */ 2946 const char *abs_wc_path, 2947 /* Indicates whether we're doing a dry run. */ 2948 svn_boolean_t dry_run, 2949 /* Number of leading components to strip from patch target paths. */ 2950 int strip_count, 2951 /* Whether to apply the patch in reverse. */ 2952 svn_boolean_t reverse, 2953 /* Whether to ignore whitespace when matching context lines. */ 2954 svn_boolean_t ignore_whitespace, 2955 /* As in svn_client_patch(). */ 2956 svn_boolean_t remove_tempfiles, 2957 /* As in svn_client_patch(). */ 2958 svn_client_patch_func_t patch_func, 2959 void *patch_baton, 2960 /* The client context. */ 2961 svn_client_ctx_t *ctx, 2962 apr_pool_t *scratch_pool) 2963{ 2964 svn_patch_t *patch; 2965 apr_pool_t *iterpool; 2966 svn_patch_file_t *patch_file; 2967 apr_array_header_t *targets_info; 2968 2969 /* Try to open the patch file. */ 2970 SVN_ERR(svn_diff_open_patch_file(&patch_file, patch_abspath, scratch_pool)); 2971 2972 /* Apply patches. */ 2973 targets_info = apr_array_make(scratch_pool, 0, 2974 sizeof(patch_target_info_t *)); 2975 iterpool = svn_pool_create(scratch_pool); 2976 do 2977 { 2978 svn_pool_clear(iterpool); 2979 2980 if (ctx->cancel_func) 2981 SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 2982 2983 SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file, 2984 reverse, ignore_whitespace, 2985 iterpool, iterpool)); 2986 if (patch) 2987 { 2988 patch_target_t *target; 2989 2990 SVN_ERR(apply_one_patch(&target, patch, abs_wc_path, 2991 ctx->wc_ctx, strip_count, 2992 ignore_whitespace, remove_tempfiles, 2993 patch_func, patch_baton, 2994 ctx->cancel_func, ctx->cancel_baton, 2995 iterpool, iterpool)); 2996 if (! target->filtered) 2997 { 2998 /* Save info we'll still need when we're done patching. */ 2999 patch_target_info_t *target_info = 3000 apr_pcalloc(scratch_pool, sizeof(patch_target_info_t)); 3001 target_info->local_abspath = apr_pstrdup(scratch_pool, 3002 target->local_abspath); 3003 target_info->deleted = target->deleted; 3004 3005 if (! target->skipped) 3006 { 3007 APR_ARRAY_PUSH(targets_info, 3008 patch_target_info_t *) = target_info; 3009 3010 if (target->has_text_changes 3011 || target->added 3012 || target->deleted) 3013 SVN_ERR(install_patched_target(target, abs_wc_path, 3014 ctx, dry_run, iterpool)); 3015 3016 if (target->has_prop_changes && (!target->deleted)) 3017 SVN_ERR(install_patched_prop_targets(target, ctx, 3018 dry_run, iterpool)); 3019 3020 SVN_ERR(write_out_rejected_hunks(target, dry_run, iterpool)); 3021 } 3022 SVN_ERR(send_patch_notification(target, ctx, iterpool)); 3023 3024 if (target->deleted && !target->skipped) 3025 { 3026 SVN_ERR(check_ancestor_delete(target_info->local_abspath, 3027 targets_info, abs_wc_path, 3028 dry_run, ctx, 3029 scratch_pool, iterpool)); 3030 } 3031 } 3032 } 3033 } 3034 while (patch); 3035 3036 SVN_ERR(svn_diff_close_patch_file(patch_file, iterpool)); 3037 svn_pool_destroy(iterpool); 3038 3039 return SVN_NO_ERROR; 3040} 3041 3042svn_error_t * 3043svn_client_patch(const char *patch_abspath, 3044 const char *wc_dir_abspath, 3045 svn_boolean_t dry_run, 3046 int strip_count, 3047 svn_boolean_t reverse, 3048 svn_boolean_t ignore_whitespace, 3049 svn_boolean_t remove_tempfiles, 3050 svn_client_patch_func_t patch_func, 3051 void *patch_baton, 3052 svn_client_ctx_t *ctx, 3053 apr_pool_t *scratch_pool) 3054{ 3055 svn_node_kind_t kind; 3056 3057 if (strip_count < 0) 3058 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, 3059 _("strip count must be positive")); 3060 3061 if (svn_path_is_url(wc_dir_abspath)) 3062 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 3063 _("'%s' is not a local path"), 3064 svn_dirent_local_style(wc_dir_abspath, 3065 scratch_pool)); 3066 3067 SVN_ERR(svn_io_check_path(patch_abspath, &kind, scratch_pool)); 3068 if (kind == svn_node_none) 3069 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 3070 _("'%s' does not exist"), 3071 svn_dirent_local_style(patch_abspath, 3072 scratch_pool)); 3073 if (kind != svn_node_file) 3074 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 3075 _("'%s' is not a file"), 3076 svn_dirent_local_style(patch_abspath, 3077 scratch_pool)); 3078 3079 SVN_ERR(svn_io_check_path(wc_dir_abspath, &kind, scratch_pool)); 3080 if (kind == svn_node_none) 3081 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 3082 _("'%s' does not exist"), 3083 svn_dirent_local_style(wc_dir_abspath, 3084 scratch_pool)); 3085 if (kind != svn_node_dir) 3086 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 3087 _("'%s' is not a directory"), 3088 svn_dirent_local_style(wc_dir_abspath, 3089 scratch_pool)); 3090 3091 SVN_WC__CALL_WITH_WRITE_LOCK( 3092 apply_patches(patch_abspath, wc_dir_abspath, dry_run, strip_count, 3093 reverse, ignore_whitespace, remove_tempfiles, 3094 patch_func, patch_baton, ctx, scratch_pool), 3095 ctx->wc_ctx, wc_dir_abspath, FALSE /* lock_anchor */, scratch_pool); 3096 return SVN_NO_ERROR; 3097} 3098