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" 51251881Speter 52251881Spetertypedef struct hunk_info_t { 53251881Speter /* The hunk. */ 54251881Speter svn_diff_hunk_t *hunk; 55251881Speter 56251881Speter /* The line where the hunk matched in the target file. */ 57251881Speter svn_linenum_t matched_line; 58251881Speter 59251881Speter /* Whether this hunk has been rejected. */ 60251881Speter svn_boolean_t rejected; 61251881Speter 62251881Speter /* Whether this hunk has already been applied (either manually 63251881Speter * or by an earlier run of patch). */ 64251881Speter svn_boolean_t already_applied; 65251881Speter 66251881Speter /* The fuzz factor used when matching this hunk, i.e. how many 67251881Speter * lines of leading and trailing context to ignore during matching. */ 68251881Speter svn_linenum_t fuzz; 69251881Speter} hunk_info_t; 70251881Speter 71251881Speter/* A struct carrying information related to the patched and unpatched 72251881Speter * content of a target, be it a property or the text of a file. */ 73251881Spetertypedef struct target_content_t { 74251881Speter /* Indicates whether unpatched content existed prior to patching. */ 75251881Speter svn_boolean_t existed; 76251881Speter 77251881Speter /* The line last read from the unpatched content. */ 78251881Speter svn_linenum_t current_line; 79251881Speter 80251881Speter /* The EOL-style of the unpatched content. Either 'none', 'fixed', 81251881Speter * or 'native'. See the documentation of svn_subst_eol_style_t. */ 82251881Speter svn_subst_eol_style_t eol_style; 83251881Speter 84251881Speter /* If the EOL_STYLE above is not 'none', this is the EOL string 85251881Speter * corresponding to the EOL-style. Else, it is the EOL string the 86251881Speter * last line read from the target file was using. */ 87251881Speter const char *eol_str; 88251881Speter 89251881Speter /* An array containing apr_off_t offsets marking the beginning of 90251881Speter * each line in the unpatched content. */ 91251881Speter apr_array_header_t *lines; 92251881Speter 93251881Speter /* An array containing hunk_info_t structures for hunks already matched. */ 94251881Speter apr_array_header_t *hunks; 95251881Speter 96251881Speter /* True if end-of-file was reached while reading from the unpatched 97251881Speter * content. */ 98251881Speter svn_boolean_t eof; 99251881Speter 100251881Speter /* The keywords of the target. They will be contracted when reading 101251881Speter * unpatched content and expanded when writing patched content. 102251881Speter * When patching properties this hash is always empty. */ 103251881Speter apr_hash_t *keywords; 104251881Speter 105251881Speter /* A callback, with an associated baton, to read a line of unpatched 106251881Speter * content. */ 107251881Speter svn_error_t *(*readline)(void *baton, svn_stringbuf_t **line, 108251881Speter const char **eol_str, svn_boolean_t *eof, 109251881Speter apr_pool_t *result_pool, apr_pool_t *scratch_pool); 110251881Speter void *read_baton; 111251881Speter 112251881Speter /* A callback to get the current byte offset within the unpatched 113251881Speter * content. Uses the read baton. */ 114251881Speter svn_error_t * (*tell)(void *baton, apr_off_t *offset, 115251881Speter apr_pool_t *scratch_pool); 116251881Speter 117251881Speter /* A callback to seek to an offset within the unpatched content. 118251881Speter * Uses the read baton. */ 119251881Speter svn_error_t * (*seek)(void *baton, apr_off_t offset, 120251881Speter apr_pool_t *scratch_pool); 121251881Speter 122251881Speter /* A callback to write data to the patched content, with an 123251881Speter * associated baton. */ 124251881Speter svn_error_t * (*write)(void *baton, const char *buf, apr_size_t len, 125251881Speter apr_pool_t *scratch_pool); 126251881Speter void *write_baton; 127251881Speter 128251881Speter} target_content_t; 129251881Speter 130251881Spetertypedef struct prop_patch_target_t { 131251881Speter 132251881Speter /* The name of the property */ 133251881Speter const char *name; 134251881Speter 135251881Speter /* The property value. This is NULL in case the property did not exist 136251881Speter * prior to patch application (see also CONTENT->existed). 137251881Speter * Note that the patch implementation does not support binary properties, 138251881Speter * so this string is not expected to contain embedded NUL characters. */ 139251881Speter const svn_string_t *value; 140251881Speter 141251881Speter /* The patched property value. 142251881Speter * This is equivalent to the target, except that in appropriate 143251881Speter * places it contains the modified text as it appears in the patch file. */ 144251881Speter svn_stringbuf_t *patched_value; 145251881Speter 146251881Speter /* All information that is specific to the content of the property. */ 147251881Speter target_content_t *content; 148251881Speter 149251881Speter /* Represents the operation performed on the property. It can be added, 150251881Speter * deleted or modified. 151251881Speter * ### Should we use flags instead since we're not using all enum values? */ 152251881Speter svn_diff_operation_kind_t operation; 153251881Speter 154251881Speter /* ### Here we'll add flags telling if the prop was added, deleted, 155251881Speter * ### had_rejects, had_local_mods prior to patching and so on. */ 156251881Speter} prop_patch_target_t; 157251881Speter 158251881Spetertypedef struct patch_target_t { 159251881Speter /* The target path as it appeared in the patch file, 160251881Speter * but in canonicalised form. */ 161251881Speter const char *canon_path_from_patchfile; 162251881Speter 163251881Speter /* The target path, relative to the working copy directory the 164251881Speter * patch is being applied to. A patch strip count applies to this 165251881Speter * and only this path. This is never NULL. */ 166251881Speter const char *local_relpath; 167251881Speter 168251881Speter /* The absolute path of the target on the filesystem. 169251881Speter * Any symlinks the path from the patch file may contain are resolved. 170251881Speter * Is not always known, so it may be NULL. */ 171251881Speter const char *local_abspath; 172251881Speter 173251881Speter /* The target file, read-only. This is NULL in case the target 174251881Speter * file did not exist prior to patch application (see also 175251881Speter * CONTENT->existed). */ 176251881Speter apr_file_t *file; 177251881Speter 178251881Speter /* The target file is a symlink */ 179251881Speter svn_boolean_t is_symlink; 180251881Speter 181251881Speter /* The patched file. 182251881Speter * This is equivalent to the target, except that in appropriate 183251881Speter * places it contains the modified text as it appears in the patch file. 184251881Speter * The data in this file is written in repository-normal form. 185251881Speter * EOL transformation and keyword contraction is performed when the 186251881Speter * patched result is installed in the working copy. */ 187251881Speter apr_file_t *patched_file; 188251881Speter 189251881Speter /* Path to the patched file. */ 190251881Speter const char *patched_path; 191251881Speter 192251881Speter /* Hunks that are rejected will be written to this file. */ 193251881Speter apr_file_t *reject_file; 194251881Speter 195251881Speter /* Path to the reject file. */ 196251881Speter const char *reject_path; 197251881Speter 198251881Speter /* The node kind of the target as found in WC-DB prior 199251881Speter * to patch application. */ 200251881Speter svn_node_kind_t db_kind; 201251881Speter 202251881Speter /* The target's kind on disk prior to patch application. */ 203251881Speter svn_node_kind_t kind_on_disk; 204251881Speter 205251881Speter /* True if the target was locally deleted prior to patching. */ 206251881Speter svn_boolean_t locally_deleted; 207251881Speter 208251881Speter /* True if the target had to be skipped for some reason. */ 209251881Speter svn_boolean_t skipped; 210251881Speter 211251881Speter /* True if the target has been filtered by the patch callback. */ 212251881Speter svn_boolean_t filtered; 213251881Speter 214251881Speter /* True if at least one hunk was rejected. */ 215251881Speter svn_boolean_t had_rejects; 216251881Speter 217251881Speter /* True if at least one property hunk was rejected. */ 218251881Speter svn_boolean_t had_prop_rejects; 219251881Speter 220251881Speter /* True if the target file had local modifications before the 221251881Speter * patch was applied to it. */ 222251881Speter svn_boolean_t local_mods; 223251881Speter 224251881Speter /* True if the target was added by the patch, which means that it did 225251881Speter * not exist on disk before patching and has content after patching. */ 226251881Speter svn_boolean_t added; 227251881Speter 228251881Speter /* True if the target ended up being deleted by the patch. */ 229251881Speter svn_boolean_t deleted; 230251881Speter 231251881Speter /* True if the target ended up being replaced by the patch 232251881Speter * (i.e. a new file was added on top locally deleted node). */ 233251881Speter svn_boolean_t replaced; 234251881Speter 235251881Speter /* True if the target has the executable bit set. */ 236251881Speter svn_boolean_t executable; 237251881Speter 238251881Speter /* True if the patch changed the text of the target. */ 239251881Speter svn_boolean_t has_text_changes; 240251881Speter 241251881Speter /* True if the patch changed any of the properties of the target. */ 242251881Speter svn_boolean_t has_prop_changes; 243251881Speter 244251881Speter /* True if the patch contained a svn:special property. */ 245251881Speter svn_boolean_t is_special; 246251881Speter 247251881Speter /* All the information that is specific to the content of the target. */ 248251881Speter target_content_t *content; 249251881Speter 250251881Speter /* A hash table of prop_patch_target_t objects keyed by property names. */ 251251881Speter apr_hash_t *prop_targets; 252251881Speter 253251881Speter} patch_target_t; 254251881Speter 255251881Speter 256251881Speter/* A smaller struct containing a subset of patch_target_t. 257251881Speter * Carries the minimal amount of information we still need for a 258251881Speter * target after we're done patching it so we can free other resources. */ 259251881Spetertypedef struct patch_target_info_t { 260251881Speter const char *local_abspath; 261251881Speter svn_boolean_t deleted; 262251881Speter} patch_target_info_t; 263251881Speter 264251881Speter 265251881Speter/* Strip STRIP_COUNT components from the front of PATH, returning 266251881Speter * the result in *RESULT, allocated in RESULT_POOL. 267251881Speter * Do temporary allocations in SCRATCH_POOL. */ 268251881Speterstatic svn_error_t * 269251881Speterstrip_path(const char **result, const char *path, int strip_count, 270251881Speter apr_pool_t *result_pool, apr_pool_t *scratch_pool) 271251881Speter{ 272251881Speter int i; 273251881Speter apr_array_header_t *components; 274251881Speter apr_array_header_t *stripped; 275251881Speter 276251881Speter components = svn_path_decompose(path, scratch_pool); 277251881Speter if (strip_count > components->nelts) 278251881Speter return svn_error_createf(SVN_ERR_CLIENT_PATCH_BAD_STRIP_COUNT, NULL, 279251881Speter _("Cannot strip %u components from '%s'"), 280251881Speter strip_count, 281251881Speter svn_dirent_local_style(path, scratch_pool)); 282251881Speter 283251881Speter stripped = apr_array_make(scratch_pool, components->nelts - strip_count, 284251881Speter sizeof(const char *)); 285251881Speter for (i = strip_count; i < components->nelts; i++) 286251881Speter { 287251881Speter const char *component; 288251881Speter 289251881Speter component = APR_ARRAY_IDX(components, i, const char *); 290251881Speter APR_ARRAY_PUSH(stripped, const char *) = component; 291251881Speter } 292251881Speter 293251881Speter *result = svn_path_compose(stripped, result_pool); 294251881Speter 295251881Speter return SVN_NO_ERROR; 296251881Speter} 297251881Speter 298251881Speter/* Obtain KEYWORDS, EOL_STYLE and EOL_STR for LOCAL_ABSPATH. 299251881Speter * WC_CTX is a context for the working copy the patch is applied to. 300251881Speter * Use RESULT_POOL for allocations of fields in TARGET. 301251881Speter * Use SCRATCH_POOL for all other allocations. */ 302251881Speterstatic svn_error_t * 303251881Speterobtain_eol_and_keywords_for_file(apr_hash_t **keywords, 304251881Speter svn_subst_eol_style_t *eol_style, 305251881Speter const char **eol_str, 306251881Speter svn_wc_context_t *wc_ctx, 307251881Speter const char *local_abspath, 308251881Speter apr_pool_t *result_pool, 309251881Speter apr_pool_t *scratch_pool) 310251881Speter{ 311251881Speter apr_hash_t *props; 312251881Speter svn_string_t *keywords_val, *eol_style_val; 313251881Speter 314251881Speter SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, local_abspath, 315251881Speter scratch_pool, scratch_pool)); 316251881Speter keywords_val = svn_hash_gets(props, SVN_PROP_KEYWORDS); 317251881Speter if (keywords_val) 318251881Speter { 319251881Speter svn_revnum_t changed_rev; 320251881Speter apr_time_t changed_date; 321251881Speter const char *rev_str; 322251881Speter const char *author; 323251881Speter const char *url; 324251881Speter const char *root_url; 325251881Speter 326251881Speter SVN_ERR(svn_wc__node_get_changed_info(&changed_rev, 327251881Speter &changed_date, 328251881Speter &author, wc_ctx, 329251881Speter local_abspath, 330251881Speter scratch_pool, 331251881Speter scratch_pool)); 332251881Speter rev_str = apr_psprintf(scratch_pool, "%ld", changed_rev); 333251881Speter SVN_ERR(svn_wc__node_get_url(&url, wc_ctx, 334251881Speter local_abspath, 335251881Speter scratch_pool, scratch_pool)); 336251881Speter SVN_ERR(svn_wc__node_get_repos_info(NULL, NULL, &root_url, NULL, 337251881Speter wc_ctx, local_abspath, 338251881Speter scratch_pool, scratch_pool)); 339251881Speter SVN_ERR(svn_subst_build_keywords3(keywords, 340251881Speter keywords_val->data, 341251881Speter rev_str, url, root_url, changed_date, 342251881Speter author, result_pool)); 343251881Speter } 344251881Speter 345251881Speter eol_style_val = svn_hash_gets(props, SVN_PROP_EOL_STYLE); 346251881Speter if (eol_style_val) 347251881Speter { 348251881Speter svn_subst_eol_style_from_value(eol_style, 349251881Speter eol_str, 350251881Speter eol_style_val->data); 351251881Speter } 352251881Speter 353251881Speter return SVN_NO_ERROR; 354251881Speter} 355251881Speter 356251881Speter/* Resolve the exact path for a patch TARGET at path PATH_FROM_PATCHFILE, 357251881Speter * which is the path of the target as it appeared in the patch file. 358251881Speter * Put a canonicalized version of PATH_FROM_PATCHFILE into 359251881Speter * TARGET->CANON_PATH_FROM_PATCHFILE. 360251881Speter * WC_CTX is a context for the working copy the patch is applied to. 361251881Speter * If possible, determine TARGET->WC_PATH, TARGET->ABS_PATH, TARGET->KIND, 362251881Speter * TARGET->ADDED, and TARGET->PARENT_DIR_EXISTS. 363251881Speter * Indicate in TARGET->SKIPPED whether the target should be skipped. 364251881Speter * STRIP_COUNT specifies the number of leading path components 365251881Speter * which should be stripped from target paths in the patch. 366251881Speter * PROP_CHANGES_ONLY specifies whether the target path is allowed to have 367251881Speter * only property changes, and no content changes (in which case the target 368251881Speter * must be a directory). 369251881Speter * Use RESULT_POOL for allocations of fields in TARGET. 370251881Speter * Use SCRATCH_POOL for all other allocations. */ 371251881Speterstatic svn_error_t * 372251881Speterresolve_target_path(patch_target_t *target, 373251881Speter const char *path_from_patchfile, 374251881Speter const char *wcroot_abspath, 375251881Speter int strip_count, 376251881Speter svn_boolean_t prop_changes_only, 377251881Speter svn_wc_context_t *wc_ctx, 378251881Speter apr_pool_t *result_pool, 379251881Speter apr_pool_t *scratch_pool) 380251881Speter{ 381251881Speter const char *stripped_path; 382251881Speter svn_wc_status3_t *status; 383251881Speter svn_error_t *err; 384251881Speter svn_boolean_t under_root; 385251881Speter 386251881Speter target->canon_path_from_patchfile = svn_dirent_internal_style( 387251881Speter path_from_patchfile, result_pool); 388251881Speter 389251881Speter /* We allow properties to be set on the wc root dir. */ 390251881Speter if (! prop_changes_only && target->canon_path_from_patchfile[0] == '\0') 391251881Speter { 392251881Speter /* An empty patch target path? What gives? Skip this. */ 393251881Speter target->skipped = TRUE; 394251881Speter target->local_abspath = NULL; 395251881Speter target->local_relpath = ""; 396251881Speter return SVN_NO_ERROR; 397251881Speter } 398251881Speter 399251881Speter if (strip_count > 0) 400251881Speter SVN_ERR(strip_path(&stripped_path, target->canon_path_from_patchfile, 401251881Speter strip_count, result_pool, scratch_pool)); 402251881Speter else 403251881Speter stripped_path = target->canon_path_from_patchfile; 404251881Speter 405251881Speter if (svn_dirent_is_absolute(stripped_path)) 406251881Speter { 407251881Speter target->local_relpath = svn_dirent_is_child(wcroot_abspath, 408251881Speter stripped_path, 409251881Speter result_pool); 410251881Speter 411251881Speter if (! target->local_relpath) 412251881Speter { 413251881Speter /* The target path is either outside of the working copy 414251881Speter * or it is the working copy itself. Skip it. */ 415251881Speter target->skipped = TRUE; 416251881Speter target->local_abspath = NULL; 417251881Speter target->local_relpath = stripped_path; 418251881Speter return SVN_NO_ERROR; 419251881Speter } 420251881Speter } 421251881Speter else 422251881Speter { 423251881Speter target->local_relpath = stripped_path; 424251881Speter } 425251881Speter 426251881Speter /* Make sure the path is secure to use. We want the target to be inside 427251881Speter * of the working copy and not be fooled by symlinks it might contain. */ 428251881Speter SVN_ERR(svn_dirent_is_under_root(&under_root, 429251881Speter &target->local_abspath, wcroot_abspath, 430251881Speter target->local_relpath, result_pool)); 431251881Speter 432251881Speter if (! under_root) 433251881Speter { 434251881Speter /* The target path is outside of the working copy. Skip it. */ 435251881Speter target->skipped = TRUE; 436251881Speter target->local_abspath = NULL; 437251881Speter return SVN_NO_ERROR; 438251881Speter } 439251881Speter 440251881Speter /* Skip things we should not be messing with. */ 441251881Speter err = svn_wc_status3(&status, wc_ctx, target->local_abspath, 442251881Speter result_pool, scratch_pool); 443251881Speter if (err) 444251881Speter { 445251881Speter if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) 446251881Speter return svn_error_trace(err); 447251881Speter 448251881Speter svn_error_clear(err); 449251881Speter 450251881Speter target->locally_deleted = TRUE; 451251881Speter target->db_kind = svn_node_none; 452251881Speter status = NULL; 453251881Speter } 454251881Speter else if (status->node_status == svn_wc_status_ignored || 455251881Speter status->node_status == svn_wc_status_unversioned || 456251881Speter status->node_status == svn_wc_status_missing || 457251881Speter status->node_status == svn_wc_status_obstructed || 458251881Speter status->conflicted) 459251881Speter { 460251881Speter target->skipped = TRUE; 461251881Speter return SVN_NO_ERROR; 462251881Speter } 463251881Speter else if (status->node_status == svn_wc_status_deleted) 464251881Speter { 465251881Speter target->locally_deleted = TRUE; 466251881Speter } 467251881Speter 468251881Speter if (status && (status->kind != svn_node_unknown)) 469251881Speter target->db_kind = status->kind; 470251881Speter else 471251881Speter target->db_kind = svn_node_none; 472251881Speter 473251881Speter SVN_ERR(svn_io_check_special_path(target->local_abspath, 474251881Speter &target->kind_on_disk, &target->is_symlink, 475251881Speter scratch_pool)); 476251881Speter 477251881Speter if (target->locally_deleted) 478251881Speter { 479251881Speter const char *moved_to_abspath; 480251881Speter 481251881Speter SVN_ERR(svn_wc__node_was_moved_away(&moved_to_abspath, NULL, 482251881Speter wc_ctx, target->local_abspath, 483251881Speter result_pool, scratch_pool)); 484251881Speter if (moved_to_abspath) 485251881Speter { 486251881Speter target->local_abspath = moved_to_abspath; 487251881Speter target->local_relpath = svn_dirent_skip_ancestor(wcroot_abspath, 488251881Speter moved_to_abspath); 489251881Speter SVN_ERR_ASSERT(target->local_relpath && 490251881Speter target->local_relpath[0] != '\0'); 491251881Speter 492251881Speter /* As far as we are concerned this target is not locally deleted. */ 493251881Speter target->locally_deleted = FALSE; 494251881Speter 495251881Speter SVN_ERR(svn_io_check_special_path(target->local_abspath, 496251881Speter &target->kind_on_disk, 497251881Speter &target->is_symlink, 498251881Speter scratch_pool)); 499251881Speter } 500251881Speter else if (target->kind_on_disk != svn_node_none) 501251881Speter { 502251881Speter target->skipped = TRUE; 503251881Speter return SVN_NO_ERROR; 504251881Speter } 505251881Speter } 506251881Speter 507251881Speter return SVN_NO_ERROR; 508251881Speter} 509251881Speter 510251881Speter/* Baton for reading from properties. */ 511251881Spetertypedef struct prop_read_baton_t { 512251881Speter const svn_string_t *value; 513251881Speter apr_off_t offset; 514251881Speter} prop_read_baton_t; 515251881Speter 516251881Speter/* Allocate *STRINGBUF in RESULT_POOL, and read into it one line from 517251881Speter * the unpatched property value accessed via BATON. 518251881Speter * Reading stops either after a line-terminator was found, or if 519251881Speter * the property value runs out in which case *EOF is set to TRUE. 520251881Speter * The line-terminator is not stored in *STRINGBUF. 521251881Speter * 522251881Speter * If the line is empty or could not be read, *line is set to NULL. 523251881Speter * 524251881Speter * The line-terminator is detected automatically and stored in *EOL 525251881Speter * if EOL is not NULL. If the end of the property value is reached 526251881Speter * and does not end with a newline character, and EOL is not NULL, 527251881Speter * *EOL is set to NULL. 528251881Speter * 529251881Speter * SCRATCH_POOL is used for temporary allocations. 530251881Speter */ 531251881Speterstatic svn_error_t * 532251881Speterreadline_prop(void *baton, svn_stringbuf_t **line, const char **eol_str, 533251881Speter svn_boolean_t *eof, apr_pool_t *result_pool, 534251881Speter apr_pool_t *scratch_pool) 535251881Speter{ 536251881Speter prop_read_baton_t *b = (prop_read_baton_t *)baton; 537251881Speter svn_stringbuf_t *str = NULL; 538251881Speter const char *c; 539251881Speter svn_boolean_t found_eof; 540251881Speter 541251881Speter if ((apr_uint64_t)b->offset >= (apr_uint64_t)b->value->len) 542251881Speter { 543251881Speter *eol_str = NULL; 544251881Speter *eof = TRUE; 545251881Speter *line = NULL; 546251881Speter return SVN_NO_ERROR; 547251881Speter } 548251881Speter 549251881Speter /* Read bytes into STR up to and including, but not storing, 550251881Speter * the next EOL sequence. */ 551251881Speter *eol_str = NULL; 552251881Speter found_eof = FALSE; 553251881Speter do 554251881Speter { 555251881Speter c = b->value->data + b->offset; 556251881Speter b->offset++; 557251881Speter 558251881Speter if (*c == '\0') 559251881Speter { 560251881Speter found_eof = TRUE; 561251881Speter break; 562251881Speter } 563251881Speter else if (*c == '\n') 564251881Speter { 565251881Speter *eol_str = "\n"; 566251881Speter } 567251881Speter else if (*c == '\r') 568251881Speter { 569251881Speter *eol_str = "\r"; 570251881Speter if (*(c + 1) == '\n') 571251881Speter { 572251881Speter *eol_str = "\r\n"; 573251881Speter b->offset++; 574251881Speter } 575251881Speter } 576251881Speter else 577251881Speter { 578251881Speter if (str == NULL) 579251881Speter str = svn_stringbuf_create_ensure(80, result_pool); 580251881Speter svn_stringbuf_appendbyte(str, *c); 581251881Speter } 582251881Speter 583251881Speter if (*eol_str) 584251881Speter break; 585251881Speter } 586251881Speter while (c < b->value->data + b->value->len); 587251881Speter 588251881Speter if (eof) 589251881Speter *eof = found_eof; 590251881Speter *line = str; 591251881Speter 592251881Speter return SVN_NO_ERROR; 593251881Speter} 594251881Speter 595251881Speter/* Return in *OFFSET the current byte offset for reading from the 596251881Speter * unpatched property value accessed via BATON. 597251881Speter * Use SCRATCH_POOL for temporary allocations. */ 598251881Speterstatic svn_error_t * 599251881Spetertell_prop(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool) 600251881Speter{ 601251881Speter prop_read_baton_t *b = (prop_read_baton_t *)baton; 602251881Speter *offset = b->offset; 603251881Speter return SVN_NO_ERROR; 604251881Speter} 605251881Speter 606251881Speter/* Seek to the specified by OFFSET in the unpatched property value accessed 607251881Speter * via BATON. Use SCRATCH_POOL for temporary allocations. */ 608251881Speterstatic svn_error_t * 609251881Speterseek_prop(void *baton, apr_off_t offset, apr_pool_t *scratch_pool) 610251881Speter{ 611251881Speter prop_read_baton_t *b = (prop_read_baton_t *)baton; 612251881Speter b->offset = offset; 613251881Speter return SVN_NO_ERROR; 614251881Speter} 615251881Speter 616251881Speter/* Write LEN bytes from BUF into the patched property value accessed 617251881Speter * via BATON. Use SCRATCH_POOL for temporary allocations. */ 618251881Speterstatic svn_error_t * 619251881Speterwrite_prop(void *baton, const char *buf, apr_size_t len, 620251881Speter apr_pool_t *scratch_pool) 621251881Speter{ 622251881Speter svn_stringbuf_t *patched_value = (svn_stringbuf_t *)baton; 623251881Speter svn_stringbuf_appendbytes(patched_value, buf, len); 624251881Speter return SVN_NO_ERROR; 625251881Speter} 626251881Speter 627251881Speter/* Initialize a PROP_TARGET structure for PROP_NAME on the patch target 628251881Speter * at LOCAL_ABSPATH. OPERATION indicates the operation performed on the 629251881Speter * property. Use working copy context WC_CTX. 630251881Speter * Allocate results in RESULT_POOL. 631251881Speter * Use SCRATCH_POOL for temporary allocations. */ 632251881Speterstatic svn_error_t * 633251881Speterinit_prop_target(prop_patch_target_t **prop_target, 634251881Speter const char *prop_name, 635251881Speter svn_diff_operation_kind_t operation, 636251881Speter svn_wc_context_t *wc_ctx, 637251881Speter const char *local_abspath, 638251881Speter apr_pool_t *result_pool, apr_pool_t *scratch_pool) 639251881Speter{ 640251881Speter prop_patch_target_t *new_prop_target; 641251881Speter target_content_t *content; 642251881Speter const svn_string_t *value; 643251881Speter svn_error_t *err; 644251881Speter prop_read_baton_t *prop_read_baton; 645251881Speter 646251881Speter content = apr_pcalloc(result_pool, sizeof(*content)); 647251881Speter 648251881Speter /* All other fields are FALSE or NULL due to apr_pcalloc(). */ 649251881Speter content->current_line = 1; 650251881Speter content->eol_style = svn_subst_eol_style_none; 651251881Speter content->lines = apr_array_make(result_pool, 0, sizeof(apr_off_t)); 652251881Speter content->hunks = apr_array_make(result_pool, 0, sizeof(hunk_info_t *)); 653251881Speter content->keywords = apr_hash_make(result_pool); 654251881Speter 655251881Speter new_prop_target = apr_pcalloc(result_pool, sizeof(*new_prop_target)); 656251881Speter new_prop_target->name = apr_pstrdup(result_pool, prop_name); 657251881Speter new_prop_target->operation = operation; 658251881Speter new_prop_target->content = content; 659251881Speter 660251881Speter err = svn_wc_prop_get2(&value, wc_ctx, local_abspath, prop_name, 661251881Speter result_pool, scratch_pool); 662251881Speter if (err) 663251881Speter { 664251881Speter if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) 665251881Speter { 666251881Speter svn_error_clear(err); 667251881Speter value = NULL; 668251881Speter } 669251881Speter else 670251881Speter return svn_error_trace(err); 671251881Speter } 672251881Speter content->existed = (value != NULL); 673251881Speter new_prop_target->value = value; 674251881Speter new_prop_target->patched_value = svn_stringbuf_create_empty(result_pool); 675251881Speter 676251881Speter 677251881Speter /* Wire up the read and write callbacks. */ 678251881Speter prop_read_baton = apr_pcalloc(result_pool, sizeof(*prop_read_baton)); 679251881Speter prop_read_baton->value = value; 680251881Speter prop_read_baton->offset = 0; 681251881Speter content->readline = readline_prop; 682251881Speter content->tell = tell_prop; 683251881Speter content->seek = seek_prop; 684251881Speter content->read_baton = prop_read_baton; 685251881Speter content->write = write_prop; 686251881Speter content->write_baton = new_prop_target->patched_value; 687251881Speter 688251881Speter *prop_target = new_prop_target; 689251881Speter 690251881Speter return SVN_NO_ERROR; 691251881Speter} 692251881Speter 693251881Speter/* Allocate *STRINGBUF in RESULT_POOL, and read into it one line from 694251881Speter * the unpatched file content accessed via BATON. 695251881Speter * Reading stops either after a line-terminator was found, 696251881Speter * or if EOF is reached in which case *EOF is set to TRUE. 697251881Speter * The line-terminator is not stored in *STRINGBUF. 698251881Speter * 699251881Speter * If the line is empty or could not be read, *line is set to NULL. 700251881Speter * 701251881Speter * The line-terminator is detected automatically and stored in *EOL 702251881Speter * if EOL is not NULL. If EOF is reached and FILE does not end 703251881Speter * with a newline character, and EOL is not NULL, *EOL is set to NULL. 704251881Speter * 705251881Speter * SCRATCH_POOL is used for temporary allocations. 706251881Speter */ 707251881Speterstatic svn_error_t * 708251881Speterreadline_file(void *baton, svn_stringbuf_t **line, const char **eol_str, 709251881Speter svn_boolean_t *eof, apr_pool_t *result_pool, 710251881Speter apr_pool_t *scratch_pool) 711251881Speter{ 712251881Speter apr_file_t *file = (apr_file_t *)baton; 713251881Speter svn_stringbuf_t *str = NULL; 714251881Speter apr_size_t numbytes; 715251881Speter char c; 716251881Speter svn_boolean_t found_eof; 717251881Speter 718251881Speter /* Read bytes into STR up to and including, but not storing, 719251881Speter * the next EOL sequence. */ 720251881Speter *eol_str = NULL; 721251881Speter numbytes = 1; 722251881Speter found_eof = FALSE; 723251881Speter while (!found_eof) 724251881Speter { 725251881Speter SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes, 726251881Speter &found_eof, scratch_pool)); 727251881Speter if (numbytes != 1) 728251881Speter { 729251881Speter found_eof = TRUE; 730251881Speter break; 731251881Speter } 732251881Speter 733251881Speter if (c == '\n') 734251881Speter { 735251881Speter *eol_str = "\n"; 736251881Speter } 737251881Speter else if (c == '\r') 738251881Speter { 739251881Speter *eol_str = "\r"; 740251881Speter 741251881Speter if (!found_eof) 742251881Speter { 743251881Speter apr_off_t pos; 744251881Speter 745251881Speter /* Check for "\r\n" by peeking at the next byte. */ 746251881Speter pos = 0; 747251881Speter SVN_ERR(svn_io_file_seek(file, APR_CUR, &pos, scratch_pool)); 748251881Speter SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes, 749251881Speter &found_eof, scratch_pool)); 750251881Speter if (numbytes == 1 && c == '\n') 751251881Speter { 752251881Speter *eol_str = "\r\n"; 753251881Speter } 754251881Speter else 755251881Speter { 756251881Speter /* Pretend we never peeked. */ 757251881Speter SVN_ERR(svn_io_file_seek(file, APR_SET, &pos, scratch_pool)); 758251881Speter found_eof = FALSE; 759251881Speter numbytes = 1; 760251881Speter } 761251881Speter } 762251881Speter } 763251881Speter else 764251881Speter { 765251881Speter if (str == NULL) 766251881Speter str = svn_stringbuf_create_ensure(80, result_pool); 767251881Speter svn_stringbuf_appendbyte(str, c); 768251881Speter } 769251881Speter 770251881Speter if (*eol_str) 771251881Speter break; 772251881Speter } 773251881Speter 774251881Speter if (eof) 775251881Speter *eof = found_eof; 776251881Speter *line = str; 777251881Speter 778251881Speter return SVN_NO_ERROR; 779251881Speter} 780251881Speter 781251881Speter/* Return in *OFFSET the current byte offset for reading from the 782251881Speter * unpatched file content accessed via BATON. 783251881Speter * Use SCRATCH_POOL for temporary allocations. */ 784251881Speterstatic svn_error_t * 785251881Spetertell_file(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool) 786251881Speter{ 787251881Speter apr_file_t *file = (apr_file_t *)baton; 788251881Speter *offset = 0; 789251881Speter SVN_ERR(svn_io_file_seek(file, APR_CUR, offset, scratch_pool)); 790251881Speter return SVN_NO_ERROR; 791251881Speter} 792251881Speter 793251881Speter/* Seek to the specified by OFFSET in the unpatched file content accessed 794251881Speter * via BATON. Use SCRATCH_POOL for temporary allocations. */ 795251881Speterstatic svn_error_t * 796251881Speterseek_file(void *baton, apr_off_t offset, apr_pool_t *scratch_pool) 797251881Speter{ 798251881Speter apr_file_t *file = (apr_file_t *)baton; 799251881Speter SVN_ERR(svn_io_file_seek(file, APR_SET, &offset, scratch_pool)); 800251881Speter return SVN_NO_ERROR; 801251881Speter} 802251881Speter 803251881Speter/* Write LEN bytes from BUF into the patched file content accessed 804251881Speter * via BATON. Use SCRATCH_POOL for temporary allocations. */ 805251881Speterstatic svn_error_t * 806251881Speterwrite_file(void *baton, const char *buf, apr_size_t len, 807251881Speter apr_pool_t *scratch_pool) 808251881Speter{ 809251881Speter apr_file_t *file = (apr_file_t *)baton; 810251881Speter SVN_ERR(svn_io_file_write_full(file, buf, len, &len, scratch_pool)); 811251881Speter return SVN_NO_ERROR; 812251881Speter} 813251881Speter 814251881Speter/* Handling symbolic links: 815251881Speter * 816251881Speter * In Subversion, symlinks can be represented on disk in two distinct ways. 817251881Speter * On systems which support symlinks, a symlink is created on disk. 818251881Speter * On systems which do not support symlink, a file is created on disk 819251881Speter * which contains the "normal form" of the symlink, which looks like: 820251881Speter * link TARGET 821251881Speter * where TARGET is the file the symlink points to. 822251881Speter * 823251881Speter * When reading symlinks (i.e. the link itself, not the file the symlink 824251881Speter * is pointing to) through the svn_subst_create_specialfile() function 825251881Speter * into a buffer, the buffer always contains the "normal form" of the symlink. 826251881Speter * Due to this representation symlinks always contain a single line of text. 827251881Speter * 828251881Speter * The functions below are needed to deal with the case where a patch 829251881Speter * wants to change the TARGET that a symlink points to. 830251881Speter */ 831251881Speter 832251881Speter/* Baton for the (readline|tell|seek|write)_symlink functions. */ 833251881Speterstruct symlink_baton_t 834251881Speter{ 835251881Speter /* The path to the symlink on disk (not the path to the target of the link) */ 836251881Speter const char *local_abspath; 837251881Speter 838251881Speter /* Indicates whether the "normal form" of the symlink has been read. */ 839251881Speter svn_boolean_t at_eof; 840251881Speter}; 841251881Speter 842251881Speter/* Allocate *STRINGBUF in RESULT_POOL, and store into it the "normal form" 843251881Speter * of the symlink accessed via BATON. 844251881Speter * 845251881Speter * Otherwise behaves like readline_file(), which see. 846251881Speter */ 847251881Speterstatic svn_error_t * 848251881Speterreadline_symlink(void *baton, svn_stringbuf_t **line, const char **eol_str, 849251881Speter svn_boolean_t *eof, apr_pool_t *result_pool, 850251881Speter apr_pool_t *scratch_pool) 851251881Speter{ 852251881Speter struct symlink_baton_t *sb = baton; 853251881Speter 854251881Speter if (eof) 855251881Speter *eof = TRUE; 856251881Speter if (eol_str) 857251881Speter *eol_str = NULL; 858251881Speter 859251881Speter if (sb->at_eof) 860251881Speter { 861251881Speter *line = NULL; 862251881Speter } 863251881Speter else 864251881Speter { 865251881Speter svn_string_t *dest; 866251881Speter 867251881Speter SVN_ERR(svn_io_read_link(&dest, sb->local_abspath, scratch_pool)); 868251881Speter *line = svn_stringbuf_createf(result_pool, "link %s", dest->data); 869251881Speter sb->at_eof = TRUE; 870251881Speter } 871251881Speter 872251881Speter return SVN_NO_ERROR; 873251881Speter} 874251881Speter 875251881Speter/* Set *OFFSET to 1 or 0 depending on whether the "normal form" of 876251881Speter * the symlink has already been read. */ 877251881Speterstatic svn_error_t * 878251881Spetertell_symlink(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool) 879251881Speter{ 880251881Speter struct symlink_baton_t *sb = baton; 881251881Speter 882251881Speter *offset = sb->at_eof ? 1 : 0; 883251881Speter return SVN_NO_ERROR; 884251881Speter} 885251881Speter 886251881Speter/* If offset is non-zero, mark the symlink as having been read in its 887251881Speter * "normal form". Else, mark the symlink as not having been read yet. */ 888251881Speterstatic svn_error_t * 889251881Speterseek_symlink(void *baton, apr_off_t offset, apr_pool_t *scratch_pool) 890251881Speter{ 891251881Speter struct symlink_baton_t *sb = baton; 892251881Speter 893251881Speter sb->at_eof = (offset != 0); 894251881Speter return SVN_NO_ERROR; 895251881Speter} 896251881Speter 897251881Speter 898251881Speter/* Set the target of the symlink accessed via BATON. 899251881Speter * The contents of BUF must be a valid "normal form" of a symlink. */ 900251881Speterstatic svn_error_t * 901251881Speterwrite_symlink(void *baton, const char *buf, apr_size_t len, 902251881Speter apr_pool_t *scratch_pool) 903251881Speter{ 904251881Speter const char *target_abspath = baton; 905251881Speter const char *new_name; 906251881Speter const char *link = apr_pstrndup(scratch_pool, buf, len); 907251881Speter 908251881Speter if (strncmp(link, "link ", 5) != 0) 909251881Speter return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, 910251881Speter _("Invalid link representation")); 911251881Speter 912251881Speter link += 5; /* Skip "link " */ 913251881Speter 914251881Speter /* We assume the entire symlink is written at once, as the patch 915251881Speter format is line based */ 916251881Speter 917251881Speter SVN_ERR(svn_io_create_unique_link(&new_name, target_abspath, link, 918251881Speter ".tmp", scratch_pool)); 919251881Speter 920251881Speter SVN_ERR(svn_io_file_rename(new_name, target_abspath, scratch_pool)); 921251881Speter 922251881Speter return SVN_NO_ERROR; 923251881Speter} 924251881Speter 925251881Speter 926251881Speter/* Return a suitable filename for the target of PATCH. 927251881Speter * Examine the ``old'' and ``new'' file names, and choose the file name 928251881Speter * with the fewest path components, the shortest basename, and the shortest 929251881Speter * total file name length (in that order). In case of a tie, return the new 930251881Speter * filename. This heuristic is also used by Larry Wall's UNIX patch (except 931251881Speter * that it prompts for a filename in case of a tie). 932251881Speter * Additionally, for compatibility with git, if one of the filenames 933251881Speter * is "/dev/null", use the other filename. */ 934251881Speterstatic const char * 935251881Speterchoose_target_filename(const svn_patch_t *patch) 936251881Speter{ 937251881Speter apr_size_t old; 938251881Speter apr_size_t new; 939251881Speter 940251881Speter if (strcmp(patch->old_filename, "/dev/null") == 0) 941251881Speter return patch->new_filename; 942251881Speter if (strcmp(patch->new_filename, "/dev/null") == 0) 943251881Speter return patch->old_filename; 944251881Speter 945251881Speter old = svn_path_component_count(patch->old_filename); 946251881Speter new = svn_path_component_count(patch->new_filename); 947251881Speter 948251881Speter if (old == new) 949251881Speter { 950251881Speter old = strlen(svn_dirent_basename(patch->old_filename, NULL)); 951251881Speter new = strlen(svn_dirent_basename(patch->new_filename, NULL)); 952251881Speter 953251881Speter if (old == new) 954251881Speter { 955251881Speter old = strlen(patch->old_filename); 956251881Speter new = strlen(patch->new_filename); 957251881Speter } 958251881Speter } 959251881Speter 960251881Speter return (old < new) ? patch->old_filename : patch->new_filename; 961251881Speter} 962251881Speter 963251881Speter/* Attempt to initialize a *PATCH_TARGET structure for a target file 964251881Speter * described by PATCH. Use working copy context WC_CTX. 965251881Speter * STRIP_COUNT specifies the number of leading path components 966251881Speter * which should be stripped from target paths in the patch. 967251881Speter * The patch target structure is allocated in RESULT_POOL, but if the target 968251881Speter * should be skipped, PATCH_TARGET->SKIPPED is set and the target should be 969251881Speter * treated as not fully initialized, e.g. the caller should not not do any 970251881Speter * further operations on the target if it is marked to be skipped. 971251881Speter * If REMOVE_TEMPFILES is TRUE, set up temporary files to be removed as 972251881Speter * soon as they are no longer needed. 973251881Speter * Use SCRATCH_POOL for all other allocations. */ 974251881Speterstatic svn_error_t * 975251881Speterinit_patch_target(patch_target_t **patch_target, 976251881Speter const svn_patch_t *patch, 977251881Speter const char *wcroot_abspath, 978251881Speter svn_wc_context_t *wc_ctx, int strip_count, 979251881Speter svn_boolean_t remove_tempfiles, 980251881Speter apr_pool_t *result_pool, apr_pool_t *scratch_pool) 981251881Speter{ 982251881Speter patch_target_t *target; 983251881Speter target_content_t *content; 984251881Speter svn_boolean_t has_prop_changes = FALSE; 985251881Speter svn_boolean_t prop_changes_only = FALSE; 986251881Speter 987251881Speter { 988251881Speter apr_hash_index_t *hi; 989251881Speter 990251881Speter for (hi = apr_hash_first(scratch_pool, patch->prop_patches); 991251881Speter hi; 992251881Speter hi = apr_hash_next(hi)) 993251881Speter { 994251881Speter svn_prop_patch_t *prop_patch = svn__apr_hash_index_val(hi); 995251881Speter if (! has_prop_changes) 996251881Speter has_prop_changes = prop_patch->hunks->nelts > 0; 997251881Speter else 998251881Speter break; 999251881Speter } 1000251881Speter } 1001251881Speter 1002251881Speter prop_changes_only = has_prop_changes && patch->hunks->nelts == 0; 1003251881Speter 1004251881Speter content = apr_pcalloc(result_pool, sizeof(*content)); 1005251881Speter 1006251881Speter /* All other fields in content are FALSE or NULL due to apr_pcalloc().*/ 1007251881Speter content->current_line = 1; 1008251881Speter content->eol_style = svn_subst_eol_style_none; 1009251881Speter content->lines = apr_array_make(result_pool, 0, sizeof(apr_off_t)); 1010251881Speter content->hunks = apr_array_make(result_pool, 0, sizeof(hunk_info_t *)); 1011251881Speter content->keywords = apr_hash_make(result_pool); 1012251881Speter 1013251881Speter target = apr_pcalloc(result_pool, sizeof(*target)); 1014251881Speter 1015251881Speter /* All other fields in target are FALSE or NULL due to apr_pcalloc(). */ 1016251881Speter target->db_kind = svn_node_none; 1017251881Speter target->kind_on_disk = svn_node_none; 1018251881Speter target->content = content; 1019251881Speter target->prop_targets = apr_hash_make(result_pool); 1020251881Speter 1021251881Speter SVN_ERR(resolve_target_path(target, choose_target_filename(patch), 1022251881Speter wcroot_abspath, strip_count, prop_changes_only, 1023251881Speter wc_ctx, result_pool, scratch_pool)); 1024251881Speter if (! target->skipped) 1025251881Speter { 1026251881Speter const char *diff_header; 1027251881Speter apr_size_t len; 1028251881Speter 1029251881Speter /* Create a temporary file to write the patched result to. 1030251881Speter * Also grab various bits of information about the file. */ 1031251881Speter if (target->is_symlink) 1032251881Speter { 1033251881Speter struct symlink_baton_t *sb = apr_pcalloc(result_pool, sizeof(*sb)); 1034251881Speter content->existed = TRUE; 1035251881Speter 1036251881Speter sb->local_abspath = target->local_abspath; 1037251881Speter 1038251881Speter /* Wire up the read callbacks. */ 1039251881Speter content->read_baton = sb; 1040251881Speter 1041251881Speter content->readline = readline_symlink; 1042251881Speter content->seek = seek_symlink; 1043251881Speter content->tell = tell_symlink; 1044251881Speter } 1045251881Speter else if (target->kind_on_disk == svn_node_file) 1046251881Speter { 1047251881Speter SVN_ERR(svn_io_file_open(&target->file, target->local_abspath, 1048251881Speter APR_READ | APR_BUFFERED, 1049251881Speter APR_OS_DEFAULT, result_pool)); 1050251881Speter SVN_ERR(svn_wc_text_modified_p2(&target->local_mods, wc_ctx, 1051251881Speter target->local_abspath, FALSE, 1052251881Speter scratch_pool)); 1053251881Speter SVN_ERR(svn_io_is_file_executable(&target->executable, 1054251881Speter target->local_abspath, 1055251881Speter scratch_pool)); 1056251881Speter SVN_ERR(obtain_eol_and_keywords_for_file(&content->keywords, 1057251881Speter &content->eol_style, 1058251881Speter &content->eol_str, 1059251881Speter wc_ctx, 1060251881Speter target->local_abspath, 1061251881Speter result_pool, 1062251881Speter scratch_pool)); 1063251881Speter content->existed = TRUE; 1064251881Speter 1065251881Speter /* Wire up the read callbacks. */ 1066251881Speter content->readline = readline_file; 1067251881Speter content->seek = seek_file; 1068251881Speter content->tell = tell_file; 1069251881Speter content->read_baton = target->file; 1070251881Speter } 1071251881Speter 1072251881Speter /* ### Is it ok to set the operation of the target already here? Isn't 1073251881Speter * ### the target supposed to be marked with an operation after we have 1074251881Speter * ### determined that the changes will apply cleanly to the WC? Maybe 1075251881Speter * ### we should have kept the patch field in patch_target_t to be 1076251881Speter * ### able to distinguish between 'what the patch says we should do' 1077251881Speter * ### and 'what we can do with the given state of our WC'. */ 1078251881Speter if (patch->operation == svn_diff_op_added) 1079251881Speter target->added = TRUE; 1080251881Speter else if (patch->operation == svn_diff_op_deleted) 1081251881Speter target->deleted = TRUE; 1082251881Speter 1083251881Speter if (! target->is_symlink) 1084251881Speter { 1085251881Speter /* Open a temporary file to write the patched result to. */ 1086251881Speter SVN_ERR(svn_io_open_unique_file3(&target->patched_file, 1087251881Speter &target->patched_path, NULL, 1088251881Speter remove_tempfiles ? 1089251881Speter svn_io_file_del_on_pool_cleanup : 1090251881Speter svn_io_file_del_none, 1091251881Speter result_pool, scratch_pool)); 1092251881Speter 1093251881Speter /* Put the write callback in place. */ 1094251881Speter content->write = write_file; 1095251881Speter content->write_baton = target->patched_file; 1096251881Speter } 1097251881Speter else 1098251881Speter { 1099251881Speter /* Put the write callback in place. */ 1100251881Speter SVN_ERR(svn_io_open_unique_file3(NULL, 1101251881Speter &target->patched_path, NULL, 1102251881Speter remove_tempfiles ? 1103251881Speter svn_io_file_del_on_pool_cleanup : 1104251881Speter svn_io_file_del_none, 1105251881Speter result_pool, scratch_pool)); 1106251881Speter 1107251881Speter content->write_baton = (void*)target->patched_path; 1108251881Speter 1109251881Speter content->write = write_symlink; 1110251881Speter } 1111251881Speter 1112251881Speter /* Open a temporary file to write rejected hunks to. */ 1113251881Speter SVN_ERR(svn_io_open_unique_file3(&target->reject_file, 1114251881Speter &target->reject_path, NULL, 1115251881Speter remove_tempfiles ? 1116251881Speter svn_io_file_del_on_pool_cleanup : 1117251881Speter svn_io_file_del_none, 1118251881Speter result_pool, scratch_pool)); 1119251881Speter 1120251881Speter /* The reject file needs a diff header. */ 1121251881Speter diff_header = apr_psprintf(scratch_pool, "--- %s%s+++ %s%s", 1122251881Speter target->canon_path_from_patchfile, 1123251881Speter APR_EOL_STR, 1124251881Speter target->canon_path_from_patchfile, 1125251881Speter APR_EOL_STR); 1126251881Speter len = strlen(diff_header); 1127251881Speter SVN_ERR(svn_io_file_write_full(target->reject_file, diff_header, len, 1128251881Speter &len, scratch_pool)); 1129251881Speter 1130251881Speter /* Handle properties. */ 1131251881Speter if (! target->skipped) 1132251881Speter { 1133251881Speter apr_hash_index_t *hi; 1134251881Speter 1135251881Speter for (hi = apr_hash_first(result_pool, patch->prop_patches); 1136251881Speter hi; 1137251881Speter hi = apr_hash_next(hi)) 1138251881Speter { 1139251881Speter const char *prop_name = svn__apr_hash_index_key(hi); 1140251881Speter svn_prop_patch_t *prop_patch = svn__apr_hash_index_val(hi); 1141251881Speter prop_patch_target_t *prop_target; 1142251881Speter 1143251881Speter SVN_ERR(init_prop_target(&prop_target, 1144251881Speter prop_name, 1145251881Speter prop_patch->operation, 1146251881Speter wc_ctx, target->local_abspath, 1147251881Speter result_pool, scratch_pool)); 1148251881Speter svn_hash_sets(target->prop_targets, prop_name, prop_target); 1149251881Speter } 1150251881Speter } 1151251881Speter } 1152251881Speter 1153251881Speter *patch_target = target; 1154251881Speter return SVN_NO_ERROR; 1155251881Speter} 1156251881Speter 1157251881Speter/* Read a *LINE from CONTENT. If the line has not been read before 1158251881Speter * mark the line in CONTENT->LINES. 1159251881Speter * If a line could be read successfully, increase CONTENT->CURRENT_LINE, 1160251881Speter * and allocate *LINE in RESULT_POOL. 1161251881Speter * Do temporary allocations in SCRATCH_POOL. 1162251881Speter */ 1163251881Speterstatic svn_error_t * 1164251881Speterreadline(target_content_t *content, 1165251881Speter const char **line, 1166251881Speter apr_pool_t *result_pool, 1167251881Speter apr_pool_t *scratch_pool) 1168251881Speter{ 1169251881Speter svn_stringbuf_t *line_raw; 1170251881Speter const char *eol_str; 1171251881Speter svn_linenum_t max_line = (svn_linenum_t)content->lines->nelts + 1; 1172251881Speter 1173251881Speter if (content->eof || content->readline == NULL) 1174251881Speter { 1175251881Speter *line = ""; 1176251881Speter return SVN_NO_ERROR; 1177251881Speter } 1178251881Speter 1179251881Speter SVN_ERR_ASSERT(content->current_line <= max_line); 1180251881Speter if (content->current_line == max_line) 1181251881Speter { 1182251881Speter apr_off_t offset; 1183251881Speter 1184251881Speter SVN_ERR(content->tell(content->read_baton, &offset, 1185251881Speter scratch_pool)); 1186251881Speter APR_ARRAY_PUSH(content->lines, apr_off_t) = offset; 1187251881Speter } 1188251881Speter 1189251881Speter SVN_ERR(content->readline(content->read_baton, &line_raw, 1190251881Speter &eol_str, &content->eof, 1191251881Speter result_pool, scratch_pool)); 1192251881Speter if (content->eol_style == svn_subst_eol_style_none) 1193251881Speter content->eol_str = eol_str; 1194251881Speter 1195251881Speter if (line_raw) 1196251881Speter { 1197251881Speter /* Contract keywords. */ 1198251881Speter SVN_ERR(svn_subst_translate_cstring2(line_raw->data, line, 1199251881Speter NULL, FALSE, 1200251881Speter content->keywords, FALSE, 1201251881Speter result_pool)); 1202251881Speter } 1203251881Speter else 1204251881Speter *line = ""; 1205251881Speter 1206251881Speter if ((line_raw && line_raw->len > 0) || eol_str) 1207251881Speter content->current_line++; 1208251881Speter 1209251881Speter SVN_ERR_ASSERT(content->current_line > 0); 1210251881Speter 1211251881Speter return SVN_NO_ERROR; 1212251881Speter} 1213251881Speter 1214251881Speter/* Seek to the specified LINE in CONTENT. 1215251881Speter * Mark any lines not read before in CONTENT->LINES. 1216251881Speter * Do temporary allocations in SCRATCH_POOL. 1217251881Speter */ 1218251881Speterstatic svn_error_t * 1219251881Speterseek_to_line(target_content_t *content, svn_linenum_t line, 1220251881Speter apr_pool_t *scratch_pool) 1221251881Speter{ 1222251881Speter svn_linenum_t saved_line; 1223251881Speter svn_boolean_t saved_eof; 1224251881Speter 1225251881Speter SVN_ERR_ASSERT(line > 0); 1226251881Speter 1227251881Speter if (line == content->current_line) 1228251881Speter return SVN_NO_ERROR; 1229251881Speter 1230251881Speter saved_line = content->current_line; 1231251881Speter saved_eof = content->eof; 1232251881Speter 1233251881Speter if (line <= (svn_linenum_t)content->lines->nelts) 1234251881Speter { 1235251881Speter apr_off_t offset; 1236251881Speter 1237251881Speter offset = APR_ARRAY_IDX(content->lines, line - 1, apr_off_t); 1238251881Speter SVN_ERR(content->seek(content->read_baton, offset, 1239251881Speter scratch_pool)); 1240251881Speter content->current_line = line; 1241251881Speter } 1242251881Speter else 1243251881Speter { 1244251881Speter const char *dummy; 1245251881Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1246251881Speter 1247251881Speter while (! content->eof && content->current_line < line) 1248251881Speter { 1249251881Speter svn_pool_clear(iterpool); 1250251881Speter SVN_ERR(readline(content, &dummy, iterpool, iterpool)); 1251251881Speter } 1252251881Speter svn_pool_destroy(iterpool); 1253251881Speter } 1254251881Speter 1255251881Speter /* After seeking backwards from EOF position clear EOF indicator. */ 1256251881Speter if (saved_eof && saved_line > content->current_line) 1257251881Speter content->eof = FALSE; 1258251881Speter 1259251881Speter return SVN_NO_ERROR; 1260251881Speter} 1261251881Speter 1262251881Speter/* Indicate in *MATCHED whether the original text of HUNK matches the patch 1263251881Speter * CONTENT at its current line. Lines within FUZZ lines of the start or 1264251881Speter * end of HUNK will always match. If IGNORE_WHITESPACE is set, we ignore 1265251881Speter * whitespace when doing the matching. When this function returns, neither 1266251881Speter * CONTENT->CURRENT_LINE nor the file offset in the target file will 1267251881Speter * have changed. If MATCH_MODIFIED is TRUE, match the modified hunk text, 1268251881Speter * rather than the original hunk text. 1269251881Speter * Do temporary allocations in POOL. */ 1270251881Speterstatic svn_error_t * 1271251881Spetermatch_hunk(svn_boolean_t *matched, target_content_t *content, 1272251881Speter svn_diff_hunk_t *hunk, svn_linenum_t fuzz, 1273251881Speter svn_boolean_t ignore_whitespace, 1274251881Speter svn_boolean_t match_modified, apr_pool_t *pool) 1275251881Speter{ 1276251881Speter svn_stringbuf_t *hunk_line; 1277251881Speter const char *target_line; 1278251881Speter svn_linenum_t lines_read; 1279251881Speter svn_linenum_t saved_line; 1280251881Speter svn_boolean_t hunk_eof; 1281251881Speter svn_boolean_t lines_matched; 1282251881Speter apr_pool_t *iterpool; 1283251881Speter svn_linenum_t hunk_length; 1284251881Speter svn_linenum_t leading_context; 1285251881Speter svn_linenum_t trailing_context; 1286251881Speter 1287251881Speter *matched = FALSE; 1288251881Speter 1289251881Speter if (content->eof) 1290251881Speter return SVN_NO_ERROR; 1291251881Speter 1292251881Speter saved_line = content->current_line; 1293251881Speter lines_read = 0; 1294251881Speter lines_matched = FALSE; 1295251881Speter leading_context = svn_diff_hunk_get_leading_context(hunk); 1296251881Speter trailing_context = svn_diff_hunk_get_trailing_context(hunk); 1297251881Speter if (match_modified) 1298251881Speter { 1299251881Speter svn_diff_hunk_reset_modified_text(hunk); 1300251881Speter hunk_length = svn_diff_hunk_get_modified_length(hunk); 1301251881Speter } 1302251881Speter else 1303251881Speter { 1304251881Speter svn_diff_hunk_reset_original_text(hunk); 1305251881Speter hunk_length = svn_diff_hunk_get_original_length(hunk); 1306251881Speter } 1307251881Speter iterpool = svn_pool_create(pool); 1308251881Speter do 1309251881Speter { 1310251881Speter const char *hunk_line_translated; 1311251881Speter 1312251881Speter svn_pool_clear(iterpool); 1313251881Speter 1314251881Speter if (match_modified) 1315251881Speter SVN_ERR(svn_diff_hunk_readline_modified_text(hunk, &hunk_line, 1316251881Speter NULL, &hunk_eof, 1317251881Speter iterpool, iterpool)); 1318251881Speter else 1319251881Speter SVN_ERR(svn_diff_hunk_readline_original_text(hunk, &hunk_line, 1320251881Speter NULL, &hunk_eof, 1321251881Speter iterpool, iterpool)); 1322251881Speter 1323251881Speter /* Contract keywords, if any, before matching. */ 1324251881Speter SVN_ERR(svn_subst_translate_cstring2(hunk_line->data, 1325251881Speter &hunk_line_translated, 1326251881Speter NULL, FALSE, 1327251881Speter content->keywords, FALSE, 1328251881Speter iterpool)); 1329251881Speter SVN_ERR(readline(content, &target_line, iterpool, iterpool)); 1330251881Speter 1331251881Speter lines_read++; 1332251881Speter 1333251881Speter /* If the last line doesn't have a newline, we get EOF but still 1334251881Speter * have a non-empty line to compare. */ 1335251881Speter if ((hunk_eof && hunk_line->len == 0) || 1336251881Speter (content->eof && *target_line == 0)) 1337251881Speter break; 1338251881Speter 1339251881Speter /* Leading/trailing fuzzy lines always match. */ 1340251881Speter if ((lines_read <= fuzz && leading_context > fuzz) || 1341251881Speter (lines_read > hunk_length - fuzz && trailing_context > fuzz)) 1342251881Speter lines_matched = TRUE; 1343251881Speter else 1344251881Speter { 1345251881Speter if (ignore_whitespace) 1346251881Speter { 1347251881Speter char *hunk_line_trimmed; 1348251881Speter char *target_line_trimmed; 1349251881Speter 1350251881Speter hunk_line_trimmed = apr_pstrdup(iterpool, hunk_line_translated); 1351251881Speter target_line_trimmed = apr_pstrdup(iterpool, target_line); 1352251881Speter apr_collapse_spaces(hunk_line_trimmed, hunk_line_trimmed); 1353251881Speter apr_collapse_spaces(target_line_trimmed, target_line_trimmed); 1354251881Speter lines_matched = ! strcmp(hunk_line_trimmed, target_line_trimmed); 1355251881Speter } 1356251881Speter else 1357251881Speter lines_matched = ! strcmp(hunk_line_translated, target_line); 1358251881Speter } 1359251881Speter } 1360251881Speter while (lines_matched); 1361251881Speter 1362251881Speter *matched = lines_matched && hunk_eof && hunk_line->len == 0; 1363251881Speter SVN_ERR(seek_to_line(content, saved_line, iterpool)); 1364251881Speter svn_pool_destroy(iterpool); 1365251881Speter 1366251881Speter return SVN_NO_ERROR; 1367251881Speter} 1368251881Speter 1369251881Speter/* Scan lines of CONTENT for a match of the original text of HUNK, 1370251881Speter * up to but not including the specified UPPER_LINE. Use fuzz factor FUZZ. 1371251881Speter * If UPPER_LINE is zero scan until EOF occurs when reading from TARGET. 1372251881Speter * Return the line at which HUNK was matched in *MATCHED_LINE. 1373251881Speter * If the hunk did not match at all, set *MATCHED_LINE to zero. 1374251881Speter * If the hunk matched multiple times, and MATCH_FIRST is TRUE, 1375251881Speter * return the line number at which the first match occurred in *MATCHED_LINE. 1376251881Speter * If the hunk matched multiple times, and MATCH_FIRST is FALSE, 1377251881Speter * return the line number at which the last match occurred in *MATCHED_LINE. 1378251881Speter * If IGNORE_WHITESPACE is set, ignore whitespace during the matching. 1379251881Speter * If MATCH_MODIFIED is TRUE, match the modified hunk text, 1380251881Speter * rather than the original hunk text. 1381251881Speter * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation. 1382251881Speter * Do all allocations in POOL. */ 1383251881Speterstatic svn_error_t * 1384251881Speterscan_for_match(svn_linenum_t *matched_line, 1385251881Speter target_content_t *content, 1386251881Speter svn_diff_hunk_t *hunk, svn_boolean_t match_first, 1387251881Speter svn_linenum_t upper_line, svn_linenum_t fuzz, 1388251881Speter svn_boolean_t ignore_whitespace, 1389251881Speter svn_boolean_t match_modified, 1390251881Speter svn_cancel_func_t cancel_func, void *cancel_baton, 1391251881Speter apr_pool_t *pool) 1392251881Speter{ 1393251881Speter apr_pool_t *iterpool; 1394251881Speter 1395251881Speter *matched_line = 0; 1396251881Speter iterpool = svn_pool_create(pool); 1397251881Speter while ((content->current_line < upper_line || upper_line == 0) && 1398251881Speter ! content->eof) 1399251881Speter { 1400251881Speter svn_boolean_t matched; 1401251881Speter 1402251881Speter svn_pool_clear(iterpool); 1403251881Speter 1404251881Speter if (cancel_func) 1405251881Speter SVN_ERR(cancel_func(cancel_baton)); 1406251881Speter 1407251881Speter SVN_ERR(match_hunk(&matched, content, hunk, fuzz, ignore_whitespace, 1408251881Speter match_modified, iterpool)); 1409251881Speter if (matched) 1410251881Speter { 1411251881Speter svn_boolean_t taken = FALSE; 1412251881Speter int i; 1413251881Speter 1414251881Speter /* Don't allow hunks to match at overlapping locations. */ 1415251881Speter for (i = 0; i < content->hunks->nelts; i++) 1416251881Speter { 1417251881Speter const hunk_info_t *hi; 1418251881Speter svn_linenum_t length; 1419251881Speter 1420251881Speter hi = APR_ARRAY_IDX(content->hunks, i, const hunk_info_t *); 1421251881Speter 1422251881Speter if (match_modified) 1423251881Speter length = svn_diff_hunk_get_modified_length(hi->hunk); 1424251881Speter else 1425251881Speter length = svn_diff_hunk_get_original_length(hi->hunk); 1426251881Speter 1427251881Speter taken = (! hi->rejected && 1428251881Speter content->current_line >= hi->matched_line && 1429251881Speter content->current_line < (hi->matched_line + length)); 1430251881Speter if (taken) 1431251881Speter break; 1432251881Speter } 1433251881Speter 1434251881Speter if (! taken) 1435251881Speter { 1436251881Speter *matched_line = content->current_line; 1437251881Speter if (match_first) 1438251881Speter break; 1439251881Speter } 1440251881Speter } 1441251881Speter 1442251881Speter if (! content->eof) 1443251881Speter SVN_ERR(seek_to_line(content, content->current_line + 1, 1444251881Speter iterpool)); 1445251881Speter } 1446251881Speter svn_pool_destroy(iterpool); 1447251881Speter 1448251881Speter return SVN_NO_ERROR; 1449251881Speter} 1450251881Speter 1451251881Speter/* Indicate in *MATCH whether the content described by CONTENT 1452251881Speter * matches the modified text of HUNK. 1453251881Speter * Use SCRATCH_POOL for temporary allocations. */ 1454251881Speterstatic svn_error_t * 1455251881Spetermatch_existing_target(svn_boolean_t *match, 1456251881Speter target_content_t *content, 1457251881Speter svn_diff_hunk_t *hunk, 1458251881Speter apr_pool_t *scratch_pool) 1459251881Speter{ 1460251881Speter svn_boolean_t lines_matched; 1461251881Speter apr_pool_t *iterpool; 1462251881Speter svn_boolean_t hunk_eof; 1463251881Speter svn_linenum_t saved_line; 1464251881Speter 1465251881Speter svn_diff_hunk_reset_modified_text(hunk); 1466251881Speter 1467251881Speter saved_line = content->current_line; 1468251881Speter 1469251881Speter iterpool = svn_pool_create(scratch_pool); 1470251881Speter do 1471251881Speter { 1472251881Speter const char *line; 1473251881Speter svn_stringbuf_t *hunk_line; 1474251881Speter const char *line_translated; 1475251881Speter const char *hunk_line_translated; 1476251881Speter 1477251881Speter svn_pool_clear(iterpool); 1478251881Speter 1479251881Speter SVN_ERR(readline(content, &line, iterpool, iterpool)); 1480251881Speter SVN_ERR(svn_diff_hunk_readline_modified_text(hunk, &hunk_line, 1481251881Speter NULL, &hunk_eof, 1482251881Speter iterpool, iterpool)); 1483251881Speter /* Contract keywords. */ 1484251881Speter SVN_ERR(svn_subst_translate_cstring2(line, &line_translated, 1485251881Speter NULL, FALSE, 1486251881Speter content->keywords, 1487251881Speter FALSE, iterpool)); 1488251881Speter SVN_ERR(svn_subst_translate_cstring2(hunk_line->data, 1489251881Speter &hunk_line_translated, 1490251881Speter NULL, FALSE, 1491251881Speter content->keywords, 1492251881Speter FALSE, iterpool)); 1493251881Speter lines_matched = ! strcmp(line_translated, hunk_line_translated); 1494251881Speter if (content->eof != hunk_eof) 1495251881Speter { 1496251881Speter svn_pool_destroy(iterpool); 1497251881Speter *match = FALSE; 1498251881Speter return SVN_NO_ERROR; 1499251881Speter } 1500251881Speter } 1501251881Speter while (lines_matched && ! content->eof && ! hunk_eof); 1502251881Speter svn_pool_destroy(iterpool); 1503251881Speter 1504251881Speter *match = (lines_matched && content->eof == hunk_eof); 1505251881Speter SVN_ERR(seek_to_line(content, saved_line, scratch_pool)); 1506251881Speter 1507251881Speter return SVN_NO_ERROR; 1508251881Speter} 1509251881Speter 1510251881Speter/* Determine the line at which a HUNK applies to CONTENT of the TARGET 1511251881Speter * file, and return an appropriate hunk_info object in *HI, allocated from 1512251881Speter * RESULT_POOL. Use fuzz factor FUZZ. Set HI->FUZZ to FUZZ. If no correct 1513251881Speter * line can be determined, set HI->REJECTED to TRUE. 1514251881Speter * IGNORE_WHITESPACE tells whether whitespace should be considered when 1515251881Speter * matching. IS_PROP_HUNK indicates whether the hunk patches file content 1516251881Speter * or a property. 1517251881Speter * When this function returns, neither CONTENT->CURRENT_LINE nor 1518251881Speter * the file offset in the target file will have changed. 1519251881Speter * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation. 1520251881Speter * Do temporary allocations in POOL. */ 1521251881Speterstatic svn_error_t * 1522251881Speterget_hunk_info(hunk_info_t **hi, patch_target_t *target, 1523251881Speter target_content_t *content, 1524251881Speter svn_diff_hunk_t *hunk, svn_linenum_t fuzz, 1525251881Speter svn_boolean_t ignore_whitespace, 1526251881Speter svn_boolean_t is_prop_hunk, 1527251881Speter svn_cancel_func_t cancel_func, void *cancel_baton, 1528251881Speter apr_pool_t *result_pool, apr_pool_t *scratch_pool) 1529251881Speter{ 1530251881Speter svn_linenum_t matched_line; 1531251881Speter svn_linenum_t original_start; 1532251881Speter svn_boolean_t already_applied; 1533251881Speter 1534251881Speter original_start = svn_diff_hunk_get_original_start(hunk); 1535251881Speter already_applied = FALSE; 1536251881Speter 1537251881Speter /* An original offset of zero means that this hunk wants to create 1538251881Speter * a new file. Don't bother matching hunks in that case, since 1539251881Speter * the hunk applies at line 1. If the file already exists, the hunk 1540251881Speter * is rejected, unless the file is versioned and its content matches 1541251881Speter * the file the patch wants to create. */ 1542251881Speter if (original_start == 0 && fuzz > 0) 1543251881Speter { 1544251881Speter matched_line = 0; /* reject any fuzz for new files */ 1545251881Speter } 1546251881Speter else if (original_start == 0 && ! is_prop_hunk) 1547251881Speter { 1548251881Speter if (target->kind_on_disk == svn_node_file) 1549251881Speter { 1550251881Speter const svn_io_dirent2_t *dirent; 1551251881Speter SVN_ERR(svn_io_stat_dirent2(&dirent, target->local_abspath, FALSE, 1552251881Speter TRUE, scratch_pool, scratch_pool)); 1553251881Speter 1554251881Speter if (dirent->kind == svn_node_file 1555251881Speter && !dirent->special 1556251881Speter && dirent->filesize == 0) 1557251881Speter { 1558251881Speter matched_line = 1; /* Matched an on-disk empty file */ 1559251881Speter } 1560251881Speter else 1561251881Speter { 1562251881Speter if (target->db_kind == svn_node_file) 1563251881Speter { 1564251881Speter svn_boolean_t file_matches; 1565251881Speter 1566251881Speter /* ### I can't reproduce anything but a no-match here. 1567251881Speter The content is already at eof, so any hunk fails */ 1568251881Speter SVN_ERR(match_existing_target(&file_matches, content, hunk, 1569251881Speter scratch_pool)); 1570251881Speter if (file_matches) 1571251881Speter { 1572251881Speter matched_line = 1; 1573251881Speter already_applied = TRUE; 1574251881Speter } 1575251881Speter else 1576251881Speter matched_line = 0; /* reject */ 1577251881Speter } 1578251881Speter else 1579251881Speter matched_line = 0; /* reject */ 1580251881Speter } 1581251881Speter } 1582251881Speter else 1583251881Speter matched_line = 1; 1584251881Speter } 1585251881Speter /* Same conditions apply as for the file case above. 1586251881Speter * 1587251881Speter * ### Since the hunk says the prop should be added we just assume so for 1588251881Speter * ### now and don't bother with storing the previous lines and such. When 1589251881Speter * ### we have the diff operation available we can just check for adds. */ 1590251881Speter else if (original_start == 0 && is_prop_hunk) 1591251881Speter { 1592251881Speter if (content->existed) 1593251881Speter { 1594251881Speter svn_boolean_t prop_matches; 1595251881Speter 1596251881Speter SVN_ERR(match_existing_target(&prop_matches, content, hunk, 1597251881Speter scratch_pool)); 1598251881Speter 1599251881Speter if (prop_matches) 1600251881Speter { 1601251881Speter matched_line = 1; 1602251881Speter already_applied = TRUE; 1603251881Speter } 1604251881Speter else 1605251881Speter matched_line = 0; /* reject */ 1606251881Speter } 1607251881Speter else 1608251881Speter matched_line = 1; 1609251881Speter } 1610251881Speter else if (original_start > 0 && content->existed) 1611251881Speter { 1612251881Speter svn_linenum_t saved_line = content->current_line; 1613251881Speter 1614251881Speter /* Scan for a match at the line where the hunk thinks it 1615251881Speter * should be going. */ 1616251881Speter SVN_ERR(seek_to_line(content, original_start, scratch_pool)); 1617251881Speter if (content->current_line != original_start) 1618251881Speter { 1619251881Speter /* Seek failed. */ 1620251881Speter matched_line = 0; 1621251881Speter } 1622251881Speter else 1623251881Speter SVN_ERR(scan_for_match(&matched_line, content, hunk, TRUE, 1624251881Speter original_start + 1, fuzz, 1625251881Speter ignore_whitespace, FALSE, 1626251881Speter cancel_func, cancel_baton, 1627251881Speter scratch_pool)); 1628251881Speter 1629251881Speter if (matched_line != original_start) 1630251881Speter { 1631251881Speter /* Check if the hunk is already applied. 1632251881Speter * We only check for an exact match here, and don't bother checking 1633251881Speter * for already applied patches with offset/fuzz, because such a 1634251881Speter * check would be ambiguous. */ 1635251881Speter if (fuzz == 0) 1636251881Speter { 1637251881Speter svn_linenum_t modified_start; 1638251881Speter 1639251881Speter modified_start = svn_diff_hunk_get_modified_start(hunk); 1640251881Speter if (modified_start == 0) 1641251881Speter { 1642251881Speter /* Patch wants to delete the file. */ 1643251881Speter already_applied = target->locally_deleted; 1644251881Speter } 1645251881Speter else 1646251881Speter { 1647251881Speter SVN_ERR(seek_to_line(content, modified_start, 1648251881Speter scratch_pool)); 1649251881Speter SVN_ERR(scan_for_match(&matched_line, content, 1650251881Speter hunk, TRUE, 1651251881Speter modified_start + 1, 1652251881Speter fuzz, ignore_whitespace, TRUE, 1653251881Speter cancel_func, cancel_baton, 1654251881Speter scratch_pool)); 1655251881Speter already_applied = (matched_line == modified_start); 1656251881Speter } 1657251881Speter } 1658251881Speter else 1659251881Speter already_applied = FALSE; 1660251881Speter 1661251881Speter if (! already_applied) 1662251881Speter { 1663251881Speter /* Scan the whole file again from the start. */ 1664251881Speter SVN_ERR(seek_to_line(content, 1, scratch_pool)); 1665251881Speter 1666251881Speter /* Scan forward towards the hunk's line and look for a line 1667251881Speter * where the hunk matches. */ 1668251881Speter SVN_ERR(scan_for_match(&matched_line, content, hunk, FALSE, 1669251881Speter original_start, fuzz, 1670251881Speter ignore_whitespace, FALSE, 1671251881Speter cancel_func, cancel_baton, 1672251881Speter scratch_pool)); 1673251881Speter 1674251881Speter /* In tie-break situations, we arbitrarily prefer early matches 1675251881Speter * to save us from scanning the rest of the file. */ 1676251881Speter if (matched_line == 0) 1677251881Speter { 1678251881Speter /* Scan forward towards the end of the file and look 1679251881Speter * for a line where the hunk matches. */ 1680251881Speter SVN_ERR(scan_for_match(&matched_line, content, hunk, 1681251881Speter TRUE, 0, fuzz, ignore_whitespace, 1682251881Speter FALSE, cancel_func, cancel_baton, 1683251881Speter scratch_pool)); 1684251881Speter } 1685251881Speter } 1686251881Speter } 1687251881Speter 1688251881Speter SVN_ERR(seek_to_line(content, saved_line, scratch_pool)); 1689251881Speter } 1690251881Speter else 1691251881Speter { 1692251881Speter /* The hunk wants to modify a file which doesn't exist. */ 1693251881Speter matched_line = 0; 1694251881Speter } 1695251881Speter 1696251881Speter (*hi) = apr_pcalloc(result_pool, sizeof(hunk_info_t)); 1697251881Speter (*hi)->hunk = hunk; 1698251881Speter (*hi)->matched_line = matched_line; 1699251881Speter (*hi)->rejected = (matched_line == 0); 1700251881Speter (*hi)->already_applied = already_applied; 1701251881Speter (*hi)->fuzz = fuzz; 1702251881Speter 1703251881Speter return SVN_NO_ERROR; 1704251881Speter} 1705251881Speter 1706251881Speter/* Copy lines to the patched content until the specified LINE has been 1707251881Speter * reached. Indicate in *EOF whether end-of-file was encountered while 1708251881Speter * reading from the target. 1709251881Speter * If LINE is zero, copy lines until end-of-file has been reached. 1710251881Speter * Do all allocations in POOL. */ 1711251881Speterstatic svn_error_t * 1712251881Spetercopy_lines_to_target(target_content_t *content, svn_linenum_t line, 1713251881Speter apr_pool_t *pool) 1714251881Speter{ 1715251881Speter apr_pool_t *iterpool; 1716251881Speter 1717251881Speter iterpool = svn_pool_create(pool); 1718251881Speter while ((content->current_line < line || line == 0) && ! content->eof) 1719251881Speter { 1720251881Speter const char *target_line; 1721251881Speter apr_size_t len; 1722251881Speter 1723251881Speter svn_pool_clear(iterpool); 1724251881Speter 1725251881Speter SVN_ERR(readline(content, &target_line, iterpool, iterpool)); 1726251881Speter if (! content->eof) 1727251881Speter target_line = apr_pstrcat(iterpool, target_line, content->eol_str, 1728251881Speter (char *)NULL); 1729251881Speter len = strlen(target_line); 1730251881Speter SVN_ERR(content->write(content->write_baton, target_line, 1731251881Speter len, iterpool)); 1732251881Speter } 1733251881Speter svn_pool_destroy(iterpool); 1734251881Speter 1735251881Speter return SVN_NO_ERROR; 1736251881Speter} 1737251881Speter 1738251881Speter/* Write the diff text of HUNK to TARGET's reject file, 1739251881Speter * and mark TARGET as having had rejects. 1740251881Speter * We don't expand keywords, nor normalise line-endings, in reject files. 1741251881Speter * Do temporary allocations in SCRATCH_POOL. */ 1742251881Speterstatic svn_error_t * 1743251881Speterreject_hunk(patch_target_t *target, target_content_t *content, 1744251881Speter svn_diff_hunk_t *hunk, const char *prop_name, 1745251881Speter apr_pool_t *pool) 1746251881Speter{ 1747251881Speter const char *hunk_header; 1748251881Speter apr_size_t len; 1749251881Speter svn_boolean_t eof; 1750251881Speter static const char * const text_atat = "@@"; 1751251881Speter static const char * const prop_atat = "##"; 1752251881Speter const char *atat; 1753251881Speter apr_pool_t *iterpool; 1754251881Speter 1755251881Speter if (prop_name) 1756251881Speter { 1757251881Speter const char *prop_header; 1758251881Speter 1759251881Speter /* ### Print 'Added', 'Deleted' or 'Modified' instead of 'Property'. 1760251881Speter */ 1761251881Speter prop_header = apr_psprintf(pool, "Property: %s\n", prop_name); 1762251881Speter len = strlen(prop_header); 1763251881Speter SVN_ERR(svn_io_file_write_full(target->reject_file, prop_header, 1764251881Speter len, &len, pool)); 1765251881Speter atat = prop_atat; 1766251881Speter } 1767251881Speter else 1768251881Speter { 1769251881Speter atat = text_atat; 1770251881Speter } 1771251881Speter 1772251881Speter hunk_header = apr_psprintf(pool, "%s -%lu,%lu +%lu,%lu %s%s", 1773251881Speter atat, 1774251881Speter svn_diff_hunk_get_original_start(hunk), 1775251881Speter svn_diff_hunk_get_original_length(hunk), 1776251881Speter svn_diff_hunk_get_modified_start(hunk), 1777251881Speter svn_diff_hunk_get_modified_length(hunk), 1778251881Speter atat, 1779251881Speter APR_EOL_STR); 1780251881Speter len = strlen(hunk_header); 1781251881Speter SVN_ERR(svn_io_file_write_full(target->reject_file, hunk_header, len, 1782251881Speter &len, pool)); 1783251881Speter 1784251881Speter iterpool = svn_pool_create(pool); 1785251881Speter do 1786251881Speter { 1787251881Speter svn_stringbuf_t *hunk_line; 1788251881Speter const char *eol_str; 1789251881Speter 1790251881Speter svn_pool_clear(iterpool); 1791251881Speter 1792251881Speter SVN_ERR(svn_diff_hunk_readline_diff_text(hunk, &hunk_line, &eol_str, 1793251881Speter &eof, iterpool, iterpool)); 1794251881Speter if (! eof) 1795251881Speter { 1796251881Speter if (hunk_line->len >= 1) 1797251881Speter { 1798251881Speter len = hunk_line->len; 1799251881Speter SVN_ERR(svn_io_file_write_full(target->reject_file, 1800251881Speter hunk_line->data, len, &len, 1801251881Speter iterpool)); 1802251881Speter } 1803251881Speter 1804251881Speter if (eol_str) 1805251881Speter { 1806251881Speter len = strlen(eol_str); 1807251881Speter SVN_ERR(svn_io_file_write_full(target->reject_file, eol_str, 1808251881Speter len, &len, iterpool)); 1809251881Speter } 1810251881Speter } 1811251881Speter } 1812251881Speter while (! eof); 1813251881Speter svn_pool_destroy(iterpool); 1814251881Speter 1815251881Speter if (prop_name) 1816251881Speter target->had_prop_rejects = TRUE; 1817251881Speter else 1818251881Speter target->had_rejects = TRUE; 1819251881Speter 1820251881Speter return SVN_NO_ERROR; 1821251881Speter} 1822251881Speter 1823251881Speter/* Write the modified text of the hunk described by HI to the patched 1824251881Speter * CONTENT. TARGET is the patch target. 1825251881Speter * If PROP_NAME is not NULL, the hunk is assumed to be targeted for 1826251881Speter * a property with the given name. 1827251881Speter * Do temporary allocations in POOL. */ 1828251881Speterstatic svn_error_t * 1829251881Speterapply_hunk(patch_target_t *target, target_content_t *content, 1830251881Speter hunk_info_t *hi, const char *prop_name, apr_pool_t *pool) 1831251881Speter{ 1832251881Speter svn_linenum_t lines_read; 1833251881Speter svn_boolean_t eof; 1834251881Speter apr_pool_t *iterpool; 1835251881Speter 1836251881Speter /* ### Is there a cleaner way to describe if we have an existing target? 1837251881Speter */ 1838251881Speter if (target->kind_on_disk == svn_node_file || prop_name) 1839251881Speter { 1840251881Speter svn_linenum_t line; 1841251881Speter 1842251881Speter /* Move forward to the hunk's line, copying data as we go. 1843251881Speter * Also copy leading lines of context which matched with fuzz. 1844251881Speter * The target has changed on the fuzzy-matched lines, 1845251881Speter * so we should retain the target's version of those lines. */ 1846251881Speter SVN_ERR(copy_lines_to_target(content, hi->matched_line + hi->fuzz, 1847251881Speter pool)); 1848251881Speter 1849251881Speter /* Skip the target's version of the hunk. 1850251881Speter * Don't skip trailing lines which matched with fuzz. */ 1851251881Speter line = content->current_line + 1852251881Speter svn_diff_hunk_get_original_length(hi->hunk) - (2 * hi->fuzz); 1853251881Speter SVN_ERR(seek_to_line(content, line, pool)); 1854251881Speter if (content->current_line != line && ! content->eof) 1855251881Speter { 1856251881Speter /* Seek failed, reject this hunk. */ 1857251881Speter hi->rejected = TRUE; 1858251881Speter SVN_ERR(reject_hunk(target, content, hi->hunk, prop_name, pool)); 1859251881Speter return SVN_NO_ERROR; 1860251881Speter } 1861251881Speter } 1862251881Speter 1863251881Speter /* Write the hunk's version to the patched result. 1864251881Speter * Don't write the lines which matched with fuzz. */ 1865251881Speter lines_read = 0; 1866251881Speter svn_diff_hunk_reset_modified_text(hi->hunk); 1867251881Speter iterpool = svn_pool_create(pool); 1868251881Speter do 1869251881Speter { 1870251881Speter svn_stringbuf_t *hunk_line; 1871251881Speter const char *eol_str; 1872251881Speter 1873251881Speter svn_pool_clear(iterpool); 1874251881Speter 1875251881Speter SVN_ERR(svn_diff_hunk_readline_modified_text(hi->hunk, &hunk_line, 1876251881Speter &eol_str, &eof, 1877251881Speter iterpool, iterpool)); 1878251881Speter lines_read++; 1879251881Speter if (lines_read > hi->fuzz && 1880251881Speter lines_read <= svn_diff_hunk_get_modified_length(hi->hunk) - hi->fuzz) 1881251881Speter { 1882251881Speter apr_size_t len; 1883251881Speter 1884251881Speter if (hunk_line->len >= 1) 1885251881Speter { 1886251881Speter len = hunk_line->len; 1887251881Speter SVN_ERR(content->write(content->write_baton, 1888251881Speter hunk_line->data, len, iterpool)); 1889251881Speter } 1890251881Speter 1891251881Speter if (eol_str) 1892251881Speter { 1893251881Speter /* Use the EOL as it was read from the patch file, 1894251881Speter * unless the target's EOL style is set by svn:eol-style */ 1895251881Speter if (content->eol_style != svn_subst_eol_style_none) 1896251881Speter eol_str = content->eol_str; 1897251881Speter 1898251881Speter len = strlen(eol_str); 1899251881Speter SVN_ERR(content->write(content->write_baton, 1900251881Speter eol_str, len, iterpool)); 1901251881Speter } 1902251881Speter } 1903251881Speter } 1904251881Speter while (! eof); 1905251881Speter svn_pool_destroy(iterpool); 1906251881Speter 1907251881Speter if (prop_name) 1908251881Speter target->has_prop_changes = TRUE; 1909251881Speter else 1910251881Speter target->has_text_changes = TRUE; 1911251881Speter 1912251881Speter return SVN_NO_ERROR; 1913251881Speter} 1914251881Speter 1915251881Speter/* Use client context CTX to send a suitable notification for hunk HI, 1916251881Speter * using TARGET to determine the path. If the hunk is a property hunk, 1917251881Speter * PROP_NAME must be the name of the property, else NULL. 1918251881Speter * Use POOL for temporary allocations. */ 1919251881Speterstatic svn_error_t * 1920251881Spetersend_hunk_notification(const hunk_info_t *hi, 1921251881Speter const patch_target_t *target, 1922251881Speter const char *prop_name, 1923251881Speter const svn_client_ctx_t *ctx, 1924251881Speter apr_pool_t *pool) 1925251881Speter{ 1926251881Speter svn_wc_notify_t *notify; 1927251881Speter svn_wc_notify_action_t action; 1928251881Speter 1929251881Speter if (hi->already_applied) 1930251881Speter action = svn_wc_notify_patch_hunk_already_applied; 1931251881Speter else if (hi->rejected) 1932251881Speter action = svn_wc_notify_patch_rejected_hunk; 1933251881Speter else 1934251881Speter action = svn_wc_notify_patch_applied_hunk; 1935251881Speter 1936251881Speter notify = svn_wc_create_notify(target->local_abspath 1937251881Speter ? target->local_abspath 1938251881Speter : target->local_relpath, 1939251881Speter action, pool); 1940251881Speter notify->hunk_original_start = 1941251881Speter svn_diff_hunk_get_original_start(hi->hunk); 1942251881Speter notify->hunk_original_length = 1943251881Speter svn_diff_hunk_get_original_length(hi->hunk); 1944251881Speter notify->hunk_modified_start = 1945251881Speter svn_diff_hunk_get_modified_start(hi->hunk); 1946251881Speter notify->hunk_modified_length = 1947251881Speter svn_diff_hunk_get_modified_length(hi->hunk); 1948251881Speter notify->hunk_matched_line = hi->matched_line; 1949251881Speter notify->hunk_fuzz = hi->fuzz; 1950251881Speter notify->prop_name = prop_name; 1951251881Speter 1952251881Speter (*ctx->notify_func2)(ctx->notify_baton2, notify, pool); 1953251881Speter 1954251881Speter return SVN_NO_ERROR; 1955251881Speter} 1956251881Speter 1957251881Speter/* Use client context CTX to send a suitable notification for a patch TARGET. 1958251881Speter * Use POOL for temporary allocations. */ 1959251881Speterstatic svn_error_t * 1960251881Spetersend_patch_notification(const patch_target_t *target, 1961251881Speter const svn_client_ctx_t *ctx, 1962251881Speter apr_pool_t *pool) 1963251881Speter{ 1964251881Speter svn_wc_notify_t *notify; 1965251881Speter svn_wc_notify_action_t action; 1966251881Speter 1967251881Speter if (! ctx->notify_func2) 1968251881Speter return SVN_NO_ERROR; 1969251881Speter 1970251881Speter if (target->skipped) 1971251881Speter action = svn_wc_notify_skip; 1972251881Speter else if (target->deleted) 1973251881Speter action = svn_wc_notify_delete; 1974251881Speter else if (target->added || target->replaced) 1975251881Speter action = svn_wc_notify_add; 1976251881Speter else 1977251881Speter action = svn_wc_notify_patch; 1978251881Speter 1979251881Speter notify = svn_wc_create_notify(target->local_abspath ? target->local_abspath 1980251881Speter : target->local_relpath, 1981251881Speter action, pool); 1982251881Speter notify->kind = svn_node_file; 1983251881Speter 1984251881Speter if (action == svn_wc_notify_skip) 1985251881Speter { 1986251881Speter if (target->db_kind == svn_node_none || 1987251881Speter target->db_kind == svn_node_unknown) 1988251881Speter notify->content_state = svn_wc_notify_state_missing; 1989251881Speter else if (target->db_kind == svn_node_dir) 1990251881Speter notify->content_state = svn_wc_notify_state_obstructed; 1991251881Speter else 1992251881Speter notify->content_state = svn_wc_notify_state_unknown; 1993251881Speter } 1994251881Speter else 1995251881Speter { 1996251881Speter if (target->had_rejects) 1997251881Speter notify->content_state = svn_wc_notify_state_conflicted; 1998251881Speter else if (target->local_mods) 1999251881Speter notify->content_state = svn_wc_notify_state_merged; 2000251881Speter else if (target->has_text_changes) 2001251881Speter notify->content_state = svn_wc_notify_state_changed; 2002251881Speter 2003251881Speter if (target->had_prop_rejects) 2004251881Speter notify->prop_state = svn_wc_notify_state_conflicted; 2005251881Speter else if (target->has_prop_changes) 2006251881Speter notify->prop_state = svn_wc_notify_state_changed; 2007251881Speter } 2008251881Speter 2009251881Speter (*ctx->notify_func2)(ctx->notify_baton2, notify, pool); 2010251881Speter 2011251881Speter if (action == svn_wc_notify_patch) 2012251881Speter { 2013251881Speter int i; 2014251881Speter apr_pool_t *iterpool; 2015251881Speter apr_hash_index_t *hash_index; 2016251881Speter 2017251881Speter iterpool = svn_pool_create(pool); 2018251881Speter for (i = 0; i < target->content->hunks->nelts; i++) 2019251881Speter { 2020251881Speter const hunk_info_t *hi; 2021251881Speter 2022251881Speter svn_pool_clear(iterpool); 2023251881Speter 2024251881Speter hi = APR_ARRAY_IDX(target->content->hunks, i, hunk_info_t *); 2025251881Speter 2026251881Speter SVN_ERR(send_hunk_notification(hi, target, NULL /* prop_name */, 2027251881Speter ctx, iterpool)); 2028251881Speter } 2029251881Speter 2030251881Speter for (hash_index = apr_hash_first(pool, target->prop_targets); 2031251881Speter hash_index; 2032251881Speter hash_index = apr_hash_next(hash_index)) 2033251881Speter { 2034251881Speter prop_patch_target_t *prop_target; 2035251881Speter 2036251881Speter prop_target = svn__apr_hash_index_val(hash_index); 2037251881Speter 2038251881Speter for (i = 0; i < prop_target->content->hunks->nelts; i++) 2039251881Speter { 2040251881Speter const hunk_info_t *hi; 2041251881Speter 2042251881Speter svn_pool_clear(iterpool); 2043251881Speter 2044251881Speter hi = APR_ARRAY_IDX(prop_target->content->hunks, i, 2045251881Speter hunk_info_t *); 2046251881Speter 2047251881Speter /* Don't notify on the hunk level for added or deleted props. */ 2048251881Speter if (prop_target->operation != svn_diff_op_added && 2049251881Speter prop_target->operation != svn_diff_op_deleted) 2050251881Speter SVN_ERR(send_hunk_notification(hi, target, prop_target->name, 2051251881Speter ctx, iterpool)); 2052251881Speter } 2053251881Speter } 2054251881Speter svn_pool_destroy(iterpool); 2055251881Speter } 2056251881Speter 2057251881Speter return SVN_NO_ERROR; 2058251881Speter} 2059251881Speter 2060289166Speterstatic void 2061289166Spetersvn_sort__array(apr_array_header_t *array, 2062289166Speter int (*comparison_func)(const void *, 2063289166Speter const void *)) 2064289166Speter{ 2065289166Speter qsort(array->elts, array->nelts, array->elt_size, comparison_func); 2066289166Speter} 2067289166Speter 2068289166Speter/* Implements the callback for svn_sort__array. Puts hunks that match 2069289166Speter before hunks that do not match, puts hunks that match in order 2070289166Speter based on postion matched, puts hunks that do not match in order 2071289166Speter based on original position. */ 2072289166Speterstatic int 2073289166Spetersort_matched_hunks(const void *a, const void *b) 2074289166Speter{ 2075289166Speter const hunk_info_t *item1 = *((const hunk_info_t * const *)a); 2076289166Speter const hunk_info_t *item2 = *((const hunk_info_t * const *)b); 2077289166Speter svn_boolean_t matched1 = !item1->rejected && !item1->already_applied; 2078289166Speter svn_boolean_t matched2 = !item2->rejected && !item2->already_applied; 2079289166Speter svn_linenum_t original1, original2; 2080289166Speter 2081289166Speter if (matched1 && matched2) 2082289166Speter { 2083289166Speter /* Both match so use order matched in file. */ 2084289166Speter if (item1->matched_line > item2->matched_line) 2085289166Speter return 1; 2086289166Speter else if (item1->matched_line == item2->matched_line) 2087289166Speter return 0; 2088289166Speter else 2089289166Speter return -1; 2090289166Speter } 2091289166Speter else if (matched2) 2092289166Speter /* Only second matches, put it before first. */ 2093289166Speter return 1; 2094289166Speter else if (matched1) 2095289166Speter /* Only first matches, put it before second. */ 2096289166Speter return -1; 2097289166Speter 2098289166Speter /* Neither matches, sort by original_start. */ 2099289166Speter original1 = svn_diff_hunk_get_original_start(item1->hunk); 2100289166Speter original2 = svn_diff_hunk_get_original_start(item2->hunk); 2101289166Speter if (original1 > original2) 2102289166Speter return 1; 2103289166Speter else if (original1 == original2) 2104289166Speter return 0; 2105289166Speter else 2106289166Speter return -1; 2107289166Speter} 2108289166Speter 2109289166Speter 2110251881Speter/* Apply a PATCH to a working copy at ABS_WC_PATH and put the result 2111251881Speter * into temporary files, to be installed in the working copy later. 2112251881Speter * Return information about the patch target in *PATCH_TARGET, allocated 2113251881Speter * in RESULT_POOL. Use WC_CTX as the working copy context. 2114251881Speter * STRIP_COUNT specifies the number of leading path components 2115251881Speter * which should be stripped from target paths in the patch. 2116251881Speter * REMOVE_TEMPFILES, PATCH_FUNC, and PATCH_BATON as in svn_client_patch(). 2117251881Speter * IGNORE_WHITESPACE tells whether whitespace should be considered when 2118251881Speter * doing the matching. 2119251881Speter * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation. 2120251881Speter * Do temporary allocations in SCRATCH_POOL. */ 2121251881Speterstatic svn_error_t * 2122251881Speterapply_one_patch(patch_target_t **patch_target, svn_patch_t *patch, 2123251881Speter const char *abs_wc_path, svn_wc_context_t *wc_ctx, 2124251881Speter int strip_count, 2125251881Speter svn_boolean_t ignore_whitespace, 2126251881Speter svn_boolean_t remove_tempfiles, 2127251881Speter svn_client_patch_func_t patch_func, 2128251881Speter void *patch_baton, 2129251881Speter svn_cancel_func_t cancel_func, 2130251881Speter void *cancel_baton, 2131251881Speter apr_pool_t *result_pool, apr_pool_t *scratch_pool) 2132251881Speter{ 2133251881Speter patch_target_t *target; 2134251881Speter apr_pool_t *iterpool; 2135251881Speter int i; 2136251881Speter static const svn_linenum_t MAX_FUZZ = 2; 2137251881Speter apr_hash_index_t *hash_index; 2138251881Speter 2139251881Speter SVN_ERR(init_patch_target(&target, patch, abs_wc_path, wc_ctx, strip_count, 2140251881Speter remove_tempfiles, result_pool, scratch_pool)); 2141251881Speter if (target->skipped) 2142251881Speter { 2143251881Speter *patch_target = target; 2144251881Speter return SVN_NO_ERROR; 2145251881Speter } 2146251881Speter 2147251881Speter if (patch_func) 2148251881Speter { 2149251881Speter SVN_ERR(patch_func(patch_baton, &target->filtered, 2150251881Speter target->canon_path_from_patchfile, 2151251881Speter target->patched_path, target->reject_path, 2152251881Speter scratch_pool)); 2153251881Speter if (target->filtered) 2154251881Speter { 2155251881Speter *patch_target = target; 2156251881Speter return SVN_NO_ERROR; 2157251881Speter } 2158251881Speter } 2159251881Speter 2160251881Speter iterpool = svn_pool_create(scratch_pool); 2161251881Speter /* Match hunks. */ 2162251881Speter for (i = 0; i < patch->hunks->nelts; i++) 2163251881Speter { 2164251881Speter svn_diff_hunk_t *hunk; 2165251881Speter hunk_info_t *hi; 2166251881Speter svn_linenum_t fuzz = 0; 2167251881Speter 2168251881Speter svn_pool_clear(iterpool); 2169251881Speter 2170251881Speter if (cancel_func) 2171251881Speter SVN_ERR(cancel_func(cancel_baton)); 2172251881Speter 2173251881Speter hunk = APR_ARRAY_IDX(patch->hunks, i, svn_diff_hunk_t *); 2174251881Speter 2175251881Speter /* Determine the line the hunk should be applied at. 2176251881Speter * If no match is found initially, try with fuzz. */ 2177251881Speter do 2178251881Speter { 2179251881Speter SVN_ERR(get_hunk_info(&hi, target, target->content, hunk, fuzz, 2180251881Speter ignore_whitespace, 2181251881Speter FALSE /* is_prop_hunk */, 2182251881Speter cancel_func, cancel_baton, 2183251881Speter result_pool, iterpool)); 2184251881Speter fuzz++; 2185251881Speter } 2186251881Speter while (hi->rejected && fuzz <= MAX_FUZZ && ! hi->already_applied); 2187251881Speter 2188251881Speter APR_ARRAY_PUSH(target->content->hunks, hunk_info_t *) = hi; 2189251881Speter } 2190251881Speter 2191289166Speter /* Hunks are applied in the order determined by the matched line and 2192289166Speter this may be different from the order of the original lines. */ 2193289166Speter svn_sort__array(target->content->hunks, sort_matched_hunks); 2194289166Speter 2195251881Speter /* Apply or reject hunks. */ 2196251881Speter for (i = 0; i < target->content->hunks->nelts; i++) 2197251881Speter { 2198251881Speter hunk_info_t *hi; 2199251881Speter 2200251881Speter svn_pool_clear(iterpool); 2201251881Speter 2202251881Speter if (cancel_func) 2203251881Speter SVN_ERR(cancel_func(cancel_baton)); 2204251881Speter 2205251881Speter hi = APR_ARRAY_IDX(target->content->hunks, i, hunk_info_t *); 2206251881Speter if (hi->already_applied) 2207251881Speter continue; 2208251881Speter else if (hi->rejected) 2209251881Speter SVN_ERR(reject_hunk(target, target->content, hi->hunk, 2210251881Speter NULL /* prop_name */, 2211251881Speter iterpool)); 2212251881Speter else 2213251881Speter SVN_ERR(apply_hunk(target, target->content, hi, 2214251881Speter NULL /* prop_name */, iterpool)); 2215251881Speter } 2216251881Speter 2217251881Speter if (target->kind_on_disk == svn_node_file) 2218251881Speter { 2219251881Speter /* Copy any remaining lines to target. */ 2220251881Speter SVN_ERR(copy_lines_to_target(target->content, 0, scratch_pool)); 2221251881Speter if (! target->content->eof) 2222251881Speter { 2223251881Speter /* We could not copy the entire target file to the temporary file, 2224251881Speter * and would truncate the target if we copied the temporary file 2225251881Speter * on top of it. Skip this target. */ 2226251881Speter target->skipped = TRUE; 2227251881Speter } 2228251881Speter } 2229251881Speter 2230251881Speter /* Match property hunks. */ 2231251881Speter for (hash_index = apr_hash_first(scratch_pool, patch->prop_patches); 2232251881Speter hash_index; 2233251881Speter hash_index = apr_hash_next(hash_index)) 2234251881Speter { 2235251881Speter svn_prop_patch_t *prop_patch; 2236251881Speter const char *prop_name; 2237251881Speter prop_patch_target_t *prop_target; 2238251881Speter 2239251881Speter prop_name = svn__apr_hash_index_key(hash_index); 2240251881Speter prop_patch = svn__apr_hash_index_val(hash_index); 2241251881Speter 2242251881Speter if (! strcmp(prop_name, SVN_PROP_SPECIAL)) 2243251881Speter target->is_special = TRUE; 2244251881Speter 2245251881Speter /* We'll store matched hunks in prop_content. */ 2246251881Speter prop_target = svn_hash_gets(target->prop_targets, prop_name); 2247251881Speter 2248251881Speter for (i = 0; i < prop_patch->hunks->nelts; i++) 2249251881Speter { 2250251881Speter svn_diff_hunk_t *hunk; 2251251881Speter hunk_info_t *hi; 2252251881Speter svn_linenum_t fuzz = 0; 2253251881Speter 2254251881Speter svn_pool_clear(iterpool); 2255251881Speter 2256251881Speter if (cancel_func) 2257251881Speter SVN_ERR(cancel_func(cancel_baton)); 2258251881Speter 2259251881Speter hunk = APR_ARRAY_IDX(prop_patch->hunks, i, svn_diff_hunk_t *); 2260251881Speter 2261251881Speter /* Determine the line the hunk should be applied at. 2262251881Speter * If no match is found initially, try with fuzz. */ 2263251881Speter do 2264251881Speter { 2265251881Speter SVN_ERR(get_hunk_info(&hi, target, prop_target->content, 2266251881Speter hunk, fuzz, 2267251881Speter ignore_whitespace, 2268251881Speter TRUE /* is_prop_hunk */, 2269251881Speter cancel_func, cancel_baton, 2270251881Speter result_pool, iterpool)); 2271251881Speter fuzz++; 2272251881Speter } 2273251881Speter while (hi->rejected && fuzz <= MAX_FUZZ && ! hi->already_applied); 2274251881Speter 2275251881Speter APR_ARRAY_PUSH(prop_target->content->hunks, hunk_info_t *) = hi; 2276251881Speter } 2277251881Speter } 2278251881Speter 2279251881Speter /* Apply or reject property hunks. */ 2280251881Speter for (hash_index = apr_hash_first(scratch_pool, target->prop_targets); 2281251881Speter hash_index; 2282251881Speter hash_index = apr_hash_next(hash_index)) 2283251881Speter { 2284251881Speter prop_patch_target_t *prop_target; 2285251881Speter 2286251881Speter prop_target = svn__apr_hash_index_val(hash_index); 2287251881Speter 2288251881Speter for (i = 0; i < prop_target->content->hunks->nelts; i++) 2289251881Speter { 2290251881Speter hunk_info_t *hi; 2291251881Speter 2292251881Speter svn_pool_clear(iterpool); 2293251881Speter 2294251881Speter hi = APR_ARRAY_IDX(prop_target->content->hunks, i, 2295251881Speter hunk_info_t *); 2296251881Speter if (hi->already_applied) 2297251881Speter continue; 2298251881Speter else if (hi->rejected) 2299251881Speter SVN_ERR(reject_hunk(target, prop_target->content, hi->hunk, 2300251881Speter prop_target->name, 2301251881Speter iterpool)); 2302251881Speter else 2303251881Speter SVN_ERR(apply_hunk(target, prop_target->content, hi, 2304251881Speter prop_target->name, 2305251881Speter iterpool)); 2306251881Speter } 2307251881Speter 2308251881Speter if (prop_target->content->existed) 2309251881Speter { 2310251881Speter /* Copy any remaining lines to target. */ 2311251881Speter SVN_ERR(copy_lines_to_target(prop_target->content, 0, 2312251881Speter scratch_pool)); 2313251881Speter if (! prop_target->content->eof) 2314251881Speter { 2315251881Speter /* We could not copy the entire target property to the 2316251881Speter * temporary file, and would truncate the target if we 2317251881Speter * copied the temporary file on top of it. Skip this target. */ 2318251881Speter target->skipped = TRUE; 2319251881Speter } 2320251881Speter } 2321251881Speter } 2322251881Speter 2323251881Speter svn_pool_destroy(iterpool); 2324251881Speter 2325251881Speter if (!target->is_symlink) 2326251881Speter { 2327251881Speter /* Now close files we don't need any longer to get their contents 2328251881Speter * flushed to disk. 2329251881Speter * But we're not closing the reject file -- it still needed and 2330251881Speter * will be closed later in write_out_rejected_hunks(). */ 2331251881Speter if (target->kind_on_disk == svn_node_file) 2332251881Speter SVN_ERR(svn_io_file_close(target->file, scratch_pool)); 2333251881Speter 2334251881Speter SVN_ERR(svn_io_file_close(target->patched_file, scratch_pool)); 2335251881Speter } 2336251881Speter 2337251881Speter if (! target->skipped) 2338251881Speter { 2339251881Speter apr_finfo_t working_file; 2340251881Speter apr_finfo_t patched_file; 2341251881Speter 2342251881Speter /* Get sizes of the patched temporary file and the working file. 2343251881Speter * We'll need those to figure out whether we should delete the 2344251881Speter * patched file. */ 2345251881Speter SVN_ERR(svn_io_stat(&patched_file, target->patched_path, 2346251881Speter APR_FINFO_SIZE | APR_FINFO_LINK, scratch_pool)); 2347251881Speter if (target->kind_on_disk == svn_node_file) 2348251881Speter SVN_ERR(svn_io_stat(&working_file, target->local_abspath, 2349251881Speter APR_FINFO_SIZE | APR_FINFO_LINK, scratch_pool)); 2350251881Speter else 2351251881Speter working_file.size = 0; 2352251881Speter 2353251881Speter if (patched_file.size == 0 && working_file.size > 0) 2354251881Speter { 2355251881Speter /* If a unidiff removes all lines from a file, that usually 2356251881Speter * means deletion, so we can confidently schedule the target 2357251881Speter * for deletion. In the rare case where the unidiff was really 2358251881Speter * meant to replace a file with an empty one, this may not 2359251881Speter * be desirable. But the deletion can easily be reverted and 2360251881Speter * creating an empty file manually is not exactly hard either. */ 2361251881Speter target->deleted = (target->db_kind == svn_node_file); 2362251881Speter } 2363251881Speter else if (patched_file.size == 0 && working_file.size == 0) 2364251881Speter { 2365251881Speter /* The target was empty or non-existent to begin with 2366251881Speter * and no content was changed by patching. 2367251881Speter * Report this as skipped if it didn't exist, unless in the special 2368251881Speter * case of adding an empty file which has properties set on it or 2369251881Speter * adding an empty file with a 'git diff' */ 2370251881Speter if (target->kind_on_disk == svn_node_none 2371251881Speter && ! target->has_prop_changes 2372251881Speter && ! target->added) 2373251881Speter target->skipped = TRUE; 2374251881Speter } 2375251881Speter else if (patched_file.size > 0 && working_file.size == 0) 2376251881Speter { 2377251881Speter /* The patch has created a file. */ 2378251881Speter if (target->locally_deleted) 2379251881Speter target->replaced = TRUE; 2380251881Speter else if (target->db_kind == svn_node_none) 2381251881Speter target->added = TRUE; 2382251881Speter } 2383251881Speter } 2384251881Speter 2385251881Speter *patch_target = target; 2386251881Speter 2387251881Speter return SVN_NO_ERROR; 2388251881Speter} 2389251881Speter 2390251881Speter/* Try to create missing parent directories for TARGET in the working copy 2391251881Speter * rooted at ABS_WC_PATH, and add the parents to version control. 2392251881Speter * If the parents cannot be created, mark the target as skipped. 2393251881Speter * Use client context CTX. If DRY_RUN is true, do not create missing 2394251881Speter * parents but issue notifications only. 2395251881Speter * Use SCRATCH_POOL for temporary allocations. */ 2396251881Speterstatic svn_error_t * 2397251881Spetercreate_missing_parents(patch_target_t *target, 2398251881Speter const char *abs_wc_path, 2399251881Speter svn_client_ctx_t *ctx, 2400251881Speter svn_boolean_t dry_run, 2401251881Speter apr_pool_t *scratch_pool) 2402251881Speter{ 2403251881Speter const char *local_abspath; 2404251881Speter apr_array_header_t *components; 2405251881Speter int present_components; 2406251881Speter int i; 2407251881Speter apr_pool_t *iterpool; 2408251881Speter 2409251881Speter /* Check if we can safely create the target's parent. */ 2410251881Speter local_abspath = abs_wc_path; 2411251881Speter components = svn_path_decompose(target->local_relpath, scratch_pool); 2412251881Speter present_components = 0; 2413251881Speter iterpool = svn_pool_create(scratch_pool); 2414251881Speter for (i = 0; i < components->nelts - 1; i++) 2415251881Speter { 2416251881Speter const char *component; 2417251881Speter svn_node_kind_t wc_kind, disk_kind; 2418251881Speter 2419251881Speter svn_pool_clear(iterpool); 2420251881Speter 2421251881Speter component = APR_ARRAY_IDX(components, i, const char *); 2422251881Speter local_abspath = svn_dirent_join(local_abspath, component, scratch_pool); 2423251881Speter 2424251881Speter SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, local_abspath, 2425251881Speter FALSE, TRUE, iterpool)); 2426251881Speter 2427251881Speter SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, iterpool)); 2428251881Speter 2429251881Speter if (disk_kind == svn_node_file || wc_kind == svn_node_file) 2430251881Speter { 2431251881Speter /* on-disk files and missing files are obstructions */ 2432251881Speter target->skipped = TRUE; 2433251881Speter break; 2434251881Speter } 2435251881Speter else if (disk_kind == svn_node_dir) 2436251881Speter { 2437251881Speter if (wc_kind == svn_node_dir) 2438251881Speter present_components++; 2439251881Speter else 2440251881Speter { 2441251881Speter target->skipped = TRUE; 2442251881Speter break; 2443251881Speter } 2444251881Speter } 2445251881Speter else if (wc_kind != svn_node_none) 2446251881Speter { 2447251881Speter /* Node is missing */ 2448251881Speter target->skipped = TRUE; 2449251881Speter break; 2450251881Speter } 2451251881Speter else 2452251881Speter { 2453251881Speter /* It's not a file, it's not a dir... 2454251881Speter Let's add a dir */ 2455251881Speter break; 2456251881Speter } 2457251881Speter } 2458251881Speter if (! target->skipped) 2459251881Speter { 2460251881Speter local_abspath = abs_wc_path; 2461251881Speter for (i = 0; i < present_components; i++) 2462251881Speter { 2463251881Speter const char *component; 2464251881Speter component = APR_ARRAY_IDX(components, i, const char *); 2465251881Speter local_abspath = svn_dirent_join(local_abspath, 2466251881Speter component, scratch_pool); 2467251881Speter } 2468251881Speter 2469251881Speter if (!dry_run && present_components < components->nelts - 1) 2470251881Speter SVN_ERR(svn_io_make_dir_recursively( 2471251881Speter svn_dirent_join( 2472251881Speter abs_wc_path, 2473251881Speter svn_relpath_dirname(target->local_relpath, 2474251881Speter scratch_pool), 2475251881Speter scratch_pool), 2476251881Speter scratch_pool)); 2477251881Speter 2478251881Speter for (i = present_components; i < components->nelts - 1; i++) 2479251881Speter { 2480251881Speter const char *component; 2481251881Speter 2482251881Speter svn_pool_clear(iterpool); 2483251881Speter 2484251881Speter component = APR_ARRAY_IDX(components, i, const char *); 2485251881Speter local_abspath = svn_dirent_join(local_abspath, component, 2486251881Speter scratch_pool); 2487251881Speter if (dry_run) 2488251881Speter { 2489251881Speter if (ctx->notify_func2) 2490251881Speter { 2491251881Speter /* Just do notification. */ 2492251881Speter svn_wc_notify_t *notify; 2493251881Speter notify = svn_wc_create_notify(local_abspath, 2494251881Speter svn_wc_notify_add, 2495251881Speter iterpool); 2496251881Speter notify->kind = svn_node_dir; 2497251881Speter ctx->notify_func2(ctx->notify_baton2, notify, 2498251881Speter iterpool); 2499251881Speter } 2500251881Speter } 2501251881Speter else 2502251881Speter { 2503251881Speter /* Create the missing component and add it 2504251881Speter * to version control. Allow cancellation since we 2505251881Speter * have not modified the working copy yet for this 2506251881Speter * target. */ 2507251881Speter 2508251881Speter if (ctx->cancel_func) 2509251881Speter SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 2510251881Speter 2511251881Speter SVN_ERR(svn_wc_add_from_disk2(ctx->wc_ctx, local_abspath, 2512251881Speter NULL /*props*/, 2513251881Speter ctx->notify_func2, ctx->notify_baton2, 2514251881Speter iterpool)); 2515251881Speter } 2516251881Speter } 2517251881Speter } 2518251881Speter 2519251881Speter svn_pool_destroy(iterpool); 2520251881Speter return SVN_NO_ERROR; 2521251881Speter} 2522251881Speter 2523251881Speter/* Install a patched TARGET into the working copy at ABS_WC_PATH. 2524251881Speter * Use client context CTX to retrieve WC_CTX, and possibly doing 2525251881Speter * notifications. If DRY_RUN is TRUE, don't modify the working copy. 2526251881Speter * Do temporary allocations in POOL. */ 2527251881Speterstatic svn_error_t * 2528251881Speterinstall_patched_target(patch_target_t *target, const char *abs_wc_path, 2529251881Speter svn_client_ctx_t *ctx, svn_boolean_t dry_run, 2530251881Speter apr_pool_t *pool) 2531251881Speter{ 2532251881Speter if (target->deleted) 2533251881Speter { 2534251881Speter if (! dry_run) 2535251881Speter { 2536251881Speter /* Schedule the target for deletion. Suppress 2537251881Speter * notification, we'll do it manually in a minute 2538251881Speter * because we also need to notify during dry-run. 2539251881Speter * Also suppress cancellation, because we'd rather 2540251881Speter * notify about what we did before aborting. */ 2541251881Speter SVN_ERR(svn_wc_delete4(ctx->wc_ctx, target->local_abspath, 2542251881Speter FALSE /* keep_local */, FALSE, 2543251881Speter NULL, NULL, NULL, NULL, pool)); 2544251881Speter } 2545251881Speter } 2546251881Speter else 2547251881Speter { 2548251881Speter svn_node_kind_t parent_db_kind; 2549251881Speter if (target->added || target->replaced) 2550251881Speter { 2551251881Speter const char *parent_abspath; 2552251881Speter 2553251881Speter parent_abspath = svn_dirent_dirname(target->local_abspath, 2554251881Speter pool); 2555251881Speter /* If the target's parent directory does not yet exist 2556251881Speter * we need to create it before we can copy the patched 2557251881Speter * result in place. */ 2558251881Speter SVN_ERR(svn_wc_read_kind2(&parent_db_kind, ctx->wc_ctx, 2559251881Speter parent_abspath, FALSE, FALSE, pool)); 2560251881Speter 2561251881Speter /* We can't add targets under nodes scheduled for delete, so add 2562251881Speter a new directory if needed. */ 2563251881Speter if (parent_db_kind == svn_node_dir 2564251881Speter || parent_db_kind == svn_node_file) 2565251881Speter { 2566251881Speter if (parent_db_kind != svn_node_dir) 2567251881Speter target->skipped = TRUE; 2568251881Speter else 2569251881Speter { 2570251881Speter svn_node_kind_t disk_kind; 2571251881Speter 2572251881Speter SVN_ERR(svn_io_check_path(parent_abspath, &disk_kind, pool)); 2573251881Speter if (disk_kind != svn_node_dir) 2574251881Speter target->skipped = TRUE; 2575251881Speter } 2576251881Speter } 2577251881Speter else 2578251881Speter SVN_ERR(create_missing_parents(target, abs_wc_path, ctx, 2579251881Speter dry_run, pool)); 2580251881Speter 2581251881Speter } 2582251881Speter else 2583251881Speter { 2584251881Speter svn_node_kind_t wc_kind; 2585251881Speter 2586251881Speter /* The target should exist */ 2587251881Speter SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, 2588251881Speter target->local_abspath, 2589251881Speter FALSE, FALSE, pool)); 2590251881Speter 2591251881Speter if (target->kind_on_disk == svn_node_none 2592251881Speter || wc_kind != target->kind_on_disk) 2593251881Speter { 2594251881Speter target->skipped = TRUE; 2595251881Speter } 2596251881Speter } 2597251881Speter 2598251881Speter if (! dry_run && ! target->skipped) 2599251881Speter { 2600251881Speter if (target->is_special) 2601251881Speter { 2602251881Speter svn_stream_t *stream; 2603251881Speter svn_stream_t *patched_stream; 2604251881Speter 2605251881Speter SVN_ERR(svn_stream_open_readonly(&patched_stream, 2606251881Speter target->patched_path, 2607251881Speter pool, pool)); 2608251881Speter SVN_ERR(svn_subst_create_specialfile(&stream, 2609251881Speter target->local_abspath, 2610251881Speter pool, pool)); 2611251881Speter SVN_ERR(svn_stream_copy3(patched_stream, stream, 2612251881Speter ctx->cancel_func, ctx->cancel_baton, 2613251881Speter pool)); 2614251881Speter } 2615251881Speter else 2616251881Speter { 2617251881Speter svn_boolean_t repair_eol; 2618251881Speter 2619251881Speter /* Copy the patched file on top of the target file. 2620251881Speter * Always expand keywords in the patched file, but repair EOL 2621251881Speter * only if svn:eol-style dictates a particular style. */ 2622251881Speter repair_eol = (target->content->eol_style == 2623251881Speter svn_subst_eol_style_fixed || 2624251881Speter target->content->eol_style == 2625251881Speter svn_subst_eol_style_native); 2626251881Speter 2627251881Speter SVN_ERR(svn_subst_copy_and_translate4( 2628251881Speter target->patched_path, target->local_abspath, 2629251881Speter target->content->eol_str, repair_eol, 2630251881Speter target->content->keywords, 2631251881Speter TRUE /* expand */, FALSE /* special */, 2632251881Speter ctx->cancel_func, ctx->cancel_baton, pool)); 2633251881Speter } 2634251881Speter 2635251881Speter if (target->added || target->replaced) 2636251881Speter { 2637251881Speter /* The target file didn't exist previously, 2638251881Speter * so add it to version control. 2639251881Speter * Suppress notification, we'll do that later (and also 2640251881Speter * during dry-run). Don't allow cancellation because 2641251881Speter * we'd rather notify about what we did before aborting. */ 2642251881Speter SVN_ERR(svn_wc_add_from_disk2(ctx->wc_ctx, target->local_abspath, 2643251881Speter NULL /*props*/, 2644251881Speter NULL, NULL, pool)); 2645251881Speter } 2646251881Speter 2647251881Speter /* Restore the target's executable bit if necessary. */ 2648251881Speter SVN_ERR(svn_io_set_file_executable(target->local_abspath, 2649251881Speter target->executable, 2650251881Speter FALSE, pool)); 2651251881Speter } 2652251881Speter } 2653251881Speter 2654251881Speter return SVN_NO_ERROR; 2655251881Speter} 2656251881Speter 2657251881Speter/* Write out rejected hunks, if any, to TARGET->REJECT_PATH. If DRY_RUN is 2658251881Speter * TRUE, don't modify the working copy. 2659251881Speter * Do temporary allocations in POOL. 2660251881Speter */ 2661251881Speterstatic svn_error_t * 2662251881Speterwrite_out_rejected_hunks(patch_target_t *target, 2663251881Speter svn_boolean_t dry_run, 2664251881Speter apr_pool_t *pool) 2665251881Speter{ 2666251881Speter SVN_ERR(svn_io_file_close(target->reject_file, pool)); 2667251881Speter 2668251881Speter if (! dry_run && (target->had_rejects || target->had_prop_rejects)) 2669251881Speter { 2670251881Speter /* Write out rejected hunks, if any. */ 2671251881Speter SVN_ERR(svn_io_copy_file(target->reject_path, 2672251881Speter apr_psprintf(pool, "%s.svnpatch.rej", 2673251881Speter target->local_abspath), 2674251881Speter FALSE, pool)); 2675251881Speter /* ### TODO mark file as conflicted. */ 2676251881Speter } 2677251881Speter return SVN_NO_ERROR; 2678251881Speter} 2679251881Speter 2680251881Speter/* Install the patched properties for TARGET. Use client context CTX to 2681251881Speter * retrieve WC_CTX. If DRY_RUN is TRUE, don't modify the working copy. 2682251881Speter * Do temporary allocations in SCRATCH_POOL. */ 2683251881Speterstatic svn_error_t * 2684251881Speterinstall_patched_prop_targets(patch_target_t *target, 2685251881Speter svn_client_ctx_t *ctx, svn_boolean_t dry_run, 2686251881Speter apr_pool_t *scratch_pool) 2687251881Speter{ 2688251881Speter apr_hash_index_t *hi; 2689251881Speter apr_pool_t *iterpool; 2690251881Speter 2691251881Speter iterpool = svn_pool_create(scratch_pool); 2692251881Speter 2693251881Speter for (hi = apr_hash_first(scratch_pool, target->prop_targets); 2694251881Speter hi; 2695251881Speter hi = apr_hash_next(hi)) 2696251881Speter { 2697251881Speter prop_patch_target_t *prop_target = svn__apr_hash_index_val(hi); 2698251881Speter const svn_string_t *prop_val; 2699251881Speter svn_error_t *err; 2700251881Speter 2701251881Speter svn_pool_clear(iterpool); 2702251881Speter 2703251881Speter if (ctx->cancel_func) 2704251881Speter SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 2705251881Speter 2706251881Speter /* For a deleted prop we only set the value to NULL. */ 2707251881Speter if (prop_target->operation == svn_diff_op_deleted) 2708251881Speter { 2709251881Speter if (! dry_run) 2710251881Speter SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, target->local_abspath, 2711251881Speter prop_target->name, NULL, svn_depth_empty, 2712251881Speter TRUE /* skip_checks */, 2713251881Speter NULL /* changelist_filter */, 2714251881Speter NULL, NULL /* cancellation */, 2715251881Speter NULL, NULL /* notification */, 2716251881Speter iterpool)); 2717251881Speter continue; 2718251881Speter } 2719251881Speter 2720251881Speter /* If the patch target doesn't exist yet, the patch wants to add an 2721251881Speter * empty file with properties set on it. So create an empty file and 2722251881Speter * add it to version control. But if the patch was in the 'git format' 2723251881Speter * then the file has already been added. 2724251881Speter * 2725251881Speter * ### How can we tell whether the patch really wanted to create 2726251881Speter * ### an empty directory? */ 2727251881Speter if (! target->has_text_changes 2728251881Speter && target->kind_on_disk == svn_node_none 2729251881Speter && ! target->added) 2730251881Speter { 2731251881Speter if (! dry_run) 2732251881Speter { 2733251881Speter SVN_ERR(svn_io_file_create(target->local_abspath, "", 2734251881Speter scratch_pool)); 2735251881Speter SVN_ERR(svn_wc_add_from_disk2(ctx->wc_ctx, target->local_abspath, 2736251881Speter NULL /*props*/, 2737251881Speter /* suppress notification */ 2738251881Speter NULL, NULL, 2739251881Speter iterpool)); 2740251881Speter } 2741251881Speter target->added = TRUE; 2742251881Speter } 2743251881Speter 2744251881Speter /* Attempt to set the property, and reject all hunks if this 2745251881Speter fails. If the property had a non-empty value, but now has 2746251881Speter an empty one, we'll just delete the property altogether. */ 2747251881Speter if (prop_target->value && prop_target->value->len 2748251881Speter && prop_target->patched_value && !prop_target->patched_value->len) 2749251881Speter prop_val = NULL; 2750251881Speter else 2751251881Speter prop_val = svn_stringbuf__morph_into_string(prop_target->patched_value); 2752251881Speter 2753251881Speter if (dry_run) 2754251881Speter { 2755251881Speter const svn_string_t *canon_propval; 2756251881Speter 2757251881Speter err = svn_wc_canonicalize_svn_prop(&canon_propval, 2758251881Speter prop_target->name, 2759251881Speter prop_val, target->local_abspath, 2760251881Speter target->db_kind, 2761251881Speter TRUE, /* ### Skipping checks */ 2762251881Speter NULL, NULL, 2763251881Speter iterpool); 2764251881Speter } 2765251881Speter else 2766251881Speter { 2767251881Speter err = svn_wc_prop_set4(ctx->wc_ctx, target->local_abspath, 2768251881Speter prop_target->name, prop_val, svn_depth_empty, 2769251881Speter TRUE /* skip_checks */, 2770251881Speter NULL /* changelist_filter */, 2771251881Speter NULL, NULL /* cancellation */, 2772251881Speter NULL, NULL /* notification */, 2773251881Speter iterpool); 2774251881Speter } 2775251881Speter 2776251881Speter if (err) 2777251881Speter { 2778251881Speter /* ### The errors which svn_wc_canonicalize_svn_prop() will 2779251881Speter * ### return aren't documented. */ 2780251881Speter if (err->apr_err == SVN_ERR_ILLEGAL_TARGET || 2781251881Speter err->apr_err == SVN_ERR_NODE_UNEXPECTED_KIND || 2782251881Speter err->apr_err == SVN_ERR_IO_UNKNOWN_EOL || 2783251881Speter err->apr_err == SVN_ERR_BAD_MIME_TYPE || 2784251881Speter err->apr_err == SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION) 2785251881Speter { 2786251881Speter int i; 2787251881Speter 2788251881Speter svn_error_clear(err); 2789251881Speter 2790251881Speter for (i = 0; i < prop_target->content->hunks->nelts; i++) 2791251881Speter { 2792251881Speter hunk_info_t *hunk_info; 2793251881Speter 2794251881Speter hunk_info = APR_ARRAY_IDX(prop_target->content->hunks, 2795251881Speter i, hunk_info_t *); 2796251881Speter hunk_info->rejected = TRUE; 2797251881Speter SVN_ERR(reject_hunk(target, prop_target->content, 2798251881Speter hunk_info->hunk, prop_target->name, 2799251881Speter iterpool)); 2800251881Speter } 2801251881Speter } 2802251881Speter else 2803251881Speter return svn_error_trace(err); 2804251881Speter } 2805251881Speter 2806251881Speter } 2807251881Speter 2808251881Speter svn_pool_destroy(iterpool); 2809251881Speter 2810251881Speter return SVN_NO_ERROR; 2811251881Speter} 2812251881Speter 2813251881Speter/* Baton for can_delete_callback */ 2814251881Speterstruct can_delete_baton_t 2815251881Speter{ 2816251881Speter svn_boolean_t must_keep; 2817251881Speter const apr_array_header_t *targets_info; 2818251881Speter const char *local_abspath; 2819251881Speter}; 2820251881Speter 2821251881Speter/* Implements svn_wc_status_func4_t. */ 2822251881Speterstatic svn_error_t * 2823251881Spetercan_delete_callback(void *baton, 2824251881Speter const char *abspath, 2825251881Speter const svn_wc_status3_t *status, 2826251881Speter apr_pool_t *pool) 2827251881Speter{ 2828251881Speter struct can_delete_baton_t *cb = baton; 2829251881Speter int i; 2830251881Speter 2831251881Speter switch(status->node_status) 2832251881Speter { 2833251881Speter case svn_wc_status_none: 2834251881Speter case svn_wc_status_deleted: 2835251881Speter return SVN_NO_ERROR; 2836251881Speter 2837251881Speter default: 2838251881Speter if (! strcmp(cb->local_abspath, abspath)) 2839251881Speter return SVN_NO_ERROR; /* Only interested in descendants */ 2840251881Speter 2841251881Speter for (i = 0; i < cb->targets_info->nelts; i++) 2842251881Speter { 2843251881Speter const patch_target_info_t *target_info = 2844251881Speter APR_ARRAY_IDX(cb->targets_info, i, const patch_target_info_t *); 2845251881Speter 2846251881Speter if (! strcmp(target_info->local_abspath, abspath)) 2847251881Speter { 2848251881Speter if (target_info->deleted) 2849251881Speter return SVN_NO_ERROR; 2850251881Speter 2851251881Speter break; /* Cease invocation; must keep */ 2852251881Speter } 2853251881Speter } 2854251881Speter 2855251881Speter cb->must_keep = TRUE; 2856251881Speter 2857251881Speter return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL); 2858251881Speter } 2859251881Speter} 2860251881Speter 2861251881Speterstatic svn_error_t * 2862251881Spetercheck_ancestor_delete(const char *deleted_target, 2863251881Speter apr_array_header_t *targets_info, 2864251881Speter const char *apply_root, 2865251881Speter svn_boolean_t dry_run, 2866251881Speter svn_client_ctx_t *ctx, 2867251881Speter apr_pool_t *result_pool, 2868251881Speter apr_pool_t *scratch_pool) 2869251881Speter{ 2870251881Speter struct can_delete_baton_t cb; 2871251881Speter svn_error_t *err; 2872251881Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 2873251881Speter 2874251881Speter const char *dir_abspath = svn_dirent_dirname(deleted_target, scratch_pool); 2875251881Speter 2876251881Speter while (svn_dirent_is_child(apply_root, dir_abspath, iterpool)) 2877251881Speter { 2878251881Speter svn_pool_clear(iterpool); 2879251881Speter 2880251881Speter cb.local_abspath = dir_abspath; 2881251881Speter cb.must_keep = FALSE; 2882251881Speter cb.targets_info = targets_info; 2883251881Speter 2884251881Speter err = svn_wc_walk_status(ctx->wc_ctx, dir_abspath, svn_depth_infinity, 2885251881Speter TRUE, FALSE, FALSE, NULL, 2886251881Speter can_delete_callback, &cb, 2887251881Speter ctx->cancel_func, ctx->cancel_baton, 2888251881Speter iterpool); 2889251881Speter 2890251881Speter if (err) 2891251881Speter { 2892251881Speter if (err->apr_err != SVN_ERR_CEASE_INVOCATION) 2893251881Speter return svn_error_trace(err); 2894251881Speter 2895251881Speter svn_error_clear(err); 2896251881Speter } 2897251881Speter 2898251881Speter if (cb.must_keep) 2899251881Speter { 2900251881Speter break; 2901251881Speter } 2902251881Speter 2903251881Speter if (! dry_run) 2904251881Speter { 2905251881Speter SVN_ERR(svn_wc_delete4(ctx->wc_ctx, dir_abspath, FALSE, FALSE, 2906251881Speter ctx->cancel_func, ctx->cancel_baton, 2907251881Speter NULL, NULL, 2908251881Speter scratch_pool)); 2909251881Speter } 2910251881Speter 2911251881Speter { 2912251881Speter patch_target_info_t *pti = apr_pcalloc(result_pool, sizeof(*pti)); 2913251881Speter 2914251881Speter pti->local_abspath = apr_pstrdup(result_pool, dir_abspath); 2915251881Speter pti->deleted = TRUE; 2916251881Speter 2917251881Speter APR_ARRAY_PUSH(targets_info, patch_target_info_t *) = pti; 2918251881Speter } 2919251881Speter 2920251881Speter 2921251881Speter if (ctx->notify_func2) 2922251881Speter { 2923251881Speter svn_wc_notify_t *notify; 2924251881Speter 2925251881Speter notify = svn_wc_create_notify(dir_abspath, svn_wc_notify_delete, 2926251881Speter iterpool); 2927251881Speter notify->kind = svn_node_dir; 2928251881Speter 2929251881Speter ctx->notify_func2(ctx->notify_baton2, notify, iterpool); 2930251881Speter } 2931251881Speter 2932251881Speter /* And check if we must also delete the parent */ 2933251881Speter dir_abspath = svn_dirent_dirname(dir_abspath, scratch_pool); 2934251881Speter } 2935251881Speter 2936251881Speter svn_pool_destroy(iterpool); 2937251881Speter 2938251881Speter return SVN_NO_ERROR; 2939251881Speter} 2940251881Speter 2941251881Speter/* This function is the main entry point into the patch code. */ 2942251881Speterstatic svn_error_t * 2943251881Speterapply_patches(/* The path to the patch file. */ 2944251881Speter const char *patch_abspath, 2945251881Speter /* The abspath to the working copy the patch should be applied to. */ 2946251881Speter const char *abs_wc_path, 2947251881Speter /* Indicates whether we're doing a dry run. */ 2948251881Speter svn_boolean_t dry_run, 2949251881Speter /* Number of leading components to strip from patch target paths. */ 2950251881Speter int strip_count, 2951251881Speter /* Whether to apply the patch in reverse. */ 2952251881Speter svn_boolean_t reverse, 2953251881Speter /* Whether to ignore whitespace when matching context lines. */ 2954251881Speter svn_boolean_t ignore_whitespace, 2955251881Speter /* As in svn_client_patch(). */ 2956251881Speter svn_boolean_t remove_tempfiles, 2957251881Speter /* As in svn_client_patch(). */ 2958251881Speter svn_client_patch_func_t patch_func, 2959251881Speter void *patch_baton, 2960251881Speter /* The client context. */ 2961251881Speter svn_client_ctx_t *ctx, 2962251881Speter apr_pool_t *scratch_pool) 2963251881Speter{ 2964251881Speter svn_patch_t *patch; 2965251881Speter apr_pool_t *iterpool; 2966251881Speter svn_patch_file_t *patch_file; 2967251881Speter apr_array_header_t *targets_info; 2968251881Speter 2969251881Speter /* Try to open the patch file. */ 2970251881Speter SVN_ERR(svn_diff_open_patch_file(&patch_file, patch_abspath, scratch_pool)); 2971251881Speter 2972251881Speter /* Apply patches. */ 2973251881Speter targets_info = apr_array_make(scratch_pool, 0, 2974251881Speter sizeof(patch_target_info_t *)); 2975251881Speter iterpool = svn_pool_create(scratch_pool); 2976251881Speter do 2977251881Speter { 2978251881Speter svn_pool_clear(iterpool); 2979251881Speter 2980251881Speter if (ctx->cancel_func) 2981251881Speter SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 2982251881Speter 2983251881Speter SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file, 2984251881Speter reverse, ignore_whitespace, 2985251881Speter iterpool, iterpool)); 2986251881Speter if (patch) 2987251881Speter { 2988251881Speter patch_target_t *target; 2989251881Speter 2990251881Speter SVN_ERR(apply_one_patch(&target, patch, abs_wc_path, 2991251881Speter ctx->wc_ctx, strip_count, 2992251881Speter ignore_whitespace, remove_tempfiles, 2993251881Speter patch_func, patch_baton, 2994251881Speter ctx->cancel_func, ctx->cancel_baton, 2995251881Speter iterpool, iterpool)); 2996251881Speter if (! target->filtered) 2997251881Speter { 2998251881Speter /* Save info we'll still need when we're done patching. */ 2999251881Speter patch_target_info_t *target_info = 3000251881Speter apr_pcalloc(scratch_pool, sizeof(patch_target_info_t)); 3001251881Speter target_info->local_abspath = apr_pstrdup(scratch_pool, 3002251881Speter target->local_abspath); 3003251881Speter target_info->deleted = target->deleted; 3004251881Speter 3005251881Speter if (! target->skipped) 3006251881Speter { 3007251881Speter APR_ARRAY_PUSH(targets_info, 3008251881Speter patch_target_info_t *) = target_info; 3009251881Speter 3010251881Speter if (target->has_text_changes 3011251881Speter || target->added 3012251881Speter || target->deleted) 3013251881Speter SVN_ERR(install_patched_target(target, abs_wc_path, 3014251881Speter ctx, dry_run, iterpool)); 3015251881Speter 3016251881Speter if (target->has_prop_changes && (!target->deleted)) 3017251881Speter SVN_ERR(install_patched_prop_targets(target, ctx, 3018251881Speter dry_run, iterpool)); 3019251881Speter 3020251881Speter SVN_ERR(write_out_rejected_hunks(target, dry_run, iterpool)); 3021251881Speter } 3022251881Speter SVN_ERR(send_patch_notification(target, ctx, iterpool)); 3023251881Speter 3024251881Speter if (target->deleted && !target->skipped) 3025251881Speter { 3026251881Speter SVN_ERR(check_ancestor_delete(target_info->local_abspath, 3027251881Speter targets_info, abs_wc_path, 3028251881Speter dry_run, ctx, 3029251881Speter scratch_pool, iterpool)); 3030251881Speter } 3031251881Speter } 3032251881Speter } 3033251881Speter } 3034251881Speter while (patch); 3035251881Speter 3036251881Speter SVN_ERR(svn_diff_close_patch_file(patch_file, iterpool)); 3037251881Speter svn_pool_destroy(iterpool); 3038251881Speter 3039251881Speter return SVN_NO_ERROR; 3040251881Speter} 3041251881Speter 3042251881Spetersvn_error_t * 3043251881Spetersvn_client_patch(const char *patch_abspath, 3044251881Speter const char *wc_dir_abspath, 3045251881Speter svn_boolean_t dry_run, 3046251881Speter int strip_count, 3047251881Speter svn_boolean_t reverse, 3048251881Speter svn_boolean_t ignore_whitespace, 3049251881Speter svn_boolean_t remove_tempfiles, 3050251881Speter svn_client_patch_func_t patch_func, 3051251881Speter void *patch_baton, 3052251881Speter svn_client_ctx_t *ctx, 3053251881Speter apr_pool_t *scratch_pool) 3054251881Speter{ 3055251881Speter svn_node_kind_t kind; 3056251881Speter 3057251881Speter if (strip_count < 0) 3058251881Speter return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, 3059251881Speter _("strip count must be positive")); 3060251881Speter 3061251881Speter if (svn_path_is_url(wc_dir_abspath)) 3062251881Speter return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 3063251881Speter _("'%s' is not a local path"), 3064251881Speter svn_dirent_local_style(wc_dir_abspath, 3065251881Speter scratch_pool)); 3066251881Speter 3067251881Speter SVN_ERR(svn_io_check_path(patch_abspath, &kind, scratch_pool)); 3068251881Speter if (kind == svn_node_none) 3069251881Speter return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 3070251881Speter _("'%s' does not exist"), 3071251881Speter svn_dirent_local_style(patch_abspath, 3072251881Speter scratch_pool)); 3073251881Speter if (kind != svn_node_file) 3074251881Speter return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 3075251881Speter _("'%s' is not a file"), 3076251881Speter svn_dirent_local_style(patch_abspath, 3077251881Speter scratch_pool)); 3078251881Speter 3079251881Speter SVN_ERR(svn_io_check_path(wc_dir_abspath, &kind, scratch_pool)); 3080251881Speter if (kind == svn_node_none) 3081251881Speter return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 3082251881Speter _("'%s' does not exist"), 3083251881Speter svn_dirent_local_style(wc_dir_abspath, 3084251881Speter scratch_pool)); 3085251881Speter if (kind != svn_node_dir) 3086251881Speter return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 3087251881Speter _("'%s' is not a directory"), 3088251881Speter svn_dirent_local_style(wc_dir_abspath, 3089251881Speter scratch_pool)); 3090251881Speter 3091251881Speter SVN_WC__CALL_WITH_WRITE_LOCK( 3092251881Speter apply_patches(patch_abspath, wc_dir_abspath, dry_run, strip_count, 3093251881Speter reverse, ignore_whitespace, remove_tempfiles, 3094251881Speter patch_func, patch_baton, ctx, scratch_pool), 3095251881Speter ctx->wc_ctx, wc_dir_abspath, FALSE /* lock_anchor */, scratch_pool); 3096251881Speter return SVN_NO_ERROR; 3097251881Speter} 3098