Update from subversion 1.9.2 to 1.9.4.
[freebsd-src.git] / contrib / subversion / subversion / libsvn_client / patch.c
blob6d8d0a4632c882056a021e25da6c556aacce6976
1 /*
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
20 * under the License.
21 * ====================================================================
24 /* ==================================================================== */
28 /*** Includes. ***/
30 #include <apr_hash.h>
31 #include <apr_fnmatch.h>
32 #include "svn_client.h"
33 #include "svn_dirent_uri.h"
34 #include "svn_diff.h"
35 #include "svn_hash.h"
36 #include "svn_io.h"
37 #include "svn_path.h"
38 #include "svn_pools.h"
39 #include "svn_props.h"
40 #include "svn_sorts.h"
41 #include "svn_subst.h"
42 #include "svn_wc.h"
43 #include "client.h"
45 #include "svn_private_config.h"
46 #include "private/svn_eol_private.h"
47 #include "private/svn_wc_private.h"
48 #include "private/svn_dep_compat.h"
49 #include "private/svn_string_private.h"
50 #include "private/svn_subr_private.h"
51 #include "private/svn_sorts_private.h"
53 typedef struct hunk_info_t {
54 /* The hunk. */
55 svn_diff_hunk_t *hunk;
57 /* The line where the hunk matched in the target file. */
58 svn_linenum_t matched_line;
60 /* Whether this hunk has been rejected. */
61 svn_boolean_t rejected;
63 /* Whether this hunk has already been applied (either manually
64 * or by an earlier run of patch). */
65 svn_boolean_t already_applied;
67 /* The fuzz factor used when matching this hunk, i.e. how many
68 * lines of leading and trailing context to ignore during matching. */
69 svn_linenum_t fuzz;
70 } hunk_info_t;
72 /* A struct carrying information related to the patched and unpatched
73 * content of a target, be it a property or the text of a file. */
74 typedef struct target_content_t {
75 /* Indicates whether unpatched content existed prior to patching. */
76 svn_boolean_t existed;
78 /* The line last read from the unpatched content. */
79 svn_linenum_t current_line;
81 /* The EOL-style of the unpatched content. Either 'none', 'fixed',
82 * or 'native'. See the documentation of svn_subst_eol_style_t. */
83 svn_subst_eol_style_t eol_style;
85 /* If the EOL_STYLE above is not 'none', this is the EOL string
86 * corresponding to the EOL-style. Else, it is the EOL string the
87 * last line read from the target file was using. */
88 const char *eol_str;
90 /* An array containing apr_off_t offsets marking the beginning of
91 * each line in the unpatched content. */
92 apr_array_header_t *lines;
94 /* An array containing hunk_info_t structures for hunks already matched. */
95 apr_array_header_t *hunks;
97 /* True if end-of-file was reached while reading from the unpatched
98 * content. */
99 svn_boolean_t eof;
101 /* The keywords of the target. They will be contracted when reading
102 * unpatched content and expanded when writing patched content.
103 * When patching properties this hash is always empty. */
104 apr_hash_t *keywords;
106 /* A callback, with an associated baton, to read a line of unpatched
107 * content. */
108 svn_error_t *(*readline)(void *baton, svn_stringbuf_t **line,
109 const char **eol_str, svn_boolean_t *eof,
110 apr_pool_t *result_pool, apr_pool_t *scratch_pool);
111 void *read_baton;
113 /* A callback to get the current byte offset within the unpatched
114 * content. Uses the read baton. */
115 svn_error_t * (*tell)(void *baton, apr_off_t *offset,
116 apr_pool_t *scratch_pool);
118 /* A callback to seek to an offset within the unpatched content.
119 * Uses the read baton. */
120 svn_error_t * (*seek)(void *baton, apr_off_t offset,
121 apr_pool_t *scratch_pool);
123 /* A callback to write data to the patched content, with an
124 * associated baton. */
125 svn_error_t * (*write)(void *baton, const char *buf, apr_size_t len,
126 apr_pool_t *scratch_pool);
127 void *write_baton;
129 } target_content_t;
131 typedef struct prop_patch_target_t {
133 /* The name of the property */
134 const char *name;
136 /* The property value. This is NULL in case the property did not exist
137 * prior to patch application (see also CONTENT->existed).
138 * Note that the patch implementation does not support binary properties,
139 * so this string is not expected to contain embedded NUL characters. */
140 const svn_string_t *value;
142 /* The patched property value.
143 * This is equivalent to the target, except that in appropriate
144 * places it contains the modified text as it appears in the patch file. */
145 svn_stringbuf_t *patched_value;
147 /* All information that is specific to the content of the property. */
148 target_content_t *content;
150 /* Represents the operation performed on the property. It can be added,
151 * deleted or modified.
152 * ### Should we use flags instead since we're not using all enum values? */
153 svn_diff_operation_kind_t operation;
155 /* ### Here we'll add flags telling if the prop was added, deleted,
156 * ### had_rejects, had_local_mods prior to patching and so on. */
157 } prop_patch_target_t;
159 typedef struct patch_target_t {
160 /* The target path as it appeared in the patch file,
161 * but in canonicalised form. */
162 const char *canon_path_from_patchfile;
164 /* The target path, relative to the working copy directory the
165 * patch is being applied to. A patch strip count applies to this
166 * and only this path. This is never NULL. */
167 const char *local_relpath;
169 /* The absolute path of the target on the filesystem.
170 * Any symlinks the path from the patch file may contain are resolved.
171 * Is not always known, so it may be NULL. */
172 const char *local_abspath;
174 /* The target file, read-only. This is NULL in case the target
175 * file did not exist prior to patch application (see also
176 * CONTENT->existed). */
177 apr_file_t *file;
179 /* The target file is a symlink */
180 svn_boolean_t is_symlink;
182 /* The patched file.
183 * This is equivalent to the target, except that in appropriate
184 * places it contains the modified text as it appears in the patch file.
185 * The data in this file is written in repository-normal form.
186 * EOL transformation and keyword contraction is performed when the
187 * patched result is installed in the working copy. */
188 apr_file_t *patched_file;
190 /* Path to the patched file. */
191 const char *patched_path;
193 /* Hunks that are rejected will be written to this file. */
194 apr_file_t *reject_file;
196 /* Path to the reject file. */
197 const char *reject_path;
199 /* The node kind of the target as found in WC-DB prior
200 * to patch application. */
201 svn_node_kind_t db_kind;
203 /* The target's kind on disk prior to patch application. */
204 svn_node_kind_t kind_on_disk;
206 /* True if the target was locally deleted prior to patching. */
207 svn_boolean_t locally_deleted;
209 /* True if the target had to be skipped for some reason. */
210 svn_boolean_t skipped;
212 /* True if at least one hunk was rejected. */
213 svn_boolean_t had_rejects;
215 /* True if at least one property hunk was rejected. */
216 svn_boolean_t had_prop_rejects;
218 /* True if the target file had local modifications before the
219 * patch was applied to it. */
220 svn_boolean_t local_mods;
222 /* True if the target was added by the patch, which means that it did
223 * not exist on disk before patching and has content after patching. */
224 svn_boolean_t added;
226 /* True if the target ended up being deleted by the patch. */
227 svn_boolean_t deleted;
229 /* True if the target ended up being replaced by the patch
230 * (i.e. a new file was added on top locally deleted node). */
231 svn_boolean_t replaced;
233 /* Set if the target is supposed to be moved by the patch.
234 * This applies to --git diffs which carry "rename from/to" headers. */
235 const char *move_target_abspath;
237 /* True if the target has the executable bit set. */
238 svn_boolean_t executable;
240 /* True if the patch changed the text of the target. */
241 svn_boolean_t has_text_changes;
243 /* True if the patch changed any of the properties of the target. */
244 svn_boolean_t has_prop_changes;
246 /* True if the patch contained a svn:special property. */
247 svn_boolean_t is_special;
249 /* All the information that is specific to the content of the target. */
250 target_content_t *content;
252 /* A hash table of prop_patch_target_t objects keyed by property names. */
253 apr_hash_t *prop_targets;
255 } patch_target_t;
258 /* A smaller struct containing a subset of patch_target_t.
259 * Carries the minimal amount of information we still need for a
260 * target after we're done patching it so we can free other resources. */
261 typedef struct patch_target_info_t {
262 const char *local_abspath;
263 svn_boolean_t deleted;
264 } patch_target_info_t;
267 /* Strip STRIP_COUNT components from the front of PATH, returning
268 * the result in *RESULT, allocated in RESULT_POOL.
269 * Do temporary allocations in SCRATCH_POOL. */
270 static svn_error_t *
271 strip_path(const char **result, const char *path, int strip_count,
272 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
274 int i;
275 apr_array_header_t *components;
276 apr_array_header_t *stripped;
278 components = svn_path_decompose(path, scratch_pool);
279 if (strip_count > components->nelts)
280 return svn_error_createf(SVN_ERR_CLIENT_PATCH_BAD_STRIP_COUNT, NULL,
281 _("Cannot strip %u components from '%s'"),
282 strip_count,
283 svn_dirent_local_style(path, scratch_pool));
285 stripped = apr_array_make(scratch_pool, components->nelts - strip_count,
286 sizeof(const char *));
287 for (i = strip_count; i < components->nelts; i++)
289 const char *component;
291 component = APR_ARRAY_IDX(components, i, const char *);
292 APR_ARRAY_PUSH(stripped, const char *) = component;
295 *result = svn_path_compose(stripped, result_pool);
297 return SVN_NO_ERROR;
300 /* Obtain KEYWORDS, EOL_STYLE and EOL_STR for LOCAL_ABSPATH.
301 * WC_CTX is a context for the working copy the patch is applied to.
302 * Use RESULT_POOL for allocations of fields in TARGET.
303 * Use SCRATCH_POOL for all other allocations. */
304 static svn_error_t *
305 obtain_eol_and_keywords_for_file(apr_hash_t **keywords,
306 svn_subst_eol_style_t *eol_style,
307 const char **eol_str,
308 svn_wc_context_t *wc_ctx,
309 const char *local_abspath,
310 apr_pool_t *result_pool,
311 apr_pool_t *scratch_pool)
313 apr_hash_t *props;
314 svn_string_t *keywords_val, *eol_style_val;
316 SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, local_abspath,
317 scratch_pool, scratch_pool));
318 keywords_val = svn_hash_gets(props, SVN_PROP_KEYWORDS);
319 if (keywords_val)
321 svn_revnum_t changed_rev;
322 apr_time_t changed_date;
323 const char *rev_str;
324 const char *author;
325 const char *url;
326 const char *repos_root_url;
327 const char *repos_relpath;
329 SVN_ERR(svn_wc__node_get_changed_info(&changed_rev,
330 &changed_date,
331 &author, wc_ctx,
332 local_abspath,
333 scratch_pool,
334 scratch_pool));
335 rev_str = apr_psprintf(scratch_pool, "%ld", changed_rev);
336 SVN_ERR(svn_wc__node_get_repos_info(NULL, &repos_relpath, &repos_root_url,
337 NULL,
338 wc_ctx, local_abspath,
339 scratch_pool, scratch_pool));
340 url = svn_path_url_add_component2(repos_root_url, repos_relpath,
341 scratch_pool);
343 SVN_ERR(svn_subst_build_keywords3(keywords,
344 keywords_val->data,
345 rev_str, url, repos_root_url,
346 changed_date,
347 author, result_pool));
350 eol_style_val = svn_hash_gets(props, SVN_PROP_EOL_STYLE);
351 if (eol_style_val)
353 svn_subst_eol_style_from_value(eol_style,
354 eol_str,
355 eol_style_val->data);
358 return SVN_NO_ERROR;
361 /* Resolve the exact path for a patch TARGET at path PATH_FROM_PATCHFILE,
362 * which is the path of the target as it appeared in the patch file.
363 * Put a canonicalized version of PATH_FROM_PATCHFILE into
364 * TARGET->CANON_PATH_FROM_PATCHFILE.
365 * WC_CTX is a context for the working copy the patch is applied to.
366 * If possible, determine TARGET->WC_PATH, TARGET->ABS_PATH, TARGET->KIND,
367 * TARGET->ADDED, and TARGET->PARENT_DIR_EXISTS.
368 * Indicate in TARGET->SKIPPED whether the target should be skipped.
369 * STRIP_COUNT specifies the number of leading path components
370 * which should be stripped from target paths in the patch.
371 * PROP_CHANGES_ONLY specifies whether the target path is allowed to have
372 * only property changes, and no content changes (in which case the target
373 * must be a directory).
374 * Use RESULT_POOL for allocations of fields in TARGET.
375 * Use SCRATCH_POOL for all other allocations. */
376 static svn_error_t *
377 resolve_target_path(patch_target_t *target,
378 const char *path_from_patchfile,
379 const char *wcroot_abspath,
380 int strip_count,
381 svn_boolean_t prop_changes_only,
382 svn_wc_context_t *wc_ctx,
383 apr_pool_t *result_pool,
384 apr_pool_t *scratch_pool)
386 const char *stripped_path;
387 svn_wc_status3_t *status;
388 svn_error_t *err;
389 svn_boolean_t under_root;
391 target->canon_path_from_patchfile = svn_dirent_internal_style(
392 path_from_patchfile, result_pool);
394 /* We allow properties to be set on the wc root dir. */
395 if (! prop_changes_only && target->canon_path_from_patchfile[0] == '\0')
397 /* An empty patch target path? What gives? Skip this. */
398 target->skipped = TRUE;
399 target->local_abspath = NULL;
400 target->local_relpath = "";
401 return SVN_NO_ERROR;
404 if (strip_count > 0)
405 SVN_ERR(strip_path(&stripped_path, target->canon_path_from_patchfile,
406 strip_count, result_pool, scratch_pool));
407 else
408 stripped_path = target->canon_path_from_patchfile;
410 if (svn_dirent_is_absolute(stripped_path))
412 target->local_relpath = svn_dirent_is_child(wcroot_abspath,
413 stripped_path,
414 result_pool);
416 if (! target->local_relpath)
418 /* The target path is either outside of the working copy
419 * or it is the working copy itself. Skip it. */
420 target->skipped = TRUE;
421 target->local_abspath = NULL;
422 target->local_relpath = stripped_path;
423 return SVN_NO_ERROR;
426 else
428 target->local_relpath = stripped_path;
431 /* Make sure the path is secure to use. We want the target to be inside
432 * of the working copy and not be fooled by symlinks it might contain. */
433 SVN_ERR(svn_dirent_is_under_root(&under_root,
434 &target->local_abspath, wcroot_abspath,
435 target->local_relpath, result_pool));
437 if (! under_root)
439 /* The target path is outside of the working copy. Skip it. */
440 target->skipped = TRUE;
441 target->local_abspath = NULL;
442 return SVN_NO_ERROR;
445 /* Skip things we should not be messing with. */
446 err = svn_wc_status3(&status, wc_ctx, target->local_abspath,
447 result_pool, scratch_pool);
448 if (err)
450 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
451 return svn_error_trace(err);
453 svn_error_clear(err);
455 target->locally_deleted = TRUE;
456 target->db_kind = svn_node_none;
457 status = NULL;
459 else if (status->node_status == svn_wc_status_ignored ||
460 status->node_status == svn_wc_status_unversioned ||
461 status->node_status == svn_wc_status_missing ||
462 status->node_status == svn_wc_status_obstructed ||
463 status->conflicted)
465 target->skipped = TRUE;
466 return SVN_NO_ERROR;
468 else if (status->node_status == svn_wc_status_deleted)
470 target->locally_deleted = TRUE;
473 if (status && (status->kind != svn_node_unknown))
474 target->db_kind = status->kind;
475 else
476 target->db_kind = svn_node_none;
478 SVN_ERR(svn_io_check_special_path(target->local_abspath,
479 &target->kind_on_disk, &target->is_symlink,
480 scratch_pool));
482 if (target->locally_deleted)
484 const char *moved_to_abspath;
486 SVN_ERR(svn_wc__node_was_moved_away(&moved_to_abspath, NULL,
487 wc_ctx, target->local_abspath,
488 result_pool, scratch_pool));
489 /* ### BUG: moved_to_abspath contains the target where the op-root was
490 ### moved to... not the target itself! */
491 if (moved_to_abspath)
493 target->local_abspath = moved_to_abspath;
494 target->local_relpath = svn_dirent_skip_ancestor(wcroot_abspath,
495 moved_to_abspath);
496 SVN_ERR_ASSERT(target->local_relpath &&
497 target->local_relpath[0] != '\0');
499 /* As far as we are concerned this target is not locally deleted. */
500 target->locally_deleted = FALSE;
502 SVN_ERR(svn_io_check_special_path(target->local_abspath,
503 &target->kind_on_disk,
504 &target->is_symlink,
505 scratch_pool));
507 else if (target->kind_on_disk != svn_node_none)
509 target->skipped = TRUE;
510 return SVN_NO_ERROR;
514 return SVN_NO_ERROR;
517 /* Baton for reading from properties. */
518 typedef struct prop_read_baton_t {
519 const svn_string_t *value;
520 apr_off_t offset;
521 } prop_read_baton_t;
523 /* Allocate *STRINGBUF in RESULT_POOL, and read into it one line from
524 * the unpatched property value accessed via BATON.
525 * Reading stops either after a line-terminator was found, or if
526 * the property value runs out in which case *EOF is set to TRUE.
527 * The line-terminator is not stored in *STRINGBUF.
529 * If the line is empty or could not be read, *line is set to NULL.
531 * The line-terminator is detected automatically and stored in *EOL
532 * if EOL is not NULL. If the end of the property value is reached
533 * and does not end with a newline character, and EOL is not NULL,
534 * *EOL is set to NULL.
536 * SCRATCH_POOL is used for temporary allocations.
538 static svn_error_t *
539 readline_prop(void *baton, svn_stringbuf_t **line, const char **eol_str,
540 svn_boolean_t *eof, apr_pool_t *result_pool,
541 apr_pool_t *scratch_pool)
543 prop_read_baton_t *b = (prop_read_baton_t *)baton;
544 svn_stringbuf_t *str = NULL;
545 const char *c;
546 svn_boolean_t found_eof;
548 if ((apr_uint64_t)b->offset >= (apr_uint64_t)b->value->len)
550 *eol_str = NULL;
551 *eof = TRUE;
552 *line = NULL;
553 return SVN_NO_ERROR;
556 /* Read bytes into STR up to and including, but not storing,
557 * the next EOL sequence. */
558 *eol_str = NULL;
559 found_eof = FALSE;
562 c = b->value->data + b->offset;
563 b->offset++;
565 if (*c == '\0')
567 found_eof = TRUE;
568 break;
570 else if (*c == '\n')
572 *eol_str = "\n";
574 else if (*c == '\r')
576 *eol_str = "\r";
577 if (*(c + 1) == '\n')
579 *eol_str = "\r\n";
580 b->offset++;
583 else
585 if (str == NULL)
586 str = svn_stringbuf_create_ensure(80, result_pool);
587 svn_stringbuf_appendbyte(str, *c);
590 if (*eol_str)
591 break;
593 while (c < b->value->data + b->value->len);
595 if (eof)
596 *eof = found_eof;
597 *line = str;
599 return SVN_NO_ERROR;
602 /* Return in *OFFSET the current byte offset for reading from the
603 * unpatched property value accessed via BATON.
604 * Use SCRATCH_POOL for temporary allocations. */
605 static svn_error_t *
606 tell_prop(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool)
608 prop_read_baton_t *b = (prop_read_baton_t *)baton;
609 *offset = b->offset;
610 return SVN_NO_ERROR;
613 /* Seek to the specified by OFFSET in the unpatched property value accessed
614 * via BATON. Use SCRATCH_POOL for temporary allocations. */
615 static svn_error_t *
616 seek_prop(void *baton, apr_off_t offset, apr_pool_t *scratch_pool)
618 prop_read_baton_t *b = (prop_read_baton_t *)baton;
619 b->offset = offset;
620 return SVN_NO_ERROR;
623 /* Write LEN bytes from BUF into the patched property value accessed
624 * via BATON. Use SCRATCH_POOL for temporary allocations. */
625 static svn_error_t *
626 write_prop(void *baton, const char *buf, apr_size_t len,
627 apr_pool_t *scratch_pool)
629 svn_stringbuf_t *patched_value = (svn_stringbuf_t *)baton;
630 svn_stringbuf_appendbytes(patched_value, buf, len);
631 return SVN_NO_ERROR;
634 /* Initialize a PROP_TARGET structure for PROP_NAME on the patch target
635 * at LOCAL_ABSPATH. OPERATION indicates the operation performed on the
636 * property. Use working copy context WC_CTX.
637 * Allocate results in RESULT_POOL.
638 * Use SCRATCH_POOL for temporary allocations. */
639 static svn_error_t *
640 init_prop_target(prop_patch_target_t **prop_target,
641 const char *prop_name,
642 svn_diff_operation_kind_t operation,
643 svn_wc_context_t *wc_ctx,
644 const char *local_abspath,
645 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
647 prop_patch_target_t *new_prop_target;
648 target_content_t *content;
649 const svn_string_t *value;
650 svn_error_t *err;
651 prop_read_baton_t *prop_read_baton;
653 content = apr_pcalloc(result_pool, sizeof(*content));
655 /* All other fields are FALSE or NULL due to apr_pcalloc(). */
656 content->current_line = 1;
657 content->eol_style = svn_subst_eol_style_none;
658 content->lines = apr_array_make(result_pool, 0, sizeof(apr_off_t));
659 content->hunks = apr_array_make(result_pool, 0, sizeof(hunk_info_t *));
660 content->keywords = apr_hash_make(result_pool);
662 new_prop_target = apr_pcalloc(result_pool, sizeof(*new_prop_target));
663 new_prop_target->name = apr_pstrdup(result_pool, prop_name);
664 new_prop_target->operation = operation;
665 new_prop_target->content = content;
667 err = svn_wc_prop_get2(&value, wc_ctx, local_abspath, prop_name,
668 result_pool, scratch_pool);
669 if (err)
671 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
673 svn_error_clear(err);
674 value = NULL;
676 else
677 return svn_error_trace(err);
679 content->existed = (value != NULL);
680 new_prop_target->value = value;
681 new_prop_target->patched_value = svn_stringbuf_create_empty(result_pool);
684 /* Wire up the read and write callbacks. */
685 prop_read_baton = apr_pcalloc(result_pool, sizeof(*prop_read_baton));
686 prop_read_baton->value = value;
687 prop_read_baton->offset = 0;
688 content->readline = readline_prop;
689 content->tell = tell_prop;
690 content->seek = seek_prop;
691 content->read_baton = prop_read_baton;
692 content->write = write_prop;
693 content->write_baton = new_prop_target->patched_value;
695 *prop_target = new_prop_target;
697 return SVN_NO_ERROR;
700 /* Allocate *STRINGBUF in RESULT_POOL, and read into it one line from
701 * the unpatched file content accessed via BATON.
702 * Reading stops either after a line-terminator was found,
703 * or if EOF is reached in which case *EOF is set to TRUE.
704 * The line-terminator is not stored in *STRINGBUF.
706 * If the line is empty or could not be read, *line is set to NULL.
708 * The line-terminator is detected automatically and stored in *EOL
709 * if EOL is not NULL. If EOF is reached and FILE does not end
710 * with a newline character, and EOL is not NULL, *EOL is set to NULL.
712 * SCRATCH_POOL is used for temporary allocations.
714 static svn_error_t *
715 readline_file(void *baton, svn_stringbuf_t **line, const char **eol_str,
716 svn_boolean_t *eof, apr_pool_t *result_pool,
717 apr_pool_t *scratch_pool)
719 apr_file_t *file = (apr_file_t *)baton;
720 svn_stringbuf_t *str = NULL;
721 apr_size_t numbytes;
722 char c;
723 svn_boolean_t found_eof;
725 /* Read bytes into STR up to and including, but not storing,
726 * the next EOL sequence. */
727 *eol_str = NULL;
728 numbytes = 1;
729 found_eof = FALSE;
730 while (!found_eof)
732 SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes,
733 &found_eof, scratch_pool));
734 if (numbytes != 1)
736 found_eof = TRUE;
737 break;
740 if (c == '\n')
742 *eol_str = "\n";
744 else if (c == '\r')
746 *eol_str = "\r";
748 if (!found_eof)
750 apr_off_t pos;
752 /* Check for "\r\n" by peeking at the next byte. */
753 pos = 0;
754 SVN_ERR(svn_io_file_seek(file, APR_CUR, &pos, scratch_pool));
755 SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes,
756 &found_eof, scratch_pool));
757 if (numbytes == 1 && c == '\n')
759 *eol_str = "\r\n";
761 else
763 /* Pretend we never peeked. */
764 SVN_ERR(svn_io_file_seek(file, APR_SET, &pos, scratch_pool));
765 found_eof = FALSE;
766 numbytes = 1;
770 else
772 if (str == NULL)
773 str = svn_stringbuf_create_ensure(80, result_pool);
774 svn_stringbuf_appendbyte(str, c);
777 if (*eol_str)
778 break;
781 if (eof)
782 *eof = found_eof;
783 *line = str;
785 return SVN_NO_ERROR;
788 /* Return in *OFFSET the current byte offset for reading from the
789 * unpatched file content accessed via BATON.
790 * Use SCRATCH_POOL for temporary allocations. */
791 static svn_error_t *
792 tell_file(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool)
794 apr_file_t *file = (apr_file_t *)baton;
795 *offset = 0;
796 SVN_ERR(svn_io_file_seek(file, APR_CUR, offset, scratch_pool));
797 return SVN_NO_ERROR;
800 /* Seek to the specified by OFFSET in the unpatched file content accessed
801 * via BATON. Use SCRATCH_POOL for temporary allocations. */
802 static svn_error_t *
803 seek_file(void *baton, apr_off_t offset, apr_pool_t *scratch_pool)
805 apr_file_t *file = (apr_file_t *)baton;
806 SVN_ERR(svn_io_file_seek(file, APR_SET, &offset, scratch_pool));
807 return SVN_NO_ERROR;
810 /* Write LEN bytes from BUF into the patched file content accessed
811 * via BATON. Use SCRATCH_POOL for temporary allocations. */
812 static svn_error_t *
813 write_file(void *baton, const char *buf, apr_size_t len,
814 apr_pool_t *scratch_pool)
816 apr_file_t *file = (apr_file_t *)baton;
817 SVN_ERR(svn_io_file_write_full(file, buf, len, &len, scratch_pool));
818 return SVN_NO_ERROR;
821 /* Handling symbolic links:
823 * In Subversion, symlinks can be represented on disk in two distinct ways.
824 * On systems which support symlinks, a symlink is created on disk.
825 * On systems which do not support symlink, a file is created on disk
826 * which contains the "normal form" of the symlink, which looks like:
827 * link TARGET
828 * where TARGET is the file the symlink points to.
830 * When reading symlinks (i.e. the link itself, not the file the symlink
831 * is pointing to) through the svn_subst_create_specialfile() function
832 * into a buffer, the buffer always contains the "normal form" of the symlink.
833 * Due to this representation symlinks always contain a single line of text.
835 * The functions below are needed to deal with the case where a patch
836 * wants to change the TARGET that a symlink points to.
839 /* Baton for the (readline|tell|seek|write)_symlink functions. */
840 struct symlink_baton_t
842 /* The path to the symlink on disk (not the path to the target of the link) */
843 const char *local_abspath;
845 /* Indicates whether the "normal form" of the symlink has been read. */
846 svn_boolean_t at_eof;
849 /* Allocate *STRINGBUF in RESULT_POOL, and store into it the "normal form"
850 * of the symlink accessed via BATON.
852 * Otherwise behaves like readline_file(), which see.
854 static svn_error_t *
855 readline_symlink(void *baton, svn_stringbuf_t **line, const char **eol_str,
856 svn_boolean_t *eof, apr_pool_t *result_pool,
857 apr_pool_t *scratch_pool)
859 struct symlink_baton_t *sb = baton;
861 if (eof)
862 *eof = TRUE;
863 if (eol_str)
864 *eol_str = NULL;
866 if (sb->at_eof)
868 *line = NULL;
870 else
872 svn_string_t *dest;
874 SVN_ERR(svn_io_read_link(&dest, sb->local_abspath, scratch_pool));
875 *line = svn_stringbuf_createf(result_pool, "link %s", dest->data);
876 sb->at_eof = TRUE;
879 return SVN_NO_ERROR;
882 /* Set *OFFSET to 1 or 0 depending on whether the "normal form" of
883 * the symlink has already been read. */
884 static svn_error_t *
885 tell_symlink(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool)
887 struct symlink_baton_t *sb = baton;
889 *offset = sb->at_eof ? 1 : 0;
890 return SVN_NO_ERROR;
893 /* If offset is non-zero, mark the symlink as having been read in its
894 * "normal form". Else, mark the symlink as not having been read yet. */
895 static svn_error_t *
896 seek_symlink(void *baton, apr_off_t offset, apr_pool_t *scratch_pool)
898 struct symlink_baton_t *sb = baton;
900 sb->at_eof = (offset != 0);
901 return SVN_NO_ERROR;
905 /* Set the target of the symlink accessed via BATON.
906 * The contents of BUF must be a valid "normal form" of a symlink. */
907 static svn_error_t *
908 write_symlink(void *baton, const char *buf, apr_size_t len,
909 apr_pool_t *scratch_pool)
911 const char *target_abspath = baton;
912 const char *new_name;
913 const char *link = apr_pstrndup(scratch_pool, buf, len);
915 if (strncmp(link, "link ", 5) != 0)
916 return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL,
917 _("Invalid link representation"));
919 link += 5; /* Skip "link " */
921 /* We assume the entire symlink is written at once, as the patch
922 format is line based */
924 SVN_ERR(svn_io_create_unique_link(&new_name, target_abspath, link,
925 ".tmp", scratch_pool));
927 SVN_ERR(svn_io_file_rename(new_name, target_abspath, scratch_pool));
929 return SVN_NO_ERROR;
933 /* Return a suitable filename for the target of PATCH.
934 * Examine the ``old'' and ``new'' file names, and choose the file name
935 * with the fewest path components, the shortest basename, and the shortest
936 * total file name length (in that order). In case of a tie, return the new
937 * filename. This heuristic is also used by Larry Wall's UNIX patch (except
938 * that it prompts for a filename in case of a tie).
939 * Additionally, for compatibility with git, if one of the filenames
940 * is "/dev/null", use the other filename. */
941 static const char *
942 choose_target_filename(const svn_patch_t *patch)
944 apr_size_t old;
945 apr_size_t new;
947 if (strcmp(patch->old_filename, "/dev/null") == 0)
948 return patch->new_filename;
949 if (strcmp(patch->new_filename, "/dev/null") == 0)
950 return patch->old_filename;
952 /* If the patch renames the target, use the old name while
953 * applying hunks. The target will be renamed to the new name
954 * after hunks have been applied. */
955 if (patch->operation == svn_diff_op_moved)
956 return patch->old_filename;
958 old = svn_path_component_count(patch->old_filename);
959 new = svn_path_component_count(patch->new_filename);
961 if (old == new)
963 old = strlen(svn_dirent_basename(patch->old_filename, NULL));
964 new = strlen(svn_dirent_basename(patch->new_filename, NULL));
966 if (old == new)
968 old = strlen(patch->old_filename);
969 new = strlen(patch->new_filename);
973 return (old < new) ? patch->old_filename : patch->new_filename;
976 /* Attempt to initialize a *PATCH_TARGET structure for a target file
977 * described by PATCH. Use working copy context WC_CTX.
978 * STRIP_COUNT specifies the number of leading path components
979 * which should be stripped from target paths in the patch.
980 * The patch target structure is allocated in RESULT_POOL, but if the target
981 * should be skipped, PATCH_TARGET->SKIPPED is set and the target should be
982 * treated as not fully initialized, e.g. the caller should not not do any
983 * further operations on the target if it is marked to be skipped.
984 * If REMOVE_TEMPFILES is TRUE, set up temporary files to be removed as
985 * soon as they are no longer needed.
986 * Use SCRATCH_POOL for all other allocations. */
987 static svn_error_t *
988 init_patch_target(patch_target_t **patch_target,
989 const svn_patch_t *patch,
990 const char *wcroot_abspath,
991 svn_wc_context_t *wc_ctx, int strip_count,
992 svn_boolean_t remove_tempfiles,
993 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
995 patch_target_t *target;
996 target_content_t *content;
997 svn_boolean_t has_prop_changes = FALSE;
998 svn_boolean_t prop_changes_only = FALSE;
1001 apr_hash_index_t *hi;
1003 for (hi = apr_hash_first(scratch_pool, patch->prop_patches);
1005 hi = apr_hash_next(hi))
1007 svn_prop_patch_t *prop_patch = apr_hash_this_val(hi);
1008 if (! has_prop_changes)
1009 has_prop_changes = prop_patch->hunks->nelts > 0;
1010 else
1011 break;
1015 prop_changes_only = has_prop_changes && patch->hunks->nelts == 0;
1017 content = apr_pcalloc(result_pool, sizeof(*content));
1019 /* All other fields in content are FALSE or NULL due to apr_pcalloc().*/
1020 content->current_line = 1;
1021 content->eol_style = svn_subst_eol_style_none;
1022 content->lines = apr_array_make(result_pool, 0, sizeof(apr_off_t));
1023 content->hunks = apr_array_make(result_pool, 0, sizeof(hunk_info_t *));
1024 content->keywords = apr_hash_make(result_pool);
1026 target = apr_pcalloc(result_pool, sizeof(*target));
1028 /* All other fields in target are FALSE or NULL due to apr_pcalloc(). */
1029 target->db_kind = svn_node_none;
1030 target->kind_on_disk = svn_node_none;
1031 target->content = content;
1032 target->prop_targets = apr_hash_make(result_pool);
1034 SVN_ERR(resolve_target_path(target, choose_target_filename(patch),
1035 wcroot_abspath, strip_count, prop_changes_only,
1036 wc_ctx, result_pool, scratch_pool));
1037 *patch_target = target;
1038 if (! target->skipped)
1040 const char *diff_header;
1041 apr_size_t len;
1043 /* Create a temporary file to write the patched result to.
1044 * Also grab various bits of information about the file. */
1045 if (target->is_symlink)
1047 struct symlink_baton_t *sb = apr_pcalloc(result_pool, sizeof(*sb));
1048 content->existed = TRUE;
1050 sb->local_abspath = target->local_abspath;
1052 /* Wire up the read callbacks. */
1053 content->read_baton = sb;
1055 content->readline = readline_symlink;
1056 content->seek = seek_symlink;
1057 content->tell = tell_symlink;
1059 else if (target->kind_on_disk == svn_node_file)
1061 SVN_ERR(svn_io_file_open(&target->file, target->local_abspath,
1062 APR_READ | APR_BUFFERED,
1063 APR_OS_DEFAULT, result_pool));
1064 SVN_ERR(svn_wc_text_modified_p2(&target->local_mods, wc_ctx,
1065 target->local_abspath, FALSE,
1066 scratch_pool));
1067 SVN_ERR(svn_io_is_file_executable(&target->executable,
1068 target->local_abspath,
1069 scratch_pool));
1070 SVN_ERR(obtain_eol_and_keywords_for_file(&content->keywords,
1071 &content->eol_style,
1072 &content->eol_str,
1073 wc_ctx,
1074 target->local_abspath,
1075 result_pool,
1076 scratch_pool));
1077 content->existed = TRUE;
1079 /* Wire up the read callbacks. */
1080 content->readline = readline_file;
1081 content->seek = seek_file;
1082 content->tell = tell_file;
1083 content->read_baton = target->file;
1086 /* ### Is it ok to set the operation of the target already here? Isn't
1087 * ### the target supposed to be marked with an operation after we have
1088 * ### determined that the changes will apply cleanly to the WC? Maybe
1089 * ### we should have kept the patch field in patch_target_t to be
1090 * ### able to distinguish between 'what the patch says we should do'
1091 * ### and 'what we can do with the given state of our WC'. */
1092 if (patch->operation == svn_diff_op_added)
1093 target->added = TRUE;
1094 else if (patch->operation == svn_diff_op_deleted)
1095 target->deleted = TRUE;
1096 else if (patch->operation == svn_diff_op_moved)
1098 const char *move_target_path;
1099 const char *move_target_relpath;
1100 svn_boolean_t under_root;
1101 svn_node_kind_t kind_on_disk;
1102 svn_node_kind_t wc_kind;
1104 move_target_path = svn_dirent_internal_style(patch->new_filename,
1105 scratch_pool);
1107 if (strip_count > 0)
1108 SVN_ERR(strip_path(&move_target_path, move_target_path,
1109 strip_count, scratch_pool, scratch_pool));
1111 if (svn_dirent_is_absolute(move_target_path))
1113 move_target_relpath = svn_dirent_is_child(wcroot_abspath,
1114 move_target_path,
1115 scratch_pool);
1116 if (! move_target_relpath)
1118 /* The move target path is either outside of the working
1119 * copy or it is the working copy itself. Skip it. */
1120 target->skipped = TRUE;
1121 target->local_abspath = NULL;
1122 return SVN_NO_ERROR;
1125 else
1126 move_target_relpath = move_target_path;
1128 /* Make sure the move target path is secure to use. */
1129 SVN_ERR(svn_dirent_is_under_root(&under_root,
1130 &target->move_target_abspath,
1131 wcroot_abspath,
1132 move_target_relpath, result_pool));
1133 if (! under_root)
1135 /* The target path is outside of the working copy. Skip it. */
1136 target->skipped = TRUE;
1137 target->local_abspath = NULL;
1138 return SVN_NO_ERROR;
1141 SVN_ERR(svn_io_check_path(target->move_target_abspath,
1142 &kind_on_disk, scratch_pool));
1143 SVN_ERR(svn_wc_read_kind2(&wc_kind, wc_ctx,
1144 target->move_target_abspath,
1145 FALSE, FALSE, scratch_pool));
1146 if (kind_on_disk != svn_node_none || wc_kind != svn_node_none)
1148 /* The move target path already exists on disk. Skip target. */
1149 target->skipped = TRUE;
1150 target->move_target_abspath = NULL;
1151 return SVN_NO_ERROR;
1155 if (! target->is_symlink)
1157 /* Open a temporary file to write the patched result to. */
1158 SVN_ERR(svn_io_open_unique_file3(&target->patched_file,
1159 &target->patched_path, NULL,
1160 remove_tempfiles ?
1161 svn_io_file_del_on_pool_cleanup :
1162 svn_io_file_del_none,
1163 result_pool, scratch_pool));
1165 /* Put the write callback in place. */
1166 content->write = write_file;
1167 content->write_baton = target->patched_file;
1169 else
1171 /* Put the write callback in place. */
1172 SVN_ERR(svn_io_open_unique_file3(NULL,
1173 &target->patched_path, NULL,
1174 remove_tempfiles ?
1175 svn_io_file_del_on_pool_cleanup :
1176 svn_io_file_del_none,
1177 result_pool, scratch_pool));
1179 content->write_baton = (void*)target->patched_path;
1181 content->write = write_symlink;
1184 /* Open a temporary file to write rejected hunks to. */
1185 SVN_ERR(svn_io_open_unique_file3(&target->reject_file,
1186 &target->reject_path, NULL,
1187 remove_tempfiles ?
1188 svn_io_file_del_on_pool_cleanup :
1189 svn_io_file_del_none,
1190 result_pool, scratch_pool));
1192 /* The reject file needs a diff header. */
1193 diff_header = apr_psprintf(scratch_pool, "--- %s%s+++ %s%s",
1194 target->canon_path_from_patchfile,
1195 APR_EOL_STR,
1196 target->canon_path_from_patchfile,
1197 APR_EOL_STR);
1198 len = strlen(diff_header);
1199 SVN_ERR(svn_io_file_write_full(target->reject_file, diff_header, len,
1200 &len, scratch_pool));
1202 /* Handle properties. */
1203 if (! target->skipped)
1205 apr_hash_index_t *hi;
1207 for (hi = apr_hash_first(result_pool, patch->prop_patches);
1209 hi = apr_hash_next(hi))
1211 const char *prop_name = apr_hash_this_key(hi);
1212 svn_prop_patch_t *prop_patch = apr_hash_this_val(hi);
1213 prop_patch_target_t *prop_target;
1215 SVN_ERR(init_prop_target(&prop_target,
1216 prop_name,
1217 prop_patch->operation,
1218 wc_ctx, target->local_abspath,
1219 result_pool, scratch_pool));
1220 svn_hash_sets(target->prop_targets, prop_name, prop_target);
1225 return SVN_NO_ERROR;
1228 /* Read a *LINE from CONTENT. If the line has not been read before
1229 * mark the line in CONTENT->LINES.
1230 * If a line could be read successfully, increase CONTENT->CURRENT_LINE,
1231 * and allocate *LINE in RESULT_POOL.
1232 * Do temporary allocations in SCRATCH_POOL.
1234 static svn_error_t *
1235 readline(target_content_t *content,
1236 const char **line,
1237 apr_pool_t *result_pool,
1238 apr_pool_t *scratch_pool)
1240 svn_stringbuf_t *line_raw;
1241 const char *eol_str;
1242 svn_linenum_t max_line = (svn_linenum_t)content->lines->nelts + 1;
1244 if (content->eof || content->readline == NULL)
1246 *line = "";
1247 return SVN_NO_ERROR;
1250 SVN_ERR_ASSERT(content->current_line <= max_line);
1251 if (content->current_line == max_line)
1253 apr_off_t offset;
1255 SVN_ERR(content->tell(content->read_baton, &offset,
1256 scratch_pool));
1257 APR_ARRAY_PUSH(content->lines, apr_off_t) = offset;
1260 SVN_ERR(content->readline(content->read_baton, &line_raw,
1261 &eol_str, &content->eof,
1262 result_pool, scratch_pool));
1263 if (content->eol_style == svn_subst_eol_style_none)
1264 content->eol_str = eol_str;
1266 if (line_raw)
1268 /* Contract keywords. */
1269 SVN_ERR(svn_subst_translate_cstring2(line_raw->data, line,
1270 NULL, FALSE,
1271 content->keywords, FALSE,
1272 result_pool));
1274 else
1275 *line = "";
1277 if ((line_raw && line_raw->len > 0) || eol_str)
1278 content->current_line++;
1280 SVN_ERR_ASSERT(content->current_line > 0);
1282 return SVN_NO_ERROR;
1285 /* Seek to the specified LINE in CONTENT.
1286 * Mark any lines not read before in CONTENT->LINES.
1287 * Do temporary allocations in SCRATCH_POOL.
1289 static svn_error_t *
1290 seek_to_line(target_content_t *content, svn_linenum_t line,
1291 apr_pool_t *scratch_pool)
1293 svn_linenum_t saved_line;
1294 svn_boolean_t saved_eof;
1296 SVN_ERR_ASSERT(line > 0);
1298 if (line == content->current_line)
1299 return SVN_NO_ERROR;
1301 saved_line = content->current_line;
1302 saved_eof = content->eof;
1304 if (line <= (svn_linenum_t)content->lines->nelts)
1306 apr_off_t offset;
1308 offset = APR_ARRAY_IDX(content->lines, line - 1, apr_off_t);
1309 SVN_ERR(content->seek(content->read_baton, offset,
1310 scratch_pool));
1311 content->current_line = line;
1313 else
1315 const char *dummy;
1316 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1318 while (! content->eof && content->current_line < line)
1320 svn_pool_clear(iterpool);
1321 SVN_ERR(readline(content, &dummy, iterpool, iterpool));
1323 svn_pool_destroy(iterpool);
1326 /* After seeking backwards from EOF position clear EOF indicator. */
1327 if (saved_eof && saved_line > content->current_line)
1328 content->eof = FALSE;
1330 return SVN_NO_ERROR;
1333 /* Indicate in *MATCHED whether the original text of HUNK matches the patch
1334 * CONTENT at its current line. Lines within FUZZ lines of the start or
1335 * end of HUNK will always match. If IGNORE_WHITESPACE is set, we ignore
1336 * whitespace when doing the matching. When this function returns, neither
1337 * CONTENT->CURRENT_LINE nor the file offset in the target file will
1338 * have changed. If MATCH_MODIFIED is TRUE, match the modified hunk text,
1339 * rather than the original hunk text.
1340 * Do temporary allocations in POOL. */
1341 static svn_error_t *
1342 match_hunk(svn_boolean_t *matched, target_content_t *content,
1343 svn_diff_hunk_t *hunk, svn_linenum_t fuzz,
1344 svn_boolean_t ignore_whitespace,
1345 svn_boolean_t match_modified, apr_pool_t *pool)
1347 svn_stringbuf_t *hunk_line;
1348 const char *target_line;
1349 svn_linenum_t lines_read;
1350 svn_linenum_t saved_line;
1351 svn_boolean_t hunk_eof;
1352 svn_boolean_t lines_matched;
1353 apr_pool_t *iterpool;
1354 svn_linenum_t hunk_length;
1355 svn_linenum_t leading_context;
1356 svn_linenum_t trailing_context;
1358 *matched = FALSE;
1360 if (content->eof)
1361 return SVN_NO_ERROR;
1363 saved_line = content->current_line;
1364 lines_read = 0;
1365 lines_matched = FALSE;
1366 leading_context = svn_diff_hunk_get_leading_context(hunk);
1367 trailing_context = svn_diff_hunk_get_trailing_context(hunk);
1368 if (match_modified)
1370 svn_diff_hunk_reset_modified_text(hunk);
1371 hunk_length = svn_diff_hunk_get_modified_length(hunk);
1373 else
1375 svn_diff_hunk_reset_original_text(hunk);
1376 hunk_length = svn_diff_hunk_get_original_length(hunk);
1378 iterpool = svn_pool_create(pool);
1381 const char *hunk_line_translated;
1383 svn_pool_clear(iterpool);
1385 if (match_modified)
1386 SVN_ERR(svn_diff_hunk_readline_modified_text(hunk, &hunk_line,
1387 NULL, &hunk_eof,
1388 iterpool, iterpool));
1389 else
1390 SVN_ERR(svn_diff_hunk_readline_original_text(hunk, &hunk_line,
1391 NULL, &hunk_eof,
1392 iterpool, iterpool));
1394 /* Contract keywords, if any, before matching. */
1395 SVN_ERR(svn_subst_translate_cstring2(hunk_line->data,
1396 &hunk_line_translated,
1397 NULL, FALSE,
1398 content->keywords, FALSE,
1399 iterpool));
1400 SVN_ERR(readline(content, &target_line, iterpool, iterpool));
1402 lines_read++;
1404 /* If the last line doesn't have a newline, we get EOF but still
1405 * have a non-empty line to compare. */
1406 if ((hunk_eof && hunk_line->len == 0) ||
1407 (content->eof && *target_line == 0))
1408 break;
1410 /* Leading/trailing fuzzy lines always match. */
1411 if ((lines_read <= fuzz && leading_context > fuzz) ||
1412 (lines_read > hunk_length - fuzz && trailing_context > fuzz))
1413 lines_matched = TRUE;
1414 else
1416 if (ignore_whitespace)
1418 char *hunk_line_trimmed;
1419 char *target_line_trimmed;
1421 hunk_line_trimmed = apr_pstrdup(iterpool, hunk_line_translated);
1422 target_line_trimmed = apr_pstrdup(iterpool, target_line);
1423 apr_collapse_spaces(hunk_line_trimmed, hunk_line_trimmed);
1424 apr_collapse_spaces(target_line_trimmed, target_line_trimmed);
1425 lines_matched = ! strcmp(hunk_line_trimmed, target_line_trimmed);
1427 else
1428 lines_matched = ! strcmp(hunk_line_translated, target_line);
1431 while (lines_matched);
1433 *matched = lines_matched && hunk_eof && hunk_line->len == 0;
1434 SVN_ERR(seek_to_line(content, saved_line, iterpool));
1435 svn_pool_destroy(iterpool);
1437 return SVN_NO_ERROR;
1440 /* Scan lines of CONTENT for a match of the original text of HUNK,
1441 * up to but not including the specified UPPER_LINE. Use fuzz factor FUZZ.
1442 * If UPPER_LINE is zero scan until EOF occurs when reading from TARGET.
1443 * Return the line at which HUNK was matched in *MATCHED_LINE.
1444 * If the hunk did not match at all, set *MATCHED_LINE to zero.
1445 * If the hunk matched multiple times, and MATCH_FIRST is TRUE,
1446 * return the line number at which the first match occurred in *MATCHED_LINE.
1447 * If the hunk matched multiple times, and MATCH_FIRST is FALSE,
1448 * return the line number at which the last match occurred in *MATCHED_LINE.
1449 * If IGNORE_WHITESPACE is set, ignore whitespace during the matching.
1450 * If MATCH_MODIFIED is TRUE, match the modified hunk text,
1451 * rather than the original hunk text.
1452 * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
1453 * Do all allocations in POOL. */
1454 static svn_error_t *
1455 scan_for_match(svn_linenum_t *matched_line,
1456 target_content_t *content,
1457 svn_diff_hunk_t *hunk, svn_boolean_t match_first,
1458 svn_linenum_t upper_line, svn_linenum_t fuzz,
1459 svn_boolean_t ignore_whitespace,
1460 svn_boolean_t match_modified,
1461 svn_cancel_func_t cancel_func, void *cancel_baton,
1462 apr_pool_t *pool)
1464 apr_pool_t *iterpool;
1466 *matched_line = 0;
1467 iterpool = svn_pool_create(pool);
1468 while ((content->current_line < upper_line || upper_line == 0) &&
1469 ! content->eof)
1471 svn_boolean_t matched;
1473 svn_pool_clear(iterpool);
1475 if (cancel_func)
1476 SVN_ERR(cancel_func(cancel_baton));
1478 SVN_ERR(match_hunk(&matched, content, hunk, fuzz, ignore_whitespace,
1479 match_modified, iterpool));
1480 if (matched)
1482 svn_boolean_t taken = FALSE;
1483 int i;
1485 /* Don't allow hunks to match at overlapping locations. */
1486 for (i = 0; i < content->hunks->nelts; i++)
1488 const hunk_info_t *hi;
1489 svn_linenum_t length;
1491 hi = APR_ARRAY_IDX(content->hunks, i, const hunk_info_t *);
1493 if (match_modified)
1494 length = svn_diff_hunk_get_modified_length(hi->hunk);
1495 else
1496 length = svn_diff_hunk_get_original_length(hi->hunk);
1498 taken = (! hi->rejected &&
1499 content->current_line >= hi->matched_line &&
1500 content->current_line < (hi->matched_line + length));
1501 if (taken)
1502 break;
1505 if (! taken)
1507 *matched_line = content->current_line;
1508 if (match_first)
1509 break;
1513 if (! content->eof)
1514 SVN_ERR(seek_to_line(content, content->current_line + 1,
1515 iterpool));
1517 svn_pool_destroy(iterpool);
1519 return SVN_NO_ERROR;
1522 /* Indicate in *MATCH whether the content described by CONTENT
1523 * matches the modified text of HUNK.
1524 * Use SCRATCH_POOL for temporary allocations. */
1525 static svn_error_t *
1526 match_existing_target(svn_boolean_t *match,
1527 target_content_t *content,
1528 svn_diff_hunk_t *hunk,
1529 apr_pool_t *scratch_pool)
1531 svn_boolean_t lines_matched;
1532 apr_pool_t *iterpool;
1533 svn_boolean_t hunk_eof;
1534 svn_linenum_t saved_line;
1536 svn_diff_hunk_reset_modified_text(hunk);
1538 saved_line = content->current_line;
1540 iterpool = svn_pool_create(scratch_pool);
1543 const char *line;
1544 svn_stringbuf_t *hunk_line;
1545 const char *line_translated;
1546 const char *hunk_line_translated;
1548 svn_pool_clear(iterpool);
1550 SVN_ERR(readline(content, &line, iterpool, iterpool));
1551 SVN_ERR(svn_diff_hunk_readline_modified_text(hunk, &hunk_line,
1552 NULL, &hunk_eof,
1553 iterpool, iterpool));
1554 /* Contract keywords. */
1555 SVN_ERR(svn_subst_translate_cstring2(line, &line_translated,
1556 NULL, FALSE,
1557 content->keywords,
1558 FALSE, iterpool));
1559 SVN_ERR(svn_subst_translate_cstring2(hunk_line->data,
1560 &hunk_line_translated,
1561 NULL, FALSE,
1562 content->keywords,
1563 FALSE, iterpool));
1564 lines_matched = ! strcmp(line_translated, hunk_line_translated);
1565 if (content->eof != hunk_eof)
1567 svn_pool_destroy(iterpool);
1568 *match = FALSE;
1569 return SVN_NO_ERROR;
1572 while (lines_matched && ! content->eof && ! hunk_eof);
1573 svn_pool_destroy(iterpool);
1575 *match = (lines_matched && content->eof == hunk_eof);
1576 SVN_ERR(seek_to_line(content, saved_line, scratch_pool));
1578 return SVN_NO_ERROR;
1581 /* Determine the line at which a HUNK applies to CONTENT of the TARGET
1582 * file, and return an appropriate hunk_info object in *HI, allocated from
1583 * RESULT_POOL. Use fuzz factor FUZZ. Set HI->FUZZ to FUZZ. If no correct
1584 * line can be determined, set HI->REJECTED to TRUE. PREVIOUS_OFFSET
1585 * is the offset at which the previous matching hunk was applied, or zero.
1586 * IGNORE_WHITESPACE tells whether whitespace should be considered when
1587 * matching. IS_PROP_HUNK indicates whether the hunk patches file content
1588 * or a property.
1589 * When this function returns, neither CONTENT->CURRENT_LINE nor
1590 * the file offset in the target file will have changed.
1591 * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
1592 * Do temporary allocations in POOL. */
1593 static svn_error_t *
1594 get_hunk_info(hunk_info_t **hi, patch_target_t *target,
1595 target_content_t *content,
1596 svn_diff_hunk_t *hunk, svn_linenum_t fuzz,
1597 svn_linenum_t previous_offset,
1598 svn_boolean_t ignore_whitespace,
1599 svn_boolean_t is_prop_hunk,
1600 svn_cancel_func_t cancel_func, void *cancel_baton,
1601 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
1603 svn_linenum_t matched_line;
1604 svn_linenum_t original_start;
1605 svn_boolean_t already_applied;
1607 original_start = svn_diff_hunk_get_original_start(hunk) + previous_offset;
1608 already_applied = FALSE;
1610 /* An original offset of zero means that this hunk wants to create
1611 * a new file. Don't bother matching hunks in that case, since
1612 * the hunk applies at line 1. If the file already exists, the hunk
1613 * is rejected, unless the file is versioned and its content matches
1614 * the file the patch wants to create. */
1615 if (original_start == 0 && fuzz > 0)
1617 matched_line = 0; /* reject any fuzz for new files */
1619 else if (original_start == 0 && ! is_prop_hunk)
1621 if (target->kind_on_disk == svn_node_file)
1623 const svn_io_dirent2_t *dirent;
1624 SVN_ERR(svn_io_stat_dirent2(&dirent, target->local_abspath, FALSE,
1625 TRUE, scratch_pool, scratch_pool));
1627 if (dirent->kind == svn_node_file
1628 && !dirent->special
1629 && dirent->filesize == 0)
1631 matched_line = 1; /* Matched an on-disk empty file */
1633 else
1635 if (target->db_kind == svn_node_file)
1637 svn_boolean_t file_matches;
1639 /* ### I can't reproduce anything but a no-match here.
1640 The content is already at eof, so any hunk fails */
1641 SVN_ERR(match_existing_target(&file_matches, content, hunk,
1642 scratch_pool));
1643 if (file_matches)
1645 matched_line = 1;
1646 already_applied = TRUE;
1648 else
1649 matched_line = 0; /* reject */
1651 else
1652 matched_line = 0; /* reject */
1655 else
1656 matched_line = 1;
1658 /* Same conditions apply as for the file case above.
1660 * ### Since the hunk says the prop should be added we just assume so for
1661 * ### now and don't bother with storing the previous lines and such. When
1662 * ### we have the diff operation available we can just check for adds. */
1663 else if (original_start == 0 && is_prop_hunk)
1665 if (content->existed)
1667 svn_boolean_t prop_matches;
1669 SVN_ERR(match_existing_target(&prop_matches, content, hunk,
1670 scratch_pool));
1672 if (prop_matches)
1674 matched_line = 1;
1675 already_applied = TRUE;
1677 else
1678 matched_line = 0; /* reject */
1680 else
1681 matched_line = 1;
1683 else if (original_start > 0 && content->existed)
1685 svn_linenum_t saved_line = content->current_line;
1687 /* Scan for a match at the line where the hunk thinks it
1688 * should be going. */
1689 SVN_ERR(seek_to_line(content, original_start, scratch_pool));
1690 if (content->current_line != original_start)
1692 /* Seek failed. */
1693 matched_line = 0;
1695 else
1696 SVN_ERR(scan_for_match(&matched_line, content, hunk, TRUE,
1697 original_start + 1, fuzz,
1698 ignore_whitespace, FALSE,
1699 cancel_func, cancel_baton,
1700 scratch_pool));
1702 if (matched_line != original_start)
1704 /* Check if the hunk is already applied.
1705 * We only check for an exact match here, and don't bother checking
1706 * for already applied patches with offset/fuzz, because such a
1707 * check would be ambiguous. */
1708 if (fuzz == 0)
1710 svn_linenum_t modified_start;
1712 modified_start = svn_diff_hunk_get_modified_start(hunk);
1713 if (modified_start == 0)
1715 /* Patch wants to delete the file.
1717 ### locally_deleted is always false here? */
1718 already_applied = target->locally_deleted;
1720 else
1722 SVN_ERR(seek_to_line(content, modified_start,
1723 scratch_pool));
1724 SVN_ERR(scan_for_match(&matched_line, content,
1725 hunk, TRUE,
1726 modified_start + 1,
1727 fuzz, ignore_whitespace, TRUE,
1728 cancel_func, cancel_baton,
1729 scratch_pool));
1730 already_applied = (matched_line == modified_start);
1733 else
1734 already_applied = FALSE;
1736 if (! already_applied)
1738 int i;
1739 svn_linenum_t search_start = 1, search_end = 0;
1740 svn_linenum_t matched_line2;
1742 /* Search for closest match before or after original
1743 start. We have no backward search so search forwards
1744 from the previous match (or start of file) to the
1745 original start looking for the last match. Then
1746 search forwards from the original start looking for a
1747 better match. Finally search forwards from the start
1748 of file to the previous hunk if that could result in
1749 a better match. */
1751 for (i = content->hunks->nelts; i > 0; --i)
1753 const hunk_info_t *prev
1754 = APR_ARRAY_IDX(content->hunks, i - 1, const hunk_info_t *);
1755 if (!prev->rejected)
1757 svn_linenum_t length;
1759 length = svn_diff_hunk_get_original_length(prev->hunk);
1760 search_start = prev->matched_line + length;
1761 break;
1765 /* Search from the previous match, or start of file,
1766 towards the original location. */
1767 SVN_ERR(seek_to_line(content, search_start, scratch_pool));
1768 SVN_ERR(scan_for_match(&matched_line, content, hunk, FALSE,
1769 original_start, fuzz,
1770 ignore_whitespace, FALSE,
1771 cancel_func, cancel_baton,
1772 scratch_pool));
1774 /* If a match we only need to search forwards for a
1775 better match, otherwise to the end of the file. */
1776 if (matched_line)
1777 search_end = original_start + (original_start - matched_line);
1779 /* Search from original location, towards the end. */
1780 SVN_ERR(seek_to_line(content, original_start + 1, scratch_pool));
1781 SVN_ERR(scan_for_match(&matched_line2, content, hunk,
1782 TRUE, search_end, fuzz, ignore_whitespace,
1783 FALSE, cancel_func, cancel_baton,
1784 scratch_pool));
1786 /* Chose the forward match if it is closer than the
1787 backward match or if there is no backward match. */
1788 if (matched_line2
1789 && (!matched_line
1790 || (matched_line2 - original_start
1791 < original_start - matched_line)))
1792 matched_line = matched_line2;
1794 /* Search from before previous hunk if there could be a
1795 better match. */
1796 if (search_start > 1
1797 && (!matched_line
1798 || (matched_line > original_start
1799 && (matched_line - original_start
1800 > original_start - search_start))))
1802 svn_linenum_t search_start2 = 1;
1804 if (matched_line
1805 && matched_line - original_start < original_start)
1806 search_start2
1807 = original_start - (matched_line - original_start) + 1;
1809 SVN_ERR(seek_to_line(content, search_start2, scratch_pool));
1810 SVN_ERR(scan_for_match(&matched_line2, content, hunk, FALSE,
1811 search_start - 1, fuzz,
1812 ignore_whitespace, FALSE,
1813 cancel_func, cancel_baton,
1814 scratch_pool));
1815 if (matched_line2)
1816 matched_line = matched_line2;
1821 SVN_ERR(seek_to_line(content, saved_line, scratch_pool));
1823 else
1825 /* The hunk wants to modify a file which doesn't exist. */
1826 matched_line = 0;
1829 (*hi) = apr_pcalloc(result_pool, sizeof(hunk_info_t));
1830 (*hi)->hunk = hunk;
1831 (*hi)->matched_line = matched_line;
1832 (*hi)->rejected = (matched_line == 0);
1833 (*hi)->already_applied = already_applied;
1834 (*hi)->fuzz = fuzz;
1836 return SVN_NO_ERROR;
1839 /* Copy lines to the patched content until the specified LINE has been
1840 * reached. Indicate in *EOF whether end-of-file was encountered while
1841 * reading from the target.
1842 * If LINE is zero, copy lines until end-of-file has been reached.
1843 * Do all allocations in POOL. */
1844 static svn_error_t *
1845 copy_lines_to_target(target_content_t *content, svn_linenum_t line,
1846 apr_pool_t *pool)
1848 apr_pool_t *iterpool;
1850 iterpool = svn_pool_create(pool);
1851 while ((content->current_line < line || line == 0) && ! content->eof)
1853 const char *target_line;
1854 apr_size_t len;
1856 svn_pool_clear(iterpool);
1858 SVN_ERR(readline(content, &target_line, iterpool, iterpool));
1859 if (! content->eof)
1860 target_line = apr_pstrcat(iterpool, target_line, content->eol_str,
1861 SVN_VA_NULL);
1862 len = strlen(target_line);
1863 SVN_ERR(content->write(content->write_baton, target_line,
1864 len, iterpool));
1866 svn_pool_destroy(iterpool);
1868 return SVN_NO_ERROR;
1871 /* Write the diff text of HUNK to TARGET's reject file,
1872 * and mark TARGET as having had rejects.
1873 * We don't expand keywords, nor normalise line-endings, in reject files.
1874 * Do temporary allocations in SCRATCH_POOL. */
1875 static svn_error_t *
1876 reject_hunk(patch_target_t *target, target_content_t *content,
1877 svn_diff_hunk_t *hunk, const char *prop_name,
1878 apr_pool_t *pool)
1880 const char *hunk_header;
1881 apr_size_t len;
1882 svn_boolean_t eof;
1883 static const char * const text_atat = "@@";
1884 static const char * const prop_atat = "##";
1885 const char *atat;
1886 apr_pool_t *iterpool;
1888 if (prop_name)
1890 const char *prop_header;
1892 /* ### Print 'Added', 'Deleted' or 'Modified' instead of 'Property'.
1894 prop_header = apr_psprintf(pool, "Property: %s\n", prop_name);
1895 len = strlen(prop_header);
1896 SVN_ERR(svn_io_file_write_full(target->reject_file, prop_header,
1897 len, &len, pool));
1898 atat = prop_atat;
1900 else
1902 atat = text_atat;
1905 hunk_header = apr_psprintf(pool, "%s -%lu,%lu +%lu,%lu %s%s",
1906 atat,
1907 svn_diff_hunk_get_original_start(hunk),
1908 svn_diff_hunk_get_original_length(hunk),
1909 svn_diff_hunk_get_modified_start(hunk),
1910 svn_diff_hunk_get_modified_length(hunk),
1911 atat,
1912 APR_EOL_STR);
1913 len = strlen(hunk_header);
1914 SVN_ERR(svn_io_file_write_full(target->reject_file, hunk_header, len,
1915 &len, pool));
1917 iterpool = svn_pool_create(pool);
1920 svn_stringbuf_t *hunk_line;
1921 const char *eol_str;
1923 svn_pool_clear(iterpool);
1925 SVN_ERR(svn_diff_hunk_readline_diff_text(hunk, &hunk_line, &eol_str,
1926 &eof, iterpool, iterpool));
1927 if (! eof)
1929 if (hunk_line->len >= 1)
1931 len = hunk_line->len;
1932 SVN_ERR(svn_io_file_write_full(target->reject_file,
1933 hunk_line->data, len, &len,
1934 iterpool));
1937 if (eol_str)
1939 len = strlen(eol_str);
1940 SVN_ERR(svn_io_file_write_full(target->reject_file, eol_str,
1941 len, &len, iterpool));
1945 while (! eof);
1946 svn_pool_destroy(iterpool);
1948 if (prop_name)
1949 target->had_prop_rejects = TRUE;
1950 else
1951 target->had_rejects = TRUE;
1953 return SVN_NO_ERROR;
1956 /* Write the modified text of the hunk described by HI to the patched
1957 * CONTENT. TARGET is the patch target.
1958 * If PROP_NAME is not NULL, the hunk is assumed to be targeted for
1959 * a property with the given name.
1960 * Do temporary allocations in POOL. */
1961 static svn_error_t *
1962 apply_hunk(patch_target_t *target, target_content_t *content,
1963 hunk_info_t *hi, const char *prop_name, apr_pool_t *pool)
1965 svn_linenum_t lines_read;
1966 svn_boolean_t eof;
1967 apr_pool_t *iterpool;
1969 /* ### Is there a cleaner way to describe if we have an existing target?
1971 if (target->kind_on_disk == svn_node_file || prop_name)
1973 svn_linenum_t line;
1975 /* Move forward to the hunk's line, copying data as we go.
1976 * Also copy leading lines of context which matched with fuzz.
1977 * The target has changed on the fuzzy-matched lines,
1978 * so we should retain the target's version of those lines. */
1979 SVN_ERR(copy_lines_to_target(content, hi->matched_line + hi->fuzz,
1980 pool));
1982 /* Skip the target's version of the hunk.
1983 * Don't skip trailing lines which matched with fuzz. */
1984 line = content->current_line +
1985 svn_diff_hunk_get_original_length(hi->hunk) - (2 * hi->fuzz);
1986 SVN_ERR(seek_to_line(content, line, pool));
1987 if (content->current_line != line && ! content->eof)
1989 /* Seek failed, reject this hunk. */
1990 hi->rejected = TRUE;
1991 SVN_ERR(reject_hunk(target, content, hi->hunk, prop_name, pool));
1992 return SVN_NO_ERROR;
1996 /* Write the hunk's version to the patched result.
1997 * Don't write the lines which matched with fuzz. */
1998 lines_read = 0;
1999 svn_diff_hunk_reset_modified_text(hi->hunk);
2000 iterpool = svn_pool_create(pool);
2003 svn_stringbuf_t *hunk_line;
2004 const char *eol_str;
2006 svn_pool_clear(iterpool);
2008 SVN_ERR(svn_diff_hunk_readline_modified_text(hi->hunk, &hunk_line,
2009 &eol_str, &eof,
2010 iterpool, iterpool));
2011 lines_read++;
2012 if (lines_read > hi->fuzz &&
2013 lines_read <= svn_diff_hunk_get_modified_length(hi->hunk) - hi->fuzz)
2015 apr_size_t len;
2017 if (hunk_line->len >= 1)
2019 len = hunk_line->len;
2020 SVN_ERR(content->write(content->write_baton,
2021 hunk_line->data, len, iterpool));
2024 if (eol_str)
2026 /* Use the EOL as it was read from the patch file,
2027 * unless the target's EOL style is set by svn:eol-style */
2028 if (content->eol_style != svn_subst_eol_style_none)
2029 eol_str = content->eol_str;
2031 len = strlen(eol_str);
2032 SVN_ERR(content->write(content->write_baton,
2033 eol_str, len, iterpool));
2037 while (! eof);
2038 svn_pool_destroy(iterpool);
2040 if (prop_name)
2041 target->has_prop_changes = TRUE;
2042 else
2043 target->has_text_changes = TRUE;
2045 return SVN_NO_ERROR;
2048 /* Use client context CTX to send a suitable notification for hunk HI,
2049 * using TARGET to determine the path. If the hunk is a property hunk,
2050 * PROP_NAME must be the name of the property, else NULL.
2051 * Use POOL for temporary allocations. */
2052 static svn_error_t *
2053 send_hunk_notification(const hunk_info_t *hi,
2054 const patch_target_t *target,
2055 const char *prop_name,
2056 const svn_client_ctx_t *ctx,
2057 apr_pool_t *pool)
2059 svn_wc_notify_t *notify;
2060 svn_wc_notify_action_t action;
2062 if (hi->already_applied)
2063 action = svn_wc_notify_patch_hunk_already_applied;
2064 else if (hi->rejected)
2065 action = svn_wc_notify_patch_rejected_hunk;
2066 else
2067 action = svn_wc_notify_patch_applied_hunk;
2069 notify = svn_wc_create_notify(target->local_abspath
2070 ? target->local_abspath
2071 : target->local_relpath,
2072 action, pool);
2073 notify->hunk_original_start =
2074 svn_diff_hunk_get_original_start(hi->hunk);
2075 notify->hunk_original_length =
2076 svn_diff_hunk_get_original_length(hi->hunk);
2077 notify->hunk_modified_start =
2078 svn_diff_hunk_get_modified_start(hi->hunk);
2079 notify->hunk_modified_length =
2080 svn_diff_hunk_get_modified_length(hi->hunk);
2081 notify->hunk_matched_line = hi->matched_line;
2082 notify->hunk_fuzz = hi->fuzz;
2083 notify->prop_name = prop_name;
2085 ctx->notify_func2(ctx->notify_baton2, notify, pool);
2087 return SVN_NO_ERROR;
2090 /* Use client context CTX to send a suitable notification for a patch TARGET.
2091 * Use POOL for temporary allocations. */
2092 static svn_error_t *
2093 send_patch_notification(const patch_target_t *target,
2094 const svn_client_ctx_t *ctx,
2095 apr_pool_t *pool)
2097 svn_wc_notify_t *notify;
2098 svn_wc_notify_action_t action;
2099 const char *notify_path;
2101 if (! ctx->notify_func2)
2102 return SVN_NO_ERROR;
2104 if (target->skipped)
2105 action = svn_wc_notify_skip;
2106 else if (target->deleted)
2107 action = svn_wc_notify_delete;
2108 else if (target->added || target->replaced || target->move_target_abspath)
2109 action = svn_wc_notify_add;
2110 else
2111 action = svn_wc_notify_patch;
2113 if (target->move_target_abspath)
2114 notify_path = target->move_target_abspath;
2115 else
2116 notify_path = target->local_abspath ? target->local_abspath
2117 : target->local_relpath;
2119 notify = svn_wc_create_notify(notify_path, action, pool);
2120 notify->kind = svn_node_file;
2122 if (action == svn_wc_notify_skip)
2124 if (target->db_kind == svn_node_none ||
2125 target->db_kind == svn_node_unknown)
2126 notify->content_state = svn_wc_notify_state_missing;
2127 else if (target->db_kind == svn_node_dir)
2128 notify->content_state = svn_wc_notify_state_obstructed;
2129 else
2130 notify->content_state = svn_wc_notify_state_unknown;
2132 else
2134 if (target->had_rejects)
2135 notify->content_state = svn_wc_notify_state_conflicted;
2136 else if (target->local_mods)
2137 notify->content_state = svn_wc_notify_state_merged;
2138 else if (target->has_text_changes)
2139 notify->content_state = svn_wc_notify_state_changed;
2141 if (target->had_prop_rejects)
2142 notify->prop_state = svn_wc_notify_state_conflicted;
2143 else if (target->has_prop_changes)
2144 notify->prop_state = svn_wc_notify_state_changed;
2147 ctx->notify_func2(ctx->notify_baton2, notify, pool);
2149 if (action == svn_wc_notify_patch)
2151 int i;
2152 apr_pool_t *iterpool;
2153 apr_hash_index_t *hash_index;
2155 iterpool = svn_pool_create(pool);
2156 for (i = 0; i < target->content->hunks->nelts; i++)
2158 const hunk_info_t *hi;
2160 svn_pool_clear(iterpool);
2162 hi = APR_ARRAY_IDX(target->content->hunks, i, hunk_info_t *);
2164 SVN_ERR(send_hunk_notification(hi, target, NULL /* prop_name */,
2165 ctx, iterpool));
2168 for (hash_index = apr_hash_first(pool, target->prop_targets);
2169 hash_index;
2170 hash_index = apr_hash_next(hash_index))
2172 prop_patch_target_t *prop_target;
2174 prop_target = apr_hash_this_val(hash_index);
2176 for (i = 0; i < prop_target->content->hunks->nelts; i++)
2178 const hunk_info_t *hi;
2180 svn_pool_clear(iterpool);
2182 hi = APR_ARRAY_IDX(prop_target->content->hunks, i,
2183 hunk_info_t *);
2185 /* Don't notify on the hunk level for added or deleted props. */
2186 if (prop_target->operation != svn_diff_op_added &&
2187 prop_target->operation != svn_diff_op_deleted)
2188 SVN_ERR(send_hunk_notification(hi, target, prop_target->name,
2189 ctx, iterpool));
2192 svn_pool_destroy(iterpool);
2195 if (target->move_target_abspath)
2197 /* Notify about deletion of move source. */
2198 notify = svn_wc_create_notify(target->local_abspath,
2199 svn_wc_notify_delete, pool);
2200 notify->kind = svn_node_file;
2201 ctx->notify_func2(ctx->notify_baton2, notify, pool);
2204 return SVN_NO_ERROR;
2207 /* Implements the callback for svn_sort__array. Puts hunks that match
2208 before hunks that do not match, puts hunks that match in order
2209 based on postion matched, puts hunks that do not match in order
2210 based on original position. */
2211 static int
2212 sort_matched_hunks(const void *a, const void *b)
2214 const hunk_info_t *item1 = *((const hunk_info_t * const *)a);
2215 const hunk_info_t *item2 = *((const hunk_info_t * const *)b);
2216 svn_boolean_t matched1 = !item1->rejected && !item1->already_applied;
2217 svn_boolean_t matched2 = !item2->rejected && !item2->already_applied;
2218 svn_linenum_t original1, original2;
2220 if (matched1 && matched2)
2222 /* Both match so use order matched in file. */
2223 if (item1->matched_line > item2->matched_line)
2224 return 1;
2225 else if (item1->matched_line == item2->matched_line)
2226 return 0;
2227 else
2228 return -1;
2230 else if (matched2)
2231 /* Only second matches, put it before first. */
2232 return 1;
2233 else if (matched1)
2234 /* Only first matches, put it before second. */
2235 return -1;
2237 /* Neither matches, sort by original_start. */
2238 original1 = svn_diff_hunk_get_original_start(item1->hunk);
2239 original2 = svn_diff_hunk_get_original_start(item2->hunk);
2240 if (original1 > original2)
2241 return 1;
2242 else if (original1 == original2)
2243 return 0;
2244 else
2245 return -1;
2249 /* Apply a PATCH to a working copy at ABS_WC_PATH and put the result
2250 * into temporary files, to be installed in the working copy later.
2251 * Return information about the patch target in *PATCH_TARGET, allocated
2252 * in RESULT_POOL. Use WC_CTX as the working copy context.
2253 * STRIP_COUNT specifies the number of leading path components
2254 * which should be stripped from target paths in the patch.
2255 * REMOVE_TEMPFILES, PATCH_FUNC, and PATCH_BATON as in svn_client_patch().
2256 * IGNORE_WHITESPACE tells whether whitespace should be considered when
2257 * doing the matching.
2258 * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
2259 * Do temporary allocations in SCRATCH_POOL. */
2260 static svn_error_t *
2261 apply_one_patch(patch_target_t **patch_target, svn_patch_t *patch,
2262 const char *abs_wc_path, svn_wc_context_t *wc_ctx,
2263 int strip_count,
2264 svn_boolean_t ignore_whitespace,
2265 svn_boolean_t remove_tempfiles,
2266 svn_cancel_func_t cancel_func,
2267 void *cancel_baton,
2268 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
2270 patch_target_t *target;
2271 apr_pool_t *iterpool;
2272 int i;
2273 static const svn_linenum_t MAX_FUZZ = 2;
2274 apr_hash_index_t *hash_index;
2275 svn_linenum_t previous_offset = 0;
2277 SVN_ERR(init_patch_target(&target, patch, abs_wc_path, wc_ctx, strip_count,
2278 remove_tempfiles, result_pool, scratch_pool));
2279 if (target->skipped)
2281 *patch_target = target;
2282 return SVN_NO_ERROR;
2285 iterpool = svn_pool_create(scratch_pool);
2286 /* Match hunks. */
2287 for (i = 0; i < patch->hunks->nelts; i++)
2289 svn_diff_hunk_t *hunk;
2290 hunk_info_t *hi;
2291 svn_linenum_t fuzz = 0;
2293 svn_pool_clear(iterpool);
2295 if (cancel_func)
2296 SVN_ERR(cancel_func(cancel_baton));
2298 hunk = APR_ARRAY_IDX(patch->hunks, i, svn_diff_hunk_t *);
2300 /* Determine the line the hunk should be applied at.
2301 * If no match is found initially, try with fuzz. */
2304 SVN_ERR(get_hunk_info(&hi, target, target->content, hunk, fuzz,
2305 previous_offset,
2306 ignore_whitespace,
2307 FALSE /* is_prop_hunk */,
2308 cancel_func, cancel_baton,
2309 result_pool, iterpool));
2310 fuzz++;
2312 while (hi->rejected && fuzz <= MAX_FUZZ && ! hi->already_applied);
2314 if (hi->matched_line)
2315 previous_offset
2316 = hi->matched_line - svn_diff_hunk_get_original_start(hunk);
2318 APR_ARRAY_PUSH(target->content->hunks, hunk_info_t *) = hi;
2321 /* Hunks are applied in the order determined by the matched line and
2322 this may be different from the order of the original lines. */
2323 svn_sort__array(target->content->hunks, sort_matched_hunks);
2325 /* Apply or reject hunks. */
2326 for (i = 0; i < target->content->hunks->nelts; i++)
2328 hunk_info_t *hi;
2330 svn_pool_clear(iterpool);
2332 if (cancel_func)
2333 SVN_ERR(cancel_func(cancel_baton));
2335 hi = APR_ARRAY_IDX(target->content->hunks, i, hunk_info_t *);
2336 if (hi->already_applied)
2337 continue;
2338 else if (hi->rejected)
2339 SVN_ERR(reject_hunk(target, target->content, hi->hunk,
2340 NULL /* prop_name */,
2341 iterpool));
2342 else
2343 SVN_ERR(apply_hunk(target, target->content, hi,
2344 NULL /* prop_name */, iterpool));
2347 if (target->kind_on_disk == svn_node_file)
2349 /* Copy any remaining lines to target. */
2350 SVN_ERR(copy_lines_to_target(target->content, 0, scratch_pool));
2351 if (! target->content->eof)
2353 /* We could not copy the entire target file to the temporary file,
2354 * and would truncate the target if we copied the temporary file
2355 * on top of it. Skip this target. */
2356 target->skipped = TRUE;
2360 /* Match property hunks. */
2361 for (hash_index = apr_hash_first(scratch_pool, patch->prop_patches);
2362 hash_index;
2363 hash_index = apr_hash_next(hash_index))
2365 svn_prop_patch_t *prop_patch;
2366 const char *prop_name;
2367 prop_patch_target_t *prop_target;
2369 prop_name = apr_hash_this_key(hash_index);
2370 prop_patch = apr_hash_this_val(hash_index);
2372 if (! strcmp(prop_name, SVN_PROP_SPECIAL))
2373 target->is_special = TRUE;
2375 /* We'll store matched hunks in prop_content. */
2376 prop_target = svn_hash_gets(target->prop_targets, prop_name);
2378 for (i = 0; i < prop_patch->hunks->nelts; i++)
2380 svn_diff_hunk_t *hunk;
2381 hunk_info_t *hi;
2382 svn_linenum_t fuzz = 0;
2384 svn_pool_clear(iterpool);
2386 if (cancel_func)
2387 SVN_ERR(cancel_func(cancel_baton));
2389 hunk = APR_ARRAY_IDX(prop_patch->hunks, i, svn_diff_hunk_t *);
2391 /* Determine the line the hunk should be applied at.
2392 * If no match is found initially, try with fuzz. */
2395 SVN_ERR(get_hunk_info(&hi, target, prop_target->content,
2396 hunk, fuzz, 0,
2397 ignore_whitespace,
2398 TRUE /* is_prop_hunk */,
2399 cancel_func, cancel_baton,
2400 result_pool, iterpool));
2401 fuzz++;
2403 while (hi->rejected && fuzz <= MAX_FUZZ && ! hi->already_applied);
2405 APR_ARRAY_PUSH(prop_target->content->hunks, hunk_info_t *) = hi;
2409 /* Apply or reject property hunks. */
2410 for (hash_index = apr_hash_first(scratch_pool, target->prop_targets);
2411 hash_index;
2412 hash_index = apr_hash_next(hash_index))
2414 prop_patch_target_t *prop_target;
2416 prop_target = apr_hash_this_val(hash_index);
2418 for (i = 0; i < prop_target->content->hunks->nelts; i++)
2420 hunk_info_t *hi;
2422 svn_pool_clear(iterpool);
2424 hi = APR_ARRAY_IDX(prop_target->content->hunks, i,
2425 hunk_info_t *);
2426 if (hi->already_applied)
2427 continue;
2428 else if (hi->rejected)
2429 SVN_ERR(reject_hunk(target, prop_target->content, hi->hunk,
2430 prop_target->name,
2431 iterpool));
2432 else
2433 SVN_ERR(apply_hunk(target, prop_target->content, hi,
2434 prop_target->name,
2435 iterpool));
2438 if (prop_target->content->existed)
2440 /* Copy any remaining lines to target. */
2441 SVN_ERR(copy_lines_to_target(prop_target->content, 0,
2442 scratch_pool));
2443 if (! prop_target->content->eof)
2445 /* We could not copy the entire target property to the
2446 * temporary file, and would truncate the target if we
2447 * copied the temporary file on top of it. Skip this target. */
2448 target->skipped = TRUE;
2453 svn_pool_destroy(iterpool);
2455 if (!target->is_symlink)
2457 /* Now close files we don't need any longer to get their contents
2458 * flushed to disk.
2459 * But we're not closing the reject file -- it still needed and
2460 * will be closed later in write_out_rejected_hunks(). */
2461 if (target->kind_on_disk == svn_node_file)
2462 SVN_ERR(svn_io_file_close(target->file, scratch_pool));
2464 SVN_ERR(svn_io_file_close(target->patched_file, scratch_pool));
2467 if (! target->skipped)
2469 apr_finfo_t working_file;
2470 apr_finfo_t patched_file;
2472 /* Get sizes of the patched temporary file and the working file.
2473 * We'll need those to figure out whether we should delete the
2474 * patched file. */
2475 SVN_ERR(svn_io_stat(&patched_file, target->patched_path,
2476 APR_FINFO_SIZE | APR_FINFO_LINK, scratch_pool));
2477 if (target->kind_on_disk == svn_node_file)
2478 SVN_ERR(svn_io_stat(&working_file, target->local_abspath,
2479 APR_FINFO_SIZE | APR_FINFO_LINK, scratch_pool));
2480 else
2481 working_file.size = 0;
2483 if (patched_file.size == 0 && working_file.size > 0)
2485 /* If a unidiff removes all lines from a file, that usually
2486 * means deletion, so we can confidently schedule the target
2487 * for deletion. In the rare case where the unidiff was really
2488 * meant to replace a file with an empty one, this may not
2489 * be desirable. But the deletion can easily be reverted and
2490 * creating an empty file manually is not exactly hard either. */
2491 target->deleted = (target->db_kind == svn_node_file);
2493 else if (patched_file.size == 0 && working_file.size == 0)
2495 /* The target was empty or non-existent to begin with
2496 * and no content was changed by patching.
2497 * Report this as skipped if it didn't exist, unless in the special
2498 * case of adding an empty file which has properties set on it or
2499 * adding an empty file with a 'git diff' */
2500 if (target->kind_on_disk == svn_node_none
2501 && ! target->has_prop_changes
2502 && ! target->added)
2503 target->skipped = TRUE;
2505 else if (patched_file.size > 0 && working_file.size == 0)
2507 /* The patch has created a file. */
2508 if (target->locally_deleted)
2509 target->replaced = TRUE;
2510 else if (target->db_kind == svn_node_none)
2511 target->added = TRUE;
2515 *patch_target = target;
2517 return SVN_NO_ERROR;
2520 /* Try to create missing parent directories for TARGET in the working copy
2521 * rooted at ABS_WC_PATH, and add the parents to version control.
2522 * If the parents cannot be created, mark the target as skipped.
2523 * Use client context CTX. If DRY_RUN is true, do not create missing
2524 * parents but issue notifications only.
2525 * Use SCRATCH_POOL for temporary allocations. */
2526 static svn_error_t *
2527 create_missing_parents(patch_target_t *target,
2528 const char *abs_wc_path,
2529 svn_client_ctx_t *ctx,
2530 svn_boolean_t dry_run,
2531 apr_pool_t *scratch_pool)
2533 const char *local_abspath;
2534 apr_array_header_t *components;
2535 int present_components;
2536 int i;
2537 apr_pool_t *iterpool;
2539 /* Check if we can safely create the target's parent. */
2540 local_abspath = abs_wc_path;
2541 components = svn_path_decompose(target->local_relpath, scratch_pool);
2542 present_components = 0;
2543 iterpool = svn_pool_create(scratch_pool);
2544 for (i = 0; i < components->nelts - 1; i++)
2546 const char *component;
2547 svn_node_kind_t wc_kind, disk_kind;
2549 svn_pool_clear(iterpool);
2551 component = APR_ARRAY_IDX(components, i, const char *);
2552 local_abspath = svn_dirent_join(local_abspath, component, scratch_pool);
2554 SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, local_abspath,
2555 FALSE, TRUE, iterpool));
2557 SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, iterpool));
2559 if (disk_kind == svn_node_file || wc_kind == svn_node_file)
2561 /* on-disk files and missing files are obstructions */
2562 target->skipped = TRUE;
2563 break;
2565 else if (disk_kind == svn_node_dir)
2567 if (wc_kind == svn_node_dir)
2568 present_components++;
2569 else
2571 target->skipped = TRUE;
2572 break;
2575 else if (wc_kind != svn_node_none)
2577 /* Node is missing */
2578 target->skipped = TRUE;
2579 break;
2581 else
2583 /* It's not a file, it's not a dir...
2584 Let's add a dir */
2585 break;
2588 if (! target->skipped)
2590 local_abspath = abs_wc_path;
2591 for (i = 0; i < present_components; i++)
2593 const char *component;
2594 component = APR_ARRAY_IDX(components, i, const char *);
2595 local_abspath = svn_dirent_join(local_abspath,
2596 component, scratch_pool);
2599 if (!dry_run && present_components < components->nelts - 1)
2600 SVN_ERR(svn_io_make_dir_recursively(
2601 svn_dirent_join(
2602 abs_wc_path,
2603 svn_relpath_dirname(target->local_relpath,
2604 scratch_pool),
2605 scratch_pool),
2606 scratch_pool));
2608 for (i = present_components; i < components->nelts - 1; i++)
2610 const char *component;
2612 svn_pool_clear(iterpool);
2614 component = APR_ARRAY_IDX(components, i, const char *);
2615 local_abspath = svn_dirent_join(local_abspath, component,
2616 scratch_pool);
2617 if (dry_run)
2619 if (ctx->notify_func2)
2621 /* Just do notification. */
2622 svn_wc_notify_t *notify;
2623 notify = svn_wc_create_notify(local_abspath,
2624 svn_wc_notify_add,
2625 iterpool);
2626 notify->kind = svn_node_dir;
2627 ctx->notify_func2(ctx->notify_baton2, notify,
2628 iterpool);
2631 else
2633 /* Create the missing component and add it
2634 * to version control. Allow cancellation since we
2635 * have not modified the working copy yet for this
2636 * target. */
2638 if (ctx->cancel_func)
2639 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
2641 SVN_ERR(svn_wc_add_from_disk3(ctx->wc_ctx, local_abspath,
2642 NULL /*props*/,
2643 FALSE /* skip checks */,
2644 ctx->notify_func2, ctx->notify_baton2,
2645 iterpool));
2650 svn_pool_destroy(iterpool);
2651 return SVN_NO_ERROR;
2654 /* Install a patched TARGET into the working copy at ABS_WC_PATH.
2655 * Use client context CTX to retrieve WC_CTX, and possibly doing
2656 * notifications. If DRY_RUN is TRUE, don't modify the working copy.
2657 * Do temporary allocations in POOL. */
2658 static svn_error_t *
2659 install_patched_target(patch_target_t *target, const char *abs_wc_path,
2660 svn_client_ctx_t *ctx, svn_boolean_t dry_run,
2661 apr_pool_t *pool)
2663 if (target->deleted)
2665 if (! dry_run)
2667 /* Schedule the target for deletion. Suppress
2668 * notification, we'll do it manually in a minute
2669 * because we also need to notify during dry-run.
2670 * Also suppress cancellation, because we'd rather
2671 * notify about what we did before aborting. */
2672 SVN_ERR(svn_wc_delete4(ctx->wc_ctx, target->local_abspath,
2673 FALSE /* keep_local */, FALSE,
2674 NULL, NULL, NULL, NULL, pool));
2677 else
2679 svn_node_kind_t parent_db_kind;
2680 if (target->added || target->replaced)
2682 const char *parent_abspath;
2684 parent_abspath = svn_dirent_dirname(target->local_abspath,
2685 pool);
2686 /* If the target's parent directory does not yet exist
2687 * we need to create it before we can copy the patched
2688 * result in place. */
2689 SVN_ERR(svn_wc_read_kind2(&parent_db_kind, ctx->wc_ctx,
2690 parent_abspath, FALSE, FALSE, pool));
2692 /* We can't add targets under nodes scheduled for delete, so add
2693 a new directory if needed. */
2694 if (parent_db_kind == svn_node_dir
2695 || parent_db_kind == svn_node_file)
2697 if (parent_db_kind != svn_node_dir)
2698 target->skipped = TRUE;
2699 else
2701 svn_node_kind_t disk_kind;
2703 SVN_ERR(svn_io_check_path(parent_abspath, &disk_kind, pool));
2704 if (disk_kind != svn_node_dir)
2705 target->skipped = TRUE;
2708 else
2709 SVN_ERR(create_missing_parents(target, abs_wc_path, ctx,
2710 dry_run, pool));
2713 else
2715 svn_node_kind_t wc_kind;
2717 /* The target should exist */
2718 SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx,
2719 target->local_abspath,
2720 FALSE, FALSE, pool));
2722 if (target->kind_on_disk == svn_node_none
2723 || wc_kind != target->kind_on_disk)
2725 target->skipped = TRUE;
2729 if (! dry_run && ! target->skipped)
2731 if (target->is_special)
2733 svn_stream_t *stream;
2734 svn_stream_t *patched_stream;
2736 SVN_ERR(svn_stream_open_readonly(&patched_stream,
2737 target->patched_path,
2738 pool, pool));
2739 SVN_ERR(svn_subst_create_specialfile(&stream,
2740 target->local_abspath,
2741 pool, pool));
2742 SVN_ERR(svn_stream_copy3(patched_stream, stream,
2743 ctx->cancel_func, ctx->cancel_baton,
2744 pool));
2746 else
2748 svn_boolean_t repair_eol;
2750 /* Copy the patched file on top of the target file.
2751 * Always expand keywords in the patched file, but repair EOL
2752 * only if svn:eol-style dictates a particular style. */
2753 repair_eol = (target->content->eol_style ==
2754 svn_subst_eol_style_fixed ||
2755 target->content->eol_style ==
2756 svn_subst_eol_style_native);
2758 SVN_ERR(svn_subst_copy_and_translate4(
2759 target->patched_path,
2760 target->move_target_abspath
2761 ? target->move_target_abspath
2762 : target->local_abspath,
2763 target->content->eol_str, repair_eol,
2764 target->content->keywords,
2765 TRUE /* expand */, FALSE /* special */,
2766 ctx->cancel_func, ctx->cancel_baton, pool));
2769 if (target->added || target->replaced)
2771 /* The target file didn't exist previously,
2772 * so add it to version control.
2773 * Suppress notification, we'll do that later (and also
2774 * during dry-run). Don't allow cancellation because
2775 * we'd rather notify about what we did before aborting. */
2776 SVN_ERR(svn_wc_add_from_disk3(ctx->wc_ctx, target->local_abspath,
2777 NULL /*props*/,
2778 FALSE /* skip checks */,
2779 NULL, NULL, pool));
2782 /* Restore the target's executable bit if necessary. */
2783 SVN_ERR(svn_io_set_file_executable(target->move_target_abspath
2784 ? target->move_target_abspath
2785 : target->local_abspath,
2786 target->executable,
2787 FALSE, pool));
2789 if (target->move_target_abspath)
2791 /* ### Copying the patched content to the move target location,
2792 * performing the move in meta-data, and removing the file at
2793 * the move source should be one atomic operation. */
2795 /* ### Create missing parents. */
2797 /* Perform the move in meta-data. */
2798 SVN_ERR(svn_wc__move2(ctx->wc_ctx,
2799 target->local_abspath,
2800 target->move_target_abspath,
2801 TRUE, /* metadata_only */
2802 FALSE, /* allow_mixed_revisions */
2803 NULL, NULL, NULL, NULL,
2804 pool));
2806 /* Delete the patch target's old location from disk. */
2807 SVN_ERR(svn_io_remove_file2(target->local_abspath, FALSE, pool));
2812 return SVN_NO_ERROR;
2815 /* Write out rejected hunks, if any, to TARGET->REJECT_PATH. If DRY_RUN is
2816 * TRUE, don't modify the working copy.
2817 * Do temporary allocations in POOL.
2819 static svn_error_t *
2820 write_out_rejected_hunks(patch_target_t *target,
2821 svn_boolean_t dry_run,
2822 apr_pool_t *pool)
2824 SVN_ERR(svn_io_file_close(target->reject_file, pool));
2826 if (! dry_run && (target->had_rejects || target->had_prop_rejects))
2828 /* Write out rejected hunks, if any. */
2829 SVN_ERR(svn_io_copy_file(target->reject_path,
2830 apr_psprintf(pool, "%s.svnpatch.rej",
2831 target->local_abspath),
2832 FALSE, pool));
2833 /* ### TODO mark file as conflicted. */
2835 return SVN_NO_ERROR;
2838 /* Install the patched properties for TARGET. Use client context CTX to
2839 * retrieve WC_CTX. If DRY_RUN is TRUE, don't modify the working copy.
2840 * Do temporary allocations in SCRATCH_POOL. */
2841 static svn_error_t *
2842 install_patched_prop_targets(patch_target_t *target,
2843 svn_client_ctx_t *ctx, svn_boolean_t dry_run,
2844 apr_pool_t *scratch_pool)
2846 apr_hash_index_t *hi;
2847 apr_pool_t *iterpool;
2849 iterpool = svn_pool_create(scratch_pool);
2851 for (hi = apr_hash_first(scratch_pool, target->prop_targets);
2853 hi = apr_hash_next(hi))
2855 prop_patch_target_t *prop_target = apr_hash_this_val(hi);
2856 const svn_string_t *prop_val;
2857 svn_error_t *err;
2859 svn_pool_clear(iterpool);
2861 if (ctx->cancel_func)
2862 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
2864 /* For a deleted prop we only set the value to NULL. */
2865 if (prop_target->operation == svn_diff_op_deleted)
2867 if (! dry_run)
2868 SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, target->local_abspath,
2869 prop_target->name, NULL, svn_depth_empty,
2870 TRUE /* skip_checks */,
2871 NULL /* changelist_filter */,
2872 NULL, NULL /* cancellation */,
2873 NULL, NULL /* notification */,
2874 iterpool));
2875 continue;
2878 /* If the patch target doesn't exist yet, the patch wants to add an
2879 * empty file with properties set on it. So create an empty file and
2880 * add it to version control. But if the patch was in the 'git format'
2881 * then the file has already been added.
2883 * ### How can we tell whether the patch really wanted to create
2884 * ### an empty directory? */
2885 if (! target->has_text_changes
2886 && target->kind_on_disk == svn_node_none
2887 && ! target->added)
2889 if (! dry_run)
2891 SVN_ERR(svn_io_file_create_empty(target->local_abspath,
2892 scratch_pool));
2893 SVN_ERR(svn_wc_add_from_disk3(ctx->wc_ctx, target->local_abspath,
2894 NULL /*props*/,
2895 FALSE /* skip checks */,
2896 /* suppress notification */
2897 NULL, NULL,
2898 iterpool));
2900 target->added = TRUE;
2903 /* Attempt to set the property, and reject all hunks if this
2904 fails. If the property had a non-empty value, but now has
2905 an empty one, we'll just delete the property altogether. */
2906 if (prop_target->value && prop_target->value->len
2907 && prop_target->patched_value && !prop_target->patched_value->len)
2908 prop_val = NULL;
2909 else
2910 prop_val = svn_stringbuf__morph_into_string(prop_target->patched_value);
2912 if (dry_run)
2914 const svn_string_t *canon_propval;
2916 err = svn_wc_canonicalize_svn_prop(&canon_propval,
2917 prop_target->name,
2918 prop_val, target->local_abspath,
2919 target->db_kind,
2920 TRUE, /* ### Skipping checks */
2921 NULL, NULL,
2922 iterpool);
2924 else
2926 err = svn_wc_prop_set4(ctx->wc_ctx, target->local_abspath,
2927 prop_target->name, prop_val, svn_depth_empty,
2928 TRUE /* skip_checks */,
2929 NULL /* changelist_filter */,
2930 NULL, NULL /* cancellation */,
2931 NULL, NULL /* notification */,
2932 iterpool);
2935 if (err)
2937 /* ### The errors which svn_wc_canonicalize_svn_prop() will
2938 * ### return aren't documented. */
2939 if (err->apr_err == SVN_ERR_ILLEGAL_TARGET ||
2940 err->apr_err == SVN_ERR_NODE_UNEXPECTED_KIND ||
2941 err->apr_err == SVN_ERR_IO_UNKNOWN_EOL ||
2942 err->apr_err == SVN_ERR_BAD_MIME_TYPE ||
2943 err->apr_err == SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION)
2945 int i;
2947 svn_error_clear(err);
2949 for (i = 0; i < prop_target->content->hunks->nelts; i++)
2951 hunk_info_t *hunk_info;
2953 hunk_info = APR_ARRAY_IDX(prop_target->content->hunks,
2954 i, hunk_info_t *);
2955 hunk_info->rejected = TRUE;
2956 SVN_ERR(reject_hunk(target, prop_target->content,
2957 hunk_info->hunk, prop_target->name,
2958 iterpool));
2961 else
2962 return svn_error_trace(err);
2967 svn_pool_destroy(iterpool);
2969 return SVN_NO_ERROR;
2972 /* Baton for can_delete_callback */
2973 struct can_delete_baton_t
2975 svn_boolean_t must_keep;
2976 const apr_array_header_t *targets_info;
2977 const char *local_abspath;
2980 /* Implements svn_wc_status_func4_t. */
2981 static svn_error_t *
2982 can_delete_callback(void *baton,
2983 const char *abspath,
2984 const svn_wc_status3_t *status,
2985 apr_pool_t *pool)
2987 struct can_delete_baton_t *cb = baton;
2988 int i;
2990 switch(status->node_status)
2992 case svn_wc_status_none:
2993 case svn_wc_status_deleted:
2994 return SVN_NO_ERROR;
2996 default:
2997 if (! strcmp(cb->local_abspath, abspath))
2998 return SVN_NO_ERROR; /* Only interested in descendants */
3000 for (i = 0; i < cb->targets_info->nelts; i++)
3002 const patch_target_info_t *target_info =
3003 APR_ARRAY_IDX(cb->targets_info, i, const patch_target_info_t *);
3005 if (! strcmp(target_info->local_abspath, abspath))
3007 if (target_info->deleted)
3008 return SVN_NO_ERROR;
3010 break; /* Cease invocation; must keep */
3014 cb->must_keep = TRUE;
3016 return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL);
3020 static svn_error_t *
3021 check_ancestor_delete(const char *deleted_target,
3022 apr_array_header_t *targets_info,
3023 const char *apply_root,
3024 svn_boolean_t dry_run,
3025 svn_client_ctx_t *ctx,
3026 apr_pool_t *result_pool,
3027 apr_pool_t *scratch_pool)
3029 struct can_delete_baton_t cb;
3030 svn_error_t *err;
3031 apr_array_header_t *ignores;
3032 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
3034 const char *dir_abspath = svn_dirent_dirname(deleted_target, scratch_pool);
3036 SVN_ERR(svn_wc_get_default_ignores(&ignores, ctx->config, scratch_pool));
3038 while (svn_dirent_is_child(apply_root, dir_abspath, iterpool))
3040 svn_pool_clear(iterpool);
3042 cb.local_abspath = dir_abspath;
3043 cb.must_keep = FALSE;
3044 cb.targets_info = targets_info;
3046 err = svn_wc_walk_status(ctx->wc_ctx, dir_abspath, svn_depth_infinity,
3047 TRUE, FALSE, FALSE, ignores,
3048 can_delete_callback, &cb,
3049 ctx->cancel_func, ctx->cancel_baton,
3050 iterpool);
3052 if (err)
3054 if (err->apr_err != SVN_ERR_CEASE_INVOCATION)
3055 return svn_error_trace(err);
3057 svn_error_clear(err);
3060 if (cb.must_keep)
3062 break;
3065 if (! dry_run)
3067 SVN_ERR(svn_wc_delete4(ctx->wc_ctx, dir_abspath, FALSE, FALSE,
3068 ctx->cancel_func, ctx->cancel_baton,
3069 NULL, NULL,
3070 scratch_pool));
3074 patch_target_info_t *pti = apr_pcalloc(result_pool, sizeof(*pti));
3076 pti->local_abspath = apr_pstrdup(result_pool, dir_abspath);
3077 pti->deleted = TRUE;
3079 APR_ARRAY_PUSH(targets_info, patch_target_info_t *) = pti;
3083 if (ctx->notify_func2)
3085 svn_wc_notify_t *notify;
3087 notify = svn_wc_create_notify(dir_abspath, svn_wc_notify_delete,
3088 iterpool);
3089 notify->kind = svn_node_dir;
3091 ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
3094 /* And check if we must also delete the parent */
3095 dir_abspath = svn_dirent_dirname(dir_abspath, scratch_pool);
3098 svn_pool_destroy(iterpool);
3100 return SVN_NO_ERROR;
3103 /* This function is the main entry point into the patch code. */
3104 static svn_error_t *
3105 apply_patches(/* The path to the patch file. */
3106 const char *patch_abspath,
3107 /* The abspath to the working copy the patch should be applied to. */
3108 const char *abs_wc_path,
3109 /* Indicates whether we're doing a dry run. */
3110 svn_boolean_t dry_run,
3111 /* Number of leading components to strip from patch target paths. */
3112 int strip_count,
3113 /* Whether to apply the patch in reverse. */
3114 svn_boolean_t reverse,
3115 /* Whether to ignore whitespace when matching context lines. */
3116 svn_boolean_t ignore_whitespace,
3117 /* As in svn_client_patch(). */
3118 svn_boolean_t remove_tempfiles,
3119 /* As in svn_client_patch(). */
3120 svn_client_patch_func_t patch_func,
3121 void *patch_baton,
3122 /* The client context. */
3123 svn_client_ctx_t *ctx,
3124 apr_pool_t *scratch_pool)
3126 svn_patch_t *patch;
3127 apr_pool_t *iterpool;
3128 svn_patch_file_t *patch_file;
3129 apr_array_header_t *targets_info;
3131 /* Try to open the patch file. */
3132 SVN_ERR(svn_diff_open_patch_file(&patch_file, patch_abspath, scratch_pool));
3134 /* Apply patches. */
3135 targets_info = apr_array_make(scratch_pool, 0,
3136 sizeof(patch_target_info_t *));
3137 iterpool = svn_pool_create(scratch_pool);
3140 svn_pool_clear(iterpool);
3142 if (ctx->cancel_func)
3143 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
3145 SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file,
3146 reverse, ignore_whitespace,
3147 iterpool, iterpool));
3148 if (patch)
3150 patch_target_t *target;
3151 svn_boolean_t filtered = FALSE;
3153 SVN_ERR(apply_one_patch(&target, patch, abs_wc_path,
3154 ctx->wc_ctx, strip_count,
3155 ignore_whitespace, remove_tempfiles,
3156 ctx->cancel_func, ctx->cancel_baton,
3157 iterpool, iterpool));
3159 if (!target->skipped && patch_func)
3161 SVN_ERR(patch_func(patch_baton, &filtered,
3162 target->canon_path_from_patchfile,
3163 target->patched_path, target->reject_path,
3164 iterpool));
3167 if (! filtered)
3169 /* Save info we'll still need when we're done patching. */
3170 patch_target_info_t *target_info =
3171 apr_pcalloc(scratch_pool, sizeof(patch_target_info_t));
3172 target_info->local_abspath = apr_pstrdup(scratch_pool,
3173 target->local_abspath);
3174 target_info->deleted = target->deleted;
3176 if (! target->skipped)
3178 APR_ARRAY_PUSH(targets_info,
3179 patch_target_info_t *) = target_info;
3181 if (target->has_text_changes
3182 || target->added
3183 || target->move_target_abspath
3184 || target->deleted)
3185 SVN_ERR(install_patched_target(target, abs_wc_path,
3186 ctx, dry_run, iterpool));
3188 if (target->has_prop_changes && (!target->deleted))
3189 SVN_ERR(install_patched_prop_targets(target, ctx,
3190 dry_run, iterpool));
3192 SVN_ERR(write_out_rejected_hunks(target, dry_run, iterpool));
3194 SVN_ERR(send_patch_notification(target, ctx, iterpool));
3196 if (target->deleted && !target->skipped)
3198 SVN_ERR(check_ancestor_delete(target_info->local_abspath,
3199 targets_info, abs_wc_path,
3200 dry_run, ctx,
3201 scratch_pool, iterpool));
3206 while (patch);
3208 SVN_ERR(svn_diff_close_patch_file(patch_file, iterpool));
3209 svn_pool_destroy(iterpool);
3211 return SVN_NO_ERROR;
3214 svn_error_t *
3215 svn_client_patch(const char *patch_abspath,
3216 const char *wc_dir_abspath,
3217 svn_boolean_t dry_run,
3218 int strip_count,
3219 svn_boolean_t reverse,
3220 svn_boolean_t ignore_whitespace,
3221 svn_boolean_t remove_tempfiles,
3222 svn_client_patch_func_t patch_func,
3223 void *patch_baton,
3224 svn_client_ctx_t *ctx,
3225 apr_pool_t *scratch_pool)
3227 svn_node_kind_t kind;
3229 if (strip_count < 0)
3230 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
3231 _("strip count must be positive"));
3233 if (svn_path_is_url(wc_dir_abspath))
3234 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3235 _("'%s' is not a local path"),
3236 svn_dirent_local_style(wc_dir_abspath,
3237 scratch_pool));
3239 SVN_ERR(svn_io_check_path(patch_abspath, &kind, scratch_pool));
3240 if (kind == svn_node_none)
3241 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3242 _("'%s' does not exist"),
3243 svn_dirent_local_style(patch_abspath,
3244 scratch_pool));
3245 if (kind != svn_node_file)
3246 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3247 _("'%s' is not a file"),
3248 svn_dirent_local_style(patch_abspath,
3249 scratch_pool));
3251 SVN_ERR(svn_io_check_path(wc_dir_abspath, &kind, scratch_pool));
3252 if (kind == svn_node_none)
3253 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3254 _("'%s' does not exist"),
3255 svn_dirent_local_style(wc_dir_abspath,
3256 scratch_pool));
3257 if (kind != svn_node_dir)
3258 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3259 _("'%s' is not a directory"),
3260 svn_dirent_local_style(wc_dir_abspath,
3261 scratch_pool));
3263 SVN_WC__CALL_WITH_WRITE_LOCK(
3264 apply_patches(patch_abspath, wc_dir_abspath, dry_run, strip_count,
3265 reverse, ignore_whitespace, remove_tempfiles,
3266 patch_func, patch_baton, ctx, scratch_pool),
3267 ctx->wc_ctx, wc_dir_abspath, FALSE /* lock_anchor */, scratch_pool);
3268 return SVN_NO_ERROR;