1 /*
2 * patch.c: patch application support
3 *
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
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
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 * ====================================================================
22 */
23
24 /* ==================================================================== */
25
26
27
28 /*** Includes. ***/
29
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"
44
45 #include "svn_private_config.h"
46 #include "private/svn_eol_private.h"
47 #include "private/svn_wc_private.h"
48 #include "private/svn_dep_compat.h"
49 #include "private/svn_diff_private.h"
50 #include "private/svn_string_private.h"
51 #include "private/svn_subr_private.h"
52 #include "private/svn_sorts_private.h"
53
54 typedef struct hunk_info_t {
55 /* The hunk. */
56 svn_diff_hunk_t *hunk;
57
58 /* The line where the hunk matched in the target file. */
59 svn_linenum_t matched_line;
60
61 /* Whether this hunk has been rejected. */
62 svn_boolean_t rejected;
63
64 /* Whether this hunk has already been applied (either manually
65 * or by an earlier run of patch). */
66 svn_boolean_t already_applied;
67
68 /* The fuzz factor used when matching this hunk, i.e. how many
69 * lines of leading and trailing context to ignore during matching. */
70 svn_linenum_t match_fuzz;
71
72 /* match_fuzz + the penalty caused by bad patch files */
73 svn_linenum_t report_fuzz;
74 } hunk_info_t;
75
76 /* A struct carrying information related to the patched and unpatched
77 * content of a target, be it a property or the text of a file. */
78 typedef struct target_content_t {
79 /* Indicates whether unpatched content existed prior to patching. */
80 svn_boolean_t existed;
81
82 /* The line last read from the unpatched content. */
83 svn_linenum_t current_line;
84
85 /* The EOL-style of the unpatched content. Either 'none', 'fixed',
86 * or 'native'. See the documentation of svn_subst_eol_style_t. */
87 svn_subst_eol_style_t eol_style;
88
89 /* If the EOL_STYLE above is not 'none', this is the EOL string
90 * corresponding to the EOL-style. Else, it is the EOL string the
91 * last line read from the target file was using. */
92 const char *eol_str;
93
94 /* An array containing apr_off_t offsets marking the beginning of
95 * each line in the unpatched content. */
96 apr_array_header_t *lines;
97
98 /* An array containing hunk_info_t structures for hunks already matched. */
99 apr_array_header_t *hunks;
100
101 /* True if end-of-file was reached while reading from the unpatched
102 * content. */
103 svn_boolean_t eof;
104
105 /* The keywords of the target. They will be contracted when reading
106 * unpatched content and expanded when writing patched content.
107 * When patching properties this hash is always empty. */
108 apr_hash_t *keywords;
109
110 /* A callback, with an associated baton, to read a line of unpatched
111 * content. */
112 svn_error_t *(*readline)(void *baton, svn_stringbuf_t **line,
113 const char **eol_str, svn_boolean_t *eof,
114 apr_pool_t *result_pool, apr_pool_t *scratch_pool);
115 void *read_baton;
116
117 /* A callback to get the current byte offset within the unpatched
118 * content. Uses the read baton. */
119 svn_error_t * (*tell)(void *baton, apr_off_t *offset,
120 apr_pool_t *scratch_pool);
121
122 /* A callback to seek to an offset within the unpatched content.
123 * Uses the read baton. */
124 svn_error_t * (*seek)(void *baton, apr_off_t offset,
125 apr_pool_t *scratch_pool);
126
127 /* A callback to write data to the patched content, with an
128 * associated baton. */
129 svn_error_t * (*write)(void *baton, const char *buf, apr_size_t len,
130 apr_pool_t *scratch_pool);
131 void *write_baton;
132
133 } target_content_t;
134
135 typedef struct prop_patch_target_t {
136
137 /* The name of the property */
138 const char *name;
139
140 /* The property value. This is NULL in case the property did not exist
141 * prior to patch application (see also CONTENT->existed).
142 * Note that the patch implementation does not support binary properties,
143 * so this string is not expected to contain embedded NUL characters. */
144 const svn_string_t *value;
145
146 /* The patched property value.
147 * This is equivalent to the target, except that in appropriate
148 * places it contains the modified text as it appears in the patch file. */
149 svn_stringbuf_t *patched_value;
150
151 /* All information that is specific to the content of the property. */
152 target_content_t *content;
153
154 /* Represents the operation performed on the property. It can be added,
155 * deleted or modified.
156 * ### Should we use flags instead since we're not using all enum values? */
157 svn_diff_operation_kind_t operation;
158
159 /* When true the property change won't be applied */
160 svn_boolean_t skipped;
161
162 /* ### Here we'll add flags telling if the prop was added, deleted,
163 * ### had_rejects, had_local_mods prior to patching and so on. */
164 } prop_patch_target_t;
165
166 typedef struct patch_target_t {
167 /* The target path as it appeared in the patch file,
168 * but in canonicalised form. */
169 const char *canon_path_from_patchfile;
170
171 /* The target path, relative to the working copy directory the
172 * patch is being applied to. A patch strip count applies to this
173 * and only this path. This is never NULL. */
174 const char *local_relpath;
175
176 /* The absolute path of the target on the filesystem.
177 * Any symlinks the path from the patch file may contain are resolved.
178 * Is not always known, so it may be NULL. */
179 const char *local_abspath;
180
181 /* The target file, read-only. This is NULL in case the target
182 * file did not exist prior to patch application (see also
183 * CONTENT->existed). */
184 apr_file_t *file;
185
186 /* The target file is a symlink */
187 svn_boolean_t is_symlink;
188
189 /* The patched file.
190 * This is equivalent to the target, except that in appropriate
191 * places it contains the modified text as it appears in the patch file.
192 * The data in this file is written in repository-normal form.
193 * EOL transformation and keyword contraction is performed when the
194 * patched result is installed in the working copy. */
195 apr_file_t *patched_file;
196
197 /* Path to the patched file. */
198 const char *patched_path;
199
200 /* Hunks that are rejected will be written to this stream. */
201 svn_stream_t *reject_stream;
202
203 /* Path to the reject file. */
204 const char *reject_path;
205
206 /* The node kind of the target as found in WC-DB prior
207 * to patch application. */
208 svn_node_kind_t db_kind;
209
210 /* The target's kind on disk prior to patch application. */
211 svn_node_kind_t kind_on_disk;
212
213 /* True if the target was locally deleted prior to patching. */
214 svn_boolean_t locally_deleted;
215
216 /* True if the target had to be skipped for some reason. */
217 svn_boolean_t skipped;
218
219 /* True if the reason for skipping is a local obstruction */
220 svn_boolean_t obstructed;
221
222 /* True if at least one hunk was rejected. */
223 svn_boolean_t had_rejects;
224
225 /* True if at least one property hunk was rejected. */
226 svn_boolean_t had_prop_rejects;
227
228 /* True if at least one hunk was handled as already applied */
229 svn_boolean_t had_already_applied;
230
231 /* True if at least one property hunk was handled as already applied */
232 svn_boolean_t had_prop_already_applied;
233
234 /* The operation on the target as set in the patch file */
235 svn_diff_operation_kind_t operation;
236
237 /* True if the target was added by the patch, which means that it did
238 * not exist on disk before patching and has content after patching. */
239 svn_boolean_t added;
240
241 /* True if the target ended up being deleted by the patch. */
242 svn_boolean_t deleted;
243
244 /* Set if the target is supposed to be moved by the patch.
245 * This applies to --git diffs which carry "rename from/to" headers. */
246 const char *move_target_abspath;
247
248 /* True if the target has the executable bit set. */
249 svn_boolean_t executable;
250
251 /* True if the patch changed the text of the target. */
252 svn_boolean_t has_text_changes;
253
254 /* True if the patch changed any of the properties of the target. */
255 svn_boolean_t has_prop_changes;
256
257 /* True if the patch contained a svn:special property. */
258 svn_boolean_t is_special;
259
260 /* All the information that is specific to the content of the target. */
261 target_content_t *content;
262
263 /* A hash table of prop_patch_target_t objects keyed by property names. */
264 apr_hash_t *prop_targets;
265
266 /* When TRUE, this patch uses the raw git symlink format instead of the
267 Subversion internal style format where links start with 'link '. */
268 svn_boolean_t git_symlink_format;
269
270 } patch_target_t;
271
272
273 /* A smaller struct containing a subset of patch_target_t.
274 * Carries the minimal amount of information we still need for a
275 * target after we're done patching it so we can free other resources. */
276 typedef struct patch_target_info_t {
277 const char *local_abspath;
278 svn_boolean_t deleted;
279 svn_boolean_t added;
280 } patch_target_info_t;
281
282 /* Check if LOCAL_ABSPATH is recorded as added in TARGETS_INFO */
283 static svn_boolean_t
target_is_added(const apr_array_header_t * targets_info,const char * local_abspath,apr_pool_t * scratch_pool)284 target_is_added(const apr_array_header_t *targets_info,
285 const char *local_abspath,
286 apr_pool_t *scratch_pool)
287 {
288 int i;
289
290 for (i = targets_info->nelts - 1; i >= 0; i--)
291 {
292 const patch_target_info_t *target_info =
293 APR_ARRAY_IDX(targets_info, i, const patch_target_info_t *);
294
295 const char *info = svn_dirent_skip_ancestor(target_info->local_abspath,
296 local_abspath);
297
298 if (info && !*info)
299 return target_info->added;
300 else if (info)
301 return FALSE;
302 }
303
304 return FALSE;
305 }
306
307 /* Check if LOCAL_ABSPATH or an ancestor is recorded as deleted in
308 TARGETS_INFO */
309 static svn_boolean_t
target_is_deleted(const apr_array_header_t * targets_info,const char * local_abspath,apr_pool_t * scratch_pool)310 target_is_deleted(const apr_array_header_t *targets_info,
311 const char *local_abspath,
312 apr_pool_t *scratch_pool)
313 {
314 int i;
315
316 for (i = targets_info->nelts - 1; i >= 0; i--)
317 {
318 const patch_target_info_t *target_info =
319 APR_ARRAY_IDX(targets_info, i, const patch_target_info_t *);
320
321 const char *info = svn_dirent_skip_ancestor(target_info->local_abspath,
322 local_abspath);
323
324 if (info)
325 return target_info->deleted;
326 }
327
328 return FALSE;
329 }
330
331
332 /* Strip STRIP_COUNT components from the front of PATH, returning
333 * the result in *RESULT, allocated in RESULT_POOL.
334 * Do temporary allocations in SCRATCH_POOL. */
335 static svn_error_t *
strip_path(const char ** result,const char * path,int strip_count,apr_pool_t * result_pool,apr_pool_t * scratch_pool)336 strip_path(const char **result, const char *path, int strip_count,
337 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
338 {
339 int i;
340 apr_array_header_t *components;
341 apr_array_header_t *stripped;
342
343 components = svn_path_decompose(path, scratch_pool);
344 if (strip_count > components->nelts)
345 return svn_error_createf(SVN_ERR_CLIENT_PATCH_BAD_STRIP_COUNT, NULL,
346 _("Cannot strip %u components from '%s'"),
347 strip_count,
348 svn_dirent_local_style(path, scratch_pool));
349
350 stripped = apr_array_make(scratch_pool, components->nelts - strip_count,
351 sizeof(const char *));
352 for (i = strip_count; i < components->nelts; i++)
353 {
354 const char *component;
355
356 component = APR_ARRAY_IDX(components, i, const char *);
357 APR_ARRAY_PUSH(stripped, const char *) = component;
358 }
359
360 *result = svn_path_compose(stripped, result_pool);
361
362 return SVN_NO_ERROR;
363 }
364
365 /* Obtain KEYWORDS, EOL_STYLE and EOL_STR for LOCAL_ABSPATH.
366 * WC_CTX is a context for the working copy the patch is applied to.
367 * Use RESULT_POOL for allocations of fields in TARGET.
368 * Use SCRATCH_POOL for all other allocations. */
369 static svn_error_t *
obtain_eol_and_keywords_for_file(apr_hash_t ** keywords,svn_subst_eol_style_t * eol_style,const char ** eol_str,svn_wc_context_t * wc_ctx,const char * local_abspath,apr_pool_t * result_pool,apr_pool_t * scratch_pool)370 obtain_eol_and_keywords_for_file(apr_hash_t **keywords,
371 svn_subst_eol_style_t *eol_style,
372 const char **eol_str,
373 svn_wc_context_t *wc_ctx,
374 const char *local_abspath,
375 apr_pool_t *result_pool,
376 apr_pool_t *scratch_pool)
377 {
378 apr_hash_t *props;
379 svn_string_t *keywords_val, *eol_style_val;
380
381 SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, local_abspath,
382 scratch_pool, scratch_pool));
383 keywords_val = svn_hash_gets(props, SVN_PROP_KEYWORDS);
384 if (keywords_val)
385 {
386 svn_revnum_t changed_rev;
387 apr_time_t changed_date;
388 const char *rev_str;
389 const char *author;
390 const char *url;
391 const char *repos_root_url;
392 const char *repos_relpath;
393
394 SVN_ERR(svn_wc__node_get_changed_info(&changed_rev,
395 &changed_date,
396 &author, wc_ctx,
397 local_abspath,
398 scratch_pool,
399 scratch_pool));
400 rev_str = apr_psprintf(scratch_pool, "%ld", changed_rev);
401 SVN_ERR(svn_wc__node_get_repos_info(NULL, &repos_relpath, &repos_root_url,
402 NULL,
403 wc_ctx, local_abspath,
404 scratch_pool, scratch_pool));
405 url = svn_path_url_add_component2(repos_root_url, repos_relpath,
406 scratch_pool);
407
408 SVN_ERR(svn_subst_build_keywords3(keywords,
409 keywords_val->data,
410 rev_str, url, repos_root_url,
411 changed_date,
412 author, result_pool));
413 }
414
415 eol_style_val = svn_hash_gets(props, SVN_PROP_EOL_STYLE);
416 if (eol_style_val)
417 {
418 svn_subst_eol_style_from_value(eol_style,
419 eol_str,
420 eol_style_val->data);
421 }
422
423 return SVN_NO_ERROR;
424 }
425
426 /* Resolve the exact path for a patch TARGET at path PATH_FROM_PATCHFILE,
427 * which is the path of the target as it appeared in the patch file.
428 * Put a canonicalized version of PATH_FROM_PATCHFILE into
429 * TARGET->CANON_PATH_FROM_PATCHFILE.
430 * WC_CTX is a context for the working copy the patch is applied to.
431 * If possible, determine TARGET->WC_PATH, TARGET->ABS_PATH, TARGET->KIND,
432 * TARGET->ADDED, and TARGET->PARENT_DIR_EXISTS.
433 * Indicate in TARGET->SKIPPED whether the target should be skipped.
434 * STRIP_COUNT specifies the number of leading path components
435 * which should be stripped from target paths in the patch.
436 * HAS_TEXT_CHANGES specifies whether the target path will have some text
437 * changes applied, implying that the target should be a file and not a
438 * directory.
439 * Use RESULT_POOL for allocations of fields in TARGET.
440 * Use SCRATCH_POOL for all other allocations. */
441 static svn_error_t *
resolve_target_path(patch_target_t * target,const char * path_from_patchfile,const char * root_abspath,int strip_count,svn_boolean_t has_text_changes,svn_boolean_t follow_moves,svn_wc_context_t * wc_ctx,const apr_array_header_t * targets_info,apr_pool_t * result_pool,apr_pool_t * scratch_pool)442 resolve_target_path(patch_target_t *target,
443 const char *path_from_patchfile,
444 const char *root_abspath,
445 int strip_count,
446 svn_boolean_t has_text_changes,
447 svn_boolean_t follow_moves,
448 svn_wc_context_t *wc_ctx,
449 const apr_array_header_t *targets_info,
450 apr_pool_t *result_pool,
451 apr_pool_t *scratch_pool)
452 {
453 const char *stripped_path;
454 svn_wc_status3_t *status;
455 svn_error_t *err;
456 svn_boolean_t under_root;
457
458 target->canon_path_from_patchfile = svn_dirent_internal_style(
459 path_from_patchfile, result_pool);
460
461 /* We can't handle text changes on the patch root dir. */
462 if (has_text_changes && target->canon_path_from_patchfile[0] == '\0')
463 {
464 /* An empty patch target path? What gives? Skip this. */
465 target->skipped = TRUE;
466 target->local_abspath = NULL;
467 target->local_relpath = "";
468 return SVN_NO_ERROR;
469 }
470
471 if (strip_count > 0)
472 SVN_ERR(strip_path(&stripped_path, target->canon_path_from_patchfile,
473 strip_count, result_pool, scratch_pool));
474 else
475 stripped_path = target->canon_path_from_patchfile;
476
477 if (svn_dirent_is_absolute(stripped_path))
478 {
479 target->local_relpath = svn_dirent_is_child(root_abspath,
480 stripped_path,
481 result_pool);
482
483 if (! target->local_relpath)
484 {
485 /* The target path is either outside of the working copy
486 * or it is the patch root itself. Skip it. */
487 target->skipped = TRUE;
488 target->local_abspath = NULL;
489 target->local_relpath = stripped_path;
490 return SVN_NO_ERROR;
491 }
492 }
493 else
494 {
495 target->local_relpath = stripped_path;
496 }
497
498 /* Make sure the path is secure to use. We want the target to be inside
499 * the locked tree and not be fooled by symlinks it might contain. */
500 SVN_ERR(svn_dirent_is_under_root(&under_root,
501 &target->local_abspath, root_abspath,
502 target->local_relpath, result_pool));
503
504 if (! under_root)
505 {
506 /* The target path is outside of the working copy. Skip it. */
507 target->skipped = TRUE;
508 target->local_abspath = NULL;
509 return SVN_NO_ERROR;
510 }
511
512 if (target_is_deleted(targets_info, target->local_abspath, scratch_pool))
513 {
514 target->locally_deleted = TRUE;
515 target->db_kind = svn_node_none;
516 return SVN_NO_ERROR;
517 }
518
519 /* Skip things we should not be messing with. */
520 err = svn_wc_status3(&status, wc_ctx, target->local_abspath,
521 result_pool, scratch_pool);
522 if (err)
523 {
524 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
525 return svn_error_trace(err);
526
527 svn_error_clear(err);
528
529 target->locally_deleted = TRUE;
530 target->db_kind = svn_node_none;
531 status = NULL;
532 }
533 else if (status->node_status == svn_wc_status_ignored ||
534 status->node_status == svn_wc_status_unversioned ||
535 status->node_status == svn_wc_status_missing ||
536 status->node_status == svn_wc_status_obstructed ||
537 status->conflicted)
538 {
539 target->skipped = TRUE;
540 target->obstructed = TRUE;
541 return SVN_NO_ERROR;
542 }
543 else if (status->node_status == svn_wc_status_deleted)
544 {
545 target->locally_deleted = TRUE;
546 }
547
548 if (status && (status->kind != svn_node_unknown))
549 target->db_kind = status->kind;
550 else
551 target->db_kind = svn_node_none;
552
553 SVN_ERR(svn_io_check_special_path(target->local_abspath,
554 &target->kind_on_disk, &target->is_symlink,
555 scratch_pool));
556
557 if (target->locally_deleted)
558 {
559 const char *moved_to_abspath = NULL;
560
561 if (follow_moves
562 && !target_is_added(targets_info, target->local_abspath,
563 scratch_pool))
564 {
565 SVN_ERR(svn_wc__node_was_moved_away(&moved_to_abspath, NULL,
566 wc_ctx, target->local_abspath,
567 result_pool, scratch_pool));
568 }
569
570 if (moved_to_abspath)
571 {
572 target->local_abspath = moved_to_abspath;
573 target->local_relpath = svn_dirent_skip_ancestor(root_abspath,
574 moved_to_abspath);
575
576 if (!target->local_relpath || target->local_relpath[0] == '\0')
577 {
578 /* The target path is outside of the patch area. Skip it. */
579 target->skipped = TRUE;
580 return SVN_NO_ERROR;
581 }
582
583 /* As far as we are concerned this target is not locally deleted. */
584 target->locally_deleted = FALSE;
585
586 SVN_ERR(svn_io_check_special_path(target->local_abspath,
587 &target->kind_on_disk,
588 &target->is_symlink,
589 scratch_pool));
590 }
591 else if (target->kind_on_disk != svn_node_none)
592 {
593 target->skipped = TRUE;
594 return SVN_NO_ERROR;
595 }
596 }
597
598 #ifndef HAVE_SYMLINK
599 if (target->kind_on_disk == svn_node_file
600 && !target->is_symlink
601 && !target->locally_deleted
602 && status->prop_status != svn_wc_status_none)
603 {
604 const svn_string_t *value;
605
606 SVN_ERR(svn_wc_prop_get2(&value, wc_ctx, target->local_abspath,
607 SVN_PROP_SPECIAL, scratch_pool, scratch_pool));
608
609 if (value)
610 target->is_symlink = TRUE;
611 }
612 #endif
613
614 return SVN_NO_ERROR;
615 }
616
617 /* Baton for reading from properties. */
618 typedef struct prop_read_baton_t {
619 const svn_string_t *value;
620 apr_off_t offset;
621 } prop_read_baton_t;
622
623 /* Allocate *STRINGBUF in RESULT_POOL, and read into it one line from
624 * the unpatched property value accessed via BATON.
625 * Reading stops either after a line-terminator was found, or if
626 * the property value runs out in which case *EOF is set to TRUE.
627 * The line-terminator is not stored in *STRINGBUF.
628 *
629 * If the line is empty or could not be read, *line is set to NULL.
630 *
631 * The line-terminator is detected automatically and stored in *EOL
632 * if EOL is not NULL. If the end of the property value is reached
633 * and does not end with a newline character, and EOL is not NULL,
634 * *EOL is set to NULL.
635 *
636 * SCRATCH_POOL is used for temporary allocations.
637 */
638 static svn_error_t *
readline_prop(void * baton,svn_stringbuf_t ** line,const char ** eol_str,svn_boolean_t * eof,apr_pool_t * result_pool,apr_pool_t * scratch_pool)639 readline_prop(void *baton, svn_stringbuf_t **line, const char **eol_str,
640 svn_boolean_t *eof, apr_pool_t *result_pool,
641 apr_pool_t *scratch_pool)
642 {
643 prop_read_baton_t *b = baton;
644 svn_stringbuf_t *str = NULL;
645 const char *c;
646 svn_boolean_t found_eof;
647
648 if ((apr_uint64_t)b->offset >= (apr_uint64_t)b->value->len)
649 {
650 *eol_str = NULL;
651 *eof = TRUE;
652 *line = NULL;
653 return SVN_NO_ERROR;
654 }
655
656 /* Read bytes into STR up to and including, but not storing,
657 * the next EOL sequence. */
658 *eol_str = NULL;
659 found_eof = FALSE;
660 do
661 {
662 c = b->value->data + b->offset;
663 b->offset++;
664
665 if (*c == '\0')
666 {
667 found_eof = TRUE;
668 break;
669 }
670 else if (*c == '\n')
671 {
672 *eol_str = "\n";
673 }
674 else if (*c == '\r')
675 {
676 *eol_str = "\r";
677 if (*(c + 1) == '\n')
678 {
679 *eol_str = "\r\n";
680 b->offset++;
681 }
682 }
683 else
684 {
685 if (str == NULL)
686 str = svn_stringbuf_create_ensure(80, result_pool);
687 svn_stringbuf_appendbyte(str, *c);
688 }
689
690 if (*eol_str)
691 break;
692 }
693 while (c < b->value->data + b->value->len);
694
695 if (eof)
696 *eof = found_eof && !(str && str->len > 0);
697 *line = str;
698
699 return SVN_NO_ERROR;
700 }
701
702 /* Return in *OFFSET the current byte offset for reading from the
703 * unpatched property value accessed via BATON.
704 * Use SCRATCH_POOL for temporary allocations. */
705 static svn_error_t *
tell_prop(void * baton,apr_off_t * offset,apr_pool_t * scratch_pool)706 tell_prop(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool)
707 {
708 prop_read_baton_t *b = baton;
709
710 *offset = b->offset;
711 return SVN_NO_ERROR;
712 }
713
714 /* Seek to the specified by OFFSET in the unpatched property value accessed
715 * via BATON. Use SCRATCH_POOL for temporary allocations. */
716 static svn_error_t *
seek_prop(void * baton,apr_off_t offset,apr_pool_t * scratch_pool)717 seek_prop(void *baton, apr_off_t offset, apr_pool_t *scratch_pool)
718 {
719 prop_read_baton_t *b = baton;
720
721 b->offset = offset;
722 return SVN_NO_ERROR;
723 }
724
725 /* Write LEN bytes from BUF into the patched property value accessed
726 * via BATON. Use SCRATCH_POOL for temporary allocations. */
727 static svn_error_t *
write_prop(void * baton,const char * buf,apr_size_t len,apr_pool_t * scratch_pool)728 write_prop(void *baton, const char *buf, apr_size_t len,
729 apr_pool_t *scratch_pool)
730 {
731 svn_stringbuf_t *patched_value = baton;
732
733 svn_stringbuf_appendbytes(patched_value, buf, len);
734 return SVN_NO_ERROR;
735 }
736
737 /* Initialize a PROP_TARGET structure for PROP_NAME on the patch target
738 * at LOCAL_ABSPATH. OPERATION indicates the operation performed on the
739 * property. Use working copy context WC_CTX.
740 * Allocate results in RESULT_POOL.
741 * Use SCRATCH_POOL for temporary allocations. */
742 static svn_error_t *
init_prop_target(prop_patch_target_t ** prop_target,const patch_target_t * target,const char * prop_name,svn_diff_operation_kind_t operation,svn_wc_context_t * wc_ctx,const char * local_abspath,apr_pool_t * result_pool,apr_pool_t * scratch_pool)743 init_prop_target(prop_patch_target_t **prop_target,
744 const patch_target_t *target,
745 const char *prop_name,
746 svn_diff_operation_kind_t operation,
747 svn_wc_context_t *wc_ctx,
748 const char *local_abspath,
749 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
750 {
751 prop_patch_target_t *new_prop_target;
752 target_content_t *content;
753 const svn_string_t *value;
754 prop_read_baton_t *prop_read_baton;
755
756 content = apr_pcalloc(result_pool, sizeof(*content));
757
758 /* All other fields are FALSE or NULL due to apr_pcalloc(). */
759 content->current_line = 1;
760 content->eol_style = svn_subst_eol_style_none;
761 content->lines = apr_array_make(result_pool, 0, sizeof(apr_off_t));
762 content->hunks = apr_array_make(result_pool, 0, sizeof(hunk_info_t *));
763 content->keywords = apr_hash_make(result_pool);
764
765 new_prop_target = apr_pcalloc(result_pool, sizeof(*new_prop_target));
766 new_prop_target->name = apr_pstrdup(result_pool, prop_name);
767 new_prop_target->operation = operation;
768 new_prop_target->content = content;
769
770 if (!(target->deleted || target->db_kind == svn_node_none))
771 SVN_ERR(svn_wc_prop_get2(&value, wc_ctx, local_abspath, prop_name,
772 result_pool, scratch_pool));
773 else
774 value = NULL;
775
776 content->existed = (value != NULL);
777 new_prop_target->value = value;
778 new_prop_target->patched_value = svn_stringbuf_create_empty(result_pool);
779
780
781 /* Wire up the read and write callbacks. */
782 prop_read_baton = apr_pcalloc(result_pool, sizeof(*prop_read_baton));
783 prop_read_baton->value = value;
784 prop_read_baton->offset = 0;
785 content->readline = readline_prop;
786 content->tell = tell_prop;
787 content->seek = seek_prop;
788 content->read_baton = prop_read_baton;
789 content->write = write_prop;
790 content->write_baton = new_prop_target->patched_value;
791
792 *prop_target = new_prop_target;
793
794 return SVN_NO_ERROR;
795 }
796
797 /* Allocate *STRINGBUF in RESULT_POOL, and read into it one line from
798 * the unpatched file content accessed via BATON.
799 * Reading stops either after a line-terminator was found,
800 * or if EOF is reached in which case *EOF is set to TRUE.
801 * The line-terminator is not stored in *STRINGBUF.
802 *
803 * If the line is empty or could not be read, *line is set to NULL.
804 *
805 * The line-terminator is detected automatically and stored in *EOL
806 * if EOL is not NULL. If EOF is reached and FILE does not end
807 * with a newline character, and EOL is not NULL, *EOL is set to NULL.
808 *
809 * SCRATCH_POOL is used for temporary allocations.
810 */
811 static svn_error_t *
readline_file(void * baton,svn_stringbuf_t ** line,const char ** eol_str,svn_boolean_t * eof,apr_pool_t * result_pool,apr_pool_t * scratch_pool)812 readline_file(void *baton, svn_stringbuf_t **line, const char **eol_str,
813 svn_boolean_t *eof, apr_pool_t *result_pool,
814 apr_pool_t *scratch_pool)
815 {
816 apr_file_t *file = baton;
817
818 SVN_ERR(svn_io_file_readline(file, line, eol_str, eof, APR_SIZE_MAX,
819 result_pool, scratch_pool));
820
821 if (!(*line)->len)
822 *line = NULL;
823 else
824 *eof = FALSE;
825
826 return SVN_NO_ERROR;
827 }
828
829 /* Return in *OFFSET the current byte offset for reading from the
830 * unpatched file content accessed via BATON.
831 * Use SCRATCH_POOL for temporary allocations. */
832 static svn_error_t *
tell_file(void * baton,apr_off_t * offset,apr_pool_t * scratch_pool)833 tell_file(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool)
834 {
835 apr_file_t *file = baton;
836
837 SVN_ERR(svn_io_file_get_offset(offset, file, scratch_pool));
838 return SVN_NO_ERROR;
839 }
840
841 /* Seek to the specified by OFFSET in the unpatched file content accessed
842 * via BATON. Use SCRATCH_POOL for temporary allocations. */
843 static svn_error_t *
seek_file(void * baton,apr_off_t offset,apr_pool_t * scratch_pool)844 seek_file(void *baton, apr_off_t offset, apr_pool_t *scratch_pool)
845 {
846 apr_file_t *file = baton;
847
848 SVN_ERR(svn_io_file_seek(file, APR_SET, &offset, scratch_pool));
849 return SVN_NO_ERROR;
850 }
851
852 /* Write LEN bytes from BUF into the patched file content accessed
853 * via BATON. Use SCRATCH_POOL for temporary allocations. */
854 static svn_error_t *
write_file(void * baton,const char * buf,apr_size_t len,apr_pool_t * scratch_pool)855 write_file(void *baton, const char *buf, apr_size_t len,
856 apr_pool_t *scratch_pool)
857 {
858 apr_file_t *file = baton;
859
860 SVN_ERR(svn_io_file_write_full(file, buf, len, &len, scratch_pool));
861 return SVN_NO_ERROR;
862 }
863
864 /* Symlinks appear in patches in their repository normal form, abstracted by
865 * the svn_subst_* module. The functions below enable patches to change the
866 * targets of symlinks.
867 */
868
869 /* Baton for the (readline|tell|seek|write)_symlink functions. */
870 struct symlink_baton_t
871 {
872 /* The path to the symlink on disk (not the path to the target of the link) */
873 const char *local_abspath;
874
875 /* Indicates whether the "normal form" of the symlink has been read. */
876 svn_boolean_t at_eof;
877 };
878
879 /* Allocate *STRINGBUF in RESULT_POOL, and store into it the "normal form"
880 * of the symlink accessed via BATON.
881 *
882 * Otherwise behaves like readline_file(), which see.
883 */
884 static svn_error_t *
readline_symlink(void * baton,svn_stringbuf_t ** line,const char ** eol_str,svn_boolean_t * eof,apr_pool_t * result_pool,apr_pool_t * scratch_pool)885 readline_symlink(void *baton, svn_stringbuf_t **line, const char **eol_str,
886 svn_boolean_t *eof, apr_pool_t *result_pool,
887 apr_pool_t *scratch_pool)
888 {
889 struct symlink_baton_t *sb = baton;
890
891 if (eof)
892 *eof = TRUE;
893 if (eol_str)
894 *eol_str = NULL;
895
896 if (sb->at_eof)
897 {
898 *line = NULL;
899 }
900 else
901 {
902 svn_stream_t *stream;
903 const apr_size_t len_hint = 64; /* arbitrary */
904
905 SVN_ERR(svn_subst_read_specialfile(&stream, sb->local_abspath,
906 scratch_pool, scratch_pool));
907 SVN_ERR(svn_stringbuf_from_stream(line, stream, len_hint, result_pool));
908 *eof = FALSE;
909 sb->at_eof = TRUE;
910 }
911
912 return SVN_NO_ERROR;
913 }
914
915 /* Identical to readline_symlink(), but returns symlink in raw format to
916 * allow patching links in git-style.
917 */
918 static svn_error_t *
readline_symlink_git(void * baton,svn_stringbuf_t ** line,const char ** eol_str,svn_boolean_t * eof,apr_pool_t * result_pool,apr_pool_t * scratch_pool)919 readline_symlink_git(void *baton, svn_stringbuf_t **line, const char **eol_str,
920 svn_boolean_t *eof, apr_pool_t *result_pool,
921 apr_pool_t *scratch_pool)
922 {
923 SVN_ERR(readline_symlink(baton, line, eol_str, eof,
924 result_pool, scratch_pool));
925
926 if (*line && (*line)->len > 5 && !strncmp((*line)->data, "link ", 5))
927 svn_stringbuf_remove(*line, 0, 5); /* Skip "link " */
928
929 return SVN_NO_ERROR;
930 }
931
932 /* Set *OFFSET to 1 or 0 depending on whether the "normal form" of
933 * the symlink has already been read. */
934 static svn_error_t *
tell_symlink(void * baton,apr_off_t * offset,apr_pool_t * scratch_pool)935 tell_symlink(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool)
936 {
937 struct symlink_baton_t *sb = baton;
938
939 *offset = sb->at_eof ? 1 : 0;
940 return SVN_NO_ERROR;
941 }
942
943 /* If offset is non-zero, mark the symlink as having been read in its
944 * "normal form". Else, mark the symlink as not having been read yet. */
945 static svn_error_t *
seek_symlink(void * baton,apr_off_t offset,apr_pool_t * scratch_pool)946 seek_symlink(void *baton, apr_off_t offset, apr_pool_t *scratch_pool)
947 {
948 struct symlink_baton_t *sb = baton;
949
950 sb->at_eof = (offset != 0);
951 return SVN_NO_ERROR;
952 }
953
954 /* Return a suitable filename for the target of PATCH.
955 * Examine the ``old'' and ``new'' file names, and choose the file name
956 * with the fewest path components, the shortest basename, and the shortest
957 * total file name length (in that order). In case of a tie, return the new
958 * filename. This heuristic is also used by Larry Wall's UNIX patch (except
959 * that it prompts for a filename in case of a tie).
960 * Additionally, for compatibility with git, if one of the filenames
961 * is "/dev/null", use the other filename. */
962 static const char *
choose_target_filename(const svn_patch_t * patch)963 choose_target_filename(const svn_patch_t *patch)
964 {
965 apr_size_t old;
966 apr_size_t new;
967
968 if (strcmp(patch->old_filename, "/dev/null") == 0)
969 return patch->new_filename;
970 if (strcmp(patch->new_filename, "/dev/null") == 0)
971 return patch->old_filename;
972
973 /* If the patch renames the target, use the old name while
974 * applying hunks. The target will be renamed to the new name
975 * after hunks have been applied. */
976 if (patch->operation == svn_diff_op_moved)
977 return patch->old_filename;
978
979 old = svn_path_component_count(patch->old_filename);
980 new = svn_path_component_count(patch->new_filename);
981
982 if (old == new)
983 {
984 old = strlen(svn_dirent_basename(patch->old_filename, NULL));
985 new = strlen(svn_dirent_basename(patch->new_filename, NULL));
986
987 if (old == new)
988 {
989 old = strlen(patch->old_filename);
990 new = strlen(patch->new_filename);
991 }
992 }
993
994 return (old < new) ? patch->old_filename : patch->new_filename;
995 }
996
997 /* Attempt to initialize a *PATCH_TARGET structure for a target file
998 * described by PATCH. Use working copy context WC_CTX.
999 * STRIP_COUNT specifies the number of leading path components
1000 * which should be stripped from target paths in the patch.
1001 * The patch target structure is allocated in RESULT_POOL, but if the target
1002 * should be skipped, PATCH_TARGET->SKIPPED is set and the target should be
1003 * treated as not fully initialized, e.g. the caller should not not do any
1004 * further operations on the target if it is marked to be skipped.
1005 * If REMOVE_TEMPFILES is TRUE, set up temporary files to be removed as
1006 * soon as they are no longer needed.
1007 * Use SCRATCH_POOL for all other allocations. */
1008 static svn_error_t *
init_patch_target(patch_target_t ** patch_target,const svn_patch_t * patch,const char * root_abspath,svn_wc_context_t * wc_ctx,int strip_count,svn_boolean_t remove_tempfiles,const apr_array_header_t * targets_info,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1009 init_patch_target(patch_target_t **patch_target,
1010 const svn_patch_t *patch,
1011 const char *root_abspath,
1012 svn_wc_context_t *wc_ctx, int strip_count,
1013 svn_boolean_t remove_tempfiles,
1014 const apr_array_header_t *targets_info,
1015 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
1016 {
1017 patch_target_t *target;
1018 target_content_t *content;
1019 svn_boolean_t has_text_changes = FALSE;
1020 svn_boolean_t follow_moves;
1021
1022 has_text_changes = ((patch->hunks && patch->hunks->nelts > 0)
1023 || patch->binary_patch);
1024
1025 content = apr_pcalloc(result_pool, sizeof(*content));
1026
1027 /* All other fields in content are FALSE or NULL due to apr_pcalloc().*/
1028 content->current_line = 1;
1029 content->eol_style = svn_subst_eol_style_none;
1030 content->lines = apr_array_make(result_pool, 0, sizeof(apr_off_t));
1031 content->hunks = apr_array_make(result_pool, 0, sizeof(hunk_info_t *));
1032 content->keywords = apr_hash_make(result_pool);
1033
1034 target = apr_pcalloc(result_pool, sizeof(*target));
1035
1036 /* All other fields in target are FALSE or NULL due to apr_pcalloc(). */
1037 target->db_kind = svn_node_none;
1038 target->kind_on_disk = svn_node_none;
1039 target->content = content;
1040 target->prop_targets = apr_hash_make(result_pool);
1041 target->operation = patch->operation;
1042
1043 if (patch->operation == svn_diff_op_added /* Allow replacing */
1044 || patch->operation == svn_diff_op_moved)
1045 {
1046 follow_moves = FALSE;
1047 }
1048 else if (patch->operation == svn_diff_op_unchanged
1049 && patch->hunks && patch->hunks->nelts == 1)
1050 {
1051 svn_diff_hunk_t *hunk = APR_ARRAY_IDX(patch->hunks, 0,
1052 svn_diff_hunk_t *);
1053
1054 follow_moves = (svn_diff_hunk_get_original_start(hunk) != 0);
1055 }
1056 else
1057 follow_moves = TRUE;
1058
1059 SVN_ERR(resolve_target_path(target, choose_target_filename(patch),
1060 root_abspath, strip_count, has_text_changes,
1061 follow_moves, wc_ctx, targets_info,
1062 result_pool, scratch_pool));
1063 *patch_target = target;
1064 if (! target->skipped)
1065 {
1066 if (patch->old_symlink_bit == svn_tristate_true
1067 || patch->new_symlink_bit == svn_tristate_true)
1068 {
1069 target->git_symlink_format = TRUE;
1070 }
1071
1072 /* ### Is it ok to set the operation of the target already here? Isn't
1073 * ### the target supposed to be marked with an operation after we have
1074 * ### determined that the changes will apply cleanly to the WC? Maybe
1075 * ### we should have kept the patch field in patch_target_t to be
1076 * ### able to distinguish between 'what the patch says we should do'
1077 * ### and 'what we can do with the given state of our WC'. */
1078 if (patch->operation == svn_diff_op_added)
1079 target->added = TRUE;
1080 else if (patch->operation == svn_diff_op_deleted)
1081 target->deleted = TRUE;
1082 else if (patch->operation == svn_diff_op_moved)
1083 {
1084 const char *move_target_path;
1085 const char *move_target_relpath;
1086 svn_boolean_t under_root;
1087 svn_boolean_t is_special;
1088 svn_node_kind_t kind_on_disk;
1089 svn_node_kind_t wc_kind;
1090
1091 move_target_path = svn_dirent_internal_style(patch->new_filename,
1092 scratch_pool);
1093
1094 if (strip_count > 0)
1095 SVN_ERR(strip_path(&move_target_path, move_target_path,
1096 strip_count, scratch_pool, scratch_pool));
1097
1098 if (svn_dirent_is_absolute(move_target_path))
1099 {
1100 move_target_relpath = svn_dirent_is_child(root_abspath,
1101 move_target_path,
1102 scratch_pool);
1103 if (! move_target_relpath)
1104 {
1105 /* The move target path is either outside of the working
1106 * copy or it is the working copy itself. Skip it. */
1107 target->skipped = TRUE;
1108 return SVN_NO_ERROR;
1109 }
1110 }
1111 else
1112 move_target_relpath = move_target_path;
1113
1114 /* Make sure the move target path is secure to use. */
1115 SVN_ERR(svn_dirent_is_under_root(&under_root,
1116 &target->move_target_abspath,
1117 root_abspath,
1118 move_target_relpath, result_pool));
1119 if (! under_root)
1120 {
1121 /* The target path is outside of the working copy. Skip it. */
1122 target->skipped = TRUE;
1123 target->move_target_abspath = NULL;
1124 return SVN_NO_ERROR;
1125 }
1126
1127 SVN_ERR(svn_io_check_special_path(target->move_target_abspath,
1128 &kind_on_disk, &is_special,
1129 scratch_pool));
1130 SVN_ERR(svn_wc_read_kind2(&wc_kind, wc_ctx,
1131 target->move_target_abspath,
1132 FALSE, FALSE, scratch_pool));
1133 if (wc_kind == svn_node_file || wc_kind == svn_node_dir)
1134 {
1135 /* The move target path already exists on disk. */
1136 svn_error_t *err;
1137 const char *moved_from_abspath;
1138
1139 err = svn_wc__node_was_moved_here(&moved_from_abspath, NULL,
1140 wc_ctx,
1141 target->move_target_abspath,
1142 scratch_pool, scratch_pool);
1143
1144 if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
1145 {
1146 svn_error_clear(err);
1147 err = NULL;
1148 moved_from_abspath = NULL;
1149 }
1150 else
1151 SVN_ERR(err);
1152
1153 if (moved_from_abspath && (strcmp(moved_from_abspath,
1154 target->local_abspath) == 0))
1155 {
1156 target->local_abspath = target->move_target_abspath;
1157 target->move_target_abspath = NULL;
1158 target->operation = svn_diff_op_modified;
1159 target->locally_deleted = FALSE;
1160 target->db_kind = wc_kind;
1161 target->kind_on_disk = kind_on_disk;
1162 target->is_special = is_special;
1163
1164 target->had_already_applied = TRUE; /* Make sure we notify */
1165 }
1166 else
1167 {
1168 target->skipped = TRUE;
1169 target->move_target_abspath = NULL;
1170 return SVN_NO_ERROR;
1171 }
1172
1173 }
1174 else if (kind_on_disk != svn_node_none
1175 || target_is_added(targets_info, target->move_target_abspath,
1176 scratch_pool))
1177 {
1178 target->skipped = TRUE;
1179 target->move_target_abspath = NULL;
1180 return SVN_NO_ERROR;
1181 }
1182 }
1183
1184 /* Create a temporary file to write the patched result to.
1185 * Also grab various bits of information about the file. */
1186 if (target->is_symlink)
1187 {
1188 struct symlink_baton_t *sb = apr_pcalloc(result_pool, sizeof(*sb));
1189 content->existed = TRUE;
1190
1191 sb->local_abspath = target->local_abspath;
1192
1193 /* Wire up the read callbacks. */
1194 content->read_baton = sb;
1195
1196 content->readline = target->git_symlink_format ? readline_symlink_git
1197 : readline_symlink;
1198 content->seek = seek_symlink;
1199 content->tell = tell_symlink;
1200 }
1201 else if (target->kind_on_disk == svn_node_file)
1202 {
1203 SVN_ERR(svn_io_file_open(&target->file, target->local_abspath,
1204 APR_READ | APR_BUFFERED,
1205 APR_OS_DEFAULT, result_pool));
1206 SVN_ERR(svn_io_is_file_executable(&target->executable,
1207 target->local_abspath,
1208 scratch_pool));
1209 SVN_ERR(obtain_eol_and_keywords_for_file(&content->keywords,
1210 &content->eol_style,
1211 &content->eol_str,
1212 wc_ctx,
1213 target->local_abspath,
1214 result_pool,
1215 scratch_pool));
1216 content->existed = TRUE;
1217
1218 /* Wire up the read callbacks. */
1219 content->readline = readline_file;
1220 content->seek = seek_file;
1221 content->tell = tell_file;
1222 content->read_baton = target->file;
1223 }
1224
1225 /* Open a temporary file to write the patched result to. */
1226 SVN_ERR(svn_io_open_unique_file3(&target->patched_file,
1227 &target->patched_path, NULL,
1228 remove_tempfiles ?
1229 svn_io_file_del_on_pool_cleanup :
1230 svn_io_file_del_none,
1231 result_pool, scratch_pool));
1232
1233 /* Put the write callback in place. */
1234 content->write = write_file;
1235 content->write_baton = target->patched_file;
1236
1237 /* Open a temporary stream to write rejected hunks to. */
1238 SVN_ERR(svn_stream_open_unique(&target->reject_stream,
1239 &target->reject_path, NULL,
1240 remove_tempfiles ?
1241 svn_io_file_del_on_pool_cleanup :
1242 svn_io_file_del_none,
1243 result_pool, scratch_pool));
1244
1245 /* Handle properties. */
1246 if (! target->skipped)
1247 {
1248 apr_hash_index_t *hi;
1249
1250 for (hi = apr_hash_first(result_pool, patch->prop_patches);
1251 hi;
1252 hi = apr_hash_next(hi))
1253 {
1254 const char *prop_name = apr_hash_this_key(hi);
1255 svn_prop_patch_t *prop_patch = apr_hash_this_val(hi);
1256 prop_patch_target_t *prop_target;
1257
1258 SVN_ERR(init_prop_target(&prop_target,
1259 target, prop_name,
1260 prop_patch->operation,
1261 wc_ctx, target->local_abspath,
1262 result_pool, scratch_pool));
1263 svn_hash_sets(target->prop_targets, prop_name, prop_target);
1264 }
1265
1266 /* Now, check for out-of-band mode changes and convert these in
1267 their Subversion equivalent properties. */
1268 if (patch->new_executable_bit != svn_tristate_unknown
1269 && patch->new_executable_bit != patch->old_executable_bit)
1270 {
1271 svn_diff_operation_kind_t operation;
1272
1273 if (patch->new_executable_bit == svn_tristate_true)
1274 operation = svn_diff_op_added;
1275 else if (patch->new_executable_bit == svn_tristate_false)
1276 {
1277 /* Made non-executable. */
1278 if (patch->old_executable_bit == svn_tristate_true)
1279 operation = svn_diff_op_deleted;
1280 else
1281 operation = svn_diff_op_unchanged;
1282 }
1283 else
1284 operation = svn_diff_op_unchanged;
1285
1286 if (operation != svn_diff_op_unchanged)
1287 {
1288 prop_patch_target_t *prop_target;
1289
1290 prop_target = svn_hash_gets(target->prop_targets,
1291 SVN_PROP_EXECUTABLE);
1292
1293 if (prop_target && operation != prop_target->operation)
1294 {
1295 return svn_error_createf(SVN_ERR_INVALID_INPUT, NULL,
1296 _("Invalid patch: specifies "
1297 "contradicting mode changes and "
1298 "%s changes (for '%s')"),
1299 SVN_PROP_EXECUTABLE,
1300 target->local_abspath);
1301 }
1302 else if (!prop_target)
1303 {
1304 SVN_ERR(init_prop_target(&prop_target,
1305 target, SVN_PROP_EXECUTABLE,
1306 operation,
1307 wc_ctx, target->local_abspath,
1308 result_pool, scratch_pool));
1309 svn_hash_sets(target->prop_targets, SVN_PROP_EXECUTABLE,
1310 prop_target);
1311 }
1312 }
1313 }
1314
1315 if (patch->new_symlink_bit != svn_tristate_unknown
1316 && patch->new_symlink_bit != patch->old_symlink_bit)
1317 {
1318 svn_diff_operation_kind_t operation;
1319
1320 if (patch->new_symlink_bit == svn_tristate_true)
1321 operation = svn_diff_op_added;
1322 else if (patch->new_symlink_bit == svn_tristate_false)
1323 {
1324 /* Made non-symlink. */
1325 if (patch->old_symlink_bit == svn_tristate_true)
1326 operation = svn_diff_op_deleted;
1327 else
1328 operation = svn_diff_op_unchanged;
1329 }
1330 else
1331 operation = svn_diff_op_unchanged;
1332
1333 if (operation != svn_diff_op_unchanged)
1334 {
1335 prop_patch_target_t *prop_target;
1336 prop_target = svn_hash_gets(target->prop_targets,
1337 SVN_PROP_SPECIAL);
1338
1339 if (prop_target && operation != prop_target->operation)
1340 {
1341 return svn_error_createf(SVN_ERR_INVALID_INPUT, NULL,
1342 _("Invalid patch: specifies "
1343 "contradicting mode changes and "
1344 "%s changes (for '%s')"),
1345 SVN_PROP_SPECIAL,
1346 target->local_abspath);
1347 }
1348 else if (!prop_target)
1349 {
1350 SVN_ERR(init_prop_target(&prop_target,
1351 target, SVN_PROP_SPECIAL,
1352 operation,
1353 wc_ctx, target->local_abspath,
1354 result_pool, scratch_pool));
1355 svn_hash_sets(target->prop_targets, SVN_PROP_SPECIAL,
1356 prop_target);
1357 }
1358 }
1359 }
1360 }
1361 }
1362
1363 if ((target->locally_deleted || target->db_kind == svn_node_none)
1364 && !target->added
1365 && target->operation == svn_diff_op_unchanged)
1366 {
1367 svn_boolean_t maybe_add = FALSE;
1368
1369 if (patch->hunks && patch->hunks->nelts == 1)
1370 {
1371 svn_diff_hunk_t *hunk = APR_ARRAY_IDX(patch->hunks, 0,
1372 svn_diff_hunk_t *);
1373
1374 if (svn_diff_hunk_get_original_start(hunk) == 0)
1375 maybe_add = TRUE;
1376 }
1377 else if (patch->prop_patches && apr_hash_count(patch->prop_patches))
1378 {
1379 apr_hash_index_t *hi;
1380 svn_boolean_t all_add = TRUE;
1381
1382 for (hi = apr_hash_first(result_pool, patch->prop_patches);
1383 hi;
1384 hi = apr_hash_next(hi))
1385 {
1386 svn_prop_patch_t *prop_patch = apr_hash_this_val(hi);
1387
1388 if (prop_patch->operation != svn_diff_op_added)
1389 {
1390 all_add = FALSE;
1391 break;
1392 }
1393 }
1394
1395 maybe_add = all_add;
1396 }
1397 /* Other implied types */
1398
1399 if (maybe_add)
1400 target->added = TRUE;
1401 }
1402 else if (!target->deleted && !target->added
1403 && target->operation == svn_diff_op_unchanged)
1404 {
1405 svn_boolean_t maybe_delete = FALSE;
1406
1407 if (patch->hunks && patch->hunks->nelts == 1)
1408 {
1409 svn_diff_hunk_t *hunk = APR_ARRAY_IDX(patch->hunks, 0,
1410 svn_diff_hunk_t *);
1411
1412 if (svn_diff_hunk_get_modified_start(hunk) == 0)
1413 maybe_delete = TRUE;
1414 }
1415
1416 /* Other implied types */
1417
1418 if (maybe_delete)
1419 target->deleted = TRUE;
1420 }
1421
1422 if (target->reject_stream != NULL)
1423 {
1424 /* The reject file needs a diff header. */
1425 const char *left_src = target->canon_path_from_patchfile;
1426 const char *right_src = target->canon_path_from_patchfile;
1427
1428 /* Handle moves specifically? */
1429 if (target->added)
1430 left_src = "/dev/null";
1431 if (target->deleted)
1432 right_src = "/dev/null";
1433
1434 SVN_ERR(svn_stream_printf(target->reject_stream, scratch_pool,
1435 "--- %s" APR_EOL_STR
1436 "+++ %s" APR_EOL_STR,
1437 left_src, right_src));
1438 }
1439
1440 return SVN_NO_ERROR;
1441 }
1442
1443 /* Read a *LINE from CONTENT. If the line has not been read before
1444 * mark the line in CONTENT->LINES.
1445 * If a line could be read successfully, increase CONTENT->CURRENT_LINE,
1446 * and allocate *LINE in RESULT_POOL.
1447 * Do temporary allocations in SCRATCH_POOL.
1448 */
1449 static svn_error_t *
readline(target_content_t * content,const char ** line,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1450 readline(target_content_t *content,
1451 const char **line,
1452 apr_pool_t *result_pool,
1453 apr_pool_t *scratch_pool)
1454 {
1455 svn_stringbuf_t *line_raw;
1456 const char *eol_str;
1457 svn_linenum_t max_line = (svn_linenum_t)content->lines->nelts + 1;
1458
1459 if (content->eof || content->readline == NULL)
1460 {
1461 *line = "";
1462 return SVN_NO_ERROR;
1463 }
1464
1465 SVN_ERR_ASSERT(content->current_line <= max_line);
1466 if (content->current_line == max_line)
1467 {
1468 apr_off_t offset;
1469
1470 SVN_ERR(content->tell(content->read_baton, &offset,
1471 scratch_pool));
1472 APR_ARRAY_PUSH(content->lines, apr_off_t) = offset;
1473 }
1474
1475 SVN_ERR(content->readline(content->read_baton, &line_raw,
1476 &eol_str, &content->eof,
1477 result_pool, scratch_pool));
1478 if (content->eol_style == svn_subst_eol_style_none)
1479 content->eol_str = eol_str;
1480
1481 if (line_raw)
1482 {
1483 /* Contract keywords. */
1484 SVN_ERR(svn_subst_translate_cstring2(line_raw->data, line,
1485 NULL, FALSE,
1486 content->keywords, FALSE,
1487 result_pool));
1488 }
1489 else
1490 *line = "";
1491
1492 if ((line_raw && line_raw->len > 0) || eol_str)
1493 content->current_line++;
1494
1495 SVN_ERR_ASSERT(content->current_line > 0);
1496
1497 return SVN_NO_ERROR;
1498 }
1499
1500 /* Seek to the specified LINE in CONTENT.
1501 * Mark any lines not read before in CONTENT->LINES.
1502 * Do temporary allocations in SCRATCH_POOL.
1503 */
1504 static svn_error_t *
seek_to_line(target_content_t * content,svn_linenum_t line,apr_pool_t * scratch_pool)1505 seek_to_line(target_content_t *content, svn_linenum_t line,
1506 apr_pool_t *scratch_pool)
1507 {
1508 svn_linenum_t saved_line;
1509 svn_boolean_t saved_eof;
1510
1511 SVN_ERR_ASSERT(line > 0);
1512
1513 if (line == content->current_line)
1514 return SVN_NO_ERROR;
1515
1516 saved_line = content->current_line;
1517 saved_eof = content->eof;
1518
1519 if (line <= (svn_linenum_t)content->lines->nelts)
1520 {
1521 apr_off_t offset;
1522
1523 offset = APR_ARRAY_IDX(content->lines, line - 1, apr_off_t);
1524 SVN_ERR(content->seek(content->read_baton, offset,
1525 scratch_pool));
1526 content->current_line = line;
1527 }
1528 else
1529 {
1530 const char *dummy;
1531 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1532
1533 while (! content->eof && content->current_line < line)
1534 {
1535 svn_pool_clear(iterpool);
1536 SVN_ERR(readline(content, &dummy, iterpool, iterpool));
1537 }
1538 svn_pool_destroy(iterpool);
1539 }
1540
1541 /* After seeking backwards from EOF position clear EOF indicator. */
1542 if (saved_eof && saved_line > content->current_line)
1543 content->eof = FALSE;
1544
1545 return SVN_NO_ERROR;
1546 }
1547
1548 /* Indicate in *MATCHED whether the original text of HUNK matches the patch
1549 * CONTENT at its current line. Lines within FUZZ lines of the start or
1550 * end of HUNK will always match. If IGNORE_WHITESPACE is set, we ignore
1551 * whitespace when doing the matching. When this function returns, neither
1552 * CONTENT->CURRENT_LINE nor the file offset in the target file will
1553 * have changed. If MATCH_MODIFIED is TRUE, match the modified hunk text,
1554 * rather than the original hunk text.
1555 * Do temporary allocations in POOL. */
1556 static svn_error_t *
match_hunk(svn_boolean_t * matched,target_content_t * content,svn_diff_hunk_t * hunk,svn_linenum_t fuzz,svn_boolean_t ignore_whitespace,svn_boolean_t match_modified,apr_pool_t * pool)1557 match_hunk(svn_boolean_t *matched, target_content_t *content,
1558 svn_diff_hunk_t *hunk, svn_linenum_t fuzz,
1559 svn_boolean_t ignore_whitespace,
1560 svn_boolean_t match_modified, apr_pool_t *pool)
1561 {
1562 svn_stringbuf_t *hunk_line;
1563 const char *target_line;
1564 svn_linenum_t lines_read;
1565 svn_linenum_t saved_line;
1566 svn_boolean_t hunk_eof;
1567 svn_boolean_t lines_matched;
1568 apr_pool_t *iterpool;
1569 svn_linenum_t hunk_length;
1570 svn_linenum_t leading_context;
1571 svn_linenum_t trailing_context;
1572 svn_linenum_t fuzz_penalty;
1573
1574 *matched = FALSE;
1575
1576 if (content->eof)
1577 return SVN_NO_ERROR;
1578
1579 fuzz_penalty = svn_diff_hunk__get_fuzz_penalty(hunk);
1580
1581 if (fuzz_penalty > fuzz)
1582 return SVN_NO_ERROR;
1583 else
1584 fuzz -= fuzz_penalty;
1585
1586 saved_line = content->current_line;
1587 lines_read = 0;
1588 lines_matched = FALSE;
1589 leading_context = svn_diff_hunk_get_leading_context(hunk);
1590 trailing_context = svn_diff_hunk_get_trailing_context(hunk);
1591 if (match_modified)
1592 {
1593 svn_diff_hunk_reset_modified_text(hunk);
1594 hunk_length = svn_diff_hunk_get_modified_length(hunk);
1595 }
1596 else
1597 {
1598 svn_diff_hunk_reset_original_text(hunk);
1599 hunk_length = svn_diff_hunk_get_original_length(hunk);
1600 }
1601 iterpool = svn_pool_create(pool);
1602 do
1603 {
1604 const char *hunk_line_translated;
1605
1606 svn_pool_clear(iterpool);
1607
1608 if (match_modified)
1609 SVN_ERR(svn_diff_hunk_readline_modified_text(hunk, &hunk_line,
1610 NULL, &hunk_eof,
1611 iterpool, iterpool));
1612 else
1613 SVN_ERR(svn_diff_hunk_readline_original_text(hunk, &hunk_line,
1614 NULL, &hunk_eof,
1615 iterpool, iterpool));
1616
1617 /* Contract keywords, if any, before matching. */
1618 SVN_ERR(svn_subst_translate_cstring2(hunk_line->data,
1619 &hunk_line_translated,
1620 NULL, FALSE,
1621 content->keywords, FALSE,
1622 iterpool));
1623 SVN_ERR(readline(content, &target_line, iterpool, iterpool));
1624
1625 lines_read++;
1626
1627 /* If the last line doesn't have a newline, we get EOF but still
1628 * have a non-empty line to compare. */
1629 if ((hunk_eof && hunk_line->len == 0) ||
1630 (content->eof && *target_line == 0))
1631 break;
1632
1633 /* Leading/trailing fuzzy lines always match. */
1634 if ((lines_read <= fuzz && leading_context > fuzz) ||
1635 (lines_read > hunk_length - fuzz && trailing_context > fuzz))
1636 lines_matched = TRUE;
1637 else
1638 {
1639 if (ignore_whitespace)
1640 {
1641 char *hunk_line_trimmed;
1642 char *target_line_trimmed;
1643
1644 hunk_line_trimmed = apr_pstrdup(iterpool, hunk_line_translated);
1645 target_line_trimmed = apr_pstrdup(iterpool, target_line);
1646 apr_collapse_spaces(hunk_line_trimmed, hunk_line_trimmed);
1647 apr_collapse_spaces(target_line_trimmed, target_line_trimmed);
1648 lines_matched = ! strcmp(hunk_line_trimmed, target_line_trimmed);
1649 }
1650 else
1651 lines_matched = ! strcmp(hunk_line_translated, target_line);
1652 }
1653 }
1654 while (lines_matched);
1655
1656 *matched = lines_matched && hunk_eof && hunk_line->len == 0;
1657 SVN_ERR(seek_to_line(content, saved_line, iterpool));
1658 svn_pool_destroy(iterpool);
1659
1660 return SVN_NO_ERROR;
1661 }
1662
1663 /* Scan lines of CONTENT for a match of the original text of HUNK,
1664 * up to but not including the specified UPPER_LINE. Use fuzz factor FUZZ.
1665 * If UPPER_LINE is zero scan until EOF occurs when reading from TARGET.
1666 * Return the line at which HUNK was matched in *MATCHED_LINE.
1667 * If the hunk did not match at all, set *MATCHED_LINE to zero.
1668 * If the hunk matched multiple times, and MATCH_FIRST is TRUE,
1669 * return the line number at which the first match occurred in *MATCHED_LINE.
1670 * If the hunk matched multiple times, and MATCH_FIRST is FALSE,
1671 * return the line number at which the last match occurred in *MATCHED_LINE.
1672 * If IGNORE_WHITESPACE is set, ignore whitespace during the matching.
1673 * If MATCH_MODIFIED is TRUE, match the modified hunk text,
1674 * rather than the original hunk text.
1675 * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
1676 * Do all allocations in POOL. */
1677 static svn_error_t *
scan_for_match(svn_linenum_t * matched_line,target_content_t * content,svn_diff_hunk_t * hunk,svn_boolean_t match_first,svn_linenum_t upper_line,svn_linenum_t fuzz,svn_boolean_t ignore_whitespace,svn_boolean_t match_modified,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * pool)1678 scan_for_match(svn_linenum_t *matched_line,
1679 target_content_t *content,
1680 svn_diff_hunk_t *hunk, svn_boolean_t match_first,
1681 svn_linenum_t upper_line, svn_linenum_t fuzz,
1682 svn_boolean_t ignore_whitespace,
1683 svn_boolean_t match_modified,
1684 svn_cancel_func_t cancel_func, void *cancel_baton,
1685 apr_pool_t *pool)
1686 {
1687 apr_pool_t *iterpool;
1688
1689 *matched_line = 0;
1690 iterpool = svn_pool_create(pool);
1691 while ((content->current_line < upper_line || upper_line == 0) &&
1692 ! content->eof)
1693 {
1694 svn_boolean_t matched;
1695
1696 svn_pool_clear(iterpool);
1697
1698 if (cancel_func)
1699 SVN_ERR(cancel_func(cancel_baton));
1700
1701 SVN_ERR(match_hunk(&matched, content, hunk, fuzz, ignore_whitespace,
1702 match_modified, iterpool));
1703 if (matched)
1704 {
1705 svn_boolean_t taken = FALSE;
1706 int i;
1707
1708 /* Don't allow hunks to match at overlapping locations. */
1709 for (i = 0; i < content->hunks->nelts; i++)
1710 {
1711 const hunk_info_t *hi;
1712 svn_linenum_t length;
1713
1714 hi = APR_ARRAY_IDX(content->hunks, i, const hunk_info_t *);
1715
1716 if (match_modified)
1717 length = svn_diff_hunk_get_modified_length(hi->hunk);
1718 else
1719 length = svn_diff_hunk_get_original_length(hi->hunk);
1720
1721 taken = (! hi->rejected &&
1722 content->current_line >= hi->matched_line &&
1723 content->current_line < (hi->matched_line + length));
1724 if (taken)
1725 break;
1726 }
1727
1728 if (! taken)
1729 {
1730 *matched_line = content->current_line;
1731 if (match_first)
1732 break;
1733 }
1734 }
1735
1736 if (! content->eof)
1737 SVN_ERR(seek_to_line(content, content->current_line + 1,
1738 iterpool));
1739 }
1740 svn_pool_destroy(iterpool);
1741
1742 return SVN_NO_ERROR;
1743 }
1744
1745 /* Indicate in *MATCH whether the content described by CONTENT
1746 * matches the modified text of HUNK.
1747 * Use SCRATCH_POOL for temporary allocations. */
1748 static svn_error_t *
match_existing_target(svn_boolean_t * match,target_content_t * content,svn_diff_hunk_t * hunk,apr_pool_t * scratch_pool)1749 match_existing_target(svn_boolean_t *match,
1750 target_content_t *content,
1751 svn_diff_hunk_t *hunk,
1752 apr_pool_t *scratch_pool)
1753 {
1754 svn_boolean_t lines_matched;
1755 apr_pool_t *iterpool;
1756 svn_boolean_t hunk_eof;
1757 svn_linenum_t saved_line;
1758
1759 svn_diff_hunk_reset_modified_text(hunk);
1760
1761 saved_line = content->current_line;
1762
1763 iterpool = svn_pool_create(scratch_pool);
1764 do
1765 {
1766 const char *line;
1767 svn_stringbuf_t *hunk_line;
1768 const char *line_translated;
1769 const char *hunk_line_translated;
1770
1771 svn_pool_clear(iterpool);
1772
1773 SVN_ERR(readline(content, &line, iterpool, iterpool));
1774 SVN_ERR(svn_diff_hunk_readline_modified_text(hunk, &hunk_line,
1775 NULL, &hunk_eof,
1776 iterpool, iterpool));
1777 /* Contract keywords. */
1778 SVN_ERR(svn_subst_translate_cstring2(line, &line_translated,
1779 NULL, FALSE,
1780 content->keywords,
1781 FALSE, iterpool));
1782 SVN_ERR(svn_subst_translate_cstring2(hunk_line->data,
1783 &hunk_line_translated,
1784 NULL, FALSE,
1785 content->keywords,
1786 FALSE, iterpool));
1787 lines_matched = ! strcmp(line_translated, hunk_line_translated);
1788 if (content->eof != hunk_eof)
1789 {
1790 svn_pool_destroy(iterpool);
1791 *match = FALSE;
1792 return SVN_NO_ERROR;
1793 }
1794 }
1795 while (lines_matched && ! content->eof && ! hunk_eof);
1796 svn_pool_destroy(iterpool);
1797
1798 *match = (lines_matched && content->eof == hunk_eof);
1799 SVN_ERR(seek_to_line(content, saved_line, scratch_pool));
1800
1801 return SVN_NO_ERROR;
1802 }
1803
1804 /* Determine the line at which a HUNK applies to CONTENT of the TARGET
1805 * file, and return an appropriate hunk_info object in *HI, allocated from
1806 * RESULT_POOL. Use fuzz factor FUZZ. Set HI->FUZZ to FUZZ. If no correct
1807 * line can be determined, set HI->REJECTED to TRUE. PREVIOUS_OFFSET
1808 * is the offset at which the previous matching hunk was applied, or zero.
1809 * IGNORE_WHITESPACE tells whether whitespace should be considered when
1810 * matching. IS_PROP_HUNK indicates whether the hunk patches file content
1811 * or a property.
1812 * When this function returns, neither CONTENT->CURRENT_LINE nor
1813 * the file offset in the target file will have changed.
1814 * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
1815 * Do temporary allocations in POOL. */
1816 static svn_error_t *
get_hunk_info(hunk_info_t ** hi,patch_target_t * target,target_content_t * content,svn_diff_hunk_t * hunk,svn_linenum_t fuzz,svn_linenum_t previous_offset,svn_boolean_t ignore_whitespace,svn_boolean_t is_prop_hunk,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1817 get_hunk_info(hunk_info_t **hi, patch_target_t *target,
1818 target_content_t *content,
1819 svn_diff_hunk_t *hunk, svn_linenum_t fuzz,
1820 svn_linenum_t previous_offset,
1821 svn_boolean_t ignore_whitespace,
1822 svn_boolean_t is_prop_hunk,
1823 svn_cancel_func_t cancel_func, void *cancel_baton,
1824 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
1825 {
1826 svn_linenum_t matched_line;
1827 svn_linenum_t original_start;
1828 svn_boolean_t already_applied;
1829
1830 original_start = svn_diff_hunk_get_original_start(hunk) + previous_offset;
1831 already_applied = FALSE;
1832
1833 /* An original offset of zero means that this hunk wants to create
1834 * a new file. Don't bother matching hunks in that case, since
1835 * the hunk applies at line 1. If the file already exists, the hunk
1836 * is rejected, unless the file is versioned and its content matches
1837 * the file the patch wants to create. */
1838 if (original_start == 0 && fuzz > 0)
1839 {
1840 matched_line = 0; /* reject any fuzz for new files */
1841 }
1842 else if (original_start == 0 && ! is_prop_hunk)
1843 {
1844 if (target->kind_on_disk == svn_node_file)
1845 {
1846 const svn_io_dirent2_t *dirent;
1847 SVN_ERR(svn_io_stat_dirent2(&dirent, target->local_abspath, FALSE,
1848 TRUE, scratch_pool, scratch_pool));
1849
1850 if (dirent->kind == svn_node_file
1851 && !dirent->special
1852 && dirent->filesize == 0)
1853 {
1854 matched_line = 1; /* Matched an on-disk empty file */
1855 }
1856 else
1857 {
1858 if (target->db_kind == svn_node_file)
1859 {
1860 svn_boolean_t file_matches;
1861
1862 SVN_ERR(match_existing_target(&file_matches, content, hunk,
1863 scratch_pool));
1864 if (file_matches)
1865 {
1866 matched_line = 1;
1867 already_applied = TRUE;
1868 }
1869 else
1870 matched_line = 0; /* reject */
1871 }
1872 else
1873 matched_line = 0; /* reject */
1874 }
1875 }
1876 else
1877 matched_line = 1;
1878 }
1879 /* Same conditions apply as for the file case above.
1880 *
1881 * ### Since the hunk says the prop should be added we just assume so for
1882 * ### now and don't bother with storing the previous lines and such. When
1883 * ### we have the diff operation available we can just check for adds. */
1884 else if (original_start == 0 && is_prop_hunk)
1885 {
1886 if (content->existed)
1887 {
1888 svn_boolean_t prop_matches;
1889
1890 SVN_ERR(match_existing_target(&prop_matches, content, hunk,
1891 scratch_pool));
1892
1893 if (prop_matches)
1894 {
1895 matched_line = 1;
1896 already_applied = TRUE;
1897 }
1898 else
1899 matched_line = 0; /* reject */
1900 }
1901 else
1902 matched_line = 1;
1903 }
1904 else if (original_start > 0 && content->existed)
1905 {
1906 svn_linenum_t modified_start;
1907 svn_linenum_t saved_line = content->current_line;
1908
1909 modified_start = svn_diff_hunk_get_modified_start(hunk);
1910
1911 /* Scan for a match at the line where the hunk thinks it
1912 * should be going. */
1913 SVN_ERR(seek_to_line(content, original_start, scratch_pool));
1914 if (content->current_line != original_start)
1915 {
1916 /* Seek failed. */
1917 matched_line = 0;
1918 }
1919 else
1920 SVN_ERR(scan_for_match(&matched_line, content, hunk, TRUE,
1921 original_start + 1, fuzz,
1922 ignore_whitespace, FALSE,
1923 cancel_func, cancel_baton,
1924 scratch_pool));
1925
1926 if (matched_line != original_start)
1927 {
1928 /* Check if the hunk is already applied.
1929 * We only check for an exact match here, and don't bother checking
1930 * for already applied patches with offset/fuzz, because such a
1931 * check would be ambiguous. */
1932 if (fuzz == 0)
1933 {
1934 if (modified_start == 0
1935 && (target->operation == svn_diff_op_unchanged
1936 || target->operation == svn_diff_op_deleted))
1937 {
1938 /* Patch wants to delete the file. */
1939
1940 already_applied = target->locally_deleted;
1941 }
1942 else
1943 {
1944 svn_linenum_t seek_to;
1945
1946 if (modified_start == 0)
1947 seek_to = 1; /* Empty file case */
1948 else
1949 seek_to = modified_start;
1950
1951 SVN_ERR(seek_to_line(content, seek_to, scratch_pool));
1952 SVN_ERR(scan_for_match(&matched_line, content,
1953 hunk, TRUE,
1954 modified_start + 1,
1955 fuzz, ignore_whitespace, TRUE,
1956 cancel_func, cancel_baton,
1957 scratch_pool));
1958 already_applied = (matched_line == modified_start);
1959 }
1960 }
1961 else
1962 already_applied = FALSE;
1963
1964 if (! already_applied)
1965 {
1966 int i;
1967 svn_linenum_t search_start = 1, search_end = 0;
1968 svn_linenum_t matched_line2;
1969
1970 /* Search for closest match before or after original
1971 start. We have no backward search so search forwards
1972 from the previous match (or start of file) to the
1973 original start looking for the last match. Then
1974 search forwards from the original start looking for a
1975 better match. Finally search forwards from the start
1976 of file to the previous hunk if that could result in
1977 a better match. */
1978
1979 for (i = content->hunks->nelts; i > 0; --i)
1980 {
1981 const hunk_info_t *prev
1982 = APR_ARRAY_IDX(content->hunks, i - 1, const hunk_info_t *);
1983 if (!prev->rejected)
1984 {
1985 svn_linenum_t length;
1986
1987 length = svn_diff_hunk_get_original_length(prev->hunk);
1988 search_start = prev->matched_line + length;
1989 break;
1990 }
1991 }
1992
1993 /* Search from the previous match, or start of file,
1994 towards the original location. */
1995 SVN_ERR(seek_to_line(content, search_start, scratch_pool));
1996 SVN_ERR(scan_for_match(&matched_line, content, hunk, FALSE,
1997 original_start, fuzz,
1998 ignore_whitespace, FALSE,
1999 cancel_func, cancel_baton,
2000 scratch_pool));
2001
2002 /* If a match we only need to search forwards for a
2003 better match, otherwise to the end of the file. */
2004 if (matched_line)
2005 search_end = original_start + (original_start - matched_line);
2006
2007 /* Search from original location, towards the end. */
2008 SVN_ERR(seek_to_line(content, original_start + 1, scratch_pool));
2009 SVN_ERR(scan_for_match(&matched_line2, content, hunk,
2010 TRUE, search_end, fuzz, ignore_whitespace,
2011 FALSE, cancel_func, cancel_baton,
2012 scratch_pool));
2013
2014 /* Chose the forward match if it is closer than the
2015 backward match or if there is no backward match. */
2016 if (matched_line2
2017 && (!matched_line
2018 || (matched_line2 - original_start
2019 < original_start - matched_line)))
2020 matched_line = matched_line2;
2021
2022 /* Search from before previous hunk if there could be a
2023 better match. */
2024 if (search_start > 1
2025 && (!matched_line
2026 || (matched_line > original_start
2027 && (matched_line - original_start
2028 > original_start - search_start))))
2029 {
2030 svn_linenum_t search_start2 = 1;
2031
2032 if (matched_line
2033 && matched_line - original_start < original_start)
2034 search_start2
2035 = original_start - (matched_line - original_start) + 1;
2036
2037 SVN_ERR(seek_to_line(content, search_start2, scratch_pool));
2038 SVN_ERR(scan_for_match(&matched_line2, content, hunk, FALSE,
2039 search_start - 1, fuzz,
2040 ignore_whitespace, FALSE,
2041 cancel_func, cancel_baton,
2042 scratch_pool));
2043 if (matched_line2)
2044 matched_line = matched_line2;
2045 }
2046 }
2047 }
2048 else if (matched_line > 0
2049 && fuzz == 0
2050 && (svn_diff_hunk_get_leading_context(hunk) == 0
2051 || svn_diff_hunk_get_trailing_context(hunk) == 0)
2052 && (svn_diff_hunk_get_modified_length(hunk) >
2053 svn_diff_hunk_get_original_length(hunk)))
2054 {
2055 /* Check that we are not applying the same change that just adds some
2056 lines again, when we don't have enough context to see the
2057 difference */
2058 svn_linenum_t reverse_matched_line;
2059
2060 SVN_ERR(seek_to_line(content, modified_start, scratch_pool));
2061 SVN_ERR(scan_for_match(&reverse_matched_line, content,
2062 hunk, TRUE,
2063 modified_start + 1,
2064 fuzz, ignore_whitespace, TRUE,
2065 cancel_func, cancel_baton,
2066 scratch_pool));
2067
2068 /* We might want to check that we are actually at the start or the
2069 end of the file. Having no context implies that we should be. */
2070 already_applied = (reverse_matched_line == modified_start);
2071 }
2072
2073 SVN_ERR(seek_to_line(content, saved_line, scratch_pool));
2074 }
2075 else if (!content->existed && svn_diff_hunk_get_modified_start(hunk) == 0)
2076 {
2077 /* The hunk wants to delete a file or property which doesn't exist. */
2078 matched_line = 0;
2079 already_applied = TRUE;
2080 }
2081 else
2082 {
2083 /* The hunk wants to modify a file or property which doesn't exist. */
2084 matched_line = 0;
2085 }
2086
2087 (*hi) = apr_pcalloc(result_pool, sizeof(hunk_info_t));
2088 (*hi)->hunk = hunk;
2089 (*hi)->matched_line = matched_line;
2090 (*hi)->rejected = (matched_line == 0);
2091 (*hi)->already_applied = already_applied;
2092 (*hi)->report_fuzz = fuzz;
2093 (*hi)->match_fuzz = fuzz - svn_diff_hunk__get_fuzz_penalty(hunk);
2094
2095 return SVN_NO_ERROR;
2096 }
2097
2098 /* Copy lines to the patched content until the specified LINE has been
2099 * reached. Indicate in *EOF whether end-of-file was encountered while
2100 * reading from the target.
2101 * If LINE is zero, copy lines until end-of-file has been reached.
2102 * Do all allocations in POOL. */
2103 static svn_error_t *
copy_lines_to_target(target_content_t * content,svn_linenum_t line,apr_pool_t * pool)2104 copy_lines_to_target(target_content_t *content, svn_linenum_t line,
2105 apr_pool_t *pool)
2106 {
2107 apr_pool_t *iterpool;
2108
2109 iterpool = svn_pool_create(pool);
2110 while ((content->current_line < line || line == 0) && ! content->eof)
2111 {
2112 const char *target_line;
2113 apr_size_t len;
2114
2115 svn_pool_clear(iterpool);
2116
2117 SVN_ERR(readline(content, &target_line, iterpool, iterpool));
2118 if (! content->eof)
2119 target_line = apr_pstrcat(iterpool, target_line, content->eol_str,
2120 SVN_VA_NULL);
2121 len = strlen(target_line);
2122 SVN_ERR(content->write(content->write_baton, target_line,
2123 len, iterpool));
2124 }
2125 svn_pool_destroy(iterpool);
2126
2127 return SVN_NO_ERROR;
2128 }
2129
2130 /* Write the diff text of HUNK to TARGET's reject file,
2131 * and mark TARGET as having had rejects.
2132 * We don't expand keywords, nor normalise line-endings, in reject files.
2133 * Do temporary allocations in SCRATCH_POOL. */
2134 static svn_error_t *
reject_hunk(patch_target_t * target,target_content_t * content,svn_diff_hunk_t * hunk,const char * prop_name,apr_pool_t * pool)2135 reject_hunk(patch_target_t *target, target_content_t *content,
2136 svn_diff_hunk_t *hunk, const char *prop_name,
2137 apr_pool_t *pool)
2138 {
2139 svn_boolean_t eof;
2140 static const char * const text_atat = "@@";
2141 static const char * const prop_atat = "##";
2142 const char *atat;
2143 apr_pool_t *iterpool;
2144
2145 if (prop_name)
2146 {
2147 /* ### Print 'Added', 'Deleted' or 'Modified' instead of 'Property'. */
2148 svn_stream_printf(target->reject_stream,
2149 pool, "Property: %s" APR_EOL_STR, prop_name);
2150 atat = prop_atat;
2151 }
2152 else
2153 {
2154 atat = text_atat;
2155 }
2156
2157 SVN_ERR(svn_stream_printf(target->reject_stream, pool,
2158 "%s -%lu,%lu +%lu,%lu %s" APR_EOL_STR,
2159 atat,
2160 svn_diff_hunk_get_original_start(hunk),
2161 svn_diff_hunk_get_original_length(hunk),
2162 svn_diff_hunk_get_modified_start(hunk),
2163 svn_diff_hunk_get_modified_length(hunk),
2164 atat));
2165
2166 iterpool = svn_pool_create(pool);
2167 do
2168 {
2169 svn_stringbuf_t *hunk_line;
2170 const char *eol_str;
2171
2172 svn_pool_clear(iterpool);
2173
2174 SVN_ERR(svn_diff_hunk_readline_diff_text(hunk, &hunk_line, &eol_str,
2175 &eof, iterpool, iterpool));
2176 if (! eof)
2177 {
2178 if (hunk_line->len >= 1)
2179 {
2180 apr_size_t len = hunk_line->len;
2181
2182 SVN_ERR(svn_stream_write(target->reject_stream,
2183 hunk_line->data, &len));
2184 }
2185
2186 if (eol_str)
2187 {
2188 SVN_ERR(svn_stream_puts(target->reject_stream, eol_str));
2189 }
2190 }
2191 }
2192 while (! eof);
2193 svn_pool_destroy(iterpool);
2194
2195 if (prop_name)
2196 target->had_prop_rejects = TRUE;
2197 else
2198 target->had_rejects = TRUE;
2199
2200 return SVN_NO_ERROR;
2201 }
2202
2203 /* Write the modified text of the hunk described by HI to the patched
2204 * CONTENT. TARGET is the patch target.
2205 * If PROP_NAME is not NULL, the hunk is assumed to be targeted for
2206 * a property with the given name.
2207 * Do temporary allocations in POOL. */
2208 static svn_error_t *
apply_hunk(patch_target_t * target,target_content_t * content,hunk_info_t * hi,const char * prop_name,apr_pool_t * pool)2209 apply_hunk(patch_target_t *target, target_content_t *content,
2210 hunk_info_t *hi, const char *prop_name, apr_pool_t *pool)
2211 {
2212 svn_linenum_t lines_read;
2213 svn_boolean_t eof;
2214 apr_pool_t *iterpool;
2215 svn_linenum_t fuzz = hi->match_fuzz;
2216
2217 /* ### Is there a cleaner way to describe if we have an existing target?
2218 */
2219 if (target->kind_on_disk == svn_node_file || prop_name)
2220 {
2221 svn_linenum_t line;
2222
2223 /* Move forward to the hunk's line, copying data as we go.
2224 * Also copy leading lines of context which matched with fuzz.
2225 * The target has changed on the fuzzy-matched lines,
2226 * so we should retain the target's version of those lines. */
2227 SVN_ERR(copy_lines_to_target(content, hi->matched_line + fuzz,
2228 pool));
2229
2230 /* Skip the target's version of the hunk.
2231 * Don't skip trailing lines which matched with fuzz. */
2232 line = content->current_line +
2233 svn_diff_hunk_get_original_length(hi->hunk) - (2 * fuzz);
2234 SVN_ERR(seek_to_line(content, line, pool));
2235 if (content->current_line != line && ! content->eof)
2236 {
2237 /* Seek failed, reject this hunk. */
2238 hi->rejected = TRUE;
2239 SVN_ERR(reject_hunk(target, content, hi->hunk, prop_name, pool));
2240 return SVN_NO_ERROR;
2241 }
2242 }
2243
2244 /* Write the hunk's version to the patched result.
2245 * Don't write the lines which matched with fuzz. */
2246 lines_read = 0;
2247 svn_diff_hunk_reset_modified_text(hi->hunk);
2248 iterpool = svn_pool_create(pool);
2249 do
2250 {
2251 svn_stringbuf_t *hunk_line;
2252 const char *eol_str;
2253
2254 svn_pool_clear(iterpool);
2255
2256 SVN_ERR(svn_diff_hunk_readline_modified_text(hi->hunk, &hunk_line,
2257 &eol_str, &eof,
2258 iterpool, iterpool));
2259 lines_read++;
2260 if (lines_read > fuzz &&
2261 lines_read <= svn_diff_hunk_get_modified_length(hi->hunk) - fuzz)
2262 {
2263 apr_size_t len;
2264
2265 if (hunk_line->len >= 1)
2266 {
2267 len = hunk_line->len;
2268 SVN_ERR(content->write(content->write_baton,
2269 hunk_line->data, len, iterpool));
2270 }
2271
2272 if (eol_str)
2273 {
2274 /* Use the EOL as it was read from the patch file,
2275 * unless the target's EOL style is set by svn:eol-style */
2276 if (content->eol_style != svn_subst_eol_style_none)
2277 eol_str = content->eol_str;
2278
2279 len = strlen(eol_str);
2280 SVN_ERR(content->write(content->write_baton,
2281 eol_str, len, iterpool));
2282 }
2283 }
2284 }
2285 while (! eof);
2286 svn_pool_destroy(iterpool);
2287
2288 if (prop_name)
2289 target->has_prop_changes = TRUE;
2290 else
2291 target->has_text_changes = TRUE;
2292
2293 return SVN_NO_ERROR;
2294 }
2295
2296 /* Use client context CTX to send a suitable notification for hunk HI,
2297 * using TARGET to determine the path. If the hunk is a property hunk,
2298 * PROP_NAME must be the name of the property, else NULL.
2299 * Use POOL for temporary allocations. */
2300 static svn_error_t *
send_hunk_notification(const hunk_info_t * hi,const patch_target_t * target,const char * prop_name,const svn_client_ctx_t * ctx,apr_pool_t * pool)2301 send_hunk_notification(const hunk_info_t *hi,
2302 const patch_target_t *target,
2303 const char *prop_name,
2304 const svn_client_ctx_t *ctx,
2305 apr_pool_t *pool)
2306 {
2307 svn_wc_notify_t *notify;
2308 svn_wc_notify_action_t action;
2309
2310 if (hi->already_applied)
2311 action = svn_wc_notify_patch_hunk_already_applied;
2312 else if (hi->rejected)
2313 action = svn_wc_notify_patch_rejected_hunk;
2314 else
2315 action = svn_wc_notify_patch_applied_hunk;
2316
2317 notify = svn_wc_create_notify(target->local_abspath
2318 ? target->local_abspath
2319 : target->local_relpath,
2320 action, pool);
2321 notify->hunk_original_start =
2322 svn_diff_hunk_get_original_start(hi->hunk);
2323 notify->hunk_original_length =
2324 svn_diff_hunk_get_original_length(hi->hunk);
2325 notify->hunk_modified_start =
2326 svn_diff_hunk_get_modified_start(hi->hunk);
2327 notify->hunk_modified_length =
2328 svn_diff_hunk_get_modified_length(hi->hunk);
2329 notify->hunk_matched_line = hi->matched_line;
2330 notify->hunk_fuzz = hi->report_fuzz;
2331 notify->prop_name = prop_name;
2332
2333 ctx->notify_func2(ctx->notify_baton2, notify, pool);
2334
2335 return SVN_NO_ERROR;
2336 }
2337
2338 /* Use client context CTX to send a suitable notification for a patch TARGET.
2339 * Use POOL for temporary allocations. */
2340 static svn_error_t *
send_patch_notification(const patch_target_t * target,const svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)2341 send_patch_notification(const patch_target_t *target,
2342 const svn_client_ctx_t *ctx,
2343 apr_pool_t *scratch_pool)
2344 {
2345 svn_wc_notify_t *notify;
2346 svn_wc_notify_action_t action;
2347 const char *notify_path;
2348
2349 if (! ctx->notify_func2)
2350 return SVN_NO_ERROR;
2351
2352 if (target->skipped)
2353 action = svn_wc_notify_skip;
2354 else if (target->deleted)
2355 action = svn_wc_notify_delete;
2356 else if (target->added || target->move_target_abspath)
2357 action = svn_wc_notify_add;
2358 else
2359 action = svn_wc_notify_patch;
2360
2361 if (target->move_target_abspath)
2362 notify_path = target->move_target_abspath;
2363 else
2364 notify_path = target->local_abspath ? target->local_abspath
2365 : target->local_relpath;
2366
2367 notify = svn_wc_create_notify(notify_path, action, scratch_pool);
2368 notify->kind = (target->db_kind == svn_node_dir) ? svn_node_dir
2369 : svn_node_file;
2370
2371 if (action == svn_wc_notify_skip)
2372 {
2373 if (target->obstructed)
2374 notify->content_state = svn_wc_notify_state_obstructed;
2375 else if (target->db_kind == svn_node_none ||
2376 target->db_kind == svn_node_unknown)
2377 notify->content_state = svn_wc_notify_state_missing;
2378 else
2379 notify->content_state = svn_wc_notify_state_unknown;
2380 }
2381 else
2382 {
2383 if (target->had_rejects)
2384 notify->content_state = svn_wc_notify_state_conflicted;
2385 else if (target->has_text_changes)
2386 notify->content_state = svn_wc_notify_state_changed;
2387 else if (target->had_already_applied)
2388 notify->content_state = svn_wc_notify_state_merged;
2389 else
2390 notify->content_state = svn_wc_notify_state_unchanged;
2391
2392 if (target->had_prop_rejects)
2393 notify->prop_state = svn_wc_notify_state_conflicted;
2394 else if (target->has_prop_changes)
2395 notify->prop_state = svn_wc_notify_state_changed;
2396 else if (target->had_prop_already_applied)
2397 notify->prop_state = svn_wc_notify_state_merged;
2398 else
2399 notify->prop_state = svn_wc_notify_state_unchanged;
2400 }
2401
2402 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
2403
2404 if (action == svn_wc_notify_patch)
2405 {
2406 int i;
2407 apr_pool_t *iterpool;
2408 apr_array_header_t *prop_targets;
2409
2410 iterpool = svn_pool_create(scratch_pool);
2411 for (i = 0; i < target->content->hunks->nelts; i++)
2412 {
2413 const hunk_info_t *hi;
2414
2415 svn_pool_clear(iterpool);
2416
2417 hi = APR_ARRAY_IDX(target->content->hunks, i, hunk_info_t *);
2418
2419 SVN_ERR(send_hunk_notification(hi, target, NULL /* prop_name */,
2420 ctx, iterpool));
2421 }
2422
2423 prop_targets = svn_sort__hash(target->prop_targets,
2424 svn_sort_compare_items_lexically,
2425 scratch_pool);
2426 for (i = 0; i < prop_targets->nelts; i++)
2427 {
2428 int j;
2429 svn_sort__item_t item = APR_ARRAY_IDX(prop_targets, i,
2430 svn_sort__item_t);
2431
2432 prop_patch_target_t *prop_target = item.value;
2433
2434 for (j = 0; j < prop_target->content->hunks->nelts; j++)
2435 {
2436 const hunk_info_t *hi;
2437
2438 svn_pool_clear(iterpool);
2439
2440 hi = APR_ARRAY_IDX(prop_target->content->hunks, j,
2441 hunk_info_t *);
2442
2443 /* Don't notify on the hunk level for added or deleted props. */
2444 if ((prop_target->operation != svn_diff_op_added &&
2445 prop_target->operation != svn_diff_op_deleted)
2446 || hi->rejected || hi->already_applied)
2447 SVN_ERR(send_hunk_notification(hi, target, prop_target->name,
2448 ctx, iterpool));
2449 }
2450 }
2451 svn_pool_destroy(iterpool);
2452 }
2453
2454 if (!target->skipped && target->move_target_abspath)
2455 {
2456 /* Notify about deletion of move source. */
2457 notify = svn_wc_create_notify(target->local_abspath,
2458 svn_wc_notify_delete, scratch_pool);
2459 notify->kind = svn_node_file;
2460 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
2461 }
2462
2463 return SVN_NO_ERROR;
2464 }
2465
2466 /* Implements the callback for svn_sort__array. Puts hunks that match
2467 before hunks that do not match, puts hunks that match in order
2468 based on postion matched, puts hunks that do not match in order
2469 based on original position. */
2470 static int
sort_matched_hunks(const void * a,const void * b)2471 sort_matched_hunks(const void *a, const void *b)
2472 {
2473 const hunk_info_t *item1 = *((const hunk_info_t * const *)a);
2474 const hunk_info_t *item2 = *((const hunk_info_t * const *)b);
2475 svn_boolean_t matched1 = !item1->rejected && !item1->already_applied;
2476 svn_boolean_t matched2 = !item2->rejected && !item2->already_applied;
2477 svn_linenum_t original1, original2;
2478
2479 if (matched1 && matched2)
2480 {
2481 /* Both match so use order matched in file. */
2482 if (item1->matched_line > item2->matched_line)
2483 return 1;
2484 else if (item1->matched_line == item2->matched_line)
2485 return 0;
2486 else
2487 return -1;
2488 }
2489 else if (matched2)
2490 /* Only second matches, put it before first. */
2491 return 1;
2492 else if (matched1)
2493 /* Only first matches, put it before second. */
2494 return -1;
2495
2496 /* Neither matches, sort by original_start. */
2497 original1 = svn_diff_hunk_get_original_start(item1->hunk);
2498 original2 = svn_diff_hunk_get_original_start(item2->hunk);
2499 if (original1 > original2)
2500 return 1;
2501 else if (original1 == original2)
2502 return 0;
2503 else
2504 return -1;
2505 }
2506
2507
2508 /* Apply a PATCH to a working copy at ABS_WC_PATH and put the result
2509 * into temporary files, to be installed in the working copy later.
2510 * Return information about the patch target in *PATCH_TARGET, allocated
2511 * in RESULT_POOL. Use WC_CTX as the working copy context.
2512 * STRIP_COUNT specifies the number of leading path components
2513 * which should be stripped from target paths in the patch.
2514 * REMOVE_TEMPFILES is as in svn_client_patch().
2515 * TARGETS_INFO is for preserving info across calls.
2516 * IGNORE_WHITESPACE tells whether whitespace should be considered when
2517 * doing the matching.
2518 * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
2519 * Do temporary allocations in SCRATCH_POOL. */
2520 static svn_error_t *
apply_one_patch(patch_target_t ** patch_target,svn_patch_t * patch,const char * abs_wc_path,svn_wc_context_t * wc_ctx,int strip_count,svn_boolean_t ignore_whitespace,svn_boolean_t remove_tempfiles,const apr_array_header_t * targets_info,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)2521 apply_one_patch(patch_target_t **patch_target, svn_patch_t *patch,
2522 const char *abs_wc_path, svn_wc_context_t *wc_ctx,
2523 int strip_count,
2524 svn_boolean_t ignore_whitespace,
2525 svn_boolean_t remove_tempfiles,
2526 const apr_array_header_t *targets_info,
2527 svn_cancel_func_t cancel_func,
2528 void *cancel_baton,
2529 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
2530 {
2531 patch_target_t *target;
2532 apr_pool_t *iterpool;
2533 int i;
2534 static const svn_linenum_t MAX_FUZZ = 2;
2535 apr_hash_index_t *hash_index;
2536 svn_linenum_t previous_offset = 0;
2537 apr_array_header_t *prop_targets;
2538
2539 SVN_ERR(init_patch_target(&target, patch, abs_wc_path, wc_ctx, strip_count,
2540 remove_tempfiles, targets_info,
2541 result_pool, scratch_pool));
2542 if (target->skipped)
2543 {
2544 *patch_target = target;
2545 return SVN_NO_ERROR;
2546 }
2547
2548 iterpool = svn_pool_create(scratch_pool);
2549
2550 if (patch->hunks && patch->hunks->nelts)
2551 {
2552 /* Match hunks. */
2553 for (i = 0; i < patch->hunks->nelts; i++)
2554 {
2555 svn_diff_hunk_t *hunk;
2556 hunk_info_t *hi;
2557 svn_linenum_t fuzz = 0;
2558
2559 svn_pool_clear(iterpool);
2560
2561 if (cancel_func)
2562 SVN_ERR(cancel_func(cancel_baton));
2563
2564 hunk = APR_ARRAY_IDX(patch->hunks, i, svn_diff_hunk_t *);
2565
2566 /* Determine the line the hunk should be applied at.
2567 * If no match is found initially, try with fuzz. */
2568 do
2569 {
2570 SVN_ERR(get_hunk_info(&hi, target, target->content, hunk, fuzz,
2571 previous_offset,
2572 ignore_whitespace,
2573 FALSE /* is_prop_hunk */,
2574 cancel_func, cancel_baton,
2575 result_pool, iterpool));
2576 fuzz++;
2577 }
2578 while (hi->rejected && fuzz <= MAX_FUZZ && ! hi->already_applied);
2579
2580 if (hi->matched_line)
2581 previous_offset
2582 = hi->matched_line - svn_diff_hunk_get_original_start(hunk);
2583
2584 APR_ARRAY_PUSH(target->content->hunks, hunk_info_t *) = hi;
2585 }
2586
2587 /* Hunks are applied in the order determined by the matched line and
2588 this may be different from the order of the original lines. */
2589 svn_sort__array(target->content->hunks, sort_matched_hunks);
2590
2591 /* Apply or reject hunks. */
2592 for (i = 0; i < target->content->hunks->nelts; i++)
2593 {
2594 hunk_info_t *hi;
2595
2596 svn_pool_clear(iterpool);
2597
2598 if (cancel_func)
2599 SVN_ERR(cancel_func(cancel_baton));
2600
2601 hi = APR_ARRAY_IDX(target->content->hunks, i, hunk_info_t *);
2602 if (hi->already_applied)
2603 {
2604 target->had_already_applied = TRUE;
2605 continue;
2606 }
2607 else if (hi->rejected)
2608 SVN_ERR(reject_hunk(target, target->content, hi->hunk,
2609 NULL /* prop_name */,
2610 iterpool));
2611 else
2612 SVN_ERR(apply_hunk(target, target->content, hi,
2613 NULL /* prop_name */, iterpool));
2614 }
2615
2616 if (target->kind_on_disk == svn_node_file)
2617 {
2618 /* Copy any remaining lines to target. */
2619 SVN_ERR(copy_lines_to_target(target->content, 0, scratch_pool));
2620 if (! target->content->eof)
2621 {
2622 /* We could not copy the entire target file to the temporary
2623 * file, and would truncate the target if we copied the
2624 * temporary file on top of it. Skip this target. */
2625 target->skipped = TRUE;
2626 }
2627 }
2628 }
2629 else if (patch->binary_patch)
2630 {
2631 svn_stream_t *orig_stream;
2632 svn_boolean_t same;
2633
2634 if (target->file)
2635 orig_stream = svn_stream_from_aprfile2(target->file, TRUE, iterpool);
2636 else
2637 orig_stream = svn_stream_empty(iterpool);
2638
2639 SVN_ERR(svn_stream_contents_same2(
2640 &same, orig_stream,
2641 svn_diff_get_binary_diff_original_stream(patch->binary_patch,
2642 iterpool),
2643 iterpool));
2644 svn_pool_clear(iterpool);
2645
2646 if (same)
2647 {
2648 /* The file in the working copy is identical to the one expected by
2649 the patch... So we can write the result stream; no fuzz,
2650 just a 100% match */
2651
2652 target->has_text_changes = TRUE;
2653 }
2654 else
2655 {
2656 /* Perhaps the file is identical to the resulting version, implying
2657 that the patch has already been applied */
2658 if (target->file)
2659 {
2660 apr_off_t start = 0;
2661
2662 SVN_ERR(svn_io_file_seek(target->file, APR_SET, &start, iterpool));
2663
2664 orig_stream = svn_stream_from_aprfile2(target->file, TRUE, iterpool);
2665 }
2666 else
2667 orig_stream = svn_stream_empty(iterpool);
2668
2669 SVN_ERR(svn_stream_contents_same2(
2670 &same, orig_stream,
2671 svn_diff_get_binary_diff_result_stream(patch->binary_patch,
2672 iterpool),
2673 iterpool));
2674 svn_pool_clear(iterpool);
2675
2676 if (same)
2677 target->had_already_applied = TRUE;
2678 }
2679
2680 if (same)
2681 {
2682 SVN_ERR(svn_stream_copy3(
2683 svn_diff_get_binary_diff_result_stream(patch->binary_patch,
2684 iterpool),
2685 svn_stream_from_aprfile2(target->patched_file, TRUE,
2686 iterpool),
2687 cancel_func, cancel_baton,
2688 iterpool));
2689 }
2690 else
2691 {
2692 /* ### TODO: Implement a proper reject of a binary patch
2693
2694 This should at least setup things for a proper notification,
2695 and perhaps install a normal text conflict. Unlike normal unified
2696 diff based patches we have all the versions we would need for
2697 that in a much easier format than can be obtained from the patch
2698 file. */
2699 target->skipped = TRUE;
2700 }
2701 }
2702 else if (target->move_target_abspath)
2703 {
2704 /* ### Why do we do this?
2705 BH: I don't know, but if we don't do this some tests
2706 on git style patches break.
2707
2708 ### It would be much better to really move the actual file instead
2709 of copying to a temporary file; move that to target and then
2710 delete the original file
2711
2712 ### BH: I have absolutely no idea if moving directories would work.
2713 */
2714 if (target->kind_on_disk == svn_node_file)
2715 {
2716 /* Copy any remaining lines to target. (read: all lines) */
2717 SVN_ERR(copy_lines_to_target(target->content, 0, scratch_pool));
2718 if (!target->content->eof)
2719 {
2720 /* We could not copy the entire target file to the temporary
2721 * file, and would truncate the target if we copied the
2722 * temporary file on top of it. Skip this target. */
2723 target->skipped = TRUE;
2724 }
2725 }
2726 }
2727
2728 if (target->had_rejects || target->locally_deleted)
2729 target->deleted = FALSE;
2730
2731 if (target->added
2732 && !(target->locally_deleted || target->db_kind == svn_node_none))
2733 {
2734 target->added = FALSE;
2735 }
2736
2737 /* Assume nothing changed. Will be updated via property hunks */
2738 target->is_special = target->is_symlink;
2739
2740 /* Match property hunks. */
2741 for (hash_index = apr_hash_first(scratch_pool, patch->prop_patches);
2742 hash_index;
2743 hash_index = apr_hash_next(hash_index))
2744 {
2745 svn_prop_patch_t *prop_patch;
2746 const char *prop_name;
2747 prop_patch_target_t *prop_target;
2748
2749 prop_name = apr_hash_this_key(hash_index);
2750 prop_patch = apr_hash_this_val(hash_index);
2751
2752 if (!strcmp(prop_name, SVN_PROP_SPECIAL))
2753 target->is_special = (prop_patch->operation != svn_diff_op_deleted);
2754
2755 /* We'll store matched hunks in prop_content. */
2756 prop_target = svn_hash_gets(target->prop_targets, prop_name);
2757
2758 for (i = 0; i < prop_patch->hunks->nelts; i++)
2759 {
2760 svn_diff_hunk_t *hunk;
2761 hunk_info_t *hi;
2762 svn_linenum_t fuzz = 0;
2763
2764 svn_pool_clear(iterpool);
2765
2766 if (cancel_func)
2767 SVN_ERR(cancel_func(cancel_baton));
2768
2769 hunk = APR_ARRAY_IDX(prop_patch->hunks, i, svn_diff_hunk_t *);
2770
2771 /* Determine the line the hunk should be applied at.
2772 * If no match is found initially, try with fuzz. */
2773 do
2774 {
2775 SVN_ERR(get_hunk_info(&hi, target, prop_target->content,
2776 hunk, fuzz, 0,
2777 ignore_whitespace,
2778 TRUE /* is_prop_hunk */,
2779 cancel_func, cancel_baton,
2780 result_pool, iterpool));
2781 fuzz++;
2782 }
2783 while (hi->rejected && fuzz <= MAX_FUZZ && ! hi->already_applied);
2784
2785 APR_ARRAY_PUSH(prop_target->content->hunks, hunk_info_t *) = hi;
2786 }
2787 }
2788
2789 /* Match implied property hunks. */
2790 if (patch->new_executable_bit != svn_tristate_unknown
2791 && patch->new_executable_bit != patch->old_executable_bit
2792 && svn_hash_gets(target->prop_targets, SVN_PROP_EXECUTABLE)
2793 && !svn_hash_gets(patch->prop_patches, SVN_PROP_EXECUTABLE))
2794 {
2795 hunk_info_t *hi;
2796 svn_diff_hunk_t *hunk;
2797 prop_patch_target_t *prop_target = svn_hash_gets(target->prop_targets,
2798 SVN_PROP_EXECUTABLE);
2799
2800 if (patch->new_executable_bit == svn_tristate_true)
2801 SVN_ERR(svn_diff_hunk__create_adds_single_line(
2802 &hunk,
2803 SVN_PROP_EXECUTABLE_VALUE,
2804 patch,
2805 result_pool,
2806 iterpool));
2807 else
2808 SVN_ERR(svn_diff_hunk__create_deletes_single_line(
2809 &hunk,
2810 SVN_PROP_EXECUTABLE_VALUE,
2811 patch,
2812 result_pool,
2813 iterpool));
2814
2815 /* Derive a hunk_info from hunk. */
2816 SVN_ERR(get_hunk_info(&hi, target, prop_target->content,
2817 hunk, 0 /* fuzz */, 0 /* previous_offset */,
2818 ignore_whitespace,
2819 TRUE /* is_prop_hunk */,
2820 cancel_func, cancel_baton,
2821 result_pool, iterpool));
2822 APR_ARRAY_PUSH(prop_target->content->hunks, hunk_info_t *) = hi;
2823 }
2824
2825 if (patch->new_symlink_bit != svn_tristate_unknown
2826 && patch->new_symlink_bit != patch->old_symlink_bit
2827 && svn_hash_gets(target->prop_targets, SVN_PROP_SPECIAL)
2828 && !svn_hash_gets(patch->prop_patches, SVN_PROP_SPECIAL))
2829 {
2830 hunk_info_t *hi;
2831 svn_diff_hunk_t *hunk;
2832
2833 prop_patch_target_t *prop_target = svn_hash_gets(target->prop_targets,
2834 SVN_PROP_SPECIAL);
2835
2836 if (patch->new_symlink_bit == svn_tristate_true)
2837 {
2838 SVN_ERR(svn_diff_hunk__create_adds_single_line(
2839 &hunk,
2840 SVN_PROP_SPECIAL_VALUE,
2841 patch,
2842 result_pool,
2843 iterpool));
2844 target->is_special = TRUE;
2845 }
2846 else
2847 {
2848 SVN_ERR(svn_diff_hunk__create_deletes_single_line(
2849 &hunk,
2850 SVN_PROP_SPECIAL_VALUE,
2851 patch,
2852 result_pool,
2853 iterpool));
2854 target->is_special = FALSE;
2855 }
2856
2857 /* Derive a hunk_info from hunk. */
2858 SVN_ERR(get_hunk_info(&hi, target, prop_target->content,
2859 hunk, 0 /* fuzz */, 0 /* previous_offset */,
2860 ignore_whitespace,
2861 TRUE /* is_prop_hunk */,
2862 cancel_func, cancel_baton,
2863 result_pool, iterpool));
2864 APR_ARRAY_PUSH(prop_target->content->hunks, hunk_info_t *) = hi;
2865 }
2866
2867 /* When the node is deleted or does not exist after the patch is applied
2868 we should reject a few more property hunks that can't be applied even
2869 though the source matched */
2870 if (target->deleted
2871 || (!target->added &&
2872 (target->locally_deleted || target->db_kind == svn_node_none)))
2873 {
2874 for (hash_index = apr_hash_first(scratch_pool, target->prop_targets);
2875 hash_index;
2876 hash_index = apr_hash_next(hash_index))
2877 {
2878 prop_patch_target_t *prop_target = apr_hash_this_val(hash_index);
2879
2880 if (prop_target->operation == svn_diff_op_deleted)
2881 continue;
2882
2883 for (i = 0; i < prop_target->content->hunks->nelts; i++)
2884 {
2885 hunk_info_t *hi;
2886
2887 hi = APR_ARRAY_IDX(prop_target->content->hunks, i, hunk_info_t*);
2888
2889 if (hi->already_applied || hi->rejected)
2890 continue;
2891 else
2892 {
2893 hi->rejected = TRUE;
2894 prop_target->skipped = TRUE;
2895
2896 if (!target->deleted && !target->added)
2897 target->skipped = TRUE;
2898 }
2899 }
2900 }
2901 }
2902
2903 /* Apply or reject property hunks. */
2904
2905 prop_targets = svn_sort__hash(target->prop_targets,
2906 svn_sort_compare_items_lexically,
2907 scratch_pool);
2908 for (i = 0; i < prop_targets->nelts; i++)
2909 {
2910 svn_sort__item_t item = APR_ARRAY_IDX(prop_targets, i, svn_sort__item_t);
2911 prop_patch_target_t *prop_target = item.value;
2912 svn_boolean_t applied_one = FALSE;
2913 int j;
2914
2915 for (j = 0; j < prop_target->content->hunks->nelts; j++)
2916 {
2917 hunk_info_t *hi;
2918
2919 svn_pool_clear(iterpool);
2920
2921 hi = APR_ARRAY_IDX(prop_target->content->hunks, j,
2922 hunk_info_t *);
2923 if (hi->already_applied)
2924 {
2925 target->had_prop_already_applied = TRUE;
2926 continue;
2927 }
2928 else if (hi->rejected)
2929 SVN_ERR(reject_hunk(target, prop_target->content, hi->hunk,
2930 prop_target->name,
2931 iterpool));
2932 else
2933 {
2934 SVN_ERR(apply_hunk(target, prop_target->content, hi,
2935 prop_target->name,
2936 iterpool));
2937 applied_one = TRUE;
2938 }
2939 }
2940
2941 if (!applied_one)
2942 prop_target->skipped = TRUE;
2943
2944 if (applied_one && prop_target->content->existed)
2945 {
2946 /* Copy any remaining lines to target. */
2947 SVN_ERR(copy_lines_to_target(prop_target->content, 0,
2948 scratch_pool));
2949 if (! prop_target->content->eof)
2950 {
2951 /* We could not copy the entire target property to the
2952 * temporary stream, and would truncate the target if we
2953 * copied the temporary stream on top of it. Skip this target. */
2954 prop_target->skipped = TRUE;
2955 }
2956 }
2957 }
2958
2959 svn_pool_destroy(iterpool);
2960
2961 if (!target->is_symlink)
2962 {
2963 /* Now close files we don't need any longer to get their contents
2964 * flushed to disk.
2965 * But we're not closing the reject file -- it still needed and
2966 * will be closed later in write_out_rejected_hunks(). */
2967 if (target->kind_on_disk == svn_node_file)
2968 SVN_ERR(svn_io_file_close(target->file, scratch_pool));
2969 }
2970
2971 SVN_ERR(svn_io_file_close(target->patched_file, scratch_pool));
2972
2973 *patch_target = target;
2974
2975 return SVN_NO_ERROR;
2976 }
2977
2978 /* Try to create missing parent directories for TARGET in the working copy
2979 * rooted at ABS_WC_PATH, and add the parents to version control.
2980 * If the parents cannot be created, mark the target as skipped.
2981 *
2982 * In dry run mode record missing parents in ALREADY_ADDED
2983 *
2984 * Use client context CTX. If DRY_RUN is true, do not create missing
2985 * parents but issue notifications only.
2986 * Use SCRATCH_POOL for temporary allocations. */
2987 static svn_error_t *
create_missing_parents(patch_target_t * target,const char * abs_wc_path,svn_client_ctx_t * ctx,svn_boolean_t dry_run,apr_array_header_t * targets_info,apr_pool_t * scratch_pool)2988 create_missing_parents(patch_target_t *target,
2989 const char *abs_wc_path,
2990 svn_client_ctx_t *ctx,
2991 svn_boolean_t dry_run,
2992 apr_array_header_t *targets_info,
2993 apr_pool_t *scratch_pool)
2994 {
2995 const char *local_abspath;
2996 apr_array_header_t *components;
2997 int present_components;
2998 int i;
2999 apr_pool_t *iterpool;
3000
3001 /* Check if we can safely create the target's parent. */
3002 local_abspath = abs_wc_path;
3003 components = svn_path_decompose(target->local_relpath, scratch_pool);
3004 present_components = 0;
3005 iterpool = svn_pool_create(scratch_pool);
3006 for (i = 0; i < components->nelts - 1; i++)
3007 {
3008 const char *component;
3009 svn_node_kind_t wc_kind, disk_kind;
3010
3011 svn_pool_clear(iterpool);
3012
3013 component = APR_ARRAY_IDX(components, i, const char *);
3014 local_abspath = svn_dirent_join(local_abspath, component, scratch_pool);
3015
3016 SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, local_abspath,
3017 FALSE, TRUE, iterpool));
3018
3019 SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, iterpool));
3020
3021 if (disk_kind == svn_node_file || wc_kind == svn_node_file)
3022 {
3023 /* on-disk files and missing files are obstructions */
3024 target->skipped = TRUE;
3025 break;
3026 }
3027 else if (disk_kind == svn_node_dir)
3028 {
3029 if (wc_kind == svn_node_dir)
3030 present_components++;
3031 else
3032 {
3033 target->skipped = TRUE;
3034 break;
3035 }
3036 }
3037 else if (wc_kind != svn_node_none)
3038 {
3039 /* Node is missing */
3040 target->skipped = TRUE;
3041 break;
3042 }
3043 else
3044 {
3045 /* It's not a file, it's not a dir...
3046 Let's add a dir */
3047 break;
3048 }
3049 }
3050 if (! target->skipped)
3051 {
3052 local_abspath = abs_wc_path;
3053 for (i = 0; i < present_components; i++)
3054 {
3055 const char *component;
3056 component = APR_ARRAY_IDX(components, i, const char *);
3057 local_abspath = svn_dirent_join(local_abspath,
3058 component, scratch_pool);
3059 }
3060
3061 if (!dry_run && present_components < components->nelts - 1)
3062 SVN_ERR(svn_io_make_dir_recursively(
3063 svn_dirent_join(
3064 abs_wc_path,
3065 svn_relpath_dirname(target->local_relpath,
3066 scratch_pool),
3067 scratch_pool),
3068 scratch_pool));
3069
3070 for (i = present_components; i < components->nelts - 1; i++)
3071 {
3072 const char *component;
3073 patch_target_info_t *pti;
3074
3075 svn_pool_clear(iterpool);
3076
3077 if (ctx->cancel_func)
3078 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
3079
3080 component = APR_ARRAY_IDX(components, i, const char *);
3081 local_abspath = svn_dirent_join(local_abspath, component,
3082 scratch_pool);
3083
3084 if (target_is_added(targets_info, local_abspath, iterpool))
3085 continue;
3086
3087 pti = apr_pcalloc(targets_info->pool, sizeof(*pti));
3088
3089 pti->local_abspath = apr_pstrdup(targets_info->pool,
3090 local_abspath);
3091 pti->added = TRUE;
3092
3093 APR_ARRAY_PUSH(targets_info, patch_target_info_t *) = pti;
3094
3095 if (dry_run)
3096 {
3097 if (ctx->notify_func2)
3098 {
3099 /* Just do notification. */
3100 svn_wc_notify_t *notify;
3101 notify = svn_wc_create_notify(local_abspath,
3102 svn_wc_notify_add,
3103 iterpool);
3104 notify->kind = svn_node_dir;
3105 ctx->notify_func2(ctx->notify_baton2, notify,
3106 iterpool);
3107 }
3108 }
3109 else
3110 {
3111 /* Create the missing component and add it
3112 * to version control. Allow cancellation since we
3113 * have not modified the working copy yet for this
3114 * target. */
3115 SVN_ERR(svn_wc_add_from_disk3(ctx->wc_ctx, local_abspath,
3116 NULL /*props*/,
3117 FALSE /* skip checks */,
3118 ctx->notify_func2, ctx->notify_baton2,
3119 iterpool));
3120 }
3121 }
3122 }
3123
3124 svn_pool_destroy(iterpool);
3125 return SVN_NO_ERROR;
3126 }
3127
3128 /* Install a patched TARGET into the working copy at ABS_WC_PATH.
3129 * Use client context CTX to retrieve WC_CTX, and possibly doing
3130 * notifications.
3131 *
3132 * Pass on ALREADY_ADDED to allow recording already added ancestors
3133 * in dry-run mode.
3134 *
3135 * If DRY_RUN is TRUE, don't modify the working copy.
3136 * Do temporary allocations in POOL. */
3137 static svn_error_t *
install_patched_target(patch_target_t * target,const char * abs_wc_path,svn_client_ctx_t * ctx,svn_boolean_t dry_run,apr_array_header_t * targets_info,apr_pool_t * pool)3138 install_patched_target(patch_target_t *target, const char *abs_wc_path,
3139 svn_client_ctx_t *ctx, svn_boolean_t dry_run,
3140 apr_array_header_t *targets_info,
3141 apr_pool_t *pool)
3142 {
3143 if (target->deleted)
3144 {
3145 if (! dry_run)
3146 {
3147 /* Schedule the target for deletion. Suppress
3148 * notification, we'll do it manually in a minute
3149 * because we also need to notify during dry-run.
3150 * Also suppress cancellation, because we'd rather
3151 * notify about what we did before aborting. */
3152 SVN_ERR(svn_wc_delete4(ctx->wc_ctx, target->local_abspath,
3153 FALSE /* keep_local */, FALSE,
3154 ctx->cancel_func, ctx->cancel_baton,
3155 NULL, NULL /* notify */,
3156 pool));
3157 }
3158 }
3159 else
3160 {
3161 svn_node_kind_t parent_db_kind;
3162 if (target->added)
3163 {
3164 const char *parent_abspath;
3165
3166 parent_abspath = svn_dirent_dirname(target->local_abspath,
3167 pool);
3168 /* If the target's parent directory does not yet exist
3169 * we need to create it before we can copy the patched
3170 * result in place. */
3171 SVN_ERR(svn_wc_read_kind2(&parent_db_kind, ctx->wc_ctx,
3172 parent_abspath, FALSE, FALSE, pool));
3173
3174 /* We can't add targets under nodes scheduled for delete, so add
3175 a new directory if needed. */
3176 if (parent_db_kind == svn_node_dir
3177 || parent_db_kind == svn_node_file)
3178 {
3179 if (parent_db_kind != svn_node_dir)
3180 target->skipped = TRUE;
3181 else
3182 {
3183 svn_node_kind_t disk_kind;
3184
3185 SVN_ERR(svn_io_check_path(parent_abspath, &disk_kind, pool));
3186 if (disk_kind != svn_node_dir)
3187 target->skipped = TRUE;
3188 }
3189 }
3190 else
3191 SVN_ERR(create_missing_parents(target, abs_wc_path, ctx,
3192 dry_run, targets_info, pool));
3193
3194 }
3195 else
3196 {
3197 svn_node_kind_t wc_kind;
3198
3199 /* The target should exist */
3200 SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx,
3201 target->local_abspath,
3202 FALSE, FALSE, pool));
3203
3204 if (target->kind_on_disk == svn_node_none
3205 || wc_kind != target->kind_on_disk)
3206 {
3207 target->skipped = TRUE;
3208 if (wc_kind != target->kind_on_disk)
3209 target->obstructed = TRUE;
3210 }
3211 }
3212
3213 if (! dry_run && ! target->skipped)
3214 {
3215 if (target->is_special)
3216 {
3217 svn_stream_t *stream;
3218 svn_stream_t *patched_stream;
3219
3220 SVN_ERR(svn_stream_open_readonly(&patched_stream,
3221 target->patched_path,
3222 pool, pool));
3223 SVN_ERR(svn_subst_create_specialfile(&stream,
3224 target->local_abspath,
3225 pool, pool));
3226 if (target->git_symlink_format)
3227 SVN_ERR(svn_stream_puts(stream, "link "));
3228 SVN_ERR(svn_stream_copy3(patched_stream, stream,
3229 ctx->cancel_func, ctx->cancel_baton,
3230 pool));
3231 }
3232 else
3233 {
3234 svn_boolean_t repair_eol;
3235
3236 /* Copy the patched file on top of the target file.
3237 * Always expand keywords in the patched file, but repair EOL
3238 * only if svn:eol-style dictates a particular style. */
3239 repair_eol = (target->content->eol_style ==
3240 svn_subst_eol_style_fixed ||
3241 target->content->eol_style ==
3242 svn_subst_eol_style_native);
3243
3244 SVN_ERR(svn_subst_copy_and_translate4(
3245 target->patched_path,
3246 target->move_target_abspath
3247 ? target->move_target_abspath
3248 : target->local_abspath,
3249 target->content->eol_str, repair_eol,
3250 target->content->keywords,
3251 TRUE /* expand */, FALSE /* special */,
3252 ctx->cancel_func, ctx->cancel_baton, pool));
3253 }
3254
3255 if (target->added)
3256 {
3257 /* The target file didn't exist previously,
3258 * so add it to version control.
3259 * Suppress notification, we'll do that later (and also
3260 * during dry-run). Don't allow cancellation because
3261 * we'd rather notify about what we did before aborting. */
3262 SVN_ERR(svn_wc_add_from_disk3(ctx->wc_ctx, target->local_abspath,
3263 NULL /*props*/,
3264 FALSE /* skip checks */,
3265 NULL, NULL, pool));
3266 }
3267
3268 /* Restore the target's executable bit if necessary. */
3269 SVN_ERR(svn_io_set_file_executable(target->move_target_abspath
3270 ? target->move_target_abspath
3271 : target->local_abspath,
3272 target->executable,
3273 FALSE, pool));
3274
3275 if (target->move_target_abspath)
3276 {
3277 /* ### Copying the patched content to the move target location,
3278 * performing the move in meta-data, and removing the file at
3279 * the move source should be one atomic operation. */
3280
3281 /* ### Create missing parents. */
3282
3283 /* Perform the move in meta-data. */
3284 SVN_ERR(svn_wc__move2(ctx->wc_ctx,
3285 target->local_abspath,
3286 target->move_target_abspath,
3287 TRUE, /* metadata_only */
3288 FALSE, /* allow_mixed_revisions */
3289 ctx->cancel_func, ctx->cancel_baton,
3290 NULL, NULL,
3291 pool));
3292
3293 /* Delete the patch target's old location from disk. */
3294 SVN_ERR(svn_io_remove_file2(target->local_abspath, FALSE, pool));
3295 }
3296 }
3297 }
3298
3299 return SVN_NO_ERROR;
3300 }
3301
3302 /* Write out rejected hunks, if any, to TARGET->REJECT_PATH. If DRY_RUN is
3303 * TRUE, don't modify the working copy.
3304 * Do temporary allocations in POOL.
3305 */
3306 static svn_error_t *
write_out_rejected_hunks(patch_target_t * target,const char * root_abspath,svn_boolean_t dry_run,apr_pool_t * scratch_pool)3307 write_out_rejected_hunks(patch_target_t *target,
3308 const char *root_abspath,
3309 svn_boolean_t dry_run,
3310 apr_pool_t *scratch_pool)
3311 {
3312 if (! dry_run && (target->had_rejects || target->had_prop_rejects))
3313 {
3314 /* Write out rejected hunks, if any. */
3315 apr_file_t *reject_file;
3316 svn_error_t *err;
3317
3318 err = svn_io_open_uniquely_named(&reject_file, NULL,
3319 svn_dirent_dirname(target->local_abspath,
3320 scratch_pool),
3321 svn_dirent_basename(
3322 target->local_abspath,
3323 NULL),
3324 ".svnpatch.rej",
3325 svn_io_file_del_none,
3326 scratch_pool, scratch_pool);
3327 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
3328 {
3329 /* The hunk applies to a file in a directory which does not exist.
3330 * Put the reject file into the working copy root instead. */
3331 svn_error_clear(err);
3332 SVN_ERR(svn_io_open_uniquely_named(&reject_file, NULL,
3333 root_abspath,
3334 svn_dirent_basename(
3335 target->local_abspath,
3336 NULL),
3337 ".svnpatch.rej",
3338 svn_io_file_del_none,
3339 scratch_pool, scratch_pool));
3340 }
3341 else
3342 SVN_ERR(err);
3343
3344 SVN_ERR(svn_stream_reset(target->reject_stream));
3345
3346 /* svn_stream_copy3() closes the files for us */
3347 SVN_ERR(svn_stream_copy3(target->reject_stream,
3348 svn_stream_from_aprfile2(reject_file, FALSE,
3349 scratch_pool),
3350 NULL, NULL, scratch_pool));
3351 /* ### TODO mark file as conflicted. */
3352 }
3353 else
3354 SVN_ERR(svn_stream_close(target->reject_stream));
3355
3356 return SVN_NO_ERROR;
3357 }
3358
3359 /* Install the patched properties for TARGET. Use client context CTX to
3360 * retrieve WC_CTX. If DRY_RUN is TRUE, don't modify the working copy.
3361 * Do temporary allocations in SCRATCH_POOL. */
3362 static svn_error_t *
install_patched_prop_targets(patch_target_t * target,svn_client_ctx_t * ctx,svn_boolean_t dry_run,apr_pool_t * scratch_pool)3363 install_patched_prop_targets(patch_target_t *target,
3364 svn_client_ctx_t *ctx, svn_boolean_t dry_run,
3365 apr_pool_t *scratch_pool)
3366 {
3367 apr_hash_index_t *hi;
3368 apr_pool_t *iterpool;
3369 const char *local_abspath;
3370
3371 /* Apply properties to a move target if there is one */
3372 if (target->move_target_abspath)
3373 local_abspath = target->move_target_abspath;
3374 else
3375 local_abspath = target->local_abspath;
3376
3377 iterpool = svn_pool_create(scratch_pool);
3378
3379 for (hi = apr_hash_first(scratch_pool, target->prop_targets);
3380 hi;
3381 hi = apr_hash_next(hi))
3382 {
3383 prop_patch_target_t *prop_target = apr_hash_this_val(hi);
3384 const svn_string_t *prop_val;
3385 svn_error_t *err;
3386
3387 svn_pool_clear(iterpool);
3388
3389 if (ctx->cancel_func)
3390 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
3391
3392 if (prop_target->skipped)
3393 continue;
3394
3395 /* For a deleted prop we only set the value to NULL. */
3396 if (prop_target->operation == svn_diff_op_deleted)
3397 {
3398 if (! dry_run)
3399 SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath,
3400 prop_target->name, NULL, svn_depth_empty,
3401 TRUE /* skip_checks */,
3402 NULL /* changelist_filter */,
3403 NULL, NULL /* cancellation */,
3404 NULL, NULL /* notification */,
3405 iterpool));
3406 continue;
3407 }
3408
3409 /* Attempt to set the property, and reject all hunks if this
3410 fails. If the property had a non-empty value, but now has
3411 an empty one, we'll just delete the property altogether. */
3412 if (prop_target->value && prop_target->value->len
3413 && prop_target->patched_value && !prop_target->patched_value->len)
3414 prop_val = NULL;
3415 else
3416 prop_val = svn_stringbuf__morph_into_string(prop_target->patched_value);
3417
3418 if (dry_run)
3419 {
3420 const svn_string_t *canon_propval;
3421
3422 err = svn_wc_canonicalize_svn_prop(&canon_propval,
3423 prop_target->name,
3424 prop_val, local_abspath,
3425 target->db_kind,
3426 TRUE, /* ### Skipping checks */
3427 NULL, NULL,
3428 iterpool);
3429 }
3430 else
3431 {
3432 err = svn_wc_prop_set4(ctx->wc_ctx, local_abspath,
3433 prop_target->name, prop_val, svn_depth_empty,
3434 TRUE /* skip_checks */,
3435 NULL /* changelist_filter */,
3436 NULL, NULL /* cancellation */,
3437 NULL, NULL /* notification */,
3438 iterpool);
3439 }
3440
3441 if (err)
3442 {
3443 /* ### The errors which svn_wc_canonicalize_svn_prop() will
3444 * ### return aren't documented. */
3445 if (err->apr_err == SVN_ERR_ILLEGAL_TARGET ||
3446 err->apr_err == SVN_ERR_NODE_UNEXPECTED_KIND ||
3447 err->apr_err == SVN_ERR_IO_UNKNOWN_EOL ||
3448 err->apr_err == SVN_ERR_BAD_MIME_TYPE ||
3449 err->apr_err == SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION)
3450 {
3451 int i;
3452
3453 svn_error_clear(err);
3454
3455 for (i = 0; i < prop_target->content->hunks->nelts; i++)
3456 {
3457 hunk_info_t *hunk_info;
3458
3459 hunk_info = APR_ARRAY_IDX(prop_target->content->hunks,
3460 i, hunk_info_t *);
3461 hunk_info->rejected = TRUE;
3462 SVN_ERR(reject_hunk(target, prop_target->content,
3463 hunk_info->hunk, prop_target->name,
3464 iterpool));
3465 }
3466 }
3467 else
3468 return svn_error_trace(err);
3469 }
3470
3471 }
3472
3473 svn_pool_destroy(iterpool);
3474
3475 return SVN_NO_ERROR;
3476 }
3477
3478 /* Baton for can_delete_callback */
3479 struct can_delete_baton_t
3480 {
3481 svn_boolean_t must_keep;
3482 const apr_array_header_t *targets_info;
3483 const char *local_abspath;
3484 };
3485
3486 /* Implements svn_wc_status_func4_t. */
3487 static svn_error_t *
can_delete_callback(void * baton,const char * abspath,const svn_wc_status3_t * status,apr_pool_t * pool)3488 can_delete_callback(void *baton,
3489 const char *abspath,
3490 const svn_wc_status3_t *status,
3491 apr_pool_t *pool)
3492 {
3493 struct can_delete_baton_t *cb = baton;
3494 int i;
3495
3496 switch(status->node_status)
3497 {
3498 case svn_wc_status_none:
3499 case svn_wc_status_deleted:
3500 return SVN_NO_ERROR;
3501
3502 default:
3503 if (! strcmp(cb->local_abspath, abspath))
3504 return SVN_NO_ERROR; /* Only interested in descendants */
3505
3506 for (i = 0; i < cb->targets_info->nelts; i++)
3507 {
3508 const patch_target_info_t *target_info =
3509 APR_ARRAY_IDX(cb->targets_info, i, const patch_target_info_t *);
3510
3511 if (! strcmp(target_info->local_abspath, abspath))
3512 {
3513 if (target_info->deleted)
3514 return SVN_NO_ERROR;
3515
3516 break; /* Cease invocation; must keep */
3517 }
3518 }
3519
3520 cb->must_keep = TRUE;
3521
3522 return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL);
3523 }
3524 }
3525
3526 static svn_error_t *
check_ancestor_delete(const char * deleted_target,apr_array_header_t * targets_info,const char * apply_root,svn_boolean_t dry_run,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)3527 check_ancestor_delete(const char *deleted_target,
3528 apr_array_header_t *targets_info,
3529 const char *apply_root,
3530 svn_boolean_t dry_run,
3531 svn_client_ctx_t *ctx,
3532 apr_pool_t *result_pool,
3533 apr_pool_t *scratch_pool)
3534 {
3535 struct can_delete_baton_t cb;
3536 svn_error_t *err;
3537 apr_array_header_t *ignores;
3538 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
3539
3540 const char *dir_abspath = svn_dirent_dirname(deleted_target, scratch_pool);
3541
3542 SVN_ERR(svn_wc_get_default_ignores(&ignores, ctx->config, scratch_pool));
3543
3544 while (svn_dirent_is_child(apply_root, dir_abspath, iterpool))
3545 {
3546 svn_pool_clear(iterpool);
3547
3548 cb.local_abspath = dir_abspath;
3549 cb.must_keep = FALSE;
3550 cb.targets_info = targets_info;
3551
3552 err = svn_wc_walk_status(ctx->wc_ctx, dir_abspath, svn_depth_infinity,
3553 TRUE, FALSE, FALSE, ignores,
3554 can_delete_callback, &cb,
3555 ctx->cancel_func, ctx->cancel_baton,
3556 iterpool);
3557
3558 if (err)
3559 {
3560 if (err->apr_err != SVN_ERR_CEASE_INVOCATION)
3561 return svn_error_trace(err);
3562
3563 svn_error_clear(err);
3564 }
3565
3566 if (cb.must_keep)
3567 {
3568 break;
3569 }
3570
3571 if (! dry_run)
3572 {
3573 SVN_ERR(svn_wc_delete4(ctx->wc_ctx, dir_abspath, FALSE, FALSE,
3574 ctx->cancel_func, ctx->cancel_baton,
3575 NULL, NULL,
3576 scratch_pool));
3577 }
3578
3579 {
3580 patch_target_info_t *pti = apr_pcalloc(result_pool, sizeof(*pti));
3581
3582 pti->local_abspath = apr_pstrdup(result_pool, dir_abspath);
3583 pti->deleted = TRUE;
3584
3585 APR_ARRAY_PUSH(targets_info, patch_target_info_t *) = pti;
3586 }
3587
3588
3589 if (ctx->notify_func2)
3590 {
3591 svn_wc_notify_t *notify;
3592
3593 notify = svn_wc_create_notify(dir_abspath, svn_wc_notify_delete,
3594 iterpool);
3595 notify->kind = svn_node_dir;
3596
3597 ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
3598 }
3599
3600 /* And check if we must also delete the parent */
3601 dir_abspath = svn_dirent_dirname(dir_abspath, scratch_pool);
3602 }
3603
3604 svn_pool_destroy(iterpool);
3605
3606 return SVN_NO_ERROR;
3607 }
3608
3609 /* This function is the main entry point into the patch code. */
3610 static svn_error_t *
apply_patches(const char * patch_abspath,const char * root_abspath,svn_boolean_t dry_run,int strip_count,svn_boolean_t reverse,svn_boolean_t ignore_whitespace,svn_boolean_t remove_tempfiles,svn_client_patch_func_t patch_func,void * patch_baton,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)3611 apply_patches(/* The path to the patch file. */
3612 const char *patch_abspath,
3613 /* The abspath to the working copy the patch should be applied to. */
3614 const char *root_abspath,
3615 /* Indicates whether we're doing a dry run. */
3616 svn_boolean_t dry_run,
3617 /* Number of leading components to strip from patch target paths. */
3618 int strip_count,
3619 /* Whether to apply the patch in reverse. */
3620 svn_boolean_t reverse,
3621 /* Whether to ignore whitespace when matching context lines. */
3622 svn_boolean_t ignore_whitespace,
3623 /* As in svn_client_patch(). */
3624 svn_boolean_t remove_tempfiles,
3625 /* As in svn_client_patch(). */
3626 svn_client_patch_func_t patch_func,
3627 void *patch_baton,
3628 /* The client context. */
3629 svn_client_ctx_t *ctx,
3630 apr_pool_t *scratch_pool)
3631 {
3632 svn_patch_t *patch;
3633 apr_pool_t *iterpool;
3634 svn_patch_file_t *patch_file;
3635 apr_array_header_t *targets_info;
3636
3637 /* Try to open the patch file. */
3638 SVN_ERR(svn_diff_open_patch_file(&patch_file, patch_abspath, scratch_pool));
3639
3640 /* Apply patches. */
3641 targets_info = apr_array_make(scratch_pool, 0,
3642 sizeof(patch_target_info_t *));
3643 iterpool = svn_pool_create(scratch_pool);
3644 do
3645 {
3646 svn_pool_clear(iterpool);
3647
3648 if (ctx->cancel_func)
3649 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
3650
3651 SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file,
3652 reverse, ignore_whitespace,
3653 iterpool, iterpool));
3654 if (patch)
3655 {
3656 patch_target_t *target;
3657 svn_boolean_t filtered = FALSE;
3658
3659 SVN_ERR(apply_one_patch(&target, patch, root_abspath,
3660 ctx->wc_ctx, strip_count,
3661 ignore_whitespace, remove_tempfiles,
3662 targets_info,
3663 ctx->cancel_func, ctx->cancel_baton,
3664 iterpool, iterpool));
3665
3666 if (!target->skipped && patch_func)
3667 {
3668 SVN_ERR(patch_func(patch_baton, &filtered,
3669 target->canon_path_from_patchfile,
3670 target->patched_path, target->reject_path,
3671 iterpool));
3672 }
3673
3674 if (! filtered)
3675 {
3676 /* Save info we'll still need when we're done patching. */
3677 patch_target_info_t *target_info =
3678 apr_pcalloc(scratch_pool, sizeof(patch_target_info_t));
3679 target_info->local_abspath = apr_pstrdup(scratch_pool,
3680 target->local_abspath);
3681 target_info->deleted = target->deleted;
3682 target_info->added = target->added;
3683
3684 if (! target->skipped)
3685 {
3686 if (target->has_text_changes
3687 || target->added
3688 || target->move_target_abspath
3689 || target->deleted)
3690 SVN_ERR(install_patched_target(target, root_abspath,
3691 ctx, dry_run,
3692 targets_info, iterpool));
3693
3694 if (target->has_prop_changes && (!target->deleted))
3695 SVN_ERR(install_patched_prop_targets(target, ctx,
3696 dry_run, iterpool));
3697
3698 SVN_ERR(write_out_rejected_hunks(target, root_abspath,
3699 dry_run, iterpool));
3700
3701 APR_ARRAY_PUSH(targets_info,
3702 patch_target_info_t *) = target_info;
3703 }
3704 SVN_ERR(send_patch_notification(target, ctx, iterpool));
3705
3706 if (target->deleted && !target->skipped)
3707 {
3708 SVN_ERR(check_ancestor_delete(target_info->local_abspath,
3709 targets_info, root_abspath,
3710 dry_run, ctx,
3711 scratch_pool, iterpool));
3712 }
3713 }
3714 }
3715 }
3716 while (patch);
3717
3718 SVN_ERR(svn_diff_close_patch_file(patch_file, iterpool));
3719 svn_pool_destroy(iterpool);
3720
3721 return SVN_NO_ERROR;
3722 }
3723
3724 svn_error_t *
svn_client_patch(const char * patch_abspath,const char * wc_dir_abspath,svn_boolean_t dry_run,int strip_count,svn_boolean_t reverse,svn_boolean_t ignore_whitespace,svn_boolean_t remove_tempfiles,svn_client_patch_func_t patch_func,void * patch_baton,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)3725 svn_client_patch(const char *patch_abspath,
3726 const char *wc_dir_abspath,
3727 svn_boolean_t dry_run,
3728 int strip_count,
3729 svn_boolean_t reverse,
3730 svn_boolean_t ignore_whitespace,
3731 svn_boolean_t remove_tempfiles,
3732 svn_client_patch_func_t patch_func,
3733 void *patch_baton,
3734 svn_client_ctx_t *ctx,
3735 apr_pool_t *scratch_pool)
3736 {
3737 svn_node_kind_t kind;
3738
3739 if (strip_count < 0)
3740 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
3741 _("strip count must be positive"));
3742
3743 if (svn_path_is_url(wc_dir_abspath))
3744 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3745 _("'%s' is not a local path"),
3746 svn_dirent_local_style(wc_dir_abspath,
3747 scratch_pool));
3748
3749 SVN_ERR(svn_io_check_path(patch_abspath, &kind, scratch_pool));
3750 if (kind == svn_node_none)
3751 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3752 _("'%s' does not exist"),
3753 svn_dirent_local_style(patch_abspath,
3754 scratch_pool));
3755 if (kind != svn_node_file)
3756 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3757 _("'%s' is not a file"),
3758 svn_dirent_local_style(patch_abspath,
3759 scratch_pool));
3760
3761 SVN_ERR(svn_io_check_path(wc_dir_abspath, &kind, scratch_pool));
3762 if (kind == svn_node_none)
3763 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3764 _("'%s' does not exist"),
3765 svn_dirent_local_style(wc_dir_abspath,
3766 scratch_pool));
3767 if (kind != svn_node_dir)
3768 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3769 _("'%s' is not a directory"),
3770 svn_dirent_local_style(wc_dir_abspath,
3771 scratch_pool));
3772
3773 SVN_WC__CALL_WITH_WRITE_LOCK(
3774 apply_patches(patch_abspath, wc_dir_abspath, dry_run, strip_count,
3775 reverse, ignore_whitespace, remove_tempfiles,
3776 patch_func, patch_baton, ctx, scratch_pool),
3777 ctx->wc_ctx, wc_dir_abspath, FALSE /* lock_anchor */, scratch_pool);
3778 return SVN_NO_ERROR;
3779 }
3780