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