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