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