1251881Speter/* 2251881Speter * patch.c: patch application support 3251881Speter * 4251881Speter * ==================================================================== 5251881Speter * Licensed to the Apache Software Foundation (ASF) under one 6251881Speter * or more contributor license agreements. See the NOTICE file 7251881Speter * distributed with this work for additional information 8251881Speter * regarding copyright ownership. The ASF licenses this file 9251881Speter * to you under the Apache License, Version 2.0 (the 10251881Speter * "License"); you may not use this file except in compliance 11251881Speter * with the License. You may obtain a copy of the License at 12251881Speter * 13251881Speter * http://www.apache.org/licenses/LICENSE-2.0 14251881Speter * 15251881Speter * Unless required by applicable law or agreed to in writing, 16251881Speter * software distributed under the License is distributed on an 17251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18251881Speter * KIND, either express or implied. See the License for the 19251881Speter * specific language governing permissions and limitations 20251881Speter * under the License. 21251881Speter * ==================================================================== 22251881Speter */ 23251881Speter 24251881Speter/* ==================================================================== */ 25251881Speter 26251881Speter 27251881Speter 28251881Speter/*** Includes. ***/ 29251881Speter 30251881Speter#include <apr_hash.h> 31251881Speter#include <apr_fnmatch.h> 32251881Speter#include "svn_client.h" 33251881Speter#include "svn_dirent_uri.h" 34251881Speter#include "svn_diff.h" 35251881Speter#include "svn_hash.h" 36251881Speter#include "svn_io.h" 37251881Speter#include "svn_path.h" 38251881Speter#include "svn_pools.h" 39251881Speter#include "svn_props.h" 40251881Speter#include "svn_sorts.h" 41251881Speter#include "svn_subst.h" 42251881Speter#include "svn_wc.h" 43251881Speter#include "client.h" 44251881Speter 45251881Speter#include "svn_private_config.h" 46251881Speter#include "private/svn_eol_private.h" 47251881Speter#include "private/svn_wc_private.h" 48251881Speter#include "private/svn_dep_compat.h" 49251881Speter#include "private/svn_string_private.h" 50251881Speter#include "private/svn_subr_private.h" 51299742Sdim#include "private/svn_sorts_private.h" 52251881Speter 53251881Spetertypedef struct hunk_info_t { 54251881Speter /* The hunk. */ 55251881Speter svn_diff_hunk_t *hunk; 56251881Speter 57251881Speter /* The line where the hunk matched in the target file. */ 58251881Speter svn_linenum_t matched_line; 59251881Speter 60251881Speter /* Whether this hunk has been rejected. */ 61251881Speter svn_boolean_t rejected; 62251881Speter 63251881Speter /* Whether this hunk has already been applied (either manually 64251881Speter * or by an earlier run of patch). */ 65251881Speter svn_boolean_t already_applied; 66251881Speter 67251881Speter /* The fuzz factor used when matching this hunk, i.e. how many 68251881Speter * lines of leading and trailing context to ignore during matching. */ 69251881Speter svn_linenum_t fuzz; 70251881Speter} hunk_info_t; 71251881Speter 72251881Speter/* A struct carrying information related to the patched and unpatched 73251881Speter * content of a target, be it a property or the text of a file. */ 74251881Spetertypedef struct target_content_t { 75251881Speter /* Indicates whether unpatched content existed prior to patching. */ 76251881Speter svn_boolean_t existed; 77251881Speter 78251881Speter /* The line last read from the unpatched content. */ 79251881Speter svn_linenum_t current_line; 80251881Speter 81251881Speter /* The EOL-style of the unpatched content. Either 'none', 'fixed', 82251881Speter * or 'native'. See the documentation of svn_subst_eol_style_t. */ 83251881Speter svn_subst_eol_style_t eol_style; 84251881Speter 85251881Speter /* If the EOL_STYLE above is not 'none', this is the EOL string 86251881Speter * corresponding to the EOL-style. Else, it is the EOL string the 87251881Speter * last line read from the target file was using. */ 88251881Speter const char *eol_str; 89251881Speter 90251881Speter /* An array containing apr_off_t offsets marking the beginning of 91251881Speter * each line in the unpatched content. */ 92251881Speter apr_array_header_t *lines; 93251881Speter 94251881Speter /* An array containing hunk_info_t structures for hunks already matched. */ 95251881Speter apr_array_header_t *hunks; 96251881Speter 97251881Speter /* True if end-of-file was reached while reading from the unpatched 98251881Speter * content. */ 99251881Speter svn_boolean_t eof; 100251881Speter 101251881Speter /* The keywords of the target. They will be contracted when reading 102251881Speter * unpatched content and expanded when writing patched content. 103251881Speter * When patching properties this hash is always empty. */ 104251881Speter apr_hash_t *keywords; 105251881Speter 106251881Speter /* A callback, with an associated baton, to read a line of unpatched 107251881Speter * content. */ 108251881Speter svn_error_t *(*readline)(void *baton, svn_stringbuf_t **line, 109251881Speter const char **eol_str, svn_boolean_t *eof, 110251881Speter apr_pool_t *result_pool, apr_pool_t *scratch_pool); 111251881Speter void *read_baton; 112251881Speter 113251881Speter /* A callback to get the current byte offset within the unpatched 114251881Speter * content. Uses the read baton. */ 115251881Speter svn_error_t * (*tell)(void *baton, apr_off_t *offset, 116251881Speter apr_pool_t *scratch_pool); 117251881Speter 118251881Speter /* A callback to seek to an offset within the unpatched content. 119251881Speter * Uses the read baton. */ 120251881Speter svn_error_t * (*seek)(void *baton, apr_off_t offset, 121251881Speter apr_pool_t *scratch_pool); 122251881Speter 123251881Speter /* A callback to write data to the patched content, with an 124251881Speter * associated baton. */ 125251881Speter svn_error_t * (*write)(void *baton, const char *buf, apr_size_t len, 126251881Speter apr_pool_t *scratch_pool); 127251881Speter void *write_baton; 128251881Speter 129251881Speter} target_content_t; 130251881Speter 131251881Spetertypedef struct prop_patch_target_t { 132251881Speter 133251881Speter /* The name of the property */ 134251881Speter const char *name; 135251881Speter 136251881Speter /* The property value. This is NULL in case the property did not exist 137251881Speter * prior to patch application (see also CONTENT->existed). 138251881Speter * Note that the patch implementation does not support binary properties, 139251881Speter * so this string is not expected to contain embedded NUL characters. */ 140251881Speter const svn_string_t *value; 141251881Speter 142251881Speter /* The patched property value. 143251881Speter * This is equivalent to the target, except that in appropriate 144251881Speter * places it contains the modified text as it appears in the patch file. */ 145251881Speter svn_stringbuf_t *patched_value; 146251881Speter 147251881Speter /* All information that is specific to the content of the property. */ 148251881Speter target_content_t *content; 149251881Speter 150251881Speter /* Represents the operation performed on the property. It can be added, 151251881Speter * deleted or modified. 152251881Speter * ### Should we use flags instead since we're not using all enum values? */ 153251881Speter svn_diff_operation_kind_t operation; 154251881Speter 155251881Speter /* ### Here we'll add flags telling if the prop was added, deleted, 156251881Speter * ### had_rejects, had_local_mods prior to patching and so on. */ 157251881Speter} prop_patch_target_t; 158251881Speter 159251881Spetertypedef struct patch_target_t { 160251881Speter /* The target path as it appeared in the patch file, 161251881Speter * but in canonicalised form. */ 162251881Speter const char *canon_path_from_patchfile; 163251881Speter 164251881Speter /* The target path, relative to the working copy directory the 165251881Speter * patch is being applied to. A patch strip count applies to this 166251881Speter * and only this path. This is never NULL. */ 167251881Speter const char *local_relpath; 168251881Speter 169251881Speter /* The absolute path of the target on the filesystem. 170251881Speter * Any symlinks the path from the patch file may contain are resolved. 171251881Speter * Is not always known, so it may be NULL. */ 172251881Speter const char *local_abspath; 173251881Speter 174251881Speter /* The target file, read-only. This is NULL in case the target 175251881Speter * file did not exist prior to patch application (see also 176251881Speter * CONTENT->existed). */ 177251881Speter apr_file_t *file; 178251881Speter 179251881Speter /* The target file is a symlink */ 180251881Speter svn_boolean_t is_symlink; 181251881Speter 182251881Speter /* The patched file. 183251881Speter * This is equivalent to the target, except that in appropriate 184251881Speter * places it contains the modified text as it appears in the patch file. 185251881Speter * The data in this file is written in repository-normal form. 186251881Speter * EOL transformation and keyword contraction is performed when the 187251881Speter * patched result is installed in the working copy. */ 188251881Speter apr_file_t *patched_file; 189251881Speter 190251881Speter /* Path to the patched file. */ 191251881Speter const char *patched_path; 192251881Speter 193251881Speter /* Hunks that are rejected will be written to this file. */ 194251881Speter apr_file_t *reject_file; 195251881Speter 196251881Speter /* Path to the reject file. */ 197251881Speter const char *reject_path; 198251881Speter 199251881Speter /* The node kind of the target as found in WC-DB prior 200251881Speter * to patch application. */ 201251881Speter svn_node_kind_t db_kind; 202251881Speter 203251881Speter /* The target's kind on disk prior to patch application. */ 204251881Speter svn_node_kind_t kind_on_disk; 205251881Speter 206251881Speter /* True if the target was locally deleted prior to patching. */ 207251881Speter svn_boolean_t locally_deleted; 208251881Speter 209251881Speter /* True if the target had to be skipped for some reason. */ 210251881Speter svn_boolean_t skipped; 211251881Speter 212251881Speter /* True if at least one hunk was rejected. */ 213251881Speter svn_boolean_t had_rejects; 214251881Speter 215251881Speter /* True if at least one property hunk was rejected. */ 216251881Speter svn_boolean_t had_prop_rejects; 217251881Speter 218251881Speter /* True if the target file had local modifications before the 219251881Speter * patch was applied to it. */ 220251881Speter svn_boolean_t local_mods; 221251881Speter 222251881Speter /* True if the target was added by the patch, which means that it did 223251881Speter * not exist on disk before patching and has content after patching. */ 224251881Speter svn_boolean_t added; 225251881Speter 226251881Speter /* True if the target ended up being deleted by the patch. */ 227251881Speter svn_boolean_t deleted; 228251881Speter 229251881Speter /* True if the target ended up being replaced by the patch 230251881Speter * (i.e. a new file was added on top locally deleted node). */ 231251881Speter svn_boolean_t replaced; 232251881Speter 233299742Sdim /* Set if the target is supposed to be moved by the patch. 234299742Sdim * This applies to --git diffs which carry "rename from/to" headers. */ 235299742Sdim const char *move_target_abspath; 236299742Sdim 237251881Speter /* True if the target has the executable bit set. */ 238251881Speter svn_boolean_t executable; 239251881Speter 240251881Speter /* True if the patch changed the text of the target. */ 241251881Speter svn_boolean_t has_text_changes; 242251881Speter 243251881Speter /* True if the patch changed any of the properties of the target. */ 244251881Speter svn_boolean_t has_prop_changes; 245251881Speter 246251881Speter /* True if the patch contained a svn:special property. */ 247251881Speter svn_boolean_t is_special; 248251881Speter 249251881Speter /* All the information that is specific to the content of the target. */ 250251881Speter target_content_t *content; 251251881Speter 252251881Speter /* A hash table of prop_patch_target_t objects keyed by property names. */ 253251881Speter apr_hash_t *prop_targets; 254251881Speter 255251881Speter} patch_target_t; 256251881Speter 257251881Speter 258251881Speter/* A smaller struct containing a subset of patch_target_t. 259251881Speter * Carries the minimal amount of information we still need for a 260251881Speter * target after we're done patching it so we can free other resources. */ 261251881Spetertypedef struct patch_target_info_t { 262251881Speter const char *local_abspath; 263251881Speter svn_boolean_t deleted; 264251881Speter} patch_target_info_t; 265251881Speter 266251881Speter 267251881Speter/* Strip STRIP_COUNT components from the front of PATH, returning 268251881Speter * the result in *RESULT, allocated in RESULT_POOL. 269251881Speter * Do temporary allocations in SCRATCH_POOL. */ 270251881Speterstatic svn_error_t * 271251881Speterstrip_path(const char **result, const char *path, int strip_count, 272251881Speter apr_pool_t *result_pool, apr_pool_t *scratch_pool) 273251881Speter{ 274251881Speter int i; 275251881Speter apr_array_header_t *components; 276251881Speter apr_array_header_t *stripped; 277251881Speter 278251881Speter components = svn_path_decompose(path, scratch_pool); 279251881Speter if (strip_count > components->nelts) 280251881Speter return svn_error_createf(SVN_ERR_CLIENT_PATCH_BAD_STRIP_COUNT, NULL, 281251881Speter _("Cannot strip %u components from '%s'"), 282251881Speter strip_count, 283251881Speter svn_dirent_local_style(path, scratch_pool)); 284251881Speter 285251881Speter stripped = apr_array_make(scratch_pool, components->nelts - strip_count, 286251881Speter sizeof(const char *)); 287251881Speter for (i = strip_count; i < components->nelts; i++) 288251881Speter { 289251881Speter const char *component; 290251881Speter 291251881Speter component = APR_ARRAY_IDX(components, i, const char *); 292251881Speter APR_ARRAY_PUSH(stripped, const char *) = component; 293251881Speter } 294251881Speter 295251881Speter *result = svn_path_compose(stripped, result_pool); 296251881Speter 297251881Speter return SVN_NO_ERROR; 298251881Speter} 299251881Speter 300251881Speter/* Obtain KEYWORDS, EOL_STYLE and EOL_STR for LOCAL_ABSPATH. 301251881Speter * WC_CTX is a context for the working copy the patch is applied to. 302251881Speter * Use RESULT_POOL for allocations of fields in TARGET. 303251881Speter * Use SCRATCH_POOL for all other allocations. */ 304251881Speterstatic svn_error_t * 305251881Speterobtain_eol_and_keywords_for_file(apr_hash_t **keywords, 306251881Speter svn_subst_eol_style_t *eol_style, 307251881Speter const char **eol_str, 308251881Speter svn_wc_context_t *wc_ctx, 309251881Speter const char *local_abspath, 310251881Speter apr_pool_t *result_pool, 311251881Speter apr_pool_t *scratch_pool) 312251881Speter{ 313251881Speter apr_hash_t *props; 314251881Speter svn_string_t *keywords_val, *eol_style_val; 315251881Speter 316251881Speter SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, local_abspath, 317251881Speter scratch_pool, scratch_pool)); 318251881Speter keywords_val = svn_hash_gets(props, SVN_PROP_KEYWORDS); 319251881Speter if (keywords_val) 320251881Speter { 321251881Speter svn_revnum_t changed_rev; 322251881Speter apr_time_t changed_date; 323251881Speter const char *rev_str; 324251881Speter const char *author; 325251881Speter const char *url; 326299742Sdim const char *repos_root_url; 327299742Sdim const char *repos_relpath; 328251881Speter 329251881Speter SVN_ERR(svn_wc__node_get_changed_info(&changed_rev, 330251881Speter &changed_date, 331251881Speter &author, wc_ctx, 332251881Speter local_abspath, 333251881Speter scratch_pool, 334251881Speter scratch_pool)); 335251881Speter rev_str = apr_psprintf(scratch_pool, "%ld", changed_rev); 336299742Sdim SVN_ERR(svn_wc__node_get_repos_info(NULL, &repos_relpath, &repos_root_url, 337299742Sdim NULL, 338251881Speter wc_ctx, local_abspath, 339251881Speter scratch_pool, scratch_pool)); 340299742Sdim url = svn_path_url_add_component2(repos_root_url, repos_relpath, 341299742Sdim scratch_pool); 342299742Sdim 343251881Speter SVN_ERR(svn_subst_build_keywords3(keywords, 344251881Speter keywords_val->data, 345299742Sdim rev_str, url, repos_root_url, 346299742Sdim changed_date, 347251881Speter author, result_pool)); 348251881Speter } 349251881Speter 350251881Speter eol_style_val = svn_hash_gets(props, SVN_PROP_EOL_STYLE); 351251881Speter if (eol_style_val) 352251881Speter { 353251881Speter svn_subst_eol_style_from_value(eol_style, 354251881Speter eol_str, 355251881Speter eol_style_val->data); 356251881Speter } 357251881Speter 358251881Speter return SVN_NO_ERROR; 359251881Speter} 360251881Speter 361251881Speter/* Resolve the exact path for a patch TARGET at path PATH_FROM_PATCHFILE, 362251881Speter * which is the path of the target as it appeared in the patch file. 363251881Speter * Put a canonicalized version of PATH_FROM_PATCHFILE into 364251881Speter * TARGET->CANON_PATH_FROM_PATCHFILE. 365251881Speter * WC_CTX is a context for the working copy the patch is applied to. 366251881Speter * If possible, determine TARGET->WC_PATH, TARGET->ABS_PATH, TARGET->KIND, 367251881Speter * TARGET->ADDED, and TARGET->PARENT_DIR_EXISTS. 368251881Speter * Indicate in TARGET->SKIPPED whether the target should be skipped. 369251881Speter * STRIP_COUNT specifies the number of leading path components 370251881Speter * which should be stripped from target paths in the patch. 371251881Speter * PROP_CHANGES_ONLY specifies whether the target path is allowed to have 372251881Speter * only property changes, and no content changes (in which case the target 373251881Speter * must be a directory). 374251881Speter * Use RESULT_POOL for allocations of fields in TARGET. 375251881Speter * Use SCRATCH_POOL for all other allocations. */ 376251881Speterstatic svn_error_t * 377251881Speterresolve_target_path(patch_target_t *target, 378251881Speter const char *path_from_patchfile, 379251881Speter const char *wcroot_abspath, 380251881Speter int strip_count, 381251881Speter svn_boolean_t prop_changes_only, 382251881Speter svn_wc_context_t *wc_ctx, 383251881Speter apr_pool_t *result_pool, 384251881Speter apr_pool_t *scratch_pool) 385251881Speter{ 386251881Speter const char *stripped_path; 387251881Speter svn_wc_status3_t *status; 388251881Speter svn_error_t *err; 389251881Speter svn_boolean_t under_root; 390251881Speter 391251881Speter target->canon_path_from_patchfile = svn_dirent_internal_style( 392251881Speter path_from_patchfile, result_pool); 393251881Speter 394251881Speter /* We allow properties to be set on the wc root dir. */ 395251881Speter if (! prop_changes_only && target->canon_path_from_patchfile[0] == '\0') 396251881Speter { 397251881Speter /* An empty patch target path? What gives? Skip this. */ 398251881Speter target->skipped = TRUE; 399251881Speter target->local_abspath = NULL; 400251881Speter target->local_relpath = ""; 401251881Speter return SVN_NO_ERROR; 402251881Speter } 403251881Speter 404251881Speter if (strip_count > 0) 405251881Speter SVN_ERR(strip_path(&stripped_path, target->canon_path_from_patchfile, 406251881Speter strip_count, result_pool, scratch_pool)); 407251881Speter else 408251881Speter stripped_path = target->canon_path_from_patchfile; 409251881Speter 410251881Speter if (svn_dirent_is_absolute(stripped_path)) 411251881Speter { 412251881Speter target->local_relpath = svn_dirent_is_child(wcroot_abspath, 413251881Speter stripped_path, 414251881Speter result_pool); 415251881Speter 416251881Speter if (! target->local_relpath) 417251881Speter { 418251881Speter /* The target path is either outside of the working copy 419251881Speter * or it is the working copy itself. Skip it. */ 420251881Speter target->skipped = TRUE; 421251881Speter target->local_abspath = NULL; 422251881Speter target->local_relpath = stripped_path; 423251881Speter return SVN_NO_ERROR; 424251881Speter } 425251881Speter } 426251881Speter else 427251881Speter { 428251881Speter target->local_relpath = stripped_path; 429251881Speter } 430251881Speter 431251881Speter /* Make sure the path is secure to use. We want the target to be inside 432251881Speter * of the working copy and not be fooled by symlinks it might contain. */ 433251881Speter SVN_ERR(svn_dirent_is_under_root(&under_root, 434251881Speter &target->local_abspath, wcroot_abspath, 435251881Speter target->local_relpath, result_pool)); 436251881Speter 437251881Speter if (! under_root) 438251881Speter { 439251881Speter /* The target path is outside of the working copy. Skip it. */ 440251881Speter target->skipped = TRUE; 441251881Speter target->local_abspath = NULL; 442251881Speter return SVN_NO_ERROR; 443251881Speter } 444251881Speter 445251881Speter /* Skip things we should not be messing with. */ 446251881Speter err = svn_wc_status3(&status, wc_ctx, target->local_abspath, 447251881Speter result_pool, scratch_pool); 448251881Speter if (err) 449251881Speter { 450251881Speter if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) 451251881Speter return svn_error_trace(err); 452251881Speter 453251881Speter svn_error_clear(err); 454251881Speter 455251881Speter target->locally_deleted = TRUE; 456251881Speter target->db_kind = svn_node_none; 457251881Speter status = NULL; 458251881Speter } 459251881Speter else if (status->node_status == svn_wc_status_ignored || 460251881Speter status->node_status == svn_wc_status_unversioned || 461251881Speter status->node_status == svn_wc_status_missing || 462251881Speter status->node_status == svn_wc_status_obstructed || 463251881Speter status->conflicted) 464251881Speter { 465251881Speter target->skipped = TRUE; 466251881Speter return SVN_NO_ERROR; 467251881Speter } 468251881Speter else if (status->node_status == svn_wc_status_deleted) 469251881Speter { 470251881Speter target->locally_deleted = TRUE; 471251881Speter } 472251881Speter 473251881Speter if (status && (status->kind != svn_node_unknown)) 474251881Speter target->db_kind = status->kind; 475251881Speter else 476251881Speter target->db_kind = svn_node_none; 477251881Speter 478251881Speter SVN_ERR(svn_io_check_special_path(target->local_abspath, 479251881Speter &target->kind_on_disk, &target->is_symlink, 480251881Speter scratch_pool)); 481251881Speter 482251881Speter if (target->locally_deleted) 483251881Speter { 484251881Speter const char *moved_to_abspath; 485251881Speter 486251881Speter SVN_ERR(svn_wc__node_was_moved_away(&moved_to_abspath, NULL, 487251881Speter wc_ctx, target->local_abspath, 488251881Speter result_pool, scratch_pool)); 489299742Sdim /* ### BUG: moved_to_abspath contains the target where the op-root was 490299742Sdim ### moved to... not the target itself! */ 491251881Speter if (moved_to_abspath) 492251881Speter { 493251881Speter target->local_abspath = moved_to_abspath; 494251881Speter target->local_relpath = svn_dirent_skip_ancestor(wcroot_abspath, 495251881Speter moved_to_abspath); 496251881Speter SVN_ERR_ASSERT(target->local_relpath && 497251881Speter target->local_relpath[0] != '\0'); 498251881Speter 499251881Speter /* As far as we are concerned this target is not locally deleted. */ 500251881Speter target->locally_deleted = FALSE; 501251881Speter 502251881Speter SVN_ERR(svn_io_check_special_path(target->local_abspath, 503251881Speter &target->kind_on_disk, 504251881Speter &target->is_symlink, 505251881Speter scratch_pool)); 506251881Speter } 507251881Speter else if (target->kind_on_disk != svn_node_none) 508251881Speter { 509251881Speter target->skipped = TRUE; 510251881Speter return SVN_NO_ERROR; 511251881Speter } 512251881Speter } 513251881Speter 514251881Speter return SVN_NO_ERROR; 515251881Speter} 516251881Speter 517251881Speter/* Baton for reading from properties. */ 518251881Spetertypedef struct prop_read_baton_t { 519251881Speter const svn_string_t *value; 520251881Speter apr_off_t offset; 521251881Speter} prop_read_baton_t; 522251881Speter 523251881Speter/* Allocate *STRINGBUF in RESULT_POOL, and read into it one line from 524251881Speter * the unpatched property value accessed via BATON. 525251881Speter * Reading stops either after a line-terminator was found, or if 526251881Speter * the property value runs out in which case *EOF is set to TRUE. 527251881Speter * The line-terminator is not stored in *STRINGBUF. 528251881Speter * 529251881Speter * If the line is empty or could not be read, *line is set to NULL. 530251881Speter * 531251881Speter * The line-terminator is detected automatically and stored in *EOL 532251881Speter * if EOL is not NULL. If the end of the property value is reached 533251881Speter * and does not end with a newline character, and EOL is not NULL, 534251881Speter * *EOL is set to NULL. 535251881Speter * 536251881Speter * SCRATCH_POOL is used for temporary allocations. 537251881Speter */ 538251881Speterstatic svn_error_t * 539251881Speterreadline_prop(void *baton, svn_stringbuf_t **line, const char **eol_str, 540251881Speter svn_boolean_t *eof, apr_pool_t *result_pool, 541251881Speter apr_pool_t *scratch_pool) 542251881Speter{ 543251881Speter prop_read_baton_t *b = (prop_read_baton_t *)baton; 544251881Speter svn_stringbuf_t *str = NULL; 545251881Speter const char *c; 546251881Speter svn_boolean_t found_eof; 547251881Speter 548251881Speter if ((apr_uint64_t)b->offset >= (apr_uint64_t)b->value->len) 549251881Speter { 550251881Speter *eol_str = NULL; 551251881Speter *eof = TRUE; 552251881Speter *line = NULL; 553251881Speter return SVN_NO_ERROR; 554251881Speter } 555251881Speter 556251881Speter /* Read bytes into STR up to and including, but not storing, 557251881Speter * the next EOL sequence. */ 558251881Speter *eol_str = NULL; 559251881Speter found_eof = FALSE; 560251881Speter do 561251881Speter { 562251881Speter c = b->value->data + b->offset; 563251881Speter b->offset++; 564251881Speter 565251881Speter if (*c == '\0') 566251881Speter { 567251881Speter found_eof = TRUE; 568251881Speter break; 569251881Speter } 570251881Speter else if (*c == '\n') 571251881Speter { 572251881Speter *eol_str = "\n"; 573251881Speter } 574251881Speter else if (*c == '\r') 575251881Speter { 576251881Speter *eol_str = "\r"; 577251881Speter if (*(c + 1) == '\n') 578251881Speter { 579251881Speter *eol_str = "\r\n"; 580251881Speter b->offset++; 581251881Speter } 582251881Speter } 583251881Speter else 584251881Speter { 585251881Speter if (str == NULL) 586251881Speter str = svn_stringbuf_create_ensure(80, result_pool); 587251881Speter svn_stringbuf_appendbyte(str, *c); 588251881Speter } 589251881Speter 590251881Speter if (*eol_str) 591251881Speter break; 592251881Speter } 593251881Speter while (c < b->value->data + b->value->len); 594251881Speter 595251881Speter if (eof) 596251881Speter *eof = found_eof; 597251881Speter *line = str; 598251881Speter 599251881Speter return SVN_NO_ERROR; 600251881Speter} 601251881Speter 602251881Speter/* Return in *OFFSET the current byte offset for reading from the 603251881Speter * unpatched property value accessed via BATON. 604251881Speter * Use SCRATCH_POOL for temporary allocations. */ 605251881Speterstatic svn_error_t * 606251881Spetertell_prop(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool) 607251881Speter{ 608251881Speter prop_read_baton_t *b = (prop_read_baton_t *)baton; 609251881Speter *offset = b->offset; 610251881Speter return SVN_NO_ERROR; 611251881Speter} 612251881Speter 613251881Speter/* Seek to the specified by OFFSET in the unpatched property value accessed 614251881Speter * via BATON. Use SCRATCH_POOL for temporary allocations. */ 615251881Speterstatic svn_error_t * 616251881Speterseek_prop(void *baton, apr_off_t offset, apr_pool_t *scratch_pool) 617251881Speter{ 618251881Speter prop_read_baton_t *b = (prop_read_baton_t *)baton; 619251881Speter b->offset = offset; 620251881Speter return SVN_NO_ERROR; 621251881Speter} 622251881Speter 623251881Speter/* Write LEN bytes from BUF into the patched property value accessed 624251881Speter * via BATON. Use SCRATCH_POOL for temporary allocations. */ 625251881Speterstatic svn_error_t * 626251881Speterwrite_prop(void *baton, const char *buf, apr_size_t len, 627251881Speter apr_pool_t *scratch_pool) 628251881Speter{ 629251881Speter svn_stringbuf_t *patched_value = (svn_stringbuf_t *)baton; 630251881Speter svn_stringbuf_appendbytes(patched_value, buf, len); 631251881Speter return SVN_NO_ERROR; 632251881Speter} 633251881Speter 634251881Speter/* Initialize a PROP_TARGET structure for PROP_NAME on the patch target 635251881Speter * at LOCAL_ABSPATH. OPERATION indicates the operation performed on the 636251881Speter * property. Use working copy context WC_CTX. 637251881Speter * Allocate results in RESULT_POOL. 638251881Speter * Use SCRATCH_POOL for temporary allocations. */ 639251881Speterstatic svn_error_t * 640251881Speterinit_prop_target(prop_patch_target_t **prop_target, 641251881Speter const char *prop_name, 642251881Speter svn_diff_operation_kind_t operation, 643251881Speter svn_wc_context_t *wc_ctx, 644251881Speter const char *local_abspath, 645251881Speter apr_pool_t *result_pool, apr_pool_t *scratch_pool) 646251881Speter{ 647251881Speter prop_patch_target_t *new_prop_target; 648251881Speter target_content_t *content; 649251881Speter const svn_string_t *value; 650251881Speter svn_error_t *err; 651251881Speter prop_read_baton_t *prop_read_baton; 652251881Speter 653251881Speter content = apr_pcalloc(result_pool, sizeof(*content)); 654251881Speter 655251881Speter /* All other fields are FALSE or NULL due to apr_pcalloc(). */ 656251881Speter content->current_line = 1; 657251881Speter content->eol_style = svn_subst_eol_style_none; 658251881Speter content->lines = apr_array_make(result_pool, 0, sizeof(apr_off_t)); 659251881Speter content->hunks = apr_array_make(result_pool, 0, sizeof(hunk_info_t *)); 660251881Speter content->keywords = apr_hash_make(result_pool); 661251881Speter 662251881Speter new_prop_target = apr_pcalloc(result_pool, sizeof(*new_prop_target)); 663251881Speter new_prop_target->name = apr_pstrdup(result_pool, prop_name); 664251881Speter new_prop_target->operation = operation; 665251881Speter new_prop_target->content = content; 666251881Speter 667251881Speter err = svn_wc_prop_get2(&value, wc_ctx, local_abspath, prop_name, 668251881Speter result_pool, scratch_pool); 669251881Speter if (err) 670251881Speter { 671251881Speter if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) 672251881Speter { 673251881Speter svn_error_clear(err); 674251881Speter value = NULL; 675251881Speter } 676251881Speter else 677251881Speter return svn_error_trace(err); 678251881Speter } 679251881Speter content->existed = (value != NULL); 680251881Speter new_prop_target->value = value; 681251881Speter new_prop_target->patched_value = svn_stringbuf_create_empty(result_pool); 682251881Speter 683251881Speter 684251881Speter /* Wire up the read and write callbacks. */ 685251881Speter prop_read_baton = apr_pcalloc(result_pool, sizeof(*prop_read_baton)); 686251881Speter prop_read_baton->value = value; 687251881Speter prop_read_baton->offset = 0; 688251881Speter content->readline = readline_prop; 689251881Speter content->tell = tell_prop; 690251881Speter content->seek = seek_prop; 691251881Speter content->read_baton = prop_read_baton; 692251881Speter content->write = write_prop; 693251881Speter content->write_baton = new_prop_target->patched_value; 694251881Speter 695251881Speter *prop_target = new_prop_target; 696251881Speter 697251881Speter return SVN_NO_ERROR; 698251881Speter} 699251881Speter 700251881Speter/* Allocate *STRINGBUF in RESULT_POOL, and read into it one line from 701251881Speter * the unpatched file content accessed via BATON. 702251881Speter * Reading stops either after a line-terminator was found, 703251881Speter * or if EOF is reached in which case *EOF is set to TRUE. 704251881Speter * The line-terminator is not stored in *STRINGBUF. 705251881Speter * 706251881Speter * If the line is empty or could not be read, *line is set to NULL. 707251881Speter * 708251881Speter * The line-terminator is detected automatically and stored in *EOL 709251881Speter * if EOL is not NULL. If EOF is reached and FILE does not end 710251881Speter * with a newline character, and EOL is not NULL, *EOL is set to NULL. 711251881Speter * 712251881Speter * SCRATCH_POOL is used for temporary allocations. 713251881Speter */ 714251881Speterstatic svn_error_t * 715251881Speterreadline_file(void *baton, svn_stringbuf_t **line, const char **eol_str, 716251881Speter svn_boolean_t *eof, apr_pool_t *result_pool, 717251881Speter apr_pool_t *scratch_pool) 718251881Speter{ 719251881Speter apr_file_t *file = (apr_file_t *)baton; 720251881Speter svn_stringbuf_t *str = NULL; 721251881Speter apr_size_t numbytes; 722251881Speter char c; 723251881Speter svn_boolean_t found_eof; 724251881Speter 725251881Speter /* Read bytes into STR up to and including, but not storing, 726251881Speter * the next EOL sequence. */ 727251881Speter *eol_str = NULL; 728251881Speter numbytes = 1; 729251881Speter found_eof = FALSE; 730251881Speter while (!found_eof) 731251881Speter { 732251881Speter SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes, 733251881Speter &found_eof, scratch_pool)); 734251881Speter if (numbytes != 1) 735251881Speter { 736251881Speter found_eof = TRUE; 737251881Speter break; 738251881Speter } 739251881Speter 740251881Speter if (c == '\n') 741251881Speter { 742251881Speter *eol_str = "\n"; 743251881Speter } 744251881Speter else if (c == '\r') 745251881Speter { 746251881Speter *eol_str = "\r"; 747251881Speter 748251881Speter if (!found_eof) 749251881Speter { 750251881Speter apr_off_t pos; 751251881Speter 752251881Speter /* Check for "\r\n" by peeking at the next byte. */ 753251881Speter pos = 0; 754251881Speter SVN_ERR(svn_io_file_seek(file, APR_CUR, &pos, scratch_pool)); 755251881Speter SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes, 756251881Speter &found_eof, scratch_pool)); 757251881Speter if (numbytes == 1 && c == '\n') 758251881Speter { 759251881Speter *eol_str = "\r\n"; 760251881Speter } 761251881Speter else 762251881Speter { 763251881Speter /* Pretend we never peeked. */ 764251881Speter SVN_ERR(svn_io_file_seek(file, APR_SET, &pos, scratch_pool)); 765251881Speter found_eof = FALSE; 766251881Speter numbytes = 1; 767251881Speter } 768251881Speter } 769251881Speter } 770251881Speter else 771251881Speter { 772251881Speter if (str == NULL) 773251881Speter str = svn_stringbuf_create_ensure(80, result_pool); 774251881Speter svn_stringbuf_appendbyte(str, c); 775251881Speter } 776251881Speter 777251881Speter if (*eol_str) 778251881Speter break; 779251881Speter } 780251881Speter 781251881Speter if (eof) 782251881Speter *eof = found_eof; 783251881Speter *line = str; 784251881Speter 785251881Speter return SVN_NO_ERROR; 786251881Speter} 787251881Speter 788251881Speter/* Return in *OFFSET the current byte offset for reading from the 789251881Speter * unpatched file content accessed via BATON. 790251881Speter * Use SCRATCH_POOL for temporary allocations. */ 791251881Speterstatic svn_error_t * 792251881Spetertell_file(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool) 793251881Speter{ 794251881Speter apr_file_t *file = (apr_file_t *)baton; 795251881Speter *offset = 0; 796251881Speter SVN_ERR(svn_io_file_seek(file, APR_CUR, offset, scratch_pool)); 797251881Speter return SVN_NO_ERROR; 798251881Speter} 799251881Speter 800251881Speter/* Seek to the specified by OFFSET in the unpatched file content accessed 801251881Speter * via BATON. Use SCRATCH_POOL for temporary allocations. */ 802251881Speterstatic svn_error_t * 803251881Speterseek_file(void *baton, apr_off_t offset, apr_pool_t *scratch_pool) 804251881Speter{ 805251881Speter apr_file_t *file = (apr_file_t *)baton; 806251881Speter SVN_ERR(svn_io_file_seek(file, APR_SET, &offset, scratch_pool)); 807251881Speter return SVN_NO_ERROR; 808251881Speter} 809251881Speter 810251881Speter/* Write LEN bytes from BUF into the patched file content accessed 811251881Speter * via BATON. Use SCRATCH_POOL for temporary allocations. */ 812251881Speterstatic svn_error_t * 813251881Speterwrite_file(void *baton, const char *buf, apr_size_t len, 814251881Speter apr_pool_t *scratch_pool) 815251881Speter{ 816251881Speter apr_file_t *file = (apr_file_t *)baton; 817251881Speter SVN_ERR(svn_io_file_write_full(file, buf, len, &len, scratch_pool)); 818251881Speter return SVN_NO_ERROR; 819251881Speter} 820251881Speter 821251881Speter/* Handling symbolic links: 822251881Speter * 823251881Speter * In Subversion, symlinks can be represented on disk in two distinct ways. 824251881Speter * On systems which support symlinks, a symlink is created on disk. 825251881Speter * On systems which do not support symlink, a file is created on disk 826251881Speter * which contains the "normal form" of the symlink, which looks like: 827251881Speter * link TARGET 828251881Speter * where TARGET is the file the symlink points to. 829251881Speter * 830251881Speter * When reading symlinks (i.e. the link itself, not the file the symlink 831251881Speter * is pointing to) through the svn_subst_create_specialfile() function 832251881Speter * into a buffer, the buffer always contains the "normal form" of the symlink. 833251881Speter * Due to this representation symlinks always contain a single line of text. 834251881Speter * 835251881Speter * The functions below are needed to deal with the case where a patch 836251881Speter * wants to change the TARGET that a symlink points to. 837251881Speter */ 838251881Speter 839251881Speter/* Baton for the (readline|tell|seek|write)_symlink functions. */ 840251881Speterstruct symlink_baton_t 841251881Speter{ 842251881Speter /* The path to the symlink on disk (not the path to the target of the link) */ 843251881Speter const char *local_abspath; 844251881Speter 845251881Speter /* Indicates whether the "normal form" of the symlink has been read. */ 846251881Speter svn_boolean_t at_eof; 847251881Speter}; 848251881Speter 849251881Speter/* Allocate *STRINGBUF in RESULT_POOL, and store into it the "normal form" 850251881Speter * of the symlink accessed via BATON. 851251881Speter * 852251881Speter * Otherwise behaves like readline_file(), which see. 853251881Speter */ 854251881Speterstatic svn_error_t * 855251881Speterreadline_symlink(void *baton, svn_stringbuf_t **line, const char **eol_str, 856251881Speter svn_boolean_t *eof, apr_pool_t *result_pool, 857251881Speter apr_pool_t *scratch_pool) 858251881Speter{ 859251881Speter struct symlink_baton_t *sb = baton; 860251881Speter 861251881Speter if (eof) 862251881Speter *eof = TRUE; 863251881Speter if (eol_str) 864251881Speter *eol_str = NULL; 865251881Speter 866251881Speter if (sb->at_eof) 867251881Speter { 868251881Speter *line = NULL; 869251881Speter } 870251881Speter else 871251881Speter { 872251881Speter svn_string_t *dest; 873251881Speter 874251881Speter SVN_ERR(svn_io_read_link(&dest, sb->local_abspath, scratch_pool)); 875251881Speter *line = svn_stringbuf_createf(result_pool, "link %s", dest->data); 876251881Speter sb->at_eof = TRUE; 877251881Speter } 878251881Speter 879251881Speter return SVN_NO_ERROR; 880251881Speter} 881251881Speter 882251881Speter/* Set *OFFSET to 1 or 0 depending on whether the "normal form" of 883251881Speter * the symlink has already been read. */ 884251881Speterstatic svn_error_t * 885251881Spetertell_symlink(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool) 886251881Speter{ 887251881Speter struct symlink_baton_t *sb = baton; 888251881Speter 889251881Speter *offset = sb->at_eof ? 1 : 0; 890251881Speter return SVN_NO_ERROR; 891251881Speter} 892251881Speter 893251881Speter/* If offset is non-zero, mark the symlink as having been read in its 894251881Speter * "normal form". Else, mark the symlink as not having been read yet. */ 895251881Speterstatic svn_error_t * 896251881Speterseek_symlink(void *baton, apr_off_t offset, apr_pool_t *scratch_pool) 897251881Speter{ 898251881Speter struct symlink_baton_t *sb = baton; 899251881Speter 900251881Speter sb->at_eof = (offset != 0); 901251881Speter return SVN_NO_ERROR; 902251881Speter} 903251881Speter 904251881Speter 905251881Speter/* Set the target of the symlink accessed via BATON. 906251881Speter * The contents of BUF must be a valid "normal form" of a symlink. */ 907251881Speterstatic svn_error_t * 908251881Speterwrite_symlink(void *baton, const char *buf, apr_size_t len, 909251881Speter apr_pool_t *scratch_pool) 910251881Speter{ 911251881Speter const char *target_abspath = baton; 912251881Speter const char *new_name; 913251881Speter const char *link = apr_pstrndup(scratch_pool, buf, len); 914251881Speter 915251881Speter if (strncmp(link, "link ", 5) != 0) 916251881Speter return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, 917251881Speter _("Invalid link representation")); 918251881Speter 919251881Speter link += 5; /* Skip "link " */ 920251881Speter 921251881Speter /* We assume the entire symlink is written at once, as the patch 922251881Speter format is line based */ 923251881Speter 924251881Speter SVN_ERR(svn_io_create_unique_link(&new_name, target_abspath, link, 925251881Speter ".tmp", scratch_pool)); 926251881Speter 927251881Speter SVN_ERR(svn_io_file_rename(new_name, target_abspath, scratch_pool)); 928251881Speter 929251881Speter return SVN_NO_ERROR; 930251881Speter} 931251881Speter 932251881Speter 933251881Speter/* Return a suitable filename for the target of PATCH. 934251881Speter * Examine the ``old'' and ``new'' file names, and choose the file name 935251881Speter * with the fewest path components, the shortest basename, and the shortest 936251881Speter * total file name length (in that order). In case of a tie, return the new 937251881Speter * filename. This heuristic is also used by Larry Wall's UNIX patch (except 938251881Speter * that it prompts for a filename in case of a tie). 939251881Speter * Additionally, for compatibility with git, if one of the filenames 940251881Speter * is "/dev/null", use the other filename. */ 941251881Speterstatic const char * 942251881Speterchoose_target_filename(const svn_patch_t *patch) 943251881Speter{ 944251881Speter apr_size_t old; 945251881Speter apr_size_t new; 946251881Speter 947251881Speter if (strcmp(patch->old_filename, "/dev/null") == 0) 948251881Speter return patch->new_filename; 949251881Speter if (strcmp(patch->new_filename, "/dev/null") == 0) 950251881Speter return patch->old_filename; 951251881Speter 952299742Sdim /* If the patch renames the target, use the old name while 953299742Sdim * applying hunks. The target will be renamed to the new name 954299742Sdim * after hunks have been applied. */ 955299742Sdim if (patch->operation == svn_diff_op_moved) 956299742Sdim return patch->old_filename; 957299742Sdim 958251881Speter old = svn_path_component_count(patch->old_filename); 959251881Speter new = svn_path_component_count(patch->new_filename); 960251881Speter 961251881Speter if (old == new) 962251881Speter { 963251881Speter old = strlen(svn_dirent_basename(patch->old_filename, NULL)); 964251881Speter new = strlen(svn_dirent_basename(patch->new_filename, NULL)); 965251881Speter 966251881Speter if (old == new) 967251881Speter { 968251881Speter old = strlen(patch->old_filename); 969251881Speter new = strlen(patch->new_filename); 970251881Speter } 971251881Speter } 972251881Speter 973251881Speter return (old < new) ? patch->old_filename : patch->new_filename; 974251881Speter} 975251881Speter 976251881Speter/* Attempt to initialize a *PATCH_TARGET structure for a target file 977251881Speter * described by PATCH. Use working copy context WC_CTX. 978251881Speter * STRIP_COUNT specifies the number of leading path components 979251881Speter * which should be stripped from target paths in the patch. 980251881Speter * The patch target structure is allocated in RESULT_POOL, but if the target 981251881Speter * should be skipped, PATCH_TARGET->SKIPPED is set and the target should be 982251881Speter * treated as not fully initialized, e.g. the caller should not not do any 983251881Speter * further operations on the target if it is marked to be skipped. 984251881Speter * If REMOVE_TEMPFILES is TRUE, set up temporary files to be removed as 985251881Speter * soon as they are no longer needed. 986251881Speter * Use SCRATCH_POOL for all other allocations. */ 987251881Speterstatic svn_error_t * 988251881Speterinit_patch_target(patch_target_t **patch_target, 989251881Speter const svn_patch_t *patch, 990251881Speter const char *wcroot_abspath, 991251881Speter svn_wc_context_t *wc_ctx, int strip_count, 992251881Speter svn_boolean_t remove_tempfiles, 993251881Speter apr_pool_t *result_pool, apr_pool_t *scratch_pool) 994251881Speter{ 995251881Speter patch_target_t *target; 996251881Speter target_content_t *content; 997251881Speter svn_boolean_t has_prop_changes = FALSE; 998251881Speter svn_boolean_t prop_changes_only = FALSE; 999251881Speter 1000251881Speter { 1001251881Speter apr_hash_index_t *hi; 1002251881Speter 1003251881Speter for (hi = apr_hash_first(scratch_pool, patch->prop_patches); 1004251881Speter hi; 1005251881Speter hi = apr_hash_next(hi)) 1006251881Speter { 1007299742Sdim svn_prop_patch_t *prop_patch = apr_hash_this_val(hi); 1008251881Speter if (! has_prop_changes) 1009251881Speter has_prop_changes = prop_patch->hunks->nelts > 0; 1010251881Speter else 1011251881Speter break; 1012251881Speter } 1013251881Speter } 1014251881Speter 1015251881Speter prop_changes_only = has_prop_changes && patch->hunks->nelts == 0; 1016251881Speter 1017251881Speter content = apr_pcalloc(result_pool, sizeof(*content)); 1018251881Speter 1019251881Speter /* All other fields in content are FALSE or NULL due to apr_pcalloc().*/ 1020251881Speter content->current_line = 1; 1021251881Speter content->eol_style = svn_subst_eol_style_none; 1022251881Speter content->lines = apr_array_make(result_pool, 0, sizeof(apr_off_t)); 1023251881Speter content->hunks = apr_array_make(result_pool, 0, sizeof(hunk_info_t *)); 1024251881Speter content->keywords = apr_hash_make(result_pool); 1025251881Speter 1026251881Speter target = apr_pcalloc(result_pool, sizeof(*target)); 1027251881Speter 1028251881Speter /* All other fields in target are FALSE or NULL due to apr_pcalloc(). */ 1029251881Speter target->db_kind = svn_node_none; 1030251881Speter target->kind_on_disk = svn_node_none; 1031251881Speter target->content = content; 1032251881Speter target->prop_targets = apr_hash_make(result_pool); 1033251881Speter 1034251881Speter SVN_ERR(resolve_target_path(target, choose_target_filename(patch), 1035251881Speter wcroot_abspath, strip_count, prop_changes_only, 1036251881Speter wc_ctx, result_pool, scratch_pool)); 1037299742Sdim *patch_target = target; 1038251881Speter if (! target->skipped) 1039251881Speter { 1040251881Speter const char *diff_header; 1041251881Speter apr_size_t len; 1042251881Speter 1043251881Speter /* Create a temporary file to write the patched result to. 1044251881Speter * Also grab various bits of information about the file. */ 1045251881Speter if (target->is_symlink) 1046251881Speter { 1047251881Speter struct symlink_baton_t *sb = apr_pcalloc(result_pool, sizeof(*sb)); 1048251881Speter content->existed = TRUE; 1049251881Speter 1050251881Speter sb->local_abspath = target->local_abspath; 1051251881Speter 1052251881Speter /* Wire up the read callbacks. */ 1053251881Speter content->read_baton = sb; 1054251881Speter 1055251881Speter content->readline = readline_symlink; 1056251881Speter content->seek = seek_symlink; 1057251881Speter content->tell = tell_symlink; 1058251881Speter } 1059251881Speter else if (target->kind_on_disk == svn_node_file) 1060251881Speter { 1061251881Speter SVN_ERR(svn_io_file_open(&target->file, target->local_abspath, 1062251881Speter APR_READ | APR_BUFFERED, 1063251881Speter APR_OS_DEFAULT, result_pool)); 1064251881Speter SVN_ERR(svn_wc_text_modified_p2(&target->local_mods, wc_ctx, 1065251881Speter target->local_abspath, FALSE, 1066251881Speter scratch_pool)); 1067251881Speter SVN_ERR(svn_io_is_file_executable(&target->executable, 1068251881Speter target->local_abspath, 1069251881Speter scratch_pool)); 1070251881Speter SVN_ERR(obtain_eol_and_keywords_for_file(&content->keywords, 1071251881Speter &content->eol_style, 1072251881Speter &content->eol_str, 1073251881Speter wc_ctx, 1074251881Speter target->local_abspath, 1075251881Speter result_pool, 1076251881Speter scratch_pool)); 1077251881Speter content->existed = TRUE; 1078251881Speter 1079251881Speter /* Wire up the read callbacks. */ 1080251881Speter content->readline = readline_file; 1081251881Speter content->seek = seek_file; 1082251881Speter content->tell = tell_file; 1083251881Speter content->read_baton = target->file; 1084251881Speter } 1085251881Speter 1086251881Speter /* ### Is it ok to set the operation of the target already here? Isn't 1087251881Speter * ### the target supposed to be marked with an operation after we have 1088251881Speter * ### determined that the changes will apply cleanly to the WC? Maybe 1089251881Speter * ### we should have kept the patch field in patch_target_t to be 1090251881Speter * ### able to distinguish between 'what the patch says we should do' 1091251881Speter * ### and 'what we can do with the given state of our WC'. */ 1092251881Speter if (patch->operation == svn_diff_op_added) 1093251881Speter target->added = TRUE; 1094251881Speter else if (patch->operation == svn_diff_op_deleted) 1095251881Speter target->deleted = TRUE; 1096299742Sdim else if (patch->operation == svn_diff_op_moved) 1097299742Sdim { 1098299742Sdim const char *move_target_path; 1099299742Sdim const char *move_target_relpath; 1100299742Sdim svn_boolean_t under_root; 1101299742Sdim svn_node_kind_t kind_on_disk; 1102299742Sdim svn_node_kind_t wc_kind; 1103251881Speter 1104299742Sdim move_target_path = svn_dirent_internal_style(patch->new_filename, 1105299742Sdim scratch_pool); 1106299742Sdim 1107299742Sdim if (strip_count > 0) 1108299742Sdim SVN_ERR(strip_path(&move_target_path, move_target_path, 1109299742Sdim strip_count, scratch_pool, scratch_pool)); 1110299742Sdim 1111299742Sdim if (svn_dirent_is_absolute(move_target_path)) 1112299742Sdim { 1113299742Sdim move_target_relpath = svn_dirent_is_child(wcroot_abspath, 1114299742Sdim move_target_path, 1115299742Sdim scratch_pool); 1116299742Sdim if (! move_target_relpath) 1117299742Sdim { 1118299742Sdim /* The move target path is either outside of the working 1119299742Sdim * copy or it is the working copy itself. Skip it. */ 1120299742Sdim target->skipped = TRUE; 1121299742Sdim target->local_abspath = NULL; 1122299742Sdim return SVN_NO_ERROR; 1123299742Sdim } 1124299742Sdim } 1125299742Sdim else 1126299742Sdim move_target_relpath = move_target_path; 1127299742Sdim 1128299742Sdim /* Make sure the move target path is secure to use. */ 1129299742Sdim SVN_ERR(svn_dirent_is_under_root(&under_root, 1130299742Sdim &target->move_target_abspath, 1131299742Sdim wcroot_abspath, 1132299742Sdim move_target_relpath, result_pool)); 1133299742Sdim if (! under_root) 1134299742Sdim { 1135299742Sdim /* The target path is outside of the working copy. Skip it. */ 1136299742Sdim target->skipped = TRUE; 1137299742Sdim target->local_abspath = NULL; 1138299742Sdim return SVN_NO_ERROR; 1139299742Sdim } 1140299742Sdim 1141299742Sdim SVN_ERR(svn_io_check_path(target->move_target_abspath, 1142299742Sdim &kind_on_disk, scratch_pool)); 1143299742Sdim SVN_ERR(svn_wc_read_kind2(&wc_kind, wc_ctx, 1144299742Sdim target->move_target_abspath, 1145299742Sdim FALSE, FALSE, scratch_pool)); 1146299742Sdim if (kind_on_disk != svn_node_none || wc_kind != svn_node_none) 1147299742Sdim { 1148299742Sdim /* The move target path already exists on disk. Skip target. */ 1149299742Sdim target->skipped = TRUE; 1150299742Sdim target->move_target_abspath = NULL; 1151299742Sdim return SVN_NO_ERROR; 1152299742Sdim } 1153299742Sdim } 1154299742Sdim 1155251881Speter if (! target->is_symlink) 1156251881Speter { 1157251881Speter /* Open a temporary file to write the patched result to. */ 1158251881Speter SVN_ERR(svn_io_open_unique_file3(&target->patched_file, 1159251881Speter &target->patched_path, NULL, 1160251881Speter remove_tempfiles ? 1161251881Speter svn_io_file_del_on_pool_cleanup : 1162251881Speter svn_io_file_del_none, 1163251881Speter result_pool, scratch_pool)); 1164251881Speter 1165251881Speter /* Put the write callback in place. */ 1166251881Speter content->write = write_file; 1167251881Speter content->write_baton = target->patched_file; 1168251881Speter } 1169251881Speter else 1170251881Speter { 1171251881Speter /* Put the write callback in place. */ 1172251881Speter SVN_ERR(svn_io_open_unique_file3(NULL, 1173251881Speter &target->patched_path, NULL, 1174251881Speter remove_tempfiles ? 1175251881Speter svn_io_file_del_on_pool_cleanup : 1176251881Speter svn_io_file_del_none, 1177251881Speter result_pool, scratch_pool)); 1178251881Speter 1179251881Speter content->write_baton = (void*)target->patched_path; 1180251881Speter 1181251881Speter content->write = write_symlink; 1182251881Speter } 1183251881Speter 1184251881Speter /* Open a temporary file to write rejected hunks to. */ 1185251881Speter SVN_ERR(svn_io_open_unique_file3(&target->reject_file, 1186251881Speter &target->reject_path, NULL, 1187251881Speter remove_tempfiles ? 1188251881Speter svn_io_file_del_on_pool_cleanup : 1189251881Speter svn_io_file_del_none, 1190251881Speter result_pool, scratch_pool)); 1191251881Speter 1192251881Speter /* The reject file needs a diff header. */ 1193251881Speter diff_header = apr_psprintf(scratch_pool, "--- %s%s+++ %s%s", 1194251881Speter target->canon_path_from_patchfile, 1195251881Speter APR_EOL_STR, 1196251881Speter target->canon_path_from_patchfile, 1197251881Speter APR_EOL_STR); 1198251881Speter len = strlen(diff_header); 1199251881Speter SVN_ERR(svn_io_file_write_full(target->reject_file, diff_header, len, 1200251881Speter &len, scratch_pool)); 1201251881Speter 1202251881Speter /* Handle properties. */ 1203251881Speter if (! target->skipped) 1204251881Speter { 1205251881Speter apr_hash_index_t *hi; 1206251881Speter 1207251881Speter for (hi = apr_hash_first(result_pool, patch->prop_patches); 1208251881Speter hi; 1209251881Speter hi = apr_hash_next(hi)) 1210251881Speter { 1211299742Sdim const char *prop_name = apr_hash_this_key(hi); 1212299742Sdim svn_prop_patch_t *prop_patch = apr_hash_this_val(hi); 1213251881Speter prop_patch_target_t *prop_target; 1214251881Speter 1215251881Speter SVN_ERR(init_prop_target(&prop_target, 1216251881Speter prop_name, 1217251881Speter prop_patch->operation, 1218251881Speter wc_ctx, target->local_abspath, 1219251881Speter result_pool, scratch_pool)); 1220251881Speter svn_hash_sets(target->prop_targets, prop_name, prop_target); 1221251881Speter } 1222251881Speter } 1223251881Speter } 1224251881Speter 1225251881Speter return SVN_NO_ERROR; 1226251881Speter} 1227251881Speter 1228251881Speter/* Read a *LINE from CONTENT. If the line has not been read before 1229251881Speter * mark the line in CONTENT->LINES. 1230251881Speter * If a line could be read successfully, increase CONTENT->CURRENT_LINE, 1231251881Speter * and allocate *LINE in RESULT_POOL. 1232251881Speter * Do temporary allocations in SCRATCH_POOL. 1233251881Speter */ 1234251881Speterstatic svn_error_t * 1235251881Speterreadline(target_content_t *content, 1236251881Speter const char **line, 1237251881Speter apr_pool_t *result_pool, 1238251881Speter apr_pool_t *scratch_pool) 1239251881Speter{ 1240251881Speter svn_stringbuf_t *line_raw; 1241251881Speter const char *eol_str; 1242251881Speter svn_linenum_t max_line = (svn_linenum_t)content->lines->nelts + 1; 1243251881Speter 1244251881Speter if (content->eof || content->readline == NULL) 1245251881Speter { 1246251881Speter *line = ""; 1247251881Speter return SVN_NO_ERROR; 1248251881Speter } 1249251881Speter 1250251881Speter SVN_ERR_ASSERT(content->current_line <= max_line); 1251251881Speter if (content->current_line == max_line) 1252251881Speter { 1253251881Speter apr_off_t offset; 1254251881Speter 1255251881Speter SVN_ERR(content->tell(content->read_baton, &offset, 1256251881Speter scratch_pool)); 1257251881Speter APR_ARRAY_PUSH(content->lines, apr_off_t) = offset; 1258251881Speter } 1259251881Speter 1260251881Speter SVN_ERR(content->readline(content->read_baton, &line_raw, 1261251881Speter &eol_str, &content->eof, 1262251881Speter result_pool, scratch_pool)); 1263251881Speter if (content->eol_style == svn_subst_eol_style_none) 1264251881Speter content->eol_str = eol_str; 1265251881Speter 1266251881Speter if (line_raw) 1267251881Speter { 1268251881Speter /* Contract keywords. */ 1269251881Speter SVN_ERR(svn_subst_translate_cstring2(line_raw->data, line, 1270251881Speter NULL, FALSE, 1271251881Speter content->keywords, FALSE, 1272251881Speter result_pool)); 1273251881Speter } 1274251881Speter else 1275251881Speter *line = ""; 1276251881Speter 1277251881Speter if ((line_raw && line_raw->len > 0) || eol_str) 1278251881Speter content->current_line++; 1279251881Speter 1280251881Speter SVN_ERR_ASSERT(content->current_line > 0); 1281251881Speter 1282251881Speter return SVN_NO_ERROR; 1283251881Speter} 1284251881Speter 1285251881Speter/* Seek to the specified LINE in CONTENT. 1286251881Speter * Mark any lines not read before in CONTENT->LINES. 1287251881Speter * Do temporary allocations in SCRATCH_POOL. 1288251881Speter */ 1289251881Speterstatic svn_error_t * 1290251881Speterseek_to_line(target_content_t *content, svn_linenum_t line, 1291251881Speter apr_pool_t *scratch_pool) 1292251881Speter{ 1293251881Speter svn_linenum_t saved_line; 1294251881Speter svn_boolean_t saved_eof; 1295251881Speter 1296251881Speter SVN_ERR_ASSERT(line > 0); 1297251881Speter 1298251881Speter if (line == content->current_line) 1299251881Speter return SVN_NO_ERROR; 1300251881Speter 1301251881Speter saved_line = content->current_line; 1302251881Speter saved_eof = content->eof; 1303251881Speter 1304251881Speter if (line <= (svn_linenum_t)content->lines->nelts) 1305251881Speter { 1306251881Speter apr_off_t offset; 1307251881Speter 1308251881Speter offset = APR_ARRAY_IDX(content->lines, line - 1, apr_off_t); 1309251881Speter SVN_ERR(content->seek(content->read_baton, offset, 1310251881Speter scratch_pool)); 1311251881Speter content->current_line = line; 1312251881Speter } 1313251881Speter else 1314251881Speter { 1315251881Speter const char *dummy; 1316251881Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1317251881Speter 1318251881Speter while (! content->eof && content->current_line < line) 1319251881Speter { 1320251881Speter svn_pool_clear(iterpool); 1321251881Speter SVN_ERR(readline(content, &dummy, iterpool, iterpool)); 1322251881Speter } 1323251881Speter svn_pool_destroy(iterpool); 1324251881Speter } 1325251881Speter 1326251881Speter /* After seeking backwards from EOF position clear EOF indicator. */ 1327251881Speter if (saved_eof && saved_line > content->current_line) 1328251881Speter content->eof = FALSE; 1329251881Speter 1330251881Speter return SVN_NO_ERROR; 1331251881Speter} 1332251881Speter 1333251881Speter/* Indicate in *MATCHED whether the original text of HUNK matches the patch 1334251881Speter * CONTENT at its current line. Lines within FUZZ lines of the start or 1335251881Speter * end of HUNK will always match. If IGNORE_WHITESPACE is set, we ignore 1336251881Speter * whitespace when doing the matching. When this function returns, neither 1337251881Speter * CONTENT->CURRENT_LINE nor the file offset in the target file will 1338251881Speter * have changed. If MATCH_MODIFIED is TRUE, match the modified hunk text, 1339251881Speter * rather than the original hunk text. 1340251881Speter * Do temporary allocations in POOL. */ 1341251881Speterstatic svn_error_t * 1342251881Spetermatch_hunk(svn_boolean_t *matched, target_content_t *content, 1343251881Speter svn_diff_hunk_t *hunk, svn_linenum_t fuzz, 1344251881Speter svn_boolean_t ignore_whitespace, 1345251881Speter svn_boolean_t match_modified, apr_pool_t *pool) 1346251881Speter{ 1347251881Speter svn_stringbuf_t *hunk_line; 1348251881Speter const char *target_line; 1349251881Speter svn_linenum_t lines_read; 1350251881Speter svn_linenum_t saved_line; 1351251881Speter svn_boolean_t hunk_eof; 1352251881Speter svn_boolean_t lines_matched; 1353251881Speter apr_pool_t *iterpool; 1354251881Speter svn_linenum_t hunk_length; 1355251881Speter svn_linenum_t leading_context; 1356251881Speter svn_linenum_t trailing_context; 1357251881Speter 1358251881Speter *matched = FALSE; 1359251881Speter 1360251881Speter if (content->eof) 1361251881Speter return SVN_NO_ERROR; 1362251881Speter 1363251881Speter saved_line = content->current_line; 1364251881Speter lines_read = 0; 1365251881Speter lines_matched = FALSE; 1366251881Speter leading_context = svn_diff_hunk_get_leading_context(hunk); 1367251881Speter trailing_context = svn_diff_hunk_get_trailing_context(hunk); 1368251881Speter if (match_modified) 1369251881Speter { 1370251881Speter svn_diff_hunk_reset_modified_text(hunk); 1371251881Speter hunk_length = svn_diff_hunk_get_modified_length(hunk); 1372251881Speter } 1373251881Speter else 1374251881Speter { 1375251881Speter svn_diff_hunk_reset_original_text(hunk); 1376251881Speter hunk_length = svn_diff_hunk_get_original_length(hunk); 1377251881Speter } 1378251881Speter iterpool = svn_pool_create(pool); 1379251881Speter do 1380251881Speter { 1381251881Speter const char *hunk_line_translated; 1382251881Speter 1383251881Speter svn_pool_clear(iterpool); 1384251881Speter 1385251881Speter if (match_modified) 1386251881Speter SVN_ERR(svn_diff_hunk_readline_modified_text(hunk, &hunk_line, 1387251881Speter NULL, &hunk_eof, 1388251881Speter iterpool, iterpool)); 1389251881Speter else 1390251881Speter SVN_ERR(svn_diff_hunk_readline_original_text(hunk, &hunk_line, 1391251881Speter NULL, &hunk_eof, 1392251881Speter iterpool, iterpool)); 1393251881Speter 1394251881Speter /* Contract keywords, if any, before matching. */ 1395251881Speter SVN_ERR(svn_subst_translate_cstring2(hunk_line->data, 1396251881Speter &hunk_line_translated, 1397251881Speter NULL, FALSE, 1398251881Speter content->keywords, FALSE, 1399251881Speter iterpool)); 1400251881Speter SVN_ERR(readline(content, &target_line, iterpool, iterpool)); 1401251881Speter 1402251881Speter lines_read++; 1403251881Speter 1404251881Speter /* If the last line doesn't have a newline, we get EOF but still 1405251881Speter * have a non-empty line to compare. */ 1406251881Speter if ((hunk_eof && hunk_line->len == 0) || 1407251881Speter (content->eof && *target_line == 0)) 1408251881Speter break; 1409251881Speter 1410251881Speter /* Leading/trailing fuzzy lines always match. */ 1411251881Speter if ((lines_read <= fuzz && leading_context > fuzz) || 1412251881Speter (lines_read > hunk_length - fuzz && trailing_context > fuzz)) 1413251881Speter lines_matched = TRUE; 1414251881Speter else 1415251881Speter { 1416251881Speter if (ignore_whitespace) 1417251881Speter { 1418251881Speter char *hunk_line_trimmed; 1419251881Speter char *target_line_trimmed; 1420251881Speter 1421251881Speter hunk_line_trimmed = apr_pstrdup(iterpool, hunk_line_translated); 1422251881Speter target_line_trimmed = apr_pstrdup(iterpool, target_line); 1423251881Speter apr_collapse_spaces(hunk_line_trimmed, hunk_line_trimmed); 1424251881Speter apr_collapse_spaces(target_line_trimmed, target_line_trimmed); 1425251881Speter lines_matched = ! strcmp(hunk_line_trimmed, target_line_trimmed); 1426251881Speter } 1427251881Speter else 1428251881Speter lines_matched = ! strcmp(hunk_line_translated, target_line); 1429251881Speter } 1430251881Speter } 1431251881Speter while (lines_matched); 1432251881Speter 1433251881Speter *matched = lines_matched && hunk_eof && hunk_line->len == 0; 1434251881Speter SVN_ERR(seek_to_line(content, saved_line, iterpool)); 1435251881Speter svn_pool_destroy(iterpool); 1436251881Speter 1437251881Speter return SVN_NO_ERROR; 1438251881Speter} 1439251881Speter 1440251881Speter/* Scan lines of CONTENT for a match of the original text of HUNK, 1441251881Speter * up to but not including the specified UPPER_LINE. Use fuzz factor FUZZ. 1442251881Speter * If UPPER_LINE is zero scan until EOF occurs when reading from TARGET. 1443251881Speter * Return the line at which HUNK was matched in *MATCHED_LINE. 1444251881Speter * If the hunk did not match at all, set *MATCHED_LINE to zero. 1445251881Speter * If the hunk matched multiple times, and MATCH_FIRST is TRUE, 1446251881Speter * return the line number at which the first match occurred in *MATCHED_LINE. 1447251881Speter * If the hunk matched multiple times, and MATCH_FIRST is FALSE, 1448251881Speter * return the line number at which the last match occurred in *MATCHED_LINE. 1449251881Speter * If IGNORE_WHITESPACE is set, ignore whitespace during the matching. 1450251881Speter * If MATCH_MODIFIED is TRUE, match the modified hunk text, 1451251881Speter * rather than the original hunk text. 1452251881Speter * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation. 1453251881Speter * Do all allocations in POOL. */ 1454251881Speterstatic svn_error_t * 1455251881Speterscan_for_match(svn_linenum_t *matched_line, 1456251881Speter target_content_t *content, 1457251881Speter svn_diff_hunk_t *hunk, svn_boolean_t match_first, 1458251881Speter svn_linenum_t upper_line, svn_linenum_t fuzz, 1459251881Speter svn_boolean_t ignore_whitespace, 1460251881Speter svn_boolean_t match_modified, 1461251881Speter svn_cancel_func_t cancel_func, void *cancel_baton, 1462251881Speter apr_pool_t *pool) 1463251881Speter{ 1464251881Speter apr_pool_t *iterpool; 1465251881Speter 1466251881Speter *matched_line = 0; 1467251881Speter iterpool = svn_pool_create(pool); 1468251881Speter while ((content->current_line < upper_line || upper_line == 0) && 1469251881Speter ! content->eof) 1470251881Speter { 1471251881Speter svn_boolean_t matched; 1472251881Speter 1473251881Speter svn_pool_clear(iterpool); 1474251881Speter 1475251881Speter if (cancel_func) 1476251881Speter SVN_ERR(cancel_func(cancel_baton)); 1477251881Speter 1478251881Speter SVN_ERR(match_hunk(&matched, content, hunk, fuzz, ignore_whitespace, 1479251881Speter match_modified, iterpool)); 1480251881Speter if (matched) 1481251881Speter { 1482251881Speter svn_boolean_t taken = FALSE; 1483251881Speter int i; 1484251881Speter 1485251881Speter /* Don't allow hunks to match at overlapping locations. */ 1486251881Speter for (i = 0; i < content->hunks->nelts; i++) 1487251881Speter { 1488251881Speter const hunk_info_t *hi; 1489251881Speter svn_linenum_t length; 1490251881Speter 1491251881Speter hi = APR_ARRAY_IDX(content->hunks, i, const hunk_info_t *); 1492251881Speter 1493251881Speter if (match_modified) 1494251881Speter length = svn_diff_hunk_get_modified_length(hi->hunk); 1495251881Speter else 1496251881Speter length = svn_diff_hunk_get_original_length(hi->hunk); 1497251881Speter 1498251881Speter taken = (! hi->rejected && 1499251881Speter content->current_line >= hi->matched_line && 1500251881Speter content->current_line < (hi->matched_line + length)); 1501251881Speter if (taken) 1502251881Speter break; 1503251881Speter } 1504251881Speter 1505251881Speter if (! taken) 1506251881Speter { 1507251881Speter *matched_line = content->current_line; 1508251881Speter if (match_first) 1509251881Speter break; 1510251881Speter } 1511251881Speter } 1512251881Speter 1513251881Speter if (! content->eof) 1514251881Speter SVN_ERR(seek_to_line(content, content->current_line + 1, 1515251881Speter iterpool)); 1516251881Speter } 1517251881Speter svn_pool_destroy(iterpool); 1518251881Speter 1519251881Speter return SVN_NO_ERROR; 1520251881Speter} 1521251881Speter 1522251881Speter/* Indicate in *MATCH whether the content described by CONTENT 1523251881Speter * matches the modified text of HUNK. 1524251881Speter * Use SCRATCH_POOL for temporary allocations. */ 1525251881Speterstatic svn_error_t * 1526251881Spetermatch_existing_target(svn_boolean_t *match, 1527251881Speter target_content_t *content, 1528251881Speter svn_diff_hunk_t *hunk, 1529251881Speter apr_pool_t *scratch_pool) 1530251881Speter{ 1531251881Speter svn_boolean_t lines_matched; 1532251881Speter apr_pool_t *iterpool; 1533251881Speter svn_boolean_t hunk_eof; 1534251881Speter svn_linenum_t saved_line; 1535251881Speter 1536251881Speter svn_diff_hunk_reset_modified_text(hunk); 1537251881Speter 1538251881Speter saved_line = content->current_line; 1539251881Speter 1540251881Speter iterpool = svn_pool_create(scratch_pool); 1541251881Speter do 1542251881Speter { 1543251881Speter const char *line; 1544251881Speter svn_stringbuf_t *hunk_line; 1545251881Speter const char *line_translated; 1546251881Speter const char *hunk_line_translated; 1547251881Speter 1548251881Speter svn_pool_clear(iterpool); 1549251881Speter 1550251881Speter SVN_ERR(readline(content, &line, iterpool, iterpool)); 1551251881Speter SVN_ERR(svn_diff_hunk_readline_modified_text(hunk, &hunk_line, 1552251881Speter NULL, &hunk_eof, 1553251881Speter iterpool, iterpool)); 1554251881Speter /* Contract keywords. */ 1555251881Speter SVN_ERR(svn_subst_translate_cstring2(line, &line_translated, 1556251881Speter NULL, FALSE, 1557251881Speter content->keywords, 1558251881Speter FALSE, iterpool)); 1559251881Speter SVN_ERR(svn_subst_translate_cstring2(hunk_line->data, 1560251881Speter &hunk_line_translated, 1561251881Speter NULL, FALSE, 1562251881Speter content->keywords, 1563251881Speter FALSE, iterpool)); 1564251881Speter lines_matched = ! strcmp(line_translated, hunk_line_translated); 1565251881Speter if (content->eof != hunk_eof) 1566251881Speter { 1567251881Speter svn_pool_destroy(iterpool); 1568251881Speter *match = FALSE; 1569251881Speter return SVN_NO_ERROR; 1570251881Speter } 1571251881Speter } 1572251881Speter while (lines_matched && ! content->eof && ! hunk_eof); 1573251881Speter svn_pool_destroy(iterpool); 1574251881Speter 1575251881Speter *match = (lines_matched && content->eof == hunk_eof); 1576251881Speter SVN_ERR(seek_to_line(content, saved_line, scratch_pool)); 1577251881Speter 1578251881Speter return SVN_NO_ERROR; 1579251881Speter} 1580251881Speter 1581251881Speter/* Determine the line at which a HUNK applies to CONTENT of the TARGET 1582251881Speter * file, and return an appropriate hunk_info object in *HI, allocated from 1583251881Speter * RESULT_POOL. Use fuzz factor FUZZ. Set HI->FUZZ to FUZZ. If no correct 1584299742Sdim * line can be determined, set HI->REJECTED to TRUE. PREVIOUS_OFFSET 1585299742Sdim * is the offset at which the previous matching hunk was applied, or zero. 1586251881Speter * IGNORE_WHITESPACE tells whether whitespace should be considered when 1587251881Speter * matching. IS_PROP_HUNK indicates whether the hunk patches file content 1588251881Speter * or a property. 1589251881Speter * When this function returns, neither CONTENT->CURRENT_LINE nor 1590251881Speter * the file offset in the target file will have changed. 1591251881Speter * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation. 1592251881Speter * Do temporary allocations in POOL. */ 1593251881Speterstatic svn_error_t * 1594251881Speterget_hunk_info(hunk_info_t **hi, patch_target_t *target, 1595251881Speter target_content_t *content, 1596251881Speter svn_diff_hunk_t *hunk, svn_linenum_t fuzz, 1597299742Sdim svn_linenum_t previous_offset, 1598251881Speter svn_boolean_t ignore_whitespace, 1599251881Speter svn_boolean_t is_prop_hunk, 1600251881Speter svn_cancel_func_t cancel_func, void *cancel_baton, 1601251881Speter apr_pool_t *result_pool, apr_pool_t *scratch_pool) 1602251881Speter{ 1603251881Speter svn_linenum_t matched_line; 1604251881Speter svn_linenum_t original_start; 1605251881Speter svn_boolean_t already_applied; 1606251881Speter 1607299742Sdim original_start = svn_diff_hunk_get_original_start(hunk) + previous_offset; 1608251881Speter already_applied = FALSE; 1609251881Speter 1610251881Speter /* An original offset of zero means that this hunk wants to create 1611251881Speter * a new file. Don't bother matching hunks in that case, since 1612251881Speter * the hunk applies at line 1. If the file already exists, the hunk 1613251881Speter * is rejected, unless the file is versioned and its content matches 1614251881Speter * the file the patch wants to create. */ 1615251881Speter if (original_start == 0 && fuzz > 0) 1616251881Speter { 1617251881Speter matched_line = 0; /* reject any fuzz for new files */ 1618251881Speter } 1619251881Speter else if (original_start == 0 && ! is_prop_hunk) 1620251881Speter { 1621251881Speter if (target->kind_on_disk == svn_node_file) 1622251881Speter { 1623251881Speter const svn_io_dirent2_t *dirent; 1624251881Speter SVN_ERR(svn_io_stat_dirent2(&dirent, target->local_abspath, FALSE, 1625251881Speter TRUE, scratch_pool, scratch_pool)); 1626251881Speter 1627251881Speter if (dirent->kind == svn_node_file 1628251881Speter && !dirent->special 1629251881Speter && dirent->filesize == 0) 1630251881Speter { 1631251881Speter matched_line = 1; /* Matched an on-disk empty file */ 1632251881Speter } 1633251881Speter else 1634251881Speter { 1635251881Speter if (target->db_kind == svn_node_file) 1636251881Speter { 1637251881Speter svn_boolean_t file_matches; 1638251881Speter 1639251881Speter /* ### I can't reproduce anything but a no-match here. 1640251881Speter The content is already at eof, so any hunk fails */ 1641251881Speter SVN_ERR(match_existing_target(&file_matches, content, hunk, 1642251881Speter scratch_pool)); 1643251881Speter if (file_matches) 1644251881Speter { 1645251881Speter matched_line = 1; 1646251881Speter already_applied = TRUE; 1647251881Speter } 1648251881Speter else 1649251881Speter matched_line = 0; /* reject */ 1650251881Speter } 1651251881Speter else 1652251881Speter matched_line = 0; /* reject */ 1653251881Speter } 1654251881Speter } 1655251881Speter else 1656251881Speter matched_line = 1; 1657251881Speter } 1658251881Speter /* Same conditions apply as for the file case above. 1659251881Speter * 1660251881Speter * ### Since the hunk says the prop should be added we just assume so for 1661251881Speter * ### now and don't bother with storing the previous lines and such. When 1662251881Speter * ### we have the diff operation available we can just check for adds. */ 1663251881Speter else if (original_start == 0 && is_prop_hunk) 1664251881Speter { 1665251881Speter if (content->existed) 1666251881Speter { 1667251881Speter svn_boolean_t prop_matches; 1668251881Speter 1669251881Speter SVN_ERR(match_existing_target(&prop_matches, content, hunk, 1670251881Speter scratch_pool)); 1671251881Speter 1672251881Speter if (prop_matches) 1673251881Speter { 1674251881Speter matched_line = 1; 1675251881Speter already_applied = TRUE; 1676251881Speter } 1677251881Speter else 1678251881Speter matched_line = 0; /* reject */ 1679251881Speter } 1680251881Speter else 1681251881Speter matched_line = 1; 1682251881Speter } 1683251881Speter else if (original_start > 0 && content->existed) 1684251881Speter { 1685251881Speter svn_linenum_t saved_line = content->current_line; 1686251881Speter 1687251881Speter /* Scan for a match at the line where the hunk thinks it 1688251881Speter * should be going. */ 1689251881Speter SVN_ERR(seek_to_line(content, original_start, scratch_pool)); 1690251881Speter if (content->current_line != original_start) 1691251881Speter { 1692251881Speter /* Seek failed. */ 1693251881Speter matched_line = 0; 1694251881Speter } 1695251881Speter else 1696251881Speter SVN_ERR(scan_for_match(&matched_line, content, hunk, TRUE, 1697251881Speter original_start + 1, fuzz, 1698251881Speter ignore_whitespace, FALSE, 1699251881Speter cancel_func, cancel_baton, 1700251881Speter scratch_pool)); 1701251881Speter 1702251881Speter if (matched_line != original_start) 1703251881Speter { 1704251881Speter /* Check if the hunk is already applied. 1705251881Speter * We only check for an exact match here, and don't bother checking 1706251881Speter * for already applied patches with offset/fuzz, because such a 1707251881Speter * check would be ambiguous. */ 1708251881Speter if (fuzz == 0) 1709251881Speter { 1710251881Speter svn_linenum_t modified_start; 1711251881Speter 1712251881Speter modified_start = svn_diff_hunk_get_modified_start(hunk); 1713251881Speter if (modified_start == 0) 1714251881Speter { 1715299742Sdim /* Patch wants to delete the file. 1716299742Sdim 1717299742Sdim ### locally_deleted is always false here? */ 1718251881Speter already_applied = target->locally_deleted; 1719251881Speter } 1720251881Speter else 1721251881Speter { 1722251881Speter SVN_ERR(seek_to_line(content, modified_start, 1723251881Speter scratch_pool)); 1724251881Speter SVN_ERR(scan_for_match(&matched_line, content, 1725251881Speter hunk, TRUE, 1726251881Speter modified_start + 1, 1727251881Speter fuzz, ignore_whitespace, TRUE, 1728251881Speter cancel_func, cancel_baton, 1729251881Speter scratch_pool)); 1730251881Speter already_applied = (matched_line == modified_start); 1731251881Speter } 1732251881Speter } 1733251881Speter else 1734251881Speter already_applied = FALSE; 1735251881Speter 1736251881Speter if (! already_applied) 1737251881Speter { 1738299742Sdim int i; 1739299742Sdim svn_linenum_t search_start = 1, search_end = 0; 1740299742Sdim svn_linenum_t matched_line2; 1741251881Speter 1742299742Sdim /* Search for closest match before or after original 1743299742Sdim start. We have no backward search so search forwards 1744299742Sdim from the previous match (or start of file) to the 1745299742Sdim original start looking for the last match. Then 1746299742Sdim search forwards from the original start looking for a 1747299742Sdim better match. Finally search forwards from the start 1748299742Sdim of file to the previous hunk if that could result in 1749299742Sdim a better match. */ 1750299742Sdim 1751299742Sdim for (i = content->hunks->nelts; i > 0; --i) 1752299742Sdim { 1753299742Sdim const hunk_info_t *prev 1754299742Sdim = APR_ARRAY_IDX(content->hunks, i - 1, const hunk_info_t *); 1755299742Sdim if (!prev->rejected) 1756299742Sdim { 1757299742Sdim svn_linenum_t length; 1758299742Sdim 1759299742Sdim length = svn_diff_hunk_get_original_length(prev->hunk); 1760299742Sdim search_start = prev->matched_line + length; 1761299742Sdim break; 1762299742Sdim } 1763299742Sdim } 1764299742Sdim 1765299742Sdim /* Search from the previous match, or start of file, 1766299742Sdim towards the original location. */ 1767299742Sdim SVN_ERR(seek_to_line(content, search_start, scratch_pool)); 1768251881Speter SVN_ERR(scan_for_match(&matched_line, content, hunk, FALSE, 1769251881Speter original_start, fuzz, 1770251881Speter ignore_whitespace, FALSE, 1771251881Speter cancel_func, cancel_baton, 1772251881Speter scratch_pool)); 1773251881Speter 1774299742Sdim /* If a match we only need to search forwards for a 1775299742Sdim better match, otherwise to the end of the file. */ 1776299742Sdim if (matched_line) 1777299742Sdim search_end = original_start + (original_start - matched_line); 1778299742Sdim 1779299742Sdim /* Search from original location, towards the end. */ 1780299742Sdim SVN_ERR(seek_to_line(content, original_start + 1, scratch_pool)); 1781299742Sdim SVN_ERR(scan_for_match(&matched_line2, content, hunk, 1782299742Sdim TRUE, search_end, fuzz, ignore_whitespace, 1783299742Sdim FALSE, cancel_func, cancel_baton, 1784299742Sdim scratch_pool)); 1785299742Sdim 1786299742Sdim /* Chose the forward match if it is closer than the 1787299742Sdim backward match or if there is no backward match. */ 1788299742Sdim if (matched_line2 1789299742Sdim && (!matched_line 1790299742Sdim || (matched_line2 - original_start 1791299742Sdim < original_start - matched_line))) 1792299742Sdim matched_line = matched_line2; 1793299742Sdim 1794299742Sdim /* Search from before previous hunk if there could be a 1795299742Sdim better match. */ 1796299742Sdim if (search_start > 1 1797299742Sdim && (!matched_line 1798299742Sdim || (matched_line > original_start 1799299742Sdim && (matched_line - original_start 1800299742Sdim > original_start - search_start)))) 1801251881Speter { 1802299742Sdim svn_linenum_t search_start2 = 1; 1803299742Sdim 1804299742Sdim if (matched_line 1805299742Sdim && matched_line - original_start < original_start) 1806299742Sdim search_start2 1807299742Sdim = original_start - (matched_line - original_start) + 1; 1808299742Sdim 1809299742Sdim SVN_ERR(seek_to_line(content, search_start2, scratch_pool)); 1810299742Sdim SVN_ERR(scan_for_match(&matched_line2, content, hunk, FALSE, 1811299742Sdim search_start - 1, fuzz, 1812299742Sdim ignore_whitespace, FALSE, 1813299742Sdim cancel_func, cancel_baton, 1814251881Speter scratch_pool)); 1815299742Sdim if (matched_line2) 1816299742Sdim matched_line = matched_line2; 1817251881Speter } 1818251881Speter } 1819251881Speter } 1820251881Speter 1821251881Speter SVN_ERR(seek_to_line(content, saved_line, scratch_pool)); 1822251881Speter } 1823251881Speter else 1824251881Speter { 1825251881Speter /* The hunk wants to modify a file which doesn't exist. */ 1826251881Speter matched_line = 0; 1827251881Speter } 1828251881Speter 1829251881Speter (*hi) = apr_pcalloc(result_pool, sizeof(hunk_info_t)); 1830251881Speter (*hi)->hunk = hunk; 1831251881Speter (*hi)->matched_line = matched_line; 1832251881Speter (*hi)->rejected = (matched_line == 0); 1833251881Speter (*hi)->already_applied = already_applied; 1834251881Speter (*hi)->fuzz = fuzz; 1835251881Speter 1836251881Speter return SVN_NO_ERROR; 1837251881Speter} 1838251881Speter 1839251881Speter/* Copy lines to the patched content until the specified LINE has been 1840251881Speter * reached. Indicate in *EOF whether end-of-file was encountered while 1841251881Speter * reading from the target. 1842251881Speter * If LINE is zero, copy lines until end-of-file has been reached. 1843251881Speter * Do all allocations in POOL. */ 1844251881Speterstatic svn_error_t * 1845251881Spetercopy_lines_to_target(target_content_t *content, svn_linenum_t line, 1846251881Speter apr_pool_t *pool) 1847251881Speter{ 1848251881Speter apr_pool_t *iterpool; 1849251881Speter 1850251881Speter iterpool = svn_pool_create(pool); 1851251881Speter while ((content->current_line < line || line == 0) && ! content->eof) 1852251881Speter { 1853251881Speter const char *target_line; 1854251881Speter apr_size_t len; 1855251881Speter 1856251881Speter svn_pool_clear(iterpool); 1857251881Speter 1858251881Speter SVN_ERR(readline(content, &target_line, iterpool, iterpool)); 1859251881Speter if (! content->eof) 1860251881Speter target_line = apr_pstrcat(iterpool, target_line, content->eol_str, 1861299742Sdim SVN_VA_NULL); 1862251881Speter len = strlen(target_line); 1863251881Speter SVN_ERR(content->write(content->write_baton, target_line, 1864251881Speter len, iterpool)); 1865251881Speter } 1866251881Speter svn_pool_destroy(iterpool); 1867251881Speter 1868251881Speter return SVN_NO_ERROR; 1869251881Speter} 1870251881Speter 1871251881Speter/* Write the diff text of HUNK to TARGET's reject file, 1872251881Speter * and mark TARGET as having had rejects. 1873251881Speter * We don't expand keywords, nor normalise line-endings, in reject files. 1874251881Speter * Do temporary allocations in SCRATCH_POOL. */ 1875251881Speterstatic svn_error_t * 1876251881Speterreject_hunk(patch_target_t *target, target_content_t *content, 1877251881Speter svn_diff_hunk_t *hunk, const char *prop_name, 1878251881Speter apr_pool_t *pool) 1879251881Speter{ 1880251881Speter const char *hunk_header; 1881251881Speter apr_size_t len; 1882251881Speter svn_boolean_t eof; 1883251881Speter static const char * const text_atat = "@@"; 1884251881Speter static const char * const prop_atat = "##"; 1885251881Speter const char *atat; 1886251881Speter apr_pool_t *iterpool; 1887251881Speter 1888251881Speter if (prop_name) 1889251881Speter { 1890251881Speter const char *prop_header; 1891251881Speter 1892251881Speter /* ### Print 'Added', 'Deleted' or 'Modified' instead of 'Property'. 1893251881Speter */ 1894251881Speter prop_header = apr_psprintf(pool, "Property: %s\n", prop_name); 1895251881Speter len = strlen(prop_header); 1896251881Speter SVN_ERR(svn_io_file_write_full(target->reject_file, prop_header, 1897251881Speter len, &len, pool)); 1898251881Speter atat = prop_atat; 1899251881Speter } 1900251881Speter else 1901251881Speter { 1902251881Speter atat = text_atat; 1903251881Speter } 1904251881Speter 1905251881Speter hunk_header = apr_psprintf(pool, "%s -%lu,%lu +%lu,%lu %s%s", 1906251881Speter atat, 1907251881Speter svn_diff_hunk_get_original_start(hunk), 1908251881Speter svn_diff_hunk_get_original_length(hunk), 1909251881Speter svn_diff_hunk_get_modified_start(hunk), 1910251881Speter svn_diff_hunk_get_modified_length(hunk), 1911251881Speter atat, 1912251881Speter APR_EOL_STR); 1913251881Speter len = strlen(hunk_header); 1914251881Speter SVN_ERR(svn_io_file_write_full(target->reject_file, hunk_header, len, 1915251881Speter &len, pool)); 1916251881Speter 1917251881Speter iterpool = svn_pool_create(pool); 1918251881Speter do 1919251881Speter { 1920251881Speter svn_stringbuf_t *hunk_line; 1921251881Speter const char *eol_str; 1922251881Speter 1923251881Speter svn_pool_clear(iterpool); 1924251881Speter 1925251881Speter SVN_ERR(svn_diff_hunk_readline_diff_text(hunk, &hunk_line, &eol_str, 1926251881Speter &eof, iterpool, iterpool)); 1927251881Speter if (! eof) 1928251881Speter { 1929251881Speter if (hunk_line->len >= 1) 1930251881Speter { 1931251881Speter len = hunk_line->len; 1932251881Speter SVN_ERR(svn_io_file_write_full(target->reject_file, 1933251881Speter hunk_line->data, len, &len, 1934251881Speter iterpool)); 1935251881Speter } 1936251881Speter 1937251881Speter if (eol_str) 1938251881Speter { 1939251881Speter len = strlen(eol_str); 1940251881Speter SVN_ERR(svn_io_file_write_full(target->reject_file, eol_str, 1941251881Speter len, &len, iterpool)); 1942251881Speter } 1943251881Speter } 1944251881Speter } 1945251881Speter while (! eof); 1946251881Speter svn_pool_destroy(iterpool); 1947251881Speter 1948251881Speter if (prop_name) 1949251881Speter target->had_prop_rejects = TRUE; 1950251881Speter else 1951251881Speter target->had_rejects = TRUE; 1952251881Speter 1953251881Speter return SVN_NO_ERROR; 1954251881Speter} 1955251881Speter 1956251881Speter/* Write the modified text of the hunk described by HI to the patched 1957251881Speter * CONTENT. TARGET is the patch target. 1958251881Speter * If PROP_NAME is not NULL, the hunk is assumed to be targeted for 1959251881Speter * a property with the given name. 1960251881Speter * Do temporary allocations in POOL. */ 1961251881Speterstatic svn_error_t * 1962251881Speterapply_hunk(patch_target_t *target, target_content_t *content, 1963251881Speter hunk_info_t *hi, const char *prop_name, apr_pool_t *pool) 1964251881Speter{ 1965251881Speter svn_linenum_t lines_read; 1966251881Speter svn_boolean_t eof; 1967251881Speter apr_pool_t *iterpool; 1968251881Speter 1969251881Speter /* ### Is there a cleaner way to describe if we have an existing target? 1970251881Speter */ 1971251881Speter if (target->kind_on_disk == svn_node_file || prop_name) 1972251881Speter { 1973251881Speter svn_linenum_t line; 1974251881Speter 1975251881Speter /* Move forward to the hunk's line, copying data as we go. 1976251881Speter * Also copy leading lines of context which matched with fuzz. 1977251881Speter * The target has changed on the fuzzy-matched lines, 1978251881Speter * so we should retain the target's version of those lines. */ 1979251881Speter SVN_ERR(copy_lines_to_target(content, hi->matched_line + hi->fuzz, 1980251881Speter pool)); 1981251881Speter 1982251881Speter /* Skip the target's version of the hunk. 1983251881Speter * Don't skip trailing lines which matched with fuzz. */ 1984251881Speter line = content->current_line + 1985251881Speter svn_diff_hunk_get_original_length(hi->hunk) - (2 * hi->fuzz); 1986251881Speter SVN_ERR(seek_to_line(content, line, pool)); 1987251881Speter if (content->current_line != line && ! content->eof) 1988251881Speter { 1989251881Speter /* Seek failed, reject this hunk. */ 1990251881Speter hi->rejected = TRUE; 1991251881Speter SVN_ERR(reject_hunk(target, content, hi->hunk, prop_name, pool)); 1992251881Speter return SVN_NO_ERROR; 1993251881Speter } 1994251881Speter } 1995251881Speter 1996251881Speter /* Write the hunk's version to the patched result. 1997251881Speter * Don't write the lines which matched with fuzz. */ 1998251881Speter lines_read = 0; 1999251881Speter svn_diff_hunk_reset_modified_text(hi->hunk); 2000251881Speter iterpool = svn_pool_create(pool); 2001251881Speter do 2002251881Speter { 2003251881Speter svn_stringbuf_t *hunk_line; 2004251881Speter const char *eol_str; 2005251881Speter 2006251881Speter svn_pool_clear(iterpool); 2007251881Speter 2008251881Speter SVN_ERR(svn_diff_hunk_readline_modified_text(hi->hunk, &hunk_line, 2009251881Speter &eol_str, &eof, 2010251881Speter iterpool, iterpool)); 2011251881Speter lines_read++; 2012251881Speter if (lines_read > hi->fuzz && 2013251881Speter lines_read <= svn_diff_hunk_get_modified_length(hi->hunk) - hi->fuzz) 2014251881Speter { 2015251881Speter apr_size_t len; 2016251881Speter 2017251881Speter if (hunk_line->len >= 1) 2018251881Speter { 2019251881Speter len = hunk_line->len; 2020251881Speter SVN_ERR(content->write(content->write_baton, 2021251881Speter hunk_line->data, len, iterpool)); 2022251881Speter } 2023251881Speter 2024251881Speter if (eol_str) 2025251881Speter { 2026251881Speter /* Use the EOL as it was read from the patch file, 2027251881Speter * unless the target's EOL style is set by svn:eol-style */ 2028251881Speter if (content->eol_style != svn_subst_eol_style_none) 2029251881Speter eol_str = content->eol_str; 2030251881Speter 2031251881Speter len = strlen(eol_str); 2032251881Speter SVN_ERR(content->write(content->write_baton, 2033251881Speter eol_str, len, iterpool)); 2034251881Speter } 2035251881Speter } 2036251881Speter } 2037251881Speter while (! eof); 2038251881Speter svn_pool_destroy(iterpool); 2039251881Speter 2040251881Speter if (prop_name) 2041251881Speter target->has_prop_changes = TRUE; 2042251881Speter else 2043251881Speter target->has_text_changes = TRUE; 2044251881Speter 2045251881Speter return SVN_NO_ERROR; 2046251881Speter} 2047251881Speter 2048251881Speter/* Use client context CTX to send a suitable notification for hunk HI, 2049251881Speter * using TARGET to determine the path. If the hunk is a property hunk, 2050251881Speter * PROP_NAME must be the name of the property, else NULL. 2051251881Speter * Use POOL for temporary allocations. */ 2052251881Speterstatic svn_error_t * 2053251881Spetersend_hunk_notification(const hunk_info_t *hi, 2054251881Speter const patch_target_t *target, 2055251881Speter const char *prop_name, 2056251881Speter const svn_client_ctx_t *ctx, 2057251881Speter apr_pool_t *pool) 2058251881Speter{ 2059251881Speter svn_wc_notify_t *notify; 2060251881Speter svn_wc_notify_action_t action; 2061251881Speter 2062251881Speter if (hi->already_applied) 2063251881Speter action = svn_wc_notify_patch_hunk_already_applied; 2064251881Speter else if (hi->rejected) 2065251881Speter action = svn_wc_notify_patch_rejected_hunk; 2066251881Speter else 2067251881Speter action = svn_wc_notify_patch_applied_hunk; 2068251881Speter 2069251881Speter notify = svn_wc_create_notify(target->local_abspath 2070251881Speter ? target->local_abspath 2071251881Speter : target->local_relpath, 2072251881Speter action, pool); 2073251881Speter notify->hunk_original_start = 2074251881Speter svn_diff_hunk_get_original_start(hi->hunk); 2075251881Speter notify->hunk_original_length = 2076251881Speter svn_diff_hunk_get_original_length(hi->hunk); 2077251881Speter notify->hunk_modified_start = 2078251881Speter svn_diff_hunk_get_modified_start(hi->hunk); 2079251881Speter notify->hunk_modified_length = 2080251881Speter svn_diff_hunk_get_modified_length(hi->hunk); 2081251881Speter notify->hunk_matched_line = hi->matched_line; 2082251881Speter notify->hunk_fuzz = hi->fuzz; 2083251881Speter notify->prop_name = prop_name; 2084251881Speter 2085299742Sdim ctx->notify_func2(ctx->notify_baton2, notify, pool); 2086251881Speter 2087251881Speter return SVN_NO_ERROR; 2088251881Speter} 2089251881Speter 2090251881Speter/* Use client context CTX to send a suitable notification for a patch TARGET. 2091251881Speter * Use POOL for temporary allocations. */ 2092251881Speterstatic svn_error_t * 2093251881Spetersend_patch_notification(const patch_target_t *target, 2094251881Speter const svn_client_ctx_t *ctx, 2095251881Speter apr_pool_t *pool) 2096251881Speter{ 2097251881Speter svn_wc_notify_t *notify; 2098251881Speter svn_wc_notify_action_t action; 2099299742Sdim const char *notify_path; 2100251881Speter 2101251881Speter if (! ctx->notify_func2) 2102251881Speter return SVN_NO_ERROR; 2103251881Speter 2104251881Speter if (target->skipped) 2105251881Speter action = svn_wc_notify_skip; 2106251881Speter else if (target->deleted) 2107251881Speter action = svn_wc_notify_delete; 2108299742Sdim else if (target->added || target->replaced || target->move_target_abspath) 2109251881Speter action = svn_wc_notify_add; 2110251881Speter else 2111251881Speter action = svn_wc_notify_patch; 2112251881Speter 2113299742Sdim if (target->move_target_abspath) 2114299742Sdim notify_path = target->move_target_abspath; 2115299742Sdim else 2116299742Sdim notify_path = target->local_abspath ? target->local_abspath 2117299742Sdim : target->local_relpath; 2118299742Sdim 2119299742Sdim notify = svn_wc_create_notify(notify_path, action, pool); 2120251881Speter notify->kind = svn_node_file; 2121251881Speter 2122251881Speter if (action == svn_wc_notify_skip) 2123251881Speter { 2124251881Speter if (target->db_kind == svn_node_none || 2125251881Speter target->db_kind == svn_node_unknown) 2126251881Speter notify->content_state = svn_wc_notify_state_missing; 2127251881Speter else if (target->db_kind == svn_node_dir) 2128251881Speter notify->content_state = svn_wc_notify_state_obstructed; 2129251881Speter else 2130251881Speter notify->content_state = svn_wc_notify_state_unknown; 2131251881Speter } 2132251881Speter else 2133251881Speter { 2134251881Speter if (target->had_rejects) 2135251881Speter notify->content_state = svn_wc_notify_state_conflicted; 2136251881Speter else if (target->local_mods) 2137251881Speter notify->content_state = svn_wc_notify_state_merged; 2138251881Speter else if (target->has_text_changes) 2139251881Speter notify->content_state = svn_wc_notify_state_changed; 2140251881Speter 2141251881Speter if (target->had_prop_rejects) 2142251881Speter notify->prop_state = svn_wc_notify_state_conflicted; 2143251881Speter else if (target->has_prop_changes) 2144251881Speter notify->prop_state = svn_wc_notify_state_changed; 2145251881Speter } 2146251881Speter 2147299742Sdim ctx->notify_func2(ctx->notify_baton2, notify, pool); 2148251881Speter 2149251881Speter if (action == svn_wc_notify_patch) 2150251881Speter { 2151251881Speter int i; 2152251881Speter apr_pool_t *iterpool; 2153251881Speter apr_hash_index_t *hash_index; 2154251881Speter 2155251881Speter iterpool = svn_pool_create(pool); 2156251881Speter for (i = 0; i < target->content->hunks->nelts; i++) 2157251881Speter { 2158251881Speter const hunk_info_t *hi; 2159251881Speter 2160251881Speter svn_pool_clear(iterpool); 2161251881Speter 2162251881Speter hi = APR_ARRAY_IDX(target->content->hunks, i, hunk_info_t *); 2163251881Speter 2164251881Speter SVN_ERR(send_hunk_notification(hi, target, NULL /* prop_name */, 2165251881Speter ctx, iterpool)); 2166251881Speter } 2167251881Speter 2168251881Speter for (hash_index = apr_hash_first(pool, target->prop_targets); 2169251881Speter hash_index; 2170251881Speter hash_index = apr_hash_next(hash_index)) 2171251881Speter { 2172251881Speter prop_patch_target_t *prop_target; 2173251881Speter 2174299742Sdim prop_target = apr_hash_this_val(hash_index); 2175251881Speter 2176251881Speter for (i = 0; i < prop_target->content->hunks->nelts; i++) 2177251881Speter { 2178251881Speter const hunk_info_t *hi; 2179251881Speter 2180251881Speter svn_pool_clear(iterpool); 2181251881Speter 2182251881Speter hi = APR_ARRAY_IDX(prop_target->content->hunks, i, 2183251881Speter hunk_info_t *); 2184251881Speter 2185251881Speter /* Don't notify on the hunk level for added or deleted props. */ 2186251881Speter if (prop_target->operation != svn_diff_op_added && 2187251881Speter prop_target->operation != svn_diff_op_deleted) 2188251881Speter SVN_ERR(send_hunk_notification(hi, target, prop_target->name, 2189251881Speter ctx, iterpool)); 2190251881Speter } 2191251881Speter } 2192251881Speter svn_pool_destroy(iterpool); 2193251881Speter } 2194251881Speter 2195299742Sdim if (target->move_target_abspath) 2196299742Sdim { 2197299742Sdim /* Notify about deletion of move source. */ 2198299742Sdim notify = svn_wc_create_notify(target->local_abspath, 2199299742Sdim svn_wc_notify_delete, pool); 2200299742Sdim notify->kind = svn_node_file; 2201299742Sdim ctx->notify_func2(ctx->notify_baton2, notify, pool); 2202299742Sdim } 2203299742Sdim 2204251881Speter return SVN_NO_ERROR; 2205251881Speter} 2206251881Speter 2207289166Speter/* Implements the callback for svn_sort__array. Puts hunks that match 2208289166Speter before hunks that do not match, puts hunks that match in order 2209289166Speter based on postion matched, puts hunks that do not match in order 2210289166Speter based on original position. */ 2211289166Speterstatic int 2212289166Spetersort_matched_hunks(const void *a, const void *b) 2213289166Speter{ 2214289166Speter const hunk_info_t *item1 = *((const hunk_info_t * const *)a); 2215289166Speter const hunk_info_t *item2 = *((const hunk_info_t * const *)b); 2216289166Speter svn_boolean_t matched1 = !item1->rejected && !item1->already_applied; 2217289166Speter svn_boolean_t matched2 = !item2->rejected && !item2->already_applied; 2218289166Speter svn_linenum_t original1, original2; 2219289166Speter 2220289166Speter if (matched1 && matched2) 2221289166Speter { 2222289166Speter /* Both match so use order matched in file. */ 2223289166Speter if (item1->matched_line > item2->matched_line) 2224289166Speter return 1; 2225289166Speter else if (item1->matched_line == item2->matched_line) 2226289166Speter return 0; 2227289166Speter else 2228289166Speter return -1; 2229289166Speter } 2230289166Speter else if (matched2) 2231289166Speter /* Only second matches, put it before first. */ 2232289166Speter return 1; 2233289166Speter else if (matched1) 2234289166Speter /* Only first matches, put it before second. */ 2235289166Speter return -1; 2236289166Speter 2237289166Speter /* Neither matches, sort by original_start. */ 2238289166Speter original1 = svn_diff_hunk_get_original_start(item1->hunk); 2239289166Speter original2 = svn_diff_hunk_get_original_start(item2->hunk); 2240289166Speter if (original1 > original2) 2241289166Speter return 1; 2242289166Speter else if (original1 == original2) 2243289166Speter return 0; 2244289166Speter else 2245289166Speter return -1; 2246289166Speter} 2247289166Speter 2248289166Speter 2249251881Speter/* Apply a PATCH to a working copy at ABS_WC_PATH and put the result 2250251881Speter * into temporary files, to be installed in the working copy later. 2251251881Speter * Return information about the patch target in *PATCH_TARGET, allocated 2252251881Speter * in RESULT_POOL. Use WC_CTX as the working copy context. 2253251881Speter * STRIP_COUNT specifies the number of leading path components 2254251881Speter * which should be stripped from target paths in the patch. 2255251881Speter * REMOVE_TEMPFILES, PATCH_FUNC, and PATCH_BATON as in svn_client_patch(). 2256251881Speter * IGNORE_WHITESPACE tells whether whitespace should be considered when 2257251881Speter * doing the matching. 2258251881Speter * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation. 2259251881Speter * Do temporary allocations in SCRATCH_POOL. */ 2260251881Speterstatic svn_error_t * 2261251881Speterapply_one_patch(patch_target_t **patch_target, svn_patch_t *patch, 2262251881Speter const char *abs_wc_path, svn_wc_context_t *wc_ctx, 2263251881Speter int strip_count, 2264251881Speter svn_boolean_t ignore_whitespace, 2265251881Speter svn_boolean_t remove_tempfiles, 2266251881Speter svn_cancel_func_t cancel_func, 2267251881Speter void *cancel_baton, 2268251881Speter apr_pool_t *result_pool, apr_pool_t *scratch_pool) 2269251881Speter{ 2270251881Speter patch_target_t *target; 2271251881Speter apr_pool_t *iterpool; 2272251881Speter int i; 2273251881Speter static const svn_linenum_t MAX_FUZZ = 2; 2274251881Speter apr_hash_index_t *hash_index; 2275299742Sdim svn_linenum_t previous_offset = 0; 2276251881Speter 2277251881Speter SVN_ERR(init_patch_target(&target, patch, abs_wc_path, wc_ctx, strip_count, 2278251881Speter remove_tempfiles, result_pool, scratch_pool)); 2279251881Speter if (target->skipped) 2280251881Speter { 2281251881Speter *patch_target = target; 2282251881Speter return SVN_NO_ERROR; 2283251881Speter } 2284251881Speter 2285251881Speter iterpool = svn_pool_create(scratch_pool); 2286251881Speter /* Match hunks. */ 2287251881Speter for (i = 0; i < patch->hunks->nelts; i++) 2288251881Speter { 2289251881Speter svn_diff_hunk_t *hunk; 2290251881Speter hunk_info_t *hi; 2291251881Speter svn_linenum_t fuzz = 0; 2292251881Speter 2293251881Speter svn_pool_clear(iterpool); 2294251881Speter 2295251881Speter if (cancel_func) 2296251881Speter SVN_ERR(cancel_func(cancel_baton)); 2297251881Speter 2298251881Speter hunk = APR_ARRAY_IDX(patch->hunks, i, svn_diff_hunk_t *); 2299251881Speter 2300251881Speter /* Determine the line the hunk should be applied at. 2301251881Speter * If no match is found initially, try with fuzz. */ 2302251881Speter do 2303251881Speter { 2304251881Speter SVN_ERR(get_hunk_info(&hi, target, target->content, hunk, fuzz, 2305299742Sdim previous_offset, 2306251881Speter ignore_whitespace, 2307251881Speter FALSE /* is_prop_hunk */, 2308251881Speter cancel_func, cancel_baton, 2309251881Speter result_pool, iterpool)); 2310251881Speter fuzz++; 2311251881Speter } 2312251881Speter while (hi->rejected && fuzz <= MAX_FUZZ && ! hi->already_applied); 2313251881Speter 2314299742Sdim if (hi->matched_line) 2315299742Sdim previous_offset 2316299742Sdim = hi->matched_line - svn_diff_hunk_get_original_start(hunk); 2317299742Sdim 2318251881Speter APR_ARRAY_PUSH(target->content->hunks, hunk_info_t *) = hi; 2319251881Speter } 2320251881Speter 2321289166Speter /* Hunks are applied in the order determined by the matched line and 2322289166Speter this may be different from the order of the original lines. */ 2323289166Speter svn_sort__array(target->content->hunks, sort_matched_hunks); 2324289166Speter 2325251881Speter /* Apply or reject hunks. */ 2326251881Speter for (i = 0; i < target->content->hunks->nelts; i++) 2327251881Speter { 2328251881Speter hunk_info_t *hi; 2329251881Speter 2330251881Speter svn_pool_clear(iterpool); 2331251881Speter 2332251881Speter if (cancel_func) 2333251881Speter SVN_ERR(cancel_func(cancel_baton)); 2334251881Speter 2335251881Speter hi = APR_ARRAY_IDX(target->content->hunks, i, hunk_info_t *); 2336251881Speter if (hi->already_applied) 2337251881Speter continue; 2338251881Speter else if (hi->rejected) 2339251881Speter SVN_ERR(reject_hunk(target, target->content, hi->hunk, 2340251881Speter NULL /* prop_name */, 2341251881Speter iterpool)); 2342251881Speter else 2343251881Speter SVN_ERR(apply_hunk(target, target->content, hi, 2344251881Speter NULL /* prop_name */, iterpool)); 2345251881Speter } 2346251881Speter 2347251881Speter if (target->kind_on_disk == svn_node_file) 2348251881Speter { 2349251881Speter /* Copy any remaining lines to target. */ 2350251881Speter SVN_ERR(copy_lines_to_target(target->content, 0, scratch_pool)); 2351251881Speter if (! target->content->eof) 2352251881Speter { 2353251881Speter /* We could not copy the entire target file to the temporary file, 2354251881Speter * and would truncate the target if we copied the temporary file 2355251881Speter * on top of it. Skip this target. */ 2356251881Speter target->skipped = TRUE; 2357251881Speter } 2358251881Speter } 2359251881Speter 2360251881Speter /* Match property hunks. */ 2361251881Speter for (hash_index = apr_hash_first(scratch_pool, patch->prop_patches); 2362251881Speter hash_index; 2363251881Speter hash_index = apr_hash_next(hash_index)) 2364251881Speter { 2365251881Speter svn_prop_patch_t *prop_patch; 2366251881Speter const char *prop_name; 2367251881Speter prop_patch_target_t *prop_target; 2368251881Speter 2369299742Sdim prop_name = apr_hash_this_key(hash_index); 2370299742Sdim prop_patch = apr_hash_this_val(hash_index); 2371251881Speter 2372251881Speter if (! strcmp(prop_name, SVN_PROP_SPECIAL)) 2373251881Speter target->is_special = TRUE; 2374251881Speter 2375251881Speter /* We'll store matched hunks in prop_content. */ 2376251881Speter prop_target = svn_hash_gets(target->prop_targets, prop_name); 2377251881Speter 2378251881Speter for (i = 0; i < prop_patch->hunks->nelts; i++) 2379251881Speter { 2380251881Speter svn_diff_hunk_t *hunk; 2381251881Speter hunk_info_t *hi; 2382251881Speter svn_linenum_t fuzz = 0; 2383251881Speter 2384251881Speter svn_pool_clear(iterpool); 2385251881Speter 2386251881Speter if (cancel_func) 2387251881Speter SVN_ERR(cancel_func(cancel_baton)); 2388251881Speter 2389251881Speter hunk = APR_ARRAY_IDX(prop_patch->hunks, i, svn_diff_hunk_t *); 2390251881Speter 2391251881Speter /* Determine the line the hunk should be applied at. 2392251881Speter * If no match is found initially, try with fuzz. */ 2393251881Speter do 2394251881Speter { 2395251881Speter SVN_ERR(get_hunk_info(&hi, target, prop_target->content, 2396299742Sdim hunk, fuzz, 0, 2397251881Speter ignore_whitespace, 2398251881Speter TRUE /* is_prop_hunk */, 2399251881Speter cancel_func, cancel_baton, 2400251881Speter result_pool, iterpool)); 2401251881Speter fuzz++; 2402251881Speter } 2403251881Speter while (hi->rejected && fuzz <= MAX_FUZZ && ! hi->already_applied); 2404251881Speter 2405251881Speter APR_ARRAY_PUSH(prop_target->content->hunks, hunk_info_t *) = hi; 2406251881Speter } 2407251881Speter } 2408251881Speter 2409251881Speter /* Apply or reject property hunks. */ 2410251881Speter for (hash_index = apr_hash_first(scratch_pool, target->prop_targets); 2411251881Speter hash_index; 2412251881Speter hash_index = apr_hash_next(hash_index)) 2413251881Speter { 2414251881Speter prop_patch_target_t *prop_target; 2415251881Speter 2416299742Sdim prop_target = apr_hash_this_val(hash_index); 2417251881Speter 2418251881Speter for (i = 0; i < prop_target->content->hunks->nelts; i++) 2419251881Speter { 2420251881Speter hunk_info_t *hi; 2421251881Speter 2422251881Speter svn_pool_clear(iterpool); 2423251881Speter 2424251881Speter hi = APR_ARRAY_IDX(prop_target->content->hunks, i, 2425251881Speter hunk_info_t *); 2426251881Speter if (hi->already_applied) 2427251881Speter continue; 2428251881Speter else if (hi->rejected) 2429251881Speter SVN_ERR(reject_hunk(target, prop_target->content, hi->hunk, 2430251881Speter prop_target->name, 2431251881Speter iterpool)); 2432251881Speter else 2433251881Speter SVN_ERR(apply_hunk(target, prop_target->content, hi, 2434251881Speter prop_target->name, 2435251881Speter iterpool)); 2436251881Speter } 2437251881Speter 2438251881Speter if (prop_target->content->existed) 2439251881Speter { 2440251881Speter /* Copy any remaining lines to target. */ 2441251881Speter SVN_ERR(copy_lines_to_target(prop_target->content, 0, 2442251881Speter scratch_pool)); 2443251881Speter if (! prop_target->content->eof) 2444251881Speter { 2445251881Speter /* We could not copy the entire target property to the 2446251881Speter * temporary file, and would truncate the target if we 2447251881Speter * copied the temporary file on top of it. Skip this target. */ 2448251881Speter target->skipped = TRUE; 2449251881Speter } 2450251881Speter } 2451251881Speter } 2452251881Speter 2453251881Speter svn_pool_destroy(iterpool); 2454251881Speter 2455251881Speter if (!target->is_symlink) 2456251881Speter { 2457251881Speter /* Now close files we don't need any longer to get their contents 2458251881Speter * flushed to disk. 2459251881Speter * But we're not closing the reject file -- it still needed and 2460251881Speter * will be closed later in write_out_rejected_hunks(). */ 2461251881Speter if (target->kind_on_disk == svn_node_file) 2462251881Speter SVN_ERR(svn_io_file_close(target->file, scratch_pool)); 2463251881Speter 2464251881Speter SVN_ERR(svn_io_file_close(target->patched_file, scratch_pool)); 2465251881Speter } 2466251881Speter 2467251881Speter if (! target->skipped) 2468251881Speter { 2469251881Speter apr_finfo_t working_file; 2470251881Speter apr_finfo_t patched_file; 2471251881Speter 2472251881Speter /* Get sizes of the patched temporary file and the working file. 2473251881Speter * We'll need those to figure out whether we should delete the 2474251881Speter * patched file. */ 2475251881Speter SVN_ERR(svn_io_stat(&patched_file, target->patched_path, 2476251881Speter APR_FINFO_SIZE | APR_FINFO_LINK, scratch_pool)); 2477251881Speter if (target->kind_on_disk == svn_node_file) 2478251881Speter SVN_ERR(svn_io_stat(&working_file, target->local_abspath, 2479251881Speter APR_FINFO_SIZE | APR_FINFO_LINK, scratch_pool)); 2480251881Speter else 2481251881Speter working_file.size = 0; 2482251881Speter 2483251881Speter if (patched_file.size == 0 && working_file.size > 0) 2484251881Speter { 2485251881Speter /* If a unidiff removes all lines from a file, that usually 2486251881Speter * means deletion, so we can confidently schedule the target 2487251881Speter * for deletion. In the rare case where the unidiff was really 2488251881Speter * meant to replace a file with an empty one, this may not 2489251881Speter * be desirable. But the deletion can easily be reverted and 2490251881Speter * creating an empty file manually is not exactly hard either. */ 2491251881Speter target->deleted = (target->db_kind == svn_node_file); 2492251881Speter } 2493251881Speter else if (patched_file.size == 0 && working_file.size == 0) 2494251881Speter { 2495251881Speter /* The target was empty or non-existent to begin with 2496251881Speter * and no content was changed by patching. 2497251881Speter * Report this as skipped if it didn't exist, unless in the special 2498251881Speter * case of adding an empty file which has properties set on it or 2499251881Speter * adding an empty file with a 'git diff' */ 2500251881Speter if (target->kind_on_disk == svn_node_none 2501251881Speter && ! target->has_prop_changes 2502251881Speter && ! target->added) 2503251881Speter target->skipped = TRUE; 2504251881Speter } 2505251881Speter else if (patched_file.size > 0 && working_file.size == 0) 2506251881Speter { 2507251881Speter /* The patch has created a file. */ 2508251881Speter if (target->locally_deleted) 2509251881Speter target->replaced = TRUE; 2510251881Speter else if (target->db_kind == svn_node_none) 2511251881Speter target->added = TRUE; 2512251881Speter } 2513251881Speter } 2514251881Speter 2515251881Speter *patch_target = target; 2516251881Speter 2517251881Speter return SVN_NO_ERROR; 2518251881Speter} 2519251881Speter 2520251881Speter/* Try to create missing parent directories for TARGET in the working copy 2521251881Speter * rooted at ABS_WC_PATH, and add the parents to version control. 2522251881Speter * If the parents cannot be created, mark the target as skipped. 2523251881Speter * Use client context CTX. If DRY_RUN is true, do not create missing 2524251881Speter * parents but issue notifications only. 2525251881Speter * Use SCRATCH_POOL for temporary allocations. */ 2526251881Speterstatic svn_error_t * 2527251881Spetercreate_missing_parents(patch_target_t *target, 2528251881Speter const char *abs_wc_path, 2529251881Speter svn_client_ctx_t *ctx, 2530251881Speter svn_boolean_t dry_run, 2531251881Speter apr_pool_t *scratch_pool) 2532251881Speter{ 2533251881Speter const char *local_abspath; 2534251881Speter apr_array_header_t *components; 2535251881Speter int present_components; 2536251881Speter int i; 2537251881Speter apr_pool_t *iterpool; 2538251881Speter 2539251881Speter /* Check if we can safely create the target's parent. */ 2540251881Speter local_abspath = abs_wc_path; 2541251881Speter components = svn_path_decompose(target->local_relpath, scratch_pool); 2542251881Speter present_components = 0; 2543251881Speter iterpool = svn_pool_create(scratch_pool); 2544251881Speter for (i = 0; i < components->nelts - 1; i++) 2545251881Speter { 2546251881Speter const char *component; 2547251881Speter svn_node_kind_t wc_kind, disk_kind; 2548251881Speter 2549251881Speter svn_pool_clear(iterpool); 2550251881Speter 2551251881Speter component = APR_ARRAY_IDX(components, i, const char *); 2552251881Speter local_abspath = svn_dirent_join(local_abspath, component, scratch_pool); 2553251881Speter 2554251881Speter SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, local_abspath, 2555251881Speter FALSE, TRUE, iterpool)); 2556251881Speter 2557251881Speter SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, iterpool)); 2558251881Speter 2559251881Speter if (disk_kind == svn_node_file || wc_kind == svn_node_file) 2560251881Speter { 2561251881Speter /* on-disk files and missing files are obstructions */ 2562251881Speter target->skipped = TRUE; 2563251881Speter break; 2564251881Speter } 2565251881Speter else if (disk_kind == svn_node_dir) 2566251881Speter { 2567251881Speter if (wc_kind == svn_node_dir) 2568251881Speter present_components++; 2569251881Speter else 2570251881Speter { 2571251881Speter target->skipped = TRUE; 2572251881Speter break; 2573251881Speter } 2574251881Speter } 2575251881Speter else if (wc_kind != svn_node_none) 2576251881Speter { 2577251881Speter /* Node is missing */ 2578251881Speter target->skipped = TRUE; 2579251881Speter break; 2580251881Speter } 2581251881Speter else 2582251881Speter { 2583251881Speter /* It's not a file, it's not a dir... 2584251881Speter Let's add a dir */ 2585251881Speter break; 2586251881Speter } 2587251881Speter } 2588251881Speter if (! target->skipped) 2589251881Speter { 2590251881Speter local_abspath = abs_wc_path; 2591251881Speter for (i = 0; i < present_components; i++) 2592251881Speter { 2593251881Speter const char *component; 2594251881Speter component = APR_ARRAY_IDX(components, i, const char *); 2595251881Speter local_abspath = svn_dirent_join(local_abspath, 2596251881Speter component, scratch_pool); 2597251881Speter } 2598251881Speter 2599251881Speter if (!dry_run && present_components < components->nelts - 1) 2600251881Speter SVN_ERR(svn_io_make_dir_recursively( 2601251881Speter svn_dirent_join( 2602251881Speter abs_wc_path, 2603251881Speter svn_relpath_dirname(target->local_relpath, 2604251881Speter scratch_pool), 2605251881Speter scratch_pool), 2606251881Speter scratch_pool)); 2607251881Speter 2608251881Speter for (i = present_components; i < components->nelts - 1; i++) 2609251881Speter { 2610251881Speter const char *component; 2611251881Speter 2612251881Speter svn_pool_clear(iterpool); 2613251881Speter 2614251881Speter component = APR_ARRAY_IDX(components, i, const char *); 2615251881Speter local_abspath = svn_dirent_join(local_abspath, component, 2616251881Speter scratch_pool); 2617251881Speter if (dry_run) 2618251881Speter { 2619251881Speter if (ctx->notify_func2) 2620251881Speter { 2621251881Speter /* Just do notification. */ 2622251881Speter svn_wc_notify_t *notify; 2623251881Speter notify = svn_wc_create_notify(local_abspath, 2624251881Speter svn_wc_notify_add, 2625251881Speter iterpool); 2626251881Speter notify->kind = svn_node_dir; 2627251881Speter ctx->notify_func2(ctx->notify_baton2, notify, 2628251881Speter iterpool); 2629251881Speter } 2630251881Speter } 2631251881Speter else 2632251881Speter { 2633251881Speter /* Create the missing component and add it 2634251881Speter * to version control. Allow cancellation since we 2635251881Speter * have not modified the working copy yet for this 2636251881Speter * target. */ 2637251881Speter 2638251881Speter if (ctx->cancel_func) 2639251881Speter SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 2640251881Speter 2641299742Sdim SVN_ERR(svn_wc_add_from_disk3(ctx->wc_ctx, local_abspath, 2642251881Speter NULL /*props*/, 2643299742Sdim FALSE /* skip checks */, 2644251881Speter ctx->notify_func2, ctx->notify_baton2, 2645251881Speter iterpool)); 2646251881Speter } 2647251881Speter } 2648251881Speter } 2649251881Speter 2650251881Speter svn_pool_destroy(iterpool); 2651251881Speter return SVN_NO_ERROR; 2652251881Speter} 2653251881Speter 2654251881Speter/* Install a patched TARGET into the working copy at ABS_WC_PATH. 2655251881Speter * Use client context CTX to retrieve WC_CTX, and possibly doing 2656251881Speter * notifications. If DRY_RUN is TRUE, don't modify the working copy. 2657251881Speter * Do temporary allocations in POOL. */ 2658251881Speterstatic svn_error_t * 2659251881Speterinstall_patched_target(patch_target_t *target, const char *abs_wc_path, 2660251881Speter svn_client_ctx_t *ctx, svn_boolean_t dry_run, 2661251881Speter apr_pool_t *pool) 2662251881Speter{ 2663251881Speter if (target->deleted) 2664251881Speter { 2665251881Speter if (! dry_run) 2666251881Speter { 2667251881Speter /* Schedule the target for deletion. Suppress 2668251881Speter * notification, we'll do it manually in a minute 2669251881Speter * because we also need to notify during dry-run. 2670251881Speter * Also suppress cancellation, because we'd rather 2671251881Speter * notify about what we did before aborting. */ 2672251881Speter SVN_ERR(svn_wc_delete4(ctx->wc_ctx, target->local_abspath, 2673251881Speter FALSE /* keep_local */, FALSE, 2674251881Speter NULL, NULL, NULL, NULL, pool)); 2675251881Speter } 2676251881Speter } 2677251881Speter else 2678251881Speter { 2679251881Speter svn_node_kind_t parent_db_kind; 2680251881Speter if (target->added || target->replaced) 2681251881Speter { 2682251881Speter const char *parent_abspath; 2683251881Speter 2684251881Speter parent_abspath = svn_dirent_dirname(target->local_abspath, 2685251881Speter pool); 2686251881Speter /* If the target's parent directory does not yet exist 2687251881Speter * we need to create it before we can copy the patched 2688251881Speter * result in place. */ 2689251881Speter SVN_ERR(svn_wc_read_kind2(&parent_db_kind, ctx->wc_ctx, 2690251881Speter parent_abspath, FALSE, FALSE, pool)); 2691251881Speter 2692251881Speter /* We can't add targets under nodes scheduled for delete, so add 2693251881Speter a new directory if needed. */ 2694251881Speter if (parent_db_kind == svn_node_dir 2695251881Speter || parent_db_kind == svn_node_file) 2696251881Speter { 2697251881Speter if (parent_db_kind != svn_node_dir) 2698251881Speter target->skipped = TRUE; 2699251881Speter else 2700251881Speter { 2701251881Speter svn_node_kind_t disk_kind; 2702251881Speter 2703251881Speter SVN_ERR(svn_io_check_path(parent_abspath, &disk_kind, pool)); 2704251881Speter if (disk_kind != svn_node_dir) 2705251881Speter target->skipped = TRUE; 2706251881Speter } 2707251881Speter } 2708251881Speter else 2709251881Speter SVN_ERR(create_missing_parents(target, abs_wc_path, ctx, 2710251881Speter dry_run, pool)); 2711251881Speter 2712251881Speter } 2713251881Speter else 2714251881Speter { 2715251881Speter svn_node_kind_t wc_kind; 2716251881Speter 2717251881Speter /* The target should exist */ 2718251881Speter SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, 2719251881Speter target->local_abspath, 2720251881Speter FALSE, FALSE, pool)); 2721251881Speter 2722251881Speter if (target->kind_on_disk == svn_node_none 2723251881Speter || wc_kind != target->kind_on_disk) 2724251881Speter { 2725251881Speter target->skipped = TRUE; 2726251881Speter } 2727251881Speter } 2728251881Speter 2729251881Speter if (! dry_run && ! target->skipped) 2730251881Speter { 2731251881Speter if (target->is_special) 2732251881Speter { 2733251881Speter svn_stream_t *stream; 2734251881Speter svn_stream_t *patched_stream; 2735251881Speter 2736251881Speter SVN_ERR(svn_stream_open_readonly(&patched_stream, 2737251881Speter target->patched_path, 2738251881Speter pool, pool)); 2739251881Speter SVN_ERR(svn_subst_create_specialfile(&stream, 2740251881Speter target->local_abspath, 2741251881Speter pool, pool)); 2742251881Speter SVN_ERR(svn_stream_copy3(patched_stream, stream, 2743251881Speter ctx->cancel_func, ctx->cancel_baton, 2744251881Speter pool)); 2745251881Speter } 2746251881Speter else 2747251881Speter { 2748251881Speter svn_boolean_t repair_eol; 2749251881Speter 2750251881Speter /* Copy the patched file on top of the target file. 2751251881Speter * Always expand keywords in the patched file, but repair EOL 2752251881Speter * only if svn:eol-style dictates a particular style. */ 2753251881Speter repair_eol = (target->content->eol_style == 2754251881Speter svn_subst_eol_style_fixed || 2755251881Speter target->content->eol_style == 2756251881Speter svn_subst_eol_style_native); 2757251881Speter 2758251881Speter SVN_ERR(svn_subst_copy_and_translate4( 2759299742Sdim target->patched_path, 2760299742Sdim target->move_target_abspath 2761299742Sdim ? target->move_target_abspath 2762299742Sdim : target->local_abspath, 2763251881Speter target->content->eol_str, repair_eol, 2764251881Speter target->content->keywords, 2765251881Speter TRUE /* expand */, FALSE /* special */, 2766251881Speter ctx->cancel_func, ctx->cancel_baton, pool)); 2767251881Speter } 2768251881Speter 2769251881Speter if (target->added || target->replaced) 2770251881Speter { 2771251881Speter /* The target file didn't exist previously, 2772251881Speter * so add it to version control. 2773251881Speter * Suppress notification, we'll do that later (and also 2774251881Speter * during dry-run). Don't allow cancellation because 2775251881Speter * we'd rather notify about what we did before aborting. */ 2776299742Sdim SVN_ERR(svn_wc_add_from_disk3(ctx->wc_ctx, target->local_abspath, 2777251881Speter NULL /*props*/, 2778299742Sdim FALSE /* skip checks */, 2779251881Speter NULL, NULL, pool)); 2780251881Speter } 2781251881Speter 2782251881Speter /* Restore the target's executable bit if necessary. */ 2783299742Sdim SVN_ERR(svn_io_set_file_executable(target->move_target_abspath 2784299742Sdim ? target->move_target_abspath 2785299742Sdim : target->local_abspath, 2786251881Speter target->executable, 2787251881Speter FALSE, pool)); 2788299742Sdim 2789299742Sdim if (target->move_target_abspath) 2790299742Sdim { 2791299742Sdim /* ### Copying the patched content to the move target location, 2792299742Sdim * performing the move in meta-data, and removing the file at 2793299742Sdim * the move source should be one atomic operation. */ 2794299742Sdim 2795299742Sdim /* ### Create missing parents. */ 2796299742Sdim 2797299742Sdim /* Perform the move in meta-data. */ 2798299742Sdim SVN_ERR(svn_wc__move2(ctx->wc_ctx, 2799299742Sdim target->local_abspath, 2800299742Sdim target->move_target_abspath, 2801299742Sdim TRUE, /* metadata_only */ 2802299742Sdim FALSE, /* allow_mixed_revisions */ 2803299742Sdim NULL, NULL, NULL, NULL, 2804299742Sdim pool)); 2805299742Sdim 2806299742Sdim /* Delete the patch target's old location from disk. */ 2807299742Sdim SVN_ERR(svn_io_remove_file2(target->local_abspath, FALSE, pool)); 2808299742Sdim } 2809251881Speter } 2810251881Speter } 2811251881Speter 2812251881Speter return SVN_NO_ERROR; 2813251881Speter} 2814251881Speter 2815251881Speter/* Write out rejected hunks, if any, to TARGET->REJECT_PATH. If DRY_RUN is 2816251881Speter * TRUE, don't modify the working copy. 2817251881Speter * Do temporary allocations in POOL. 2818251881Speter */ 2819251881Speterstatic svn_error_t * 2820251881Speterwrite_out_rejected_hunks(patch_target_t *target, 2821251881Speter svn_boolean_t dry_run, 2822251881Speter apr_pool_t *pool) 2823251881Speter{ 2824251881Speter SVN_ERR(svn_io_file_close(target->reject_file, pool)); 2825251881Speter 2826251881Speter if (! dry_run && (target->had_rejects || target->had_prop_rejects)) 2827251881Speter { 2828251881Speter /* Write out rejected hunks, if any. */ 2829251881Speter SVN_ERR(svn_io_copy_file(target->reject_path, 2830251881Speter apr_psprintf(pool, "%s.svnpatch.rej", 2831251881Speter target->local_abspath), 2832251881Speter FALSE, pool)); 2833251881Speter /* ### TODO mark file as conflicted. */ 2834251881Speter } 2835251881Speter return SVN_NO_ERROR; 2836251881Speter} 2837251881Speter 2838251881Speter/* Install the patched properties for TARGET. Use client context CTX to 2839251881Speter * retrieve WC_CTX. If DRY_RUN is TRUE, don't modify the working copy. 2840251881Speter * Do temporary allocations in SCRATCH_POOL. */ 2841251881Speterstatic svn_error_t * 2842251881Speterinstall_patched_prop_targets(patch_target_t *target, 2843251881Speter svn_client_ctx_t *ctx, svn_boolean_t dry_run, 2844251881Speter apr_pool_t *scratch_pool) 2845251881Speter{ 2846251881Speter apr_hash_index_t *hi; 2847251881Speter apr_pool_t *iterpool; 2848251881Speter 2849251881Speter iterpool = svn_pool_create(scratch_pool); 2850251881Speter 2851251881Speter for (hi = apr_hash_first(scratch_pool, target->prop_targets); 2852251881Speter hi; 2853251881Speter hi = apr_hash_next(hi)) 2854251881Speter { 2855299742Sdim prop_patch_target_t *prop_target = apr_hash_this_val(hi); 2856251881Speter const svn_string_t *prop_val; 2857251881Speter svn_error_t *err; 2858251881Speter 2859251881Speter svn_pool_clear(iterpool); 2860251881Speter 2861251881Speter if (ctx->cancel_func) 2862251881Speter SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 2863251881Speter 2864251881Speter /* For a deleted prop we only set the value to NULL. */ 2865251881Speter if (prop_target->operation == svn_diff_op_deleted) 2866251881Speter { 2867251881Speter if (! dry_run) 2868251881Speter SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, target->local_abspath, 2869251881Speter prop_target->name, NULL, svn_depth_empty, 2870251881Speter TRUE /* skip_checks */, 2871251881Speter NULL /* changelist_filter */, 2872251881Speter NULL, NULL /* cancellation */, 2873251881Speter NULL, NULL /* notification */, 2874251881Speter iterpool)); 2875251881Speter continue; 2876251881Speter } 2877251881Speter 2878251881Speter /* If the patch target doesn't exist yet, the patch wants to add an 2879251881Speter * empty file with properties set on it. So create an empty file and 2880251881Speter * add it to version control. But if the patch was in the 'git format' 2881251881Speter * then the file has already been added. 2882251881Speter * 2883251881Speter * ### How can we tell whether the patch really wanted to create 2884251881Speter * ### an empty directory? */ 2885251881Speter if (! target->has_text_changes 2886251881Speter && target->kind_on_disk == svn_node_none 2887251881Speter && ! target->added) 2888251881Speter { 2889251881Speter if (! dry_run) 2890251881Speter { 2891299742Sdim SVN_ERR(svn_io_file_create_empty(target->local_abspath, 2892299742Sdim scratch_pool)); 2893299742Sdim SVN_ERR(svn_wc_add_from_disk3(ctx->wc_ctx, target->local_abspath, 2894251881Speter NULL /*props*/, 2895299742Sdim FALSE /* skip checks */, 2896251881Speter /* suppress notification */ 2897251881Speter NULL, NULL, 2898251881Speter iterpool)); 2899251881Speter } 2900251881Speter target->added = TRUE; 2901251881Speter } 2902251881Speter 2903251881Speter /* Attempt to set the property, and reject all hunks if this 2904251881Speter fails. If the property had a non-empty value, but now has 2905251881Speter an empty one, we'll just delete the property altogether. */ 2906251881Speter if (prop_target->value && prop_target->value->len 2907251881Speter && prop_target->patched_value && !prop_target->patched_value->len) 2908251881Speter prop_val = NULL; 2909251881Speter else 2910251881Speter prop_val = svn_stringbuf__morph_into_string(prop_target->patched_value); 2911251881Speter 2912251881Speter if (dry_run) 2913251881Speter { 2914251881Speter const svn_string_t *canon_propval; 2915251881Speter 2916251881Speter err = svn_wc_canonicalize_svn_prop(&canon_propval, 2917251881Speter prop_target->name, 2918251881Speter prop_val, target->local_abspath, 2919251881Speter target->db_kind, 2920251881Speter TRUE, /* ### Skipping checks */ 2921251881Speter NULL, NULL, 2922251881Speter iterpool); 2923251881Speter } 2924251881Speter else 2925251881Speter { 2926251881Speter err = svn_wc_prop_set4(ctx->wc_ctx, target->local_abspath, 2927251881Speter prop_target->name, prop_val, svn_depth_empty, 2928251881Speter TRUE /* skip_checks */, 2929251881Speter NULL /* changelist_filter */, 2930251881Speter NULL, NULL /* cancellation */, 2931251881Speter NULL, NULL /* notification */, 2932251881Speter iterpool); 2933251881Speter } 2934251881Speter 2935251881Speter if (err) 2936251881Speter { 2937251881Speter /* ### The errors which svn_wc_canonicalize_svn_prop() will 2938251881Speter * ### return aren't documented. */ 2939251881Speter if (err->apr_err == SVN_ERR_ILLEGAL_TARGET || 2940251881Speter err->apr_err == SVN_ERR_NODE_UNEXPECTED_KIND || 2941251881Speter err->apr_err == SVN_ERR_IO_UNKNOWN_EOL || 2942251881Speter err->apr_err == SVN_ERR_BAD_MIME_TYPE || 2943251881Speter err->apr_err == SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION) 2944251881Speter { 2945251881Speter int i; 2946251881Speter 2947251881Speter svn_error_clear(err); 2948251881Speter 2949251881Speter for (i = 0; i < prop_target->content->hunks->nelts; i++) 2950251881Speter { 2951251881Speter hunk_info_t *hunk_info; 2952251881Speter 2953251881Speter hunk_info = APR_ARRAY_IDX(prop_target->content->hunks, 2954251881Speter i, hunk_info_t *); 2955251881Speter hunk_info->rejected = TRUE; 2956251881Speter SVN_ERR(reject_hunk(target, prop_target->content, 2957251881Speter hunk_info->hunk, prop_target->name, 2958251881Speter iterpool)); 2959251881Speter } 2960251881Speter } 2961251881Speter else 2962251881Speter return svn_error_trace(err); 2963251881Speter } 2964251881Speter 2965251881Speter } 2966251881Speter 2967251881Speter svn_pool_destroy(iterpool); 2968251881Speter 2969251881Speter return SVN_NO_ERROR; 2970251881Speter} 2971251881Speter 2972251881Speter/* Baton for can_delete_callback */ 2973251881Speterstruct can_delete_baton_t 2974251881Speter{ 2975251881Speter svn_boolean_t must_keep; 2976251881Speter const apr_array_header_t *targets_info; 2977251881Speter const char *local_abspath; 2978251881Speter}; 2979251881Speter 2980251881Speter/* Implements svn_wc_status_func4_t. */ 2981251881Speterstatic svn_error_t * 2982251881Spetercan_delete_callback(void *baton, 2983251881Speter const char *abspath, 2984251881Speter const svn_wc_status3_t *status, 2985251881Speter apr_pool_t *pool) 2986251881Speter{ 2987251881Speter struct can_delete_baton_t *cb = baton; 2988251881Speter int i; 2989251881Speter 2990251881Speter switch(status->node_status) 2991251881Speter { 2992251881Speter case svn_wc_status_none: 2993251881Speter case svn_wc_status_deleted: 2994251881Speter return SVN_NO_ERROR; 2995251881Speter 2996251881Speter default: 2997251881Speter if (! strcmp(cb->local_abspath, abspath)) 2998251881Speter return SVN_NO_ERROR; /* Only interested in descendants */ 2999251881Speter 3000251881Speter for (i = 0; i < cb->targets_info->nelts; i++) 3001251881Speter { 3002251881Speter const patch_target_info_t *target_info = 3003251881Speter APR_ARRAY_IDX(cb->targets_info, i, const patch_target_info_t *); 3004251881Speter 3005251881Speter if (! strcmp(target_info->local_abspath, abspath)) 3006251881Speter { 3007251881Speter if (target_info->deleted) 3008251881Speter return SVN_NO_ERROR; 3009251881Speter 3010251881Speter break; /* Cease invocation; must keep */ 3011251881Speter } 3012251881Speter } 3013251881Speter 3014251881Speter cb->must_keep = TRUE; 3015251881Speter 3016251881Speter return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL); 3017251881Speter } 3018251881Speter} 3019251881Speter 3020251881Speterstatic svn_error_t * 3021251881Spetercheck_ancestor_delete(const char *deleted_target, 3022251881Speter apr_array_header_t *targets_info, 3023251881Speter const char *apply_root, 3024251881Speter svn_boolean_t dry_run, 3025251881Speter svn_client_ctx_t *ctx, 3026251881Speter apr_pool_t *result_pool, 3027251881Speter apr_pool_t *scratch_pool) 3028251881Speter{ 3029251881Speter struct can_delete_baton_t cb; 3030251881Speter svn_error_t *err; 3031299742Sdim apr_array_header_t *ignores; 3032251881Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 3033251881Speter 3034251881Speter const char *dir_abspath = svn_dirent_dirname(deleted_target, scratch_pool); 3035251881Speter 3036299742Sdim SVN_ERR(svn_wc_get_default_ignores(&ignores, ctx->config, scratch_pool)); 3037299742Sdim 3038251881Speter while (svn_dirent_is_child(apply_root, dir_abspath, iterpool)) 3039251881Speter { 3040251881Speter svn_pool_clear(iterpool); 3041251881Speter 3042251881Speter cb.local_abspath = dir_abspath; 3043251881Speter cb.must_keep = FALSE; 3044251881Speter cb.targets_info = targets_info; 3045251881Speter 3046251881Speter err = svn_wc_walk_status(ctx->wc_ctx, dir_abspath, svn_depth_infinity, 3047299742Sdim TRUE, FALSE, FALSE, ignores, 3048251881Speter can_delete_callback, &cb, 3049251881Speter ctx->cancel_func, ctx->cancel_baton, 3050251881Speter iterpool); 3051251881Speter 3052251881Speter if (err) 3053251881Speter { 3054251881Speter if (err->apr_err != SVN_ERR_CEASE_INVOCATION) 3055251881Speter return svn_error_trace(err); 3056251881Speter 3057251881Speter svn_error_clear(err); 3058251881Speter } 3059251881Speter 3060251881Speter if (cb.must_keep) 3061251881Speter { 3062251881Speter break; 3063251881Speter } 3064251881Speter 3065251881Speter if (! dry_run) 3066251881Speter { 3067251881Speter SVN_ERR(svn_wc_delete4(ctx->wc_ctx, dir_abspath, FALSE, FALSE, 3068251881Speter ctx->cancel_func, ctx->cancel_baton, 3069251881Speter NULL, NULL, 3070251881Speter scratch_pool)); 3071251881Speter } 3072251881Speter 3073251881Speter { 3074251881Speter patch_target_info_t *pti = apr_pcalloc(result_pool, sizeof(*pti)); 3075251881Speter 3076251881Speter pti->local_abspath = apr_pstrdup(result_pool, dir_abspath); 3077251881Speter pti->deleted = TRUE; 3078251881Speter 3079251881Speter APR_ARRAY_PUSH(targets_info, patch_target_info_t *) = pti; 3080251881Speter } 3081251881Speter 3082251881Speter 3083251881Speter if (ctx->notify_func2) 3084251881Speter { 3085251881Speter svn_wc_notify_t *notify; 3086251881Speter 3087251881Speter notify = svn_wc_create_notify(dir_abspath, svn_wc_notify_delete, 3088251881Speter iterpool); 3089251881Speter notify->kind = svn_node_dir; 3090251881Speter 3091251881Speter ctx->notify_func2(ctx->notify_baton2, notify, iterpool); 3092251881Speter } 3093251881Speter 3094251881Speter /* And check if we must also delete the parent */ 3095251881Speter dir_abspath = svn_dirent_dirname(dir_abspath, scratch_pool); 3096251881Speter } 3097251881Speter 3098251881Speter svn_pool_destroy(iterpool); 3099251881Speter 3100251881Speter return SVN_NO_ERROR; 3101251881Speter} 3102251881Speter 3103251881Speter/* This function is the main entry point into the patch code. */ 3104251881Speterstatic svn_error_t * 3105251881Speterapply_patches(/* The path to the patch file. */ 3106251881Speter const char *patch_abspath, 3107251881Speter /* The abspath to the working copy the patch should be applied to. */ 3108251881Speter const char *abs_wc_path, 3109251881Speter /* Indicates whether we're doing a dry run. */ 3110251881Speter svn_boolean_t dry_run, 3111251881Speter /* Number of leading components to strip from patch target paths. */ 3112251881Speter int strip_count, 3113251881Speter /* Whether to apply the patch in reverse. */ 3114251881Speter svn_boolean_t reverse, 3115251881Speter /* Whether to ignore whitespace when matching context lines. */ 3116251881Speter svn_boolean_t ignore_whitespace, 3117251881Speter /* As in svn_client_patch(). */ 3118251881Speter svn_boolean_t remove_tempfiles, 3119251881Speter /* As in svn_client_patch(). */ 3120251881Speter svn_client_patch_func_t patch_func, 3121251881Speter void *patch_baton, 3122251881Speter /* The client context. */ 3123251881Speter svn_client_ctx_t *ctx, 3124251881Speter apr_pool_t *scratch_pool) 3125251881Speter{ 3126251881Speter svn_patch_t *patch; 3127251881Speter apr_pool_t *iterpool; 3128251881Speter svn_patch_file_t *patch_file; 3129251881Speter apr_array_header_t *targets_info; 3130251881Speter 3131251881Speter /* Try to open the patch file. */ 3132251881Speter SVN_ERR(svn_diff_open_patch_file(&patch_file, patch_abspath, scratch_pool)); 3133251881Speter 3134251881Speter /* Apply patches. */ 3135251881Speter targets_info = apr_array_make(scratch_pool, 0, 3136251881Speter sizeof(patch_target_info_t *)); 3137251881Speter iterpool = svn_pool_create(scratch_pool); 3138251881Speter do 3139251881Speter { 3140251881Speter svn_pool_clear(iterpool); 3141251881Speter 3142251881Speter if (ctx->cancel_func) 3143251881Speter SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 3144251881Speter 3145251881Speter SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file, 3146251881Speter reverse, ignore_whitespace, 3147251881Speter iterpool, iterpool)); 3148251881Speter if (patch) 3149251881Speter { 3150251881Speter patch_target_t *target; 3151299742Sdim svn_boolean_t filtered = FALSE; 3152251881Speter 3153251881Speter SVN_ERR(apply_one_patch(&target, patch, abs_wc_path, 3154251881Speter ctx->wc_ctx, strip_count, 3155251881Speter ignore_whitespace, remove_tempfiles, 3156251881Speter ctx->cancel_func, ctx->cancel_baton, 3157251881Speter iterpool, iterpool)); 3158299742Sdim 3159299742Sdim if (!target->skipped && patch_func) 3160251881Speter { 3161299742Sdim SVN_ERR(patch_func(patch_baton, &filtered, 3162299742Sdim target->canon_path_from_patchfile, 3163299742Sdim target->patched_path, target->reject_path, 3164299742Sdim iterpool)); 3165299742Sdim } 3166299742Sdim 3167299742Sdim if (! filtered) 3168299742Sdim { 3169251881Speter /* Save info we'll still need when we're done patching. */ 3170251881Speter patch_target_info_t *target_info = 3171251881Speter apr_pcalloc(scratch_pool, sizeof(patch_target_info_t)); 3172251881Speter target_info->local_abspath = apr_pstrdup(scratch_pool, 3173251881Speter target->local_abspath); 3174251881Speter target_info->deleted = target->deleted; 3175251881Speter 3176251881Speter if (! target->skipped) 3177251881Speter { 3178251881Speter APR_ARRAY_PUSH(targets_info, 3179251881Speter patch_target_info_t *) = target_info; 3180251881Speter 3181251881Speter if (target->has_text_changes 3182251881Speter || target->added 3183299742Sdim || target->move_target_abspath 3184251881Speter || target->deleted) 3185251881Speter SVN_ERR(install_patched_target(target, abs_wc_path, 3186251881Speter ctx, dry_run, iterpool)); 3187251881Speter 3188251881Speter if (target->has_prop_changes && (!target->deleted)) 3189251881Speter SVN_ERR(install_patched_prop_targets(target, ctx, 3190251881Speter dry_run, iterpool)); 3191251881Speter 3192251881Speter SVN_ERR(write_out_rejected_hunks(target, dry_run, iterpool)); 3193251881Speter } 3194251881Speter SVN_ERR(send_patch_notification(target, ctx, iterpool)); 3195251881Speter 3196251881Speter if (target->deleted && !target->skipped) 3197251881Speter { 3198251881Speter SVN_ERR(check_ancestor_delete(target_info->local_abspath, 3199251881Speter targets_info, abs_wc_path, 3200251881Speter dry_run, ctx, 3201251881Speter scratch_pool, iterpool)); 3202251881Speter } 3203251881Speter } 3204251881Speter } 3205251881Speter } 3206251881Speter while (patch); 3207251881Speter 3208251881Speter SVN_ERR(svn_diff_close_patch_file(patch_file, iterpool)); 3209251881Speter svn_pool_destroy(iterpool); 3210251881Speter 3211251881Speter return SVN_NO_ERROR; 3212251881Speter} 3213251881Speter 3214251881Spetersvn_error_t * 3215251881Spetersvn_client_patch(const char *patch_abspath, 3216251881Speter const char *wc_dir_abspath, 3217251881Speter svn_boolean_t dry_run, 3218251881Speter int strip_count, 3219251881Speter svn_boolean_t reverse, 3220251881Speter svn_boolean_t ignore_whitespace, 3221251881Speter svn_boolean_t remove_tempfiles, 3222251881Speter svn_client_patch_func_t patch_func, 3223251881Speter void *patch_baton, 3224251881Speter svn_client_ctx_t *ctx, 3225251881Speter apr_pool_t *scratch_pool) 3226251881Speter{ 3227251881Speter svn_node_kind_t kind; 3228251881Speter 3229251881Speter if (strip_count < 0) 3230251881Speter return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, 3231251881Speter _("strip count must be positive")); 3232251881Speter 3233251881Speter if (svn_path_is_url(wc_dir_abspath)) 3234251881Speter return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 3235251881Speter _("'%s' is not a local path"), 3236251881Speter svn_dirent_local_style(wc_dir_abspath, 3237251881Speter scratch_pool)); 3238251881Speter 3239251881Speter SVN_ERR(svn_io_check_path(patch_abspath, &kind, scratch_pool)); 3240251881Speter if (kind == svn_node_none) 3241251881Speter return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 3242251881Speter _("'%s' does not exist"), 3243251881Speter svn_dirent_local_style(patch_abspath, 3244251881Speter scratch_pool)); 3245251881Speter if (kind != svn_node_file) 3246251881Speter return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 3247251881Speter _("'%s' is not a file"), 3248251881Speter svn_dirent_local_style(patch_abspath, 3249251881Speter scratch_pool)); 3250251881Speter 3251251881Speter SVN_ERR(svn_io_check_path(wc_dir_abspath, &kind, scratch_pool)); 3252251881Speter if (kind == svn_node_none) 3253251881Speter return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 3254251881Speter _("'%s' does not exist"), 3255251881Speter svn_dirent_local_style(wc_dir_abspath, 3256251881Speter scratch_pool)); 3257251881Speter if (kind != svn_node_dir) 3258251881Speter return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 3259251881Speter _("'%s' is not a directory"), 3260251881Speter svn_dirent_local_style(wc_dir_abspath, 3261251881Speter scratch_pool)); 3262251881Speter 3263251881Speter SVN_WC__CALL_WITH_WRITE_LOCK( 3264251881Speter apply_patches(patch_abspath, wc_dir_abspath, dry_run, strip_count, 3265251881Speter reverse, ignore_whitespace, remove_tempfiles, 3266251881Speter patch_func, patch_baton, ctx, scratch_pool), 3267251881Speter ctx->wc_ctx, wc_dir_abspath, FALSE /* lock_anchor */, scratch_pool); 3268251881Speter return SVN_NO_ERROR; 3269251881Speter} 3270