2 * patch.c: patch application support
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
24 /* ==================================================================== */
31 #include <apr_fnmatch.h>
32 #include "svn_client.h"
33 #include "svn_dirent_uri.h"
38 #include "svn_pools.h"
39 #include "svn_props.h"
40 #include "svn_sorts.h"
41 #include "svn_subst.h"
45 #include "svn_private_config.h"
46 #include "private/svn_eol_private.h"
47 #include "private/svn_wc_private.h"
48 #include "private/svn_dep_compat.h"
49 #include "private/svn_string_private.h"
50 #include "private/svn_subr_private.h"
51 #include "private/svn_sorts_private.h"
53 typedef struct hunk_info_t {
55 svn_diff_hunk_t *hunk;
57 /* The line where the hunk matched in the target file. */
58 svn_linenum_t matched_line;
60 /* Whether this hunk has been rejected. */
61 svn_boolean_t rejected;
63 /* Whether this hunk has already been applied (either manually
64 * or by an earlier run of patch). */
65 svn_boolean_t already_applied;
67 /* The fuzz factor used when matching this hunk, i.e. how many
68 * lines of leading and trailing context to ignore during matching. */
72 /* A struct carrying information related to the patched and unpatched
73 * content of a target, be it a property or the text of a file. */
74 typedef struct target_content_t {
75 /* Indicates whether unpatched content existed prior to patching. */
76 svn_boolean_t existed;
78 /* The line last read from the unpatched content. */
79 svn_linenum_t current_line;
81 /* The EOL-style of the unpatched content. Either 'none', 'fixed',
82 * or 'native'. See the documentation of svn_subst_eol_style_t. */
83 svn_subst_eol_style_t eol_style;
85 /* If the EOL_STYLE above is not 'none', this is the EOL string
86 * corresponding to the EOL-style. Else, it is the EOL string the
87 * last line read from the target file was using. */
90 /* An array containing apr_off_t offsets marking the beginning of
91 * each line in the unpatched content. */
92 apr_array_header_t *lines;
94 /* An array containing hunk_info_t structures for hunks already matched. */
95 apr_array_header_t *hunks;
97 /* True if end-of-file was reached while reading from the unpatched
101 /* The keywords of the target. They will be contracted when reading
102 * unpatched content and expanded when writing patched content.
103 * When patching properties this hash is always empty. */
104 apr_hash_t *keywords;
106 /* A callback, with an associated baton, to read a line of unpatched
108 svn_error_t *(*readline)(void *baton, svn_stringbuf_t **line,
109 const char **eol_str, svn_boolean_t *eof,
110 apr_pool_t *result_pool, apr_pool_t *scratch_pool);
113 /* A callback to get the current byte offset within the unpatched
114 * content. Uses the read baton. */
115 svn_error_t * (*tell)(void *baton, apr_off_t *offset,
116 apr_pool_t *scratch_pool);
118 /* A callback to seek to an offset within the unpatched content.
119 * Uses the read baton. */
120 svn_error_t * (*seek)(void *baton, apr_off_t offset,
121 apr_pool_t *scratch_pool);
123 /* A callback to write data to the patched content, with an
124 * associated baton. */
125 svn_error_t * (*write)(void *baton, const char *buf, apr_size_t len,
126 apr_pool_t *scratch_pool);
131 typedef struct prop_patch_target_t {
133 /* The name of the property */
136 /* The property value. This is NULL in case the property did not exist
137 * prior to patch application (see also CONTENT->existed).
138 * Note that the patch implementation does not support binary properties,
139 * so this string is not expected to contain embedded NUL characters. */
140 const svn_string_t *value;
142 /* The patched property value.
143 * This is equivalent to the target, except that in appropriate
144 * places it contains the modified text as it appears in the patch file. */
145 svn_stringbuf_t *patched_value;
147 /* All information that is specific to the content of the property. */
148 target_content_t *content;
150 /* Represents the operation performed on the property. It can be added,
151 * deleted or modified.
152 * ### Should we use flags instead since we're not using all enum values? */
153 svn_diff_operation_kind_t operation;
155 /* ### Here we'll add flags telling if the prop was added, deleted,
156 * ### had_rejects, had_local_mods prior to patching and so on. */
157 } prop_patch_target_t;
159 typedef struct patch_target_t {
160 /* The target path as it appeared in the patch file,
161 * but in canonicalised form. */
162 const char *canon_path_from_patchfile;
164 /* The target path, relative to the working copy directory the
165 * patch is being applied to. A patch strip count applies to this
166 * and only this path. This is never NULL. */
167 const char *local_relpath;
169 /* The absolute path of the target on the filesystem.
170 * Any symlinks the path from the patch file may contain are resolved.
171 * Is not always known, so it may be NULL. */
172 const char *local_abspath;
174 /* The target file, read-only. This is NULL in case the target
175 * file did not exist prior to patch application (see also
176 * CONTENT->existed). */
179 /* The target file is a symlink */
180 svn_boolean_t is_symlink;
183 * This is equivalent to the target, except that in appropriate
184 * places it contains the modified text as it appears in the patch file.
185 * The data in this file is written in repository-normal form.
186 * EOL transformation and keyword contraction is performed when the
187 * patched result is installed in the working copy. */
188 apr_file_t *patched_file;
190 /* Path to the patched file. */
191 const char *patched_path;
193 /* Hunks that are rejected will be written to this file. */
194 apr_file_t *reject_file;
196 /* Path to the reject file. */
197 const char *reject_path;
199 /* The node kind of the target as found in WC-DB prior
200 * to patch application. */
201 svn_node_kind_t db_kind;
203 /* The target's kind on disk prior to patch application. */
204 svn_node_kind_t kind_on_disk;
206 /* True if the target was locally deleted prior to patching. */
207 svn_boolean_t locally_deleted;
209 /* True if the target had to be skipped for some reason. */
210 svn_boolean_t skipped;
212 /* True if the target has been filtered by the patch callback. */
213 svn_boolean_t filtered;
215 /* True if at least one hunk was rejected. */
216 svn_boolean_t had_rejects;
218 /* True if at least one property hunk was rejected. */
219 svn_boolean_t had_prop_rejects;
221 /* True if the target file had local modifications before the
222 * patch was applied to it. */
223 svn_boolean_t local_mods;
225 /* True if the target was added by the patch, which means that it did
226 * not exist on disk before patching and has content after patching. */
229 /* True if the target ended up being deleted by the patch. */
230 svn_boolean_t deleted;
232 /* True if the target ended up being replaced by the patch
233 * (i.e. a new file was added on top locally deleted node). */
234 svn_boolean_t replaced;
236 /* Set if the target is supposed to be moved by the patch.
237 * This applies to --git diffs which carry "rename from/to" headers. */
238 const char *move_target_abspath;
240 /* True if the target has the executable bit set. */
241 svn_boolean_t executable;
243 /* True if the patch changed the text of the target. */
244 svn_boolean_t has_text_changes;
246 /* True if the patch changed any of the properties of the target. */
247 svn_boolean_t has_prop_changes;
249 /* True if the patch contained a svn:special property. */
250 svn_boolean_t is_special;
252 /* All the information that is specific to the content of the target. */
253 target_content_t *content;
255 /* A hash table of prop_patch_target_t objects keyed by property names. */
256 apr_hash_t *prop_targets;
261 /* A smaller struct containing a subset of patch_target_t.
262 * Carries the minimal amount of information we still need for a
263 * target after we're done patching it so we can free other resources. */
264 typedef struct patch_target_info_t {
265 const char *local_abspath;
266 svn_boolean_t deleted;
267 } patch_target_info_t;
270 /* Strip STRIP_COUNT components from the front of PATH, returning
271 * the result in *RESULT, allocated in RESULT_POOL.
272 * Do temporary allocations in SCRATCH_POOL. */
274 strip_path(const char **result, const char *path, int strip_count,
275 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
278 apr_array_header_t *components;
279 apr_array_header_t *stripped;
281 components = svn_path_decompose(path, scratch_pool);
282 if (strip_count > components->nelts)
283 return svn_error_createf(SVN_ERR_CLIENT_PATCH_BAD_STRIP_COUNT, NULL,
284 _("Cannot strip %u components from '%s'"),
286 svn_dirent_local_style(path, scratch_pool));
288 stripped = apr_array_make(scratch_pool, components->nelts - strip_count,
289 sizeof(const char *));
290 for (i = strip_count; i < components->nelts; i++)
292 const char *component;
294 component = APR_ARRAY_IDX(components, i, const char *);
295 APR_ARRAY_PUSH(stripped, const char *) = component;
298 *result = svn_path_compose(stripped, result_pool);
303 /* Obtain KEYWORDS, EOL_STYLE and EOL_STR for LOCAL_ABSPATH.
304 * WC_CTX is a context for the working copy the patch is applied to.
305 * Use RESULT_POOL for allocations of fields in TARGET.
306 * Use SCRATCH_POOL for all other allocations. */
308 obtain_eol_and_keywords_for_file(apr_hash_t **keywords,
309 svn_subst_eol_style_t *eol_style,
310 const char **eol_str,
311 svn_wc_context_t *wc_ctx,
312 const char *local_abspath,
313 apr_pool_t *result_pool,
314 apr_pool_t *scratch_pool)
317 svn_string_t *keywords_val, *eol_style_val;
319 SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, local_abspath,
320 scratch_pool, scratch_pool));
321 keywords_val = svn_hash_gets(props, SVN_PROP_KEYWORDS);
324 svn_revnum_t changed_rev;
325 apr_time_t changed_date;
329 const char *repos_root_url;
330 const char *repos_relpath;
332 SVN_ERR(svn_wc__node_get_changed_info(&changed_rev,
338 rev_str = apr_psprintf(scratch_pool, "%ld", changed_rev);
339 SVN_ERR(svn_wc__node_get_repos_info(NULL, &repos_relpath, &repos_root_url,
341 wc_ctx, local_abspath,
342 scratch_pool, scratch_pool));
343 url = svn_path_url_add_component2(repos_root_url, repos_relpath,
346 SVN_ERR(svn_subst_build_keywords3(keywords,
348 rev_str, url, repos_root_url,
350 author, result_pool));
353 eol_style_val = svn_hash_gets(props, SVN_PROP_EOL_STYLE);
356 svn_subst_eol_style_from_value(eol_style,
358 eol_style_val->data);
364 /* Resolve the exact path for a patch TARGET at path PATH_FROM_PATCHFILE,
365 * which is the path of the target as it appeared in the patch file.
366 * Put a canonicalized version of PATH_FROM_PATCHFILE into
367 * TARGET->CANON_PATH_FROM_PATCHFILE.
368 * WC_CTX is a context for the working copy the patch is applied to.
369 * If possible, determine TARGET->WC_PATH, TARGET->ABS_PATH, TARGET->KIND,
370 * TARGET->ADDED, and TARGET->PARENT_DIR_EXISTS.
371 * Indicate in TARGET->SKIPPED whether the target should be skipped.
372 * STRIP_COUNT specifies the number of leading path components
373 * which should be stripped from target paths in the patch.
374 * PROP_CHANGES_ONLY specifies whether the target path is allowed to have
375 * only property changes, and no content changes (in which case the target
376 * must be a directory).
377 * Use RESULT_POOL for allocations of fields in TARGET.
378 * Use SCRATCH_POOL for all other allocations. */
380 resolve_target_path(patch_target_t *target,
381 const char *path_from_patchfile,
382 const char *wcroot_abspath,
384 svn_boolean_t prop_changes_only,
385 svn_wc_context_t *wc_ctx,
386 apr_pool_t *result_pool,
387 apr_pool_t *scratch_pool)
389 const char *stripped_path;
390 svn_wc_status3_t *status;
392 svn_boolean_t under_root;
394 target->canon_path_from_patchfile = svn_dirent_internal_style(
395 path_from_patchfile, result_pool);
397 /* We allow properties to be set on the wc root dir. */
398 if (! prop_changes_only && target->canon_path_from_patchfile[0] == '\0')
400 /* An empty patch target path? What gives? Skip this. */
401 target->skipped = TRUE;
402 target->local_abspath = NULL;
403 target->local_relpath = "";
408 SVN_ERR(strip_path(&stripped_path, target->canon_path_from_patchfile,
409 strip_count, result_pool, scratch_pool));
411 stripped_path = target->canon_path_from_patchfile;
413 if (svn_dirent_is_absolute(stripped_path))
415 target->local_relpath = svn_dirent_is_child(wcroot_abspath,
419 if (! target->local_relpath)
421 /* The target path is either outside of the working copy
422 * or it is the working copy itself. Skip it. */
423 target->skipped = TRUE;
424 target->local_abspath = NULL;
425 target->local_relpath = stripped_path;
431 target->local_relpath = stripped_path;
434 /* Make sure the path is secure to use. We want the target to be inside
435 * of the working copy and not be fooled by symlinks it might contain. */
436 SVN_ERR(svn_dirent_is_under_root(&under_root,
437 &target->local_abspath, wcroot_abspath,
438 target->local_relpath, result_pool));
442 /* The target path is outside of the working copy. Skip it. */
443 target->skipped = TRUE;
444 target->local_abspath = NULL;
448 /* Skip things we should not be messing with. */
449 err = svn_wc_status3(&status, wc_ctx, target->local_abspath,
450 result_pool, scratch_pool);
453 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
454 return svn_error_trace(err);
456 svn_error_clear(err);
458 target->locally_deleted = TRUE;
459 target->db_kind = svn_node_none;
462 else if (status->node_status == svn_wc_status_ignored ||
463 status->node_status == svn_wc_status_unversioned ||
464 status->node_status == svn_wc_status_missing ||
465 status->node_status == svn_wc_status_obstructed ||
468 target->skipped = TRUE;
471 else if (status->node_status == svn_wc_status_deleted)
473 target->locally_deleted = TRUE;
476 if (status && (status->kind != svn_node_unknown))
477 target->db_kind = status->kind;
479 target->db_kind = svn_node_none;
481 SVN_ERR(svn_io_check_special_path(target->local_abspath,
482 &target->kind_on_disk, &target->is_symlink,
485 if (target->locally_deleted)
487 const char *moved_to_abspath;
489 SVN_ERR(svn_wc__node_was_moved_away(&moved_to_abspath, NULL,
490 wc_ctx, target->local_abspath,
491 result_pool, scratch_pool));
492 if (moved_to_abspath)
494 target->local_abspath = moved_to_abspath;
495 target->local_relpath = svn_dirent_skip_ancestor(wcroot_abspath,
497 SVN_ERR_ASSERT(target->local_relpath &&
498 target->local_relpath[0] != '\0');
500 /* As far as we are concerned this target is not locally deleted. */
501 target->locally_deleted = FALSE;
503 SVN_ERR(svn_io_check_special_path(target->local_abspath,
504 &target->kind_on_disk,
508 else if (target->kind_on_disk != svn_node_none)
510 target->skipped = TRUE;
518 /* Baton for reading from properties. */
519 typedef struct prop_read_baton_t {
520 const svn_string_t *value;
524 /* Allocate *STRINGBUF in RESULT_POOL, and read into it one line from
525 * the unpatched property value accessed via BATON.
526 * Reading stops either after a line-terminator was found, or if
527 * the property value runs out in which case *EOF is set to TRUE.
528 * The line-terminator is not stored in *STRINGBUF.
530 * If the line is empty or could not be read, *line is set to NULL.
532 * The line-terminator is detected automatically and stored in *EOL
533 * if EOL is not NULL. If the end of the property value is reached
534 * and does not end with a newline character, and EOL is not NULL,
535 * *EOL is set to NULL.
537 * SCRATCH_POOL is used for temporary allocations.
540 readline_prop(void *baton, svn_stringbuf_t **line, const char **eol_str,
541 svn_boolean_t *eof, apr_pool_t *result_pool,
542 apr_pool_t *scratch_pool)
544 prop_read_baton_t *b = (prop_read_baton_t *)baton;
545 svn_stringbuf_t *str = NULL;
547 svn_boolean_t found_eof;
549 if ((apr_uint64_t)b->offset >= (apr_uint64_t)b->value->len)
557 /* Read bytes into STR up to and including, but not storing,
558 * the next EOL sequence. */
563 c = b->value->data + b->offset;
578 if (*(c + 1) == '\n')
587 str = svn_stringbuf_create_ensure(80, result_pool);
588 svn_stringbuf_appendbyte(str, *c);
594 while (c < b->value->data + b->value->len);
603 /* Return in *OFFSET the current byte offset for reading from the
604 * unpatched property value accessed via BATON.
605 * Use SCRATCH_POOL for temporary allocations. */
607 tell_prop(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool)
609 prop_read_baton_t *b = (prop_read_baton_t *)baton;
614 /* Seek to the specified by OFFSET in the unpatched property value accessed
615 * via BATON. Use SCRATCH_POOL for temporary allocations. */
617 seek_prop(void *baton, apr_off_t offset, apr_pool_t *scratch_pool)
619 prop_read_baton_t *b = (prop_read_baton_t *)baton;
624 /* Write LEN bytes from BUF into the patched property value accessed
625 * via BATON. Use SCRATCH_POOL for temporary allocations. */
627 write_prop(void *baton, const char *buf, apr_size_t len,
628 apr_pool_t *scratch_pool)
630 svn_stringbuf_t *patched_value = (svn_stringbuf_t *)baton;
631 svn_stringbuf_appendbytes(patched_value, buf, len);
635 /* Initialize a PROP_TARGET structure for PROP_NAME on the patch target
636 * at LOCAL_ABSPATH. OPERATION indicates the operation performed on the
637 * property. Use working copy context WC_CTX.
638 * Allocate results in RESULT_POOL.
639 * Use SCRATCH_POOL for temporary allocations. */
641 init_prop_target(prop_patch_target_t **prop_target,
642 const char *prop_name,
643 svn_diff_operation_kind_t operation,
644 svn_wc_context_t *wc_ctx,
645 const char *local_abspath,
646 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
648 prop_patch_target_t *new_prop_target;
649 target_content_t *content;
650 const svn_string_t *value;
652 prop_read_baton_t *prop_read_baton;
654 content = apr_pcalloc(result_pool, sizeof(*content));
656 /* All other fields are FALSE or NULL due to apr_pcalloc(). */
657 content->current_line = 1;
658 content->eol_style = svn_subst_eol_style_none;
659 content->lines = apr_array_make(result_pool, 0, sizeof(apr_off_t));
660 content->hunks = apr_array_make(result_pool, 0, sizeof(hunk_info_t *));
661 content->keywords = apr_hash_make(result_pool);
663 new_prop_target = apr_pcalloc(result_pool, sizeof(*new_prop_target));
664 new_prop_target->name = apr_pstrdup(result_pool, prop_name);
665 new_prop_target->operation = operation;
666 new_prop_target->content = content;
668 err = svn_wc_prop_get2(&value, wc_ctx, local_abspath, prop_name,
669 result_pool, scratch_pool);
672 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
674 svn_error_clear(err);
678 return svn_error_trace(err);
680 content->existed = (value != NULL);
681 new_prop_target->value = value;
682 new_prop_target->patched_value = svn_stringbuf_create_empty(result_pool);
685 /* Wire up the read and write callbacks. */
686 prop_read_baton = apr_pcalloc(result_pool, sizeof(*prop_read_baton));
687 prop_read_baton->value = value;
688 prop_read_baton->offset = 0;
689 content->readline = readline_prop;
690 content->tell = tell_prop;
691 content->seek = seek_prop;
692 content->read_baton = prop_read_baton;
693 content->write = write_prop;
694 content->write_baton = new_prop_target->patched_value;
696 *prop_target = new_prop_target;
701 /* Allocate *STRINGBUF in RESULT_POOL, and read into it one line from
702 * the unpatched file content accessed via BATON.
703 * Reading stops either after a line-terminator was found,
704 * or if EOF is reached in which case *EOF is set to TRUE.
705 * The line-terminator is not stored in *STRINGBUF.
707 * If the line is empty or could not be read, *line is set to NULL.
709 * The line-terminator is detected automatically and stored in *EOL
710 * if EOL is not NULL. If EOF is reached and FILE does not end
711 * with a newline character, and EOL is not NULL, *EOL is set to NULL.
713 * SCRATCH_POOL is used for temporary allocations.
716 readline_file(void *baton, svn_stringbuf_t **line, const char **eol_str,
717 svn_boolean_t *eof, apr_pool_t *result_pool,
718 apr_pool_t *scratch_pool)
720 apr_file_t *file = (apr_file_t *)baton;
721 svn_stringbuf_t *str = NULL;
724 svn_boolean_t found_eof;
726 /* Read bytes into STR up to and including, but not storing,
727 * the next EOL sequence. */
733 SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes,
734 &found_eof, scratch_pool));
753 /* Check for "\r\n" by peeking at the next byte. */
755 SVN_ERR(svn_io_file_seek(file, APR_CUR, &pos, scratch_pool));
756 SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes,
757 &found_eof, scratch_pool));
758 if (numbytes == 1 && c == '\n')
764 /* Pretend we never peeked. */
765 SVN_ERR(svn_io_file_seek(file, APR_SET, &pos, scratch_pool));
774 str = svn_stringbuf_create_ensure(80, result_pool);
775 svn_stringbuf_appendbyte(str, c);
789 /* Return in *OFFSET the current byte offset for reading from the
790 * unpatched file content accessed via BATON.
791 * Use SCRATCH_POOL for temporary allocations. */
793 tell_file(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool)
795 apr_file_t *file = (apr_file_t *)baton;
797 SVN_ERR(svn_io_file_seek(file, APR_CUR, offset, scratch_pool));
801 /* Seek to the specified by OFFSET in the unpatched file content accessed
802 * via BATON. Use SCRATCH_POOL for temporary allocations. */
804 seek_file(void *baton, apr_off_t offset, apr_pool_t *scratch_pool)
806 apr_file_t *file = (apr_file_t *)baton;
807 SVN_ERR(svn_io_file_seek(file, APR_SET, &offset, scratch_pool));
811 /* Write LEN bytes from BUF into the patched file content accessed
812 * via BATON. Use SCRATCH_POOL for temporary allocations. */
814 write_file(void *baton, const char *buf, apr_size_t len,
815 apr_pool_t *scratch_pool)
817 apr_file_t *file = (apr_file_t *)baton;
818 SVN_ERR(svn_io_file_write_full(file, buf, len, &len, scratch_pool));
822 /* Handling symbolic links:
824 * In Subversion, symlinks can be represented on disk in two distinct ways.
825 * On systems which support symlinks, a symlink is created on disk.
826 * On systems which do not support symlink, a file is created on disk
827 * which contains the "normal form" of the symlink, which looks like:
829 * where TARGET is the file the symlink points to.
831 * When reading symlinks (i.e. the link itself, not the file the symlink
832 * is pointing to) through the svn_subst_create_specialfile() function
833 * into a buffer, the buffer always contains the "normal form" of the symlink.
834 * Due to this representation symlinks always contain a single line of text.
836 * The functions below are needed to deal with the case where a patch
837 * wants to change the TARGET that a symlink points to.
840 /* Baton for the (readline|tell|seek|write)_symlink functions. */
841 struct symlink_baton_t
843 /* The path to the symlink on disk (not the path to the target of the link) */
844 const char *local_abspath;
846 /* Indicates whether the "normal form" of the symlink has been read. */
847 svn_boolean_t at_eof;
850 /* Allocate *STRINGBUF in RESULT_POOL, and store into it the "normal form"
851 * of the symlink accessed via BATON.
853 * Otherwise behaves like readline_file(), which see.
856 readline_symlink(void *baton, svn_stringbuf_t **line, const char **eol_str,
857 svn_boolean_t *eof, apr_pool_t *result_pool,
858 apr_pool_t *scratch_pool)
860 struct symlink_baton_t *sb = baton;
875 SVN_ERR(svn_io_read_link(&dest, sb->local_abspath, scratch_pool));
876 *line = svn_stringbuf_createf(result_pool, "link %s", dest->data);
883 /* Set *OFFSET to 1 or 0 depending on whether the "normal form" of
884 * the symlink has already been read. */
886 tell_symlink(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool)
888 struct symlink_baton_t *sb = baton;
890 *offset = sb->at_eof ? 1 : 0;
894 /* If offset is non-zero, mark the symlink as having been read in its
895 * "normal form". Else, mark the symlink as not having been read yet. */
897 seek_symlink(void *baton, apr_off_t offset, apr_pool_t *scratch_pool)
899 struct symlink_baton_t *sb = baton;
901 sb->at_eof = (offset != 0);
906 /* Set the target of the symlink accessed via BATON.
907 * The contents of BUF must be a valid "normal form" of a symlink. */
909 write_symlink(void *baton, const char *buf, apr_size_t len,
910 apr_pool_t *scratch_pool)
912 const char *target_abspath = baton;
913 const char *new_name;
914 const char *link = apr_pstrndup(scratch_pool, buf, len);
916 if (strncmp(link, "link ", 5) != 0)
917 return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL,
918 _("Invalid link representation"));
920 link += 5; /* Skip "link " */
922 /* We assume the entire symlink is written at once, as the patch
923 format is line based */
925 SVN_ERR(svn_io_create_unique_link(&new_name, target_abspath, link,
926 ".tmp", scratch_pool));
928 SVN_ERR(svn_io_file_rename(new_name, target_abspath, scratch_pool));
934 /* Return a suitable filename for the target of PATCH.
935 * Examine the ``old'' and ``new'' file names, and choose the file name
936 * with the fewest path components, the shortest basename, and the shortest
937 * total file name length (in that order). In case of a tie, return the new
938 * filename. This heuristic is also used by Larry Wall's UNIX patch (except
939 * that it prompts for a filename in case of a tie).
940 * Additionally, for compatibility with git, if one of the filenames
941 * is "/dev/null", use the other filename. */
943 choose_target_filename(const svn_patch_t *patch)
948 if (strcmp(patch->old_filename, "/dev/null") == 0)
949 return patch->new_filename;
950 if (strcmp(patch->new_filename, "/dev/null") == 0)
951 return patch->old_filename;
953 /* If the patch renames the target, use the old name while
954 * applying hunks. The target will be renamed to the new name
955 * after hunks have been applied. */
956 if (patch->operation == svn_diff_op_moved)
957 return patch->old_filename;
959 old = svn_path_component_count(patch->old_filename);
960 new = svn_path_component_count(patch->new_filename);
964 old = strlen(svn_dirent_basename(patch->old_filename, NULL));
965 new = strlen(svn_dirent_basename(patch->new_filename, NULL));
969 old = strlen(patch->old_filename);
970 new = strlen(patch->new_filename);
974 return (old < new) ? patch->old_filename : patch->new_filename;
977 /* Attempt to initialize a *PATCH_TARGET structure for a target file
978 * described by PATCH. Use working copy context WC_CTX.
979 * STRIP_COUNT specifies the number of leading path components
980 * which should be stripped from target paths in the patch.
981 * The patch target structure is allocated in RESULT_POOL, but if the target
982 * should be skipped, PATCH_TARGET->SKIPPED is set and the target should be
983 * treated as not fully initialized, e.g. the caller should not not do any
984 * further operations on the target if it is marked to be skipped.
985 * If REMOVE_TEMPFILES is TRUE, set up temporary files to be removed as
986 * soon as they are no longer needed.
987 * Use SCRATCH_POOL for all other allocations. */
989 init_patch_target(patch_target_t **patch_target,
990 const svn_patch_t *patch,
991 const char *wcroot_abspath,
992 svn_wc_context_t *wc_ctx, int strip_count,
993 svn_boolean_t remove_tempfiles,
994 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
996 patch_target_t *target;
997 target_content_t *content;
998 svn_boolean_t has_prop_changes = FALSE;
999 svn_boolean_t prop_changes_only = FALSE;
1002 apr_hash_index_t *hi;
1004 for (hi = apr_hash_first(scratch_pool, patch->prop_patches);
1006 hi = apr_hash_next(hi))
1008 svn_prop_patch_t *prop_patch = apr_hash_this_val(hi);
1009 if (! has_prop_changes)
1010 has_prop_changes = prop_patch->hunks->nelts > 0;
1016 prop_changes_only = has_prop_changes && patch->hunks->nelts == 0;
1018 content = apr_pcalloc(result_pool, sizeof(*content));
1020 /* All other fields in content are FALSE or NULL due to apr_pcalloc().*/
1021 content->current_line = 1;
1022 content->eol_style = svn_subst_eol_style_none;
1023 content->lines = apr_array_make(result_pool, 0, sizeof(apr_off_t));
1024 content->hunks = apr_array_make(result_pool, 0, sizeof(hunk_info_t *));
1025 content->keywords = apr_hash_make(result_pool);
1027 target = apr_pcalloc(result_pool, sizeof(*target));
1029 /* All other fields in target are FALSE or NULL due to apr_pcalloc(). */
1030 target->db_kind = svn_node_none;
1031 target->kind_on_disk = svn_node_none;
1032 target->content = content;
1033 target->prop_targets = apr_hash_make(result_pool);
1035 SVN_ERR(resolve_target_path(target, choose_target_filename(patch),
1036 wcroot_abspath, strip_count, prop_changes_only,
1037 wc_ctx, result_pool, scratch_pool));
1038 *patch_target = target;
1039 if (! target->skipped)
1041 const char *diff_header;
1044 /* Create a temporary file to write the patched result to.
1045 * Also grab various bits of information about the file. */
1046 if (target->is_symlink)
1048 struct symlink_baton_t *sb = apr_pcalloc(result_pool, sizeof(*sb));
1049 content->existed = TRUE;
1051 sb->local_abspath = target->local_abspath;
1053 /* Wire up the read callbacks. */
1054 content->read_baton = sb;
1056 content->readline = readline_symlink;
1057 content->seek = seek_symlink;
1058 content->tell = tell_symlink;
1060 else if (target->kind_on_disk == svn_node_file)
1062 SVN_ERR(svn_io_file_open(&target->file, target->local_abspath,
1063 APR_READ | APR_BUFFERED,
1064 APR_OS_DEFAULT, result_pool));
1065 SVN_ERR(svn_wc_text_modified_p2(&target->local_mods, wc_ctx,
1066 target->local_abspath, FALSE,
1068 SVN_ERR(svn_io_is_file_executable(&target->executable,
1069 target->local_abspath,
1071 SVN_ERR(obtain_eol_and_keywords_for_file(&content->keywords,
1072 &content->eol_style,
1075 target->local_abspath,
1078 content->existed = TRUE;
1080 /* Wire up the read callbacks. */
1081 content->readline = readline_file;
1082 content->seek = seek_file;
1083 content->tell = tell_file;
1084 content->read_baton = target->file;
1087 /* ### Is it ok to set the operation of the target already here? Isn't
1088 * ### the target supposed to be marked with an operation after we have
1089 * ### determined that the changes will apply cleanly to the WC? Maybe
1090 * ### we should have kept the patch field in patch_target_t to be
1091 * ### able to distinguish between 'what the patch says we should do'
1092 * ### and 'what we can do with the given state of our WC'. */
1093 if (patch->operation == svn_diff_op_added)
1094 target->added = TRUE;
1095 else if (patch->operation == svn_diff_op_deleted)
1096 target->deleted = TRUE;
1097 else if (patch->operation == svn_diff_op_moved)
1099 const char *move_target_path;
1100 const char *move_target_relpath;
1101 svn_boolean_t under_root;
1102 svn_node_kind_t kind_on_disk;
1103 svn_node_kind_t wc_kind;
1105 move_target_path = svn_dirent_internal_style(patch->new_filename,
1108 if (strip_count > 0)
1109 SVN_ERR(strip_path(&move_target_path, move_target_path,
1110 strip_count, scratch_pool, scratch_pool));
1112 if (svn_dirent_is_absolute(move_target_path))
1114 move_target_relpath = svn_dirent_is_child(wcroot_abspath,
1117 if (! move_target_relpath)
1119 /* The move target path is either outside of the working
1120 * copy or it is the working copy itself. Skip it. */
1121 target->skipped = TRUE;
1122 target->local_abspath = NULL;
1123 return SVN_NO_ERROR;
1127 move_target_relpath = move_target_path;
1129 /* Make sure the move target path is secure to use. */
1130 SVN_ERR(svn_dirent_is_under_root(&under_root,
1131 &target->move_target_abspath,
1133 move_target_relpath, result_pool));
1136 /* The target path is outside of the working copy. Skip it. */
1137 target->skipped = TRUE;
1138 target->local_abspath = NULL;
1139 return SVN_NO_ERROR;
1142 SVN_ERR(svn_io_check_path(target->move_target_abspath,
1143 &kind_on_disk, scratch_pool));
1144 SVN_ERR(svn_wc_read_kind2(&wc_kind, wc_ctx,
1145 target->move_target_abspath,
1146 FALSE, FALSE, scratch_pool));
1147 if (kind_on_disk != svn_node_none || wc_kind != svn_node_none)
1149 /* The move target path already exists on disk. Skip target. */
1150 target->skipped = TRUE;
1151 target->move_target_abspath = NULL;
1152 return SVN_NO_ERROR;
1156 if (! target->is_symlink)
1158 /* Open a temporary file to write the patched result to. */
1159 SVN_ERR(svn_io_open_unique_file3(&target->patched_file,
1160 &target->patched_path, NULL,
1162 svn_io_file_del_on_pool_cleanup :
1163 svn_io_file_del_none,
1164 result_pool, scratch_pool));
1166 /* Put the write callback in place. */
1167 content->write = write_file;
1168 content->write_baton = target->patched_file;
1172 /* Put the write callback in place. */
1173 SVN_ERR(svn_io_open_unique_file3(NULL,
1174 &target->patched_path, NULL,
1176 svn_io_file_del_on_pool_cleanup :
1177 svn_io_file_del_none,
1178 result_pool, scratch_pool));
1180 content->write_baton = (void*)target->patched_path;
1182 content->write = write_symlink;
1185 /* Open a temporary file to write rejected hunks to. */
1186 SVN_ERR(svn_io_open_unique_file3(&target->reject_file,
1187 &target->reject_path, NULL,
1189 svn_io_file_del_on_pool_cleanup :
1190 svn_io_file_del_none,
1191 result_pool, scratch_pool));
1193 /* The reject file needs a diff header. */
1194 diff_header = apr_psprintf(scratch_pool, "--- %s%s+++ %s%s",
1195 target->canon_path_from_patchfile,
1197 target->canon_path_from_patchfile,
1199 len = strlen(diff_header);
1200 SVN_ERR(svn_io_file_write_full(target->reject_file, diff_header, len,
1201 &len, scratch_pool));
1203 /* Handle properties. */
1204 if (! target->skipped)
1206 apr_hash_index_t *hi;
1208 for (hi = apr_hash_first(result_pool, patch->prop_patches);
1210 hi = apr_hash_next(hi))
1212 const char *prop_name = apr_hash_this_key(hi);
1213 svn_prop_patch_t *prop_patch = apr_hash_this_val(hi);
1214 prop_patch_target_t *prop_target;
1216 SVN_ERR(init_prop_target(&prop_target,
1218 prop_patch->operation,
1219 wc_ctx, target->local_abspath,
1220 result_pool, scratch_pool));
1221 svn_hash_sets(target->prop_targets, prop_name, prop_target);
1226 return SVN_NO_ERROR;
1229 /* Read a *LINE from CONTENT. If the line has not been read before
1230 * mark the line in CONTENT->LINES.
1231 * If a line could be read successfully, increase CONTENT->CURRENT_LINE,
1232 * and allocate *LINE in RESULT_POOL.
1233 * Do temporary allocations in SCRATCH_POOL.
1235 static svn_error_t *
1236 readline(target_content_t *content,
1238 apr_pool_t *result_pool,
1239 apr_pool_t *scratch_pool)
1241 svn_stringbuf_t *line_raw;
1242 const char *eol_str;
1243 svn_linenum_t max_line = (svn_linenum_t)content->lines->nelts + 1;
1245 if (content->eof || content->readline == NULL)
1248 return SVN_NO_ERROR;
1251 SVN_ERR_ASSERT(content->current_line <= max_line);
1252 if (content->current_line == max_line)
1256 SVN_ERR(content->tell(content->read_baton, &offset,
1258 APR_ARRAY_PUSH(content->lines, apr_off_t) = offset;
1261 SVN_ERR(content->readline(content->read_baton, &line_raw,
1262 &eol_str, &content->eof,
1263 result_pool, scratch_pool));
1264 if (content->eol_style == svn_subst_eol_style_none)
1265 content->eol_str = eol_str;
1269 /* Contract keywords. */
1270 SVN_ERR(svn_subst_translate_cstring2(line_raw->data, line,
1272 content->keywords, FALSE,
1278 if ((line_raw && line_raw->len > 0) || eol_str)
1279 content->current_line++;
1281 SVN_ERR_ASSERT(content->current_line > 0);
1283 return SVN_NO_ERROR;
1286 /* Seek to the specified LINE in CONTENT.
1287 * Mark any lines not read before in CONTENT->LINES.
1288 * Do temporary allocations in SCRATCH_POOL.
1290 static svn_error_t *
1291 seek_to_line(target_content_t *content, svn_linenum_t line,
1292 apr_pool_t *scratch_pool)
1294 svn_linenum_t saved_line;
1295 svn_boolean_t saved_eof;
1297 SVN_ERR_ASSERT(line > 0);
1299 if (line == content->current_line)
1300 return SVN_NO_ERROR;
1302 saved_line = content->current_line;
1303 saved_eof = content->eof;
1305 if (line <= (svn_linenum_t)content->lines->nelts)
1309 offset = APR_ARRAY_IDX(content->lines, line - 1, apr_off_t);
1310 SVN_ERR(content->seek(content->read_baton, offset,
1312 content->current_line = line;
1317 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1319 while (! content->eof && content->current_line < line)
1321 svn_pool_clear(iterpool);
1322 SVN_ERR(readline(content, &dummy, iterpool, iterpool));
1324 svn_pool_destroy(iterpool);
1327 /* After seeking backwards from EOF position clear EOF indicator. */
1328 if (saved_eof && saved_line > content->current_line)
1329 content->eof = FALSE;
1331 return SVN_NO_ERROR;
1334 /* Indicate in *MATCHED whether the original text of HUNK matches the patch
1335 * CONTENT at its current line. Lines within FUZZ lines of the start or
1336 * end of HUNK will always match. If IGNORE_WHITESPACE is set, we ignore
1337 * whitespace when doing the matching. When this function returns, neither
1338 * CONTENT->CURRENT_LINE nor the file offset in the target file will
1339 * have changed. If MATCH_MODIFIED is TRUE, match the modified hunk text,
1340 * rather than the original hunk text.
1341 * Do temporary allocations in POOL. */
1342 static svn_error_t *
1343 match_hunk(svn_boolean_t *matched, target_content_t *content,
1344 svn_diff_hunk_t *hunk, svn_linenum_t fuzz,
1345 svn_boolean_t ignore_whitespace,
1346 svn_boolean_t match_modified, apr_pool_t *pool)
1348 svn_stringbuf_t *hunk_line;
1349 const char *target_line;
1350 svn_linenum_t lines_read;
1351 svn_linenum_t saved_line;
1352 svn_boolean_t hunk_eof;
1353 svn_boolean_t lines_matched;
1354 apr_pool_t *iterpool;
1355 svn_linenum_t hunk_length;
1356 svn_linenum_t leading_context;
1357 svn_linenum_t trailing_context;
1362 return SVN_NO_ERROR;
1364 saved_line = content->current_line;
1366 lines_matched = FALSE;
1367 leading_context = svn_diff_hunk_get_leading_context(hunk);
1368 trailing_context = svn_diff_hunk_get_trailing_context(hunk);
1371 svn_diff_hunk_reset_modified_text(hunk);
1372 hunk_length = svn_diff_hunk_get_modified_length(hunk);
1376 svn_diff_hunk_reset_original_text(hunk);
1377 hunk_length = svn_diff_hunk_get_original_length(hunk);
1379 iterpool = svn_pool_create(pool);
1382 const char *hunk_line_translated;
1384 svn_pool_clear(iterpool);
1387 SVN_ERR(svn_diff_hunk_readline_modified_text(hunk, &hunk_line,
1389 iterpool, iterpool));
1391 SVN_ERR(svn_diff_hunk_readline_original_text(hunk, &hunk_line,
1393 iterpool, iterpool));
1395 /* Contract keywords, if any, before matching. */
1396 SVN_ERR(svn_subst_translate_cstring2(hunk_line->data,
1397 &hunk_line_translated,
1399 content->keywords, FALSE,
1401 SVN_ERR(readline(content, &target_line, iterpool, iterpool));
1405 /* If the last line doesn't have a newline, we get EOF but still
1406 * have a non-empty line to compare. */
1407 if ((hunk_eof && hunk_line->len == 0) ||
1408 (content->eof && *target_line == 0))
1411 /* Leading/trailing fuzzy lines always match. */
1412 if ((lines_read <= fuzz && leading_context > fuzz) ||
1413 (lines_read > hunk_length - fuzz && trailing_context > fuzz))
1414 lines_matched = TRUE;
1417 if (ignore_whitespace)
1419 char *hunk_line_trimmed;
1420 char *target_line_trimmed;
1422 hunk_line_trimmed = apr_pstrdup(iterpool, hunk_line_translated);
1423 target_line_trimmed = apr_pstrdup(iterpool, target_line);
1424 apr_collapse_spaces(hunk_line_trimmed, hunk_line_trimmed);
1425 apr_collapse_spaces(target_line_trimmed, target_line_trimmed);
1426 lines_matched = ! strcmp(hunk_line_trimmed, target_line_trimmed);
1429 lines_matched = ! strcmp(hunk_line_translated, target_line);
1432 while (lines_matched);
1434 *matched = lines_matched && hunk_eof && hunk_line->len == 0;
1435 SVN_ERR(seek_to_line(content, saved_line, iterpool));
1436 svn_pool_destroy(iterpool);
1438 return SVN_NO_ERROR;
1441 /* Scan lines of CONTENT for a match of the original text of HUNK,
1442 * up to but not including the specified UPPER_LINE. Use fuzz factor FUZZ.
1443 * If UPPER_LINE is zero scan until EOF occurs when reading from TARGET.
1444 * Return the line at which HUNK was matched in *MATCHED_LINE.
1445 * If the hunk did not match at all, set *MATCHED_LINE to zero.
1446 * If the hunk matched multiple times, and MATCH_FIRST is TRUE,
1447 * return the line number at which the first match occurred in *MATCHED_LINE.
1448 * If the hunk matched multiple times, and MATCH_FIRST is FALSE,
1449 * return the line number at which the last match occurred in *MATCHED_LINE.
1450 * If IGNORE_WHITESPACE is set, ignore whitespace during the matching.
1451 * If MATCH_MODIFIED is TRUE, match the modified hunk text,
1452 * rather than the original hunk text.
1453 * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
1454 * Do all allocations in POOL. */
1455 static svn_error_t *
1456 scan_for_match(svn_linenum_t *matched_line,
1457 target_content_t *content,
1458 svn_diff_hunk_t *hunk, svn_boolean_t match_first,
1459 svn_linenum_t upper_line, svn_linenum_t fuzz,
1460 svn_boolean_t ignore_whitespace,
1461 svn_boolean_t match_modified,
1462 svn_cancel_func_t cancel_func, void *cancel_baton,
1465 apr_pool_t *iterpool;
1468 iterpool = svn_pool_create(pool);
1469 while ((content->current_line < upper_line || upper_line == 0) &&
1472 svn_boolean_t matched;
1474 svn_pool_clear(iterpool);
1477 SVN_ERR(cancel_func(cancel_baton));
1479 SVN_ERR(match_hunk(&matched, content, hunk, fuzz, ignore_whitespace,
1480 match_modified, iterpool));
1483 svn_boolean_t taken = FALSE;
1486 /* Don't allow hunks to match at overlapping locations. */
1487 for (i = 0; i < content->hunks->nelts; i++)
1489 const hunk_info_t *hi;
1490 svn_linenum_t length;
1492 hi = APR_ARRAY_IDX(content->hunks, i, const hunk_info_t *);
1495 length = svn_diff_hunk_get_modified_length(hi->hunk);
1497 length = svn_diff_hunk_get_original_length(hi->hunk);
1499 taken = (! hi->rejected &&
1500 content->current_line >= hi->matched_line &&
1501 content->current_line < (hi->matched_line + length));
1508 *matched_line = content->current_line;
1515 SVN_ERR(seek_to_line(content, content->current_line + 1,
1518 svn_pool_destroy(iterpool);
1520 return SVN_NO_ERROR;
1523 /* Indicate in *MATCH whether the content described by CONTENT
1524 * matches the modified text of HUNK.
1525 * Use SCRATCH_POOL for temporary allocations. */
1526 static svn_error_t *
1527 match_existing_target(svn_boolean_t *match,
1528 target_content_t *content,
1529 svn_diff_hunk_t *hunk,
1530 apr_pool_t *scratch_pool)
1532 svn_boolean_t lines_matched;
1533 apr_pool_t *iterpool;
1534 svn_boolean_t hunk_eof;
1535 svn_linenum_t saved_line;
1537 svn_diff_hunk_reset_modified_text(hunk);
1539 saved_line = content->current_line;
1541 iterpool = svn_pool_create(scratch_pool);
1545 svn_stringbuf_t *hunk_line;
1546 const char *line_translated;
1547 const char *hunk_line_translated;
1549 svn_pool_clear(iterpool);
1551 SVN_ERR(readline(content, &line, iterpool, iterpool));
1552 SVN_ERR(svn_diff_hunk_readline_modified_text(hunk, &hunk_line,
1554 iterpool, iterpool));
1555 /* Contract keywords. */
1556 SVN_ERR(svn_subst_translate_cstring2(line, &line_translated,
1560 SVN_ERR(svn_subst_translate_cstring2(hunk_line->data,
1561 &hunk_line_translated,
1565 lines_matched = ! strcmp(line_translated, hunk_line_translated);
1566 if (content->eof != hunk_eof)
1568 svn_pool_destroy(iterpool);
1570 return SVN_NO_ERROR;
1573 while (lines_matched && ! content->eof && ! hunk_eof);
1574 svn_pool_destroy(iterpool);
1576 *match = (lines_matched && content->eof == hunk_eof);
1577 SVN_ERR(seek_to_line(content, saved_line, scratch_pool));
1579 return SVN_NO_ERROR;
1582 /* Determine the line at which a HUNK applies to CONTENT of the TARGET
1583 * file, and return an appropriate hunk_info object in *HI, allocated from
1584 * RESULT_POOL. Use fuzz factor FUZZ. Set HI->FUZZ to FUZZ. If no correct
1585 * line can be determined, set HI->REJECTED to TRUE. PREVIOUS_OFFSET
1586 * is the offset at which the previous matching hunk was applied, or zero.
1587 * IGNORE_WHITESPACE tells whether whitespace should be considered when
1588 * matching. IS_PROP_HUNK indicates whether the hunk patches file content
1590 * When this function returns, neither CONTENT->CURRENT_LINE nor
1591 * the file offset in the target file will have changed.
1592 * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
1593 * Do temporary allocations in POOL. */
1594 static svn_error_t *
1595 get_hunk_info(hunk_info_t **hi, patch_target_t *target,
1596 target_content_t *content,
1597 svn_diff_hunk_t *hunk, svn_linenum_t fuzz,
1598 svn_linenum_t previous_offset,
1599 svn_boolean_t ignore_whitespace,
1600 svn_boolean_t is_prop_hunk,
1601 svn_cancel_func_t cancel_func, void *cancel_baton,
1602 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
1604 svn_linenum_t matched_line;
1605 svn_linenum_t original_start;
1606 svn_boolean_t already_applied;
1608 original_start = svn_diff_hunk_get_original_start(hunk) + previous_offset;
1609 already_applied = FALSE;
1611 /* An original offset of zero means that this hunk wants to create
1612 * a new file. Don't bother matching hunks in that case, since
1613 * the hunk applies at line 1. If the file already exists, the hunk
1614 * is rejected, unless the file is versioned and its content matches
1615 * the file the patch wants to create. */
1616 if (original_start == 0 && fuzz > 0)
1618 matched_line = 0; /* reject any fuzz for new files */
1620 else if (original_start == 0 && ! is_prop_hunk)
1622 if (target->kind_on_disk == svn_node_file)
1624 const svn_io_dirent2_t *dirent;
1625 SVN_ERR(svn_io_stat_dirent2(&dirent, target->local_abspath, FALSE,
1626 TRUE, scratch_pool, scratch_pool));
1628 if (dirent->kind == svn_node_file
1630 && dirent->filesize == 0)
1632 matched_line = 1; /* Matched an on-disk empty file */
1636 if (target->db_kind == svn_node_file)
1638 svn_boolean_t file_matches;
1640 /* ### I can't reproduce anything but a no-match here.
1641 The content is already at eof, so any hunk fails */
1642 SVN_ERR(match_existing_target(&file_matches, content, hunk,
1647 already_applied = TRUE;
1650 matched_line = 0; /* reject */
1653 matched_line = 0; /* reject */
1659 /* Same conditions apply as for the file case above.
1661 * ### Since the hunk says the prop should be added we just assume so for
1662 * ### now and don't bother with storing the previous lines and such. When
1663 * ### we have the diff operation available we can just check for adds. */
1664 else if (original_start == 0 && is_prop_hunk)
1666 if (content->existed)
1668 svn_boolean_t prop_matches;
1670 SVN_ERR(match_existing_target(&prop_matches, content, hunk,
1676 already_applied = TRUE;
1679 matched_line = 0; /* reject */
1684 else if (original_start > 0 && content->existed)
1686 svn_linenum_t saved_line = content->current_line;
1688 /* Scan for a match at the line where the hunk thinks it
1689 * should be going. */
1690 SVN_ERR(seek_to_line(content, original_start, scratch_pool));
1691 if (content->current_line != original_start)
1697 SVN_ERR(scan_for_match(&matched_line, content, hunk, TRUE,
1698 original_start + 1, fuzz,
1699 ignore_whitespace, FALSE,
1700 cancel_func, cancel_baton,
1703 if (matched_line != original_start)
1705 /* Check if the hunk is already applied.
1706 * We only check for an exact match here, and don't bother checking
1707 * for already applied patches with offset/fuzz, because such a
1708 * check would be ambiguous. */
1711 svn_linenum_t modified_start;
1713 modified_start = svn_diff_hunk_get_modified_start(hunk);
1714 if (modified_start == 0)
1716 /* Patch wants to delete the file.
1718 ### locally_deleted is always false here? */
1719 already_applied = target->locally_deleted;
1723 SVN_ERR(seek_to_line(content, modified_start,
1725 SVN_ERR(scan_for_match(&matched_line, content,
1728 fuzz, ignore_whitespace, TRUE,
1729 cancel_func, cancel_baton,
1731 already_applied = (matched_line == modified_start);
1735 already_applied = FALSE;
1737 if (! already_applied)
1740 svn_linenum_t search_start = 1, search_end = 0;
1741 svn_linenum_t matched_line2;
1743 /* Search for closest match before or after original
1744 start. We have no backward search so search forwards
1745 from the previous match (or start of file) to the
1746 original start looking for the last match. Then
1747 search forwards from the original start looking for a
1748 better match. Finally search forwards from the start
1749 of file to the previous hunk if that could result in
1752 for (i = content->hunks->nelts; i > 0; --i)
1754 const hunk_info_t *prev
1755 = APR_ARRAY_IDX(content->hunks, i - 1, const hunk_info_t *);
1756 if (!prev->rejected)
1758 svn_linenum_t length;
1760 length = svn_diff_hunk_get_original_length(prev->hunk);
1761 search_start = prev->matched_line + length;
1766 /* Search from the previous match, or start of file,
1767 towards the original location. */
1768 SVN_ERR(seek_to_line(content, search_start, scratch_pool));
1769 SVN_ERR(scan_for_match(&matched_line, content, hunk, FALSE,
1770 original_start, fuzz,
1771 ignore_whitespace, FALSE,
1772 cancel_func, cancel_baton,
1775 /* If a match we only need to search forwards for a
1776 better match, otherwise to the end of the file. */
1778 search_end = original_start + (original_start - matched_line);
1780 /* Search from original location, towards the end. */
1781 SVN_ERR(seek_to_line(content, original_start + 1, scratch_pool));
1782 SVN_ERR(scan_for_match(&matched_line2, content, hunk,
1783 TRUE, search_end, fuzz, ignore_whitespace,
1784 FALSE, cancel_func, cancel_baton,
1787 /* Chose the forward match if it is closer than the
1788 backward match or if there is no backward match. */
1791 || (matched_line2 - original_start
1792 < original_start - matched_line)))
1793 matched_line = matched_line2;
1795 /* Search from before previous hunk if there could be a
1797 if (search_start > 1
1799 || (matched_line > original_start
1800 && (matched_line - original_start
1801 > original_start - search_start))))
1803 svn_linenum_t search_start2 = 1;
1806 && matched_line - original_start < original_start)
1808 = original_start - (matched_line - original_start) + 1;
1810 SVN_ERR(seek_to_line(content, search_start2, scratch_pool));
1811 SVN_ERR(scan_for_match(&matched_line2, content, hunk, FALSE,
1812 search_start - 1, fuzz,
1813 ignore_whitespace, FALSE,
1814 cancel_func, cancel_baton,
1817 matched_line = matched_line2;
1822 SVN_ERR(seek_to_line(content, saved_line, scratch_pool));
1826 /* The hunk wants to modify a file which doesn't exist. */
1830 (*hi) = apr_pcalloc(result_pool, sizeof(hunk_info_t));
1832 (*hi)->matched_line = matched_line;
1833 (*hi)->rejected = (matched_line == 0);
1834 (*hi)->already_applied = already_applied;
1837 return SVN_NO_ERROR;
1840 /* Copy lines to the patched content until the specified LINE has been
1841 * reached. Indicate in *EOF whether end-of-file was encountered while
1842 * reading from the target.
1843 * If LINE is zero, copy lines until end-of-file has been reached.
1844 * Do all allocations in POOL. */
1845 static svn_error_t *
1846 copy_lines_to_target(target_content_t *content, svn_linenum_t line,
1849 apr_pool_t *iterpool;
1851 iterpool = svn_pool_create(pool);
1852 while ((content->current_line < line || line == 0) && ! content->eof)
1854 const char *target_line;
1857 svn_pool_clear(iterpool);
1859 SVN_ERR(readline(content, &target_line, iterpool, iterpool));
1861 target_line = apr_pstrcat(iterpool, target_line, content->eol_str,
1863 len = strlen(target_line);
1864 SVN_ERR(content->write(content->write_baton, target_line,
1867 svn_pool_destroy(iterpool);
1869 return SVN_NO_ERROR;
1872 /* Write the diff text of HUNK to TARGET's reject file,
1873 * and mark TARGET as having had rejects.
1874 * We don't expand keywords, nor normalise line-endings, in reject files.
1875 * Do temporary allocations in SCRATCH_POOL. */
1876 static svn_error_t *
1877 reject_hunk(patch_target_t *target, target_content_t *content,
1878 svn_diff_hunk_t *hunk, const char *prop_name,
1881 const char *hunk_header;
1884 static const char * const text_atat = "@@";
1885 static const char * const prop_atat = "##";
1887 apr_pool_t *iterpool;
1891 const char *prop_header;
1893 /* ### Print 'Added', 'Deleted' or 'Modified' instead of 'Property'.
1895 prop_header = apr_psprintf(pool, "Property: %s\n", prop_name);
1896 len = strlen(prop_header);
1897 SVN_ERR(svn_io_file_write_full(target->reject_file, prop_header,
1906 hunk_header = apr_psprintf(pool, "%s -%lu,%lu +%lu,%lu %s%s",
1908 svn_diff_hunk_get_original_start(hunk),
1909 svn_diff_hunk_get_original_length(hunk),
1910 svn_diff_hunk_get_modified_start(hunk),
1911 svn_diff_hunk_get_modified_length(hunk),
1914 len = strlen(hunk_header);
1915 SVN_ERR(svn_io_file_write_full(target->reject_file, hunk_header, len,
1918 iterpool = svn_pool_create(pool);
1921 svn_stringbuf_t *hunk_line;
1922 const char *eol_str;
1924 svn_pool_clear(iterpool);
1926 SVN_ERR(svn_diff_hunk_readline_diff_text(hunk, &hunk_line, &eol_str,
1927 &eof, iterpool, iterpool));
1930 if (hunk_line->len >= 1)
1932 len = hunk_line->len;
1933 SVN_ERR(svn_io_file_write_full(target->reject_file,
1934 hunk_line->data, len, &len,
1940 len = strlen(eol_str);
1941 SVN_ERR(svn_io_file_write_full(target->reject_file, eol_str,
1942 len, &len, iterpool));
1947 svn_pool_destroy(iterpool);
1950 target->had_prop_rejects = TRUE;
1952 target->had_rejects = TRUE;
1954 return SVN_NO_ERROR;
1957 /* Write the modified text of the hunk described by HI to the patched
1958 * CONTENT. TARGET is the patch target.
1959 * If PROP_NAME is not NULL, the hunk is assumed to be targeted for
1960 * a property with the given name.
1961 * Do temporary allocations in POOL. */
1962 static svn_error_t *
1963 apply_hunk(patch_target_t *target, target_content_t *content,
1964 hunk_info_t *hi, const char *prop_name, apr_pool_t *pool)
1966 svn_linenum_t lines_read;
1968 apr_pool_t *iterpool;
1970 /* ### Is there a cleaner way to describe if we have an existing target?
1972 if (target->kind_on_disk == svn_node_file || prop_name)
1976 /* Move forward to the hunk's line, copying data as we go.
1977 * Also copy leading lines of context which matched with fuzz.
1978 * The target has changed on the fuzzy-matched lines,
1979 * so we should retain the target's version of those lines. */
1980 SVN_ERR(copy_lines_to_target(content, hi->matched_line + hi->fuzz,
1983 /* Skip the target's version of the hunk.
1984 * Don't skip trailing lines which matched with fuzz. */
1985 line = content->current_line +
1986 svn_diff_hunk_get_original_length(hi->hunk) - (2 * hi->fuzz);
1987 SVN_ERR(seek_to_line(content, line, pool));
1988 if (content->current_line != line && ! content->eof)
1990 /* Seek failed, reject this hunk. */
1991 hi->rejected = TRUE;
1992 SVN_ERR(reject_hunk(target, content, hi->hunk, prop_name, pool));
1993 return SVN_NO_ERROR;
1997 /* Write the hunk's version to the patched result.
1998 * Don't write the lines which matched with fuzz. */
2000 svn_diff_hunk_reset_modified_text(hi->hunk);
2001 iterpool = svn_pool_create(pool);
2004 svn_stringbuf_t *hunk_line;
2005 const char *eol_str;
2007 svn_pool_clear(iterpool);
2009 SVN_ERR(svn_diff_hunk_readline_modified_text(hi->hunk, &hunk_line,
2011 iterpool, iterpool));
2013 if (lines_read > hi->fuzz &&
2014 lines_read <= svn_diff_hunk_get_modified_length(hi->hunk) - hi->fuzz)
2018 if (hunk_line->len >= 1)
2020 len = hunk_line->len;
2021 SVN_ERR(content->write(content->write_baton,
2022 hunk_line->data, len, iterpool));
2027 /* Use the EOL as it was read from the patch file,
2028 * unless the target's EOL style is set by svn:eol-style */
2029 if (content->eol_style != svn_subst_eol_style_none)
2030 eol_str = content->eol_str;
2032 len = strlen(eol_str);
2033 SVN_ERR(content->write(content->write_baton,
2034 eol_str, len, iterpool));
2039 svn_pool_destroy(iterpool);
2042 target->has_prop_changes = TRUE;
2044 target->has_text_changes = TRUE;
2046 return SVN_NO_ERROR;
2049 /* Use client context CTX to send a suitable notification for hunk HI,
2050 * using TARGET to determine the path. If the hunk is a property hunk,
2051 * PROP_NAME must be the name of the property, else NULL.
2052 * Use POOL for temporary allocations. */
2053 static svn_error_t *
2054 send_hunk_notification(const hunk_info_t *hi,
2055 const patch_target_t *target,
2056 const char *prop_name,
2057 const svn_client_ctx_t *ctx,
2060 svn_wc_notify_t *notify;
2061 svn_wc_notify_action_t action;
2063 if (hi->already_applied)
2064 action = svn_wc_notify_patch_hunk_already_applied;
2065 else if (hi->rejected)
2066 action = svn_wc_notify_patch_rejected_hunk;
2068 action = svn_wc_notify_patch_applied_hunk;
2070 notify = svn_wc_create_notify(target->local_abspath
2071 ? target->local_abspath
2072 : target->local_relpath,
2074 notify->hunk_original_start =
2075 svn_diff_hunk_get_original_start(hi->hunk);
2076 notify->hunk_original_length =
2077 svn_diff_hunk_get_original_length(hi->hunk);
2078 notify->hunk_modified_start =
2079 svn_diff_hunk_get_modified_start(hi->hunk);
2080 notify->hunk_modified_length =
2081 svn_diff_hunk_get_modified_length(hi->hunk);
2082 notify->hunk_matched_line = hi->matched_line;
2083 notify->hunk_fuzz = hi->fuzz;
2084 notify->prop_name = prop_name;
2086 ctx->notify_func2(ctx->notify_baton2, notify, pool);
2088 return SVN_NO_ERROR;
2091 /* Use client context CTX to send a suitable notification for a patch TARGET.
2092 * Use POOL for temporary allocations. */
2093 static svn_error_t *
2094 send_patch_notification(const patch_target_t *target,
2095 const svn_client_ctx_t *ctx,
2098 svn_wc_notify_t *notify;
2099 svn_wc_notify_action_t action;
2100 const char *notify_path;
2102 if (! ctx->notify_func2)
2103 return SVN_NO_ERROR;
2105 if (target->skipped)
2106 action = svn_wc_notify_skip;
2107 else if (target->deleted)
2108 action = svn_wc_notify_delete;
2109 else if (target->added || target->replaced || target->move_target_abspath)
2110 action = svn_wc_notify_add;
2112 action = svn_wc_notify_patch;
2114 if (target->move_target_abspath)
2115 notify_path = target->move_target_abspath;
2117 notify_path = target->local_abspath ? target->local_abspath
2118 : target->local_relpath;
2120 notify = svn_wc_create_notify(notify_path, action, pool);
2121 notify->kind = svn_node_file;
2123 if (action == svn_wc_notify_skip)
2125 if (target->db_kind == svn_node_none ||
2126 target->db_kind == svn_node_unknown)
2127 notify->content_state = svn_wc_notify_state_missing;
2128 else if (target->db_kind == svn_node_dir)
2129 notify->content_state = svn_wc_notify_state_obstructed;
2131 notify->content_state = svn_wc_notify_state_unknown;
2135 if (target->had_rejects)
2136 notify->content_state = svn_wc_notify_state_conflicted;
2137 else if (target->local_mods)
2138 notify->content_state = svn_wc_notify_state_merged;
2139 else if (target->has_text_changes)
2140 notify->content_state = svn_wc_notify_state_changed;
2142 if (target->had_prop_rejects)
2143 notify->prop_state = svn_wc_notify_state_conflicted;
2144 else if (target->has_prop_changes)
2145 notify->prop_state = svn_wc_notify_state_changed;
2148 ctx->notify_func2(ctx->notify_baton2, notify, pool);
2150 if (action == svn_wc_notify_patch)
2153 apr_pool_t *iterpool;
2154 apr_hash_index_t *hash_index;
2156 iterpool = svn_pool_create(pool);
2157 for (i = 0; i < target->content->hunks->nelts; i++)
2159 const hunk_info_t *hi;
2161 svn_pool_clear(iterpool);
2163 hi = APR_ARRAY_IDX(target->content->hunks, i, hunk_info_t *);
2165 SVN_ERR(send_hunk_notification(hi, target, NULL /* prop_name */,
2169 for (hash_index = apr_hash_first(pool, target->prop_targets);
2171 hash_index = apr_hash_next(hash_index))
2173 prop_patch_target_t *prop_target;
2175 prop_target = apr_hash_this_val(hash_index);
2177 for (i = 0; i < prop_target->content->hunks->nelts; i++)
2179 const hunk_info_t *hi;
2181 svn_pool_clear(iterpool);
2183 hi = APR_ARRAY_IDX(prop_target->content->hunks, i,
2186 /* Don't notify on the hunk level for added or deleted props. */
2187 if (prop_target->operation != svn_diff_op_added &&
2188 prop_target->operation != svn_diff_op_deleted)
2189 SVN_ERR(send_hunk_notification(hi, target, prop_target->name,
2193 svn_pool_destroy(iterpool);
2196 if (target->move_target_abspath)
2198 /* Notify about deletion of move source. */
2199 notify = svn_wc_create_notify(target->local_abspath,
2200 svn_wc_notify_delete, pool);
2201 notify->kind = svn_node_file;
2202 ctx->notify_func2(ctx->notify_baton2, notify, pool);
2205 return SVN_NO_ERROR;
2208 /* Implements the callback for svn_sort__array. Puts hunks that match
2209 before hunks that do not match, puts hunks that match in order
2210 based on postion matched, puts hunks that do not match in order
2211 based on original position. */
2213 sort_matched_hunks(const void *a, const void *b)
2215 const hunk_info_t *item1 = *((const hunk_info_t * const *)a);
2216 const hunk_info_t *item2 = *((const hunk_info_t * const *)b);
2217 svn_boolean_t matched1 = !item1->rejected && !item1->already_applied;
2218 svn_boolean_t matched2 = !item2->rejected && !item2->already_applied;
2219 svn_linenum_t original1, original2;
2221 if (matched1 && matched2)
2223 /* Both match so use order matched in file. */
2224 if (item1->matched_line > item2->matched_line)
2226 else if (item1->matched_line == item2->matched_line)
2232 /* Only second matches, put it before first. */
2235 /* Only first matches, put it before second. */
2238 /* Neither matches, sort by original_start. */
2239 original1 = svn_diff_hunk_get_original_start(item1->hunk);
2240 original2 = svn_diff_hunk_get_original_start(item2->hunk);
2241 if (original1 > original2)
2243 else if (original1 == original2)
2250 /* Apply a PATCH to a working copy at ABS_WC_PATH and put the result
2251 * into temporary files, to be installed in the working copy later.
2252 * Return information about the patch target in *PATCH_TARGET, allocated
2253 * in RESULT_POOL. Use WC_CTX as the working copy context.
2254 * STRIP_COUNT specifies the number of leading path components
2255 * which should be stripped from target paths in the patch.
2256 * REMOVE_TEMPFILES, PATCH_FUNC, and PATCH_BATON as in svn_client_patch().
2257 * IGNORE_WHITESPACE tells whether whitespace should be considered when
2258 * doing the matching.
2259 * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
2260 * Do temporary allocations in SCRATCH_POOL. */
2261 static svn_error_t *
2262 apply_one_patch(patch_target_t **patch_target, svn_patch_t *patch,
2263 const char *abs_wc_path, svn_wc_context_t *wc_ctx,
2265 svn_boolean_t ignore_whitespace,
2266 svn_boolean_t remove_tempfiles,
2267 svn_client_patch_func_t patch_func,
2269 svn_cancel_func_t cancel_func,
2271 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
2273 patch_target_t *target;
2274 apr_pool_t *iterpool;
2276 static const svn_linenum_t MAX_FUZZ = 2;
2277 apr_hash_index_t *hash_index;
2278 svn_linenum_t previous_offset = 0;
2280 SVN_ERR(init_patch_target(&target, patch, abs_wc_path, wc_ctx, strip_count,
2281 remove_tempfiles, result_pool, scratch_pool));
2282 if (target->skipped)
2284 *patch_target = target;
2285 return SVN_NO_ERROR;
2290 SVN_ERR(patch_func(patch_baton, &target->filtered,
2291 target->canon_path_from_patchfile,
2292 target->patched_path, target->reject_path,
2294 if (target->filtered)
2296 *patch_target = target;
2297 return SVN_NO_ERROR;
2301 iterpool = svn_pool_create(scratch_pool);
2303 for (i = 0; i < patch->hunks->nelts; i++)
2305 svn_diff_hunk_t *hunk;
2307 svn_linenum_t fuzz = 0;
2309 svn_pool_clear(iterpool);
2312 SVN_ERR(cancel_func(cancel_baton));
2314 hunk = APR_ARRAY_IDX(patch->hunks, i, svn_diff_hunk_t *);
2316 /* Determine the line the hunk should be applied at.
2317 * If no match is found initially, try with fuzz. */
2320 SVN_ERR(get_hunk_info(&hi, target, target->content, hunk, fuzz,
2323 FALSE /* is_prop_hunk */,
2324 cancel_func, cancel_baton,
2325 result_pool, iterpool));
2328 while (hi->rejected && fuzz <= MAX_FUZZ && ! hi->already_applied);
2330 if (hi->matched_line)
2332 = hi->matched_line - svn_diff_hunk_get_original_start(hunk);
2334 APR_ARRAY_PUSH(target->content->hunks, hunk_info_t *) = hi;
2337 /* Hunks are applied in the order determined by the matched line and
2338 this may be different from the order of the original lines. */
2339 svn_sort__array(target->content->hunks, sort_matched_hunks);
2341 /* Apply or reject hunks. */
2342 for (i = 0; i < target->content->hunks->nelts; i++)
2346 svn_pool_clear(iterpool);
2349 SVN_ERR(cancel_func(cancel_baton));
2351 hi = APR_ARRAY_IDX(target->content->hunks, i, hunk_info_t *);
2352 if (hi->already_applied)
2354 else if (hi->rejected)
2355 SVN_ERR(reject_hunk(target, target->content, hi->hunk,
2356 NULL /* prop_name */,
2359 SVN_ERR(apply_hunk(target, target->content, hi,
2360 NULL /* prop_name */, iterpool));
2363 if (target->kind_on_disk == svn_node_file)
2365 /* Copy any remaining lines to target. */
2366 SVN_ERR(copy_lines_to_target(target->content, 0, scratch_pool));
2367 if (! target->content->eof)
2369 /* We could not copy the entire target file to the temporary file,
2370 * and would truncate the target if we copied the temporary file
2371 * on top of it. Skip this target. */
2372 target->skipped = TRUE;
2376 /* Match property hunks. */
2377 for (hash_index = apr_hash_first(scratch_pool, patch->prop_patches);
2379 hash_index = apr_hash_next(hash_index))
2381 svn_prop_patch_t *prop_patch;
2382 const char *prop_name;
2383 prop_patch_target_t *prop_target;
2385 prop_name = apr_hash_this_key(hash_index);
2386 prop_patch = apr_hash_this_val(hash_index);
2388 if (! strcmp(prop_name, SVN_PROP_SPECIAL))
2389 target->is_special = TRUE;
2391 /* We'll store matched hunks in prop_content. */
2392 prop_target = svn_hash_gets(target->prop_targets, prop_name);
2394 for (i = 0; i < prop_patch->hunks->nelts; i++)
2396 svn_diff_hunk_t *hunk;
2398 svn_linenum_t fuzz = 0;
2400 svn_pool_clear(iterpool);
2403 SVN_ERR(cancel_func(cancel_baton));
2405 hunk = APR_ARRAY_IDX(prop_patch->hunks, i, svn_diff_hunk_t *);
2407 /* Determine the line the hunk should be applied at.
2408 * If no match is found initially, try with fuzz. */
2411 SVN_ERR(get_hunk_info(&hi, target, prop_target->content,
2414 TRUE /* is_prop_hunk */,
2415 cancel_func, cancel_baton,
2416 result_pool, iterpool));
2419 while (hi->rejected && fuzz <= MAX_FUZZ && ! hi->already_applied);
2421 APR_ARRAY_PUSH(prop_target->content->hunks, hunk_info_t *) = hi;
2425 /* Apply or reject property hunks. */
2426 for (hash_index = apr_hash_first(scratch_pool, target->prop_targets);
2428 hash_index = apr_hash_next(hash_index))
2430 prop_patch_target_t *prop_target;
2432 prop_target = apr_hash_this_val(hash_index);
2434 for (i = 0; i < prop_target->content->hunks->nelts; i++)
2438 svn_pool_clear(iterpool);
2440 hi = APR_ARRAY_IDX(prop_target->content->hunks, i,
2442 if (hi->already_applied)
2444 else if (hi->rejected)
2445 SVN_ERR(reject_hunk(target, prop_target->content, hi->hunk,
2449 SVN_ERR(apply_hunk(target, prop_target->content, hi,
2454 if (prop_target->content->existed)
2456 /* Copy any remaining lines to target. */
2457 SVN_ERR(copy_lines_to_target(prop_target->content, 0,
2459 if (! prop_target->content->eof)
2461 /* We could not copy the entire target property to the
2462 * temporary file, and would truncate the target if we
2463 * copied the temporary file on top of it. Skip this target. */
2464 target->skipped = TRUE;
2469 svn_pool_destroy(iterpool);
2471 if (!target->is_symlink)
2473 /* Now close files we don't need any longer to get their contents
2475 * But we're not closing the reject file -- it still needed and
2476 * will be closed later in write_out_rejected_hunks(). */
2477 if (target->kind_on_disk == svn_node_file)
2478 SVN_ERR(svn_io_file_close(target->file, scratch_pool));
2480 SVN_ERR(svn_io_file_close(target->patched_file, scratch_pool));
2483 if (! target->skipped)
2485 apr_finfo_t working_file;
2486 apr_finfo_t patched_file;
2488 /* Get sizes of the patched temporary file and the working file.
2489 * We'll need those to figure out whether we should delete the
2491 SVN_ERR(svn_io_stat(&patched_file, target->patched_path,
2492 APR_FINFO_SIZE | APR_FINFO_LINK, scratch_pool));
2493 if (target->kind_on_disk == svn_node_file)
2494 SVN_ERR(svn_io_stat(&working_file, target->local_abspath,
2495 APR_FINFO_SIZE | APR_FINFO_LINK, scratch_pool));
2497 working_file.size = 0;
2499 if (patched_file.size == 0 && working_file.size > 0)
2501 /* If a unidiff removes all lines from a file, that usually
2502 * means deletion, so we can confidently schedule the target
2503 * for deletion. In the rare case where the unidiff was really
2504 * meant to replace a file with an empty one, this may not
2505 * be desirable. But the deletion can easily be reverted and
2506 * creating an empty file manually is not exactly hard either. */
2507 target->deleted = (target->db_kind == svn_node_file);
2509 else if (patched_file.size == 0 && working_file.size == 0)
2511 /* The target was empty or non-existent to begin with
2512 * and no content was changed by patching.
2513 * Report this as skipped if it didn't exist, unless in the special
2514 * case of adding an empty file which has properties set on it or
2515 * adding an empty file with a 'git diff' */
2516 if (target->kind_on_disk == svn_node_none
2517 && ! target->has_prop_changes
2519 target->skipped = TRUE;
2521 else if (patched_file.size > 0 && working_file.size == 0)
2523 /* The patch has created a file. */
2524 if (target->locally_deleted)
2525 target->replaced = TRUE;
2526 else if (target->db_kind == svn_node_none)
2527 target->added = TRUE;
2531 *patch_target = target;
2533 return SVN_NO_ERROR;
2536 /* Try to create missing parent directories for TARGET in the working copy
2537 * rooted at ABS_WC_PATH, and add the parents to version control.
2538 * If the parents cannot be created, mark the target as skipped.
2539 * Use client context CTX. If DRY_RUN is true, do not create missing
2540 * parents but issue notifications only.
2541 * Use SCRATCH_POOL for temporary allocations. */
2542 static svn_error_t *
2543 create_missing_parents(patch_target_t *target,
2544 const char *abs_wc_path,
2545 svn_client_ctx_t *ctx,
2546 svn_boolean_t dry_run,
2547 apr_pool_t *scratch_pool)
2549 const char *local_abspath;
2550 apr_array_header_t *components;
2551 int present_components;
2553 apr_pool_t *iterpool;
2555 /* Check if we can safely create the target's parent. */
2556 local_abspath = abs_wc_path;
2557 components = svn_path_decompose(target->local_relpath, scratch_pool);
2558 present_components = 0;
2559 iterpool = svn_pool_create(scratch_pool);
2560 for (i = 0; i < components->nelts - 1; i++)
2562 const char *component;
2563 svn_node_kind_t wc_kind, disk_kind;
2565 svn_pool_clear(iterpool);
2567 component = APR_ARRAY_IDX(components, i, const char *);
2568 local_abspath = svn_dirent_join(local_abspath, component, scratch_pool);
2570 SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, local_abspath,
2571 FALSE, TRUE, iterpool));
2573 SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, iterpool));
2575 if (disk_kind == svn_node_file || wc_kind == svn_node_file)
2577 /* on-disk files and missing files are obstructions */
2578 target->skipped = TRUE;
2581 else if (disk_kind == svn_node_dir)
2583 if (wc_kind == svn_node_dir)
2584 present_components++;
2587 target->skipped = TRUE;
2591 else if (wc_kind != svn_node_none)
2593 /* Node is missing */
2594 target->skipped = TRUE;
2599 /* It's not a file, it's not a dir...
2604 if (! target->skipped)
2606 local_abspath = abs_wc_path;
2607 for (i = 0; i < present_components; i++)
2609 const char *component;
2610 component = APR_ARRAY_IDX(components, i, const char *);
2611 local_abspath = svn_dirent_join(local_abspath,
2612 component, scratch_pool);
2615 if (!dry_run && present_components < components->nelts - 1)
2616 SVN_ERR(svn_io_make_dir_recursively(
2619 svn_relpath_dirname(target->local_relpath,
2624 for (i = present_components; i < components->nelts - 1; i++)
2626 const char *component;
2628 svn_pool_clear(iterpool);
2630 component = APR_ARRAY_IDX(components, i, const char *);
2631 local_abspath = svn_dirent_join(local_abspath, component,
2635 if (ctx->notify_func2)
2637 /* Just do notification. */
2638 svn_wc_notify_t *notify;
2639 notify = svn_wc_create_notify(local_abspath,
2642 notify->kind = svn_node_dir;
2643 ctx->notify_func2(ctx->notify_baton2, notify,
2649 /* Create the missing component and add it
2650 * to version control. Allow cancellation since we
2651 * have not modified the working copy yet for this
2654 if (ctx->cancel_func)
2655 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
2657 SVN_ERR(svn_wc_add_from_disk3(ctx->wc_ctx, local_abspath,
2659 FALSE /* skip checks */,
2660 ctx->notify_func2, ctx->notify_baton2,
2666 svn_pool_destroy(iterpool);
2667 return SVN_NO_ERROR;
2670 /* Install a patched TARGET into the working copy at ABS_WC_PATH.
2671 * Use client context CTX to retrieve WC_CTX, and possibly doing
2672 * notifications. If DRY_RUN is TRUE, don't modify the working copy.
2673 * Do temporary allocations in POOL. */
2674 static svn_error_t *
2675 install_patched_target(patch_target_t *target, const char *abs_wc_path,
2676 svn_client_ctx_t *ctx, svn_boolean_t dry_run,
2679 if (target->deleted)
2683 /* Schedule the target for deletion. Suppress
2684 * notification, we'll do it manually in a minute
2685 * because we also need to notify during dry-run.
2686 * Also suppress cancellation, because we'd rather
2687 * notify about what we did before aborting. */
2688 SVN_ERR(svn_wc_delete4(ctx->wc_ctx, target->local_abspath,
2689 FALSE /* keep_local */, FALSE,
2690 NULL, NULL, NULL, NULL, pool));
2695 svn_node_kind_t parent_db_kind;
2696 if (target->added || target->replaced)
2698 const char *parent_abspath;
2700 parent_abspath = svn_dirent_dirname(target->local_abspath,
2702 /* If the target's parent directory does not yet exist
2703 * we need to create it before we can copy the patched
2704 * result in place. */
2705 SVN_ERR(svn_wc_read_kind2(&parent_db_kind, ctx->wc_ctx,
2706 parent_abspath, FALSE, FALSE, pool));
2708 /* We can't add targets under nodes scheduled for delete, so add
2709 a new directory if needed. */
2710 if (parent_db_kind == svn_node_dir
2711 || parent_db_kind == svn_node_file)
2713 if (parent_db_kind != svn_node_dir)
2714 target->skipped = TRUE;
2717 svn_node_kind_t disk_kind;
2719 SVN_ERR(svn_io_check_path(parent_abspath, &disk_kind, pool));
2720 if (disk_kind != svn_node_dir)
2721 target->skipped = TRUE;
2725 SVN_ERR(create_missing_parents(target, abs_wc_path, ctx,
2731 svn_node_kind_t wc_kind;
2733 /* The target should exist */
2734 SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx,
2735 target->local_abspath,
2736 FALSE, FALSE, pool));
2738 if (target->kind_on_disk == svn_node_none
2739 || wc_kind != target->kind_on_disk)
2741 target->skipped = TRUE;
2745 if (! dry_run && ! target->skipped)
2747 if (target->is_special)
2749 svn_stream_t *stream;
2750 svn_stream_t *patched_stream;
2752 SVN_ERR(svn_stream_open_readonly(&patched_stream,
2753 target->patched_path,
2755 SVN_ERR(svn_subst_create_specialfile(&stream,
2756 target->local_abspath,
2758 SVN_ERR(svn_stream_copy3(patched_stream, stream,
2759 ctx->cancel_func, ctx->cancel_baton,
2764 svn_boolean_t repair_eol;
2766 /* Copy the patched file on top of the target file.
2767 * Always expand keywords in the patched file, but repair EOL
2768 * only if svn:eol-style dictates a particular style. */
2769 repair_eol = (target->content->eol_style ==
2770 svn_subst_eol_style_fixed ||
2771 target->content->eol_style ==
2772 svn_subst_eol_style_native);
2774 SVN_ERR(svn_subst_copy_and_translate4(
2775 target->patched_path,
2776 target->move_target_abspath
2777 ? target->move_target_abspath
2778 : target->local_abspath,
2779 target->content->eol_str, repair_eol,
2780 target->content->keywords,
2781 TRUE /* expand */, FALSE /* special */,
2782 ctx->cancel_func, ctx->cancel_baton, pool));
2785 if (target->added || target->replaced)
2787 /* The target file didn't exist previously,
2788 * so add it to version control.
2789 * Suppress notification, we'll do that later (and also
2790 * during dry-run). Don't allow cancellation because
2791 * we'd rather notify about what we did before aborting. */
2792 SVN_ERR(svn_wc_add_from_disk3(ctx->wc_ctx, target->local_abspath,
2794 FALSE /* skip checks */,
2798 /* Restore the target's executable bit if necessary. */
2799 SVN_ERR(svn_io_set_file_executable(target->move_target_abspath
2800 ? target->move_target_abspath
2801 : target->local_abspath,
2805 if (target->move_target_abspath)
2807 /* ### Copying the patched content to the move target location,
2808 * performing the move in meta-data, and removing the file at
2809 * the move source should be one atomic operation. */
2811 /* ### Create missing parents. */
2813 /* Perform the move in meta-data. */
2814 SVN_ERR(svn_wc__move2(ctx->wc_ctx,
2815 target->local_abspath,
2816 target->move_target_abspath,
2817 TRUE, /* metadata_only */
2818 FALSE, /* allow_mixed_revisions */
2819 NULL, NULL, NULL, NULL,
2822 /* Delete the patch target's old location from disk. */
2823 SVN_ERR(svn_io_remove_file2(target->local_abspath, FALSE, pool));
2828 return SVN_NO_ERROR;
2831 /* Write out rejected hunks, if any, to TARGET->REJECT_PATH. If DRY_RUN is
2832 * TRUE, don't modify the working copy.
2833 * Do temporary allocations in POOL.
2835 static svn_error_t *
2836 write_out_rejected_hunks(patch_target_t *target,
2837 svn_boolean_t dry_run,
2840 SVN_ERR(svn_io_file_close(target->reject_file, pool));
2842 if (! dry_run && (target->had_rejects || target->had_prop_rejects))
2844 /* Write out rejected hunks, if any. */
2845 SVN_ERR(svn_io_copy_file(target->reject_path,
2846 apr_psprintf(pool, "%s.svnpatch.rej",
2847 target->local_abspath),
2849 /* ### TODO mark file as conflicted. */
2851 return SVN_NO_ERROR;
2854 /* Install the patched properties for TARGET. Use client context CTX to
2855 * retrieve WC_CTX. If DRY_RUN is TRUE, don't modify the working copy.
2856 * Do temporary allocations in SCRATCH_POOL. */
2857 static svn_error_t *
2858 install_patched_prop_targets(patch_target_t *target,
2859 svn_client_ctx_t *ctx, svn_boolean_t dry_run,
2860 apr_pool_t *scratch_pool)
2862 apr_hash_index_t *hi;
2863 apr_pool_t *iterpool;
2865 iterpool = svn_pool_create(scratch_pool);
2867 for (hi = apr_hash_first(scratch_pool, target->prop_targets);
2869 hi = apr_hash_next(hi))
2871 prop_patch_target_t *prop_target = apr_hash_this_val(hi);
2872 const svn_string_t *prop_val;
2875 svn_pool_clear(iterpool);
2877 if (ctx->cancel_func)
2878 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
2880 /* For a deleted prop we only set the value to NULL. */
2881 if (prop_target->operation == svn_diff_op_deleted)
2884 SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, target->local_abspath,
2885 prop_target->name, NULL, svn_depth_empty,
2886 TRUE /* skip_checks */,
2887 NULL /* changelist_filter */,
2888 NULL, NULL /* cancellation */,
2889 NULL, NULL /* notification */,
2894 /* If the patch target doesn't exist yet, the patch wants to add an
2895 * empty file with properties set on it. So create an empty file and
2896 * add it to version control. But if the patch was in the 'git format'
2897 * then the file has already been added.
2899 * ### How can we tell whether the patch really wanted to create
2900 * ### an empty directory? */
2901 if (! target->has_text_changes
2902 && target->kind_on_disk == svn_node_none
2907 SVN_ERR(svn_io_file_create_empty(target->local_abspath,
2909 SVN_ERR(svn_wc_add_from_disk3(ctx->wc_ctx, target->local_abspath,
2911 FALSE /* skip checks */,
2912 /* suppress notification */
2916 target->added = TRUE;
2919 /* Attempt to set the property, and reject all hunks if this
2920 fails. If the property had a non-empty value, but now has
2921 an empty one, we'll just delete the property altogether. */
2922 if (prop_target->value && prop_target->value->len
2923 && prop_target->patched_value && !prop_target->patched_value->len)
2926 prop_val = svn_stringbuf__morph_into_string(prop_target->patched_value);
2930 const svn_string_t *canon_propval;
2932 err = svn_wc_canonicalize_svn_prop(&canon_propval,
2934 prop_val, target->local_abspath,
2936 TRUE, /* ### Skipping checks */
2942 err = svn_wc_prop_set4(ctx->wc_ctx, target->local_abspath,
2943 prop_target->name, prop_val, svn_depth_empty,
2944 TRUE /* skip_checks */,
2945 NULL /* changelist_filter */,
2946 NULL, NULL /* cancellation */,
2947 NULL, NULL /* notification */,
2953 /* ### The errors which svn_wc_canonicalize_svn_prop() will
2954 * ### return aren't documented. */
2955 if (err->apr_err == SVN_ERR_ILLEGAL_TARGET ||
2956 err->apr_err == SVN_ERR_NODE_UNEXPECTED_KIND ||
2957 err->apr_err == SVN_ERR_IO_UNKNOWN_EOL ||
2958 err->apr_err == SVN_ERR_BAD_MIME_TYPE ||
2959 err->apr_err == SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION)
2963 svn_error_clear(err);
2965 for (i = 0; i < prop_target->content->hunks->nelts; i++)
2967 hunk_info_t *hunk_info;
2969 hunk_info = APR_ARRAY_IDX(prop_target->content->hunks,
2971 hunk_info->rejected = TRUE;
2972 SVN_ERR(reject_hunk(target, prop_target->content,
2973 hunk_info->hunk, prop_target->name,
2978 return svn_error_trace(err);
2983 svn_pool_destroy(iterpool);
2985 return SVN_NO_ERROR;
2988 /* Baton for can_delete_callback */
2989 struct can_delete_baton_t
2991 svn_boolean_t must_keep;
2992 const apr_array_header_t *targets_info;
2993 const char *local_abspath;
2996 /* Implements svn_wc_status_func4_t. */
2997 static svn_error_t *
2998 can_delete_callback(void *baton,
2999 const char *abspath,
3000 const svn_wc_status3_t *status,
3003 struct can_delete_baton_t *cb = baton;
3006 switch(status->node_status)
3008 case svn_wc_status_none:
3009 case svn_wc_status_deleted:
3010 return SVN_NO_ERROR;
3013 if (! strcmp(cb->local_abspath, abspath))
3014 return SVN_NO_ERROR; /* Only interested in descendants */
3016 for (i = 0; i < cb->targets_info->nelts; i++)
3018 const patch_target_info_t *target_info =
3019 APR_ARRAY_IDX(cb->targets_info, i, const patch_target_info_t *);
3021 if (! strcmp(target_info->local_abspath, abspath))
3023 if (target_info->deleted)
3024 return SVN_NO_ERROR;
3026 break; /* Cease invocation; must keep */
3030 cb->must_keep = TRUE;
3032 return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL);
3036 static svn_error_t *
3037 check_ancestor_delete(const char *deleted_target,
3038 apr_array_header_t *targets_info,
3039 const char *apply_root,
3040 svn_boolean_t dry_run,
3041 svn_client_ctx_t *ctx,
3042 apr_pool_t *result_pool,
3043 apr_pool_t *scratch_pool)
3045 struct can_delete_baton_t cb;
3047 apr_array_header_t *ignores;
3048 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
3050 const char *dir_abspath = svn_dirent_dirname(deleted_target, scratch_pool);
3052 SVN_ERR(svn_wc_get_default_ignores(&ignores, ctx->config, scratch_pool));
3054 while (svn_dirent_is_child(apply_root, dir_abspath, iterpool))
3056 svn_pool_clear(iterpool);
3058 cb.local_abspath = dir_abspath;
3059 cb.must_keep = FALSE;
3060 cb.targets_info = targets_info;
3062 err = svn_wc_walk_status(ctx->wc_ctx, dir_abspath, svn_depth_infinity,
3063 TRUE, FALSE, FALSE, ignores,
3064 can_delete_callback, &cb,
3065 ctx->cancel_func, ctx->cancel_baton,
3070 if (err->apr_err != SVN_ERR_CEASE_INVOCATION)
3071 return svn_error_trace(err);
3073 svn_error_clear(err);
3083 SVN_ERR(svn_wc_delete4(ctx->wc_ctx, dir_abspath, FALSE, FALSE,
3084 ctx->cancel_func, ctx->cancel_baton,
3090 patch_target_info_t *pti = apr_pcalloc(result_pool, sizeof(*pti));
3092 pti->local_abspath = apr_pstrdup(result_pool, dir_abspath);
3093 pti->deleted = TRUE;
3095 APR_ARRAY_PUSH(targets_info, patch_target_info_t *) = pti;
3099 if (ctx->notify_func2)
3101 svn_wc_notify_t *notify;
3103 notify = svn_wc_create_notify(dir_abspath, svn_wc_notify_delete,
3105 notify->kind = svn_node_dir;
3107 ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
3110 /* And check if we must also delete the parent */
3111 dir_abspath = svn_dirent_dirname(dir_abspath, scratch_pool);
3114 svn_pool_destroy(iterpool);
3116 return SVN_NO_ERROR;
3119 /* This function is the main entry point into the patch code. */
3120 static svn_error_t *
3121 apply_patches(/* The path to the patch file. */
3122 const char *patch_abspath,
3123 /* The abspath to the working copy the patch should be applied to. */
3124 const char *abs_wc_path,
3125 /* Indicates whether we're doing a dry run. */
3126 svn_boolean_t dry_run,
3127 /* Number of leading components to strip from patch target paths. */
3129 /* Whether to apply the patch in reverse. */
3130 svn_boolean_t reverse,
3131 /* Whether to ignore whitespace when matching context lines. */
3132 svn_boolean_t ignore_whitespace,
3133 /* As in svn_client_patch(). */
3134 svn_boolean_t remove_tempfiles,
3135 /* As in svn_client_patch(). */
3136 svn_client_patch_func_t patch_func,
3138 /* The client context. */
3139 svn_client_ctx_t *ctx,
3140 apr_pool_t *scratch_pool)
3143 apr_pool_t *iterpool;
3144 svn_patch_file_t *patch_file;
3145 apr_array_header_t *targets_info;
3147 /* Try to open the patch file. */
3148 SVN_ERR(svn_diff_open_patch_file(&patch_file, patch_abspath, scratch_pool));
3150 /* Apply patches. */
3151 targets_info = apr_array_make(scratch_pool, 0,
3152 sizeof(patch_target_info_t *));
3153 iterpool = svn_pool_create(scratch_pool);
3156 svn_pool_clear(iterpool);
3158 if (ctx->cancel_func)
3159 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
3161 SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file,
3162 reverse, ignore_whitespace,
3163 iterpool, iterpool));
3166 patch_target_t *target;
3168 SVN_ERR(apply_one_patch(&target, patch, abs_wc_path,
3169 ctx->wc_ctx, strip_count,
3170 ignore_whitespace, remove_tempfiles,
3171 patch_func, patch_baton,
3172 ctx->cancel_func, ctx->cancel_baton,
3173 iterpool, iterpool));
3174 if (! target->filtered)
3176 /* Save info we'll still need when we're done patching. */
3177 patch_target_info_t *target_info =
3178 apr_pcalloc(scratch_pool, sizeof(patch_target_info_t));
3179 target_info->local_abspath = apr_pstrdup(scratch_pool,
3180 target->local_abspath);
3181 target_info->deleted = target->deleted;
3183 if (! target->skipped)
3185 APR_ARRAY_PUSH(targets_info,
3186 patch_target_info_t *) = target_info;
3188 if (target->has_text_changes
3190 || target->move_target_abspath
3192 SVN_ERR(install_patched_target(target, abs_wc_path,
3193 ctx, dry_run, iterpool));
3195 if (target->has_prop_changes && (!target->deleted))
3196 SVN_ERR(install_patched_prop_targets(target, ctx,
3197 dry_run, iterpool));
3199 SVN_ERR(write_out_rejected_hunks(target, dry_run, iterpool));
3201 SVN_ERR(send_patch_notification(target, ctx, iterpool));
3203 if (target->deleted && !target->skipped)
3205 SVN_ERR(check_ancestor_delete(target_info->local_abspath,
3206 targets_info, abs_wc_path,
3208 scratch_pool, iterpool));
3215 SVN_ERR(svn_diff_close_patch_file(patch_file, iterpool));
3216 svn_pool_destroy(iterpool);
3218 return SVN_NO_ERROR;
3222 svn_client_patch(const char *patch_abspath,
3223 const char *wc_dir_abspath,
3224 svn_boolean_t dry_run,
3226 svn_boolean_t reverse,
3227 svn_boolean_t ignore_whitespace,
3228 svn_boolean_t remove_tempfiles,
3229 svn_client_patch_func_t patch_func,
3231 svn_client_ctx_t *ctx,
3232 apr_pool_t *scratch_pool)
3234 svn_node_kind_t kind;
3236 if (strip_count < 0)
3237 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
3238 _("strip count must be positive"));
3240 if (svn_path_is_url(wc_dir_abspath))
3241 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3242 _("'%s' is not a local path"),
3243 svn_dirent_local_style(wc_dir_abspath,
3246 SVN_ERR(svn_io_check_path(patch_abspath, &kind, scratch_pool));
3247 if (kind == svn_node_none)
3248 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3249 _("'%s' does not exist"),
3250 svn_dirent_local_style(patch_abspath,
3252 if (kind != svn_node_file)
3253 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3254 _("'%s' is not a file"),
3255 svn_dirent_local_style(patch_abspath,
3258 SVN_ERR(svn_io_check_path(wc_dir_abspath, &kind, scratch_pool));
3259 if (kind == svn_node_none)
3260 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3261 _("'%s' does not exist"),
3262 svn_dirent_local_style(wc_dir_abspath,
3264 if (kind != svn_node_dir)
3265 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3266 _("'%s' is not a directory"),
3267 svn_dirent_local_style(wc_dir_abspath,
3270 SVN_WC__CALL_WITH_WRITE_LOCK(
3271 apply_patches(patch_abspath, wc_dir_abspath, dry_run, strip_count,
3272 reverse, ignore_whitespace, remove_tempfiles,
3273 patch_func, patch_baton, ctx, scratch_pool),
3274 ctx->wc_ctx, wc_dir_abspath, FALSE /* lock_anchor */, scratch_pool);
3275 return SVN_NO_ERROR;