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_diff_private.h"
50 #include "private/svn_string_private.h"
51 #include "private/svn_subr_private.h"
52 #include "private/svn_sorts_private.h"
54 typedef struct hunk_info_t {
56 svn_diff_hunk_t *hunk;
58 /* The line where the hunk matched in the target file. */
59 svn_linenum_t matched_line;
61 /* Whether this hunk has been rejected. */
62 svn_boolean_t rejected;
64 /* Whether this hunk has already been applied (either manually
65 * or by an earlier run of patch). */
66 svn_boolean_t already_applied;
68 /* The fuzz factor used when matching this hunk, i.e. how many
69 * lines of leading and trailing context to ignore during matching. */
70 svn_linenum_t match_fuzz;
72 /* match_fuzz + the penalty caused by bad patch files */
73 svn_linenum_t report_fuzz;
76 /* A struct carrying information related to the patched and unpatched
77 * content of a target, be it a property or the text of a file. */
78 typedef struct target_content_t {
79 /* Indicates whether unpatched content existed prior to patching. */
80 svn_boolean_t existed;
82 /* The line last read from the unpatched content. */
83 svn_linenum_t current_line;
85 /* The EOL-style of the unpatched content. Either 'none', 'fixed',
86 * or 'native'. See the documentation of svn_subst_eol_style_t. */
87 svn_subst_eol_style_t eol_style;
89 /* If the EOL_STYLE above is not 'none', this is the EOL string
90 * corresponding to the EOL-style. Else, it is the EOL string the
91 * last line read from the target file was using. */
94 /* An array containing apr_off_t offsets marking the beginning of
95 * each line in the unpatched content. */
96 apr_array_header_t *lines;
98 /* An array containing hunk_info_t structures for hunks already matched. */
99 apr_array_header_t *hunks;
101 /* True if end-of-file was reached while reading from the unpatched
105 /* The keywords of the target. They will be contracted when reading
106 * unpatched content and expanded when writing patched content.
107 * When patching properties this hash is always empty. */
108 apr_hash_t *keywords;
110 /* A callback, with an associated baton, to read a line of unpatched
112 svn_error_t *(*readline)(void *baton, svn_stringbuf_t **line,
113 const char **eol_str, svn_boolean_t *eof,
114 apr_pool_t *result_pool, apr_pool_t *scratch_pool);
117 /* A callback to get the current byte offset within the unpatched
118 * content. Uses the read baton. */
119 svn_error_t * (*tell)(void *baton, apr_off_t *offset,
120 apr_pool_t *scratch_pool);
122 /* A callback to seek to an offset within the unpatched content.
123 * Uses the read baton. */
124 svn_error_t * (*seek)(void *baton, apr_off_t offset,
125 apr_pool_t *scratch_pool);
127 /* A callback to write data to the patched content, with an
128 * associated baton. */
129 svn_error_t * (*write)(void *baton, const char *buf, apr_size_t len,
130 apr_pool_t *scratch_pool);
135 typedef struct prop_patch_target_t {
137 /* The name of the property */
140 /* The property value. This is NULL in case the property did not exist
141 * prior to patch application (see also CONTENT->existed).
142 * Note that the patch implementation does not support binary properties,
143 * so this string is not expected to contain embedded NUL characters. */
144 const svn_string_t *value;
146 /* The patched property value.
147 * This is equivalent to the target, except that in appropriate
148 * places it contains the modified text as it appears in the patch file. */
149 svn_stringbuf_t *patched_value;
151 /* All information that is specific to the content of the property. */
152 target_content_t *content;
154 /* Represents the operation performed on the property. It can be added,
155 * deleted or modified.
156 * ### Should we use flags instead since we're not using all enum values? */
157 svn_diff_operation_kind_t operation;
159 /* When true the property change won't be applied */
160 svn_boolean_t skipped;
162 /* ### Here we'll add flags telling if the prop was added, deleted,
163 * ### had_rejects, had_local_mods prior to patching and so on. */
164 } prop_patch_target_t;
166 typedef struct patch_target_t {
167 /* The target path as it appeared in the patch file,
168 * but in canonicalised form. */
169 const char *canon_path_from_patchfile;
171 /* The target path, relative to the working copy directory the
172 * patch is being applied to. A patch strip count applies to this
173 * and only this path. This is never NULL. */
174 const char *local_relpath;
176 /* The absolute path of the target on the filesystem.
177 * Any symlinks the path from the patch file may contain are resolved.
178 * Is not always known, so it may be NULL. */
179 const char *local_abspath;
181 /* The target file, read-only. This is NULL in case the target
182 * file did not exist prior to patch application (see also
183 * CONTENT->existed). */
186 /* The target file is a symlink */
187 svn_boolean_t is_symlink;
190 * This is equivalent to the target, except that in appropriate
191 * places it contains the modified text as it appears in the patch file.
192 * The data in this file is written in repository-normal form.
193 * EOL transformation and keyword contraction is performed when the
194 * patched result is installed in the working copy. */
195 apr_file_t *patched_file;
197 /* Path to the patched file. */
198 const char *patched_path;
200 /* Hunks that are rejected will be written to this stream. */
201 svn_stream_t *reject_stream;
203 /* Path to the reject file. */
204 const char *reject_path;
206 /* The node kind of the target as found in WC-DB prior
207 * to patch application. */
208 svn_node_kind_t db_kind;
210 /* The target's kind on disk prior to patch application. */
211 svn_node_kind_t kind_on_disk;
213 /* True if the target was locally deleted prior to patching. */
214 svn_boolean_t locally_deleted;
216 /* True if the target had to be skipped for some reason. */
217 svn_boolean_t skipped;
219 /* True if the reason for skipping is a local obstruction */
220 svn_boolean_t obstructed;
222 /* True if at least one hunk was rejected. */
223 svn_boolean_t had_rejects;
225 /* True if at least one property hunk was rejected. */
226 svn_boolean_t had_prop_rejects;
228 /* True if at least one hunk was handled as already applied */
229 svn_boolean_t had_already_applied;
231 /* True if at least one property hunk was handled as already applied */
232 svn_boolean_t had_prop_already_applied;
234 /* The operation on the target as set in the patch file */
235 svn_diff_operation_kind_t operation;
237 /* True if the target was added by the patch, which means that it did
238 * not exist on disk before patching and has content after patching. */
241 /* True if the target ended up being deleted by the patch. */
242 svn_boolean_t deleted;
244 /* Set if the target is supposed to be moved by the patch.
245 * This applies to --git diffs which carry "rename from/to" headers. */
246 const char *move_target_abspath;
248 /* True if the target has the executable bit set. */
249 svn_boolean_t executable;
251 /* True if the patch changed the text of the target. */
252 svn_boolean_t has_text_changes;
254 /* True if the patch changed any of the properties of the target. */
255 svn_boolean_t has_prop_changes;
257 /* True if the patch contained a svn:special property. */
258 svn_boolean_t is_special;
260 /* All the information that is specific to the content of the target. */
261 target_content_t *content;
263 /* A hash table of prop_patch_target_t objects keyed by property names. */
264 apr_hash_t *prop_targets;
266 /* When TRUE, this patch uses the raw git symlink format instead of the
267 Subversion internal style format where links start with 'link '. */
268 svn_boolean_t git_symlink_format;
273 /* A smaller struct containing a subset of patch_target_t.
274 * Carries the minimal amount of information we still need for a
275 * target after we're done patching it so we can free other resources. */
276 typedef struct patch_target_info_t {
277 const char *local_abspath;
278 svn_boolean_t deleted;
280 } patch_target_info_t;
282 /* Check if LOCAL_ABSPATH is recorded as added in TARGETS_INFO */
284 target_is_added(const apr_array_header_t *targets_info,
285 const char *local_abspath,
286 apr_pool_t *scratch_pool)
290 for (i = targets_info->nelts - 1; i >= 0; i--)
292 const patch_target_info_t *target_info =
293 APR_ARRAY_IDX(targets_info, i, const patch_target_info_t *);
295 const char *info = svn_dirent_skip_ancestor(target_info->local_abspath,
299 return target_info->added;
307 /* Check if LOCAL_ABSPATH or an ancestor is recorded as deleted in
310 target_is_deleted(const apr_array_header_t *targets_info,
311 const char *local_abspath,
312 apr_pool_t *scratch_pool)
316 for (i = targets_info->nelts - 1; i >= 0; i--)
318 const patch_target_info_t *target_info =
319 APR_ARRAY_IDX(targets_info, i, const patch_target_info_t *);
321 const char *info = svn_dirent_skip_ancestor(target_info->local_abspath,
325 return target_info->deleted;
332 /* Strip STRIP_COUNT components from the front of PATH, returning
333 * the result in *RESULT, allocated in RESULT_POOL.
334 * Do temporary allocations in SCRATCH_POOL. */
336 strip_path(const char **result, const char *path, int strip_count,
337 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
340 apr_array_header_t *components;
341 apr_array_header_t *stripped;
343 components = svn_path_decompose(path, scratch_pool);
344 if (strip_count > components->nelts)
345 return svn_error_createf(SVN_ERR_CLIENT_PATCH_BAD_STRIP_COUNT, NULL,
346 _("Cannot strip %u components from '%s'"),
348 svn_dirent_local_style(path, scratch_pool));
350 stripped = apr_array_make(scratch_pool, components->nelts - strip_count,
351 sizeof(const char *));
352 for (i = strip_count; i < components->nelts; i++)
354 const char *component;
356 component = APR_ARRAY_IDX(components, i, const char *);
357 APR_ARRAY_PUSH(stripped, const char *) = component;
360 *result = svn_path_compose(stripped, result_pool);
365 /* Obtain KEYWORDS, EOL_STYLE and EOL_STR for LOCAL_ABSPATH.
366 * WC_CTX is a context for the working copy the patch is applied to.
367 * Use RESULT_POOL for allocations of fields in TARGET.
368 * Use SCRATCH_POOL for all other allocations. */
370 obtain_eol_and_keywords_for_file(apr_hash_t **keywords,
371 svn_subst_eol_style_t *eol_style,
372 const char **eol_str,
373 svn_wc_context_t *wc_ctx,
374 const char *local_abspath,
375 apr_pool_t *result_pool,
376 apr_pool_t *scratch_pool)
379 svn_string_t *keywords_val, *eol_style_val;
381 SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, local_abspath,
382 scratch_pool, scratch_pool));
383 keywords_val = svn_hash_gets(props, SVN_PROP_KEYWORDS);
386 svn_revnum_t changed_rev;
387 apr_time_t changed_date;
391 const char *repos_root_url;
392 const char *repos_relpath;
394 SVN_ERR(svn_wc__node_get_changed_info(&changed_rev,
400 rev_str = apr_psprintf(scratch_pool, "%ld", changed_rev);
401 SVN_ERR(svn_wc__node_get_repos_info(NULL, &repos_relpath, &repos_root_url,
403 wc_ctx, local_abspath,
404 scratch_pool, scratch_pool));
405 url = svn_path_url_add_component2(repos_root_url, repos_relpath,
408 SVN_ERR(svn_subst_build_keywords3(keywords,
410 rev_str, url, repos_root_url,
412 author, result_pool));
415 eol_style_val = svn_hash_gets(props, SVN_PROP_EOL_STYLE);
418 svn_subst_eol_style_from_value(eol_style,
420 eol_style_val->data);
426 /* Resolve the exact path for a patch TARGET at path PATH_FROM_PATCHFILE,
427 * which is the path of the target as it appeared in the patch file.
428 * Put a canonicalized version of PATH_FROM_PATCHFILE into
429 * TARGET->CANON_PATH_FROM_PATCHFILE.
430 * WC_CTX is a context for the working copy the patch is applied to.
431 * If possible, determine TARGET->WC_PATH, TARGET->ABS_PATH, TARGET->KIND,
432 * TARGET->ADDED, and TARGET->PARENT_DIR_EXISTS.
433 * Indicate in TARGET->SKIPPED whether the target should be skipped.
434 * STRIP_COUNT specifies the number of leading path components
435 * which should be stripped from target paths in the patch.
436 * HAS_TEXT_CHANGES specifies whether the target path will have some text
437 * changes applied, implying that the target should be a file and not a
439 * Use RESULT_POOL for allocations of fields in TARGET.
440 * Use SCRATCH_POOL for all other allocations. */
442 resolve_target_path(patch_target_t *target,
443 const char *path_from_patchfile,
444 const char *root_abspath,
446 svn_boolean_t has_text_changes,
447 svn_boolean_t follow_moves,
448 svn_wc_context_t *wc_ctx,
449 const apr_array_header_t *targets_info,
450 apr_pool_t *result_pool,
451 apr_pool_t *scratch_pool)
453 const char *stripped_path;
454 svn_wc_status3_t *status;
456 svn_boolean_t under_root;
458 target->canon_path_from_patchfile = svn_dirent_internal_style(
459 path_from_patchfile, result_pool);
461 /* We can't handle text changes on the patch root dir. */
462 if (has_text_changes && target->canon_path_from_patchfile[0] == '\0')
464 /* An empty patch target path? What gives? Skip this. */
465 target->skipped = TRUE;
466 target->local_abspath = NULL;
467 target->local_relpath = "";
472 SVN_ERR(strip_path(&stripped_path, target->canon_path_from_patchfile,
473 strip_count, result_pool, scratch_pool));
475 stripped_path = target->canon_path_from_patchfile;
477 if (svn_dirent_is_absolute(stripped_path))
479 target->local_relpath = svn_dirent_is_child(root_abspath,
483 if (! target->local_relpath)
485 /* The target path is either outside of the working copy
486 * or it is the patch root itself. Skip it. */
487 target->skipped = TRUE;
488 target->local_abspath = NULL;
489 target->local_relpath = stripped_path;
495 target->local_relpath = stripped_path;
498 /* Make sure the path is secure to use. We want the target to be inside
499 * the locked tree and not be fooled by symlinks it might contain. */
500 SVN_ERR(svn_dirent_is_under_root(&under_root,
501 &target->local_abspath, root_abspath,
502 target->local_relpath, result_pool));
506 /* The target path is outside of the working copy. Skip it. */
507 target->skipped = TRUE;
508 target->local_abspath = NULL;
512 if (target_is_deleted(targets_info, target->local_abspath, scratch_pool))
514 target->locally_deleted = TRUE;
515 target->db_kind = svn_node_none;
519 /* Skip things we should not be messing with. */
520 err = svn_wc_status3(&status, wc_ctx, target->local_abspath,
521 result_pool, scratch_pool);
524 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
525 return svn_error_trace(err);
527 svn_error_clear(err);
529 target->locally_deleted = TRUE;
530 target->db_kind = svn_node_none;
533 else if (status->node_status == svn_wc_status_ignored ||
534 status->node_status == svn_wc_status_unversioned ||
535 status->node_status == svn_wc_status_missing ||
536 status->node_status == svn_wc_status_obstructed ||
539 target->skipped = TRUE;
540 target->obstructed = TRUE;
543 else if (status->node_status == svn_wc_status_deleted)
545 target->locally_deleted = TRUE;
548 if (status && (status->kind != svn_node_unknown))
549 target->db_kind = status->kind;
551 target->db_kind = svn_node_none;
553 SVN_ERR(svn_io_check_special_path(target->local_abspath,
554 &target->kind_on_disk, &target->is_symlink,
557 if (target->locally_deleted)
559 const char *moved_to_abspath = NULL;
562 && !target_is_added(targets_info, target->local_abspath,
565 SVN_ERR(svn_wc__node_was_moved_away(&moved_to_abspath, NULL,
566 wc_ctx, target->local_abspath,
567 result_pool, scratch_pool));
570 if (moved_to_abspath)
572 target->local_abspath = moved_to_abspath;
573 target->local_relpath = svn_dirent_skip_ancestor(root_abspath,
576 if (!target->local_relpath || target->local_relpath[0] == '\0')
578 /* The target path is outside of the patch area. Skip it. */
579 target->skipped = TRUE;
583 /* As far as we are concerned this target is not locally deleted. */
584 target->locally_deleted = FALSE;
586 SVN_ERR(svn_io_check_special_path(target->local_abspath,
587 &target->kind_on_disk,
591 else if (target->kind_on_disk != svn_node_none)
593 target->skipped = TRUE;
599 if (target->kind_on_disk == svn_node_file
600 && !target->is_symlink
601 && !target->locally_deleted
602 && status->prop_status != svn_wc_status_none)
604 const svn_string_t *value;
606 SVN_ERR(svn_wc_prop_get2(&value, wc_ctx, target->local_abspath,
607 SVN_PROP_SPECIAL, scratch_pool, scratch_pool));
610 target->is_symlink = TRUE;
617 /* Baton for reading from properties. */
618 typedef struct prop_read_baton_t {
619 const svn_string_t *value;
623 /* Allocate *STRINGBUF in RESULT_POOL, and read into it one line from
624 * the unpatched property value accessed via BATON.
625 * Reading stops either after a line-terminator was found, or if
626 * the property value runs out in which case *EOF is set to TRUE.
627 * The line-terminator is not stored in *STRINGBUF.
629 * If the line is empty or could not be read, *line is set to NULL.
631 * The line-terminator is detected automatically and stored in *EOL
632 * if EOL is not NULL. If the end of the property value is reached
633 * and does not end with a newline character, and EOL is not NULL,
634 * *EOL is set to NULL.
636 * SCRATCH_POOL is used for temporary allocations.
639 readline_prop(void *baton, svn_stringbuf_t **line, const char **eol_str,
640 svn_boolean_t *eof, apr_pool_t *result_pool,
641 apr_pool_t *scratch_pool)
643 prop_read_baton_t *b = baton;
644 svn_stringbuf_t *str = NULL;
646 svn_boolean_t found_eof;
648 if ((apr_uint64_t)b->offset >= (apr_uint64_t)b->value->len)
656 /* Read bytes into STR up to and including, but not storing,
657 * the next EOL sequence. */
662 c = b->value->data + b->offset;
677 if (*(c + 1) == '\n')
686 str = svn_stringbuf_create_ensure(80, result_pool);
687 svn_stringbuf_appendbyte(str, *c);
693 while (c < b->value->data + b->value->len);
696 *eof = found_eof && !(str && str->len > 0);
702 /* Return in *OFFSET the current byte offset for reading from the
703 * unpatched property value accessed via BATON.
704 * Use SCRATCH_POOL for temporary allocations. */
706 tell_prop(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool)
708 prop_read_baton_t *b = baton;
714 /* Seek to the specified by OFFSET in the unpatched property value accessed
715 * via BATON. Use SCRATCH_POOL for temporary allocations. */
717 seek_prop(void *baton, apr_off_t offset, apr_pool_t *scratch_pool)
719 prop_read_baton_t *b = baton;
725 /* Write LEN bytes from BUF into the patched property value accessed
726 * via BATON. Use SCRATCH_POOL for temporary allocations. */
728 write_prop(void *baton, const char *buf, apr_size_t len,
729 apr_pool_t *scratch_pool)
731 svn_stringbuf_t *patched_value = baton;
733 svn_stringbuf_appendbytes(patched_value, buf, len);
737 /* Initialize a PROP_TARGET structure for PROP_NAME on the patch target
738 * at LOCAL_ABSPATH. OPERATION indicates the operation performed on the
739 * property. Use working copy context WC_CTX.
740 * Allocate results in RESULT_POOL.
741 * Use SCRATCH_POOL for temporary allocations. */
743 init_prop_target(prop_patch_target_t **prop_target,
744 const patch_target_t *target,
745 const char *prop_name,
746 svn_diff_operation_kind_t operation,
747 svn_wc_context_t *wc_ctx,
748 const char *local_abspath,
749 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
751 prop_patch_target_t *new_prop_target;
752 target_content_t *content;
753 const svn_string_t *value;
754 prop_read_baton_t *prop_read_baton;
756 content = apr_pcalloc(result_pool, sizeof(*content));
758 /* All other fields are FALSE or NULL due to apr_pcalloc(). */
759 content->current_line = 1;
760 content->eol_style = svn_subst_eol_style_none;
761 content->lines = apr_array_make(result_pool, 0, sizeof(apr_off_t));
762 content->hunks = apr_array_make(result_pool, 0, sizeof(hunk_info_t *));
763 content->keywords = apr_hash_make(result_pool);
765 new_prop_target = apr_pcalloc(result_pool, sizeof(*new_prop_target));
766 new_prop_target->name = apr_pstrdup(result_pool, prop_name);
767 new_prop_target->operation = operation;
768 new_prop_target->content = content;
770 if (!(target->deleted || target->db_kind == svn_node_none))
771 SVN_ERR(svn_wc_prop_get2(&value, wc_ctx, local_abspath, prop_name,
772 result_pool, scratch_pool));
776 content->existed = (value != NULL);
777 new_prop_target->value = value;
778 new_prop_target->patched_value = svn_stringbuf_create_empty(result_pool);
781 /* Wire up the read and write callbacks. */
782 prop_read_baton = apr_pcalloc(result_pool, sizeof(*prop_read_baton));
783 prop_read_baton->value = value;
784 prop_read_baton->offset = 0;
785 content->readline = readline_prop;
786 content->tell = tell_prop;
787 content->seek = seek_prop;
788 content->read_baton = prop_read_baton;
789 content->write = write_prop;
790 content->write_baton = new_prop_target->patched_value;
792 *prop_target = new_prop_target;
797 /* Allocate *STRINGBUF in RESULT_POOL, and read into it one line from
798 * the unpatched file content accessed via BATON.
799 * Reading stops either after a line-terminator was found,
800 * or if EOF is reached in which case *EOF is set to TRUE.
801 * The line-terminator is not stored in *STRINGBUF.
803 * If the line is empty or could not be read, *line is set to NULL.
805 * The line-terminator is detected automatically and stored in *EOL
806 * if EOL is not NULL. If EOF is reached and FILE does not end
807 * with a newline character, and EOL is not NULL, *EOL is set to NULL.
809 * SCRATCH_POOL is used for temporary allocations.
812 readline_file(void *baton, svn_stringbuf_t **line, const char **eol_str,
813 svn_boolean_t *eof, apr_pool_t *result_pool,
814 apr_pool_t *scratch_pool)
816 apr_file_t *file = baton;
818 SVN_ERR(svn_io_file_readline(file, line, eol_str, eof, APR_SIZE_MAX,
819 result_pool, scratch_pool));
829 /* Return in *OFFSET the current byte offset for reading from the
830 * unpatched file content accessed via BATON.
831 * Use SCRATCH_POOL for temporary allocations. */
833 tell_file(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool)
835 apr_file_t *file = baton;
837 SVN_ERR(svn_io_file_get_offset(offset, file, scratch_pool));
841 /* Seek to the specified by OFFSET in the unpatched file content accessed
842 * via BATON. Use SCRATCH_POOL for temporary allocations. */
844 seek_file(void *baton, apr_off_t offset, apr_pool_t *scratch_pool)
846 apr_file_t *file = baton;
848 SVN_ERR(svn_io_file_seek(file, APR_SET, &offset, scratch_pool));
852 /* Write LEN bytes from BUF into the patched file content accessed
853 * via BATON. Use SCRATCH_POOL for temporary allocations. */
855 write_file(void *baton, const char *buf, apr_size_t len,
856 apr_pool_t *scratch_pool)
858 apr_file_t *file = baton;
860 SVN_ERR(svn_io_file_write_full(file, buf, len, &len, scratch_pool));
864 /* Symlinks appear in patches in their repository normal form, abstracted by
865 * the svn_subst_* module. The functions below enable patches to change the
866 * targets of symlinks.
869 /* Baton for the (readline|tell|seek|write)_symlink functions. */
870 struct symlink_baton_t
872 /* The path to the symlink on disk (not the path to the target of the link) */
873 const char *local_abspath;
875 /* Indicates whether the "normal form" of the symlink has been read. */
876 svn_boolean_t at_eof;
879 /* Allocate *STRINGBUF in RESULT_POOL, and store into it the "normal form"
880 * of the symlink accessed via BATON.
882 * Otherwise behaves like readline_file(), which see.
885 readline_symlink(void *baton, svn_stringbuf_t **line, const char **eol_str,
886 svn_boolean_t *eof, apr_pool_t *result_pool,
887 apr_pool_t *scratch_pool)
889 struct symlink_baton_t *sb = baton;
902 svn_stream_t *stream;
903 const apr_size_t len_hint = 64; /* arbitrary */
905 SVN_ERR(svn_subst_read_specialfile(&stream, sb->local_abspath,
906 scratch_pool, scratch_pool));
907 SVN_ERR(svn_stringbuf_from_stream(line, stream, len_hint, result_pool));
915 /* Identical to readline_symlink(), but returns symlink in raw format to
916 * allow patching links in git-style.
919 readline_symlink_git(void *baton, svn_stringbuf_t **line, const char **eol_str,
920 svn_boolean_t *eof, apr_pool_t *result_pool,
921 apr_pool_t *scratch_pool)
923 SVN_ERR(readline_symlink(baton, line, eol_str, eof,
924 result_pool, scratch_pool));
926 if (*line && (*line)->len > 5 && !strncmp((*line)->data, "link ", 5))
927 svn_stringbuf_remove(*line, 0, 5); /* Skip "link " */
932 /* Set *OFFSET to 1 or 0 depending on whether the "normal form" of
933 * the symlink has already been read. */
935 tell_symlink(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool)
937 struct symlink_baton_t *sb = baton;
939 *offset = sb->at_eof ? 1 : 0;
943 /* If offset is non-zero, mark the symlink as having been read in its
944 * "normal form". Else, mark the symlink as not having been read yet. */
946 seek_symlink(void *baton, apr_off_t offset, apr_pool_t *scratch_pool)
948 struct symlink_baton_t *sb = baton;
950 sb->at_eof = (offset != 0);
954 /* Return a suitable filename for the target of PATCH.
955 * Examine the ``old'' and ``new'' file names, and choose the file name
956 * with the fewest path components, the shortest basename, and the shortest
957 * total file name length (in that order). In case of a tie, return the new
958 * filename. This heuristic is also used by Larry Wall's UNIX patch (except
959 * that it prompts for a filename in case of a tie).
960 * Additionally, for compatibility with git, if one of the filenames
961 * is "/dev/null", use the other filename. */
963 choose_target_filename(const svn_patch_t *patch)
968 if (strcmp(patch->old_filename, "/dev/null") == 0)
969 return patch->new_filename;
970 if (strcmp(patch->new_filename, "/dev/null") == 0)
971 return patch->old_filename;
973 /* If the patch renames the target, use the old name while
974 * applying hunks. The target will be renamed to the new name
975 * after hunks have been applied. */
976 if (patch->operation == svn_diff_op_moved)
977 return patch->old_filename;
979 old = svn_path_component_count(patch->old_filename);
980 new = svn_path_component_count(patch->new_filename);
984 old = strlen(svn_dirent_basename(patch->old_filename, NULL));
985 new = strlen(svn_dirent_basename(patch->new_filename, NULL));
989 old = strlen(patch->old_filename);
990 new = strlen(patch->new_filename);
994 return (old < new) ? patch->old_filename : patch->new_filename;
997 /* Attempt to initialize a *PATCH_TARGET structure for a target file
998 * described by PATCH. Use working copy context WC_CTX.
999 * STRIP_COUNT specifies the number of leading path components
1000 * which should be stripped from target paths in the patch.
1001 * The patch target structure is allocated in RESULT_POOL, but if the target
1002 * should be skipped, PATCH_TARGET->SKIPPED is set and the target should be
1003 * treated as not fully initialized, e.g. the caller should not not do any
1004 * further operations on the target if it is marked to be skipped.
1005 * If REMOVE_TEMPFILES is TRUE, set up temporary files to be removed as
1006 * soon as they are no longer needed.
1007 * Use SCRATCH_POOL for all other allocations. */
1008 static svn_error_t *
1009 init_patch_target(patch_target_t **patch_target,
1010 const svn_patch_t *patch,
1011 const char *root_abspath,
1012 svn_wc_context_t *wc_ctx, int strip_count,
1013 svn_boolean_t remove_tempfiles,
1014 const apr_array_header_t *targets_info,
1015 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
1017 patch_target_t *target;
1018 target_content_t *content;
1019 svn_boolean_t has_text_changes = FALSE;
1020 svn_boolean_t follow_moves;
1022 has_text_changes = ((patch->hunks && patch->hunks->nelts > 0)
1023 || patch->binary_patch);
1025 content = apr_pcalloc(result_pool, sizeof(*content));
1027 /* All other fields in content are FALSE or NULL due to apr_pcalloc().*/
1028 content->current_line = 1;
1029 content->eol_style = svn_subst_eol_style_none;
1030 content->lines = apr_array_make(result_pool, 0, sizeof(apr_off_t));
1031 content->hunks = apr_array_make(result_pool, 0, sizeof(hunk_info_t *));
1032 content->keywords = apr_hash_make(result_pool);
1034 target = apr_pcalloc(result_pool, sizeof(*target));
1036 /* All other fields in target are FALSE or NULL due to apr_pcalloc(). */
1037 target->db_kind = svn_node_none;
1038 target->kind_on_disk = svn_node_none;
1039 target->content = content;
1040 target->prop_targets = apr_hash_make(result_pool);
1041 target->operation = patch->operation;
1043 if (patch->operation == svn_diff_op_added /* Allow replacing */
1044 || patch->operation == svn_diff_op_moved)
1046 follow_moves = FALSE;
1048 else if (patch->operation == svn_diff_op_unchanged
1049 && patch->hunks && patch->hunks->nelts == 1)
1051 svn_diff_hunk_t *hunk = APR_ARRAY_IDX(patch->hunks, 0,
1054 follow_moves = (svn_diff_hunk_get_original_start(hunk) != 0);
1057 follow_moves = TRUE;
1059 SVN_ERR(resolve_target_path(target, choose_target_filename(patch),
1060 root_abspath, strip_count, has_text_changes,
1061 follow_moves, wc_ctx, targets_info,
1062 result_pool, scratch_pool));
1063 *patch_target = target;
1064 if (! target->skipped)
1066 if (patch->old_symlink_bit == svn_tristate_true
1067 || patch->new_symlink_bit == svn_tristate_true)
1069 target->git_symlink_format = TRUE;
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;
1082 else if (patch->operation == svn_diff_op_moved)
1084 const char *move_target_path;
1085 const char *move_target_relpath;
1086 svn_boolean_t under_root;
1087 svn_boolean_t is_special;
1088 svn_node_kind_t kind_on_disk;
1089 svn_node_kind_t wc_kind;
1091 move_target_path = svn_dirent_internal_style(patch->new_filename,
1094 if (strip_count > 0)
1095 SVN_ERR(strip_path(&move_target_path, move_target_path,
1096 strip_count, scratch_pool, scratch_pool));
1098 if (svn_dirent_is_absolute(move_target_path))
1100 move_target_relpath = svn_dirent_is_child(root_abspath,
1103 if (! move_target_relpath)
1105 /* The move target path is either outside of the working
1106 * copy or it is the working copy itself. Skip it. */
1107 target->skipped = TRUE;
1108 return SVN_NO_ERROR;
1112 move_target_relpath = move_target_path;
1114 /* Make sure the move target path is secure to use. */
1115 SVN_ERR(svn_dirent_is_under_root(&under_root,
1116 &target->move_target_abspath,
1118 move_target_relpath, result_pool));
1121 /* The target path is outside of the working copy. Skip it. */
1122 target->skipped = TRUE;
1123 target->move_target_abspath = NULL;
1124 return SVN_NO_ERROR;
1127 SVN_ERR(svn_io_check_special_path(target->move_target_abspath,
1128 &kind_on_disk, &is_special,
1130 SVN_ERR(svn_wc_read_kind2(&wc_kind, wc_ctx,
1131 target->move_target_abspath,
1132 FALSE, FALSE, scratch_pool));
1133 if (wc_kind == svn_node_file || wc_kind == svn_node_dir)
1135 /* The move target path already exists on disk. */
1137 const char *moved_from_abspath;
1139 err = svn_wc__node_was_moved_here(&moved_from_abspath, NULL,
1141 target->move_target_abspath,
1142 scratch_pool, scratch_pool);
1144 if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
1146 svn_error_clear(err);
1148 moved_from_abspath = NULL;
1153 if (moved_from_abspath && (strcmp(moved_from_abspath,
1154 target->local_abspath) == 0))
1156 target->local_abspath = target->move_target_abspath;
1157 target->move_target_abspath = NULL;
1158 target->operation = svn_diff_op_modified;
1159 target->locally_deleted = FALSE;
1160 target->db_kind = wc_kind;
1161 target->kind_on_disk = kind_on_disk;
1162 target->is_special = is_special;
1164 target->had_already_applied = TRUE; /* Make sure we notify */
1168 target->skipped = TRUE;
1169 target->move_target_abspath = NULL;
1170 return SVN_NO_ERROR;
1174 else if (kind_on_disk != svn_node_none
1175 || target_is_added(targets_info, target->move_target_abspath,
1178 target->skipped = TRUE;
1179 target->move_target_abspath = NULL;
1180 return SVN_NO_ERROR;
1184 /* Create a temporary file to write the patched result to.
1185 * Also grab various bits of information about the file. */
1186 if (target->is_symlink)
1188 struct symlink_baton_t *sb = apr_pcalloc(result_pool, sizeof(*sb));
1189 content->existed = TRUE;
1191 sb->local_abspath = target->local_abspath;
1193 /* Wire up the read callbacks. */
1194 content->read_baton = sb;
1196 content->readline = target->git_symlink_format ? readline_symlink_git
1198 content->seek = seek_symlink;
1199 content->tell = tell_symlink;
1201 else if (target->kind_on_disk == svn_node_file)
1203 SVN_ERR(svn_io_file_open(&target->file, target->local_abspath,
1204 APR_READ | APR_BUFFERED,
1205 APR_OS_DEFAULT, result_pool));
1206 SVN_ERR(svn_io_is_file_executable(&target->executable,
1207 target->local_abspath,
1209 SVN_ERR(obtain_eol_and_keywords_for_file(&content->keywords,
1210 &content->eol_style,
1213 target->local_abspath,
1216 content->existed = TRUE;
1218 /* Wire up the read callbacks. */
1219 content->readline = readline_file;
1220 content->seek = seek_file;
1221 content->tell = tell_file;
1222 content->read_baton = target->file;
1225 /* Open a temporary file to write the patched result to. */
1226 SVN_ERR(svn_io_open_unique_file3(&target->patched_file,
1227 &target->patched_path, NULL,
1229 svn_io_file_del_on_pool_cleanup :
1230 svn_io_file_del_none,
1231 result_pool, scratch_pool));
1233 /* Put the write callback in place. */
1234 content->write = write_file;
1235 content->write_baton = target->patched_file;
1237 /* Open a temporary stream to write rejected hunks to. */
1238 SVN_ERR(svn_stream_open_unique(&target->reject_stream,
1239 &target->reject_path, NULL,
1241 svn_io_file_del_on_pool_cleanup :
1242 svn_io_file_del_none,
1243 result_pool, scratch_pool));
1245 /* Handle properties. */
1246 if (! target->skipped)
1248 apr_hash_index_t *hi;
1250 for (hi = apr_hash_first(result_pool, patch->prop_patches);
1252 hi = apr_hash_next(hi))
1254 const char *prop_name = apr_hash_this_key(hi);
1255 svn_prop_patch_t *prop_patch = apr_hash_this_val(hi);
1256 prop_patch_target_t *prop_target;
1258 SVN_ERR(init_prop_target(&prop_target,
1260 prop_patch->operation,
1261 wc_ctx, target->local_abspath,
1262 result_pool, scratch_pool));
1263 svn_hash_sets(target->prop_targets, prop_name, prop_target);
1266 /* Now, check for out-of-band mode changes and convert these in
1267 their Subversion equivalent properties. */
1268 if (patch->new_executable_bit != svn_tristate_unknown
1269 && patch->new_executable_bit != patch->old_executable_bit)
1271 svn_diff_operation_kind_t operation;
1273 if (patch->new_executable_bit == svn_tristate_true)
1274 operation = svn_diff_op_added;
1275 else if (patch->new_executable_bit == svn_tristate_false)
1277 /* Made non-executable. */
1278 if (patch->old_executable_bit == svn_tristate_true)
1279 operation = svn_diff_op_deleted;
1281 operation = svn_diff_op_unchanged;
1284 operation = svn_diff_op_unchanged;
1286 if (operation != svn_diff_op_unchanged)
1288 prop_patch_target_t *prop_target;
1290 prop_target = svn_hash_gets(target->prop_targets,
1291 SVN_PROP_EXECUTABLE);
1293 if (prop_target && operation != prop_target->operation)
1295 return svn_error_createf(SVN_ERR_INVALID_INPUT, NULL,
1296 _("Invalid patch: specifies "
1297 "contradicting mode changes and "
1298 "%s changes (for '%s')"),
1299 SVN_PROP_EXECUTABLE,
1300 target->local_abspath);
1302 else if (!prop_target)
1304 SVN_ERR(init_prop_target(&prop_target,
1305 target, SVN_PROP_EXECUTABLE,
1307 wc_ctx, target->local_abspath,
1308 result_pool, scratch_pool));
1309 svn_hash_sets(target->prop_targets, SVN_PROP_EXECUTABLE,
1315 if (patch->new_symlink_bit != svn_tristate_unknown
1316 && patch->new_symlink_bit != patch->old_symlink_bit)
1318 svn_diff_operation_kind_t operation;
1320 if (patch->new_symlink_bit == svn_tristate_true)
1321 operation = svn_diff_op_added;
1322 else if (patch->new_symlink_bit == svn_tristate_false)
1324 /* Made non-symlink. */
1325 if (patch->old_symlink_bit == svn_tristate_true)
1326 operation = svn_diff_op_deleted;
1328 operation = svn_diff_op_unchanged;
1331 operation = svn_diff_op_unchanged;
1333 if (operation != svn_diff_op_unchanged)
1335 prop_patch_target_t *prop_target;
1336 prop_target = svn_hash_gets(target->prop_targets,
1339 if (prop_target && operation != prop_target->operation)
1341 return svn_error_createf(SVN_ERR_INVALID_INPUT, NULL,
1342 _("Invalid patch: specifies "
1343 "contradicting mode changes and "
1344 "%s changes (for '%s')"),
1346 target->local_abspath);
1348 else if (!prop_target)
1350 SVN_ERR(init_prop_target(&prop_target,
1351 target, SVN_PROP_SPECIAL,
1353 wc_ctx, target->local_abspath,
1354 result_pool, scratch_pool));
1355 svn_hash_sets(target->prop_targets, SVN_PROP_SPECIAL,
1363 if ((target->locally_deleted || target->db_kind == svn_node_none)
1365 && target->operation == svn_diff_op_unchanged)
1367 svn_boolean_t maybe_add = FALSE;
1369 if (patch->hunks && patch->hunks->nelts == 1)
1371 svn_diff_hunk_t *hunk = APR_ARRAY_IDX(patch->hunks, 0,
1374 if (svn_diff_hunk_get_original_start(hunk) == 0)
1377 else if (patch->prop_patches && apr_hash_count(patch->prop_patches))
1379 apr_hash_index_t *hi;
1380 svn_boolean_t all_add = TRUE;
1382 for (hi = apr_hash_first(result_pool, patch->prop_patches);
1384 hi = apr_hash_next(hi))
1386 svn_prop_patch_t *prop_patch = apr_hash_this_val(hi);
1388 if (prop_patch->operation != svn_diff_op_added)
1395 maybe_add = all_add;
1397 /* Other implied types */
1400 target->added = TRUE;
1402 else if (!target->deleted && !target->added
1403 && target->operation == svn_diff_op_unchanged)
1405 svn_boolean_t maybe_delete = FALSE;
1407 if (patch->hunks && patch->hunks->nelts == 1)
1409 svn_diff_hunk_t *hunk = APR_ARRAY_IDX(patch->hunks, 0,
1412 if (svn_diff_hunk_get_modified_start(hunk) == 0)
1413 maybe_delete = TRUE;
1416 /* Other implied types */
1419 target->deleted = TRUE;
1422 if (target->reject_stream != NULL)
1424 /* The reject file needs a diff header. */
1425 const char *left_src = target->canon_path_from_patchfile;
1426 const char *right_src = target->canon_path_from_patchfile;
1428 /* Handle moves specifically? */
1430 left_src = "/dev/null";
1431 if (target->deleted)
1432 right_src = "/dev/null";
1434 SVN_ERR(svn_stream_printf(target->reject_stream, scratch_pool,
1435 "--- %s" APR_EOL_STR
1436 "+++ %s" APR_EOL_STR,
1437 left_src, right_src));
1440 return SVN_NO_ERROR;
1443 /* Read a *LINE from CONTENT. If the line has not been read before
1444 * mark the line in CONTENT->LINES.
1445 * If a line could be read successfully, increase CONTENT->CURRENT_LINE,
1446 * and allocate *LINE in RESULT_POOL.
1447 * Do temporary allocations in SCRATCH_POOL.
1449 static svn_error_t *
1450 readline(target_content_t *content,
1452 apr_pool_t *result_pool,
1453 apr_pool_t *scratch_pool)
1455 svn_stringbuf_t *line_raw;
1456 const char *eol_str;
1457 svn_linenum_t max_line = (svn_linenum_t)content->lines->nelts + 1;
1459 if (content->eof || content->readline == NULL)
1462 return SVN_NO_ERROR;
1465 SVN_ERR_ASSERT(content->current_line <= max_line);
1466 if (content->current_line == max_line)
1470 SVN_ERR(content->tell(content->read_baton, &offset,
1472 APR_ARRAY_PUSH(content->lines, apr_off_t) = offset;
1475 SVN_ERR(content->readline(content->read_baton, &line_raw,
1476 &eol_str, &content->eof,
1477 result_pool, scratch_pool));
1478 if (content->eol_style == svn_subst_eol_style_none)
1479 content->eol_str = eol_str;
1483 /* Contract keywords. */
1484 SVN_ERR(svn_subst_translate_cstring2(line_raw->data, line,
1486 content->keywords, FALSE,
1492 if ((line_raw && line_raw->len > 0) || eol_str)
1493 content->current_line++;
1495 SVN_ERR_ASSERT(content->current_line > 0);
1497 return SVN_NO_ERROR;
1500 /* Seek to the specified LINE in CONTENT.
1501 * Mark any lines not read before in CONTENT->LINES.
1502 * Do temporary allocations in SCRATCH_POOL.
1504 static svn_error_t *
1505 seek_to_line(target_content_t *content, svn_linenum_t line,
1506 apr_pool_t *scratch_pool)
1508 svn_linenum_t saved_line;
1509 svn_boolean_t saved_eof;
1511 SVN_ERR_ASSERT(line > 0);
1513 if (line == content->current_line)
1514 return SVN_NO_ERROR;
1516 saved_line = content->current_line;
1517 saved_eof = content->eof;
1519 if (line <= (svn_linenum_t)content->lines->nelts)
1523 offset = APR_ARRAY_IDX(content->lines, line - 1, apr_off_t);
1524 SVN_ERR(content->seek(content->read_baton, offset,
1526 content->current_line = line;
1531 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1533 while (! content->eof && content->current_line < line)
1535 svn_pool_clear(iterpool);
1536 SVN_ERR(readline(content, &dummy, iterpool, iterpool));
1538 svn_pool_destroy(iterpool);
1541 /* After seeking backwards from EOF position clear EOF indicator. */
1542 if (saved_eof && saved_line > content->current_line)
1543 content->eof = FALSE;
1545 return SVN_NO_ERROR;
1548 /* Indicate in *MATCHED whether the original text of HUNK matches the patch
1549 * CONTENT at its current line. Lines within FUZZ lines of the start or
1550 * end of HUNK will always match. If IGNORE_WHITESPACE is set, we ignore
1551 * whitespace when doing the matching. When this function returns, neither
1552 * CONTENT->CURRENT_LINE nor the file offset in the target file will
1553 * have changed. If MATCH_MODIFIED is TRUE, match the modified hunk text,
1554 * rather than the original hunk text.
1555 * Do temporary allocations in POOL. */
1556 static svn_error_t *
1557 match_hunk(svn_boolean_t *matched, target_content_t *content,
1558 svn_diff_hunk_t *hunk, svn_linenum_t fuzz,
1559 svn_boolean_t ignore_whitespace,
1560 svn_boolean_t match_modified, apr_pool_t *pool)
1562 svn_stringbuf_t *hunk_line;
1563 const char *target_line;
1564 svn_linenum_t lines_read;
1565 svn_linenum_t saved_line;
1566 svn_boolean_t hunk_eof;
1567 svn_boolean_t lines_matched;
1568 apr_pool_t *iterpool;
1569 svn_linenum_t hunk_length;
1570 svn_linenum_t leading_context;
1571 svn_linenum_t trailing_context;
1572 svn_linenum_t fuzz_penalty;
1577 return SVN_NO_ERROR;
1579 fuzz_penalty = svn_diff_hunk__get_fuzz_penalty(hunk);
1581 if (fuzz_penalty > fuzz)
1582 return SVN_NO_ERROR;
1584 fuzz -= fuzz_penalty;
1586 saved_line = content->current_line;
1588 lines_matched = FALSE;
1589 leading_context = svn_diff_hunk_get_leading_context(hunk);
1590 trailing_context = svn_diff_hunk_get_trailing_context(hunk);
1593 svn_diff_hunk_reset_modified_text(hunk);
1594 hunk_length = svn_diff_hunk_get_modified_length(hunk);
1598 svn_diff_hunk_reset_original_text(hunk);
1599 hunk_length = svn_diff_hunk_get_original_length(hunk);
1601 iterpool = svn_pool_create(pool);
1604 const char *hunk_line_translated;
1606 svn_pool_clear(iterpool);
1609 SVN_ERR(svn_diff_hunk_readline_modified_text(hunk, &hunk_line,
1611 iterpool, iterpool));
1613 SVN_ERR(svn_diff_hunk_readline_original_text(hunk, &hunk_line,
1615 iterpool, iterpool));
1617 /* Contract keywords, if any, before matching. */
1618 SVN_ERR(svn_subst_translate_cstring2(hunk_line->data,
1619 &hunk_line_translated,
1621 content->keywords, FALSE,
1623 SVN_ERR(readline(content, &target_line, iterpool, iterpool));
1627 /* If the last line doesn't have a newline, we get EOF but still
1628 * have a non-empty line to compare. */
1629 if ((hunk_eof && hunk_line->len == 0) ||
1630 (content->eof && *target_line == 0))
1633 /* Leading/trailing fuzzy lines always match. */
1634 if ((lines_read <= fuzz && leading_context > fuzz) ||
1635 (lines_read > hunk_length - fuzz && trailing_context > fuzz))
1636 lines_matched = TRUE;
1639 if (ignore_whitespace)
1641 char *hunk_line_trimmed;
1642 char *target_line_trimmed;
1644 hunk_line_trimmed = apr_pstrdup(iterpool, hunk_line_translated);
1645 target_line_trimmed = apr_pstrdup(iterpool, target_line);
1646 apr_collapse_spaces(hunk_line_trimmed, hunk_line_trimmed);
1647 apr_collapse_spaces(target_line_trimmed, target_line_trimmed);
1648 lines_matched = ! strcmp(hunk_line_trimmed, target_line_trimmed);
1651 lines_matched = ! strcmp(hunk_line_translated, target_line);
1654 while (lines_matched);
1656 *matched = lines_matched && hunk_eof && hunk_line->len == 0;
1657 SVN_ERR(seek_to_line(content, saved_line, iterpool));
1658 svn_pool_destroy(iterpool);
1660 return SVN_NO_ERROR;
1663 /* Scan lines of CONTENT for a match of the original text of HUNK,
1664 * up to but not including the specified UPPER_LINE. Use fuzz factor FUZZ.
1665 * If UPPER_LINE is zero scan until EOF occurs when reading from TARGET.
1666 * Return the line at which HUNK was matched in *MATCHED_LINE.
1667 * If the hunk did not match at all, set *MATCHED_LINE to zero.
1668 * If the hunk matched multiple times, and MATCH_FIRST is TRUE,
1669 * return the line number at which the first match occurred in *MATCHED_LINE.
1670 * If the hunk matched multiple times, and MATCH_FIRST is FALSE,
1671 * return the line number at which the last match occurred in *MATCHED_LINE.
1672 * If IGNORE_WHITESPACE is set, ignore whitespace during the matching.
1673 * If MATCH_MODIFIED is TRUE, match the modified hunk text,
1674 * rather than the original hunk text.
1675 * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
1676 * Do all allocations in POOL. */
1677 static svn_error_t *
1678 scan_for_match(svn_linenum_t *matched_line,
1679 target_content_t *content,
1680 svn_diff_hunk_t *hunk, svn_boolean_t match_first,
1681 svn_linenum_t upper_line, svn_linenum_t fuzz,
1682 svn_boolean_t ignore_whitespace,
1683 svn_boolean_t match_modified,
1684 svn_cancel_func_t cancel_func, void *cancel_baton,
1687 apr_pool_t *iterpool;
1690 iterpool = svn_pool_create(pool);
1691 while ((content->current_line < upper_line || upper_line == 0) &&
1694 svn_boolean_t matched;
1696 svn_pool_clear(iterpool);
1699 SVN_ERR(cancel_func(cancel_baton));
1701 SVN_ERR(match_hunk(&matched, content, hunk, fuzz, ignore_whitespace,
1702 match_modified, iterpool));
1705 svn_boolean_t taken = FALSE;
1708 /* Don't allow hunks to match at overlapping locations. */
1709 for (i = 0; i < content->hunks->nelts; i++)
1711 const hunk_info_t *hi;
1712 svn_linenum_t length;
1714 hi = APR_ARRAY_IDX(content->hunks, i, const hunk_info_t *);
1717 length = svn_diff_hunk_get_modified_length(hi->hunk);
1719 length = svn_diff_hunk_get_original_length(hi->hunk);
1721 taken = (! hi->rejected &&
1722 content->current_line >= hi->matched_line &&
1723 content->current_line < (hi->matched_line + length));
1730 *matched_line = content->current_line;
1737 SVN_ERR(seek_to_line(content, content->current_line + 1,
1740 svn_pool_destroy(iterpool);
1742 return SVN_NO_ERROR;
1745 /* Indicate in *MATCH whether the content described by CONTENT
1746 * matches the modified text of HUNK.
1747 * Use SCRATCH_POOL for temporary allocations. */
1748 static svn_error_t *
1749 match_existing_target(svn_boolean_t *match,
1750 target_content_t *content,
1751 svn_diff_hunk_t *hunk,
1752 apr_pool_t *scratch_pool)
1754 svn_boolean_t lines_matched;
1755 apr_pool_t *iterpool;
1756 svn_boolean_t hunk_eof;
1757 svn_linenum_t saved_line;
1759 svn_diff_hunk_reset_modified_text(hunk);
1761 saved_line = content->current_line;
1763 iterpool = svn_pool_create(scratch_pool);
1767 svn_stringbuf_t *hunk_line;
1768 const char *line_translated;
1769 const char *hunk_line_translated;
1771 svn_pool_clear(iterpool);
1773 SVN_ERR(readline(content, &line, iterpool, iterpool));
1774 SVN_ERR(svn_diff_hunk_readline_modified_text(hunk, &hunk_line,
1776 iterpool, iterpool));
1777 /* Contract keywords. */
1778 SVN_ERR(svn_subst_translate_cstring2(line, &line_translated,
1782 SVN_ERR(svn_subst_translate_cstring2(hunk_line->data,
1783 &hunk_line_translated,
1787 lines_matched = ! strcmp(line_translated, hunk_line_translated);
1788 if (content->eof != hunk_eof)
1790 svn_pool_destroy(iterpool);
1792 return SVN_NO_ERROR;
1795 while (lines_matched && ! content->eof && ! hunk_eof);
1796 svn_pool_destroy(iterpool);
1798 *match = (lines_matched && content->eof == hunk_eof);
1799 SVN_ERR(seek_to_line(content, saved_line, scratch_pool));
1801 return SVN_NO_ERROR;
1804 /* Determine the line at which a HUNK applies to CONTENT of the TARGET
1805 * file, and return an appropriate hunk_info object in *HI, allocated from
1806 * RESULT_POOL. Use fuzz factor FUZZ. Set HI->FUZZ to FUZZ. If no correct
1807 * line can be determined, set HI->REJECTED to TRUE. PREVIOUS_OFFSET
1808 * is the offset at which the previous matching hunk was applied, or zero.
1809 * IGNORE_WHITESPACE tells whether whitespace should be considered when
1810 * matching. IS_PROP_HUNK indicates whether the hunk patches file content
1812 * When this function returns, neither CONTENT->CURRENT_LINE nor
1813 * the file offset in the target file will have changed.
1814 * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
1815 * Do temporary allocations in POOL. */
1816 static svn_error_t *
1817 get_hunk_info(hunk_info_t **hi, patch_target_t *target,
1818 target_content_t *content,
1819 svn_diff_hunk_t *hunk, svn_linenum_t fuzz,
1820 svn_linenum_t previous_offset,
1821 svn_boolean_t ignore_whitespace,
1822 svn_boolean_t is_prop_hunk,
1823 svn_cancel_func_t cancel_func, void *cancel_baton,
1824 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
1826 svn_linenum_t matched_line;
1827 svn_linenum_t original_start;
1828 svn_boolean_t already_applied;
1830 original_start = svn_diff_hunk_get_original_start(hunk) + previous_offset;
1831 already_applied = FALSE;
1833 /* An original offset of zero means that this hunk wants to create
1834 * a new file. Don't bother matching hunks in that case, since
1835 * the hunk applies at line 1. If the file already exists, the hunk
1836 * is rejected, unless the file is versioned and its content matches
1837 * the file the patch wants to create. */
1838 if (original_start == 0 && fuzz > 0)
1840 matched_line = 0; /* reject any fuzz for new files */
1842 else if (original_start == 0 && ! is_prop_hunk)
1844 if (target->kind_on_disk == svn_node_file)
1846 const svn_io_dirent2_t *dirent;
1847 SVN_ERR(svn_io_stat_dirent2(&dirent, target->local_abspath, FALSE,
1848 TRUE, scratch_pool, scratch_pool));
1850 if (dirent->kind == svn_node_file
1852 && dirent->filesize == 0)
1854 matched_line = 1; /* Matched an on-disk empty file */
1858 if (target->db_kind == svn_node_file)
1860 svn_boolean_t file_matches;
1862 SVN_ERR(match_existing_target(&file_matches, content, hunk,
1867 already_applied = TRUE;
1870 matched_line = 0; /* reject */
1873 matched_line = 0; /* reject */
1879 /* Same conditions apply as for the file case above.
1881 * ### Since the hunk says the prop should be added we just assume so for
1882 * ### now and don't bother with storing the previous lines and such. When
1883 * ### we have the diff operation available we can just check for adds. */
1884 else if (original_start == 0 && is_prop_hunk)
1886 if (content->existed)
1888 svn_boolean_t prop_matches;
1890 SVN_ERR(match_existing_target(&prop_matches, content, hunk,
1896 already_applied = TRUE;
1899 matched_line = 0; /* reject */
1904 else if (original_start > 0 && content->existed)
1906 svn_linenum_t modified_start;
1907 svn_linenum_t saved_line = content->current_line;
1909 modified_start = svn_diff_hunk_get_modified_start(hunk);
1911 /* Scan for a match at the line where the hunk thinks it
1912 * should be going. */
1913 SVN_ERR(seek_to_line(content, original_start, scratch_pool));
1914 if (content->current_line != original_start)
1920 SVN_ERR(scan_for_match(&matched_line, content, hunk, TRUE,
1921 original_start + 1, fuzz,
1922 ignore_whitespace, FALSE,
1923 cancel_func, cancel_baton,
1926 if (matched_line != original_start)
1928 /* Check if the hunk is already applied.
1929 * We only check for an exact match here, and don't bother checking
1930 * for already applied patches with offset/fuzz, because such a
1931 * check would be ambiguous. */
1934 if (modified_start == 0
1935 && (target->operation == svn_diff_op_unchanged
1936 || target->operation == svn_diff_op_deleted))
1938 /* Patch wants to delete the file. */
1940 already_applied = target->locally_deleted;
1944 svn_linenum_t seek_to;
1946 if (modified_start == 0)
1947 seek_to = 1; /* Empty file case */
1949 seek_to = modified_start;
1951 SVN_ERR(seek_to_line(content, seek_to, scratch_pool));
1952 SVN_ERR(scan_for_match(&matched_line, content,
1955 fuzz, ignore_whitespace, TRUE,
1956 cancel_func, cancel_baton,
1958 already_applied = (matched_line == modified_start);
1962 already_applied = FALSE;
1964 if (! already_applied)
1967 svn_linenum_t search_start = 1, search_end = 0;
1968 svn_linenum_t matched_line2;
1970 /* Search for closest match before or after original
1971 start. We have no backward search so search forwards
1972 from the previous match (or start of file) to the
1973 original start looking for the last match. Then
1974 search forwards from the original start looking for a
1975 better match. Finally search forwards from the start
1976 of file to the previous hunk if that could result in
1979 for (i = content->hunks->nelts; i > 0; --i)
1981 const hunk_info_t *prev
1982 = APR_ARRAY_IDX(content->hunks, i - 1, const hunk_info_t *);
1983 if (!prev->rejected)
1985 svn_linenum_t length;
1987 length = svn_diff_hunk_get_original_length(prev->hunk);
1988 search_start = prev->matched_line + length;
1993 /* Search from the previous match, or start of file,
1994 towards the original location. */
1995 SVN_ERR(seek_to_line(content, search_start, scratch_pool));
1996 SVN_ERR(scan_for_match(&matched_line, content, hunk, FALSE,
1997 original_start, fuzz,
1998 ignore_whitespace, FALSE,
1999 cancel_func, cancel_baton,
2002 /* If a match we only need to search forwards for a
2003 better match, otherwise to the end of the file. */
2005 search_end = original_start + (original_start - matched_line);
2007 /* Search from original location, towards the end. */
2008 SVN_ERR(seek_to_line(content, original_start + 1, scratch_pool));
2009 SVN_ERR(scan_for_match(&matched_line2, content, hunk,
2010 TRUE, search_end, fuzz, ignore_whitespace,
2011 FALSE, cancel_func, cancel_baton,
2014 /* Chose the forward match if it is closer than the
2015 backward match or if there is no backward match. */
2018 || (matched_line2 - original_start
2019 < original_start - matched_line)))
2020 matched_line = matched_line2;
2022 /* Search from before previous hunk if there could be a
2024 if (search_start > 1
2026 || (matched_line > original_start
2027 && (matched_line - original_start
2028 > original_start - search_start))))
2030 svn_linenum_t search_start2 = 1;
2033 && matched_line - original_start < original_start)
2035 = original_start - (matched_line - original_start) + 1;
2037 SVN_ERR(seek_to_line(content, search_start2, scratch_pool));
2038 SVN_ERR(scan_for_match(&matched_line2, content, hunk, FALSE,
2039 search_start - 1, fuzz,
2040 ignore_whitespace, FALSE,
2041 cancel_func, cancel_baton,
2044 matched_line = matched_line2;
2048 else if (matched_line > 0
2050 && (svn_diff_hunk_get_leading_context(hunk) == 0
2051 || svn_diff_hunk_get_trailing_context(hunk) == 0)
2052 && (svn_diff_hunk_get_modified_length(hunk) >
2053 svn_diff_hunk_get_original_length(hunk)))
2055 /* Check that we are not applying the same change that just adds some
2056 lines again, when we don't have enough context to see the
2058 svn_linenum_t reverse_matched_line;
2060 SVN_ERR(seek_to_line(content, modified_start, scratch_pool));
2061 SVN_ERR(scan_for_match(&reverse_matched_line, content,
2064 fuzz, ignore_whitespace, TRUE,
2065 cancel_func, cancel_baton,
2068 /* We might want to check that we are actually at the start or the
2069 end of the file. Having no context implies that we should be. */
2070 already_applied = (reverse_matched_line == modified_start);
2073 SVN_ERR(seek_to_line(content, saved_line, scratch_pool));
2075 else if (!content->existed && svn_diff_hunk_get_modified_start(hunk) == 0)
2077 /* The hunk wants to delete a file or property which doesn't exist. */
2079 already_applied = TRUE;
2083 /* The hunk wants to modify a file or property which doesn't exist. */
2087 (*hi) = apr_pcalloc(result_pool, sizeof(hunk_info_t));
2089 (*hi)->matched_line = matched_line;
2090 (*hi)->rejected = (matched_line == 0);
2091 (*hi)->already_applied = already_applied;
2092 (*hi)->report_fuzz = fuzz;
2093 (*hi)->match_fuzz = fuzz - svn_diff_hunk__get_fuzz_penalty(hunk);
2095 return SVN_NO_ERROR;
2098 /* Copy lines to the patched content until the specified LINE has been
2099 * reached. Indicate in *EOF whether end-of-file was encountered while
2100 * reading from the target.
2101 * If LINE is zero, copy lines until end-of-file has been reached.
2102 * Do all allocations in POOL. */
2103 static svn_error_t *
2104 copy_lines_to_target(target_content_t *content, svn_linenum_t line,
2107 apr_pool_t *iterpool;
2109 iterpool = svn_pool_create(pool);
2110 while ((content->current_line < line || line == 0) && ! content->eof)
2112 const char *target_line;
2115 svn_pool_clear(iterpool);
2117 SVN_ERR(readline(content, &target_line, iterpool, iterpool));
2119 target_line = apr_pstrcat(iterpool, target_line, content->eol_str,
2121 len = strlen(target_line);
2122 SVN_ERR(content->write(content->write_baton, target_line,
2125 svn_pool_destroy(iterpool);
2127 return SVN_NO_ERROR;
2130 /* Write the diff text of HUNK to TARGET's reject file,
2131 * and mark TARGET as having had rejects.
2132 * We don't expand keywords, nor normalise line-endings, in reject files.
2133 * Do temporary allocations in SCRATCH_POOL. */
2134 static svn_error_t *
2135 reject_hunk(patch_target_t *target, target_content_t *content,
2136 svn_diff_hunk_t *hunk, const char *prop_name,
2140 static const char * const text_atat = "@@";
2141 static const char * const prop_atat = "##";
2143 apr_pool_t *iterpool;
2147 /* ### Print 'Added', 'Deleted' or 'Modified' instead of 'Property'. */
2148 svn_stream_printf(target->reject_stream,
2149 pool, "Property: %s" APR_EOL_STR, prop_name);
2157 SVN_ERR(svn_stream_printf(target->reject_stream, pool,
2158 "%s -%lu,%lu +%lu,%lu %s" APR_EOL_STR,
2160 svn_diff_hunk_get_original_start(hunk),
2161 svn_diff_hunk_get_original_length(hunk),
2162 svn_diff_hunk_get_modified_start(hunk),
2163 svn_diff_hunk_get_modified_length(hunk),
2166 iterpool = svn_pool_create(pool);
2169 svn_stringbuf_t *hunk_line;
2170 const char *eol_str;
2172 svn_pool_clear(iterpool);
2174 SVN_ERR(svn_diff_hunk_readline_diff_text(hunk, &hunk_line, &eol_str,
2175 &eof, iterpool, iterpool));
2178 if (hunk_line->len >= 1)
2180 apr_size_t len = hunk_line->len;
2182 SVN_ERR(svn_stream_write(target->reject_stream,
2183 hunk_line->data, &len));
2188 SVN_ERR(svn_stream_puts(target->reject_stream, eol_str));
2193 svn_pool_destroy(iterpool);
2196 target->had_prop_rejects = TRUE;
2198 target->had_rejects = TRUE;
2200 return SVN_NO_ERROR;
2203 /* Write the modified text of the hunk described by HI to the patched
2204 * CONTENT. TARGET is the patch target.
2205 * If PROP_NAME is not NULL, the hunk is assumed to be targeted for
2206 * a property with the given name.
2207 * Do temporary allocations in POOL. */
2208 static svn_error_t *
2209 apply_hunk(patch_target_t *target, target_content_t *content,
2210 hunk_info_t *hi, const char *prop_name, apr_pool_t *pool)
2212 svn_linenum_t lines_read;
2214 apr_pool_t *iterpool;
2215 svn_linenum_t fuzz = hi->match_fuzz;
2217 /* ### Is there a cleaner way to describe if we have an existing target?
2219 if (target->kind_on_disk == svn_node_file || prop_name)
2223 /* Move forward to the hunk's line, copying data as we go.
2224 * Also copy leading lines of context which matched with fuzz.
2225 * The target has changed on the fuzzy-matched lines,
2226 * so we should retain the target's version of those lines. */
2227 SVN_ERR(copy_lines_to_target(content, hi->matched_line + fuzz,
2230 /* Skip the target's version of the hunk.
2231 * Don't skip trailing lines which matched with fuzz. */
2232 line = content->current_line +
2233 svn_diff_hunk_get_original_length(hi->hunk) - (2 * fuzz);
2234 SVN_ERR(seek_to_line(content, line, pool));
2235 if (content->current_line != line && ! content->eof)
2237 /* Seek failed, reject this hunk. */
2238 hi->rejected = TRUE;
2239 SVN_ERR(reject_hunk(target, content, hi->hunk, prop_name, pool));
2240 return SVN_NO_ERROR;
2244 /* Write the hunk's version to the patched result.
2245 * Don't write the lines which matched with fuzz. */
2247 svn_diff_hunk_reset_modified_text(hi->hunk);
2248 iterpool = svn_pool_create(pool);
2251 svn_stringbuf_t *hunk_line;
2252 const char *eol_str;
2254 svn_pool_clear(iterpool);
2256 SVN_ERR(svn_diff_hunk_readline_modified_text(hi->hunk, &hunk_line,
2258 iterpool, iterpool));
2260 if (lines_read > fuzz &&
2261 lines_read <= svn_diff_hunk_get_modified_length(hi->hunk) - fuzz)
2265 if (hunk_line->len >= 1)
2267 len = hunk_line->len;
2268 SVN_ERR(content->write(content->write_baton,
2269 hunk_line->data, len, iterpool));
2274 /* Use the EOL as it was read from the patch file,
2275 * unless the target's EOL style is set by svn:eol-style */
2276 if (content->eol_style != svn_subst_eol_style_none)
2277 eol_str = content->eol_str;
2279 len = strlen(eol_str);
2280 SVN_ERR(content->write(content->write_baton,
2281 eol_str, len, iterpool));
2286 svn_pool_destroy(iterpool);
2289 target->has_prop_changes = TRUE;
2291 target->has_text_changes = TRUE;
2293 return SVN_NO_ERROR;
2296 /* Use client context CTX to send a suitable notification for hunk HI,
2297 * using TARGET to determine the path. If the hunk is a property hunk,
2298 * PROP_NAME must be the name of the property, else NULL.
2299 * Use POOL for temporary allocations. */
2300 static svn_error_t *
2301 send_hunk_notification(const hunk_info_t *hi,
2302 const patch_target_t *target,
2303 const char *prop_name,
2304 const svn_client_ctx_t *ctx,
2307 svn_wc_notify_t *notify;
2308 svn_wc_notify_action_t action;
2310 if (hi->already_applied)
2311 action = svn_wc_notify_patch_hunk_already_applied;
2312 else if (hi->rejected)
2313 action = svn_wc_notify_patch_rejected_hunk;
2315 action = svn_wc_notify_patch_applied_hunk;
2317 notify = svn_wc_create_notify(target->local_abspath
2318 ? target->local_abspath
2319 : target->local_relpath,
2321 notify->hunk_original_start =
2322 svn_diff_hunk_get_original_start(hi->hunk);
2323 notify->hunk_original_length =
2324 svn_diff_hunk_get_original_length(hi->hunk);
2325 notify->hunk_modified_start =
2326 svn_diff_hunk_get_modified_start(hi->hunk);
2327 notify->hunk_modified_length =
2328 svn_diff_hunk_get_modified_length(hi->hunk);
2329 notify->hunk_matched_line = hi->matched_line;
2330 notify->hunk_fuzz = hi->report_fuzz;
2331 notify->prop_name = prop_name;
2333 ctx->notify_func2(ctx->notify_baton2, notify, pool);
2335 return SVN_NO_ERROR;
2338 /* Use client context CTX to send a suitable notification for a patch TARGET.
2339 * Use POOL for temporary allocations. */
2340 static svn_error_t *
2341 send_patch_notification(const patch_target_t *target,
2342 const svn_client_ctx_t *ctx,
2343 apr_pool_t *scratch_pool)
2345 svn_wc_notify_t *notify;
2346 svn_wc_notify_action_t action;
2347 const char *notify_path;
2349 if (! ctx->notify_func2)
2350 return SVN_NO_ERROR;
2352 if (target->skipped)
2353 action = svn_wc_notify_skip;
2354 else if (target->deleted)
2355 action = svn_wc_notify_delete;
2356 else if (target->added || target->move_target_abspath)
2357 action = svn_wc_notify_add;
2359 action = svn_wc_notify_patch;
2361 if (target->move_target_abspath)
2362 notify_path = target->move_target_abspath;
2364 notify_path = target->local_abspath ? target->local_abspath
2365 : target->local_relpath;
2367 notify = svn_wc_create_notify(notify_path, action, scratch_pool);
2368 notify->kind = (target->db_kind == svn_node_dir) ? svn_node_dir
2371 if (action == svn_wc_notify_skip)
2373 if (target->obstructed)
2374 notify->content_state = svn_wc_notify_state_obstructed;
2375 else if (target->db_kind == svn_node_none ||
2376 target->db_kind == svn_node_unknown)
2377 notify->content_state = svn_wc_notify_state_missing;
2379 notify->content_state = svn_wc_notify_state_unknown;
2383 if (target->had_rejects)
2384 notify->content_state = svn_wc_notify_state_conflicted;
2385 else if (target->has_text_changes)
2386 notify->content_state = svn_wc_notify_state_changed;
2387 else if (target->had_already_applied)
2388 notify->content_state = svn_wc_notify_state_merged;
2390 notify->content_state = svn_wc_notify_state_unchanged;
2392 if (target->had_prop_rejects)
2393 notify->prop_state = svn_wc_notify_state_conflicted;
2394 else if (target->has_prop_changes)
2395 notify->prop_state = svn_wc_notify_state_changed;
2396 else if (target->had_prop_already_applied)
2397 notify->prop_state = svn_wc_notify_state_merged;
2399 notify->prop_state = svn_wc_notify_state_unchanged;
2402 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
2404 if (action == svn_wc_notify_patch)
2407 apr_pool_t *iterpool;
2408 apr_array_header_t *prop_targets;
2410 iterpool = svn_pool_create(scratch_pool);
2411 for (i = 0; i < target->content->hunks->nelts; i++)
2413 const hunk_info_t *hi;
2415 svn_pool_clear(iterpool);
2417 hi = APR_ARRAY_IDX(target->content->hunks, i, hunk_info_t *);
2419 SVN_ERR(send_hunk_notification(hi, target, NULL /* prop_name */,
2423 prop_targets = svn_sort__hash(target->prop_targets,
2424 svn_sort_compare_items_lexically,
2426 for (i = 0; i < prop_targets->nelts; i++)
2429 svn_sort__item_t item = APR_ARRAY_IDX(prop_targets, i,
2432 prop_patch_target_t *prop_target = item.value;
2434 for (j = 0; j < prop_target->content->hunks->nelts; j++)
2436 const hunk_info_t *hi;
2438 svn_pool_clear(iterpool);
2440 hi = APR_ARRAY_IDX(prop_target->content->hunks, j,
2443 /* Don't notify on the hunk level for added or deleted props. */
2444 if ((prop_target->operation != svn_diff_op_added &&
2445 prop_target->operation != svn_diff_op_deleted)
2446 || hi->rejected || hi->already_applied)
2447 SVN_ERR(send_hunk_notification(hi, target, prop_target->name,
2451 svn_pool_destroy(iterpool);
2454 if (!target->skipped && target->move_target_abspath)
2456 /* Notify about deletion of move source. */
2457 notify = svn_wc_create_notify(target->local_abspath,
2458 svn_wc_notify_delete, scratch_pool);
2459 notify->kind = svn_node_file;
2460 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
2463 return SVN_NO_ERROR;
2466 /* Implements the callback for svn_sort__array. Puts hunks that match
2467 before hunks that do not match, puts hunks that match in order
2468 based on postion matched, puts hunks that do not match in order
2469 based on original position. */
2471 sort_matched_hunks(const void *a, const void *b)
2473 const hunk_info_t *item1 = *((const hunk_info_t * const *)a);
2474 const hunk_info_t *item2 = *((const hunk_info_t * const *)b);
2475 svn_boolean_t matched1 = !item1->rejected && !item1->already_applied;
2476 svn_boolean_t matched2 = !item2->rejected && !item2->already_applied;
2477 svn_linenum_t original1, original2;
2479 if (matched1 && matched2)
2481 /* Both match so use order matched in file. */
2482 if (item1->matched_line > item2->matched_line)
2484 else if (item1->matched_line == item2->matched_line)
2490 /* Only second matches, put it before first. */
2493 /* Only first matches, put it before second. */
2496 /* Neither matches, sort by original_start. */
2497 original1 = svn_diff_hunk_get_original_start(item1->hunk);
2498 original2 = svn_diff_hunk_get_original_start(item2->hunk);
2499 if (original1 > original2)
2501 else if (original1 == original2)
2508 /* Apply a PATCH to a working copy at ABS_WC_PATH and put the result
2509 * into temporary files, to be installed in the working copy later.
2510 * Return information about the patch target in *PATCH_TARGET, allocated
2511 * in RESULT_POOL. Use WC_CTX as the working copy context.
2512 * STRIP_COUNT specifies the number of leading path components
2513 * which should be stripped from target paths in the patch.
2514 * REMOVE_TEMPFILES is as in svn_client_patch().
2515 * TARGETS_INFO is for preserving info across calls.
2516 * IGNORE_WHITESPACE tells whether whitespace should be considered when
2517 * doing the matching.
2518 * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
2519 * Do temporary allocations in SCRATCH_POOL. */
2520 static svn_error_t *
2521 apply_one_patch(patch_target_t **patch_target, svn_patch_t *patch,
2522 const char *abs_wc_path, svn_wc_context_t *wc_ctx,
2524 svn_boolean_t ignore_whitespace,
2525 svn_boolean_t remove_tempfiles,
2526 const apr_array_header_t *targets_info,
2527 svn_cancel_func_t cancel_func,
2529 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
2531 patch_target_t *target;
2532 apr_pool_t *iterpool;
2534 static const svn_linenum_t MAX_FUZZ = 2;
2535 apr_hash_index_t *hash_index;
2536 svn_linenum_t previous_offset = 0;
2537 apr_array_header_t *prop_targets;
2539 SVN_ERR(init_patch_target(&target, patch, abs_wc_path, wc_ctx, strip_count,
2540 remove_tempfiles, targets_info,
2541 result_pool, scratch_pool));
2542 if (target->skipped)
2544 *patch_target = target;
2545 return SVN_NO_ERROR;
2548 iterpool = svn_pool_create(scratch_pool);
2550 if (patch->hunks && patch->hunks->nelts)
2553 for (i = 0; i < patch->hunks->nelts; i++)
2555 svn_diff_hunk_t *hunk;
2557 svn_linenum_t fuzz = 0;
2559 svn_pool_clear(iterpool);
2562 SVN_ERR(cancel_func(cancel_baton));
2564 hunk = APR_ARRAY_IDX(patch->hunks, i, svn_diff_hunk_t *);
2566 /* Determine the line the hunk should be applied at.
2567 * If no match is found initially, try with fuzz. */
2570 SVN_ERR(get_hunk_info(&hi, target, target->content, hunk, fuzz,
2573 FALSE /* is_prop_hunk */,
2574 cancel_func, cancel_baton,
2575 result_pool, iterpool));
2578 while (hi->rejected && fuzz <= MAX_FUZZ && ! hi->already_applied);
2580 if (hi->matched_line)
2582 = hi->matched_line - svn_diff_hunk_get_original_start(hunk);
2584 APR_ARRAY_PUSH(target->content->hunks, hunk_info_t *) = hi;
2587 /* Hunks are applied in the order determined by the matched line and
2588 this may be different from the order of the original lines. */
2589 svn_sort__array(target->content->hunks, sort_matched_hunks);
2591 /* Apply or reject hunks. */
2592 for (i = 0; i < target->content->hunks->nelts; i++)
2596 svn_pool_clear(iterpool);
2599 SVN_ERR(cancel_func(cancel_baton));
2601 hi = APR_ARRAY_IDX(target->content->hunks, i, hunk_info_t *);
2602 if (hi->already_applied)
2604 target->had_already_applied = TRUE;
2607 else if (hi->rejected)
2608 SVN_ERR(reject_hunk(target, target->content, hi->hunk,
2609 NULL /* prop_name */,
2612 SVN_ERR(apply_hunk(target, target->content, hi,
2613 NULL /* prop_name */, iterpool));
2616 if (target->kind_on_disk == svn_node_file)
2618 /* Copy any remaining lines to target. */
2619 SVN_ERR(copy_lines_to_target(target->content, 0, scratch_pool));
2620 if (! target->content->eof)
2622 /* We could not copy the entire target file to the temporary
2623 * file, and would truncate the target if we copied the
2624 * temporary file on top of it. Skip this target. */
2625 target->skipped = TRUE;
2629 else if (patch->binary_patch)
2631 svn_stream_t *orig_stream;
2635 orig_stream = svn_stream_from_aprfile2(target->file, TRUE, iterpool);
2637 orig_stream = svn_stream_empty(iterpool);
2639 SVN_ERR(svn_stream_contents_same2(
2641 svn_diff_get_binary_diff_original_stream(patch->binary_patch,
2644 svn_pool_clear(iterpool);
2648 /* The file in the working copy is identical to the one expected by
2649 the patch... So we can write the result stream; no fuzz,
2650 just a 100% match */
2652 target->has_text_changes = TRUE;
2656 /* Perhaps the file is identical to the resulting version, implying
2657 that the patch has already been applied */
2660 apr_off_t start = 0;
2662 SVN_ERR(svn_io_file_seek(target->file, APR_SET, &start, iterpool));
2664 orig_stream = svn_stream_from_aprfile2(target->file, TRUE, iterpool);
2667 orig_stream = svn_stream_empty(iterpool);
2669 SVN_ERR(svn_stream_contents_same2(
2671 svn_diff_get_binary_diff_result_stream(patch->binary_patch,
2674 svn_pool_clear(iterpool);
2677 target->had_already_applied = TRUE;
2682 SVN_ERR(svn_stream_copy3(
2683 svn_diff_get_binary_diff_result_stream(patch->binary_patch,
2685 svn_stream_from_aprfile2(target->patched_file, TRUE,
2687 cancel_func, cancel_baton,
2692 /* ### TODO: Implement a proper reject of a binary patch
2694 This should at least setup things for a proper notification,
2695 and perhaps install a normal text conflict. Unlike normal unified
2696 diff based patches we have all the versions we would need for
2697 that in a much easier format than can be obtained from the patch
2699 target->skipped = TRUE;
2702 else if (target->move_target_abspath)
2704 /* ### Why do we do this?
2705 BH: I don't know, but if we don't do this some tests
2706 on git style patches break.
2708 ### It would be much better to really move the actual file instead
2709 of copying to a temporary file; move that to target and then
2710 delete the original file
2712 ### BH: I have absolutely no idea if moving directories would work.
2714 if (target->kind_on_disk == svn_node_file)
2716 /* Copy any remaining lines to target. (read: all lines) */
2717 SVN_ERR(copy_lines_to_target(target->content, 0, scratch_pool));
2718 if (!target->content->eof)
2720 /* We could not copy the entire target file to the temporary
2721 * file, and would truncate the target if we copied the
2722 * temporary file on top of it. Skip this target. */
2723 target->skipped = TRUE;
2728 if (target->had_rejects || target->locally_deleted)
2729 target->deleted = FALSE;
2732 && !(target->locally_deleted || target->db_kind == svn_node_none))
2734 target->added = FALSE;
2737 /* Assume nothing changed. Will be updated via property hunks */
2738 target->is_special = target->is_symlink;
2740 /* Match property hunks. */
2741 for (hash_index = apr_hash_first(scratch_pool, patch->prop_patches);
2743 hash_index = apr_hash_next(hash_index))
2745 svn_prop_patch_t *prop_patch;
2746 const char *prop_name;
2747 prop_patch_target_t *prop_target;
2749 prop_name = apr_hash_this_key(hash_index);
2750 prop_patch = apr_hash_this_val(hash_index);
2752 if (!strcmp(prop_name, SVN_PROP_SPECIAL))
2753 target->is_special = (prop_patch->operation != svn_diff_op_deleted);
2755 /* We'll store matched hunks in prop_content. */
2756 prop_target = svn_hash_gets(target->prop_targets, prop_name);
2758 for (i = 0; i < prop_patch->hunks->nelts; i++)
2760 svn_diff_hunk_t *hunk;
2762 svn_linenum_t fuzz = 0;
2764 svn_pool_clear(iterpool);
2767 SVN_ERR(cancel_func(cancel_baton));
2769 hunk = APR_ARRAY_IDX(prop_patch->hunks, i, svn_diff_hunk_t *);
2771 /* Determine the line the hunk should be applied at.
2772 * If no match is found initially, try with fuzz. */
2775 SVN_ERR(get_hunk_info(&hi, target, prop_target->content,
2778 TRUE /* is_prop_hunk */,
2779 cancel_func, cancel_baton,
2780 result_pool, iterpool));
2783 while (hi->rejected && fuzz <= MAX_FUZZ && ! hi->already_applied);
2785 APR_ARRAY_PUSH(prop_target->content->hunks, hunk_info_t *) = hi;
2789 /* Match implied property hunks. */
2790 if (patch->new_executable_bit != svn_tristate_unknown
2791 && patch->new_executable_bit != patch->old_executable_bit
2792 && svn_hash_gets(target->prop_targets, SVN_PROP_EXECUTABLE)
2793 && !svn_hash_gets(patch->prop_patches, SVN_PROP_EXECUTABLE))
2796 svn_diff_hunk_t *hunk;
2797 prop_patch_target_t *prop_target = svn_hash_gets(target->prop_targets,
2798 SVN_PROP_EXECUTABLE);
2800 if (patch->new_executable_bit == svn_tristate_true)
2801 SVN_ERR(svn_diff_hunk__create_adds_single_line(
2803 SVN_PROP_EXECUTABLE_VALUE,
2808 SVN_ERR(svn_diff_hunk__create_deletes_single_line(
2810 SVN_PROP_EXECUTABLE_VALUE,
2815 /* Derive a hunk_info from hunk. */
2816 SVN_ERR(get_hunk_info(&hi, target, prop_target->content,
2817 hunk, 0 /* fuzz */, 0 /* previous_offset */,
2819 TRUE /* is_prop_hunk */,
2820 cancel_func, cancel_baton,
2821 result_pool, iterpool));
2822 APR_ARRAY_PUSH(prop_target->content->hunks, hunk_info_t *) = hi;
2825 if (patch->new_symlink_bit != svn_tristate_unknown
2826 && patch->new_symlink_bit != patch->old_symlink_bit
2827 && svn_hash_gets(target->prop_targets, SVN_PROP_SPECIAL)
2828 && !svn_hash_gets(patch->prop_patches, SVN_PROP_SPECIAL))
2831 svn_diff_hunk_t *hunk;
2833 prop_patch_target_t *prop_target = svn_hash_gets(target->prop_targets,
2836 if (patch->new_symlink_bit == svn_tristate_true)
2838 SVN_ERR(svn_diff_hunk__create_adds_single_line(
2840 SVN_PROP_SPECIAL_VALUE,
2844 target->is_special = TRUE;
2848 SVN_ERR(svn_diff_hunk__create_deletes_single_line(
2850 SVN_PROP_SPECIAL_VALUE,
2854 target->is_special = FALSE;
2857 /* Derive a hunk_info from hunk. */
2858 SVN_ERR(get_hunk_info(&hi, target, prop_target->content,
2859 hunk, 0 /* fuzz */, 0 /* previous_offset */,
2861 TRUE /* is_prop_hunk */,
2862 cancel_func, cancel_baton,
2863 result_pool, iterpool));
2864 APR_ARRAY_PUSH(prop_target->content->hunks, hunk_info_t *) = hi;
2867 /* When the node is deleted or does not exist after the patch is applied
2868 we should reject a few more property hunks that can't be applied even
2869 though the source matched */
2871 || (!target->added &&
2872 (target->locally_deleted || target->db_kind == svn_node_none)))
2874 for (hash_index = apr_hash_first(scratch_pool, target->prop_targets);
2876 hash_index = apr_hash_next(hash_index))
2878 prop_patch_target_t *prop_target = apr_hash_this_val(hash_index);
2880 if (prop_target->operation == svn_diff_op_deleted)
2883 for (i = 0; i < prop_target->content->hunks->nelts; i++)
2887 hi = APR_ARRAY_IDX(prop_target->content->hunks, i, hunk_info_t*);
2889 if (hi->already_applied || hi->rejected)
2893 hi->rejected = TRUE;
2894 prop_target->skipped = TRUE;
2896 if (!target->deleted && !target->added)
2897 target->skipped = TRUE;
2903 /* Apply or reject property hunks. */
2905 prop_targets = svn_sort__hash(target->prop_targets,
2906 svn_sort_compare_items_lexically,
2908 for (i = 0; i < prop_targets->nelts; i++)
2910 svn_sort__item_t item = APR_ARRAY_IDX(prop_targets, i, svn_sort__item_t);
2911 prop_patch_target_t *prop_target = item.value;
2912 svn_boolean_t applied_one = FALSE;
2915 for (j = 0; j < prop_target->content->hunks->nelts; j++)
2919 svn_pool_clear(iterpool);
2921 hi = APR_ARRAY_IDX(prop_target->content->hunks, j,
2923 if (hi->already_applied)
2925 target->had_prop_already_applied = TRUE;
2928 else if (hi->rejected)
2929 SVN_ERR(reject_hunk(target, prop_target->content, hi->hunk,
2934 SVN_ERR(apply_hunk(target, prop_target->content, hi,
2942 prop_target->skipped = TRUE;
2944 if (applied_one && prop_target->content->existed)
2946 /* Copy any remaining lines to target. */
2947 SVN_ERR(copy_lines_to_target(prop_target->content, 0,
2949 if (! prop_target->content->eof)
2951 /* We could not copy the entire target property to the
2952 * temporary stream, and would truncate the target if we
2953 * copied the temporary stream on top of it. Skip this target. */
2954 prop_target->skipped = TRUE;
2959 svn_pool_destroy(iterpool);
2961 if (!target->is_symlink)
2963 /* Now close files we don't need any longer to get their contents
2965 * But we're not closing the reject file -- it still needed and
2966 * will be closed later in write_out_rejected_hunks(). */
2967 if (target->kind_on_disk == svn_node_file)
2968 SVN_ERR(svn_io_file_close(target->file, scratch_pool));
2971 SVN_ERR(svn_io_file_close(target->patched_file, scratch_pool));
2973 *patch_target = target;
2975 return SVN_NO_ERROR;
2978 /* Try to create missing parent directories for TARGET in the working copy
2979 * rooted at ABS_WC_PATH, and add the parents to version control.
2980 * If the parents cannot be created, mark the target as skipped.
2982 * In dry run mode record missing parents in ALREADY_ADDED
2984 * Use client context CTX. If DRY_RUN is true, do not create missing
2985 * parents but issue notifications only.
2986 * Use SCRATCH_POOL for temporary allocations. */
2987 static svn_error_t *
2988 create_missing_parents(patch_target_t *target,
2989 const char *abs_wc_path,
2990 svn_client_ctx_t *ctx,
2991 svn_boolean_t dry_run,
2992 apr_array_header_t *targets_info,
2993 apr_pool_t *scratch_pool)
2995 const char *local_abspath;
2996 apr_array_header_t *components;
2997 int present_components;
2999 apr_pool_t *iterpool;
3001 /* Check if we can safely create the target's parent. */
3002 local_abspath = abs_wc_path;
3003 components = svn_path_decompose(target->local_relpath, scratch_pool);
3004 present_components = 0;
3005 iterpool = svn_pool_create(scratch_pool);
3006 for (i = 0; i < components->nelts - 1; i++)
3008 const char *component;
3009 svn_node_kind_t wc_kind, disk_kind;
3011 svn_pool_clear(iterpool);
3013 component = APR_ARRAY_IDX(components, i, const char *);
3014 local_abspath = svn_dirent_join(local_abspath, component, scratch_pool);
3016 SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, local_abspath,
3017 FALSE, TRUE, iterpool));
3019 SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, iterpool));
3021 if (disk_kind == svn_node_file || wc_kind == svn_node_file)
3023 /* on-disk files and missing files are obstructions */
3024 target->skipped = TRUE;
3027 else if (disk_kind == svn_node_dir)
3029 if (wc_kind == svn_node_dir)
3030 present_components++;
3033 target->skipped = TRUE;
3037 else if (wc_kind != svn_node_none)
3039 /* Node is missing */
3040 target->skipped = TRUE;
3045 /* It's not a file, it's not a dir...
3050 if (! target->skipped)
3052 local_abspath = abs_wc_path;
3053 for (i = 0; i < present_components; i++)
3055 const char *component;
3056 component = APR_ARRAY_IDX(components, i, const char *);
3057 local_abspath = svn_dirent_join(local_abspath,
3058 component, scratch_pool);
3061 if (!dry_run && present_components < components->nelts - 1)
3062 SVN_ERR(svn_io_make_dir_recursively(
3065 svn_relpath_dirname(target->local_relpath,
3070 for (i = present_components; i < components->nelts - 1; i++)
3072 const char *component;
3073 patch_target_info_t *pti;
3075 svn_pool_clear(iterpool);
3077 if (ctx->cancel_func)
3078 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
3080 component = APR_ARRAY_IDX(components, i, const char *);
3081 local_abspath = svn_dirent_join(local_abspath, component,
3084 if (target_is_added(targets_info, local_abspath, iterpool))
3087 pti = apr_pcalloc(targets_info->pool, sizeof(*pti));
3089 pti->local_abspath = apr_pstrdup(targets_info->pool,
3093 APR_ARRAY_PUSH(targets_info, patch_target_info_t *) = pti;
3097 if (ctx->notify_func2)
3099 /* Just do notification. */
3100 svn_wc_notify_t *notify;
3101 notify = svn_wc_create_notify(local_abspath,
3104 notify->kind = svn_node_dir;
3105 ctx->notify_func2(ctx->notify_baton2, notify,
3111 /* Create the missing component and add it
3112 * to version control. Allow cancellation since we
3113 * have not modified the working copy yet for this
3115 SVN_ERR(svn_wc_add_from_disk3(ctx->wc_ctx, local_abspath,
3117 FALSE /* skip checks */,
3118 ctx->notify_func2, ctx->notify_baton2,
3124 svn_pool_destroy(iterpool);
3125 return SVN_NO_ERROR;
3128 /* Install a patched TARGET into the working copy at ABS_WC_PATH.
3129 * Use client context CTX to retrieve WC_CTX, and possibly doing
3132 * Pass on ALREADY_ADDED to allow recording already added ancestors
3135 * If DRY_RUN is TRUE, don't modify the working copy.
3136 * Do temporary allocations in POOL. */
3137 static svn_error_t *
3138 install_patched_target(patch_target_t *target, const char *abs_wc_path,
3139 svn_client_ctx_t *ctx, svn_boolean_t dry_run,
3140 apr_array_header_t *targets_info,
3143 if (target->deleted)
3147 /* Schedule the target for deletion. Suppress
3148 * notification, we'll do it manually in a minute
3149 * because we also need to notify during dry-run.
3150 * Also suppress cancellation, because we'd rather
3151 * notify about what we did before aborting. */
3152 SVN_ERR(svn_wc_delete4(ctx->wc_ctx, target->local_abspath,
3153 FALSE /* keep_local */, FALSE,
3154 ctx->cancel_func, ctx->cancel_baton,
3155 NULL, NULL /* notify */,
3161 svn_node_kind_t parent_db_kind;
3164 const char *parent_abspath;
3166 parent_abspath = svn_dirent_dirname(target->local_abspath,
3168 /* If the target's parent directory does not yet exist
3169 * we need to create it before we can copy the patched
3170 * result in place. */
3171 SVN_ERR(svn_wc_read_kind2(&parent_db_kind, ctx->wc_ctx,
3172 parent_abspath, FALSE, FALSE, pool));
3174 /* We can't add targets under nodes scheduled for delete, so add
3175 a new directory if needed. */
3176 if (parent_db_kind == svn_node_dir
3177 || parent_db_kind == svn_node_file)
3179 if (parent_db_kind != svn_node_dir)
3180 target->skipped = TRUE;
3183 svn_node_kind_t disk_kind;
3185 SVN_ERR(svn_io_check_path(parent_abspath, &disk_kind, pool));
3186 if (disk_kind != svn_node_dir)
3187 target->skipped = TRUE;
3191 SVN_ERR(create_missing_parents(target, abs_wc_path, ctx,
3192 dry_run, targets_info, pool));
3197 svn_node_kind_t wc_kind;
3199 /* The target should exist */
3200 SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx,
3201 target->local_abspath,
3202 FALSE, FALSE, pool));
3204 if (target->kind_on_disk == svn_node_none
3205 || wc_kind != target->kind_on_disk)
3207 target->skipped = TRUE;
3208 if (wc_kind != target->kind_on_disk)
3209 target->obstructed = TRUE;
3213 if (! dry_run && ! target->skipped)
3215 if (target->is_special)
3217 svn_stream_t *stream;
3218 svn_stream_t *patched_stream;
3220 SVN_ERR(svn_stream_open_readonly(&patched_stream,
3221 target->patched_path,
3223 SVN_ERR(svn_subst_create_specialfile(&stream,
3224 target->local_abspath,
3226 if (target->git_symlink_format)
3227 SVN_ERR(svn_stream_puts(stream, "link "));
3228 SVN_ERR(svn_stream_copy3(patched_stream, stream,
3229 ctx->cancel_func, ctx->cancel_baton,
3234 svn_boolean_t repair_eol;
3236 /* Copy the patched file on top of the target file.
3237 * Always expand keywords in the patched file, but repair EOL
3238 * only if svn:eol-style dictates a particular style. */
3239 repair_eol = (target->content->eol_style ==
3240 svn_subst_eol_style_fixed ||
3241 target->content->eol_style ==
3242 svn_subst_eol_style_native);
3244 SVN_ERR(svn_subst_copy_and_translate4(
3245 target->patched_path,
3246 target->move_target_abspath
3247 ? target->move_target_abspath
3248 : target->local_abspath,
3249 target->content->eol_str, repair_eol,
3250 target->content->keywords,
3251 TRUE /* expand */, FALSE /* special */,
3252 ctx->cancel_func, ctx->cancel_baton, pool));
3257 /* The target file didn't exist previously,
3258 * so add it to version control.
3259 * Suppress notification, we'll do that later (and also
3260 * during dry-run). Don't allow cancellation because
3261 * we'd rather notify about what we did before aborting. */
3262 SVN_ERR(svn_wc_add_from_disk3(ctx->wc_ctx, target->local_abspath,
3264 FALSE /* skip checks */,
3268 /* Restore the target's executable bit if necessary. */
3269 SVN_ERR(svn_io_set_file_executable(target->move_target_abspath
3270 ? target->move_target_abspath
3271 : target->local_abspath,
3275 if (target->move_target_abspath)
3277 /* ### Copying the patched content to the move target location,
3278 * performing the move in meta-data, and removing the file at
3279 * the move source should be one atomic operation. */
3281 /* ### Create missing parents. */
3283 /* Perform the move in meta-data. */
3284 SVN_ERR(svn_wc__move2(ctx->wc_ctx,
3285 target->local_abspath,
3286 target->move_target_abspath,
3287 TRUE, /* metadata_only */
3288 FALSE, /* allow_mixed_revisions */
3289 ctx->cancel_func, ctx->cancel_baton,
3293 /* Delete the patch target's old location from disk. */
3294 SVN_ERR(svn_io_remove_file2(target->local_abspath, FALSE, pool));
3299 return SVN_NO_ERROR;
3302 /* Write out rejected hunks, if any, to TARGET->REJECT_PATH. If DRY_RUN is
3303 * TRUE, don't modify the working copy.
3304 * Do temporary allocations in POOL.
3306 static svn_error_t *
3307 write_out_rejected_hunks(patch_target_t *target,
3308 const char *root_abspath,
3309 svn_boolean_t dry_run,
3310 apr_pool_t *scratch_pool)
3312 if (! dry_run && (target->had_rejects || target->had_prop_rejects))
3314 /* Write out rejected hunks, if any. */
3315 apr_file_t *reject_file;
3318 err = svn_io_open_uniquely_named(&reject_file, NULL,
3319 svn_dirent_dirname(target->local_abspath,
3321 svn_dirent_basename(
3322 target->local_abspath,
3325 svn_io_file_del_none,
3326 scratch_pool, scratch_pool);
3327 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
3329 /* The hunk applies to a file in a directory which does not exist.
3330 * Put the reject file into the working copy root instead. */
3331 svn_error_clear(err);
3332 SVN_ERR(svn_io_open_uniquely_named(&reject_file, NULL,
3334 svn_dirent_basename(
3335 target->local_abspath,
3338 svn_io_file_del_none,
3339 scratch_pool, scratch_pool));
3344 SVN_ERR(svn_stream_reset(target->reject_stream));
3346 /* svn_stream_copy3() closes the files for us */
3347 SVN_ERR(svn_stream_copy3(target->reject_stream,
3348 svn_stream_from_aprfile2(reject_file, FALSE,
3350 NULL, NULL, scratch_pool));
3351 /* ### TODO mark file as conflicted. */
3354 SVN_ERR(svn_stream_close(target->reject_stream));
3356 return SVN_NO_ERROR;
3359 /* Install the patched properties for TARGET. Use client context CTX to
3360 * retrieve WC_CTX. If DRY_RUN is TRUE, don't modify the working copy.
3361 * Do temporary allocations in SCRATCH_POOL. */
3362 static svn_error_t *
3363 install_patched_prop_targets(patch_target_t *target,
3364 svn_client_ctx_t *ctx, svn_boolean_t dry_run,
3365 apr_pool_t *scratch_pool)
3367 apr_hash_index_t *hi;
3368 apr_pool_t *iterpool;
3369 const char *local_abspath;
3371 /* Apply properties to a move target if there is one */
3372 if (target->move_target_abspath)
3373 local_abspath = target->move_target_abspath;
3375 local_abspath = target->local_abspath;
3377 iterpool = svn_pool_create(scratch_pool);
3379 for (hi = apr_hash_first(scratch_pool, target->prop_targets);
3381 hi = apr_hash_next(hi))
3383 prop_patch_target_t *prop_target = apr_hash_this_val(hi);
3384 const svn_string_t *prop_val;
3387 svn_pool_clear(iterpool);
3389 if (ctx->cancel_func)
3390 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
3392 if (prop_target->skipped)
3395 /* For a deleted prop we only set the value to NULL. */
3396 if (prop_target->operation == svn_diff_op_deleted)
3399 SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath,
3400 prop_target->name, NULL, svn_depth_empty,
3401 TRUE /* skip_checks */,
3402 NULL /* changelist_filter */,
3403 NULL, NULL /* cancellation */,
3404 NULL, NULL /* notification */,
3409 /* Attempt to set the property, and reject all hunks if this
3410 fails. If the property had a non-empty value, but now has
3411 an empty one, we'll just delete the property altogether. */
3412 if (prop_target->value && prop_target->value->len
3413 && prop_target->patched_value && !prop_target->patched_value->len)
3416 prop_val = svn_stringbuf__morph_into_string(prop_target->patched_value);
3420 const svn_string_t *canon_propval;
3422 err = svn_wc_canonicalize_svn_prop(&canon_propval,
3424 prop_val, local_abspath,
3426 TRUE, /* ### Skipping checks */
3432 err = svn_wc_prop_set4(ctx->wc_ctx, local_abspath,
3433 prop_target->name, prop_val, svn_depth_empty,
3434 TRUE /* skip_checks */,
3435 NULL /* changelist_filter */,
3436 NULL, NULL /* cancellation */,
3437 NULL, NULL /* notification */,
3443 /* ### The errors which svn_wc_canonicalize_svn_prop() will
3444 * ### return aren't documented. */
3445 if (err->apr_err == SVN_ERR_ILLEGAL_TARGET ||
3446 err->apr_err == SVN_ERR_NODE_UNEXPECTED_KIND ||
3447 err->apr_err == SVN_ERR_IO_UNKNOWN_EOL ||
3448 err->apr_err == SVN_ERR_BAD_MIME_TYPE ||
3449 err->apr_err == SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION)
3453 svn_error_clear(err);
3455 for (i = 0; i < prop_target->content->hunks->nelts; i++)
3457 hunk_info_t *hunk_info;
3459 hunk_info = APR_ARRAY_IDX(prop_target->content->hunks,
3461 hunk_info->rejected = TRUE;
3462 SVN_ERR(reject_hunk(target, prop_target->content,
3463 hunk_info->hunk, prop_target->name,
3468 return svn_error_trace(err);
3473 svn_pool_destroy(iterpool);
3475 return SVN_NO_ERROR;
3478 /* Baton for can_delete_callback */
3479 struct can_delete_baton_t
3481 svn_boolean_t must_keep;
3482 const apr_array_header_t *targets_info;
3483 const char *local_abspath;
3486 /* Implements svn_wc_status_func4_t. */
3487 static svn_error_t *
3488 can_delete_callback(void *baton,
3489 const char *abspath,
3490 const svn_wc_status3_t *status,
3493 struct can_delete_baton_t *cb = baton;
3496 switch(status->node_status)
3498 case svn_wc_status_none:
3499 case svn_wc_status_deleted:
3500 return SVN_NO_ERROR;
3503 if (! strcmp(cb->local_abspath, abspath))
3504 return SVN_NO_ERROR; /* Only interested in descendants */
3506 for (i = 0; i < cb->targets_info->nelts; i++)
3508 const patch_target_info_t *target_info =
3509 APR_ARRAY_IDX(cb->targets_info, i, const patch_target_info_t *);
3511 if (! strcmp(target_info->local_abspath, abspath))
3513 if (target_info->deleted)
3514 return SVN_NO_ERROR;
3516 break; /* Cease invocation; must keep */
3520 cb->must_keep = TRUE;
3522 return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL);
3526 static svn_error_t *
3527 check_ancestor_delete(const char *deleted_target,
3528 apr_array_header_t *targets_info,
3529 const char *apply_root,
3530 svn_boolean_t dry_run,
3531 svn_client_ctx_t *ctx,
3532 apr_pool_t *result_pool,
3533 apr_pool_t *scratch_pool)
3535 struct can_delete_baton_t cb;
3537 apr_array_header_t *ignores;
3538 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
3540 const char *dir_abspath = svn_dirent_dirname(deleted_target, scratch_pool);
3542 SVN_ERR(svn_wc_get_default_ignores(&ignores, ctx->config, scratch_pool));
3544 while (svn_dirent_is_child(apply_root, dir_abspath, iterpool))
3546 svn_pool_clear(iterpool);
3548 cb.local_abspath = dir_abspath;
3549 cb.must_keep = FALSE;
3550 cb.targets_info = targets_info;
3552 err = svn_wc_walk_status(ctx->wc_ctx, dir_abspath, svn_depth_infinity,
3553 TRUE, FALSE, FALSE, ignores,
3554 can_delete_callback, &cb,
3555 ctx->cancel_func, ctx->cancel_baton,
3560 if (err->apr_err != SVN_ERR_CEASE_INVOCATION)
3561 return svn_error_trace(err);
3563 svn_error_clear(err);
3573 SVN_ERR(svn_wc_delete4(ctx->wc_ctx, dir_abspath, FALSE, FALSE,
3574 ctx->cancel_func, ctx->cancel_baton,
3580 patch_target_info_t *pti = apr_pcalloc(result_pool, sizeof(*pti));
3582 pti->local_abspath = apr_pstrdup(result_pool, dir_abspath);
3583 pti->deleted = TRUE;
3585 APR_ARRAY_PUSH(targets_info, patch_target_info_t *) = pti;
3589 if (ctx->notify_func2)
3591 svn_wc_notify_t *notify;
3593 notify = svn_wc_create_notify(dir_abspath, svn_wc_notify_delete,
3595 notify->kind = svn_node_dir;
3597 ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
3600 /* And check if we must also delete the parent */
3601 dir_abspath = svn_dirent_dirname(dir_abspath, scratch_pool);
3604 svn_pool_destroy(iterpool);
3606 return SVN_NO_ERROR;
3609 /* This function is the main entry point into the patch code. */
3610 static svn_error_t *
3611 apply_patches(/* The path to the patch file. */
3612 const char *patch_abspath,
3613 /* The abspath to the working copy the patch should be applied to. */
3614 const char *root_abspath,
3615 /* Indicates whether we're doing a dry run. */
3616 svn_boolean_t dry_run,
3617 /* Number of leading components to strip from patch target paths. */
3619 /* Whether to apply the patch in reverse. */
3620 svn_boolean_t reverse,
3621 /* Whether to ignore whitespace when matching context lines. */
3622 svn_boolean_t ignore_whitespace,
3623 /* As in svn_client_patch(). */
3624 svn_boolean_t remove_tempfiles,
3625 /* As in svn_client_patch(). */
3626 svn_client_patch_func_t patch_func,
3628 /* The client context. */
3629 svn_client_ctx_t *ctx,
3630 apr_pool_t *scratch_pool)
3633 apr_pool_t *iterpool;
3634 svn_patch_file_t *patch_file;
3635 apr_array_header_t *targets_info;
3637 /* Try to open the patch file. */
3638 SVN_ERR(svn_diff_open_patch_file(&patch_file, patch_abspath, scratch_pool));
3640 /* Apply patches. */
3641 targets_info = apr_array_make(scratch_pool, 0,
3642 sizeof(patch_target_info_t *));
3643 iterpool = svn_pool_create(scratch_pool);
3646 svn_pool_clear(iterpool);
3648 if (ctx->cancel_func)
3649 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
3651 SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file,
3652 reverse, ignore_whitespace,
3653 iterpool, iterpool));
3656 patch_target_t *target;
3657 svn_boolean_t filtered = FALSE;
3659 SVN_ERR(apply_one_patch(&target, patch, root_abspath,
3660 ctx->wc_ctx, strip_count,
3661 ignore_whitespace, remove_tempfiles,
3663 ctx->cancel_func, ctx->cancel_baton,
3664 iterpool, iterpool));
3666 if (!target->skipped && patch_func)
3668 SVN_ERR(patch_func(patch_baton, &filtered,
3669 target->canon_path_from_patchfile,
3670 target->patched_path, target->reject_path,
3676 /* Save info we'll still need when we're done patching. */
3677 patch_target_info_t *target_info =
3678 apr_pcalloc(scratch_pool, sizeof(patch_target_info_t));
3679 target_info->local_abspath = apr_pstrdup(scratch_pool,
3680 target->local_abspath);
3681 target_info->deleted = target->deleted;
3682 target_info->added = target->added;
3684 if (! target->skipped)
3686 if (target->has_text_changes
3688 || target->move_target_abspath
3690 SVN_ERR(install_patched_target(target, root_abspath,
3692 targets_info, iterpool));
3694 if (target->has_prop_changes && (!target->deleted))
3695 SVN_ERR(install_patched_prop_targets(target, ctx,
3696 dry_run, iterpool));
3698 SVN_ERR(write_out_rejected_hunks(target, root_abspath,
3699 dry_run, iterpool));
3701 APR_ARRAY_PUSH(targets_info,
3702 patch_target_info_t *) = target_info;
3704 SVN_ERR(send_patch_notification(target, ctx, iterpool));
3706 if (target->deleted && !target->skipped)
3708 SVN_ERR(check_ancestor_delete(target_info->local_abspath,
3709 targets_info, root_abspath,
3711 scratch_pool, iterpool));
3718 SVN_ERR(svn_diff_close_patch_file(patch_file, iterpool));
3719 svn_pool_destroy(iterpool);
3721 return SVN_NO_ERROR;
3725 svn_client_patch(const char *patch_abspath,
3726 const char *wc_dir_abspath,
3727 svn_boolean_t dry_run,
3729 svn_boolean_t reverse,
3730 svn_boolean_t ignore_whitespace,
3731 svn_boolean_t remove_tempfiles,
3732 svn_client_patch_func_t patch_func,
3734 svn_client_ctx_t *ctx,
3735 apr_pool_t *scratch_pool)
3737 svn_node_kind_t kind;
3739 if (strip_count < 0)
3740 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
3741 _("strip count must be positive"));
3743 if (svn_path_is_url(wc_dir_abspath))
3744 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3745 _("'%s' is not a local path"),
3746 svn_dirent_local_style(wc_dir_abspath,
3749 SVN_ERR(svn_io_check_path(patch_abspath, &kind, scratch_pool));
3750 if (kind == svn_node_none)
3751 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3752 _("'%s' does not exist"),
3753 svn_dirent_local_style(patch_abspath,
3755 if (kind != svn_node_file)
3756 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3757 _("'%s' is not a file"),
3758 svn_dirent_local_style(patch_abspath,
3761 SVN_ERR(svn_io_check_path(wc_dir_abspath, &kind, scratch_pool));
3762 if (kind == svn_node_none)
3763 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3764 _("'%s' does not exist"),
3765 svn_dirent_local_style(wc_dir_abspath,
3767 if (kind != svn_node_dir)
3768 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3769 _("'%s' is not a directory"),
3770 svn_dirent_local_style(wc_dir_abspath,
3773 SVN_WC__CALL_WITH_WRITE_LOCK(
3774 apply_patches(patch_abspath, wc_dir_abspath, dry_run, strip_count,
3775 reverse, ignore_whitespace, remove_tempfiles,
3776 patch_func, patch_baton, ctx, scratch_pool),
3777 ctx->wc_ctx, wc_dir_abspath, FALSE /* lock_anchor */, scratch_pool);
3778 return SVN_NO_ERROR;