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 Q_("Cannot strip %u component from '%s'",
347 "Cannot strip %u components from '%s'",
350 svn_dirent_local_style(path, scratch_pool));
352 stripped = apr_array_make(scratch_pool, components->nelts - strip_count,
353 sizeof(const char *));
354 for (i = strip_count; i < components->nelts; i++)
356 const char *component;
358 component = APR_ARRAY_IDX(components, i, const char *);
359 APR_ARRAY_PUSH(stripped, const char *) = component;
362 *result = svn_path_compose(stripped, result_pool);
367 /* Obtain KEYWORDS, EOL_STYLE and EOL_STR for LOCAL_ABSPATH.
368 * WC_CTX is a context for the working copy the patch is applied to.
369 * Use RESULT_POOL for allocations of fields in TARGET.
370 * Use SCRATCH_POOL for all other allocations. */
372 obtain_eol_and_keywords_for_file(apr_hash_t **keywords,
373 svn_subst_eol_style_t *eol_style,
374 const char **eol_str,
375 svn_wc_context_t *wc_ctx,
376 const char *local_abspath,
377 apr_pool_t *result_pool,
378 apr_pool_t *scratch_pool)
381 svn_string_t *keywords_val, *eol_style_val;
383 SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, local_abspath,
384 scratch_pool, scratch_pool));
385 keywords_val = svn_hash_gets(props, SVN_PROP_KEYWORDS);
388 svn_revnum_t changed_rev;
389 apr_time_t changed_date;
393 const char *repos_root_url;
394 const char *repos_relpath;
396 SVN_ERR(svn_wc__node_get_changed_info(&changed_rev,
402 rev_str = apr_psprintf(scratch_pool, "%ld", changed_rev);
403 SVN_ERR(svn_wc__node_get_repos_info(NULL, &repos_relpath, &repos_root_url,
405 wc_ctx, local_abspath,
406 scratch_pool, scratch_pool));
407 url = svn_path_url_add_component2(repos_root_url, repos_relpath,
410 SVN_ERR(svn_subst_build_keywords3(keywords,
412 rev_str, url, repos_root_url,
414 author, result_pool));
417 eol_style_val = svn_hash_gets(props, SVN_PROP_EOL_STYLE);
420 svn_subst_eol_style_from_value(eol_style,
422 eol_style_val->data);
428 /* Resolve the exact path for a patch TARGET at path PATH_FROM_PATCHFILE,
429 * which is the path of the target as it appeared in the patch file.
430 * Put a canonicalized version of PATH_FROM_PATCHFILE into
431 * TARGET->CANON_PATH_FROM_PATCHFILE.
432 * WC_CTX is a context for the working copy the patch is applied to.
433 * If possible, determine TARGET->WC_PATH, TARGET->ABS_PATH, TARGET->KIND,
434 * TARGET->ADDED, and TARGET->PARENT_DIR_EXISTS.
435 * Indicate in TARGET->SKIPPED whether the target should be skipped.
436 * STRIP_COUNT specifies the number of leading path components
437 * which should be stripped from target paths in the patch.
438 * HAS_TEXT_CHANGES specifies whether the target path will have some text
439 * changes applied, implying that the target should be a file and not a
441 * Use RESULT_POOL for allocations of fields in TARGET.
442 * Use SCRATCH_POOL for all other allocations. */
444 resolve_target_path(patch_target_t *target,
445 const char *path_from_patchfile,
446 const char *root_abspath,
448 svn_boolean_t has_text_changes,
449 svn_boolean_t follow_moves,
450 svn_wc_context_t *wc_ctx,
451 const apr_array_header_t *targets_info,
452 apr_pool_t *result_pool,
453 apr_pool_t *scratch_pool)
455 const char *stripped_path;
456 svn_wc_status3_t *status;
458 svn_boolean_t under_root;
460 target->canon_path_from_patchfile = svn_dirent_internal_style(
461 path_from_patchfile, result_pool);
463 /* We can't handle text changes on the patch root dir. */
464 if (has_text_changes && target->canon_path_from_patchfile[0] == '\0')
466 /* An empty patch target path? What gives? Skip this. */
467 target->skipped = TRUE;
468 target->local_abspath = NULL;
469 target->local_relpath = "";
474 SVN_ERR(strip_path(&stripped_path, target->canon_path_from_patchfile,
475 strip_count, result_pool, scratch_pool));
477 stripped_path = target->canon_path_from_patchfile;
479 if (svn_dirent_is_absolute(stripped_path))
481 target->local_relpath = svn_dirent_is_child(root_abspath,
485 if (! target->local_relpath)
487 /* The target path is either outside of the working copy
488 * or it is the patch root itself. Skip it. */
489 target->skipped = TRUE;
490 target->local_abspath = NULL;
491 target->local_relpath = stripped_path;
497 target->local_relpath = stripped_path;
500 /* Make sure the path is secure to use. We want the target to be inside
501 * the locked tree and not be fooled by symlinks it might contain. */
502 SVN_ERR(svn_dirent_is_under_root(&under_root,
503 &target->local_abspath, root_abspath,
504 target->local_relpath, result_pool));
508 /* The target path is outside of the working copy. Skip it. */
509 target->skipped = TRUE;
510 target->local_abspath = NULL;
514 if (target_is_deleted(targets_info, target->local_abspath, scratch_pool))
516 target->locally_deleted = TRUE;
517 target->db_kind = svn_node_none;
521 /* Skip things we should not be messing with. */
522 err = svn_wc_status3(&status, wc_ctx, target->local_abspath,
523 result_pool, scratch_pool);
526 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
527 return svn_error_trace(err);
529 svn_error_clear(err);
531 target->locally_deleted = TRUE;
532 target->db_kind = svn_node_none;
535 else if (status->node_status == svn_wc_status_ignored ||
536 status->node_status == svn_wc_status_unversioned ||
537 status->node_status == svn_wc_status_missing ||
538 status->node_status == svn_wc_status_obstructed ||
541 target->skipped = TRUE;
542 target->obstructed = TRUE;
545 else if (status->node_status == svn_wc_status_deleted)
547 target->locally_deleted = TRUE;
550 if (status && (status->kind != svn_node_unknown))
551 target->db_kind = status->kind;
553 target->db_kind = svn_node_none;
555 SVN_ERR(svn_io_check_special_path(target->local_abspath,
556 &target->kind_on_disk, &target->is_symlink,
559 if (target->locally_deleted)
561 const char *moved_to_abspath = NULL;
564 && !target_is_added(targets_info, target->local_abspath,
567 SVN_ERR(svn_wc__node_was_moved_away(&moved_to_abspath, NULL,
568 wc_ctx, target->local_abspath,
569 result_pool, scratch_pool));
572 if (moved_to_abspath)
574 target->local_abspath = moved_to_abspath;
575 target->local_relpath = svn_dirent_skip_ancestor(root_abspath,
578 if (!target->local_relpath || target->local_relpath[0] == '\0')
580 /* The target path is outside of the patch area. Skip it. */
581 target->skipped = TRUE;
585 /* As far as we are concerned this target is not locally deleted. */
586 target->locally_deleted = FALSE;
588 SVN_ERR(svn_io_check_special_path(target->local_abspath,
589 &target->kind_on_disk,
593 else if (target->kind_on_disk != svn_node_none)
595 target->skipped = TRUE;
601 if (target->kind_on_disk == svn_node_file
602 && !target->is_symlink
603 && !target->locally_deleted
604 && status->prop_status != svn_wc_status_none)
606 const svn_string_t *value;
608 SVN_ERR(svn_wc_prop_get2(&value, wc_ctx, target->local_abspath,
609 SVN_PROP_SPECIAL, scratch_pool, scratch_pool));
612 target->is_symlink = TRUE;
619 /* Baton for reading from properties. */
620 typedef struct prop_read_baton_t {
621 const svn_string_t *value;
625 /* Allocate *STRINGBUF in RESULT_POOL, and read into it one line from
626 * the unpatched property value accessed via BATON.
627 * Reading stops either after a line-terminator was found, or if
628 * the property value runs out in which case *EOF is set to TRUE.
629 * The line-terminator is not stored in *STRINGBUF.
631 * If the line is empty or could not be read, *line is set to NULL.
633 * The line-terminator is detected automatically and stored in *EOL
634 * if EOL is not NULL. If the end of the property value is reached
635 * and does not end with a newline character, and EOL is not NULL,
636 * *EOL is set to NULL.
638 * SCRATCH_POOL is used for temporary allocations.
641 readline_prop(void *baton, svn_stringbuf_t **line, const char **eol_str,
642 svn_boolean_t *eof, apr_pool_t *result_pool,
643 apr_pool_t *scratch_pool)
645 prop_read_baton_t *b = baton;
646 svn_stringbuf_t *str = NULL;
648 svn_boolean_t found_eof;
650 if ((apr_uint64_t)b->offset >= (apr_uint64_t)b->value->len)
658 /* Read bytes into STR up to and including, but not storing,
659 * the next EOL sequence. */
664 c = b->value->data + b->offset;
679 if (*(c + 1) == '\n')
688 str = svn_stringbuf_create_ensure(80, result_pool);
689 svn_stringbuf_appendbyte(str, *c);
695 while (c < b->value->data + b->value->len);
698 *eof = found_eof && !(str && str->len > 0);
704 /* Return in *OFFSET the current byte offset for reading from the
705 * unpatched property value accessed via BATON.
706 * Use SCRATCH_POOL for temporary allocations. */
708 tell_prop(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool)
710 prop_read_baton_t *b = baton;
716 /* Seek to the specified by OFFSET in the unpatched property value accessed
717 * via BATON. Use SCRATCH_POOL for temporary allocations. */
719 seek_prop(void *baton, apr_off_t offset, apr_pool_t *scratch_pool)
721 prop_read_baton_t *b = baton;
727 /* Write LEN bytes from BUF into the patched property value accessed
728 * via BATON. Use SCRATCH_POOL for temporary allocations. */
730 write_prop(void *baton, const char *buf, apr_size_t len,
731 apr_pool_t *scratch_pool)
733 svn_stringbuf_t *patched_value = baton;
735 svn_stringbuf_appendbytes(patched_value, buf, len);
739 /* Initialize a PROP_TARGET structure for PROP_NAME on the patch target
740 * at LOCAL_ABSPATH. OPERATION indicates the operation performed on the
741 * property. Use working copy context WC_CTX.
742 * Allocate results in RESULT_POOL.
743 * Use SCRATCH_POOL for temporary allocations. */
745 init_prop_target(prop_patch_target_t **prop_target,
746 const patch_target_t *target,
747 const char *prop_name,
748 svn_diff_operation_kind_t operation,
749 svn_wc_context_t *wc_ctx,
750 const char *local_abspath,
751 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
753 prop_patch_target_t *new_prop_target;
754 target_content_t *content;
755 const svn_string_t *value;
756 prop_read_baton_t *prop_read_baton;
758 content = apr_pcalloc(result_pool, sizeof(*content));
760 /* All other fields are FALSE or NULL due to apr_pcalloc(). */
761 content->current_line = 1;
762 content->eol_style = svn_subst_eol_style_none;
763 content->lines = apr_array_make(result_pool, 0, sizeof(apr_off_t));
764 content->hunks = apr_array_make(result_pool, 0, sizeof(hunk_info_t *));
765 content->keywords = apr_hash_make(result_pool);
767 new_prop_target = apr_pcalloc(result_pool, sizeof(*new_prop_target));
768 new_prop_target->name = apr_pstrdup(result_pool, prop_name);
769 new_prop_target->operation = operation;
770 new_prop_target->content = content;
772 if (!(target->deleted || target->db_kind == svn_node_none))
773 SVN_ERR(svn_wc_prop_get2(&value, wc_ctx, local_abspath, prop_name,
774 result_pool, scratch_pool));
778 content->existed = (value != NULL);
779 new_prop_target->value = value;
780 new_prop_target->patched_value = svn_stringbuf_create_empty(result_pool);
783 /* Wire up the read and write callbacks. */
784 prop_read_baton = apr_pcalloc(result_pool, sizeof(*prop_read_baton));
785 prop_read_baton->value = value;
786 prop_read_baton->offset = 0;
787 content->readline = readline_prop;
788 content->tell = tell_prop;
789 content->seek = seek_prop;
790 content->read_baton = prop_read_baton;
791 content->write = write_prop;
792 content->write_baton = new_prop_target->patched_value;
794 *prop_target = new_prop_target;
799 /* Allocate *STRINGBUF in RESULT_POOL, and read into it one line from
800 * the unpatched file content accessed via BATON.
801 * Reading stops either after a line-terminator was found,
802 * or if EOF is reached in which case *EOF is set to TRUE.
803 * The line-terminator is not stored in *STRINGBUF.
805 * If the line is empty or could not be read, *line is set to NULL.
807 * The line-terminator is detected automatically and stored in *EOL
808 * if EOL is not NULL. If EOF is reached and FILE does not end
809 * with a newline character, and EOL is not NULL, *EOL is set to NULL.
811 * SCRATCH_POOL is used for temporary allocations.
814 readline_file(void *baton, svn_stringbuf_t **line, const char **eol_str,
815 svn_boolean_t *eof, apr_pool_t *result_pool,
816 apr_pool_t *scratch_pool)
818 apr_file_t *file = baton;
820 SVN_ERR(svn_io_file_readline(file, line, eol_str, eof, APR_SIZE_MAX,
821 result_pool, scratch_pool));
831 /* Return in *OFFSET the current byte offset for reading from the
832 * unpatched file content accessed via BATON.
833 * Use SCRATCH_POOL for temporary allocations. */
835 tell_file(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool)
837 apr_file_t *file = baton;
839 SVN_ERR(svn_io_file_get_offset(offset, file, scratch_pool));
843 /* Seek to the specified by OFFSET in the unpatched file content accessed
844 * via BATON. Use SCRATCH_POOL for temporary allocations. */
846 seek_file(void *baton, apr_off_t offset, apr_pool_t *scratch_pool)
848 apr_file_t *file = baton;
850 SVN_ERR(svn_io_file_seek(file, APR_SET, &offset, scratch_pool));
854 /* Write LEN bytes from BUF into the patched file content accessed
855 * via BATON. Use SCRATCH_POOL for temporary allocations. */
857 write_file(void *baton, const char *buf, apr_size_t len,
858 apr_pool_t *scratch_pool)
860 apr_file_t *file = baton;
862 SVN_ERR(svn_io_file_write_full(file, buf, len, &len, scratch_pool));
866 /* Symlinks appear in patches in their repository normal form, abstracted by
867 * the svn_subst_* module. The functions below enable patches to change the
868 * targets of symlinks.
871 /* Baton for the (readline|tell|seek|write)_symlink functions. */
872 struct symlink_baton_t
874 /* The path to the symlink on disk (not the path to the target of the link) */
875 const char *local_abspath;
877 /* Indicates whether the "normal form" of the symlink has been read. */
878 svn_boolean_t at_eof;
881 /* Allocate *STRINGBUF in RESULT_POOL, and store into it the "normal form"
882 * of the symlink accessed via BATON.
884 * Otherwise behaves like readline_file(), which see.
887 readline_symlink(void *baton, svn_stringbuf_t **line, const char **eol_str,
888 svn_boolean_t *eof, apr_pool_t *result_pool,
889 apr_pool_t *scratch_pool)
891 struct symlink_baton_t *sb = baton;
904 svn_stream_t *stream;
905 const apr_size_t len_hint = 64; /* arbitrary */
907 SVN_ERR(svn_subst_read_specialfile(&stream, sb->local_abspath,
908 scratch_pool, scratch_pool));
909 SVN_ERR(svn_stringbuf_from_stream(line, stream, len_hint, result_pool));
917 /* Identical to readline_symlink(), but returns symlink in raw format to
918 * allow patching links in git-style.
921 readline_symlink_git(void *baton, svn_stringbuf_t **line, const char **eol_str,
922 svn_boolean_t *eof, apr_pool_t *result_pool,
923 apr_pool_t *scratch_pool)
925 SVN_ERR(readline_symlink(baton, line, eol_str, eof,
926 result_pool, scratch_pool));
928 if (*line && (*line)->len > 5 && !strncmp((*line)->data, "link ", 5))
929 svn_stringbuf_remove(*line, 0, 5); /* Skip "link " */
934 /* Set *OFFSET to 1 or 0 depending on whether the "normal form" of
935 * the symlink has already been read. */
937 tell_symlink(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool)
939 struct symlink_baton_t *sb = baton;
941 *offset = sb->at_eof ? 1 : 0;
945 /* If offset is non-zero, mark the symlink as having been read in its
946 * "normal form". Else, mark the symlink as not having been read yet. */
948 seek_symlink(void *baton, apr_off_t offset, apr_pool_t *scratch_pool)
950 struct symlink_baton_t *sb = baton;
952 sb->at_eof = (offset != 0);
956 /* Return a suitable filename for the target of PATCH.
957 * Examine the ``old'' and ``new'' file names, and choose the file name
958 * with the fewest path components, the shortest basename, and the shortest
959 * total file name length (in that order). In case of a tie, return the new
960 * filename. This heuristic is also used by Larry Wall's UNIX patch (except
961 * that it prompts for a filename in case of a tie).
962 * Additionally, for compatibility with git, if one of the filenames
963 * is "/dev/null", use the other filename. */
965 choose_target_filename(const svn_patch_t *patch)
970 if (strcmp(patch->old_filename, "/dev/null") == 0)
971 return patch->new_filename;
972 if (strcmp(patch->new_filename, "/dev/null") == 0)
973 return patch->old_filename;
975 /* If the patch renames the target, use the old name while
976 * applying hunks. The target will be renamed to the new name
977 * after hunks have been applied. */
978 if (patch->operation == svn_diff_op_moved)
979 return patch->old_filename;
981 old = svn_path_component_count(patch->old_filename);
982 new = svn_path_component_count(patch->new_filename);
986 old = strlen(svn_dirent_basename(patch->old_filename, NULL));
987 new = strlen(svn_dirent_basename(patch->new_filename, NULL));
991 old = strlen(patch->old_filename);
992 new = strlen(patch->new_filename);
996 return (old < new) ? patch->old_filename : patch->new_filename;
999 /* Attempt to initialize a *PATCH_TARGET structure for a target file
1000 * described by PATCH. Use working copy context WC_CTX.
1001 * STRIP_COUNT specifies the number of leading path components
1002 * which should be stripped from target paths in the patch.
1003 * The patch target structure is allocated in RESULT_POOL, but if the target
1004 * should be skipped, PATCH_TARGET->SKIPPED is set and the target should be
1005 * treated as not fully initialized, e.g. the caller should not not do any
1006 * further operations on the target if it is marked to be skipped.
1007 * If REMOVE_TEMPFILES is TRUE, set up temporary files to be removed as
1008 * soon as they are no longer needed.
1009 * Use SCRATCH_POOL for all other allocations. */
1010 static svn_error_t *
1011 init_patch_target(patch_target_t **patch_target,
1012 const svn_patch_t *patch,
1013 const char *root_abspath,
1014 svn_wc_context_t *wc_ctx, int strip_count,
1015 svn_boolean_t remove_tempfiles,
1016 const apr_array_header_t *targets_info,
1017 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
1019 patch_target_t *target;
1020 target_content_t *content;
1021 svn_boolean_t has_text_changes = FALSE;
1022 svn_boolean_t follow_moves;
1023 const char *tempdir_abspath;
1025 has_text_changes = ((patch->hunks && patch->hunks->nelts > 0)
1026 || patch->binary_patch);
1028 content = apr_pcalloc(result_pool, sizeof(*content));
1030 /* All other fields in content are FALSE or NULL due to apr_pcalloc().*/
1031 content->current_line = 1;
1032 content->eol_style = svn_subst_eol_style_none;
1033 content->lines = apr_array_make(result_pool, 0, sizeof(apr_off_t));
1034 content->hunks = apr_array_make(result_pool, 0, sizeof(hunk_info_t *));
1035 content->keywords = apr_hash_make(result_pool);
1037 target = apr_pcalloc(result_pool, sizeof(*target));
1039 /* All other fields in target are FALSE or NULL due to apr_pcalloc(). */
1040 target->db_kind = svn_node_none;
1041 target->kind_on_disk = svn_node_none;
1042 target->content = content;
1043 target->prop_targets = apr_hash_make(result_pool);
1044 target->operation = patch->operation;
1046 if (patch->operation == svn_diff_op_added /* Allow replacing */
1047 || patch->operation == svn_diff_op_moved)
1049 follow_moves = FALSE;
1051 else if (patch->operation == svn_diff_op_unchanged
1052 && patch->hunks && patch->hunks->nelts == 1)
1054 svn_diff_hunk_t *hunk = APR_ARRAY_IDX(patch->hunks, 0,
1057 follow_moves = (svn_diff_hunk_get_original_start(hunk) != 0);
1060 follow_moves = TRUE;
1062 SVN_ERR(resolve_target_path(target, choose_target_filename(patch),
1063 root_abspath, strip_count, has_text_changes,
1064 follow_moves, wc_ctx, targets_info,
1065 result_pool, scratch_pool));
1066 *patch_target = target;
1067 if (! target->skipped)
1069 if (patch->old_symlink_bit == svn_tristate_true
1070 || patch->new_symlink_bit == svn_tristate_true)
1072 target->git_symlink_format = TRUE;
1075 /* ### Is it ok to set the operation of the target already here? Isn't
1076 * ### the target supposed to be marked with an operation after we have
1077 * ### determined that the changes will apply cleanly to the WC? Maybe
1078 * ### we should have kept the patch field in patch_target_t to be
1079 * ### able to distinguish between 'what the patch says we should do'
1080 * ### and 'what we can do with the given state of our WC'. */
1081 if (patch->operation == svn_diff_op_added)
1082 target->added = TRUE;
1083 else if (patch->operation == svn_diff_op_deleted)
1084 target->deleted = TRUE;
1085 else if (patch->operation == svn_diff_op_moved)
1087 const char *move_target_path;
1088 const char *move_target_relpath;
1089 svn_boolean_t under_root;
1090 svn_boolean_t is_special;
1091 svn_node_kind_t kind_on_disk;
1092 svn_node_kind_t wc_kind;
1094 move_target_path = svn_dirent_internal_style(patch->new_filename,
1097 if (strip_count > 0)
1098 SVN_ERR(strip_path(&move_target_path, move_target_path,
1099 strip_count, scratch_pool, scratch_pool));
1101 if (svn_dirent_is_absolute(move_target_path))
1103 move_target_relpath = svn_dirent_is_child(root_abspath,
1106 if (! move_target_relpath)
1108 /* The move target path is either outside of the working
1109 * copy or it is the working copy itself. Skip it. */
1110 target->skipped = TRUE;
1111 return SVN_NO_ERROR;
1115 move_target_relpath = move_target_path;
1117 /* Make sure the move target path is secure to use. */
1118 SVN_ERR(svn_dirent_is_under_root(&under_root,
1119 &target->move_target_abspath,
1121 move_target_relpath, result_pool));
1124 /* The target path is outside of the working copy. Skip it. */
1125 target->skipped = TRUE;
1126 target->move_target_abspath = NULL;
1127 return SVN_NO_ERROR;
1130 SVN_ERR(svn_io_check_special_path(target->move_target_abspath,
1131 &kind_on_disk, &is_special,
1133 SVN_ERR(svn_wc_read_kind2(&wc_kind, wc_ctx,
1134 target->move_target_abspath,
1135 FALSE, FALSE, scratch_pool));
1136 if (wc_kind == svn_node_file || wc_kind == svn_node_dir)
1138 /* The move target path already exists on disk. */
1140 const char *moved_from_abspath;
1142 err = svn_wc__node_was_moved_here(&moved_from_abspath, NULL,
1144 target->move_target_abspath,
1145 scratch_pool, scratch_pool);
1147 if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
1149 svn_error_clear(err);
1151 moved_from_abspath = NULL;
1156 if (moved_from_abspath && (strcmp(moved_from_abspath,
1157 target->local_abspath) == 0))
1159 target->local_abspath = target->move_target_abspath;
1160 target->move_target_abspath = NULL;
1161 target->operation = svn_diff_op_modified;
1162 target->locally_deleted = FALSE;
1163 target->db_kind = wc_kind;
1164 target->kind_on_disk = kind_on_disk;
1165 target->is_special = is_special;
1167 target->had_already_applied = TRUE; /* Make sure we notify */
1171 target->skipped = TRUE;
1172 target->move_target_abspath = NULL;
1173 return SVN_NO_ERROR;
1177 else if (kind_on_disk != svn_node_none
1178 || target_is_added(targets_info, target->move_target_abspath,
1181 target->skipped = TRUE;
1182 target->move_target_abspath = NULL;
1183 return SVN_NO_ERROR;
1187 /* Create a temporary file to write the patched result to.
1188 * Also grab various bits of information about the file. */
1189 if (target->is_symlink)
1191 struct symlink_baton_t *sb = apr_pcalloc(result_pool, sizeof(*sb));
1192 content->existed = TRUE;
1194 sb->local_abspath = target->local_abspath;
1196 /* Wire up the read callbacks. */
1197 content->read_baton = sb;
1199 content->readline = target->git_symlink_format ? readline_symlink_git
1201 content->seek = seek_symlink;
1202 content->tell = tell_symlink;
1204 else if (target->kind_on_disk == svn_node_file)
1206 SVN_ERR(svn_io_file_open(&target->file, target->local_abspath,
1207 APR_READ | APR_BUFFERED,
1208 APR_OS_DEFAULT, result_pool));
1209 SVN_ERR(svn_io_is_file_executable(&target->executable,
1210 target->local_abspath,
1212 SVN_ERR(obtain_eol_and_keywords_for_file(&content->keywords,
1213 &content->eol_style,
1216 target->local_abspath,
1219 content->existed = TRUE;
1221 /* Wire up the read callbacks. */
1222 content->readline = readline_file;
1223 content->seek = seek_file;
1224 content->tell = tell_file;
1225 content->read_baton = target->file;
1228 /* Open a temporary file to write the patched result to. */
1229 SVN_ERR(svn_wc__get_tmpdir(&tempdir_abspath, wc_ctx,
1230 target->local_abspath, scratch_pool, scratch_pool));
1231 SVN_ERR(svn_io_open_unique_file3(&target->patched_file,
1232 &target->patched_path, tempdir_abspath,
1234 svn_io_file_del_on_pool_cleanup :
1235 svn_io_file_del_none,
1236 result_pool, scratch_pool));
1238 /* Put the write callback in place. */
1239 content->write = write_file;
1240 content->write_baton = target->patched_file;
1242 /* Open a temporary stream to write rejected hunks to. */
1243 SVN_ERR(svn_stream_open_unique(&target->reject_stream,
1244 &target->reject_path, tempdir_abspath,
1246 svn_io_file_del_on_pool_cleanup :
1247 svn_io_file_del_none,
1248 result_pool, scratch_pool));
1250 /* Handle properties. */
1251 if (! target->skipped)
1253 apr_hash_index_t *hi;
1255 for (hi = apr_hash_first(result_pool, patch->prop_patches);
1257 hi = apr_hash_next(hi))
1259 const char *prop_name = apr_hash_this_key(hi);
1260 svn_prop_patch_t *prop_patch = apr_hash_this_val(hi);
1261 prop_patch_target_t *prop_target;
1263 SVN_ERR(init_prop_target(&prop_target,
1265 prop_patch->operation,
1266 wc_ctx, target->local_abspath,
1267 result_pool, scratch_pool));
1268 svn_hash_sets(target->prop_targets, prop_name, prop_target);
1271 /* Now, check for out-of-band mode changes and convert these in
1272 their Subversion equivalent properties. */
1273 if (patch->new_executable_bit != svn_tristate_unknown
1274 && patch->new_executable_bit != patch->old_executable_bit)
1276 svn_diff_operation_kind_t operation;
1278 if (patch->new_executable_bit == svn_tristate_true)
1279 operation = svn_diff_op_added;
1280 else if (patch->new_executable_bit == svn_tristate_false)
1282 /* Made non-executable. */
1283 if (patch->old_executable_bit == svn_tristate_true)
1284 operation = svn_diff_op_deleted;
1286 operation = svn_diff_op_unchanged;
1289 operation = svn_diff_op_unchanged;
1291 if (operation != svn_diff_op_unchanged)
1293 prop_patch_target_t *prop_target;
1295 prop_target = svn_hash_gets(target->prop_targets,
1296 SVN_PROP_EXECUTABLE);
1298 if (prop_target && operation != prop_target->operation)
1300 return svn_error_createf(SVN_ERR_INVALID_INPUT, NULL,
1301 _("Invalid patch: specifies "
1302 "contradicting mode changes and "
1303 "%s changes (for '%s')"),
1304 SVN_PROP_EXECUTABLE,
1305 target->local_abspath);
1307 else if (!prop_target)
1309 SVN_ERR(init_prop_target(&prop_target,
1310 target, SVN_PROP_EXECUTABLE,
1312 wc_ctx, target->local_abspath,
1313 result_pool, scratch_pool));
1314 svn_hash_sets(target->prop_targets, SVN_PROP_EXECUTABLE,
1320 if (patch->new_symlink_bit != svn_tristate_unknown
1321 && patch->new_symlink_bit != patch->old_symlink_bit)
1323 svn_diff_operation_kind_t operation;
1325 if (patch->new_symlink_bit == svn_tristate_true)
1326 operation = svn_diff_op_added;
1327 else if (patch->new_symlink_bit == svn_tristate_false)
1329 /* Made non-symlink. */
1330 if (patch->old_symlink_bit == svn_tristate_true)
1331 operation = svn_diff_op_deleted;
1333 operation = svn_diff_op_unchanged;
1336 operation = svn_diff_op_unchanged;
1338 if (operation != svn_diff_op_unchanged)
1340 prop_patch_target_t *prop_target;
1341 prop_target = svn_hash_gets(target->prop_targets,
1344 if (prop_target && operation != prop_target->operation)
1346 return svn_error_createf(SVN_ERR_INVALID_INPUT, NULL,
1347 _("Invalid patch: specifies "
1348 "contradicting mode changes and "
1349 "%s changes (for '%s')"),
1351 target->local_abspath);
1353 else if (!prop_target)
1355 SVN_ERR(init_prop_target(&prop_target,
1356 target, SVN_PROP_SPECIAL,
1358 wc_ctx, target->local_abspath,
1359 result_pool, scratch_pool));
1360 svn_hash_sets(target->prop_targets, SVN_PROP_SPECIAL,
1368 if ((target->locally_deleted || target->db_kind == svn_node_none)
1370 && target->operation == svn_diff_op_unchanged)
1372 svn_boolean_t maybe_add = FALSE;
1374 if (patch->hunks && patch->hunks->nelts == 1)
1376 svn_diff_hunk_t *hunk = APR_ARRAY_IDX(patch->hunks, 0,
1379 if (svn_diff_hunk_get_original_start(hunk) == 0)
1382 else if (patch->prop_patches && apr_hash_count(patch->prop_patches))
1384 apr_hash_index_t *hi;
1385 svn_boolean_t all_add = TRUE;
1387 for (hi = apr_hash_first(result_pool, patch->prop_patches);
1389 hi = apr_hash_next(hi))
1391 svn_prop_patch_t *prop_patch = apr_hash_this_val(hi);
1393 if (prop_patch->operation != svn_diff_op_added)
1400 maybe_add = all_add;
1402 /* Other implied types */
1405 target->added = TRUE;
1407 else if (!target->deleted && !target->added
1408 && target->operation == svn_diff_op_unchanged)
1410 svn_boolean_t maybe_delete = FALSE;
1412 if (patch->hunks && patch->hunks->nelts == 1)
1414 svn_diff_hunk_t *hunk = APR_ARRAY_IDX(patch->hunks, 0,
1417 if (svn_diff_hunk_get_modified_start(hunk) == 0)
1418 maybe_delete = TRUE;
1421 /* Other implied types */
1424 target->deleted = TRUE;
1427 if (target->reject_stream != NULL)
1429 /* The reject file needs a diff header. */
1430 const char *left_src = target->canon_path_from_patchfile;
1431 const char *right_src = target->canon_path_from_patchfile;
1433 /* Handle moves specifically? */
1435 left_src = "/dev/null";
1436 if (target->deleted)
1437 right_src = "/dev/null";
1439 SVN_ERR(svn_stream_printf(target->reject_stream, scratch_pool,
1440 "--- %s" APR_EOL_STR
1441 "+++ %s" APR_EOL_STR,
1442 left_src, right_src));
1445 return SVN_NO_ERROR;
1448 /* Read a *LINE from CONTENT. If the line has not been read before
1449 * mark the line in CONTENT->LINES.
1450 * If a line could be read successfully, increase CONTENT->CURRENT_LINE,
1451 * and allocate *LINE in RESULT_POOL.
1452 * Do temporary allocations in SCRATCH_POOL.
1454 static svn_error_t *
1455 readline(target_content_t *content,
1457 apr_pool_t *result_pool,
1458 apr_pool_t *scratch_pool)
1460 svn_stringbuf_t *line_raw;
1461 const char *eol_str;
1462 svn_linenum_t max_line = (svn_linenum_t)content->lines->nelts + 1;
1464 if (content->eof || content->readline == NULL)
1467 return SVN_NO_ERROR;
1470 SVN_ERR_ASSERT(content->current_line <= max_line);
1471 if (content->current_line == max_line)
1475 SVN_ERR(content->tell(content->read_baton, &offset,
1477 APR_ARRAY_PUSH(content->lines, apr_off_t) = offset;
1480 SVN_ERR(content->readline(content->read_baton, &line_raw,
1481 &eol_str, &content->eof,
1482 result_pool, scratch_pool));
1483 if (content->eol_style == svn_subst_eol_style_none)
1484 content->eol_str = eol_str;
1488 /* Contract keywords. */
1489 SVN_ERR(svn_subst_translate_cstring2(line_raw->data, line,
1491 content->keywords, FALSE,
1497 if ((line_raw && line_raw->len > 0) || eol_str)
1498 content->current_line++;
1500 SVN_ERR_ASSERT(content->current_line > 0);
1502 return SVN_NO_ERROR;
1505 /* Seek to the specified LINE in CONTENT.
1506 * Mark any lines not read before in CONTENT->LINES.
1507 * Do temporary allocations in SCRATCH_POOL.
1509 static svn_error_t *
1510 seek_to_line(target_content_t *content, svn_linenum_t line,
1511 apr_pool_t *scratch_pool)
1513 svn_linenum_t saved_line;
1514 svn_boolean_t saved_eof;
1516 SVN_ERR_ASSERT(line > 0);
1518 if (line == content->current_line)
1519 return SVN_NO_ERROR;
1521 saved_line = content->current_line;
1522 saved_eof = content->eof;
1524 if (line <= (svn_linenum_t)content->lines->nelts)
1528 offset = APR_ARRAY_IDX(content->lines, line - 1, apr_off_t);
1529 SVN_ERR(content->seek(content->read_baton, offset,
1531 content->current_line = line;
1536 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1538 while (! content->eof && content->current_line < line)
1540 svn_pool_clear(iterpool);
1541 SVN_ERR(readline(content, &dummy, iterpool, iterpool));
1543 svn_pool_destroy(iterpool);
1546 /* After seeking backwards from EOF position clear EOF indicator. */
1547 if (saved_eof && saved_line > content->current_line)
1548 content->eof = FALSE;
1550 return SVN_NO_ERROR;
1553 /* Indicate in *MATCHED whether the original text of HUNK matches the patch
1554 * CONTENT at its current line. Lines within FUZZ lines of the start or
1555 * end of HUNK will always match. If IGNORE_WHITESPACE is set, we ignore
1556 * whitespace when doing the matching. When this function returns, neither
1557 * CONTENT->CURRENT_LINE nor the file offset in the target file will
1558 * have changed. If MATCH_MODIFIED is TRUE, match the modified hunk text,
1559 * rather than the original hunk text.
1560 * Do temporary allocations in POOL. */
1561 static svn_error_t *
1562 match_hunk(svn_boolean_t *matched, target_content_t *content,
1563 svn_diff_hunk_t *hunk, svn_linenum_t fuzz,
1564 svn_boolean_t ignore_whitespace,
1565 svn_boolean_t match_modified, apr_pool_t *pool)
1567 svn_stringbuf_t *hunk_line;
1568 const char *target_line;
1569 svn_linenum_t lines_read;
1570 svn_linenum_t saved_line;
1571 svn_boolean_t hunk_eof;
1572 svn_boolean_t lines_matched;
1573 apr_pool_t *iterpool;
1574 svn_linenum_t hunk_length;
1575 svn_linenum_t leading_context;
1576 svn_linenum_t trailing_context;
1577 svn_linenum_t fuzz_penalty;
1582 return SVN_NO_ERROR;
1584 fuzz_penalty = svn_diff_hunk__get_fuzz_penalty(hunk);
1586 if (fuzz_penalty > fuzz)
1587 return SVN_NO_ERROR;
1589 fuzz -= fuzz_penalty;
1591 saved_line = content->current_line;
1593 lines_matched = FALSE;
1594 leading_context = svn_diff_hunk_get_leading_context(hunk);
1595 trailing_context = svn_diff_hunk_get_trailing_context(hunk);
1598 svn_diff_hunk_reset_modified_text(hunk);
1599 hunk_length = svn_diff_hunk_get_modified_length(hunk);
1603 svn_diff_hunk_reset_original_text(hunk);
1604 hunk_length = svn_diff_hunk_get_original_length(hunk);
1606 iterpool = svn_pool_create(pool);
1609 const char *hunk_line_translated;
1611 svn_pool_clear(iterpool);
1614 SVN_ERR(svn_diff_hunk_readline_modified_text(hunk, &hunk_line,
1616 iterpool, iterpool));
1618 SVN_ERR(svn_diff_hunk_readline_original_text(hunk, &hunk_line,
1620 iterpool, iterpool));
1622 /* Contract keywords, if any, before matching. */
1623 SVN_ERR(svn_subst_translate_cstring2(hunk_line->data,
1624 &hunk_line_translated,
1626 content->keywords, FALSE,
1628 SVN_ERR(readline(content, &target_line, iterpool, iterpool));
1632 /* If the last line doesn't have a newline, we get EOF but still
1633 * have a non-empty line to compare. */
1634 if ((hunk_eof && hunk_line->len == 0) ||
1635 (content->eof && *target_line == 0))
1638 /* Leading/trailing fuzzy lines always match. */
1639 if ((lines_read <= fuzz && leading_context > fuzz) ||
1640 (lines_read > hunk_length - fuzz && trailing_context > fuzz))
1641 lines_matched = TRUE;
1644 if (ignore_whitespace)
1646 char *hunk_line_trimmed;
1647 char *target_line_trimmed;
1649 hunk_line_trimmed = apr_pstrdup(iterpool, hunk_line_translated);
1650 target_line_trimmed = apr_pstrdup(iterpool, target_line);
1651 apr_collapse_spaces(hunk_line_trimmed, hunk_line_trimmed);
1652 apr_collapse_spaces(target_line_trimmed, target_line_trimmed);
1653 lines_matched = ! strcmp(hunk_line_trimmed, target_line_trimmed);
1656 lines_matched = ! strcmp(hunk_line_translated, target_line);
1659 while (lines_matched);
1661 *matched = lines_matched && hunk_eof && hunk_line->len == 0;
1662 SVN_ERR(seek_to_line(content, saved_line, iterpool));
1663 svn_pool_destroy(iterpool);
1665 return SVN_NO_ERROR;
1668 /* Scan lines of CONTENT for a match of the original text of HUNK,
1669 * up to but not including the specified UPPER_LINE. Use fuzz factor FUZZ.
1670 * If UPPER_LINE is zero scan until EOF occurs when reading from TARGET.
1671 * Return the line at which HUNK was matched in *MATCHED_LINE.
1672 * If the hunk did not match at all, set *MATCHED_LINE to zero.
1673 * If the hunk matched multiple times, and MATCH_FIRST is TRUE,
1674 * return the line number at which the first match occurred in *MATCHED_LINE.
1675 * If the hunk matched multiple times, and MATCH_FIRST is FALSE,
1676 * return the line number at which the last match occurred in *MATCHED_LINE.
1677 * If IGNORE_WHITESPACE is set, ignore whitespace during the matching.
1678 * If MATCH_MODIFIED is TRUE, match the modified hunk text,
1679 * rather than the original hunk text.
1680 * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
1681 * Do all allocations in POOL. */
1682 static svn_error_t *
1683 scan_for_match(svn_linenum_t *matched_line,
1684 target_content_t *content,
1685 svn_diff_hunk_t *hunk, svn_boolean_t match_first,
1686 svn_linenum_t upper_line, svn_linenum_t fuzz,
1687 svn_boolean_t ignore_whitespace,
1688 svn_boolean_t match_modified,
1689 svn_cancel_func_t cancel_func, void *cancel_baton,
1692 apr_pool_t *iterpool;
1695 iterpool = svn_pool_create(pool);
1696 while ((content->current_line < upper_line || upper_line == 0) &&
1699 svn_boolean_t matched;
1701 svn_pool_clear(iterpool);
1704 SVN_ERR(cancel_func(cancel_baton));
1706 SVN_ERR(match_hunk(&matched, content, hunk, fuzz, ignore_whitespace,
1707 match_modified, iterpool));
1710 svn_boolean_t taken = FALSE;
1713 /* Don't allow hunks to match at overlapping locations. */
1714 for (i = 0; i < content->hunks->nelts; i++)
1716 const hunk_info_t *hi;
1717 svn_linenum_t length;
1719 hi = APR_ARRAY_IDX(content->hunks, i, const hunk_info_t *);
1722 length = svn_diff_hunk_get_modified_length(hi->hunk);
1724 length = svn_diff_hunk_get_original_length(hi->hunk);
1726 taken = (! hi->rejected &&
1727 content->current_line >= hi->matched_line &&
1728 content->current_line < (hi->matched_line + length));
1735 *matched_line = content->current_line;
1742 SVN_ERR(seek_to_line(content, content->current_line + 1,
1745 svn_pool_destroy(iterpool);
1747 return SVN_NO_ERROR;
1750 /* Indicate in *MATCH whether the content described by CONTENT
1751 * matches the modified text of HUNK.
1752 * Use SCRATCH_POOL for temporary allocations. */
1753 static svn_error_t *
1754 match_existing_target(svn_boolean_t *match,
1755 target_content_t *content,
1756 svn_diff_hunk_t *hunk,
1757 apr_pool_t *scratch_pool)
1759 svn_boolean_t lines_matched;
1760 apr_pool_t *iterpool;
1761 svn_boolean_t hunk_eof;
1762 svn_linenum_t saved_line;
1764 svn_diff_hunk_reset_modified_text(hunk);
1766 saved_line = content->current_line;
1768 iterpool = svn_pool_create(scratch_pool);
1772 svn_stringbuf_t *hunk_line;
1773 const char *line_translated;
1774 const char *hunk_line_translated;
1776 svn_pool_clear(iterpool);
1778 SVN_ERR(readline(content, &line, iterpool, iterpool));
1779 SVN_ERR(svn_diff_hunk_readline_modified_text(hunk, &hunk_line,
1781 iterpool, iterpool));
1782 /* Contract keywords. */
1783 SVN_ERR(svn_subst_translate_cstring2(line, &line_translated,
1787 SVN_ERR(svn_subst_translate_cstring2(hunk_line->data,
1788 &hunk_line_translated,
1792 lines_matched = ! strcmp(line_translated, hunk_line_translated);
1793 if (content->eof != hunk_eof)
1795 svn_pool_destroy(iterpool);
1797 return SVN_NO_ERROR;
1800 while (lines_matched && ! content->eof && ! hunk_eof);
1801 svn_pool_destroy(iterpool);
1803 *match = (lines_matched && content->eof == hunk_eof);
1804 SVN_ERR(seek_to_line(content, saved_line, scratch_pool));
1806 return SVN_NO_ERROR;
1809 /* Determine the line at which a HUNK applies to CONTENT of the TARGET
1810 * file, and return an appropriate hunk_info object in *HI, allocated from
1811 * RESULT_POOL. Use fuzz factor FUZZ. Set HI->FUZZ to FUZZ. If no correct
1812 * line can be determined, set HI->REJECTED to TRUE. PREVIOUS_OFFSET
1813 * is the offset at which the previous matching hunk was applied, or zero.
1814 * IGNORE_WHITESPACE tells whether whitespace should be considered when
1815 * matching. IS_PROP_HUNK indicates whether the hunk patches file content
1817 * When this function returns, neither CONTENT->CURRENT_LINE nor
1818 * the file offset in the target file will have changed.
1819 * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
1820 * Do temporary allocations in POOL. */
1821 static svn_error_t *
1822 get_hunk_info(hunk_info_t **hi, patch_target_t *target,
1823 target_content_t *content,
1824 svn_diff_hunk_t *hunk, svn_linenum_t fuzz,
1825 svn_linenum_t previous_offset,
1826 svn_boolean_t ignore_whitespace,
1827 svn_boolean_t is_prop_hunk,
1828 svn_cancel_func_t cancel_func, void *cancel_baton,
1829 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
1831 svn_linenum_t matched_line;
1832 svn_linenum_t original_start;
1833 svn_boolean_t already_applied;
1835 original_start = svn_diff_hunk_get_original_start(hunk) + previous_offset;
1836 already_applied = FALSE;
1838 /* An original offset of zero means that this hunk wants to create
1839 * a new file. Don't bother matching hunks in that case, since
1840 * the hunk applies at line 1. If the file already exists, the hunk
1841 * is rejected, unless the file is versioned and its content matches
1842 * the file the patch wants to create. */
1843 if (original_start == 0 && fuzz > 0)
1845 matched_line = 0; /* reject any fuzz for new files */
1847 else if (original_start == 0 && ! is_prop_hunk)
1849 if (target->kind_on_disk == svn_node_file)
1851 const svn_io_dirent2_t *dirent;
1852 SVN_ERR(svn_io_stat_dirent2(&dirent, target->local_abspath, FALSE,
1853 TRUE, scratch_pool, scratch_pool));
1855 if (dirent->kind == svn_node_file
1857 && dirent->filesize == 0)
1859 matched_line = 1; /* Matched an on-disk empty file */
1863 if (target->db_kind == svn_node_file)
1865 svn_boolean_t file_matches;
1867 SVN_ERR(match_existing_target(&file_matches, content, hunk,
1872 already_applied = TRUE;
1875 matched_line = 0; /* reject */
1878 matched_line = 0; /* reject */
1884 /* Same conditions apply as for the file case above.
1886 * ### Since the hunk says the prop should be added we just assume so for
1887 * ### now and don't bother with storing the previous lines and such. When
1888 * ### we have the diff operation available we can just check for adds. */
1889 else if (original_start == 0 && is_prop_hunk)
1891 if (content->existed)
1893 svn_boolean_t prop_matches;
1895 SVN_ERR(match_existing_target(&prop_matches, content, hunk,
1901 already_applied = TRUE;
1904 matched_line = 0; /* reject */
1909 else if (original_start > 0 && content->existed)
1911 svn_linenum_t modified_start;
1912 svn_linenum_t saved_line = content->current_line;
1914 modified_start = svn_diff_hunk_get_modified_start(hunk);
1916 /* Scan for a match at the line where the hunk thinks it
1917 * should be going. */
1918 SVN_ERR(seek_to_line(content, original_start, scratch_pool));
1919 if (content->current_line != original_start)
1925 SVN_ERR(scan_for_match(&matched_line, content, hunk, TRUE,
1926 original_start + 1, fuzz,
1927 ignore_whitespace, FALSE,
1928 cancel_func, cancel_baton,
1931 if (matched_line != original_start)
1933 /* Check if the hunk is already applied.
1934 * We only check for an exact match here, and don't bother checking
1935 * for already applied patches with offset/fuzz, because such a
1936 * check would be ambiguous. */
1939 if (modified_start == 0
1940 && (target->operation == svn_diff_op_unchanged
1941 || target->operation == svn_diff_op_deleted))
1943 /* Patch wants to delete the file. */
1945 already_applied = target->locally_deleted;
1949 svn_linenum_t seek_to;
1951 if (modified_start == 0)
1952 seek_to = 1; /* Empty file case */
1954 seek_to = modified_start;
1956 SVN_ERR(seek_to_line(content, seek_to, scratch_pool));
1957 SVN_ERR(scan_for_match(&matched_line, content,
1960 fuzz, ignore_whitespace, TRUE,
1961 cancel_func, cancel_baton,
1963 already_applied = (matched_line == modified_start);
1967 already_applied = FALSE;
1969 if (! already_applied)
1972 svn_linenum_t search_start = 1, search_end = 0;
1973 svn_linenum_t matched_line2;
1975 /* Search for closest match before or after original
1976 start. We have no backward search so search forwards
1977 from the previous match (or start of file) to the
1978 original start looking for the last match. Then
1979 search forwards from the original start looking for a
1980 better match. Finally search forwards from the start
1981 of file to the previous hunk if that could result in
1984 for (i = content->hunks->nelts; i > 0; --i)
1986 const hunk_info_t *prev
1987 = APR_ARRAY_IDX(content->hunks, i - 1, const hunk_info_t *);
1988 if (!prev->rejected)
1990 svn_linenum_t length;
1992 length = svn_diff_hunk_get_original_length(prev->hunk);
1993 search_start = prev->matched_line + length;
1998 /* Search from the previous match, or start of file,
1999 towards the original location. */
2000 SVN_ERR(seek_to_line(content, search_start, scratch_pool));
2001 SVN_ERR(scan_for_match(&matched_line, content, hunk, FALSE,
2002 original_start, fuzz,
2003 ignore_whitespace, FALSE,
2004 cancel_func, cancel_baton,
2007 /* If a match we only need to search forwards for a
2008 better match, otherwise to the end of the file. */
2010 search_end = original_start + (original_start - matched_line);
2012 /* Search from original location, towards the end. */
2013 SVN_ERR(seek_to_line(content, original_start + 1, scratch_pool));
2014 SVN_ERR(scan_for_match(&matched_line2, content, hunk,
2015 TRUE, search_end, fuzz, ignore_whitespace,
2016 FALSE, cancel_func, cancel_baton,
2019 /* Chose the forward match if it is closer than the
2020 backward match or if there is no backward match. */
2023 || (matched_line2 - original_start
2024 < original_start - matched_line)))
2025 matched_line = matched_line2;
2027 /* Search from before previous hunk if there could be a
2029 if (search_start > 1
2031 || (matched_line > original_start
2032 && (matched_line - original_start
2033 > original_start - search_start))))
2035 svn_linenum_t search_start2 = 1;
2038 && matched_line - original_start < original_start)
2040 = original_start - (matched_line - original_start) + 1;
2042 SVN_ERR(seek_to_line(content, search_start2, scratch_pool));
2043 SVN_ERR(scan_for_match(&matched_line2, content, hunk, FALSE,
2044 search_start - 1, fuzz,
2045 ignore_whitespace, FALSE,
2046 cancel_func, cancel_baton,
2049 matched_line = matched_line2;
2053 else if (matched_line > 0
2055 && (svn_diff_hunk_get_leading_context(hunk) == 0
2056 || svn_diff_hunk_get_trailing_context(hunk) == 0)
2057 && (svn_diff_hunk_get_modified_length(hunk) >
2058 svn_diff_hunk_get_original_length(hunk)))
2060 /* Check that we are not applying the same change that just adds some
2061 lines again, when we don't have enough context to see the
2063 svn_linenum_t reverse_matched_line;
2065 SVN_ERR(seek_to_line(content, modified_start, scratch_pool));
2066 SVN_ERR(scan_for_match(&reverse_matched_line, content,
2069 fuzz, ignore_whitespace, TRUE,
2070 cancel_func, cancel_baton,
2073 /* We might want to check that we are actually at the start or the
2074 end of the file. Having no context implies that we should be. */
2075 already_applied = (reverse_matched_line == modified_start);
2078 SVN_ERR(seek_to_line(content, saved_line, scratch_pool));
2080 else if (!content->existed && svn_diff_hunk_get_modified_start(hunk) == 0)
2082 /* The hunk wants to delete a file or property which doesn't exist. */
2084 already_applied = TRUE;
2088 /* The hunk wants to modify a file or property which doesn't exist. */
2092 (*hi) = apr_pcalloc(result_pool, sizeof(hunk_info_t));
2094 (*hi)->matched_line = matched_line;
2095 (*hi)->rejected = (matched_line == 0);
2096 (*hi)->already_applied = already_applied;
2097 (*hi)->report_fuzz = fuzz;
2098 (*hi)->match_fuzz = fuzz - svn_diff_hunk__get_fuzz_penalty(hunk);
2100 return SVN_NO_ERROR;
2103 /* Copy lines to the patched content until the specified LINE has been
2104 * reached. Indicate in *EOF whether end-of-file was encountered while
2105 * reading from the target.
2106 * If LINE is zero, copy lines until end-of-file has been reached.
2107 * Do all allocations in POOL. */
2108 static svn_error_t *
2109 copy_lines_to_target(target_content_t *content, svn_linenum_t line,
2112 apr_pool_t *iterpool;
2114 iterpool = svn_pool_create(pool);
2115 while ((content->current_line < line || line == 0) && ! content->eof)
2117 const char *target_line;
2120 svn_pool_clear(iterpool);
2122 SVN_ERR(readline(content, &target_line, iterpool, iterpool));
2124 target_line = apr_pstrcat(iterpool, target_line, content->eol_str,
2126 len = strlen(target_line);
2127 SVN_ERR(content->write(content->write_baton, target_line,
2130 svn_pool_destroy(iterpool);
2132 return SVN_NO_ERROR;
2135 /* Write the diff text of HUNK to TARGET's reject file,
2136 * and mark TARGET as having had rejects.
2137 * We don't expand keywords, nor normalise line-endings, in reject files.
2138 * Do temporary allocations in SCRATCH_POOL. */
2139 static svn_error_t *
2140 reject_hunk(patch_target_t *target, target_content_t *content,
2141 svn_diff_hunk_t *hunk, const char *prop_name,
2145 static const char * const text_atat = "@@";
2146 static const char * const prop_atat = "##";
2148 apr_pool_t *iterpool;
2152 /* ### Print 'Added', 'Deleted' or 'Modified' instead of 'Property'. */
2153 SVN_ERR(svn_stream_printf(target->reject_stream,
2154 pool, "Property: %s" APR_EOL_STR, prop_name));
2162 SVN_ERR(svn_stream_printf(target->reject_stream, pool,
2163 "%s -%lu,%lu +%lu,%lu %s" APR_EOL_STR,
2165 svn_diff_hunk_get_original_start(hunk),
2166 svn_diff_hunk_get_original_length(hunk),
2167 svn_diff_hunk_get_modified_start(hunk),
2168 svn_diff_hunk_get_modified_length(hunk),
2171 iterpool = svn_pool_create(pool);
2174 svn_stringbuf_t *hunk_line;
2175 const char *eol_str;
2177 svn_pool_clear(iterpool);
2179 SVN_ERR(svn_diff_hunk_readline_diff_text(hunk, &hunk_line, &eol_str,
2180 &eof, iterpool, iterpool));
2183 if (hunk_line->len >= 1)
2185 apr_size_t len = hunk_line->len;
2187 SVN_ERR(svn_stream_write(target->reject_stream,
2188 hunk_line->data, &len));
2193 SVN_ERR(svn_stream_puts(target->reject_stream, eol_str));
2198 svn_pool_destroy(iterpool);
2201 target->had_prop_rejects = TRUE;
2203 target->had_rejects = TRUE;
2205 return SVN_NO_ERROR;
2208 /* Write the modified text of the hunk described by HI to the patched
2209 * CONTENT. TARGET is the patch target.
2210 * If PROP_NAME is not NULL, the hunk is assumed to be targeted for
2211 * a property with the given name.
2212 * Do temporary allocations in POOL. */
2213 static svn_error_t *
2214 apply_hunk(patch_target_t *target, target_content_t *content,
2215 hunk_info_t *hi, const char *prop_name, apr_pool_t *pool)
2217 svn_linenum_t lines_read;
2219 apr_pool_t *iterpool;
2220 svn_linenum_t fuzz = hi->match_fuzz;
2222 /* ### Is there a cleaner way to describe if we have an existing target?
2224 if (target->kind_on_disk == svn_node_file || prop_name)
2228 /* Move forward to the hunk's line, copying data as we go.
2229 * Also copy leading lines of context which matched with fuzz.
2230 * The target has changed on the fuzzy-matched lines,
2231 * so we should retain the target's version of those lines. */
2232 SVN_ERR(copy_lines_to_target(content, hi->matched_line + fuzz,
2235 /* Skip the target's version of the hunk.
2236 * Don't skip trailing lines which matched with fuzz. */
2237 line = content->current_line +
2238 svn_diff_hunk_get_original_length(hi->hunk) - (2 * fuzz);
2239 SVN_ERR(seek_to_line(content, line, pool));
2240 if (content->current_line != line && ! content->eof)
2242 /* Seek failed, reject this hunk. */
2243 hi->rejected = TRUE;
2244 SVN_ERR(reject_hunk(target, content, hi->hunk, prop_name, pool));
2245 return SVN_NO_ERROR;
2249 /* Write the hunk's version to the patched result.
2250 * Don't write the lines which matched with fuzz. */
2252 svn_diff_hunk_reset_modified_text(hi->hunk);
2253 iterpool = svn_pool_create(pool);
2256 svn_stringbuf_t *hunk_line;
2257 const char *eol_str;
2259 svn_pool_clear(iterpool);
2261 SVN_ERR(svn_diff_hunk_readline_modified_text(hi->hunk, &hunk_line,
2263 iterpool, iterpool));
2265 if (lines_read > fuzz &&
2266 lines_read <= svn_diff_hunk_get_modified_length(hi->hunk) - fuzz)
2270 if (hunk_line->len >= 1)
2272 len = hunk_line->len;
2273 SVN_ERR(content->write(content->write_baton,
2274 hunk_line->data, len, iterpool));
2279 /* Use the EOL as it was read from the patch file,
2280 * unless the target's EOL style is set by svn:eol-style */
2281 if (content->eol_style != svn_subst_eol_style_none)
2282 eol_str = content->eol_str;
2284 len = strlen(eol_str);
2285 SVN_ERR(content->write(content->write_baton,
2286 eol_str, len, iterpool));
2291 svn_pool_destroy(iterpool);
2294 target->has_prop_changes = TRUE;
2296 target->has_text_changes = TRUE;
2298 return SVN_NO_ERROR;
2301 /* Use client context CTX to send a suitable notification for hunk HI,
2302 * using TARGET to determine the path. If the hunk is a property hunk,
2303 * PROP_NAME must be the name of the property, else NULL.
2304 * Use POOL for temporary allocations. */
2305 static svn_error_t *
2306 send_hunk_notification(const hunk_info_t *hi,
2307 const patch_target_t *target,
2308 const char *prop_name,
2309 const svn_client_ctx_t *ctx,
2312 svn_wc_notify_t *notify;
2313 svn_wc_notify_action_t action;
2315 if (hi->already_applied)
2316 action = svn_wc_notify_patch_hunk_already_applied;
2317 else if (hi->rejected)
2318 action = svn_wc_notify_patch_rejected_hunk;
2320 action = svn_wc_notify_patch_applied_hunk;
2322 notify = svn_wc_create_notify(target->local_abspath
2323 ? target->local_abspath
2324 : target->local_relpath,
2326 notify->hunk_original_start =
2327 svn_diff_hunk_get_original_start(hi->hunk);
2328 notify->hunk_original_length =
2329 svn_diff_hunk_get_original_length(hi->hunk);
2330 notify->hunk_modified_start =
2331 svn_diff_hunk_get_modified_start(hi->hunk);
2332 notify->hunk_modified_length =
2333 svn_diff_hunk_get_modified_length(hi->hunk);
2334 notify->hunk_matched_line = hi->matched_line;
2335 notify->hunk_fuzz = hi->report_fuzz;
2336 notify->prop_name = prop_name;
2338 ctx->notify_func2(ctx->notify_baton2, notify, pool);
2340 return SVN_NO_ERROR;
2343 /* Use client context CTX to send a suitable notification for a patch TARGET.
2344 * Use POOL for temporary allocations. */
2345 static svn_error_t *
2346 send_patch_notification(const patch_target_t *target,
2347 const svn_client_ctx_t *ctx,
2348 apr_pool_t *scratch_pool)
2350 svn_wc_notify_t *notify;
2351 svn_wc_notify_action_t action;
2352 const char *notify_path;
2354 if (! ctx->notify_func2)
2355 return SVN_NO_ERROR;
2357 if (target->skipped)
2358 action = svn_wc_notify_skip;
2359 else if (target->deleted)
2360 action = svn_wc_notify_delete;
2361 else if (target->added || target->move_target_abspath)
2362 action = svn_wc_notify_add;
2364 action = svn_wc_notify_patch;
2366 if (target->move_target_abspath)
2367 notify_path = target->move_target_abspath;
2369 notify_path = target->local_abspath ? target->local_abspath
2370 : target->local_relpath;
2372 notify = svn_wc_create_notify(notify_path, action, scratch_pool);
2373 notify->kind = (target->db_kind == svn_node_dir) ? svn_node_dir
2376 if (action == svn_wc_notify_skip)
2378 if (target->obstructed)
2379 notify->content_state = svn_wc_notify_state_obstructed;
2380 else if (target->db_kind == svn_node_none ||
2381 target->db_kind == svn_node_unknown)
2382 notify->content_state = svn_wc_notify_state_missing;
2384 notify->content_state = svn_wc_notify_state_unknown;
2388 if (target->had_rejects)
2389 notify->content_state = svn_wc_notify_state_conflicted;
2390 else if (target->has_text_changes)
2391 notify->content_state = svn_wc_notify_state_changed;
2392 else if (target->had_already_applied)
2393 notify->content_state = svn_wc_notify_state_merged;
2395 notify->content_state = svn_wc_notify_state_unchanged;
2397 if (target->had_prop_rejects)
2398 notify->prop_state = svn_wc_notify_state_conflicted;
2399 else if (target->has_prop_changes)
2400 notify->prop_state = svn_wc_notify_state_changed;
2401 else if (target->had_prop_already_applied)
2402 notify->prop_state = svn_wc_notify_state_merged;
2404 notify->prop_state = svn_wc_notify_state_unchanged;
2407 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
2409 if (action == svn_wc_notify_patch)
2412 apr_pool_t *iterpool;
2413 apr_array_header_t *prop_targets;
2415 iterpool = svn_pool_create(scratch_pool);
2416 for (i = 0; i < target->content->hunks->nelts; i++)
2418 const hunk_info_t *hi;
2420 svn_pool_clear(iterpool);
2422 hi = APR_ARRAY_IDX(target->content->hunks, i, hunk_info_t *);
2424 SVN_ERR(send_hunk_notification(hi, target, NULL /* prop_name */,
2428 prop_targets = svn_sort__hash(target->prop_targets,
2429 svn_sort_compare_items_lexically,
2431 for (i = 0; i < prop_targets->nelts; i++)
2434 svn_sort__item_t item = APR_ARRAY_IDX(prop_targets, i,
2437 prop_patch_target_t *prop_target = item.value;
2439 for (j = 0; j < prop_target->content->hunks->nelts; j++)
2441 const hunk_info_t *hi;
2443 svn_pool_clear(iterpool);
2445 hi = APR_ARRAY_IDX(prop_target->content->hunks, j,
2448 /* Don't notify on the hunk level for added or deleted props. */
2449 if ((prop_target->operation != svn_diff_op_added &&
2450 prop_target->operation != svn_diff_op_deleted)
2451 || hi->rejected || hi->already_applied)
2452 SVN_ERR(send_hunk_notification(hi, target, prop_target->name,
2456 svn_pool_destroy(iterpool);
2459 if (!target->skipped && target->move_target_abspath)
2461 /* Notify about deletion of move source. */
2462 notify = svn_wc_create_notify(target->local_abspath,
2463 svn_wc_notify_delete, scratch_pool);
2464 notify->kind = svn_node_file;
2465 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
2468 return SVN_NO_ERROR;
2471 /* Implements the callback for svn_sort__array. Puts hunks that match
2472 before hunks that do not match, puts hunks that match in order
2473 based on postion matched, puts hunks that do not match in order
2474 based on original position. */
2476 sort_matched_hunks(const void *a, const void *b)
2478 const hunk_info_t *item1 = *((const hunk_info_t * const *)a);
2479 const hunk_info_t *item2 = *((const hunk_info_t * const *)b);
2480 svn_boolean_t matched1 = !item1->rejected && !item1->already_applied;
2481 svn_boolean_t matched2 = !item2->rejected && !item2->already_applied;
2482 svn_linenum_t original1, original2;
2484 if (matched1 && matched2)
2486 /* Both match so use order matched in file. */
2487 if (item1->matched_line > item2->matched_line)
2489 else if (item1->matched_line == item2->matched_line)
2495 /* Only second matches, put it before first. */
2498 /* Only first matches, put it before second. */
2501 /* Neither matches, sort by original_start. */
2502 original1 = svn_diff_hunk_get_original_start(item1->hunk);
2503 original2 = svn_diff_hunk_get_original_start(item2->hunk);
2504 if (original1 > original2)
2506 else if (original1 == original2)
2513 /* Apply a PATCH to a working copy at ABS_WC_PATH and put the result
2514 * into temporary files, to be installed in the working copy later.
2515 * Return information about the patch target in *PATCH_TARGET, allocated
2516 * in RESULT_POOL. Use WC_CTX as the working copy context.
2517 * STRIP_COUNT specifies the number of leading path components
2518 * which should be stripped from target paths in the patch.
2519 * REMOVE_TEMPFILES is as in svn_client_patch().
2520 * TARGETS_INFO is for preserving info across calls.
2521 * IGNORE_WHITESPACE tells whether whitespace should be considered when
2522 * doing the matching.
2523 * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
2524 * Do temporary allocations in SCRATCH_POOL. */
2525 static svn_error_t *
2526 apply_one_patch(patch_target_t **patch_target, svn_patch_t *patch,
2527 const char *abs_wc_path, svn_wc_context_t *wc_ctx,
2529 svn_boolean_t ignore_whitespace,
2530 svn_boolean_t remove_tempfiles,
2531 const apr_array_header_t *targets_info,
2532 svn_cancel_func_t cancel_func,
2534 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
2536 patch_target_t *target;
2537 apr_pool_t *iterpool;
2539 static const svn_linenum_t MAX_FUZZ = 2;
2540 apr_hash_index_t *hash_index;
2541 svn_linenum_t previous_offset = 0;
2542 apr_array_header_t *prop_targets;
2544 SVN_ERR(init_patch_target(&target, patch, abs_wc_path, wc_ctx, strip_count,
2545 remove_tempfiles, targets_info,
2546 result_pool, scratch_pool));
2547 if (target->skipped)
2549 *patch_target = target;
2550 return SVN_NO_ERROR;
2553 iterpool = svn_pool_create(scratch_pool);
2555 if (patch->hunks && patch->hunks->nelts)
2558 for (i = 0; i < patch->hunks->nelts; i++)
2560 svn_diff_hunk_t *hunk;
2562 svn_linenum_t fuzz = 0;
2564 svn_pool_clear(iterpool);
2567 SVN_ERR(cancel_func(cancel_baton));
2569 hunk = APR_ARRAY_IDX(patch->hunks, i, svn_diff_hunk_t *);
2571 /* Determine the line the hunk should be applied at.
2572 * If no match is found initially, try with fuzz. */
2575 SVN_ERR(get_hunk_info(&hi, target, target->content, hunk, fuzz,
2578 FALSE /* is_prop_hunk */,
2579 cancel_func, cancel_baton,
2580 result_pool, iterpool));
2583 while (hi->rejected && fuzz <= MAX_FUZZ && ! hi->already_applied);
2585 if (hi->matched_line)
2587 = hi->matched_line - svn_diff_hunk_get_original_start(hunk);
2589 APR_ARRAY_PUSH(target->content->hunks, hunk_info_t *) = hi;
2592 /* Hunks are applied in the order determined by the matched line and
2593 this may be different from the order of the original lines. */
2594 svn_sort__array(target->content->hunks, sort_matched_hunks);
2596 /* Apply or reject hunks. */
2597 for (i = 0; i < target->content->hunks->nelts; i++)
2601 svn_pool_clear(iterpool);
2604 SVN_ERR(cancel_func(cancel_baton));
2606 hi = APR_ARRAY_IDX(target->content->hunks, i, hunk_info_t *);
2607 if (hi->already_applied)
2609 target->had_already_applied = TRUE;
2612 else if (hi->rejected)
2613 SVN_ERR(reject_hunk(target, target->content, hi->hunk,
2614 NULL /* prop_name */,
2617 SVN_ERR(apply_hunk(target, target->content, hi,
2618 NULL /* prop_name */, iterpool));
2621 if (target->kind_on_disk == svn_node_file)
2623 /* Copy any remaining lines to target. */
2624 SVN_ERR(copy_lines_to_target(target->content, 0, scratch_pool));
2625 if (! target->content->eof)
2627 /* We could not copy the entire target file to the temporary
2628 * file, and would truncate the target if we copied the
2629 * temporary file on top of it. Skip this target. */
2630 target->skipped = TRUE;
2634 else if (patch->binary_patch)
2636 svn_stream_t *orig_stream;
2640 orig_stream = svn_stream_from_aprfile2(target->file, TRUE, iterpool);
2642 orig_stream = svn_stream_empty(iterpool);
2644 SVN_ERR(svn_stream_contents_same2(
2646 svn_diff_get_binary_diff_original_stream(patch->binary_patch,
2649 svn_pool_clear(iterpool);
2653 /* The file in the working copy is identical to the one expected by
2654 the patch... So we can write the result stream; no fuzz,
2655 just a 100% match */
2657 target->has_text_changes = TRUE;
2661 /* Perhaps the file is identical to the resulting version, implying
2662 that the patch has already been applied */
2665 apr_off_t start = 0;
2667 SVN_ERR(svn_io_file_seek(target->file, APR_SET, &start, iterpool));
2669 orig_stream = svn_stream_from_aprfile2(target->file, TRUE, iterpool);
2672 orig_stream = svn_stream_empty(iterpool);
2674 SVN_ERR(svn_stream_contents_same2(
2676 svn_diff_get_binary_diff_result_stream(patch->binary_patch,
2679 svn_pool_clear(iterpool);
2682 target->had_already_applied = TRUE;
2687 SVN_ERR(svn_stream_copy3(
2688 svn_diff_get_binary_diff_result_stream(patch->binary_patch,
2690 svn_stream_from_aprfile2(target->patched_file, TRUE,
2692 cancel_func, cancel_baton,
2697 /* ### TODO: Implement a proper reject of a binary patch
2699 This should at least setup things for a proper notification,
2700 and perhaps install a normal text conflict. Unlike normal unified
2701 diff based patches we have all the versions we would need for
2702 that in a much easier format than can be obtained from the patch
2704 target->skipped = TRUE;
2707 else if (target->move_target_abspath)
2709 /* ### Why do we do this?
2710 BH: I don't know, but if we don't do this some tests
2711 on git style patches break.
2713 ### It would be much better to really move the actual file instead
2714 of copying to a temporary file; move that to target and then
2715 delete the original file
2717 ### BH: I have absolutely no idea if moving directories would work.
2719 if (target->kind_on_disk == svn_node_file)
2721 /* Copy any remaining lines to target. (read: all lines) */
2722 SVN_ERR(copy_lines_to_target(target->content, 0, scratch_pool));
2723 if (!target->content->eof)
2725 /* We could not copy the entire target file to the temporary
2726 * file, and would truncate the target if we copied the
2727 * temporary file on top of it. Skip this target. */
2728 target->skipped = TRUE;
2733 if (target->had_rejects || target->locally_deleted)
2734 target->deleted = FALSE;
2737 && !(target->locally_deleted || target->db_kind == svn_node_none))
2739 target->added = FALSE;
2742 /* Assume nothing changed. Will be updated via property hunks */
2743 target->is_special = target->is_symlink;
2745 /* Match property hunks. */
2746 for (hash_index = apr_hash_first(scratch_pool, patch->prop_patches);
2748 hash_index = apr_hash_next(hash_index))
2750 svn_prop_patch_t *prop_patch;
2751 const char *prop_name;
2752 prop_patch_target_t *prop_target;
2754 prop_name = apr_hash_this_key(hash_index);
2755 prop_patch = apr_hash_this_val(hash_index);
2757 if (!strcmp(prop_name, SVN_PROP_SPECIAL))
2758 target->is_special = (prop_patch->operation != svn_diff_op_deleted);
2760 /* We'll store matched hunks in prop_content. */
2761 prop_target = svn_hash_gets(target->prop_targets, prop_name);
2763 for (i = 0; i < prop_patch->hunks->nelts; i++)
2765 svn_diff_hunk_t *hunk;
2767 svn_linenum_t fuzz = 0;
2769 svn_pool_clear(iterpool);
2772 SVN_ERR(cancel_func(cancel_baton));
2774 hunk = APR_ARRAY_IDX(prop_patch->hunks, i, svn_diff_hunk_t *);
2776 /* Determine the line the hunk should be applied at.
2777 * If no match is found initially, try with fuzz. */
2780 SVN_ERR(get_hunk_info(&hi, target, prop_target->content,
2783 TRUE /* is_prop_hunk */,
2784 cancel_func, cancel_baton,
2785 result_pool, iterpool));
2788 while (hi->rejected && fuzz <= MAX_FUZZ && ! hi->already_applied);
2790 APR_ARRAY_PUSH(prop_target->content->hunks, hunk_info_t *) = hi;
2794 /* Match implied property hunks. */
2795 if (patch->new_executable_bit != svn_tristate_unknown
2796 && patch->new_executable_bit != patch->old_executable_bit
2797 && svn_hash_gets(target->prop_targets, SVN_PROP_EXECUTABLE)
2798 && !svn_hash_gets(patch->prop_patches, SVN_PROP_EXECUTABLE))
2801 svn_diff_hunk_t *hunk;
2802 prop_patch_target_t *prop_target = svn_hash_gets(target->prop_targets,
2803 SVN_PROP_EXECUTABLE);
2805 if (patch->new_executable_bit == svn_tristate_true)
2806 SVN_ERR(svn_diff_hunk__create_adds_single_line(
2808 SVN_PROP_EXECUTABLE_VALUE,
2813 SVN_ERR(svn_diff_hunk__create_deletes_single_line(
2815 SVN_PROP_EXECUTABLE_VALUE,
2820 /* Derive a hunk_info from hunk. */
2821 SVN_ERR(get_hunk_info(&hi, target, prop_target->content,
2822 hunk, 0 /* fuzz */, 0 /* previous_offset */,
2824 TRUE /* is_prop_hunk */,
2825 cancel_func, cancel_baton,
2826 result_pool, iterpool));
2827 APR_ARRAY_PUSH(prop_target->content->hunks, hunk_info_t *) = hi;
2830 if (patch->new_symlink_bit != svn_tristate_unknown
2831 && patch->new_symlink_bit != patch->old_symlink_bit
2832 && svn_hash_gets(target->prop_targets, SVN_PROP_SPECIAL)
2833 && !svn_hash_gets(patch->prop_patches, SVN_PROP_SPECIAL))
2836 svn_diff_hunk_t *hunk;
2838 prop_patch_target_t *prop_target = svn_hash_gets(target->prop_targets,
2841 if (patch->new_symlink_bit == svn_tristate_true)
2843 SVN_ERR(svn_diff_hunk__create_adds_single_line(
2845 SVN_PROP_SPECIAL_VALUE,
2849 target->is_special = TRUE;
2853 SVN_ERR(svn_diff_hunk__create_deletes_single_line(
2855 SVN_PROP_SPECIAL_VALUE,
2859 target->is_special = FALSE;
2862 /* Derive a hunk_info from hunk. */
2863 SVN_ERR(get_hunk_info(&hi, target, prop_target->content,
2864 hunk, 0 /* fuzz */, 0 /* previous_offset */,
2866 TRUE /* is_prop_hunk */,
2867 cancel_func, cancel_baton,
2868 result_pool, iterpool));
2869 APR_ARRAY_PUSH(prop_target->content->hunks, hunk_info_t *) = hi;
2872 /* When the node is deleted or does not exist after the patch is applied
2873 we should reject a few more property hunks that can't be applied even
2874 though the source matched */
2876 || (!target->added &&
2877 (target->locally_deleted || target->db_kind == svn_node_none)))
2879 for (hash_index = apr_hash_first(scratch_pool, target->prop_targets);
2881 hash_index = apr_hash_next(hash_index))
2883 prop_patch_target_t *prop_target = apr_hash_this_val(hash_index);
2885 if (prop_target->operation == svn_diff_op_deleted)
2888 for (i = 0; i < prop_target->content->hunks->nelts; i++)
2892 hi = APR_ARRAY_IDX(prop_target->content->hunks, i, hunk_info_t*);
2894 if (hi->already_applied || hi->rejected)
2898 hi->rejected = TRUE;
2899 prop_target->skipped = TRUE;
2901 if (!target->deleted && !target->added)
2902 target->skipped = TRUE;
2908 /* Apply or reject property hunks. */
2910 prop_targets = svn_sort__hash(target->prop_targets,
2911 svn_sort_compare_items_lexically,
2913 for (i = 0; i < prop_targets->nelts; i++)
2915 svn_sort__item_t item = APR_ARRAY_IDX(prop_targets, i, svn_sort__item_t);
2916 prop_patch_target_t *prop_target = item.value;
2917 svn_boolean_t applied_one = FALSE;
2920 for (j = 0; j < prop_target->content->hunks->nelts; j++)
2924 svn_pool_clear(iterpool);
2926 hi = APR_ARRAY_IDX(prop_target->content->hunks, j,
2928 if (hi->already_applied)
2930 target->had_prop_already_applied = TRUE;
2933 else if (hi->rejected)
2934 SVN_ERR(reject_hunk(target, prop_target->content, hi->hunk,
2939 SVN_ERR(apply_hunk(target, prop_target->content, hi,
2947 prop_target->skipped = TRUE;
2949 if (applied_one && prop_target->content->existed)
2951 /* Copy any remaining lines to target. */
2952 SVN_ERR(copy_lines_to_target(prop_target->content, 0,
2954 if (! prop_target->content->eof)
2956 /* We could not copy the entire target property to the
2957 * temporary stream, and would truncate the target if we
2958 * copied the temporary stream on top of it. Skip this target. */
2959 prop_target->skipped = TRUE;
2964 svn_pool_destroy(iterpool);
2966 if (!target->is_symlink)
2968 /* Now close files we don't need any longer to get their contents
2970 * But we're not closing the reject file -- it still needed and
2971 * will be closed later in write_out_rejected_hunks(). */
2972 if (target->kind_on_disk == svn_node_file)
2973 SVN_ERR(svn_io_file_close(target->file, scratch_pool));
2976 SVN_ERR(svn_io_file_close(target->patched_file, scratch_pool));
2978 *patch_target = target;
2980 return SVN_NO_ERROR;
2983 /* Try to create missing parent directories for TARGET in the working copy
2984 * rooted at ABS_WC_PATH, and add the parents to version control.
2985 * If the parents cannot be created, mark the target as skipped.
2987 * In dry run mode record missing parents in ALREADY_ADDED
2989 * Use client context CTX. If DRY_RUN is true, do not create missing
2990 * parents but issue notifications only.
2991 * Use SCRATCH_POOL for temporary allocations. */
2992 static svn_error_t *
2993 create_missing_parents(patch_target_t *target,
2994 const char *abs_wc_path,
2995 svn_client_ctx_t *ctx,
2996 svn_boolean_t dry_run,
2997 apr_array_header_t *targets_info,
2998 apr_pool_t *scratch_pool)
3000 const char *local_abspath;
3001 apr_array_header_t *components;
3002 int present_components;
3004 apr_pool_t *iterpool;
3006 /* Check if we can safely create the target's parent. */
3007 local_abspath = abs_wc_path;
3008 components = svn_path_decompose(target->local_relpath, scratch_pool);
3009 present_components = 0;
3010 iterpool = svn_pool_create(scratch_pool);
3011 for (i = 0; i < components->nelts - 1; i++)
3013 const char *component;
3014 svn_node_kind_t wc_kind, disk_kind;
3016 svn_pool_clear(iterpool);
3018 component = APR_ARRAY_IDX(components, i, const char *);
3019 local_abspath = svn_dirent_join(local_abspath, component, scratch_pool);
3021 SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, local_abspath,
3022 FALSE, TRUE, iterpool));
3024 SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, iterpool));
3026 if (disk_kind == svn_node_file || wc_kind == svn_node_file)
3028 /* on-disk files and missing files are obstructions */
3029 target->skipped = TRUE;
3032 else if (disk_kind == svn_node_dir)
3034 if (wc_kind == svn_node_dir)
3035 present_components++;
3038 target->skipped = TRUE;
3042 else if (wc_kind != svn_node_none)
3044 /* Node is missing */
3045 target->skipped = TRUE;
3050 /* It's not a file, it's not a dir...
3055 if (! target->skipped)
3057 local_abspath = abs_wc_path;
3058 for (i = 0; i < present_components; i++)
3060 const char *component;
3061 component = APR_ARRAY_IDX(components, i, const char *);
3062 local_abspath = svn_dirent_join(local_abspath,
3063 component, scratch_pool);
3066 if (!dry_run && present_components < components->nelts - 1)
3067 SVN_ERR(svn_io_make_dir_recursively(
3070 svn_relpath_dirname(target->local_relpath,
3075 for (i = present_components; i < components->nelts - 1; i++)
3077 const char *component;
3078 patch_target_info_t *pti;
3080 svn_pool_clear(iterpool);
3082 if (ctx->cancel_func)
3083 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
3085 component = APR_ARRAY_IDX(components, i, const char *);
3086 local_abspath = svn_dirent_join(local_abspath, component,
3089 if (target_is_added(targets_info, local_abspath, iterpool))
3092 pti = apr_pcalloc(targets_info->pool, sizeof(*pti));
3094 pti->local_abspath = apr_pstrdup(targets_info->pool,
3098 APR_ARRAY_PUSH(targets_info, patch_target_info_t *) = pti;
3102 if (ctx->notify_func2)
3104 /* Just do notification. */
3105 svn_wc_notify_t *notify;
3106 notify = svn_wc_create_notify(local_abspath,
3109 notify->kind = svn_node_dir;
3110 ctx->notify_func2(ctx->notify_baton2, notify,
3116 /* Create the missing component and add it
3117 * to version control. Allow cancellation since we
3118 * have not modified the working copy yet for this
3120 SVN_ERR(svn_wc_add_from_disk3(ctx->wc_ctx, local_abspath,
3122 FALSE /* skip checks */,
3123 ctx->notify_func2, ctx->notify_baton2,
3129 svn_pool_destroy(iterpool);
3130 return SVN_NO_ERROR;
3133 /* Install a patched TARGET into the working copy at ABS_WC_PATH.
3134 * Use client context CTX to retrieve WC_CTX, and possibly doing
3137 * Pass on ALREADY_ADDED to allow recording already added ancestors
3140 * If DRY_RUN is TRUE, don't modify the working copy.
3141 * Do temporary allocations in POOL. */
3142 static svn_error_t *
3143 install_patched_target(patch_target_t *target, const char *abs_wc_path,
3144 svn_client_ctx_t *ctx, svn_boolean_t dry_run,
3145 apr_array_header_t *targets_info,
3148 if (target->deleted)
3152 /* Schedule the target for deletion. Suppress
3153 * notification, we'll do it manually in a minute
3154 * because we also need to notify during dry-run.
3155 * Also suppress cancellation, because we'd rather
3156 * notify about what we did before aborting. */
3157 SVN_ERR(svn_wc_delete4(ctx->wc_ctx, target->local_abspath,
3158 FALSE /* keep_local */, FALSE,
3159 ctx->cancel_func, ctx->cancel_baton,
3160 NULL, NULL /* notify */,
3166 svn_node_kind_t parent_db_kind;
3169 const char *parent_abspath;
3171 parent_abspath = svn_dirent_dirname(target->local_abspath,
3173 /* If the target's parent directory does not yet exist
3174 * we need to create it before we can copy the patched
3175 * result in place. */
3176 SVN_ERR(svn_wc_read_kind2(&parent_db_kind, ctx->wc_ctx,
3177 parent_abspath, FALSE, FALSE, pool));
3179 /* We can't add targets under nodes scheduled for delete, so add
3180 a new directory if needed. */
3181 if (parent_db_kind == svn_node_dir
3182 || parent_db_kind == svn_node_file)
3184 if (parent_db_kind != svn_node_dir)
3185 target->skipped = TRUE;
3188 svn_node_kind_t disk_kind;
3190 SVN_ERR(svn_io_check_path(parent_abspath, &disk_kind, pool));
3191 if (disk_kind != svn_node_dir)
3192 target->skipped = TRUE;
3196 SVN_ERR(create_missing_parents(target, abs_wc_path, ctx,
3197 dry_run, targets_info, pool));
3202 svn_node_kind_t wc_kind;
3204 /* The target should exist */
3205 SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx,
3206 target->local_abspath,
3207 FALSE, FALSE, pool));
3209 if (target->kind_on_disk == svn_node_none
3210 || wc_kind != target->kind_on_disk)
3212 target->skipped = TRUE;
3213 if (wc_kind != target->kind_on_disk)
3214 target->obstructed = TRUE;
3218 if (! dry_run && ! target->skipped)
3220 if (target->is_special)
3222 svn_stream_t *stream;
3223 svn_stream_t *patched_stream;
3225 SVN_ERR(svn_stream_open_readonly(&patched_stream,
3226 target->patched_path,
3228 SVN_ERR(svn_subst_create_specialfile(&stream,
3229 target->local_abspath,
3231 if (target->git_symlink_format)
3232 SVN_ERR(svn_stream_puts(stream, "link "));
3233 SVN_ERR(svn_stream_copy3(patched_stream, stream,
3234 ctx->cancel_func, ctx->cancel_baton,
3239 svn_boolean_t repair_eol;
3241 /* Copy the patched file on top of the target file.
3242 * Always expand keywords in the patched file, but repair EOL
3243 * only if svn:eol-style dictates a particular style. */
3244 repair_eol = (target->content->eol_style ==
3245 svn_subst_eol_style_fixed ||
3246 target->content->eol_style ==
3247 svn_subst_eol_style_native);
3249 SVN_ERR(svn_subst_copy_and_translate4(
3250 target->patched_path,
3251 target->move_target_abspath
3252 ? target->move_target_abspath
3253 : target->local_abspath,
3254 target->content->eol_str, repair_eol,
3255 target->content->keywords,
3256 TRUE /* expand */, FALSE /* special */,
3257 ctx->cancel_func, ctx->cancel_baton, pool));
3262 /* The target file didn't exist previously,
3263 * so add it to version control.
3264 * Suppress notification, we'll do that later (and also
3265 * during dry-run). Don't allow cancellation because
3266 * we'd rather notify about what we did before aborting. */
3267 SVN_ERR(svn_wc_add_from_disk3(ctx->wc_ctx, target->local_abspath,
3269 FALSE /* skip checks */,
3273 /* Restore the target's executable bit if necessary. */
3274 SVN_ERR(svn_io_set_file_executable(target->move_target_abspath
3275 ? target->move_target_abspath
3276 : target->local_abspath,
3280 if (target->move_target_abspath)
3282 /* ### Copying the patched content to the move target location,
3283 * performing the move in meta-data, and removing the file at
3284 * the move source should be one atomic operation. */
3286 /* ### Create missing parents. */
3288 /* Perform the move in meta-data. */
3289 SVN_ERR(svn_wc__move2(ctx->wc_ctx,
3290 target->local_abspath,
3291 target->move_target_abspath,
3292 TRUE, /* metadata_only */
3293 FALSE, /* allow_mixed_revisions */
3294 ctx->cancel_func, ctx->cancel_baton,
3298 /* Delete the patch target's old location from disk. */
3299 SVN_ERR(svn_io_remove_file2(target->local_abspath, FALSE, pool));
3304 return SVN_NO_ERROR;
3307 /* Write out rejected hunks, if any, to TARGET->REJECT_PATH. If DRY_RUN is
3308 * TRUE, don't modify the working copy.
3309 * Do temporary allocations in POOL.
3311 static svn_error_t *
3312 write_out_rejected_hunks(patch_target_t *target,
3313 const char *root_abspath,
3314 svn_boolean_t dry_run,
3315 apr_pool_t *scratch_pool)
3317 if (! dry_run && (target->had_rejects || target->had_prop_rejects))
3319 /* Write out rejected hunks, if any. */
3320 apr_file_t *reject_file;
3323 err = svn_io_open_uniquely_named(&reject_file, NULL,
3324 svn_dirent_dirname(target->local_abspath,
3326 svn_dirent_basename(
3327 target->local_abspath,
3330 svn_io_file_del_none,
3331 scratch_pool, scratch_pool);
3332 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
3334 /* The hunk applies to a file in a directory which does not exist.
3335 * Put the reject file into the working copy root instead. */
3336 svn_error_clear(err);
3337 SVN_ERR(svn_io_open_uniquely_named(&reject_file, NULL,
3339 svn_dirent_basename(
3340 target->local_abspath,
3343 svn_io_file_del_none,
3344 scratch_pool, scratch_pool));
3349 SVN_ERR(svn_stream_reset(target->reject_stream));
3351 /* svn_stream_copy3() closes the files for us */
3352 SVN_ERR(svn_stream_copy3(target->reject_stream,
3353 svn_stream_from_aprfile2(reject_file, FALSE,
3355 NULL, NULL, scratch_pool));
3356 /* ### TODO mark file as conflicted. */
3359 SVN_ERR(svn_stream_close(target->reject_stream));
3361 return SVN_NO_ERROR;
3364 /* Install the patched properties for TARGET. Use client context CTX to
3365 * retrieve WC_CTX. If DRY_RUN is TRUE, don't modify the working copy.
3366 * Do temporary allocations in SCRATCH_POOL. */
3367 static svn_error_t *
3368 install_patched_prop_targets(patch_target_t *target,
3369 svn_client_ctx_t *ctx, svn_boolean_t dry_run,
3370 apr_pool_t *scratch_pool)
3372 apr_hash_index_t *hi;
3373 apr_pool_t *iterpool;
3374 const char *local_abspath;
3376 /* Apply properties to a move target if there is one */
3377 if (target->move_target_abspath)
3378 local_abspath = target->move_target_abspath;
3380 local_abspath = target->local_abspath;
3382 iterpool = svn_pool_create(scratch_pool);
3384 for (hi = apr_hash_first(scratch_pool, target->prop_targets);
3386 hi = apr_hash_next(hi))
3388 prop_patch_target_t *prop_target = apr_hash_this_val(hi);
3389 const svn_string_t *prop_val;
3392 svn_pool_clear(iterpool);
3394 if (ctx->cancel_func)
3395 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
3397 if (prop_target->skipped)
3400 /* For a deleted prop we only set the value to NULL. */
3401 if (prop_target->operation == svn_diff_op_deleted)
3404 SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath,
3405 prop_target->name, NULL, svn_depth_empty,
3406 TRUE /* skip_checks */,
3407 NULL /* changelist_filter */,
3408 NULL, NULL /* cancellation */,
3409 NULL, NULL /* notification */,
3414 /* Attempt to set the property, and reject all hunks if this
3415 fails. If the property had a non-empty value, but now has
3416 an empty one, we'll just delete the property altogether. */
3417 if (prop_target->value && prop_target->value->len
3418 && prop_target->patched_value && !prop_target->patched_value->len)
3421 prop_val = svn_stringbuf__morph_into_string(prop_target->patched_value);
3425 const svn_string_t *canon_propval;
3427 err = svn_wc_canonicalize_svn_prop(&canon_propval,
3429 prop_val, local_abspath,
3431 TRUE, /* ### Skipping checks */
3437 err = svn_wc_prop_set4(ctx->wc_ctx, local_abspath,
3438 prop_target->name, prop_val, svn_depth_empty,
3439 TRUE /* skip_checks */,
3440 NULL /* changelist_filter */,
3441 NULL, NULL /* cancellation */,
3442 NULL, NULL /* notification */,
3448 /* ### The errors which svn_wc_canonicalize_svn_prop() will
3449 * ### return aren't documented. */
3450 if (err->apr_err == SVN_ERR_ILLEGAL_TARGET ||
3451 err->apr_err == SVN_ERR_NODE_UNEXPECTED_KIND ||
3452 err->apr_err == SVN_ERR_IO_UNKNOWN_EOL ||
3453 err->apr_err == SVN_ERR_BAD_MIME_TYPE ||
3454 err->apr_err == SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION)
3458 svn_error_clear(err);
3460 for (i = 0; i < prop_target->content->hunks->nelts; i++)
3462 hunk_info_t *hunk_info;
3464 hunk_info = APR_ARRAY_IDX(prop_target->content->hunks,
3466 hunk_info->rejected = TRUE;
3467 SVN_ERR(reject_hunk(target, prop_target->content,
3468 hunk_info->hunk, prop_target->name,
3473 return svn_error_trace(err);
3478 svn_pool_destroy(iterpool);
3480 return SVN_NO_ERROR;
3483 /* Baton for can_delete_callback */
3484 struct can_delete_baton_t
3486 svn_boolean_t must_keep;
3487 const apr_array_header_t *targets_info;
3488 const char *local_abspath;
3491 /* Implements svn_wc_status_func4_t. */
3492 static svn_error_t *
3493 can_delete_callback(void *baton,
3494 const char *abspath,
3495 const svn_wc_status3_t *status,
3498 struct can_delete_baton_t *cb = baton;
3501 switch(status->node_status)
3503 case svn_wc_status_none:
3504 case svn_wc_status_deleted:
3505 return SVN_NO_ERROR;
3508 if (! strcmp(cb->local_abspath, abspath))
3509 return SVN_NO_ERROR; /* Only interested in descendants */
3511 for (i = 0; i < cb->targets_info->nelts; i++)
3513 const patch_target_info_t *target_info =
3514 APR_ARRAY_IDX(cb->targets_info, i, const patch_target_info_t *);
3516 if (! strcmp(target_info->local_abspath, abspath))
3518 if (target_info->deleted)
3519 return SVN_NO_ERROR;
3521 break; /* Cease invocation; must keep */
3525 cb->must_keep = TRUE;
3527 return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL);
3531 static svn_error_t *
3532 check_ancestor_delete(const char *deleted_target,
3533 apr_array_header_t *targets_info,
3534 const char *apply_root,
3535 svn_boolean_t dry_run,
3536 svn_client_ctx_t *ctx,
3537 apr_pool_t *result_pool,
3538 apr_pool_t *scratch_pool)
3540 struct can_delete_baton_t cb;
3542 apr_array_header_t *ignores;
3543 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
3545 const char *dir_abspath = svn_dirent_dirname(deleted_target, scratch_pool);
3547 SVN_ERR(svn_wc_get_default_ignores(&ignores, ctx->config, scratch_pool));
3549 while (svn_dirent_is_child(apply_root, dir_abspath, iterpool))
3551 svn_pool_clear(iterpool);
3553 cb.local_abspath = dir_abspath;
3554 cb.must_keep = FALSE;
3555 cb.targets_info = targets_info;
3557 err = svn_wc_walk_status(ctx->wc_ctx, dir_abspath, svn_depth_infinity,
3558 TRUE, FALSE, FALSE, ignores,
3559 can_delete_callback, &cb,
3560 ctx->cancel_func, ctx->cancel_baton,
3565 if (err->apr_err != SVN_ERR_CEASE_INVOCATION)
3566 return svn_error_trace(err);
3568 svn_error_clear(err);
3578 SVN_ERR(svn_wc_delete4(ctx->wc_ctx, dir_abspath, FALSE, FALSE,
3579 ctx->cancel_func, ctx->cancel_baton,
3585 patch_target_info_t *pti = apr_pcalloc(result_pool, sizeof(*pti));
3587 pti->local_abspath = apr_pstrdup(result_pool, dir_abspath);
3588 pti->deleted = TRUE;
3590 APR_ARRAY_PUSH(targets_info, patch_target_info_t *) = pti;
3594 if (ctx->notify_func2)
3596 svn_wc_notify_t *notify;
3598 notify = svn_wc_create_notify(dir_abspath, svn_wc_notify_delete,
3600 notify->kind = svn_node_dir;
3602 ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
3605 /* And check if we must also delete the parent */
3606 dir_abspath = svn_dirent_dirname(dir_abspath, scratch_pool);
3609 svn_pool_destroy(iterpool);
3611 return SVN_NO_ERROR;
3614 /* This function is the main entry point into the patch code. */
3615 static svn_error_t *
3616 apply_patches(/* The path to the patch file. */
3617 const char *patch_abspath,
3618 /* The abspath to the working copy the patch should be applied to. */
3619 const char *root_abspath,
3620 /* Indicates whether we're doing a dry run. */
3621 svn_boolean_t dry_run,
3622 /* Number of leading components to strip from patch target paths. */
3624 /* Whether to apply the patch in reverse. */
3625 svn_boolean_t reverse,
3626 /* Whether to ignore whitespace when matching context lines. */
3627 svn_boolean_t ignore_whitespace,
3628 /* As in svn_client_patch(). */
3629 svn_boolean_t remove_tempfiles,
3630 /* As in svn_client_patch(). */
3631 svn_client_patch_func_t patch_func,
3633 /* The client context. */
3634 svn_client_ctx_t *ctx,
3635 apr_pool_t *scratch_pool)
3638 apr_pool_t *iterpool;
3639 svn_patch_file_t *patch_file;
3640 apr_array_header_t *targets_info;
3642 /* Try to open the patch file. */
3643 SVN_ERR(svn_diff_open_patch_file(&patch_file, patch_abspath, scratch_pool));
3645 /* Apply patches. */
3646 targets_info = apr_array_make(scratch_pool, 0,
3647 sizeof(patch_target_info_t *));
3648 iterpool = svn_pool_create(scratch_pool);
3651 svn_pool_clear(iterpool);
3653 if (ctx->cancel_func)
3654 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
3656 SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file,
3657 reverse, ignore_whitespace,
3658 iterpool, iterpool));
3661 patch_target_t *target;
3662 svn_boolean_t filtered = FALSE;
3664 SVN_ERR(apply_one_patch(&target, patch, root_abspath,
3665 ctx->wc_ctx, strip_count,
3666 ignore_whitespace, remove_tempfiles,
3668 ctx->cancel_func, ctx->cancel_baton,
3669 iterpool, iterpool));
3671 if (!target->skipped && patch_func)
3673 SVN_ERR(patch_func(patch_baton, &filtered,
3674 target->canon_path_from_patchfile,
3675 target->patched_path, target->reject_path,
3681 /* Save info we'll still need when we're done patching. */
3682 patch_target_info_t *target_info =
3683 apr_pcalloc(scratch_pool, sizeof(patch_target_info_t));
3684 target_info->local_abspath = apr_pstrdup(scratch_pool,
3685 target->local_abspath);
3686 target_info->deleted = target->deleted;
3687 target_info->added = target->added;
3689 if (! target->skipped)
3691 if (target->has_text_changes
3693 || target->move_target_abspath
3695 SVN_ERR(install_patched_target(target, root_abspath,
3697 targets_info, iterpool));
3699 if (target->has_prop_changes && (!target->deleted))
3700 SVN_ERR(install_patched_prop_targets(target, ctx,
3701 dry_run, iterpool));
3703 SVN_ERR(write_out_rejected_hunks(target, root_abspath,
3704 dry_run, iterpool));
3706 APR_ARRAY_PUSH(targets_info,
3707 patch_target_info_t *) = target_info;
3709 SVN_ERR(send_patch_notification(target, ctx, iterpool));
3711 if (target->deleted && !target->skipped)
3713 SVN_ERR(check_ancestor_delete(target_info->local_abspath,
3714 targets_info, root_abspath,
3716 scratch_pool, iterpool));
3723 SVN_ERR(svn_diff_close_patch_file(patch_file, iterpool));
3724 svn_pool_destroy(iterpool);
3726 return SVN_NO_ERROR;
3730 svn_client_patch(const char *patch_abspath,
3731 const char *wc_dir_abspath,
3732 svn_boolean_t dry_run,
3734 svn_boolean_t reverse,
3735 svn_boolean_t ignore_whitespace,
3736 svn_boolean_t remove_tempfiles,
3737 svn_client_patch_func_t patch_func,
3739 svn_client_ctx_t *ctx,
3740 apr_pool_t *scratch_pool)
3742 svn_node_kind_t kind;
3744 if (strip_count < 0)
3745 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
3746 _("strip count must be positive"));
3748 if (svn_path_is_url(wc_dir_abspath))
3749 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3750 _("'%s' is not a local path"),
3751 svn_dirent_local_style(wc_dir_abspath,
3754 SVN_ERR(svn_io_check_path(patch_abspath, &kind, scratch_pool));
3755 if (kind == svn_node_none)
3756 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3757 _("'%s' does not exist"),
3758 svn_dirent_local_style(patch_abspath,
3760 if (kind != svn_node_file)
3761 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3762 _("'%s' is not a file"),
3763 svn_dirent_local_style(patch_abspath,
3766 SVN_ERR(svn_io_check_path(wc_dir_abspath, &kind, scratch_pool));
3767 if (kind == svn_node_none)
3768 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3769 _("'%s' does not exist"),
3770 svn_dirent_local_style(wc_dir_abspath,
3772 if (kind != svn_node_dir)
3773 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3774 _("'%s' is not a directory"),
3775 svn_dirent_local_style(wc_dir_abspath,
3778 SVN_WC__CALL_WITH_WRITE_LOCK(
3779 apply_patches(patch_abspath, wc_dir_abspath, dry_run, strip_count,
3780 reverse, ignore_whitespace, remove_tempfiles,
3781 patch_func, patch_baton, ctx, scratch_pool),
3782 ctx->wc_ctx, wc_dir_abspath, FALSE /* lock_anchor */, scratch_pool);
3783 return SVN_NO_ERROR;