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"
52 typedef struct hunk_info_t {
54 svn_diff_hunk_t *hunk;
56 /* The line where the hunk matched in the target file. */
57 svn_linenum_t matched_line;
59 /* Whether this hunk has been rejected. */
60 svn_boolean_t rejected;
62 /* Whether this hunk has already been applied (either manually
63 * or by an earlier run of patch). */
64 svn_boolean_t already_applied;
66 /* The fuzz factor used when matching this hunk, i.e. how many
67 * lines of leading and trailing context to ignore during matching. */
71 /* A struct carrying information related to the patched and unpatched
72 * content of a target, be it a property or the text of a file. */
73 typedef struct target_content_t {
74 /* Indicates whether unpatched content existed prior to patching. */
75 svn_boolean_t existed;
77 /* The line last read from the unpatched content. */
78 svn_linenum_t current_line;
80 /* The EOL-style of the unpatched content. Either 'none', 'fixed',
81 * or 'native'. See the documentation of svn_subst_eol_style_t. */
82 svn_subst_eol_style_t eol_style;
84 /* If the EOL_STYLE above is not 'none', this is the EOL string
85 * corresponding to the EOL-style. Else, it is the EOL string the
86 * last line read from the target file was using. */
89 /* An array containing apr_off_t offsets marking the beginning of
90 * each line in the unpatched content. */
91 apr_array_header_t *lines;
93 /* An array containing hunk_info_t structures for hunks already matched. */
94 apr_array_header_t *hunks;
96 /* True if end-of-file was reached while reading from the unpatched
100 /* The keywords of the target. They will be contracted when reading
101 * unpatched content and expanded when writing patched content.
102 * When patching properties this hash is always empty. */
103 apr_hash_t *keywords;
105 /* A callback, with an associated baton, to read a line of unpatched
107 svn_error_t *(*readline)(void *baton, svn_stringbuf_t **line,
108 const char **eol_str, svn_boolean_t *eof,
109 apr_pool_t *result_pool, apr_pool_t *scratch_pool);
112 /* A callback to get the current byte offset within the unpatched
113 * content. Uses the read baton. */
114 svn_error_t * (*tell)(void *baton, apr_off_t *offset,
115 apr_pool_t *scratch_pool);
117 /* A callback to seek to an offset within the unpatched content.
118 * Uses the read baton. */
119 svn_error_t * (*seek)(void *baton, apr_off_t offset,
120 apr_pool_t *scratch_pool);
122 /* A callback to write data to the patched content, with an
123 * associated baton. */
124 svn_error_t * (*write)(void *baton, const char *buf, apr_size_t len,
125 apr_pool_t *scratch_pool);
130 typedef struct prop_patch_target_t {
132 /* The name of the property */
135 /* The property value. This is NULL in case the property did not exist
136 * prior to patch application (see also CONTENT->existed).
137 * Note that the patch implementation does not support binary properties,
138 * so this string is not expected to contain embedded NUL characters. */
139 const svn_string_t *value;
141 /* The patched property value.
142 * This is equivalent to the target, except that in appropriate
143 * places it contains the modified text as it appears in the patch file. */
144 svn_stringbuf_t *patched_value;
146 /* All information that is specific to the content of the property. */
147 target_content_t *content;
149 /* Represents the operation performed on the property. It can be added,
150 * deleted or modified.
151 * ### Should we use flags instead since we're not using all enum values? */
152 svn_diff_operation_kind_t operation;
154 /* ### Here we'll add flags telling if the prop was added, deleted,
155 * ### had_rejects, had_local_mods prior to patching and so on. */
156 } prop_patch_target_t;
158 typedef struct patch_target_t {
159 /* The target path as it appeared in the patch file,
160 * but in canonicalised form. */
161 const char *canon_path_from_patchfile;
163 /* The target path, relative to the working copy directory the
164 * patch is being applied to. A patch strip count applies to this
165 * and only this path. This is never NULL. */
166 const char *local_relpath;
168 /* The absolute path of the target on the filesystem.
169 * Any symlinks the path from the patch file may contain are resolved.
170 * Is not always known, so it may be NULL. */
171 const char *local_abspath;
173 /* The target file, read-only. This is NULL in case the target
174 * file did not exist prior to patch application (see also
175 * CONTENT->existed). */
178 /* The target file is a symlink */
179 svn_boolean_t is_symlink;
182 * This is equivalent to the target, except that in appropriate
183 * places it contains the modified text as it appears in the patch file.
184 * The data in this file is written in repository-normal form.
185 * EOL transformation and keyword contraction is performed when the
186 * patched result is installed in the working copy. */
187 apr_file_t *patched_file;
189 /* Path to the patched file. */
190 const char *patched_path;
192 /* Hunks that are rejected will be written to this file. */
193 apr_file_t *reject_file;
195 /* Path to the reject file. */
196 const char *reject_path;
198 /* The node kind of the target as found in WC-DB prior
199 * to patch application. */
200 svn_node_kind_t db_kind;
202 /* The target's kind on disk prior to patch application. */
203 svn_node_kind_t kind_on_disk;
205 /* True if the target was locally deleted prior to patching. */
206 svn_boolean_t locally_deleted;
208 /* True if the target had to be skipped for some reason. */
209 svn_boolean_t skipped;
211 /* True if the target has been filtered by the patch callback. */
212 svn_boolean_t filtered;
214 /* True if at least one hunk was rejected. */
215 svn_boolean_t had_rejects;
217 /* True if at least one property hunk was rejected. */
218 svn_boolean_t had_prop_rejects;
220 /* True if the target file had local modifications before the
221 * patch was applied to it. */
222 svn_boolean_t local_mods;
224 /* True if the target was added by the patch, which means that it did
225 * not exist on disk before patching and has content after patching. */
228 /* True if the target ended up being deleted by the patch. */
229 svn_boolean_t deleted;
231 /* True if the target ended up being replaced by the patch
232 * (i.e. a new file was added on top locally deleted node). */
233 svn_boolean_t replaced;
235 /* True if the target has the executable bit set. */
236 svn_boolean_t executable;
238 /* True if the patch changed the text of the target. */
239 svn_boolean_t has_text_changes;
241 /* True if the patch changed any of the properties of the target. */
242 svn_boolean_t has_prop_changes;
244 /* True if the patch contained a svn:special property. */
245 svn_boolean_t is_special;
247 /* All the information that is specific to the content of the target. */
248 target_content_t *content;
250 /* A hash table of prop_patch_target_t objects keyed by property names. */
251 apr_hash_t *prop_targets;
256 /* A smaller struct containing a subset of patch_target_t.
257 * Carries the minimal amount of information we still need for a
258 * target after we're done patching it so we can free other resources. */
259 typedef struct patch_target_info_t {
260 const char *local_abspath;
261 svn_boolean_t deleted;
262 } patch_target_info_t;
265 /* Strip STRIP_COUNT components from the front of PATH, returning
266 * the result in *RESULT, allocated in RESULT_POOL.
267 * Do temporary allocations in SCRATCH_POOL. */
269 strip_path(const char **result, const char *path, int strip_count,
270 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
273 apr_array_header_t *components;
274 apr_array_header_t *stripped;
276 components = svn_path_decompose(path, scratch_pool);
277 if (strip_count > components->nelts)
278 return svn_error_createf(SVN_ERR_CLIENT_PATCH_BAD_STRIP_COUNT, NULL,
279 _("Cannot strip %u components from '%s'"),
281 svn_dirent_local_style(path, scratch_pool));
283 stripped = apr_array_make(scratch_pool, components->nelts - strip_count,
284 sizeof(const char *));
285 for (i = strip_count; i < components->nelts; i++)
287 const char *component;
289 component = APR_ARRAY_IDX(components, i, const char *);
290 APR_ARRAY_PUSH(stripped, const char *) = component;
293 *result = svn_path_compose(stripped, result_pool);
298 /* Obtain KEYWORDS, EOL_STYLE and EOL_STR for LOCAL_ABSPATH.
299 * WC_CTX is a context for the working copy the patch is applied to.
300 * Use RESULT_POOL for allocations of fields in TARGET.
301 * Use SCRATCH_POOL for all other allocations. */
303 obtain_eol_and_keywords_for_file(apr_hash_t **keywords,
304 svn_subst_eol_style_t *eol_style,
305 const char **eol_str,
306 svn_wc_context_t *wc_ctx,
307 const char *local_abspath,
308 apr_pool_t *result_pool,
309 apr_pool_t *scratch_pool)
312 svn_string_t *keywords_val, *eol_style_val;
314 SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, local_abspath,
315 scratch_pool, scratch_pool));
316 keywords_val = svn_hash_gets(props, SVN_PROP_KEYWORDS);
319 svn_revnum_t changed_rev;
320 apr_time_t changed_date;
324 const char *root_url;
326 SVN_ERR(svn_wc__node_get_changed_info(&changed_rev,
332 rev_str = apr_psprintf(scratch_pool, "%ld", changed_rev);
333 SVN_ERR(svn_wc__node_get_url(&url, wc_ctx,
335 scratch_pool, scratch_pool));
336 SVN_ERR(svn_wc__node_get_repos_info(NULL, NULL, &root_url, NULL,
337 wc_ctx, local_abspath,
338 scratch_pool, scratch_pool));
339 SVN_ERR(svn_subst_build_keywords3(keywords,
341 rev_str, url, root_url, changed_date,
342 author, result_pool));
345 eol_style_val = svn_hash_gets(props, SVN_PROP_EOL_STYLE);
348 svn_subst_eol_style_from_value(eol_style,
350 eol_style_val->data);
356 /* Resolve the exact path for a patch TARGET at path PATH_FROM_PATCHFILE,
357 * which is the path of the target as it appeared in the patch file.
358 * Put a canonicalized version of PATH_FROM_PATCHFILE into
359 * TARGET->CANON_PATH_FROM_PATCHFILE.
360 * WC_CTX is a context for the working copy the patch is applied to.
361 * If possible, determine TARGET->WC_PATH, TARGET->ABS_PATH, TARGET->KIND,
362 * TARGET->ADDED, and TARGET->PARENT_DIR_EXISTS.
363 * Indicate in TARGET->SKIPPED whether the target should be skipped.
364 * STRIP_COUNT specifies the number of leading path components
365 * which should be stripped from target paths in the patch.
366 * PROP_CHANGES_ONLY specifies whether the target path is allowed to have
367 * only property changes, and no content changes (in which case the target
368 * must be a directory).
369 * Use RESULT_POOL for allocations of fields in TARGET.
370 * Use SCRATCH_POOL for all other allocations. */
372 resolve_target_path(patch_target_t *target,
373 const char *path_from_patchfile,
374 const char *wcroot_abspath,
376 svn_boolean_t prop_changes_only,
377 svn_wc_context_t *wc_ctx,
378 apr_pool_t *result_pool,
379 apr_pool_t *scratch_pool)
381 const char *stripped_path;
382 svn_wc_status3_t *status;
384 svn_boolean_t under_root;
386 target->canon_path_from_patchfile = svn_dirent_internal_style(
387 path_from_patchfile, result_pool);
389 /* We allow properties to be set on the wc root dir. */
390 if (! prop_changes_only && target->canon_path_from_patchfile[0] == '\0')
392 /* An empty patch target path? What gives? Skip this. */
393 target->skipped = TRUE;
394 target->local_abspath = NULL;
395 target->local_relpath = "";
400 SVN_ERR(strip_path(&stripped_path, target->canon_path_from_patchfile,
401 strip_count, result_pool, scratch_pool));
403 stripped_path = target->canon_path_from_patchfile;
405 if (svn_dirent_is_absolute(stripped_path))
407 target->local_relpath = svn_dirent_is_child(wcroot_abspath,
411 if (! target->local_relpath)
413 /* The target path is either outside of the working copy
414 * or it is the working copy itself. Skip it. */
415 target->skipped = TRUE;
416 target->local_abspath = NULL;
417 target->local_relpath = stripped_path;
423 target->local_relpath = stripped_path;
426 /* Make sure the path is secure to use. We want the target to be inside
427 * of the working copy and not be fooled by symlinks it might contain. */
428 SVN_ERR(svn_dirent_is_under_root(&under_root,
429 &target->local_abspath, wcroot_abspath,
430 target->local_relpath, result_pool));
434 /* The target path is outside of the working copy. Skip it. */
435 target->skipped = TRUE;
436 target->local_abspath = NULL;
440 /* Skip things we should not be messing with. */
441 err = svn_wc_status3(&status, wc_ctx, target->local_abspath,
442 result_pool, scratch_pool);
445 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
446 return svn_error_trace(err);
448 svn_error_clear(err);
450 target->locally_deleted = TRUE;
451 target->db_kind = svn_node_none;
454 else if (status->node_status == svn_wc_status_ignored ||
455 status->node_status == svn_wc_status_unversioned ||
456 status->node_status == svn_wc_status_missing ||
457 status->node_status == svn_wc_status_obstructed ||
460 target->skipped = TRUE;
463 else if (status->node_status == svn_wc_status_deleted)
465 target->locally_deleted = TRUE;
468 if (status && (status->kind != svn_node_unknown))
469 target->db_kind = status->kind;
471 target->db_kind = svn_node_none;
473 SVN_ERR(svn_io_check_special_path(target->local_abspath,
474 &target->kind_on_disk, &target->is_symlink,
477 if (target->locally_deleted)
479 const char *moved_to_abspath;
481 SVN_ERR(svn_wc__node_was_moved_away(&moved_to_abspath, NULL,
482 wc_ctx, target->local_abspath,
483 result_pool, scratch_pool));
484 if (moved_to_abspath)
486 target->local_abspath = moved_to_abspath;
487 target->local_relpath = svn_dirent_skip_ancestor(wcroot_abspath,
489 SVN_ERR_ASSERT(target->local_relpath &&
490 target->local_relpath[0] != '\0');
492 /* As far as we are concerned this target is not locally deleted. */
493 target->locally_deleted = FALSE;
495 SVN_ERR(svn_io_check_special_path(target->local_abspath,
496 &target->kind_on_disk,
500 else if (target->kind_on_disk != svn_node_none)
502 target->skipped = TRUE;
510 /* Baton for reading from properties. */
511 typedef struct prop_read_baton_t {
512 const svn_string_t *value;
516 /* Allocate *STRINGBUF in RESULT_POOL, and read into it one line from
517 * the unpatched property value accessed via BATON.
518 * Reading stops either after a line-terminator was found, or if
519 * the property value runs out in which case *EOF is set to TRUE.
520 * The line-terminator is not stored in *STRINGBUF.
522 * If the line is empty or could not be read, *line is set to NULL.
524 * The line-terminator is detected automatically and stored in *EOL
525 * if EOL is not NULL. If the end of the property value is reached
526 * and does not end with a newline character, and EOL is not NULL,
527 * *EOL is set to NULL.
529 * SCRATCH_POOL is used for temporary allocations.
532 readline_prop(void *baton, svn_stringbuf_t **line, const char **eol_str,
533 svn_boolean_t *eof, apr_pool_t *result_pool,
534 apr_pool_t *scratch_pool)
536 prop_read_baton_t *b = (prop_read_baton_t *)baton;
537 svn_stringbuf_t *str = NULL;
539 svn_boolean_t found_eof;
541 if ((apr_uint64_t)b->offset >= (apr_uint64_t)b->value->len)
549 /* Read bytes into STR up to and including, but not storing,
550 * the next EOL sequence. */
555 c = b->value->data + b->offset;
570 if (*(c + 1) == '\n')
579 str = svn_stringbuf_create_ensure(80, result_pool);
580 svn_stringbuf_appendbyte(str, *c);
586 while (c < b->value->data + b->value->len);
595 /* Return in *OFFSET the current byte offset for reading from the
596 * unpatched property value accessed via BATON.
597 * Use SCRATCH_POOL for temporary allocations. */
599 tell_prop(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool)
601 prop_read_baton_t *b = (prop_read_baton_t *)baton;
606 /* Seek to the specified by OFFSET in the unpatched property value accessed
607 * via BATON. Use SCRATCH_POOL for temporary allocations. */
609 seek_prop(void *baton, apr_off_t offset, apr_pool_t *scratch_pool)
611 prop_read_baton_t *b = (prop_read_baton_t *)baton;
616 /* Write LEN bytes from BUF into the patched property value accessed
617 * via BATON. Use SCRATCH_POOL for temporary allocations. */
619 write_prop(void *baton, const char *buf, apr_size_t len,
620 apr_pool_t *scratch_pool)
622 svn_stringbuf_t *patched_value = (svn_stringbuf_t *)baton;
623 svn_stringbuf_appendbytes(patched_value, buf, len);
627 /* Initialize a PROP_TARGET structure for PROP_NAME on the patch target
628 * at LOCAL_ABSPATH. OPERATION indicates the operation performed on the
629 * property. Use working copy context WC_CTX.
630 * Allocate results in RESULT_POOL.
631 * Use SCRATCH_POOL for temporary allocations. */
633 init_prop_target(prop_patch_target_t **prop_target,
634 const char *prop_name,
635 svn_diff_operation_kind_t operation,
636 svn_wc_context_t *wc_ctx,
637 const char *local_abspath,
638 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
640 prop_patch_target_t *new_prop_target;
641 target_content_t *content;
642 const svn_string_t *value;
644 prop_read_baton_t *prop_read_baton;
646 content = apr_pcalloc(result_pool, sizeof(*content));
648 /* All other fields are FALSE or NULL due to apr_pcalloc(). */
649 content->current_line = 1;
650 content->eol_style = svn_subst_eol_style_none;
651 content->lines = apr_array_make(result_pool, 0, sizeof(apr_off_t));
652 content->hunks = apr_array_make(result_pool, 0, sizeof(hunk_info_t *));
653 content->keywords = apr_hash_make(result_pool);
655 new_prop_target = apr_pcalloc(result_pool, sizeof(*new_prop_target));
656 new_prop_target->name = apr_pstrdup(result_pool, prop_name);
657 new_prop_target->operation = operation;
658 new_prop_target->content = content;
660 err = svn_wc_prop_get2(&value, wc_ctx, local_abspath, prop_name,
661 result_pool, scratch_pool);
664 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
666 svn_error_clear(err);
670 return svn_error_trace(err);
672 content->existed = (value != NULL);
673 new_prop_target->value = value;
674 new_prop_target->patched_value = svn_stringbuf_create_empty(result_pool);
677 /* Wire up the read and write callbacks. */
678 prop_read_baton = apr_pcalloc(result_pool, sizeof(*prop_read_baton));
679 prop_read_baton->value = value;
680 prop_read_baton->offset = 0;
681 content->readline = readline_prop;
682 content->tell = tell_prop;
683 content->seek = seek_prop;
684 content->read_baton = prop_read_baton;
685 content->write = write_prop;
686 content->write_baton = new_prop_target->patched_value;
688 *prop_target = new_prop_target;
693 /* Allocate *STRINGBUF in RESULT_POOL, and read into it one line from
694 * the unpatched file content accessed via BATON.
695 * Reading stops either after a line-terminator was found,
696 * or if EOF is reached in which case *EOF is set to TRUE.
697 * The line-terminator is not stored in *STRINGBUF.
699 * If the line is empty or could not be read, *line is set to NULL.
701 * The line-terminator is detected automatically and stored in *EOL
702 * if EOL is not NULL. If EOF is reached and FILE does not end
703 * with a newline character, and EOL is not NULL, *EOL is set to NULL.
705 * SCRATCH_POOL is used for temporary allocations.
708 readline_file(void *baton, svn_stringbuf_t **line, const char **eol_str,
709 svn_boolean_t *eof, apr_pool_t *result_pool,
710 apr_pool_t *scratch_pool)
712 apr_file_t *file = (apr_file_t *)baton;
713 svn_stringbuf_t *str = NULL;
716 svn_boolean_t found_eof;
718 /* Read bytes into STR up to and including, but not storing,
719 * the next EOL sequence. */
725 SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes,
726 &found_eof, scratch_pool));
745 /* Check for "\r\n" by peeking at the next byte. */
747 SVN_ERR(svn_io_file_seek(file, APR_CUR, &pos, scratch_pool));
748 SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes,
749 &found_eof, scratch_pool));
750 if (numbytes == 1 && c == '\n')
756 /* Pretend we never peeked. */
757 SVN_ERR(svn_io_file_seek(file, APR_SET, &pos, scratch_pool));
766 str = svn_stringbuf_create_ensure(80, result_pool);
767 svn_stringbuf_appendbyte(str, c);
781 /* Return in *OFFSET the current byte offset for reading from the
782 * unpatched file content accessed via BATON.
783 * Use SCRATCH_POOL for temporary allocations. */
785 tell_file(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool)
787 apr_file_t *file = (apr_file_t *)baton;
789 SVN_ERR(svn_io_file_seek(file, APR_CUR, offset, scratch_pool));
793 /* Seek to the specified by OFFSET in the unpatched file content accessed
794 * via BATON. Use SCRATCH_POOL for temporary allocations. */
796 seek_file(void *baton, apr_off_t offset, apr_pool_t *scratch_pool)
798 apr_file_t *file = (apr_file_t *)baton;
799 SVN_ERR(svn_io_file_seek(file, APR_SET, &offset, scratch_pool));
803 /* Write LEN bytes from BUF into the patched file content accessed
804 * via BATON. Use SCRATCH_POOL for temporary allocations. */
806 write_file(void *baton, const char *buf, apr_size_t len,
807 apr_pool_t *scratch_pool)
809 apr_file_t *file = (apr_file_t *)baton;
810 SVN_ERR(svn_io_file_write_full(file, buf, len, &len, scratch_pool));
814 /* Handling symbolic links:
816 * In Subversion, symlinks can be represented on disk in two distinct ways.
817 * On systems which support symlinks, a symlink is created on disk.
818 * On systems which do not support symlink, a file is created on disk
819 * which contains the "normal form" of the symlink, which looks like:
821 * where TARGET is the file the symlink points to.
823 * When reading symlinks (i.e. the link itself, not the file the symlink
824 * is pointing to) through the svn_subst_create_specialfile() function
825 * into a buffer, the buffer always contains the "normal form" of the symlink.
826 * Due to this representation symlinks always contain a single line of text.
828 * The functions below are needed to deal with the case where a patch
829 * wants to change the TARGET that a symlink points to.
832 /* Baton for the (readline|tell|seek|write)_symlink functions. */
833 struct symlink_baton_t
835 /* The path to the symlink on disk (not the path to the target of the link) */
836 const char *local_abspath;
838 /* Indicates whether the "normal form" of the symlink has been read. */
839 svn_boolean_t at_eof;
842 /* Allocate *STRINGBUF in RESULT_POOL, and store into it the "normal form"
843 * of the symlink accessed via BATON.
845 * Otherwise behaves like readline_file(), which see.
848 readline_symlink(void *baton, svn_stringbuf_t **line, const char **eol_str,
849 svn_boolean_t *eof, apr_pool_t *result_pool,
850 apr_pool_t *scratch_pool)
852 struct symlink_baton_t *sb = baton;
867 SVN_ERR(svn_io_read_link(&dest, sb->local_abspath, scratch_pool));
868 *line = svn_stringbuf_createf(result_pool, "link %s", dest->data);
875 /* Set *OFFSET to 1 or 0 depending on whether the "normal form" of
876 * the symlink has already been read. */
878 tell_symlink(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool)
880 struct symlink_baton_t *sb = baton;
882 *offset = sb->at_eof ? 1 : 0;
886 /* If offset is non-zero, mark the symlink as having been read in its
887 * "normal form". Else, mark the symlink as not having been read yet. */
889 seek_symlink(void *baton, apr_off_t offset, apr_pool_t *scratch_pool)
891 struct symlink_baton_t *sb = baton;
893 sb->at_eof = (offset != 0);
898 /* Set the target of the symlink accessed via BATON.
899 * The contents of BUF must be a valid "normal form" of a symlink. */
901 write_symlink(void *baton, const char *buf, apr_size_t len,
902 apr_pool_t *scratch_pool)
904 const char *target_abspath = baton;
905 const char *new_name;
906 const char *link = apr_pstrndup(scratch_pool, buf, len);
908 if (strncmp(link, "link ", 5) != 0)
909 return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL,
910 _("Invalid link representation"));
912 link += 5; /* Skip "link " */
914 /* We assume the entire symlink is written at once, as the patch
915 format is line based */
917 SVN_ERR(svn_io_create_unique_link(&new_name, target_abspath, link,
918 ".tmp", scratch_pool));
920 SVN_ERR(svn_io_file_rename(new_name, target_abspath, scratch_pool));
926 /* Return a suitable filename for the target of PATCH.
927 * Examine the ``old'' and ``new'' file names, and choose the file name
928 * with the fewest path components, the shortest basename, and the shortest
929 * total file name length (in that order). In case of a tie, return the new
930 * filename. This heuristic is also used by Larry Wall's UNIX patch (except
931 * that it prompts for a filename in case of a tie).
932 * Additionally, for compatibility with git, if one of the filenames
933 * is "/dev/null", use the other filename. */
935 choose_target_filename(const svn_patch_t *patch)
940 if (strcmp(patch->old_filename, "/dev/null") == 0)
941 return patch->new_filename;
942 if (strcmp(patch->new_filename, "/dev/null") == 0)
943 return patch->old_filename;
945 old = svn_path_component_count(patch->old_filename);
946 new = svn_path_component_count(patch->new_filename);
950 old = strlen(svn_dirent_basename(patch->old_filename, NULL));
951 new = strlen(svn_dirent_basename(patch->new_filename, NULL));
955 old = strlen(patch->old_filename);
956 new = strlen(patch->new_filename);
960 return (old < new) ? patch->old_filename : patch->new_filename;
963 /* Attempt to initialize a *PATCH_TARGET structure for a target file
964 * described by PATCH. Use working copy context WC_CTX.
965 * STRIP_COUNT specifies the number of leading path components
966 * which should be stripped from target paths in the patch.
967 * The patch target structure is allocated in RESULT_POOL, but if the target
968 * should be skipped, PATCH_TARGET->SKIPPED is set and the target should be
969 * treated as not fully initialized, e.g. the caller should not not do any
970 * further operations on the target if it is marked to be skipped.
971 * If REMOVE_TEMPFILES is TRUE, set up temporary files to be removed as
972 * soon as they are no longer needed.
973 * Use SCRATCH_POOL for all other allocations. */
975 init_patch_target(patch_target_t **patch_target,
976 const svn_patch_t *patch,
977 const char *wcroot_abspath,
978 svn_wc_context_t *wc_ctx, int strip_count,
979 svn_boolean_t remove_tempfiles,
980 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
982 patch_target_t *target;
983 target_content_t *content;
984 svn_boolean_t has_prop_changes = FALSE;
985 svn_boolean_t prop_changes_only = FALSE;
988 apr_hash_index_t *hi;
990 for (hi = apr_hash_first(scratch_pool, patch->prop_patches);
992 hi = apr_hash_next(hi))
994 svn_prop_patch_t *prop_patch = svn__apr_hash_index_val(hi);
995 if (! has_prop_changes)
996 has_prop_changes = prop_patch->hunks->nelts > 0;
1002 prop_changes_only = has_prop_changes && patch->hunks->nelts == 0;
1004 content = apr_pcalloc(result_pool, sizeof(*content));
1006 /* All other fields in content are FALSE or NULL due to apr_pcalloc().*/
1007 content->current_line = 1;
1008 content->eol_style = svn_subst_eol_style_none;
1009 content->lines = apr_array_make(result_pool, 0, sizeof(apr_off_t));
1010 content->hunks = apr_array_make(result_pool, 0, sizeof(hunk_info_t *));
1011 content->keywords = apr_hash_make(result_pool);
1013 target = apr_pcalloc(result_pool, sizeof(*target));
1015 /* All other fields in target are FALSE or NULL due to apr_pcalloc(). */
1016 target->db_kind = svn_node_none;
1017 target->kind_on_disk = svn_node_none;
1018 target->content = content;
1019 target->prop_targets = apr_hash_make(result_pool);
1021 SVN_ERR(resolve_target_path(target, choose_target_filename(patch),
1022 wcroot_abspath, strip_count, prop_changes_only,
1023 wc_ctx, result_pool, scratch_pool));
1024 if (! target->skipped)
1026 const char *diff_header;
1029 /* Create a temporary file to write the patched result to.
1030 * Also grab various bits of information about the file. */
1031 if (target->is_symlink)
1033 struct symlink_baton_t *sb = apr_pcalloc(result_pool, sizeof(*sb));
1034 content->existed = TRUE;
1036 sb->local_abspath = target->local_abspath;
1038 /* Wire up the read callbacks. */
1039 content->read_baton = sb;
1041 content->readline = readline_symlink;
1042 content->seek = seek_symlink;
1043 content->tell = tell_symlink;
1045 else if (target->kind_on_disk == svn_node_file)
1047 SVN_ERR(svn_io_file_open(&target->file, target->local_abspath,
1048 APR_READ | APR_BUFFERED,
1049 APR_OS_DEFAULT, result_pool));
1050 SVN_ERR(svn_wc_text_modified_p2(&target->local_mods, wc_ctx,
1051 target->local_abspath, FALSE,
1053 SVN_ERR(svn_io_is_file_executable(&target->executable,
1054 target->local_abspath,
1056 SVN_ERR(obtain_eol_and_keywords_for_file(&content->keywords,
1057 &content->eol_style,
1060 target->local_abspath,
1063 content->existed = TRUE;
1065 /* Wire up the read callbacks. */
1066 content->readline = readline_file;
1067 content->seek = seek_file;
1068 content->tell = tell_file;
1069 content->read_baton = target->file;
1072 /* ### Is it ok to set the operation of the target already here? Isn't
1073 * ### the target supposed to be marked with an operation after we have
1074 * ### determined that the changes will apply cleanly to the WC? Maybe
1075 * ### we should have kept the patch field in patch_target_t to be
1076 * ### able to distinguish between 'what the patch says we should do'
1077 * ### and 'what we can do with the given state of our WC'. */
1078 if (patch->operation == svn_diff_op_added)
1079 target->added = TRUE;
1080 else if (patch->operation == svn_diff_op_deleted)
1081 target->deleted = TRUE;
1083 if (! target->is_symlink)
1085 /* Open a temporary file to write the patched result to. */
1086 SVN_ERR(svn_io_open_unique_file3(&target->patched_file,
1087 &target->patched_path, NULL,
1089 svn_io_file_del_on_pool_cleanup :
1090 svn_io_file_del_none,
1091 result_pool, scratch_pool));
1093 /* Put the write callback in place. */
1094 content->write = write_file;
1095 content->write_baton = target->patched_file;
1099 /* Put the write callback in place. */
1100 SVN_ERR(svn_io_open_unique_file3(NULL,
1101 &target->patched_path, NULL,
1103 svn_io_file_del_on_pool_cleanup :
1104 svn_io_file_del_none,
1105 result_pool, scratch_pool));
1107 content->write_baton = (void*)target->patched_path;
1109 content->write = write_symlink;
1112 /* Open a temporary file to write rejected hunks to. */
1113 SVN_ERR(svn_io_open_unique_file3(&target->reject_file,
1114 &target->reject_path, NULL,
1116 svn_io_file_del_on_pool_cleanup :
1117 svn_io_file_del_none,
1118 result_pool, scratch_pool));
1120 /* The reject file needs a diff header. */
1121 diff_header = apr_psprintf(scratch_pool, "--- %s%s+++ %s%s",
1122 target->canon_path_from_patchfile,
1124 target->canon_path_from_patchfile,
1126 len = strlen(diff_header);
1127 SVN_ERR(svn_io_file_write_full(target->reject_file, diff_header, len,
1128 &len, scratch_pool));
1130 /* Handle properties. */
1131 if (! target->skipped)
1133 apr_hash_index_t *hi;
1135 for (hi = apr_hash_first(result_pool, patch->prop_patches);
1137 hi = apr_hash_next(hi))
1139 const char *prop_name = svn__apr_hash_index_key(hi);
1140 svn_prop_patch_t *prop_patch = svn__apr_hash_index_val(hi);
1141 prop_patch_target_t *prop_target;
1143 SVN_ERR(init_prop_target(&prop_target,
1145 prop_patch->operation,
1146 wc_ctx, target->local_abspath,
1147 result_pool, scratch_pool));
1148 svn_hash_sets(target->prop_targets, prop_name, prop_target);
1153 *patch_target = target;
1154 return SVN_NO_ERROR;
1157 /* Read a *LINE from CONTENT. If the line has not been read before
1158 * mark the line in CONTENT->LINES.
1159 * If a line could be read successfully, increase CONTENT->CURRENT_LINE,
1160 * and allocate *LINE in RESULT_POOL.
1161 * Do temporary allocations in SCRATCH_POOL.
1163 static svn_error_t *
1164 readline(target_content_t *content,
1166 apr_pool_t *result_pool,
1167 apr_pool_t *scratch_pool)
1169 svn_stringbuf_t *line_raw;
1170 const char *eol_str;
1171 svn_linenum_t max_line = (svn_linenum_t)content->lines->nelts + 1;
1173 if (content->eof || content->readline == NULL)
1176 return SVN_NO_ERROR;
1179 SVN_ERR_ASSERT(content->current_line <= max_line);
1180 if (content->current_line == max_line)
1184 SVN_ERR(content->tell(content->read_baton, &offset,
1186 APR_ARRAY_PUSH(content->lines, apr_off_t) = offset;
1189 SVN_ERR(content->readline(content->read_baton, &line_raw,
1190 &eol_str, &content->eof,
1191 result_pool, scratch_pool));
1192 if (content->eol_style == svn_subst_eol_style_none)
1193 content->eol_str = eol_str;
1197 /* Contract keywords. */
1198 SVN_ERR(svn_subst_translate_cstring2(line_raw->data, line,
1200 content->keywords, FALSE,
1206 if ((line_raw && line_raw->len > 0) || eol_str)
1207 content->current_line++;
1209 SVN_ERR_ASSERT(content->current_line > 0);
1211 return SVN_NO_ERROR;
1214 /* Seek to the specified LINE in CONTENT.
1215 * Mark any lines not read before in CONTENT->LINES.
1216 * Do temporary allocations in SCRATCH_POOL.
1218 static svn_error_t *
1219 seek_to_line(target_content_t *content, svn_linenum_t line,
1220 apr_pool_t *scratch_pool)
1222 svn_linenum_t saved_line;
1223 svn_boolean_t saved_eof;
1225 SVN_ERR_ASSERT(line > 0);
1227 if (line == content->current_line)
1228 return SVN_NO_ERROR;
1230 saved_line = content->current_line;
1231 saved_eof = content->eof;
1233 if (line <= (svn_linenum_t)content->lines->nelts)
1237 offset = APR_ARRAY_IDX(content->lines, line - 1, apr_off_t);
1238 SVN_ERR(content->seek(content->read_baton, offset,
1240 content->current_line = line;
1245 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1247 while (! content->eof && content->current_line < line)
1249 svn_pool_clear(iterpool);
1250 SVN_ERR(readline(content, &dummy, iterpool, iterpool));
1252 svn_pool_destroy(iterpool);
1255 /* After seeking backwards from EOF position clear EOF indicator. */
1256 if (saved_eof && saved_line > content->current_line)
1257 content->eof = FALSE;
1259 return SVN_NO_ERROR;
1262 /* Indicate in *MATCHED whether the original text of HUNK matches the patch
1263 * CONTENT at its current line. Lines within FUZZ lines of the start or
1264 * end of HUNK will always match. If IGNORE_WHITESPACE is set, we ignore
1265 * whitespace when doing the matching. When this function returns, neither
1266 * CONTENT->CURRENT_LINE nor the file offset in the target file will
1267 * have changed. If MATCH_MODIFIED is TRUE, match the modified hunk text,
1268 * rather than the original hunk text.
1269 * Do temporary allocations in POOL. */
1270 static svn_error_t *
1271 match_hunk(svn_boolean_t *matched, target_content_t *content,
1272 svn_diff_hunk_t *hunk, svn_linenum_t fuzz,
1273 svn_boolean_t ignore_whitespace,
1274 svn_boolean_t match_modified, apr_pool_t *pool)
1276 svn_stringbuf_t *hunk_line;
1277 const char *target_line;
1278 svn_linenum_t lines_read;
1279 svn_linenum_t saved_line;
1280 svn_boolean_t hunk_eof;
1281 svn_boolean_t lines_matched;
1282 apr_pool_t *iterpool;
1283 svn_linenum_t hunk_length;
1284 svn_linenum_t leading_context;
1285 svn_linenum_t trailing_context;
1290 return SVN_NO_ERROR;
1292 saved_line = content->current_line;
1294 lines_matched = FALSE;
1295 leading_context = svn_diff_hunk_get_leading_context(hunk);
1296 trailing_context = svn_diff_hunk_get_trailing_context(hunk);
1299 svn_diff_hunk_reset_modified_text(hunk);
1300 hunk_length = svn_diff_hunk_get_modified_length(hunk);
1304 svn_diff_hunk_reset_original_text(hunk);
1305 hunk_length = svn_diff_hunk_get_original_length(hunk);
1307 iterpool = svn_pool_create(pool);
1310 const char *hunk_line_translated;
1312 svn_pool_clear(iterpool);
1315 SVN_ERR(svn_diff_hunk_readline_modified_text(hunk, &hunk_line,
1317 iterpool, iterpool));
1319 SVN_ERR(svn_diff_hunk_readline_original_text(hunk, &hunk_line,
1321 iterpool, iterpool));
1323 /* Contract keywords, if any, before matching. */
1324 SVN_ERR(svn_subst_translate_cstring2(hunk_line->data,
1325 &hunk_line_translated,
1327 content->keywords, FALSE,
1329 SVN_ERR(readline(content, &target_line, iterpool, iterpool));
1333 /* If the last line doesn't have a newline, we get EOF but still
1334 * have a non-empty line to compare. */
1335 if ((hunk_eof && hunk_line->len == 0) ||
1336 (content->eof && *target_line == 0))
1339 /* Leading/trailing fuzzy lines always match. */
1340 if ((lines_read <= fuzz && leading_context > fuzz) ||
1341 (lines_read > hunk_length - fuzz && trailing_context > fuzz))
1342 lines_matched = TRUE;
1345 if (ignore_whitespace)
1347 char *hunk_line_trimmed;
1348 char *target_line_trimmed;
1350 hunk_line_trimmed = apr_pstrdup(iterpool, hunk_line_translated);
1351 target_line_trimmed = apr_pstrdup(iterpool, target_line);
1352 apr_collapse_spaces(hunk_line_trimmed, hunk_line_trimmed);
1353 apr_collapse_spaces(target_line_trimmed, target_line_trimmed);
1354 lines_matched = ! strcmp(hunk_line_trimmed, target_line_trimmed);
1357 lines_matched = ! strcmp(hunk_line_translated, target_line);
1360 while (lines_matched);
1362 *matched = lines_matched && hunk_eof && hunk_line->len == 0;
1363 SVN_ERR(seek_to_line(content, saved_line, iterpool));
1364 svn_pool_destroy(iterpool);
1366 return SVN_NO_ERROR;
1369 /* Scan lines of CONTENT for a match of the original text of HUNK,
1370 * up to but not including the specified UPPER_LINE. Use fuzz factor FUZZ.
1371 * If UPPER_LINE is zero scan until EOF occurs when reading from TARGET.
1372 * Return the line at which HUNK was matched in *MATCHED_LINE.
1373 * If the hunk did not match at all, set *MATCHED_LINE to zero.
1374 * If the hunk matched multiple times, and MATCH_FIRST is TRUE,
1375 * return the line number at which the first match occurred in *MATCHED_LINE.
1376 * If the hunk matched multiple times, and MATCH_FIRST is FALSE,
1377 * return the line number at which the last match occurred in *MATCHED_LINE.
1378 * If IGNORE_WHITESPACE is set, ignore whitespace during the matching.
1379 * If MATCH_MODIFIED is TRUE, match the modified hunk text,
1380 * rather than the original hunk text.
1381 * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
1382 * Do all allocations in POOL. */
1383 static svn_error_t *
1384 scan_for_match(svn_linenum_t *matched_line,
1385 target_content_t *content,
1386 svn_diff_hunk_t *hunk, svn_boolean_t match_first,
1387 svn_linenum_t upper_line, svn_linenum_t fuzz,
1388 svn_boolean_t ignore_whitespace,
1389 svn_boolean_t match_modified,
1390 svn_cancel_func_t cancel_func, void *cancel_baton,
1393 apr_pool_t *iterpool;
1396 iterpool = svn_pool_create(pool);
1397 while ((content->current_line < upper_line || upper_line == 0) &&
1400 svn_boolean_t matched;
1402 svn_pool_clear(iterpool);
1405 SVN_ERR(cancel_func(cancel_baton));
1407 SVN_ERR(match_hunk(&matched, content, hunk, fuzz, ignore_whitespace,
1408 match_modified, iterpool));
1411 svn_boolean_t taken = FALSE;
1414 /* Don't allow hunks to match at overlapping locations. */
1415 for (i = 0; i < content->hunks->nelts; i++)
1417 const hunk_info_t *hi;
1418 svn_linenum_t length;
1420 hi = APR_ARRAY_IDX(content->hunks, i, const hunk_info_t *);
1423 length = svn_diff_hunk_get_modified_length(hi->hunk);
1425 length = svn_diff_hunk_get_original_length(hi->hunk);
1427 taken = (! hi->rejected &&
1428 content->current_line >= hi->matched_line &&
1429 content->current_line < (hi->matched_line + length));
1436 *matched_line = content->current_line;
1443 SVN_ERR(seek_to_line(content, content->current_line + 1,
1446 svn_pool_destroy(iterpool);
1448 return SVN_NO_ERROR;
1451 /* Indicate in *MATCH whether the content described by CONTENT
1452 * matches the modified text of HUNK.
1453 * Use SCRATCH_POOL for temporary allocations. */
1454 static svn_error_t *
1455 match_existing_target(svn_boolean_t *match,
1456 target_content_t *content,
1457 svn_diff_hunk_t *hunk,
1458 apr_pool_t *scratch_pool)
1460 svn_boolean_t lines_matched;
1461 apr_pool_t *iterpool;
1462 svn_boolean_t hunk_eof;
1463 svn_linenum_t saved_line;
1465 svn_diff_hunk_reset_modified_text(hunk);
1467 saved_line = content->current_line;
1469 iterpool = svn_pool_create(scratch_pool);
1473 svn_stringbuf_t *hunk_line;
1474 const char *line_translated;
1475 const char *hunk_line_translated;
1477 svn_pool_clear(iterpool);
1479 SVN_ERR(readline(content, &line, iterpool, iterpool));
1480 SVN_ERR(svn_diff_hunk_readline_modified_text(hunk, &hunk_line,
1482 iterpool, iterpool));
1483 /* Contract keywords. */
1484 SVN_ERR(svn_subst_translate_cstring2(line, &line_translated,
1488 SVN_ERR(svn_subst_translate_cstring2(hunk_line->data,
1489 &hunk_line_translated,
1493 lines_matched = ! strcmp(line_translated, hunk_line_translated);
1494 if (content->eof != hunk_eof)
1496 svn_pool_destroy(iterpool);
1498 return SVN_NO_ERROR;
1501 while (lines_matched && ! content->eof && ! hunk_eof);
1502 svn_pool_destroy(iterpool);
1504 *match = (lines_matched && content->eof == hunk_eof);
1505 SVN_ERR(seek_to_line(content, saved_line, scratch_pool));
1507 return SVN_NO_ERROR;
1510 /* Determine the line at which a HUNK applies to CONTENT of the TARGET
1511 * file, and return an appropriate hunk_info object in *HI, allocated from
1512 * RESULT_POOL. Use fuzz factor FUZZ. Set HI->FUZZ to FUZZ. If no correct
1513 * line can be determined, set HI->REJECTED to TRUE.
1514 * IGNORE_WHITESPACE tells whether whitespace should be considered when
1515 * matching. IS_PROP_HUNK indicates whether the hunk patches file content
1517 * When this function returns, neither CONTENT->CURRENT_LINE nor
1518 * the file offset in the target file will have changed.
1519 * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
1520 * Do temporary allocations in POOL. */
1521 static svn_error_t *
1522 get_hunk_info(hunk_info_t **hi, patch_target_t *target,
1523 target_content_t *content,
1524 svn_diff_hunk_t *hunk, svn_linenum_t fuzz,
1525 svn_boolean_t ignore_whitespace,
1526 svn_boolean_t is_prop_hunk,
1527 svn_cancel_func_t cancel_func, void *cancel_baton,
1528 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
1530 svn_linenum_t matched_line;
1531 svn_linenum_t original_start;
1532 svn_boolean_t already_applied;
1534 original_start = svn_diff_hunk_get_original_start(hunk);
1535 already_applied = FALSE;
1537 /* An original offset of zero means that this hunk wants to create
1538 * a new file. Don't bother matching hunks in that case, since
1539 * the hunk applies at line 1. If the file already exists, the hunk
1540 * is rejected, unless the file is versioned and its content matches
1541 * the file the patch wants to create. */
1542 if (original_start == 0 && fuzz > 0)
1544 matched_line = 0; /* reject any fuzz for new files */
1546 else if (original_start == 0 && ! is_prop_hunk)
1548 if (target->kind_on_disk == svn_node_file)
1550 const svn_io_dirent2_t *dirent;
1551 SVN_ERR(svn_io_stat_dirent2(&dirent, target->local_abspath, FALSE,
1552 TRUE, scratch_pool, scratch_pool));
1554 if (dirent->kind == svn_node_file
1556 && dirent->filesize == 0)
1558 matched_line = 1; /* Matched an on-disk empty file */
1562 if (target->db_kind == svn_node_file)
1564 svn_boolean_t file_matches;
1566 /* ### I can't reproduce anything but a no-match here.
1567 The content is already at eof, so any hunk fails */
1568 SVN_ERR(match_existing_target(&file_matches, content, hunk,
1573 already_applied = TRUE;
1576 matched_line = 0; /* reject */
1579 matched_line = 0; /* reject */
1585 /* Same conditions apply as for the file case above.
1587 * ### Since the hunk says the prop should be added we just assume so for
1588 * ### now and don't bother with storing the previous lines and such. When
1589 * ### we have the diff operation available we can just check for adds. */
1590 else if (original_start == 0 && is_prop_hunk)
1592 if (content->existed)
1594 svn_boolean_t prop_matches;
1596 SVN_ERR(match_existing_target(&prop_matches, content, hunk,
1602 already_applied = TRUE;
1605 matched_line = 0; /* reject */
1610 else if (original_start > 0 && content->existed)
1612 svn_linenum_t saved_line = content->current_line;
1614 /* Scan for a match at the line where the hunk thinks it
1615 * should be going. */
1616 SVN_ERR(seek_to_line(content, original_start, scratch_pool));
1617 if (content->current_line != original_start)
1623 SVN_ERR(scan_for_match(&matched_line, content, hunk, TRUE,
1624 original_start + 1, fuzz,
1625 ignore_whitespace, FALSE,
1626 cancel_func, cancel_baton,
1629 if (matched_line != original_start)
1631 /* Check if the hunk is already applied.
1632 * We only check for an exact match here, and don't bother checking
1633 * for already applied patches with offset/fuzz, because such a
1634 * check would be ambiguous. */
1637 svn_linenum_t modified_start;
1639 modified_start = svn_diff_hunk_get_modified_start(hunk);
1640 if (modified_start == 0)
1642 /* Patch wants to delete the file. */
1643 already_applied = target->locally_deleted;
1647 SVN_ERR(seek_to_line(content, modified_start,
1649 SVN_ERR(scan_for_match(&matched_line, content,
1652 fuzz, ignore_whitespace, TRUE,
1653 cancel_func, cancel_baton,
1655 already_applied = (matched_line == modified_start);
1659 already_applied = FALSE;
1661 if (! already_applied)
1663 /* Scan the whole file again from the start. */
1664 SVN_ERR(seek_to_line(content, 1, scratch_pool));
1666 /* Scan forward towards the hunk's line and look for a line
1667 * where the hunk matches. */
1668 SVN_ERR(scan_for_match(&matched_line, content, hunk, FALSE,
1669 original_start, fuzz,
1670 ignore_whitespace, FALSE,
1671 cancel_func, cancel_baton,
1674 /* In tie-break situations, we arbitrarily prefer early matches
1675 * to save us from scanning the rest of the file. */
1676 if (matched_line == 0)
1678 /* Scan forward towards the end of the file and look
1679 * for a line where the hunk matches. */
1680 SVN_ERR(scan_for_match(&matched_line, content, hunk,
1681 TRUE, 0, fuzz, ignore_whitespace,
1682 FALSE, cancel_func, cancel_baton,
1688 SVN_ERR(seek_to_line(content, saved_line, scratch_pool));
1692 /* The hunk wants to modify a file which doesn't exist. */
1696 (*hi) = apr_pcalloc(result_pool, sizeof(hunk_info_t));
1698 (*hi)->matched_line = matched_line;
1699 (*hi)->rejected = (matched_line == 0);
1700 (*hi)->already_applied = already_applied;
1703 return SVN_NO_ERROR;
1706 /* Copy lines to the patched content until the specified LINE has been
1707 * reached. Indicate in *EOF whether end-of-file was encountered while
1708 * reading from the target.
1709 * If LINE is zero, copy lines until end-of-file has been reached.
1710 * Do all allocations in POOL. */
1711 static svn_error_t *
1712 copy_lines_to_target(target_content_t *content, svn_linenum_t line,
1715 apr_pool_t *iterpool;
1717 iterpool = svn_pool_create(pool);
1718 while ((content->current_line < line || line == 0) && ! content->eof)
1720 const char *target_line;
1723 svn_pool_clear(iterpool);
1725 SVN_ERR(readline(content, &target_line, iterpool, iterpool));
1727 target_line = apr_pstrcat(iterpool, target_line, content->eol_str,
1729 len = strlen(target_line);
1730 SVN_ERR(content->write(content->write_baton, target_line,
1733 svn_pool_destroy(iterpool);
1735 return SVN_NO_ERROR;
1738 /* Write the diff text of HUNK to TARGET's reject file,
1739 * and mark TARGET as having had rejects.
1740 * We don't expand keywords, nor normalise line-endings, in reject files.
1741 * Do temporary allocations in SCRATCH_POOL. */
1742 static svn_error_t *
1743 reject_hunk(patch_target_t *target, target_content_t *content,
1744 svn_diff_hunk_t *hunk, const char *prop_name,
1747 const char *hunk_header;
1750 static const char * const text_atat = "@@";
1751 static const char * const prop_atat = "##";
1753 apr_pool_t *iterpool;
1757 const char *prop_header;
1759 /* ### Print 'Added', 'Deleted' or 'Modified' instead of 'Property'.
1761 prop_header = apr_psprintf(pool, "Property: %s\n", prop_name);
1762 len = strlen(prop_header);
1763 SVN_ERR(svn_io_file_write_full(target->reject_file, prop_header,
1772 hunk_header = apr_psprintf(pool, "%s -%lu,%lu +%lu,%lu %s%s",
1774 svn_diff_hunk_get_original_start(hunk),
1775 svn_diff_hunk_get_original_length(hunk),
1776 svn_diff_hunk_get_modified_start(hunk),
1777 svn_diff_hunk_get_modified_length(hunk),
1780 len = strlen(hunk_header);
1781 SVN_ERR(svn_io_file_write_full(target->reject_file, hunk_header, len,
1784 iterpool = svn_pool_create(pool);
1787 svn_stringbuf_t *hunk_line;
1788 const char *eol_str;
1790 svn_pool_clear(iterpool);
1792 SVN_ERR(svn_diff_hunk_readline_diff_text(hunk, &hunk_line, &eol_str,
1793 &eof, iterpool, iterpool));
1796 if (hunk_line->len >= 1)
1798 len = hunk_line->len;
1799 SVN_ERR(svn_io_file_write_full(target->reject_file,
1800 hunk_line->data, len, &len,
1806 len = strlen(eol_str);
1807 SVN_ERR(svn_io_file_write_full(target->reject_file, eol_str,
1808 len, &len, iterpool));
1813 svn_pool_destroy(iterpool);
1816 target->had_prop_rejects = TRUE;
1818 target->had_rejects = TRUE;
1820 return SVN_NO_ERROR;
1823 /* Write the modified text of the hunk described by HI to the patched
1824 * CONTENT. TARGET is the patch target.
1825 * If PROP_NAME is not NULL, the hunk is assumed to be targeted for
1826 * a property with the given name.
1827 * Do temporary allocations in POOL. */
1828 static svn_error_t *
1829 apply_hunk(patch_target_t *target, target_content_t *content,
1830 hunk_info_t *hi, const char *prop_name, apr_pool_t *pool)
1832 svn_linenum_t lines_read;
1834 apr_pool_t *iterpool;
1836 /* ### Is there a cleaner way to describe if we have an existing target?
1838 if (target->kind_on_disk == svn_node_file || prop_name)
1842 /* Move forward to the hunk's line, copying data as we go.
1843 * Also copy leading lines of context which matched with fuzz.
1844 * The target has changed on the fuzzy-matched lines,
1845 * so we should retain the target's version of those lines. */
1846 SVN_ERR(copy_lines_to_target(content, hi->matched_line + hi->fuzz,
1849 /* Skip the target's version of the hunk.
1850 * Don't skip trailing lines which matched with fuzz. */
1851 line = content->current_line +
1852 svn_diff_hunk_get_original_length(hi->hunk) - (2 * hi->fuzz);
1853 SVN_ERR(seek_to_line(content, line, pool));
1854 if (content->current_line != line && ! content->eof)
1856 /* Seek failed, reject this hunk. */
1857 hi->rejected = TRUE;
1858 SVN_ERR(reject_hunk(target, content, hi->hunk, prop_name, pool));
1859 return SVN_NO_ERROR;
1863 /* Write the hunk's version to the patched result.
1864 * Don't write the lines which matched with fuzz. */
1866 svn_diff_hunk_reset_modified_text(hi->hunk);
1867 iterpool = svn_pool_create(pool);
1870 svn_stringbuf_t *hunk_line;
1871 const char *eol_str;
1873 svn_pool_clear(iterpool);
1875 SVN_ERR(svn_diff_hunk_readline_modified_text(hi->hunk, &hunk_line,
1877 iterpool, iterpool));
1879 if (lines_read > hi->fuzz &&
1880 lines_read <= svn_diff_hunk_get_modified_length(hi->hunk) - hi->fuzz)
1884 if (hunk_line->len >= 1)
1886 len = hunk_line->len;
1887 SVN_ERR(content->write(content->write_baton,
1888 hunk_line->data, len, iterpool));
1893 /* Use the EOL as it was read from the patch file,
1894 * unless the target's EOL style is set by svn:eol-style */
1895 if (content->eol_style != svn_subst_eol_style_none)
1896 eol_str = content->eol_str;
1898 len = strlen(eol_str);
1899 SVN_ERR(content->write(content->write_baton,
1900 eol_str, len, iterpool));
1905 svn_pool_destroy(iterpool);
1908 target->has_prop_changes = TRUE;
1910 target->has_text_changes = TRUE;
1912 return SVN_NO_ERROR;
1915 /* Use client context CTX to send a suitable notification for hunk HI,
1916 * using TARGET to determine the path. If the hunk is a property hunk,
1917 * PROP_NAME must be the name of the property, else NULL.
1918 * Use POOL for temporary allocations. */
1919 static svn_error_t *
1920 send_hunk_notification(const hunk_info_t *hi,
1921 const patch_target_t *target,
1922 const char *prop_name,
1923 const svn_client_ctx_t *ctx,
1926 svn_wc_notify_t *notify;
1927 svn_wc_notify_action_t action;
1929 if (hi->already_applied)
1930 action = svn_wc_notify_patch_hunk_already_applied;
1931 else if (hi->rejected)
1932 action = svn_wc_notify_patch_rejected_hunk;
1934 action = svn_wc_notify_patch_applied_hunk;
1936 notify = svn_wc_create_notify(target->local_abspath
1937 ? target->local_abspath
1938 : target->local_relpath,
1940 notify->hunk_original_start =
1941 svn_diff_hunk_get_original_start(hi->hunk);
1942 notify->hunk_original_length =
1943 svn_diff_hunk_get_original_length(hi->hunk);
1944 notify->hunk_modified_start =
1945 svn_diff_hunk_get_modified_start(hi->hunk);
1946 notify->hunk_modified_length =
1947 svn_diff_hunk_get_modified_length(hi->hunk);
1948 notify->hunk_matched_line = hi->matched_line;
1949 notify->hunk_fuzz = hi->fuzz;
1950 notify->prop_name = prop_name;
1952 (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
1954 return SVN_NO_ERROR;
1957 /* Use client context CTX to send a suitable notification for a patch TARGET.
1958 * Use POOL for temporary allocations. */
1959 static svn_error_t *
1960 send_patch_notification(const patch_target_t *target,
1961 const svn_client_ctx_t *ctx,
1964 svn_wc_notify_t *notify;
1965 svn_wc_notify_action_t action;
1967 if (! ctx->notify_func2)
1968 return SVN_NO_ERROR;
1970 if (target->skipped)
1971 action = svn_wc_notify_skip;
1972 else if (target->deleted)
1973 action = svn_wc_notify_delete;
1974 else if (target->added || target->replaced)
1975 action = svn_wc_notify_add;
1977 action = svn_wc_notify_patch;
1979 notify = svn_wc_create_notify(target->local_abspath ? target->local_abspath
1980 : target->local_relpath,
1982 notify->kind = svn_node_file;
1984 if (action == svn_wc_notify_skip)
1986 if (target->db_kind == svn_node_none ||
1987 target->db_kind == svn_node_unknown)
1988 notify->content_state = svn_wc_notify_state_missing;
1989 else if (target->db_kind == svn_node_dir)
1990 notify->content_state = svn_wc_notify_state_obstructed;
1992 notify->content_state = svn_wc_notify_state_unknown;
1996 if (target->had_rejects)
1997 notify->content_state = svn_wc_notify_state_conflicted;
1998 else if (target->local_mods)
1999 notify->content_state = svn_wc_notify_state_merged;
2000 else if (target->has_text_changes)
2001 notify->content_state = svn_wc_notify_state_changed;
2003 if (target->had_prop_rejects)
2004 notify->prop_state = svn_wc_notify_state_conflicted;
2005 else if (target->has_prop_changes)
2006 notify->prop_state = svn_wc_notify_state_changed;
2009 (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
2011 if (action == svn_wc_notify_patch)
2014 apr_pool_t *iterpool;
2015 apr_hash_index_t *hash_index;
2017 iterpool = svn_pool_create(pool);
2018 for (i = 0; i < target->content->hunks->nelts; i++)
2020 const hunk_info_t *hi;
2022 svn_pool_clear(iterpool);
2024 hi = APR_ARRAY_IDX(target->content->hunks, i, hunk_info_t *);
2026 SVN_ERR(send_hunk_notification(hi, target, NULL /* prop_name */,
2030 for (hash_index = apr_hash_first(pool, target->prop_targets);
2032 hash_index = apr_hash_next(hash_index))
2034 prop_patch_target_t *prop_target;
2036 prop_target = svn__apr_hash_index_val(hash_index);
2038 for (i = 0; i < prop_target->content->hunks->nelts; i++)
2040 const hunk_info_t *hi;
2042 svn_pool_clear(iterpool);
2044 hi = APR_ARRAY_IDX(prop_target->content->hunks, i,
2047 /* Don't notify on the hunk level for added or deleted props. */
2048 if (prop_target->operation != svn_diff_op_added &&
2049 prop_target->operation != svn_diff_op_deleted)
2050 SVN_ERR(send_hunk_notification(hi, target, prop_target->name,
2054 svn_pool_destroy(iterpool);
2057 return SVN_NO_ERROR;
2061 svn_sort__array(apr_array_header_t *array,
2062 int (*comparison_func)(const void *,
2065 qsort(array->elts, array->nelts, array->elt_size, comparison_func);
2068 /* Implements the callback for svn_sort__array. Puts hunks that match
2069 before hunks that do not match, puts hunks that match in order
2070 based on postion matched, puts hunks that do not match in order
2071 based on original position. */
2073 sort_matched_hunks(const void *a, const void *b)
2075 const hunk_info_t *item1 = *((const hunk_info_t * const *)a);
2076 const hunk_info_t *item2 = *((const hunk_info_t * const *)b);
2077 svn_boolean_t matched1 = !item1->rejected && !item1->already_applied;
2078 svn_boolean_t matched2 = !item2->rejected && !item2->already_applied;
2079 svn_linenum_t original1, original2;
2081 if (matched1 && matched2)
2083 /* Both match so use order matched in file. */
2084 if (item1->matched_line > item2->matched_line)
2086 else if (item1->matched_line == item2->matched_line)
2092 /* Only second matches, put it before first. */
2095 /* Only first matches, put it before second. */
2098 /* Neither matches, sort by original_start. */
2099 original1 = svn_diff_hunk_get_original_start(item1->hunk);
2100 original2 = svn_diff_hunk_get_original_start(item2->hunk);
2101 if (original1 > original2)
2103 else if (original1 == original2)
2110 /* Apply a PATCH to a working copy at ABS_WC_PATH and put the result
2111 * into temporary files, to be installed in the working copy later.
2112 * Return information about the patch target in *PATCH_TARGET, allocated
2113 * in RESULT_POOL. Use WC_CTX as the working copy context.
2114 * STRIP_COUNT specifies the number of leading path components
2115 * which should be stripped from target paths in the patch.
2116 * REMOVE_TEMPFILES, PATCH_FUNC, and PATCH_BATON as in svn_client_patch().
2117 * IGNORE_WHITESPACE tells whether whitespace should be considered when
2118 * doing the matching.
2119 * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
2120 * Do temporary allocations in SCRATCH_POOL. */
2121 static svn_error_t *
2122 apply_one_patch(patch_target_t **patch_target, svn_patch_t *patch,
2123 const char *abs_wc_path, svn_wc_context_t *wc_ctx,
2125 svn_boolean_t ignore_whitespace,
2126 svn_boolean_t remove_tempfiles,
2127 svn_client_patch_func_t patch_func,
2129 svn_cancel_func_t cancel_func,
2131 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
2133 patch_target_t *target;
2134 apr_pool_t *iterpool;
2136 static const svn_linenum_t MAX_FUZZ = 2;
2137 apr_hash_index_t *hash_index;
2139 SVN_ERR(init_patch_target(&target, patch, abs_wc_path, wc_ctx, strip_count,
2140 remove_tempfiles, result_pool, scratch_pool));
2141 if (target->skipped)
2143 *patch_target = target;
2144 return SVN_NO_ERROR;
2149 SVN_ERR(patch_func(patch_baton, &target->filtered,
2150 target->canon_path_from_patchfile,
2151 target->patched_path, target->reject_path,
2153 if (target->filtered)
2155 *patch_target = target;
2156 return SVN_NO_ERROR;
2160 iterpool = svn_pool_create(scratch_pool);
2162 for (i = 0; i < patch->hunks->nelts; i++)
2164 svn_diff_hunk_t *hunk;
2166 svn_linenum_t fuzz = 0;
2168 svn_pool_clear(iterpool);
2171 SVN_ERR(cancel_func(cancel_baton));
2173 hunk = APR_ARRAY_IDX(patch->hunks, i, svn_diff_hunk_t *);
2175 /* Determine the line the hunk should be applied at.
2176 * If no match is found initially, try with fuzz. */
2179 SVN_ERR(get_hunk_info(&hi, target, target->content, hunk, fuzz,
2181 FALSE /* is_prop_hunk */,
2182 cancel_func, cancel_baton,
2183 result_pool, iterpool));
2186 while (hi->rejected && fuzz <= MAX_FUZZ && ! hi->already_applied);
2188 APR_ARRAY_PUSH(target->content->hunks, hunk_info_t *) = hi;
2191 /* Hunks are applied in the order determined by the matched line and
2192 this may be different from the order of the original lines. */
2193 svn_sort__array(target->content->hunks, sort_matched_hunks);
2195 /* Apply or reject hunks. */
2196 for (i = 0; i < target->content->hunks->nelts; i++)
2200 svn_pool_clear(iterpool);
2203 SVN_ERR(cancel_func(cancel_baton));
2205 hi = APR_ARRAY_IDX(target->content->hunks, i, hunk_info_t *);
2206 if (hi->already_applied)
2208 else if (hi->rejected)
2209 SVN_ERR(reject_hunk(target, target->content, hi->hunk,
2210 NULL /* prop_name */,
2213 SVN_ERR(apply_hunk(target, target->content, hi,
2214 NULL /* prop_name */, iterpool));
2217 if (target->kind_on_disk == svn_node_file)
2219 /* Copy any remaining lines to target. */
2220 SVN_ERR(copy_lines_to_target(target->content, 0, scratch_pool));
2221 if (! target->content->eof)
2223 /* We could not copy the entire target file to the temporary file,
2224 * and would truncate the target if we copied the temporary file
2225 * on top of it. Skip this target. */
2226 target->skipped = TRUE;
2230 /* Match property hunks. */
2231 for (hash_index = apr_hash_first(scratch_pool, patch->prop_patches);
2233 hash_index = apr_hash_next(hash_index))
2235 svn_prop_patch_t *prop_patch;
2236 const char *prop_name;
2237 prop_patch_target_t *prop_target;
2239 prop_name = svn__apr_hash_index_key(hash_index);
2240 prop_patch = svn__apr_hash_index_val(hash_index);
2242 if (! strcmp(prop_name, SVN_PROP_SPECIAL))
2243 target->is_special = TRUE;
2245 /* We'll store matched hunks in prop_content. */
2246 prop_target = svn_hash_gets(target->prop_targets, prop_name);
2248 for (i = 0; i < prop_patch->hunks->nelts; i++)
2250 svn_diff_hunk_t *hunk;
2252 svn_linenum_t fuzz = 0;
2254 svn_pool_clear(iterpool);
2257 SVN_ERR(cancel_func(cancel_baton));
2259 hunk = APR_ARRAY_IDX(prop_patch->hunks, i, svn_diff_hunk_t *);
2261 /* Determine the line the hunk should be applied at.
2262 * If no match is found initially, try with fuzz. */
2265 SVN_ERR(get_hunk_info(&hi, target, prop_target->content,
2268 TRUE /* is_prop_hunk */,
2269 cancel_func, cancel_baton,
2270 result_pool, iterpool));
2273 while (hi->rejected && fuzz <= MAX_FUZZ && ! hi->already_applied);
2275 APR_ARRAY_PUSH(prop_target->content->hunks, hunk_info_t *) = hi;
2279 /* Apply or reject property hunks. */
2280 for (hash_index = apr_hash_first(scratch_pool, target->prop_targets);
2282 hash_index = apr_hash_next(hash_index))
2284 prop_patch_target_t *prop_target;
2286 prop_target = svn__apr_hash_index_val(hash_index);
2288 for (i = 0; i < prop_target->content->hunks->nelts; i++)
2292 svn_pool_clear(iterpool);
2294 hi = APR_ARRAY_IDX(prop_target->content->hunks, i,
2296 if (hi->already_applied)
2298 else if (hi->rejected)
2299 SVN_ERR(reject_hunk(target, prop_target->content, hi->hunk,
2303 SVN_ERR(apply_hunk(target, prop_target->content, hi,
2308 if (prop_target->content->existed)
2310 /* Copy any remaining lines to target. */
2311 SVN_ERR(copy_lines_to_target(prop_target->content, 0,
2313 if (! prop_target->content->eof)
2315 /* We could not copy the entire target property to the
2316 * temporary file, and would truncate the target if we
2317 * copied the temporary file on top of it. Skip this target. */
2318 target->skipped = TRUE;
2323 svn_pool_destroy(iterpool);
2325 if (!target->is_symlink)
2327 /* Now close files we don't need any longer to get their contents
2329 * But we're not closing the reject file -- it still needed and
2330 * will be closed later in write_out_rejected_hunks(). */
2331 if (target->kind_on_disk == svn_node_file)
2332 SVN_ERR(svn_io_file_close(target->file, scratch_pool));
2334 SVN_ERR(svn_io_file_close(target->patched_file, scratch_pool));
2337 if (! target->skipped)
2339 apr_finfo_t working_file;
2340 apr_finfo_t patched_file;
2342 /* Get sizes of the patched temporary file and the working file.
2343 * We'll need those to figure out whether we should delete the
2345 SVN_ERR(svn_io_stat(&patched_file, target->patched_path,
2346 APR_FINFO_SIZE | APR_FINFO_LINK, scratch_pool));
2347 if (target->kind_on_disk == svn_node_file)
2348 SVN_ERR(svn_io_stat(&working_file, target->local_abspath,
2349 APR_FINFO_SIZE | APR_FINFO_LINK, scratch_pool));
2351 working_file.size = 0;
2353 if (patched_file.size == 0 && working_file.size > 0)
2355 /* If a unidiff removes all lines from a file, that usually
2356 * means deletion, so we can confidently schedule the target
2357 * for deletion. In the rare case where the unidiff was really
2358 * meant to replace a file with an empty one, this may not
2359 * be desirable. But the deletion can easily be reverted and
2360 * creating an empty file manually is not exactly hard either. */
2361 target->deleted = (target->db_kind == svn_node_file);
2363 else if (patched_file.size == 0 && working_file.size == 0)
2365 /* The target was empty or non-existent to begin with
2366 * and no content was changed by patching.
2367 * Report this as skipped if it didn't exist, unless in the special
2368 * case of adding an empty file which has properties set on it or
2369 * adding an empty file with a 'git diff' */
2370 if (target->kind_on_disk == svn_node_none
2371 && ! target->has_prop_changes
2373 target->skipped = TRUE;
2375 else if (patched_file.size > 0 && working_file.size == 0)
2377 /* The patch has created a file. */
2378 if (target->locally_deleted)
2379 target->replaced = TRUE;
2380 else if (target->db_kind == svn_node_none)
2381 target->added = TRUE;
2385 *patch_target = target;
2387 return SVN_NO_ERROR;
2390 /* Try to create missing parent directories for TARGET in the working copy
2391 * rooted at ABS_WC_PATH, and add the parents to version control.
2392 * If the parents cannot be created, mark the target as skipped.
2393 * Use client context CTX. If DRY_RUN is true, do not create missing
2394 * parents but issue notifications only.
2395 * Use SCRATCH_POOL for temporary allocations. */
2396 static svn_error_t *
2397 create_missing_parents(patch_target_t *target,
2398 const char *abs_wc_path,
2399 svn_client_ctx_t *ctx,
2400 svn_boolean_t dry_run,
2401 apr_pool_t *scratch_pool)
2403 const char *local_abspath;
2404 apr_array_header_t *components;
2405 int present_components;
2407 apr_pool_t *iterpool;
2409 /* Check if we can safely create the target's parent. */
2410 local_abspath = abs_wc_path;
2411 components = svn_path_decompose(target->local_relpath, scratch_pool);
2412 present_components = 0;
2413 iterpool = svn_pool_create(scratch_pool);
2414 for (i = 0; i < components->nelts - 1; i++)
2416 const char *component;
2417 svn_node_kind_t wc_kind, disk_kind;
2419 svn_pool_clear(iterpool);
2421 component = APR_ARRAY_IDX(components, i, const char *);
2422 local_abspath = svn_dirent_join(local_abspath, component, scratch_pool);
2424 SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, local_abspath,
2425 FALSE, TRUE, iterpool));
2427 SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, iterpool));
2429 if (disk_kind == svn_node_file || wc_kind == svn_node_file)
2431 /* on-disk files and missing files are obstructions */
2432 target->skipped = TRUE;
2435 else if (disk_kind == svn_node_dir)
2437 if (wc_kind == svn_node_dir)
2438 present_components++;
2441 target->skipped = TRUE;
2445 else if (wc_kind != svn_node_none)
2447 /* Node is missing */
2448 target->skipped = TRUE;
2453 /* It's not a file, it's not a dir...
2458 if (! target->skipped)
2460 local_abspath = abs_wc_path;
2461 for (i = 0; i < present_components; i++)
2463 const char *component;
2464 component = APR_ARRAY_IDX(components, i, const char *);
2465 local_abspath = svn_dirent_join(local_abspath,
2466 component, scratch_pool);
2469 if (!dry_run && present_components < components->nelts - 1)
2470 SVN_ERR(svn_io_make_dir_recursively(
2473 svn_relpath_dirname(target->local_relpath,
2478 for (i = present_components; i < components->nelts - 1; i++)
2480 const char *component;
2482 svn_pool_clear(iterpool);
2484 component = APR_ARRAY_IDX(components, i, const char *);
2485 local_abspath = svn_dirent_join(local_abspath, component,
2489 if (ctx->notify_func2)
2491 /* Just do notification. */
2492 svn_wc_notify_t *notify;
2493 notify = svn_wc_create_notify(local_abspath,
2496 notify->kind = svn_node_dir;
2497 ctx->notify_func2(ctx->notify_baton2, notify,
2503 /* Create the missing component and add it
2504 * to version control. Allow cancellation since we
2505 * have not modified the working copy yet for this
2508 if (ctx->cancel_func)
2509 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
2511 SVN_ERR(svn_wc_add_from_disk2(ctx->wc_ctx, local_abspath,
2513 ctx->notify_func2, ctx->notify_baton2,
2519 svn_pool_destroy(iterpool);
2520 return SVN_NO_ERROR;
2523 /* Install a patched TARGET into the working copy at ABS_WC_PATH.
2524 * Use client context CTX to retrieve WC_CTX, and possibly doing
2525 * notifications. If DRY_RUN is TRUE, don't modify the working copy.
2526 * Do temporary allocations in POOL. */
2527 static svn_error_t *
2528 install_patched_target(patch_target_t *target, const char *abs_wc_path,
2529 svn_client_ctx_t *ctx, svn_boolean_t dry_run,
2532 if (target->deleted)
2536 /* Schedule the target for deletion. Suppress
2537 * notification, we'll do it manually in a minute
2538 * because we also need to notify during dry-run.
2539 * Also suppress cancellation, because we'd rather
2540 * notify about what we did before aborting. */
2541 SVN_ERR(svn_wc_delete4(ctx->wc_ctx, target->local_abspath,
2542 FALSE /* keep_local */, FALSE,
2543 NULL, NULL, NULL, NULL, pool));
2548 svn_node_kind_t parent_db_kind;
2549 if (target->added || target->replaced)
2551 const char *parent_abspath;
2553 parent_abspath = svn_dirent_dirname(target->local_abspath,
2555 /* If the target's parent directory does not yet exist
2556 * we need to create it before we can copy the patched
2557 * result in place. */
2558 SVN_ERR(svn_wc_read_kind2(&parent_db_kind, ctx->wc_ctx,
2559 parent_abspath, FALSE, FALSE, pool));
2561 /* We can't add targets under nodes scheduled for delete, so add
2562 a new directory if needed. */
2563 if (parent_db_kind == svn_node_dir
2564 || parent_db_kind == svn_node_file)
2566 if (parent_db_kind != svn_node_dir)
2567 target->skipped = TRUE;
2570 svn_node_kind_t disk_kind;
2572 SVN_ERR(svn_io_check_path(parent_abspath, &disk_kind, pool));
2573 if (disk_kind != svn_node_dir)
2574 target->skipped = TRUE;
2578 SVN_ERR(create_missing_parents(target, abs_wc_path, ctx,
2584 svn_node_kind_t wc_kind;
2586 /* The target should exist */
2587 SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx,
2588 target->local_abspath,
2589 FALSE, FALSE, pool));
2591 if (target->kind_on_disk == svn_node_none
2592 || wc_kind != target->kind_on_disk)
2594 target->skipped = TRUE;
2598 if (! dry_run && ! target->skipped)
2600 if (target->is_special)
2602 svn_stream_t *stream;
2603 svn_stream_t *patched_stream;
2605 SVN_ERR(svn_stream_open_readonly(&patched_stream,
2606 target->patched_path,
2608 SVN_ERR(svn_subst_create_specialfile(&stream,
2609 target->local_abspath,
2611 SVN_ERR(svn_stream_copy3(patched_stream, stream,
2612 ctx->cancel_func, ctx->cancel_baton,
2617 svn_boolean_t repair_eol;
2619 /* Copy the patched file on top of the target file.
2620 * Always expand keywords in the patched file, but repair EOL
2621 * only if svn:eol-style dictates a particular style. */
2622 repair_eol = (target->content->eol_style ==
2623 svn_subst_eol_style_fixed ||
2624 target->content->eol_style ==
2625 svn_subst_eol_style_native);
2627 SVN_ERR(svn_subst_copy_and_translate4(
2628 target->patched_path, target->local_abspath,
2629 target->content->eol_str, repair_eol,
2630 target->content->keywords,
2631 TRUE /* expand */, FALSE /* special */,
2632 ctx->cancel_func, ctx->cancel_baton, pool));
2635 if (target->added || target->replaced)
2637 /* The target file didn't exist previously,
2638 * so add it to version control.
2639 * Suppress notification, we'll do that later (and also
2640 * during dry-run). Don't allow cancellation because
2641 * we'd rather notify about what we did before aborting. */
2642 SVN_ERR(svn_wc_add_from_disk2(ctx->wc_ctx, target->local_abspath,
2647 /* Restore the target's executable bit if necessary. */
2648 SVN_ERR(svn_io_set_file_executable(target->local_abspath,
2654 return SVN_NO_ERROR;
2657 /* Write out rejected hunks, if any, to TARGET->REJECT_PATH. If DRY_RUN is
2658 * TRUE, don't modify the working copy.
2659 * Do temporary allocations in POOL.
2661 static svn_error_t *
2662 write_out_rejected_hunks(patch_target_t *target,
2663 svn_boolean_t dry_run,
2666 SVN_ERR(svn_io_file_close(target->reject_file, pool));
2668 if (! dry_run && (target->had_rejects || target->had_prop_rejects))
2670 /* Write out rejected hunks, if any. */
2671 SVN_ERR(svn_io_copy_file(target->reject_path,
2672 apr_psprintf(pool, "%s.svnpatch.rej",
2673 target->local_abspath),
2675 /* ### TODO mark file as conflicted. */
2677 return SVN_NO_ERROR;
2680 /* Install the patched properties for TARGET. Use client context CTX to
2681 * retrieve WC_CTX. If DRY_RUN is TRUE, don't modify the working copy.
2682 * Do temporary allocations in SCRATCH_POOL. */
2683 static svn_error_t *
2684 install_patched_prop_targets(patch_target_t *target,
2685 svn_client_ctx_t *ctx, svn_boolean_t dry_run,
2686 apr_pool_t *scratch_pool)
2688 apr_hash_index_t *hi;
2689 apr_pool_t *iterpool;
2691 iterpool = svn_pool_create(scratch_pool);
2693 for (hi = apr_hash_first(scratch_pool, target->prop_targets);
2695 hi = apr_hash_next(hi))
2697 prop_patch_target_t *prop_target = svn__apr_hash_index_val(hi);
2698 const svn_string_t *prop_val;
2701 svn_pool_clear(iterpool);
2703 if (ctx->cancel_func)
2704 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
2706 /* For a deleted prop we only set the value to NULL. */
2707 if (prop_target->operation == svn_diff_op_deleted)
2710 SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, target->local_abspath,
2711 prop_target->name, NULL, svn_depth_empty,
2712 TRUE /* skip_checks */,
2713 NULL /* changelist_filter */,
2714 NULL, NULL /* cancellation */,
2715 NULL, NULL /* notification */,
2720 /* If the patch target doesn't exist yet, the patch wants to add an
2721 * empty file with properties set on it. So create an empty file and
2722 * add it to version control. But if the patch was in the 'git format'
2723 * then the file has already been added.
2725 * ### How can we tell whether the patch really wanted to create
2726 * ### an empty directory? */
2727 if (! target->has_text_changes
2728 && target->kind_on_disk == svn_node_none
2733 SVN_ERR(svn_io_file_create(target->local_abspath, "",
2735 SVN_ERR(svn_wc_add_from_disk2(ctx->wc_ctx, target->local_abspath,
2737 /* suppress notification */
2741 target->added = TRUE;
2744 /* Attempt to set the property, and reject all hunks if this
2745 fails. If the property had a non-empty value, but now has
2746 an empty one, we'll just delete the property altogether. */
2747 if (prop_target->value && prop_target->value->len
2748 && prop_target->patched_value && !prop_target->patched_value->len)
2751 prop_val = svn_stringbuf__morph_into_string(prop_target->patched_value);
2755 const svn_string_t *canon_propval;
2757 err = svn_wc_canonicalize_svn_prop(&canon_propval,
2759 prop_val, target->local_abspath,
2761 TRUE, /* ### Skipping checks */
2767 err = svn_wc_prop_set4(ctx->wc_ctx, target->local_abspath,
2768 prop_target->name, prop_val, svn_depth_empty,
2769 TRUE /* skip_checks */,
2770 NULL /* changelist_filter */,
2771 NULL, NULL /* cancellation */,
2772 NULL, NULL /* notification */,
2778 /* ### The errors which svn_wc_canonicalize_svn_prop() will
2779 * ### return aren't documented. */
2780 if (err->apr_err == SVN_ERR_ILLEGAL_TARGET ||
2781 err->apr_err == SVN_ERR_NODE_UNEXPECTED_KIND ||
2782 err->apr_err == SVN_ERR_IO_UNKNOWN_EOL ||
2783 err->apr_err == SVN_ERR_BAD_MIME_TYPE ||
2784 err->apr_err == SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION)
2788 svn_error_clear(err);
2790 for (i = 0; i < prop_target->content->hunks->nelts; i++)
2792 hunk_info_t *hunk_info;
2794 hunk_info = APR_ARRAY_IDX(prop_target->content->hunks,
2796 hunk_info->rejected = TRUE;
2797 SVN_ERR(reject_hunk(target, prop_target->content,
2798 hunk_info->hunk, prop_target->name,
2803 return svn_error_trace(err);
2808 svn_pool_destroy(iterpool);
2810 return SVN_NO_ERROR;
2813 /* Baton for can_delete_callback */
2814 struct can_delete_baton_t
2816 svn_boolean_t must_keep;
2817 const apr_array_header_t *targets_info;
2818 const char *local_abspath;
2821 /* Implements svn_wc_status_func4_t. */
2822 static svn_error_t *
2823 can_delete_callback(void *baton,
2824 const char *abspath,
2825 const svn_wc_status3_t *status,
2828 struct can_delete_baton_t *cb = baton;
2831 switch(status->node_status)
2833 case svn_wc_status_none:
2834 case svn_wc_status_deleted:
2835 return SVN_NO_ERROR;
2838 if (! strcmp(cb->local_abspath, abspath))
2839 return SVN_NO_ERROR; /* Only interested in descendants */
2841 for (i = 0; i < cb->targets_info->nelts; i++)
2843 const patch_target_info_t *target_info =
2844 APR_ARRAY_IDX(cb->targets_info, i, const patch_target_info_t *);
2846 if (! strcmp(target_info->local_abspath, abspath))
2848 if (target_info->deleted)
2849 return SVN_NO_ERROR;
2851 break; /* Cease invocation; must keep */
2855 cb->must_keep = TRUE;
2857 return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL);
2861 static svn_error_t *
2862 check_ancestor_delete(const char *deleted_target,
2863 apr_array_header_t *targets_info,
2864 const char *apply_root,
2865 svn_boolean_t dry_run,
2866 svn_client_ctx_t *ctx,
2867 apr_pool_t *result_pool,
2868 apr_pool_t *scratch_pool)
2870 struct can_delete_baton_t cb;
2872 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
2874 const char *dir_abspath = svn_dirent_dirname(deleted_target, scratch_pool);
2876 while (svn_dirent_is_child(apply_root, dir_abspath, iterpool))
2878 svn_pool_clear(iterpool);
2880 cb.local_abspath = dir_abspath;
2881 cb.must_keep = FALSE;
2882 cb.targets_info = targets_info;
2884 err = svn_wc_walk_status(ctx->wc_ctx, dir_abspath, svn_depth_infinity,
2885 TRUE, FALSE, FALSE, NULL,
2886 can_delete_callback, &cb,
2887 ctx->cancel_func, ctx->cancel_baton,
2892 if (err->apr_err != SVN_ERR_CEASE_INVOCATION)
2893 return svn_error_trace(err);
2895 svn_error_clear(err);
2905 SVN_ERR(svn_wc_delete4(ctx->wc_ctx, dir_abspath, FALSE, FALSE,
2906 ctx->cancel_func, ctx->cancel_baton,
2912 patch_target_info_t *pti = apr_pcalloc(result_pool, sizeof(*pti));
2914 pti->local_abspath = apr_pstrdup(result_pool, dir_abspath);
2915 pti->deleted = TRUE;
2917 APR_ARRAY_PUSH(targets_info, patch_target_info_t *) = pti;
2921 if (ctx->notify_func2)
2923 svn_wc_notify_t *notify;
2925 notify = svn_wc_create_notify(dir_abspath, svn_wc_notify_delete,
2927 notify->kind = svn_node_dir;
2929 ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
2932 /* And check if we must also delete the parent */
2933 dir_abspath = svn_dirent_dirname(dir_abspath, scratch_pool);
2936 svn_pool_destroy(iterpool);
2938 return SVN_NO_ERROR;
2941 /* This function is the main entry point into the patch code. */
2942 static svn_error_t *
2943 apply_patches(/* The path to the patch file. */
2944 const char *patch_abspath,
2945 /* The abspath to the working copy the patch should be applied to. */
2946 const char *abs_wc_path,
2947 /* Indicates whether we're doing a dry run. */
2948 svn_boolean_t dry_run,
2949 /* Number of leading components to strip from patch target paths. */
2951 /* Whether to apply the patch in reverse. */
2952 svn_boolean_t reverse,
2953 /* Whether to ignore whitespace when matching context lines. */
2954 svn_boolean_t ignore_whitespace,
2955 /* As in svn_client_patch(). */
2956 svn_boolean_t remove_tempfiles,
2957 /* As in svn_client_patch(). */
2958 svn_client_patch_func_t patch_func,
2960 /* The client context. */
2961 svn_client_ctx_t *ctx,
2962 apr_pool_t *scratch_pool)
2965 apr_pool_t *iterpool;
2966 svn_patch_file_t *patch_file;
2967 apr_array_header_t *targets_info;
2969 /* Try to open the patch file. */
2970 SVN_ERR(svn_diff_open_patch_file(&patch_file, patch_abspath, scratch_pool));
2972 /* Apply patches. */
2973 targets_info = apr_array_make(scratch_pool, 0,
2974 sizeof(patch_target_info_t *));
2975 iterpool = svn_pool_create(scratch_pool);
2978 svn_pool_clear(iterpool);
2980 if (ctx->cancel_func)
2981 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
2983 SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file,
2984 reverse, ignore_whitespace,
2985 iterpool, iterpool));
2988 patch_target_t *target;
2990 SVN_ERR(apply_one_patch(&target, patch, abs_wc_path,
2991 ctx->wc_ctx, strip_count,
2992 ignore_whitespace, remove_tempfiles,
2993 patch_func, patch_baton,
2994 ctx->cancel_func, ctx->cancel_baton,
2995 iterpool, iterpool));
2996 if (! target->filtered)
2998 /* Save info we'll still need when we're done patching. */
2999 patch_target_info_t *target_info =
3000 apr_pcalloc(scratch_pool, sizeof(patch_target_info_t));
3001 target_info->local_abspath = apr_pstrdup(scratch_pool,
3002 target->local_abspath);
3003 target_info->deleted = target->deleted;
3005 if (! target->skipped)
3007 APR_ARRAY_PUSH(targets_info,
3008 patch_target_info_t *) = target_info;
3010 if (target->has_text_changes
3013 SVN_ERR(install_patched_target(target, abs_wc_path,
3014 ctx, dry_run, iterpool));
3016 if (target->has_prop_changes && (!target->deleted))
3017 SVN_ERR(install_patched_prop_targets(target, ctx,
3018 dry_run, iterpool));
3020 SVN_ERR(write_out_rejected_hunks(target, dry_run, iterpool));
3022 SVN_ERR(send_patch_notification(target, ctx, iterpool));
3024 if (target->deleted && !target->skipped)
3026 SVN_ERR(check_ancestor_delete(target_info->local_abspath,
3027 targets_info, abs_wc_path,
3029 scratch_pool, iterpool));
3036 SVN_ERR(svn_diff_close_patch_file(patch_file, iterpool));
3037 svn_pool_destroy(iterpool);
3039 return SVN_NO_ERROR;
3043 svn_client_patch(const char *patch_abspath,
3044 const char *wc_dir_abspath,
3045 svn_boolean_t dry_run,
3047 svn_boolean_t reverse,
3048 svn_boolean_t ignore_whitespace,
3049 svn_boolean_t remove_tempfiles,
3050 svn_client_patch_func_t patch_func,
3052 svn_client_ctx_t *ctx,
3053 apr_pool_t *scratch_pool)
3055 svn_node_kind_t kind;
3057 if (strip_count < 0)
3058 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
3059 _("strip count must be positive"));
3061 if (svn_path_is_url(wc_dir_abspath))
3062 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3063 _("'%s' is not a local path"),
3064 svn_dirent_local_style(wc_dir_abspath,
3067 SVN_ERR(svn_io_check_path(patch_abspath, &kind, scratch_pool));
3068 if (kind == svn_node_none)
3069 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3070 _("'%s' does not exist"),
3071 svn_dirent_local_style(patch_abspath,
3073 if (kind != svn_node_file)
3074 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3075 _("'%s' is not a file"),
3076 svn_dirent_local_style(patch_abspath,
3079 SVN_ERR(svn_io_check_path(wc_dir_abspath, &kind, scratch_pool));
3080 if (kind == svn_node_none)
3081 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3082 _("'%s' does not exist"),
3083 svn_dirent_local_style(wc_dir_abspath,
3085 if (kind != svn_node_dir)
3086 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3087 _("'%s' is not a directory"),
3088 svn_dirent_local_style(wc_dir_abspath,
3091 SVN_WC__CALL_WITH_WRITE_LOCK(
3092 apply_patches(patch_abspath, wc_dir_abspath, dry_run, strip_count,
3093 reverse, ignore_whitespace, remove_tempfiles,
3094 patch_func, patch_baton, ctx, scratch_pool),
3095 ctx->wc_ctx, wc_dir_abspath, FALSE /* lock_anchor */, scratch_pool);
3096 return SVN_NO_ERROR;