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