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