]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/libsvn_client/patch.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.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_string_private.h"
50 #include "private/svn_subr_private.h"
51
52 typedef struct hunk_info_t {
53   /* The hunk. */
54   svn_diff_hunk_t *hunk;
55
56   /* The line where the hunk matched in the target file. */
57   svn_linenum_t matched_line;
58
59   /* Whether this hunk has been rejected. */
60   svn_boolean_t rejected;
61
62   /* Whether this hunk has already been applied (either manually
63    * or by an earlier run of patch). */
64   svn_boolean_t already_applied;
65
66   /* The fuzz factor used when matching this hunk, i.e. how many
67    * lines of leading and trailing context to ignore during matching. */
68   svn_linenum_t fuzz;
69 } hunk_info_t;
70
71 /* A struct carrying information related to the patched and unpatched
72  * content of a target, be it a property or the text of a file. */
73 typedef struct target_content_t {
74   /* Indicates whether unpatched content existed prior to patching. */
75   svn_boolean_t existed;
76
77   /* The line last read from the unpatched content. */
78   svn_linenum_t current_line;
79
80   /* The EOL-style of the unpatched content. Either 'none', 'fixed',
81    * or 'native'. See the documentation of svn_subst_eol_style_t. */
82   svn_subst_eol_style_t eol_style;
83
84   /* If the EOL_STYLE above is not 'none', this is the EOL string
85    * corresponding to the EOL-style. Else, it is the EOL string the
86    * last line read from the target file was using. */
87   const char *eol_str;
88
89   /* An array containing apr_off_t offsets marking the beginning of
90    * each line in the unpatched content. */
91   apr_array_header_t *lines;
92
93   /* An array containing hunk_info_t structures for hunks already matched. */
94   apr_array_header_t *hunks;
95
96   /* True if end-of-file was reached while reading from the unpatched
97    * content. */
98   svn_boolean_t eof;
99
100   /* The keywords of the target. They will be contracted when reading
101    * unpatched content and expanded when writing patched content.
102    * When patching properties this hash is always empty. */
103   apr_hash_t *keywords;
104
105   /* A callback, with an associated baton, to read a line of unpatched
106    * content. */
107   svn_error_t *(*readline)(void *baton, svn_stringbuf_t **line,
108                            const char **eol_str, svn_boolean_t *eof,
109                            apr_pool_t *result_pool, apr_pool_t *scratch_pool);
110   void *read_baton;
111
112   /* A callback to get the current byte offset within the unpatched
113    * content. Uses the read baton. */
114   svn_error_t * (*tell)(void *baton, apr_off_t *offset,
115                         apr_pool_t *scratch_pool);
116
117   /* A callback to seek to an offset within the unpatched content.
118    * Uses the read baton. */
119   svn_error_t * (*seek)(void *baton, apr_off_t offset,
120                         apr_pool_t *scratch_pool);
121
122   /* A callback to write data to the patched content, with an
123    * associated baton. */
124   svn_error_t * (*write)(void *baton, const char *buf, apr_size_t len,
125                          apr_pool_t *scratch_pool);
126   void *write_baton;
127
128 } target_content_t;
129
130 typedef struct prop_patch_target_t {
131
132   /* The name of the property */
133   const char *name;
134
135   /* The property value. This is NULL in case the property did not exist
136    * prior to patch application (see also CONTENT->existed).
137    * Note that the patch implementation does not support binary properties,
138    * so this string is not expected to contain embedded NUL characters. */
139   const svn_string_t *value;
140
141   /* The patched property value.
142    * This is equivalent to the target, except that in appropriate
143    * places it contains the modified text as it appears in the patch file. */
144   svn_stringbuf_t *patched_value;
145
146   /* All information that is specific to the content of the property. */
147   target_content_t *content;
148
149   /* Represents the operation performed on the property. It can be added,
150    * deleted or modified.
151    * ### Should we use flags instead since we're not using all enum values? */
152   svn_diff_operation_kind_t operation;
153
154   /* ### Here we'll add flags telling if the prop was added, deleted,
155    * ### had_rejects, had_local_mods prior to patching and so on. */
156 } prop_patch_target_t;
157
158 typedef struct patch_target_t {
159   /* The target path as it appeared in the patch file,
160    * but in canonicalised form. */
161   const char *canon_path_from_patchfile;
162
163   /* The target path, relative to the working copy directory the
164    * patch is being applied to. A patch strip count applies to this
165    * and only this path. This is never NULL. */
166   const char *local_relpath;
167
168   /* The absolute path of the target on the filesystem.
169    * Any symlinks the path from the patch file may contain are resolved.
170    * Is not always known, so it may be NULL. */
171   const char *local_abspath;
172
173   /* The target file, read-only. This is NULL in case the target
174    * file did not exist prior to patch application (see also
175    * CONTENT->existed). */
176   apr_file_t *file;
177
178   /* The target file is a symlink */
179   svn_boolean_t is_symlink;
180
181   /* The patched file.
182    * This is equivalent to the target, except that in appropriate
183    * places it contains the modified text as it appears in the patch file.
184    * The data in this file is written in repository-normal form.
185    * EOL transformation and keyword contraction is performed when the
186    * patched result is installed in the working copy. */
187   apr_file_t *patched_file;
188
189   /* Path to the patched file. */
190   const char *patched_path;
191
192   /* Hunks that are rejected will be written to this file. */
193   apr_file_t *reject_file;
194
195   /* Path to the reject file. */
196   const char *reject_path;
197
198   /* The node kind of the target as found in WC-DB prior
199    * to patch application. */
200   svn_node_kind_t db_kind;
201
202   /* The target's kind on disk prior to patch application. */
203   svn_node_kind_t kind_on_disk;
204
205   /* True if the target was locally deleted prior to patching. */
206   svn_boolean_t locally_deleted;
207
208   /* True if the target had to be skipped for some reason. */
209   svn_boolean_t skipped;
210
211   /* True if the target has been filtered by the patch callback. */
212   svn_boolean_t filtered;
213
214   /* True if at least one hunk was rejected. */
215   svn_boolean_t had_rejects;
216
217   /* True if at least one property hunk was rejected. */
218   svn_boolean_t had_prop_rejects;
219
220   /* True if the target file had local modifications before the
221    * patch was applied to it. */
222   svn_boolean_t local_mods;
223
224   /* True if the target was added by the patch, which means that it did
225    * not exist on disk before patching and has content after patching. */
226   svn_boolean_t added;
227
228   /* True if the target ended up being deleted by the patch. */
229   svn_boolean_t deleted;
230
231   /* True if the target ended up being replaced by the patch
232    * (i.e. a new file was added on top locally deleted node). */
233   svn_boolean_t replaced;
234
235   /* True if the target has the executable bit set. */
236   svn_boolean_t executable;
237
238   /* True if the patch changed the text of the target. */
239   svn_boolean_t has_text_changes;
240
241   /* True if the patch changed any of the properties of the target. */
242   svn_boolean_t has_prop_changes;
243
244   /* True if the patch contained a svn:special property. */
245   svn_boolean_t is_special;
246
247   /* All the information that is specific to the content of the target. */
248   target_content_t *content;
249
250   /* A hash table of prop_patch_target_t objects keyed by property names. */
251   apr_hash_t *prop_targets;
252
253 } patch_target_t;
254
255
256 /* A smaller struct containing a subset of patch_target_t.
257  * Carries the minimal amount of information we still need for a
258  * target after we're done patching it so we can free other resources. */
259 typedef struct patch_target_info_t {
260   const char *local_abspath;
261   svn_boolean_t deleted;
262 } patch_target_info_t;
263
264
265 /* Strip STRIP_COUNT components from the front of PATH, returning
266  * the result in *RESULT, allocated in RESULT_POOL.
267  * Do temporary allocations in SCRATCH_POOL. */
268 static svn_error_t *
269 strip_path(const char **result, const char *path, int strip_count,
270            apr_pool_t *result_pool, apr_pool_t *scratch_pool)
271 {
272   int i;
273   apr_array_header_t *components;
274   apr_array_header_t *stripped;
275
276   components = svn_path_decompose(path, scratch_pool);
277   if (strip_count > components->nelts)
278     return svn_error_createf(SVN_ERR_CLIENT_PATCH_BAD_STRIP_COUNT, NULL,
279                              _("Cannot strip %u components from '%s'"),
280                              strip_count,
281                              svn_dirent_local_style(path, scratch_pool));
282
283   stripped = apr_array_make(scratch_pool, components->nelts - strip_count,
284                             sizeof(const char *));
285   for (i = strip_count; i < components->nelts; i++)
286     {
287       const char *component;
288
289       component = APR_ARRAY_IDX(components, i, const char *);
290       APR_ARRAY_PUSH(stripped, const char *) = component;
291     }
292
293   *result = svn_path_compose(stripped, result_pool);
294
295   return SVN_NO_ERROR;
296 }
297
298 /* Obtain KEYWORDS, EOL_STYLE and EOL_STR for LOCAL_ABSPATH.
299  * WC_CTX is a context for the working copy the patch is applied to.
300  * Use RESULT_POOL for allocations of fields in TARGET.
301  * Use SCRATCH_POOL for all other allocations. */
302 static svn_error_t *
303 obtain_eol_and_keywords_for_file(apr_hash_t **keywords,
304                                  svn_subst_eol_style_t *eol_style,
305                                  const char **eol_str,
306                                  svn_wc_context_t *wc_ctx,
307                                  const char *local_abspath,
308                                  apr_pool_t *result_pool,
309                                  apr_pool_t *scratch_pool)
310 {
311   apr_hash_t *props;
312   svn_string_t *keywords_val, *eol_style_val;
313
314   SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, local_abspath,
315                             scratch_pool, scratch_pool));
316   keywords_val = svn_hash_gets(props, SVN_PROP_KEYWORDS);
317   if (keywords_val)
318     {
319       svn_revnum_t changed_rev;
320       apr_time_t changed_date;
321       const char *rev_str;
322       const char *author;
323       const char *url;
324       const char *root_url;
325
326       SVN_ERR(svn_wc__node_get_changed_info(&changed_rev,
327                                             &changed_date,
328                                             &author, wc_ctx,
329                                             local_abspath,
330                                             scratch_pool,
331                                             scratch_pool));
332       rev_str = apr_psprintf(scratch_pool, "%ld", changed_rev);
333       SVN_ERR(svn_wc__node_get_url(&url, wc_ctx,
334                                    local_abspath,
335                                    scratch_pool, scratch_pool));
336       SVN_ERR(svn_wc__node_get_repos_info(NULL, NULL, &root_url, NULL,
337                                           wc_ctx, local_abspath,
338                                           scratch_pool, scratch_pool));
339       SVN_ERR(svn_subst_build_keywords3(keywords,
340                                         keywords_val->data,
341                                         rev_str, url, root_url, changed_date,
342                                         author, result_pool));
343     }
344
345   eol_style_val = svn_hash_gets(props, SVN_PROP_EOL_STYLE);
346   if (eol_style_val)
347     {
348       svn_subst_eol_style_from_value(eol_style,
349                                      eol_str,
350                                      eol_style_val->data);
351     }
352
353   return SVN_NO_ERROR;
354 }
355
356 /* Resolve the exact path for a patch TARGET at path PATH_FROM_PATCHFILE,
357  * which is the path of the target as it appeared in the patch file.
358  * Put a canonicalized version of PATH_FROM_PATCHFILE into
359  * TARGET->CANON_PATH_FROM_PATCHFILE.
360  * WC_CTX is a context for the working copy the patch is applied to.
361  * If possible, determine TARGET->WC_PATH, TARGET->ABS_PATH, TARGET->KIND,
362  * TARGET->ADDED, and TARGET->PARENT_DIR_EXISTS.
363  * Indicate in TARGET->SKIPPED whether the target should be skipped.
364  * STRIP_COUNT specifies the number of leading path components
365  * which should be stripped from target paths in the patch.
366  * PROP_CHANGES_ONLY specifies whether the target path is allowed to have
367  * only property changes, and no content changes (in which case the target
368  * must be a directory).
369  * Use RESULT_POOL for allocations of fields in TARGET.
370  * Use SCRATCH_POOL for all other allocations. */
371 static svn_error_t *
372 resolve_target_path(patch_target_t *target,
373                     const char *path_from_patchfile,
374                     const char *wcroot_abspath,
375                     int strip_count,
376                     svn_boolean_t prop_changes_only,
377                     svn_wc_context_t *wc_ctx,
378                     apr_pool_t *result_pool,
379                     apr_pool_t *scratch_pool)
380 {
381   const char *stripped_path;
382   svn_wc_status3_t *status;
383   svn_error_t *err;
384   svn_boolean_t under_root;
385
386   target->canon_path_from_patchfile = svn_dirent_internal_style(
387                                         path_from_patchfile, result_pool);
388
389   /* We allow properties to be set on the wc root dir. */
390   if (! prop_changes_only && target->canon_path_from_patchfile[0] == '\0')
391     {
392       /* An empty patch target path? What gives? Skip this. */
393       target->skipped = TRUE;
394       target->local_abspath = NULL;
395       target->local_relpath = "";
396       return SVN_NO_ERROR;
397     }
398
399   if (strip_count > 0)
400     SVN_ERR(strip_path(&stripped_path, target->canon_path_from_patchfile,
401                        strip_count, result_pool, scratch_pool));
402   else
403     stripped_path = target->canon_path_from_patchfile;
404
405   if (svn_dirent_is_absolute(stripped_path))
406     {
407       target->local_relpath = svn_dirent_is_child(wcroot_abspath,
408                                                   stripped_path,
409                                                   result_pool);
410
411       if (! target->local_relpath)
412         {
413           /* The target path is either outside of the working copy
414            * or it is the working copy itself. Skip it. */
415           target->skipped = TRUE;
416           target->local_abspath = NULL;
417           target->local_relpath = stripped_path;
418           return SVN_NO_ERROR;
419         }
420     }
421   else
422     {
423       target->local_relpath = stripped_path;
424     }
425
426   /* Make sure the path is secure to use. We want the target to be inside
427    * of the working copy and not be fooled by symlinks it might contain. */
428   SVN_ERR(svn_dirent_is_under_root(&under_root,
429                                    &target->local_abspath, wcroot_abspath,
430                                    target->local_relpath, result_pool));
431
432   if (! under_root)
433     {
434       /* The target path is outside of the working copy. Skip it. */
435       target->skipped = TRUE;
436       target->local_abspath = NULL;
437       return SVN_NO_ERROR;
438     }
439
440   /* Skip things we should not be messing with. */
441   err = svn_wc_status3(&status, wc_ctx, target->local_abspath,
442                        result_pool, scratch_pool);
443   if (err)
444     {
445       if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
446         return svn_error_trace(err);
447
448       svn_error_clear(err);
449
450       target->locally_deleted = TRUE;
451       target->db_kind = svn_node_none;
452       status = NULL;
453     }
454   else if (status->node_status == svn_wc_status_ignored ||
455            status->node_status == svn_wc_status_unversioned ||
456            status->node_status == svn_wc_status_missing ||
457            status->node_status == svn_wc_status_obstructed ||
458            status->conflicted)
459     {
460       target->skipped = TRUE;
461       return SVN_NO_ERROR;
462     }
463   else if (status->node_status == svn_wc_status_deleted)
464     {
465       target->locally_deleted = TRUE;
466     }
467
468   if (status && (status->kind != svn_node_unknown))
469     target->db_kind = status->kind;
470   else
471     target->db_kind = svn_node_none;
472
473   SVN_ERR(svn_io_check_special_path(target->local_abspath,
474                                     &target->kind_on_disk, &target->is_symlink,
475                                     scratch_pool));
476
477   if (target->locally_deleted)
478     {
479       const char *moved_to_abspath;
480
481       SVN_ERR(svn_wc__node_was_moved_away(&moved_to_abspath, NULL,
482                                           wc_ctx, target->local_abspath,
483                                           result_pool, scratch_pool));
484       if (moved_to_abspath)
485         {
486           target->local_abspath = moved_to_abspath;
487           target->local_relpath = svn_dirent_skip_ancestor(wcroot_abspath,
488                                                           moved_to_abspath);
489           SVN_ERR_ASSERT(target->local_relpath &&
490                          target->local_relpath[0] != '\0');
491
492           /* As far as we are concerned this target is not locally deleted. */
493           target->locally_deleted = FALSE;
494
495           SVN_ERR(svn_io_check_special_path(target->local_abspath,
496                                             &target->kind_on_disk,
497                                             &target->is_symlink,
498                                             scratch_pool));
499         }
500       else if (target->kind_on_disk != svn_node_none)
501         {
502           target->skipped = TRUE;
503           return SVN_NO_ERROR;
504         }
505     }
506
507   return SVN_NO_ERROR;
508 }
509
510 /* Baton for reading from properties. */
511 typedef struct prop_read_baton_t {
512   const svn_string_t *value;
513   apr_off_t offset;
514 } prop_read_baton_t;
515
516 /* Allocate *STRINGBUF in RESULT_POOL, and read into it one line from
517  * the unpatched property value accessed via BATON.
518  * Reading stops either after a line-terminator was found, or if
519  * the property value runs out in which case *EOF is set to TRUE.
520  * The line-terminator is not stored in *STRINGBUF.
521  *
522  * If the line is empty or could not be read, *line is set to NULL.
523  *
524  * The line-terminator is detected automatically and stored in *EOL
525  * if EOL is not NULL. If the end of the property value is reached
526  * and does not end with a newline character, and EOL is not NULL,
527  * *EOL is set to NULL.
528  *
529  * SCRATCH_POOL is used for temporary allocations.
530  */
531 static svn_error_t *
532 readline_prop(void *baton, svn_stringbuf_t **line, const char **eol_str,
533               svn_boolean_t *eof, apr_pool_t *result_pool,
534               apr_pool_t *scratch_pool)
535 {
536   prop_read_baton_t *b = (prop_read_baton_t *)baton;
537   svn_stringbuf_t *str = NULL;
538   const char *c;
539   svn_boolean_t found_eof;
540
541   if ((apr_uint64_t)b->offset >= (apr_uint64_t)b->value->len)
542     {
543       *eol_str = NULL;
544       *eof = TRUE;
545       *line = NULL;
546       return SVN_NO_ERROR;
547     }
548
549   /* Read bytes into STR up to and including, but not storing,
550    * the next EOL sequence. */
551   *eol_str = NULL;
552   found_eof = FALSE;
553   do
554     {
555       c = b->value->data + b->offset;
556       b->offset++;
557
558       if (*c == '\0')
559         {
560           found_eof = TRUE;
561           break;
562         }
563       else if (*c == '\n')
564         {
565           *eol_str = "\n";
566         }
567       else if (*c == '\r')
568         {
569           *eol_str = "\r";
570           if (*(c + 1) == '\n')
571             {
572               *eol_str = "\r\n";
573               b->offset++;
574             }
575         }
576       else
577         {
578           if (str == NULL)
579             str = svn_stringbuf_create_ensure(80, result_pool);
580           svn_stringbuf_appendbyte(str, *c);
581         }
582
583       if (*eol_str)
584         break;
585     }
586   while (c < b->value->data + b->value->len);
587
588   if (eof)
589     *eof = found_eof;
590   *line = str;
591
592   return SVN_NO_ERROR;
593 }
594
595 /* Return in *OFFSET the current byte offset for reading from the
596  * unpatched property value accessed via BATON.
597  * Use SCRATCH_POOL for temporary allocations. */
598 static svn_error_t *
599 tell_prop(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool)
600 {
601   prop_read_baton_t *b = (prop_read_baton_t *)baton;
602   *offset = b->offset;
603   return SVN_NO_ERROR;
604 }
605
606 /* Seek to the specified by OFFSET in the unpatched property value accessed
607  * via BATON. Use SCRATCH_POOL for temporary allocations. */
608 static svn_error_t *
609 seek_prop(void *baton, apr_off_t offset, apr_pool_t *scratch_pool)
610 {
611   prop_read_baton_t *b = (prop_read_baton_t *)baton;
612   b->offset = offset;
613   return SVN_NO_ERROR;
614 }
615
616 /* Write LEN bytes from BUF into the patched property value accessed
617  * via BATON. Use SCRATCH_POOL for temporary allocations. */
618 static svn_error_t *
619 write_prop(void *baton, const char *buf, apr_size_t len,
620            apr_pool_t *scratch_pool)
621 {
622   svn_stringbuf_t *patched_value = (svn_stringbuf_t *)baton;
623   svn_stringbuf_appendbytes(patched_value, buf, len);
624   return SVN_NO_ERROR;
625 }
626
627 /* Initialize a PROP_TARGET structure for PROP_NAME on the patch target
628  * at LOCAL_ABSPATH. OPERATION indicates the operation performed on the
629  * property. Use working copy context WC_CTX.
630  * Allocate results in RESULT_POOL.
631  * Use SCRATCH_POOL for temporary allocations. */
632 static svn_error_t *
633 init_prop_target(prop_patch_target_t **prop_target,
634                  const char *prop_name,
635                  svn_diff_operation_kind_t operation,
636                  svn_wc_context_t *wc_ctx,
637                  const char *local_abspath,
638                  apr_pool_t *result_pool, apr_pool_t *scratch_pool)
639 {
640   prop_patch_target_t *new_prop_target;
641   target_content_t *content;
642   const svn_string_t *value;
643   svn_error_t *err;
644   prop_read_baton_t *prop_read_baton;
645
646   content = apr_pcalloc(result_pool, sizeof(*content));
647
648   /* All other fields are FALSE or NULL due to apr_pcalloc(). */
649   content->current_line = 1;
650   content->eol_style = svn_subst_eol_style_none;
651   content->lines = apr_array_make(result_pool, 0, sizeof(apr_off_t));
652   content->hunks = apr_array_make(result_pool, 0, sizeof(hunk_info_t *));
653   content->keywords = apr_hash_make(result_pool);
654
655   new_prop_target = apr_pcalloc(result_pool, sizeof(*new_prop_target));
656   new_prop_target->name = apr_pstrdup(result_pool, prop_name);
657   new_prop_target->operation = operation;
658   new_prop_target->content = content;
659
660   err = svn_wc_prop_get2(&value, wc_ctx, local_abspath, prop_name,
661                          result_pool, scratch_pool);
662   if (err)
663     {
664       if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
665         {
666           svn_error_clear(err);
667           value = NULL;
668         }
669       else
670         return svn_error_trace(err);
671     }
672   content->existed = (value != NULL);
673   new_prop_target->value = value;
674   new_prop_target->patched_value = svn_stringbuf_create_empty(result_pool);
675
676
677   /* Wire up the read and write callbacks. */
678   prop_read_baton = apr_pcalloc(result_pool, sizeof(*prop_read_baton));
679   prop_read_baton->value = value;
680   prop_read_baton->offset = 0;
681   content->readline = readline_prop;
682   content->tell = tell_prop;
683   content->seek = seek_prop;
684   content->read_baton = prop_read_baton;
685   content->write = write_prop;
686   content->write_baton = new_prop_target->patched_value;
687
688   *prop_target = new_prop_target;
689
690   return SVN_NO_ERROR;
691 }
692
693 /* Allocate *STRINGBUF in RESULT_POOL, and read into it one line from
694  * the unpatched file content accessed via BATON.
695  * Reading stops either after a line-terminator was found,
696  * or if EOF is reached in which case *EOF is set to TRUE.
697  * The line-terminator is not stored in *STRINGBUF.
698  *
699  * If the line is empty or could not be read, *line is set to NULL.
700  *
701  * The line-terminator is detected automatically and stored in *EOL
702  * if EOL is not NULL. If EOF is reached and FILE does not end
703  * with a newline character, and EOL is not NULL, *EOL is set to NULL.
704  *
705  * SCRATCH_POOL is used for temporary allocations.
706  */
707 static svn_error_t *
708 readline_file(void *baton, svn_stringbuf_t **line, const char **eol_str,
709               svn_boolean_t *eof, apr_pool_t *result_pool,
710               apr_pool_t *scratch_pool)
711 {
712   apr_file_t *file = (apr_file_t *)baton;
713   svn_stringbuf_t *str = NULL;
714   apr_size_t numbytes;
715   char c;
716   svn_boolean_t found_eof;
717
718   /* Read bytes into STR up to and including, but not storing,
719    * the next EOL sequence. */
720   *eol_str = NULL;
721   numbytes = 1;
722   found_eof = FALSE;
723   while (!found_eof)
724     {
725       SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes,
726                                      &found_eof, scratch_pool));
727       if (numbytes != 1)
728         {
729           found_eof = TRUE;
730           break;
731         }
732
733       if (c == '\n')
734         {
735           *eol_str = "\n";
736         }
737       else if (c == '\r')
738         {
739           *eol_str = "\r";
740
741           if (!found_eof)
742             {
743               apr_off_t pos;
744
745               /* Check for "\r\n" by peeking at the next byte. */
746               pos = 0;
747               SVN_ERR(svn_io_file_seek(file, APR_CUR, &pos, scratch_pool));
748               SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes,
749                                              &found_eof, scratch_pool));
750               if (numbytes == 1 && c == '\n')
751                 {
752                   *eol_str = "\r\n";
753                 }
754               else
755                 {
756                   /* Pretend we never peeked. */
757                   SVN_ERR(svn_io_file_seek(file, APR_SET, &pos, scratch_pool));
758                   found_eof = FALSE;
759                   numbytes = 1;
760                 }
761             }
762         }
763       else
764         {
765           if (str == NULL)
766             str = svn_stringbuf_create_ensure(80, result_pool);
767           svn_stringbuf_appendbyte(str, c);
768         }
769
770       if (*eol_str)
771         break;
772     }
773
774   if (eof)
775     *eof = found_eof;
776   *line = str;
777
778   return SVN_NO_ERROR;
779 }
780
781 /* Return in *OFFSET the current byte offset for reading from the
782  * unpatched file content accessed via BATON.
783  * Use SCRATCH_POOL for temporary allocations. */
784 static svn_error_t *
785 tell_file(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool)
786 {
787   apr_file_t *file = (apr_file_t *)baton;
788   *offset = 0;
789   SVN_ERR(svn_io_file_seek(file, APR_CUR, offset, scratch_pool));
790   return SVN_NO_ERROR;
791 }
792
793 /* Seek to the specified by OFFSET in the unpatched file content accessed
794  * via BATON. Use SCRATCH_POOL for temporary allocations. */
795 static svn_error_t *
796 seek_file(void *baton, apr_off_t offset, apr_pool_t *scratch_pool)
797 {
798   apr_file_t *file = (apr_file_t *)baton;
799   SVN_ERR(svn_io_file_seek(file, APR_SET, &offset, scratch_pool));
800   return SVN_NO_ERROR;
801 }
802
803 /* Write LEN bytes from BUF into the patched file content accessed
804  * via BATON. Use SCRATCH_POOL for temporary allocations. */
805 static svn_error_t *
806 write_file(void *baton, const char *buf, apr_size_t len,
807            apr_pool_t *scratch_pool)
808 {
809   apr_file_t *file = (apr_file_t *)baton;
810   SVN_ERR(svn_io_file_write_full(file, buf, len, &len, scratch_pool));
811   return SVN_NO_ERROR;
812 }
813
814 /* Handling symbolic links:
815  *
816  * In Subversion, symlinks can be represented on disk in two distinct ways.
817  * On systems which support symlinks, a symlink is created on disk.
818  * On systems which do not support symlink, a file is created on disk
819  * which contains the "normal form" of the symlink, which looks like:
820  *   link TARGET
821  * where TARGET is the file the symlink points to.
822  *
823  * When reading symlinks (i.e. the link itself, not the file the symlink
824  * is pointing to) through the svn_subst_create_specialfile() function
825  * into a buffer, the buffer always contains the "normal form" of the symlink.
826  * Due to this representation symlinks always contain a single line of text.
827  *
828  * The functions below are needed to deal with the case where a patch
829  * wants to change the TARGET that a symlink points to.
830  */
831
832 /* Baton for the (readline|tell|seek|write)_symlink functions. */
833 struct symlink_baton_t
834 {
835   /* The path to the symlink on disk (not the path to the target of the link) */
836   const char *local_abspath;
837
838   /* Indicates whether the "normal form" of the symlink has been read. */
839   svn_boolean_t at_eof;
840 };
841
842 /* Allocate *STRINGBUF in RESULT_POOL, and store into it the "normal form"
843  * of the symlink accessed via BATON.
844  *
845  * Otherwise behaves like readline_file(), which see.
846  */
847 static svn_error_t *
848 readline_symlink(void *baton, svn_stringbuf_t **line, const char **eol_str,
849                  svn_boolean_t *eof, apr_pool_t *result_pool,
850                  apr_pool_t *scratch_pool)
851 {
852   struct symlink_baton_t *sb = baton;
853
854   if (eof)
855     *eof = TRUE;
856   if (eol_str)
857     *eol_str = NULL;
858
859   if (sb->at_eof)
860     {
861       *line = NULL;
862     }
863   else
864     {
865       svn_string_t *dest;
866
867       SVN_ERR(svn_io_read_link(&dest, sb->local_abspath, scratch_pool));
868       *line = svn_stringbuf_createf(result_pool, "link %s", dest->data);
869       sb->at_eof = TRUE;
870     }
871
872   return SVN_NO_ERROR;
873 }
874
875 /* Set *OFFSET to 1 or 0 depending on whether the "normal form" of
876  * the symlink has already been read. */
877 static svn_error_t *
878 tell_symlink(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool)
879 {
880   struct symlink_baton_t *sb = baton;
881
882   *offset = sb->at_eof ? 1 : 0;
883   return SVN_NO_ERROR;
884 }
885
886 /* If offset is non-zero, mark the symlink as having been read in its
887  * "normal form". Else, mark the symlink as not having been read yet. */
888 static svn_error_t *
889 seek_symlink(void *baton, apr_off_t offset, apr_pool_t *scratch_pool)
890 {
891   struct symlink_baton_t *sb = baton;
892
893   sb->at_eof = (offset != 0);
894   return SVN_NO_ERROR;
895 }
896
897
898 /* Set the target of the symlink accessed via BATON.
899  * The contents of BUF must be a valid "normal form" of a symlink. */
900 static svn_error_t *
901 write_symlink(void *baton, const char *buf, apr_size_t len,
902               apr_pool_t *scratch_pool)
903 {
904   const char *target_abspath = baton;
905   const char *new_name;
906   const char *link = apr_pstrndup(scratch_pool, buf, len);
907
908   if (strncmp(link, "link ", 5) != 0)
909     return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL,
910                             _("Invalid link representation"));
911
912   link += 5; /* Skip "link " */
913
914   /* We assume the entire symlink is written at once, as the patch
915      format is line based */
916
917   SVN_ERR(svn_io_create_unique_link(&new_name, target_abspath, link,
918                                     ".tmp", scratch_pool));
919
920   SVN_ERR(svn_io_file_rename(new_name, target_abspath, scratch_pool));
921
922   return SVN_NO_ERROR;
923 }
924
925
926 /* Return a suitable filename for the target of PATCH.
927  * Examine the ``old'' and ``new'' file names, and choose the file name
928  * with the fewest path components, the shortest basename, and the shortest
929  * total file name length (in that order). In case of a tie, return the new
930  * filename. This heuristic is also used by Larry Wall's UNIX patch (except
931  * that it prompts for a filename in case of a tie).
932  * Additionally, for compatibility with git, if one of the filenames
933  * is "/dev/null", use the other filename. */
934 static const char *
935 choose_target_filename(const svn_patch_t *patch)
936 {
937   apr_size_t old;
938   apr_size_t new;
939
940   if (strcmp(patch->old_filename, "/dev/null") == 0)
941     return patch->new_filename;
942   if (strcmp(patch->new_filename, "/dev/null") == 0)
943     return patch->old_filename;
944
945   old = svn_path_component_count(patch->old_filename);
946   new = svn_path_component_count(patch->new_filename);
947
948   if (old == new)
949     {
950       old = strlen(svn_dirent_basename(patch->old_filename, NULL));
951       new = strlen(svn_dirent_basename(patch->new_filename, NULL));
952
953       if (old == new)
954         {
955           old = strlen(patch->old_filename);
956           new = strlen(patch->new_filename);
957         }
958     }
959
960   return (old < new) ? patch->old_filename : patch->new_filename;
961 }
962
963 /* Attempt to initialize a *PATCH_TARGET structure for a target file
964  * described by PATCH. Use working copy context WC_CTX.
965  * STRIP_COUNT specifies the number of leading path components
966  * which should be stripped from target paths in the patch.
967  * The patch target structure is allocated in RESULT_POOL, but if the target
968  * should be skipped, PATCH_TARGET->SKIPPED is set and the target should be
969  * treated as not fully initialized, e.g. the caller should not not do any
970  * further operations on the target if it is marked to be skipped.
971  * If REMOVE_TEMPFILES is TRUE, set up temporary files to be removed as
972  * soon as they are no longer needed.
973  * Use SCRATCH_POOL for all other allocations. */
974 static svn_error_t *
975 init_patch_target(patch_target_t **patch_target,
976                   const svn_patch_t *patch,
977                   const char *wcroot_abspath,
978                   svn_wc_context_t *wc_ctx, int strip_count,
979                   svn_boolean_t remove_tempfiles,
980                   apr_pool_t *result_pool, apr_pool_t *scratch_pool)
981 {
982   patch_target_t *target;
983   target_content_t *content;
984   svn_boolean_t has_prop_changes = FALSE;
985   svn_boolean_t prop_changes_only = FALSE;
986
987   {
988     apr_hash_index_t *hi;
989
990     for (hi = apr_hash_first(scratch_pool, patch->prop_patches);
991          hi;
992          hi = apr_hash_next(hi))
993       {
994         svn_prop_patch_t *prop_patch = svn__apr_hash_index_val(hi);
995         if (! has_prop_changes)
996           has_prop_changes = prop_patch->hunks->nelts > 0;
997         else
998           break;
999       }
1000   }
1001
1002   prop_changes_only = has_prop_changes && patch->hunks->nelts == 0;
1003
1004   content = apr_pcalloc(result_pool, sizeof(*content));
1005
1006   /* All other fields in content are FALSE or NULL due to apr_pcalloc().*/
1007   content->current_line = 1;
1008   content->eol_style = svn_subst_eol_style_none;
1009   content->lines = apr_array_make(result_pool, 0, sizeof(apr_off_t));
1010   content->hunks = apr_array_make(result_pool, 0, sizeof(hunk_info_t *));
1011   content->keywords = apr_hash_make(result_pool);
1012
1013   target = apr_pcalloc(result_pool, sizeof(*target));
1014
1015   /* All other fields in target are FALSE or NULL due to apr_pcalloc(). */
1016   target->db_kind = svn_node_none;
1017   target->kind_on_disk = svn_node_none;
1018   target->content = content;
1019   target->prop_targets = apr_hash_make(result_pool);
1020
1021   SVN_ERR(resolve_target_path(target, choose_target_filename(patch),
1022                               wcroot_abspath, strip_count, prop_changes_only,
1023                               wc_ctx, result_pool, scratch_pool));
1024   if (! target->skipped)
1025     {
1026       const char *diff_header;
1027       apr_size_t len;
1028
1029       /* Create a temporary file to write the patched result to.
1030        * Also grab various bits of information about the file. */
1031       if (target->is_symlink)
1032         {
1033           struct symlink_baton_t *sb = apr_pcalloc(result_pool, sizeof(*sb));
1034           content->existed = TRUE;
1035
1036           sb->local_abspath = target->local_abspath;
1037
1038           /* Wire up the read callbacks. */
1039           content->read_baton = sb;
1040
1041           content->readline = readline_symlink;
1042           content->seek = seek_symlink;
1043           content->tell = tell_symlink;
1044         }
1045       else if (target->kind_on_disk == svn_node_file)
1046         {
1047           SVN_ERR(svn_io_file_open(&target->file, target->local_abspath,
1048                                    APR_READ | APR_BUFFERED,
1049                                    APR_OS_DEFAULT, result_pool));
1050           SVN_ERR(svn_wc_text_modified_p2(&target->local_mods, wc_ctx,
1051                                           target->local_abspath, FALSE,
1052                                           scratch_pool));
1053           SVN_ERR(svn_io_is_file_executable(&target->executable,
1054                                             target->local_abspath,
1055                                             scratch_pool));
1056           SVN_ERR(obtain_eol_and_keywords_for_file(&content->keywords,
1057                                                    &content->eol_style,
1058                                                    &content->eol_str,
1059                                                    wc_ctx,
1060                                                    target->local_abspath,
1061                                                    result_pool,
1062                                                    scratch_pool));
1063           content->existed = TRUE;
1064
1065           /* Wire up the read callbacks. */
1066           content->readline = readline_file;
1067           content->seek = seek_file;
1068           content->tell = tell_file;
1069           content->read_baton = target->file;
1070         }
1071
1072       /* ### Is it ok to set the operation of the target already here? Isn't
1073        * ### the target supposed to be marked with an operation after we have
1074        * ### determined that the changes will apply cleanly to the WC? Maybe
1075        * ### we should have kept the patch field in patch_target_t to be
1076        * ### able to distinguish between 'what the patch says we should do'
1077        * ### and 'what we can do with the given state of our WC'. */
1078       if (patch->operation == svn_diff_op_added)
1079         target->added = TRUE;
1080       else if (patch->operation == svn_diff_op_deleted)
1081         target->deleted = TRUE;
1082
1083       if (! target->is_symlink)
1084         {
1085           /* Open a temporary file to write the patched result to. */
1086           SVN_ERR(svn_io_open_unique_file3(&target->patched_file,
1087                                            &target->patched_path, NULL,
1088                                            remove_tempfiles ?
1089                                              svn_io_file_del_on_pool_cleanup :
1090                                              svn_io_file_del_none,
1091                                            result_pool, scratch_pool));
1092
1093           /* Put the write callback in place. */
1094           content->write = write_file;
1095           content->write_baton = target->patched_file;
1096         }
1097       else
1098         {
1099           /* Put the write callback in place. */
1100           SVN_ERR(svn_io_open_unique_file3(NULL,
1101                                            &target->patched_path, NULL,
1102                                            remove_tempfiles ?
1103                                              svn_io_file_del_on_pool_cleanup :
1104                                              svn_io_file_del_none,
1105                                            result_pool, scratch_pool));
1106
1107           content->write_baton = (void*)target->patched_path;
1108
1109           content->write = write_symlink;
1110         }
1111
1112       /* Open a temporary file to write rejected hunks to. */
1113       SVN_ERR(svn_io_open_unique_file3(&target->reject_file,
1114                                        &target->reject_path, NULL,
1115                                        remove_tempfiles ?
1116                                          svn_io_file_del_on_pool_cleanup :
1117                                          svn_io_file_del_none,
1118                                        result_pool, scratch_pool));
1119
1120       /* The reject file needs a diff header. */
1121       diff_header = apr_psprintf(scratch_pool, "--- %s%s+++ %s%s",
1122                                  target->canon_path_from_patchfile,
1123                                  APR_EOL_STR,
1124                                  target->canon_path_from_patchfile,
1125                                  APR_EOL_STR);
1126       len = strlen(diff_header);
1127       SVN_ERR(svn_io_file_write_full(target->reject_file, diff_header, len,
1128                                      &len, scratch_pool));
1129
1130       /* Handle properties. */
1131       if (! target->skipped)
1132         {
1133           apr_hash_index_t *hi;
1134
1135           for (hi = apr_hash_first(result_pool, patch->prop_patches);
1136                hi;
1137                hi = apr_hash_next(hi))
1138             {
1139               const char *prop_name = svn__apr_hash_index_key(hi);
1140               svn_prop_patch_t *prop_patch = svn__apr_hash_index_val(hi);
1141               prop_patch_target_t *prop_target;
1142
1143               SVN_ERR(init_prop_target(&prop_target,
1144                                        prop_name,
1145                                        prop_patch->operation,
1146                                        wc_ctx, target->local_abspath,
1147                                        result_pool, scratch_pool));
1148               svn_hash_sets(target->prop_targets, prop_name, prop_target);
1149             }
1150         }
1151     }
1152
1153   *patch_target = target;
1154   return SVN_NO_ERROR;
1155 }
1156
1157 /* Read a *LINE from CONTENT. If the line has not been read before
1158  * mark the line in CONTENT->LINES.
1159  * If a line could be read successfully, increase CONTENT->CURRENT_LINE,
1160  * and allocate *LINE in RESULT_POOL.
1161  * Do temporary allocations in SCRATCH_POOL.
1162  */
1163 static svn_error_t *
1164 readline(target_content_t *content,
1165          const char **line,
1166          apr_pool_t *result_pool,
1167          apr_pool_t *scratch_pool)
1168 {
1169   svn_stringbuf_t *line_raw;
1170   const char *eol_str;
1171   svn_linenum_t max_line = (svn_linenum_t)content->lines->nelts + 1;
1172
1173   if (content->eof || content->readline == NULL)
1174     {
1175       *line = "";
1176       return SVN_NO_ERROR;
1177     }
1178
1179   SVN_ERR_ASSERT(content->current_line <= max_line);
1180   if (content->current_line == max_line)
1181     {
1182       apr_off_t offset;
1183
1184       SVN_ERR(content->tell(content->read_baton, &offset,
1185                             scratch_pool));
1186       APR_ARRAY_PUSH(content->lines, apr_off_t) = offset;
1187     }
1188
1189   SVN_ERR(content->readline(content->read_baton, &line_raw,
1190                             &eol_str, &content->eof,
1191                             result_pool, scratch_pool));
1192   if (content->eol_style == svn_subst_eol_style_none)
1193     content->eol_str = eol_str;
1194
1195   if (line_raw)
1196     {
1197       /* Contract keywords. */
1198       SVN_ERR(svn_subst_translate_cstring2(line_raw->data, line,
1199                                            NULL, FALSE,
1200                                            content->keywords, FALSE,
1201                                            result_pool));
1202     }
1203   else
1204     *line = "";
1205
1206   if ((line_raw && line_raw->len > 0) || eol_str)
1207     content->current_line++;
1208
1209   SVN_ERR_ASSERT(content->current_line > 0);
1210
1211   return SVN_NO_ERROR;
1212 }
1213
1214 /* Seek to the specified LINE in CONTENT.
1215  * Mark any lines not read before in CONTENT->LINES.
1216  * Do temporary allocations in SCRATCH_POOL.
1217  */
1218 static svn_error_t *
1219 seek_to_line(target_content_t *content, svn_linenum_t line,
1220              apr_pool_t *scratch_pool)
1221 {
1222   svn_linenum_t saved_line;
1223   svn_boolean_t saved_eof;
1224
1225   SVN_ERR_ASSERT(line > 0);
1226
1227   if (line == content->current_line)
1228     return SVN_NO_ERROR;
1229
1230   saved_line = content->current_line;
1231   saved_eof = content->eof;
1232
1233   if (line <= (svn_linenum_t)content->lines->nelts)
1234     {
1235       apr_off_t offset;
1236
1237       offset = APR_ARRAY_IDX(content->lines, line - 1, apr_off_t);
1238       SVN_ERR(content->seek(content->read_baton, offset,
1239                             scratch_pool));
1240       content->current_line = line;
1241     }
1242   else
1243     {
1244       const char *dummy;
1245       apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1246
1247       while (! content->eof && content->current_line < line)
1248         {
1249           svn_pool_clear(iterpool);
1250           SVN_ERR(readline(content, &dummy, iterpool, iterpool));
1251         }
1252       svn_pool_destroy(iterpool);
1253     }
1254
1255   /* After seeking backwards from EOF position clear EOF indicator. */
1256   if (saved_eof && saved_line > content->current_line)
1257     content->eof = FALSE;
1258
1259   return SVN_NO_ERROR;
1260 }
1261
1262 /* Indicate in *MATCHED whether the original text of HUNK matches the patch
1263  * CONTENT at its current line. Lines within FUZZ lines of the start or
1264  * end of HUNK will always match. If IGNORE_WHITESPACE is set, we ignore
1265  * whitespace when doing the matching. When this function returns, neither
1266  * CONTENT->CURRENT_LINE nor the file offset in the target file will
1267  * have changed. If MATCH_MODIFIED is TRUE, match the modified hunk text,
1268  * rather than the original hunk text.
1269  * Do temporary allocations in POOL. */
1270 static svn_error_t *
1271 match_hunk(svn_boolean_t *matched, target_content_t *content,
1272            svn_diff_hunk_t *hunk, svn_linenum_t fuzz,
1273            svn_boolean_t ignore_whitespace,
1274            svn_boolean_t match_modified, apr_pool_t *pool)
1275 {
1276   svn_stringbuf_t *hunk_line;
1277   const char *target_line;
1278   svn_linenum_t lines_read;
1279   svn_linenum_t saved_line;
1280   svn_boolean_t hunk_eof;
1281   svn_boolean_t lines_matched;
1282   apr_pool_t *iterpool;
1283   svn_linenum_t hunk_length;
1284   svn_linenum_t leading_context;
1285   svn_linenum_t trailing_context;
1286
1287   *matched = FALSE;
1288
1289   if (content->eof)
1290     return SVN_NO_ERROR;
1291
1292   saved_line = content->current_line;
1293   lines_read = 0;
1294   lines_matched = FALSE;
1295   leading_context = svn_diff_hunk_get_leading_context(hunk);
1296   trailing_context = svn_diff_hunk_get_trailing_context(hunk);
1297   if (match_modified)
1298     {
1299       svn_diff_hunk_reset_modified_text(hunk);
1300       hunk_length = svn_diff_hunk_get_modified_length(hunk);
1301     }
1302   else
1303     {
1304       svn_diff_hunk_reset_original_text(hunk);
1305       hunk_length = svn_diff_hunk_get_original_length(hunk);
1306     }
1307   iterpool = svn_pool_create(pool);
1308   do
1309     {
1310       const char *hunk_line_translated;
1311
1312       svn_pool_clear(iterpool);
1313
1314       if (match_modified)
1315         SVN_ERR(svn_diff_hunk_readline_modified_text(hunk, &hunk_line,
1316                                                      NULL, &hunk_eof,
1317                                                      iterpool, iterpool));
1318       else
1319         SVN_ERR(svn_diff_hunk_readline_original_text(hunk, &hunk_line,
1320                                                      NULL, &hunk_eof,
1321                                                      iterpool, iterpool));
1322
1323       /* Contract keywords, if any, before matching. */
1324       SVN_ERR(svn_subst_translate_cstring2(hunk_line->data,
1325                                            &hunk_line_translated,
1326                                            NULL, FALSE,
1327                                            content->keywords, FALSE,
1328                                            iterpool));
1329       SVN_ERR(readline(content, &target_line, iterpool, iterpool));
1330
1331       lines_read++;
1332
1333       /* If the last line doesn't have a newline, we get EOF but still
1334        * have a non-empty line to compare. */
1335       if ((hunk_eof && hunk_line->len == 0) ||
1336           (content->eof && *target_line == 0))
1337         break;
1338
1339       /* Leading/trailing fuzzy lines always match. */
1340       if ((lines_read <= fuzz && leading_context > fuzz) ||
1341           (lines_read > hunk_length - fuzz && trailing_context > fuzz))
1342         lines_matched = TRUE;
1343       else
1344         {
1345           if (ignore_whitespace)
1346             {
1347               char *hunk_line_trimmed;
1348               char *target_line_trimmed;
1349
1350               hunk_line_trimmed = apr_pstrdup(iterpool, hunk_line_translated);
1351               target_line_trimmed = apr_pstrdup(iterpool, target_line);
1352               apr_collapse_spaces(hunk_line_trimmed, hunk_line_trimmed);
1353               apr_collapse_spaces(target_line_trimmed, target_line_trimmed);
1354               lines_matched = ! strcmp(hunk_line_trimmed, target_line_trimmed);
1355             }
1356           else
1357             lines_matched = ! strcmp(hunk_line_translated, target_line);
1358         }
1359     }
1360   while (lines_matched);
1361
1362   *matched = lines_matched && hunk_eof && hunk_line->len == 0;
1363   SVN_ERR(seek_to_line(content, saved_line, iterpool));
1364   svn_pool_destroy(iterpool);
1365
1366   return SVN_NO_ERROR;
1367 }
1368
1369 /* Scan lines of CONTENT for a match of the original text of HUNK,
1370  * up to but not including the specified UPPER_LINE. Use fuzz factor FUZZ.
1371  * If UPPER_LINE is zero scan until EOF occurs when reading from TARGET.
1372  * Return the line at which HUNK was matched in *MATCHED_LINE.
1373  * If the hunk did not match at all, set *MATCHED_LINE to zero.
1374  * If the hunk matched multiple times, and MATCH_FIRST is TRUE,
1375  * return the line number at which the first match occurred in *MATCHED_LINE.
1376  * If the hunk matched multiple times, and MATCH_FIRST is FALSE,
1377  * return the line number at which the last match occurred in *MATCHED_LINE.
1378  * If IGNORE_WHITESPACE is set, ignore whitespace during the matching.
1379  * If MATCH_MODIFIED is TRUE, match the modified hunk text,
1380  * rather than the original hunk text.
1381  * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
1382  * Do all allocations in POOL. */
1383 static svn_error_t *
1384 scan_for_match(svn_linenum_t *matched_line,
1385                target_content_t *content,
1386                svn_diff_hunk_t *hunk, svn_boolean_t match_first,
1387                svn_linenum_t upper_line, svn_linenum_t fuzz,
1388                svn_boolean_t ignore_whitespace,
1389                svn_boolean_t match_modified,
1390                svn_cancel_func_t cancel_func, void *cancel_baton,
1391                apr_pool_t *pool)
1392 {
1393   apr_pool_t *iterpool;
1394
1395   *matched_line = 0;
1396   iterpool = svn_pool_create(pool);
1397   while ((content->current_line < upper_line || upper_line == 0) &&
1398          ! content->eof)
1399     {
1400       svn_boolean_t matched;
1401
1402       svn_pool_clear(iterpool);
1403
1404       if (cancel_func)
1405         SVN_ERR(cancel_func(cancel_baton));
1406
1407       SVN_ERR(match_hunk(&matched, content, hunk, fuzz, ignore_whitespace,
1408                          match_modified, iterpool));
1409       if (matched)
1410         {
1411           svn_boolean_t taken = FALSE;
1412           int i;
1413
1414           /* Don't allow hunks to match at overlapping locations. */
1415           for (i = 0; i < content->hunks->nelts; i++)
1416             {
1417               const hunk_info_t *hi;
1418               svn_linenum_t length;
1419
1420               hi = APR_ARRAY_IDX(content->hunks, i, const hunk_info_t *);
1421
1422               if (match_modified)
1423                 length = svn_diff_hunk_get_modified_length(hi->hunk);
1424               else
1425                 length = svn_diff_hunk_get_original_length(hi->hunk);
1426
1427               taken = (! hi->rejected &&
1428                        content->current_line >= hi->matched_line &&
1429                        content->current_line < (hi->matched_line + length));
1430               if (taken)
1431                 break;
1432             }
1433
1434           if (! taken)
1435             {
1436               *matched_line = content->current_line;
1437               if (match_first)
1438                 break;
1439             }
1440         }
1441
1442       if (! content->eof)
1443         SVN_ERR(seek_to_line(content, content->current_line + 1,
1444                              iterpool));
1445     }
1446   svn_pool_destroy(iterpool);
1447
1448   return SVN_NO_ERROR;
1449 }
1450
1451 /* Indicate in *MATCH whether the content described by CONTENT
1452  * matches the modified text of HUNK.
1453  * Use SCRATCH_POOL for temporary allocations. */
1454 static svn_error_t *
1455 match_existing_target(svn_boolean_t *match,
1456                       target_content_t *content,
1457                       svn_diff_hunk_t *hunk,
1458                       apr_pool_t *scratch_pool)
1459 {
1460   svn_boolean_t lines_matched;
1461   apr_pool_t *iterpool;
1462   svn_boolean_t hunk_eof;
1463   svn_linenum_t saved_line;
1464
1465   svn_diff_hunk_reset_modified_text(hunk);
1466
1467   saved_line = content->current_line;
1468
1469   iterpool = svn_pool_create(scratch_pool);
1470   do
1471     {
1472       const char *line;
1473       svn_stringbuf_t *hunk_line;
1474       const char *line_translated;
1475       const char *hunk_line_translated;
1476
1477       svn_pool_clear(iterpool);
1478
1479       SVN_ERR(readline(content, &line, iterpool, iterpool));
1480       SVN_ERR(svn_diff_hunk_readline_modified_text(hunk, &hunk_line,
1481                                                    NULL, &hunk_eof,
1482                                                    iterpool, iterpool));
1483       /* Contract keywords. */
1484       SVN_ERR(svn_subst_translate_cstring2(line, &line_translated,
1485                                            NULL, FALSE,
1486                                            content->keywords,
1487                                            FALSE, iterpool));
1488       SVN_ERR(svn_subst_translate_cstring2(hunk_line->data,
1489                                            &hunk_line_translated,
1490                                            NULL, FALSE,
1491                                            content->keywords,
1492                                            FALSE, iterpool));
1493       lines_matched = ! strcmp(line_translated, hunk_line_translated);
1494       if (content->eof != hunk_eof)
1495         {
1496           svn_pool_destroy(iterpool);
1497           *match = FALSE;
1498           return SVN_NO_ERROR;
1499         }
1500       }
1501     while (lines_matched && ! content->eof && ! hunk_eof);
1502     svn_pool_destroy(iterpool);
1503
1504     *match = (lines_matched && content->eof == hunk_eof);
1505     SVN_ERR(seek_to_line(content, saved_line, scratch_pool));
1506
1507     return SVN_NO_ERROR;
1508 }
1509
1510 /* Determine the line at which a HUNK applies to CONTENT of the TARGET
1511  * file, and return an appropriate hunk_info object in *HI, allocated from
1512  * RESULT_POOL. Use fuzz factor FUZZ. Set HI->FUZZ to FUZZ. If no correct
1513  * line can be determined, set HI->REJECTED to TRUE.
1514  * IGNORE_WHITESPACE tells whether whitespace should be considered when
1515  * matching. IS_PROP_HUNK indicates whether the hunk patches file content
1516  * or a property.
1517  * When this function returns, neither CONTENT->CURRENT_LINE nor
1518  * the file offset in the target file will have changed.
1519  * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
1520  * Do temporary allocations in POOL. */
1521 static svn_error_t *
1522 get_hunk_info(hunk_info_t **hi, patch_target_t *target,
1523               target_content_t *content,
1524               svn_diff_hunk_t *hunk, svn_linenum_t fuzz,
1525               svn_boolean_t ignore_whitespace,
1526               svn_boolean_t is_prop_hunk,
1527               svn_cancel_func_t cancel_func, void *cancel_baton,
1528               apr_pool_t *result_pool, apr_pool_t *scratch_pool)
1529 {
1530   svn_linenum_t matched_line;
1531   svn_linenum_t original_start;
1532   svn_boolean_t already_applied;
1533
1534   original_start = svn_diff_hunk_get_original_start(hunk);
1535   already_applied = FALSE;
1536
1537   /* An original offset of zero means that this hunk wants to create
1538    * a new file. Don't bother matching hunks in that case, since
1539    * the hunk applies at line 1. If the file already exists, the hunk
1540    * is rejected, unless the file is versioned and its content matches
1541    * the file the patch wants to create.  */
1542   if (original_start == 0 && fuzz > 0)
1543     {
1544       matched_line = 0; /* reject any fuzz for new files */
1545     }
1546   else if (original_start == 0 && ! is_prop_hunk)
1547     {
1548       if (target->kind_on_disk == svn_node_file)
1549         {
1550           const svn_io_dirent2_t *dirent;
1551           SVN_ERR(svn_io_stat_dirent2(&dirent, target->local_abspath, FALSE,
1552                                       TRUE, scratch_pool, scratch_pool));
1553
1554           if (dirent->kind == svn_node_file
1555               && !dirent->special
1556               && dirent->filesize == 0)
1557             {
1558               matched_line = 1; /* Matched an on-disk empty file */
1559             }
1560           else
1561             {
1562               if (target->db_kind == svn_node_file)
1563                 {
1564                   svn_boolean_t file_matches;
1565
1566                   /* ### I can't reproduce anything but a no-match here.
1567                          The content is already at eof, so any hunk fails */
1568                   SVN_ERR(match_existing_target(&file_matches, content, hunk,
1569                                             scratch_pool));
1570                   if (file_matches)
1571                     {
1572                       matched_line = 1;
1573                       already_applied = TRUE;
1574                     }
1575                   else
1576                     matched_line = 0; /* reject */
1577                 }
1578               else
1579                 matched_line = 0; /* reject */
1580             }
1581         }
1582       else
1583         matched_line = 1;
1584     }
1585   /* Same conditions apply as for the file case above.
1586    *
1587    * ### Since the hunk says the prop should be added we just assume so for
1588    * ### now and don't bother with storing the previous lines and such. When
1589    * ### we have the diff operation available we can just check for adds. */
1590   else if (original_start == 0 && is_prop_hunk)
1591     {
1592       if (content->existed)
1593         {
1594           svn_boolean_t prop_matches;
1595
1596           SVN_ERR(match_existing_target(&prop_matches, content, hunk,
1597                                         scratch_pool));
1598
1599           if (prop_matches)
1600             {
1601               matched_line = 1;
1602               already_applied = TRUE;
1603             }
1604           else
1605             matched_line = 0; /* reject */
1606         }
1607       else
1608         matched_line = 1;
1609     }
1610   else if (original_start > 0 && content->existed)
1611     {
1612       svn_linenum_t saved_line = content->current_line;
1613
1614       /* Scan for a match at the line where the hunk thinks it
1615        * should be going. */
1616       SVN_ERR(seek_to_line(content, original_start, scratch_pool));
1617       if (content->current_line != original_start)
1618         {
1619           /* Seek failed. */
1620           matched_line = 0;
1621         }
1622       else
1623         SVN_ERR(scan_for_match(&matched_line, content, hunk, TRUE,
1624                                original_start + 1, fuzz,
1625                                ignore_whitespace, FALSE,
1626                                cancel_func, cancel_baton,
1627                                scratch_pool));
1628
1629       if (matched_line != original_start)
1630         {
1631           /* Check if the hunk is already applied.
1632            * We only check for an exact match here, and don't bother checking
1633            * for already applied patches with offset/fuzz, because such a
1634            * check would be ambiguous. */
1635           if (fuzz == 0)
1636             {
1637               svn_linenum_t modified_start;
1638
1639               modified_start = svn_diff_hunk_get_modified_start(hunk);
1640               if (modified_start == 0)
1641                 {
1642                   /* Patch wants to delete the file. */
1643                   already_applied = target->locally_deleted;
1644                 }
1645               else
1646                 {
1647                   SVN_ERR(seek_to_line(content, modified_start,
1648                                        scratch_pool));
1649                   SVN_ERR(scan_for_match(&matched_line, content,
1650                                          hunk, TRUE,
1651                                          modified_start + 1,
1652                                          fuzz, ignore_whitespace, TRUE,
1653                                          cancel_func, cancel_baton,
1654                                          scratch_pool));
1655                   already_applied = (matched_line == modified_start);
1656                 }
1657             }
1658           else
1659             already_applied = FALSE;
1660
1661           if (! already_applied)
1662             {
1663               /* Scan the whole file again from the start. */
1664               SVN_ERR(seek_to_line(content, 1, scratch_pool));
1665
1666               /* Scan forward towards the hunk's line and look for a line
1667                * where the hunk matches. */
1668               SVN_ERR(scan_for_match(&matched_line, content, hunk, FALSE,
1669                                      original_start, fuzz,
1670                                      ignore_whitespace, FALSE,
1671                                      cancel_func, cancel_baton,
1672                                      scratch_pool));
1673
1674               /* In tie-break situations, we arbitrarily prefer early matches
1675                * to save us from scanning the rest of the file. */
1676               if (matched_line == 0)
1677                 {
1678                   /* Scan forward towards the end of the file and look
1679                    * for a line where the hunk matches. */
1680                   SVN_ERR(scan_for_match(&matched_line, content, hunk,
1681                                          TRUE, 0, fuzz, ignore_whitespace,
1682                                          FALSE, cancel_func, cancel_baton,
1683                                          scratch_pool));
1684                 }
1685             }
1686         }
1687
1688       SVN_ERR(seek_to_line(content, saved_line, scratch_pool));
1689     }
1690   else
1691     {
1692       /* The hunk wants to modify a file which doesn't exist. */
1693       matched_line = 0;
1694     }
1695
1696   (*hi) = apr_pcalloc(result_pool, sizeof(hunk_info_t));
1697   (*hi)->hunk = hunk;
1698   (*hi)->matched_line = matched_line;
1699   (*hi)->rejected = (matched_line == 0);
1700   (*hi)->already_applied = already_applied;
1701   (*hi)->fuzz = fuzz;
1702
1703   return SVN_NO_ERROR;
1704 }
1705
1706 /* Copy lines to the patched content until the specified LINE has been
1707  * reached. Indicate in *EOF whether end-of-file was encountered while
1708  * reading from the target.
1709  * If LINE is zero, copy lines until end-of-file has been reached.
1710  * Do all allocations in POOL. */
1711 static svn_error_t *
1712 copy_lines_to_target(target_content_t *content, svn_linenum_t line,
1713                      apr_pool_t *pool)
1714 {
1715   apr_pool_t *iterpool;
1716
1717   iterpool = svn_pool_create(pool);
1718   while ((content->current_line < line || line == 0) && ! content->eof)
1719     {
1720       const char *target_line;
1721       apr_size_t len;
1722
1723       svn_pool_clear(iterpool);
1724
1725       SVN_ERR(readline(content, &target_line, iterpool, iterpool));
1726       if (! content->eof)
1727         target_line = apr_pstrcat(iterpool, target_line, content->eol_str,
1728                                   (char *)NULL);
1729       len = strlen(target_line);
1730       SVN_ERR(content->write(content->write_baton, target_line,
1731                              len, iterpool));
1732     }
1733   svn_pool_destroy(iterpool);
1734
1735   return SVN_NO_ERROR;
1736 }
1737
1738 /* Write the diff text of HUNK to TARGET's reject file,
1739  * and mark TARGET as having had rejects.
1740  * We don't expand keywords, nor normalise line-endings, in reject files.
1741  * Do temporary allocations in SCRATCH_POOL. */
1742 static svn_error_t *
1743 reject_hunk(patch_target_t *target, target_content_t *content,
1744             svn_diff_hunk_t *hunk, const char *prop_name,
1745             apr_pool_t *pool)
1746 {
1747   const char *hunk_header;
1748   apr_size_t len;
1749   svn_boolean_t eof;
1750   static const char * const text_atat = "@@";
1751   static const char * const prop_atat = "##";
1752   const char *atat;
1753   apr_pool_t *iterpool;
1754
1755   if (prop_name)
1756     {
1757       const char *prop_header;
1758
1759       /* ### Print 'Added', 'Deleted' or 'Modified' instead of 'Property'.
1760        */
1761       prop_header = apr_psprintf(pool, "Property: %s\n", prop_name);
1762       len = strlen(prop_header);
1763       SVN_ERR(svn_io_file_write_full(target->reject_file, prop_header,
1764                                      len, &len, pool));
1765       atat = prop_atat;
1766     }
1767   else
1768     {
1769       atat = text_atat;
1770     }
1771
1772   hunk_header = apr_psprintf(pool, "%s -%lu,%lu +%lu,%lu %s%s",
1773                              atat,
1774                              svn_diff_hunk_get_original_start(hunk),
1775                              svn_diff_hunk_get_original_length(hunk),
1776                              svn_diff_hunk_get_modified_start(hunk),
1777                              svn_diff_hunk_get_modified_length(hunk),
1778                              atat,
1779                              APR_EOL_STR);
1780   len = strlen(hunk_header);
1781   SVN_ERR(svn_io_file_write_full(target->reject_file, hunk_header, len,
1782                                  &len, pool));
1783
1784   iterpool = svn_pool_create(pool);
1785   do
1786     {
1787       svn_stringbuf_t *hunk_line;
1788       const char *eol_str;
1789
1790       svn_pool_clear(iterpool);
1791
1792       SVN_ERR(svn_diff_hunk_readline_diff_text(hunk, &hunk_line, &eol_str,
1793                                                &eof, iterpool, iterpool));
1794       if (! eof)
1795         {
1796           if (hunk_line->len >= 1)
1797             {
1798               len = hunk_line->len;
1799               SVN_ERR(svn_io_file_write_full(target->reject_file,
1800                                              hunk_line->data, len, &len,
1801                                              iterpool));
1802             }
1803
1804           if (eol_str)
1805             {
1806               len = strlen(eol_str);
1807               SVN_ERR(svn_io_file_write_full(target->reject_file, eol_str,
1808                                              len, &len, iterpool));
1809             }
1810         }
1811     }
1812   while (! eof);
1813   svn_pool_destroy(iterpool);
1814
1815   if (prop_name)
1816     target->had_prop_rejects = TRUE;
1817   else
1818     target->had_rejects = TRUE;
1819
1820   return SVN_NO_ERROR;
1821 }
1822
1823 /* Write the modified text of the hunk described by HI to the patched
1824  * CONTENT. TARGET is the patch target.
1825  * If PROP_NAME is not NULL, the hunk is assumed to be targeted for
1826  * a property with the given name.
1827  * Do temporary allocations in POOL. */
1828 static svn_error_t *
1829 apply_hunk(patch_target_t *target, target_content_t *content,
1830            hunk_info_t *hi, const char *prop_name, apr_pool_t *pool)
1831 {
1832   svn_linenum_t lines_read;
1833   svn_boolean_t eof;
1834   apr_pool_t *iterpool;
1835
1836   /* ### Is there a cleaner way to describe if we have an existing target?
1837    */
1838   if (target->kind_on_disk == svn_node_file || prop_name)
1839     {
1840       svn_linenum_t line;
1841
1842       /* Move forward to the hunk's line, copying data as we go.
1843        * Also copy leading lines of context which matched with fuzz.
1844        * The target has changed on the fuzzy-matched lines,
1845        * so we should retain the target's version of those lines. */
1846       SVN_ERR(copy_lines_to_target(content, hi->matched_line + hi->fuzz,
1847                                    pool));
1848
1849       /* Skip the target's version of the hunk.
1850        * Don't skip trailing lines which matched with fuzz. */
1851       line = content->current_line +
1852              svn_diff_hunk_get_original_length(hi->hunk) - (2 * hi->fuzz);
1853       SVN_ERR(seek_to_line(content, line, pool));
1854       if (content->current_line != line && ! content->eof)
1855         {
1856           /* Seek failed, reject this hunk. */
1857           hi->rejected = TRUE;
1858           SVN_ERR(reject_hunk(target, content, hi->hunk, prop_name, pool));
1859           return SVN_NO_ERROR;
1860         }
1861     }
1862
1863   /* Write the hunk's version to the patched result.
1864    * Don't write the lines which matched with fuzz. */
1865   lines_read = 0;
1866   svn_diff_hunk_reset_modified_text(hi->hunk);
1867   iterpool = svn_pool_create(pool);
1868   do
1869     {
1870       svn_stringbuf_t *hunk_line;
1871       const char *eol_str;
1872
1873       svn_pool_clear(iterpool);
1874
1875       SVN_ERR(svn_diff_hunk_readline_modified_text(hi->hunk, &hunk_line,
1876                                                    &eol_str, &eof,
1877                                                    iterpool, iterpool));
1878       lines_read++;
1879       if (lines_read > hi->fuzz &&
1880           lines_read <= svn_diff_hunk_get_modified_length(hi->hunk) - hi->fuzz)
1881         {
1882           apr_size_t len;
1883
1884           if (hunk_line->len >= 1)
1885             {
1886               len = hunk_line->len;
1887               SVN_ERR(content->write(content->write_baton,
1888                                      hunk_line->data, len, iterpool));
1889             }
1890
1891           if (eol_str)
1892             {
1893               /* Use the EOL as it was read from the patch file,
1894                * unless the target's EOL style is set by svn:eol-style */
1895               if (content->eol_style != svn_subst_eol_style_none)
1896                 eol_str = content->eol_str;
1897
1898               len = strlen(eol_str);
1899               SVN_ERR(content->write(content->write_baton,
1900                                      eol_str, len, iterpool));
1901             }
1902         }
1903     }
1904   while (! eof);
1905   svn_pool_destroy(iterpool);
1906
1907   if (prop_name)
1908     target->has_prop_changes = TRUE;
1909   else
1910     target->has_text_changes = TRUE;
1911
1912   return SVN_NO_ERROR;
1913 }
1914
1915 /* Use client context CTX to send a suitable notification for hunk HI,
1916  * using TARGET to determine the path. If the hunk is a property hunk,
1917  * PROP_NAME must be the name of the property, else NULL.
1918  * Use POOL for temporary allocations. */
1919 static svn_error_t *
1920 send_hunk_notification(const hunk_info_t *hi,
1921                        const patch_target_t *target,
1922                        const char *prop_name,
1923                        const svn_client_ctx_t *ctx,
1924                        apr_pool_t *pool)
1925 {
1926   svn_wc_notify_t *notify;
1927   svn_wc_notify_action_t action;
1928
1929   if (hi->already_applied)
1930     action = svn_wc_notify_patch_hunk_already_applied;
1931   else if (hi->rejected)
1932     action = svn_wc_notify_patch_rejected_hunk;
1933   else
1934     action = svn_wc_notify_patch_applied_hunk;
1935
1936   notify = svn_wc_create_notify(target->local_abspath
1937                                     ? target->local_abspath
1938                                     : target->local_relpath,
1939                                 action, pool);
1940   notify->hunk_original_start =
1941     svn_diff_hunk_get_original_start(hi->hunk);
1942   notify->hunk_original_length =
1943     svn_diff_hunk_get_original_length(hi->hunk);
1944   notify->hunk_modified_start =
1945     svn_diff_hunk_get_modified_start(hi->hunk);
1946   notify->hunk_modified_length =
1947     svn_diff_hunk_get_modified_length(hi->hunk);
1948   notify->hunk_matched_line = hi->matched_line;
1949   notify->hunk_fuzz = hi->fuzz;
1950   notify->prop_name = prop_name;
1951
1952   (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
1953
1954   return SVN_NO_ERROR;
1955 }
1956
1957 /* Use client context CTX to send a suitable notification for a patch TARGET.
1958  * Use POOL for temporary allocations. */
1959 static svn_error_t *
1960 send_patch_notification(const patch_target_t *target,
1961                         const svn_client_ctx_t *ctx,
1962                         apr_pool_t *pool)
1963 {
1964   svn_wc_notify_t *notify;
1965   svn_wc_notify_action_t action;
1966
1967   if (! ctx->notify_func2)
1968     return SVN_NO_ERROR;
1969
1970   if (target->skipped)
1971     action = svn_wc_notify_skip;
1972   else if (target->deleted)
1973     action = svn_wc_notify_delete;
1974   else if (target->added || target->replaced)
1975     action = svn_wc_notify_add;
1976   else
1977     action = svn_wc_notify_patch;
1978
1979   notify = svn_wc_create_notify(target->local_abspath ? target->local_abspath
1980                                                  : target->local_relpath,
1981                                 action, pool);
1982   notify->kind = svn_node_file;
1983
1984   if (action == svn_wc_notify_skip)
1985     {
1986       if (target->db_kind == svn_node_none ||
1987           target->db_kind == svn_node_unknown)
1988         notify->content_state = svn_wc_notify_state_missing;
1989       else if (target->db_kind == svn_node_dir)
1990         notify->content_state = svn_wc_notify_state_obstructed;
1991       else
1992         notify->content_state = svn_wc_notify_state_unknown;
1993     }
1994   else
1995     {
1996       if (target->had_rejects)
1997         notify->content_state = svn_wc_notify_state_conflicted;
1998       else if (target->local_mods)
1999         notify->content_state = svn_wc_notify_state_merged;
2000       else if (target->has_text_changes)
2001         notify->content_state = svn_wc_notify_state_changed;
2002
2003       if (target->had_prop_rejects)
2004         notify->prop_state = svn_wc_notify_state_conflicted;
2005       else if (target->has_prop_changes)
2006         notify->prop_state = svn_wc_notify_state_changed;
2007     }
2008
2009   (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
2010
2011   if (action == svn_wc_notify_patch)
2012     {
2013       int i;
2014       apr_pool_t *iterpool;
2015       apr_hash_index_t *hash_index;
2016
2017       iterpool = svn_pool_create(pool);
2018       for (i = 0; i < target->content->hunks->nelts; i++)
2019         {
2020           const hunk_info_t *hi;
2021
2022           svn_pool_clear(iterpool);
2023
2024           hi = APR_ARRAY_IDX(target->content->hunks, i, hunk_info_t *);
2025
2026           SVN_ERR(send_hunk_notification(hi, target, NULL /* prop_name */,
2027                                          ctx, iterpool));
2028         }
2029
2030       for (hash_index = apr_hash_first(pool, target->prop_targets);
2031            hash_index;
2032            hash_index = apr_hash_next(hash_index))
2033         {
2034           prop_patch_target_t *prop_target;
2035
2036           prop_target = svn__apr_hash_index_val(hash_index);
2037
2038           for (i = 0; i < prop_target->content->hunks->nelts; i++)
2039             {
2040               const hunk_info_t *hi;
2041
2042               svn_pool_clear(iterpool);
2043
2044               hi = APR_ARRAY_IDX(prop_target->content->hunks, i,
2045                                  hunk_info_t *);
2046
2047               /* Don't notify on the hunk level for added or deleted props. */
2048               if (prop_target->operation != svn_diff_op_added &&
2049                   prop_target->operation != svn_diff_op_deleted)
2050                 SVN_ERR(send_hunk_notification(hi, target, prop_target->name,
2051                                                ctx, iterpool));
2052             }
2053         }
2054       svn_pool_destroy(iterpool);
2055     }
2056
2057   return SVN_NO_ERROR;
2058 }
2059
2060 /* Apply a PATCH to a working copy at ABS_WC_PATH and put the result
2061  * into temporary files, to be installed in the working copy later.
2062  * Return information about the patch target in *PATCH_TARGET, allocated
2063  * in RESULT_POOL. Use WC_CTX as the working copy context.
2064  * STRIP_COUNT specifies the number of leading path components
2065  * which should be stripped from target paths in the patch.
2066  * REMOVE_TEMPFILES, PATCH_FUNC, and PATCH_BATON as in svn_client_patch().
2067  * IGNORE_WHITESPACE tells whether whitespace should be considered when
2068  * doing the matching.
2069  * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
2070  * Do temporary allocations in SCRATCH_POOL. */
2071 static svn_error_t *
2072 apply_one_patch(patch_target_t **patch_target, svn_patch_t *patch,
2073                 const char *abs_wc_path, svn_wc_context_t *wc_ctx,
2074                 int strip_count,
2075                 svn_boolean_t ignore_whitespace,
2076                 svn_boolean_t remove_tempfiles,
2077                 svn_client_patch_func_t patch_func,
2078                 void *patch_baton,
2079                 svn_cancel_func_t cancel_func,
2080                 void *cancel_baton,
2081                 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
2082 {
2083   patch_target_t *target;
2084   apr_pool_t *iterpool;
2085   int i;
2086   static const svn_linenum_t MAX_FUZZ = 2;
2087   apr_hash_index_t *hash_index;
2088
2089   SVN_ERR(init_patch_target(&target, patch, abs_wc_path, wc_ctx, strip_count,
2090                             remove_tempfiles, result_pool, scratch_pool));
2091   if (target->skipped)
2092     {
2093       *patch_target = target;
2094       return SVN_NO_ERROR;
2095     }
2096
2097   if (patch_func)
2098     {
2099       SVN_ERR(patch_func(patch_baton, &target->filtered,
2100                          target->canon_path_from_patchfile,
2101                          target->patched_path, target->reject_path,
2102                          scratch_pool));
2103       if (target->filtered)
2104         {
2105           *patch_target = target;
2106           return SVN_NO_ERROR;
2107         }
2108     }
2109
2110   iterpool = svn_pool_create(scratch_pool);
2111   /* Match hunks. */
2112   for (i = 0; i < patch->hunks->nelts; i++)
2113     {
2114       svn_diff_hunk_t *hunk;
2115       hunk_info_t *hi;
2116       svn_linenum_t fuzz = 0;
2117
2118       svn_pool_clear(iterpool);
2119
2120       if (cancel_func)
2121         SVN_ERR(cancel_func(cancel_baton));
2122
2123       hunk = APR_ARRAY_IDX(patch->hunks, i, svn_diff_hunk_t *);
2124
2125       /* Determine the line the hunk should be applied at.
2126        * If no match is found initially, try with fuzz. */
2127       do
2128         {
2129           SVN_ERR(get_hunk_info(&hi, target, target->content, hunk, fuzz,
2130                                 ignore_whitespace,
2131                                 FALSE /* is_prop_hunk */,
2132                                 cancel_func, cancel_baton,
2133                                 result_pool, iterpool));
2134           fuzz++;
2135         }
2136       while (hi->rejected && fuzz <= MAX_FUZZ && ! hi->already_applied);
2137
2138       APR_ARRAY_PUSH(target->content->hunks, hunk_info_t *) = hi;
2139     }
2140
2141   /* Apply or reject hunks. */
2142   for (i = 0; i < target->content->hunks->nelts; i++)
2143     {
2144       hunk_info_t *hi;
2145
2146       svn_pool_clear(iterpool);
2147
2148       if (cancel_func)
2149         SVN_ERR(cancel_func(cancel_baton));
2150
2151       hi = APR_ARRAY_IDX(target->content->hunks, i, hunk_info_t *);
2152       if (hi->already_applied)
2153         continue;
2154       else if (hi->rejected)
2155         SVN_ERR(reject_hunk(target, target->content, hi->hunk,
2156                             NULL /* prop_name */,
2157                             iterpool));
2158       else
2159         SVN_ERR(apply_hunk(target, target->content, hi,
2160                            NULL /* prop_name */,  iterpool));
2161     }
2162
2163   if (target->kind_on_disk == svn_node_file)
2164     {
2165       /* Copy any remaining lines to target. */
2166       SVN_ERR(copy_lines_to_target(target->content, 0, scratch_pool));
2167       if (! target->content->eof)
2168         {
2169           /* We could not copy the entire target file to the temporary file,
2170            * and would truncate the target if we copied the temporary file
2171            * on top of it. Skip this target. */
2172           target->skipped = TRUE;
2173         }
2174     }
2175
2176   /* Match property hunks. */
2177   for (hash_index = apr_hash_first(scratch_pool, patch->prop_patches);
2178        hash_index;
2179        hash_index = apr_hash_next(hash_index))
2180     {
2181       svn_prop_patch_t *prop_patch;
2182       const char *prop_name;
2183       prop_patch_target_t *prop_target;
2184
2185       prop_name = svn__apr_hash_index_key(hash_index);
2186       prop_patch = svn__apr_hash_index_val(hash_index);
2187
2188       if (! strcmp(prop_name, SVN_PROP_SPECIAL))
2189         target->is_special = TRUE;
2190
2191       /* We'll store matched hunks in prop_content. */
2192       prop_target = svn_hash_gets(target->prop_targets, prop_name);
2193
2194       for (i = 0; i < prop_patch->hunks->nelts; i++)
2195         {
2196           svn_diff_hunk_t *hunk;
2197           hunk_info_t *hi;
2198           svn_linenum_t fuzz = 0;
2199
2200           svn_pool_clear(iterpool);
2201
2202           if (cancel_func)
2203             SVN_ERR(cancel_func(cancel_baton));
2204
2205           hunk = APR_ARRAY_IDX(prop_patch->hunks, i, svn_diff_hunk_t *);
2206
2207           /* Determine the line the hunk should be applied at.
2208            * If no match is found initially, try with fuzz. */
2209           do
2210             {
2211               SVN_ERR(get_hunk_info(&hi, target, prop_target->content,
2212                                     hunk, fuzz,
2213                                     ignore_whitespace,
2214                                     TRUE /* is_prop_hunk */,
2215                                     cancel_func, cancel_baton,
2216                                     result_pool, iterpool));
2217               fuzz++;
2218             }
2219           while (hi->rejected && fuzz <= MAX_FUZZ && ! hi->already_applied);
2220
2221           APR_ARRAY_PUSH(prop_target->content->hunks, hunk_info_t *) = hi;
2222         }
2223     }
2224
2225   /* Apply or reject property hunks. */
2226   for (hash_index = apr_hash_first(scratch_pool, target->prop_targets);
2227        hash_index;
2228        hash_index = apr_hash_next(hash_index))
2229     {
2230       prop_patch_target_t *prop_target;
2231
2232       prop_target = svn__apr_hash_index_val(hash_index);
2233
2234       for (i = 0; i < prop_target->content->hunks->nelts; i++)
2235         {
2236           hunk_info_t *hi;
2237
2238           svn_pool_clear(iterpool);
2239
2240           hi = APR_ARRAY_IDX(prop_target->content->hunks, i,
2241                              hunk_info_t *);
2242           if (hi->already_applied)
2243             continue;
2244           else if (hi->rejected)
2245             SVN_ERR(reject_hunk(target, prop_target->content, hi->hunk,
2246                                 prop_target->name,
2247                                 iterpool));
2248           else
2249             SVN_ERR(apply_hunk(target, prop_target->content, hi,
2250                                prop_target->name,
2251                                iterpool));
2252         }
2253
2254         if (prop_target->content->existed)
2255           {
2256             /* Copy any remaining lines to target. */
2257             SVN_ERR(copy_lines_to_target(prop_target->content, 0,
2258                                          scratch_pool));
2259             if (! prop_target->content->eof)
2260               {
2261                 /* We could not copy the entire target property to the
2262                  * temporary file, and would truncate the target if we
2263                  * copied the temporary file on top of it. Skip this target.  */
2264                 target->skipped = TRUE;
2265               }
2266           }
2267       }
2268
2269   svn_pool_destroy(iterpool);
2270
2271   if (!target->is_symlink)
2272     {
2273       /* Now close files we don't need any longer to get their contents
2274        * flushed to disk.
2275        * But we're not closing the reject file -- it still needed and
2276        * will be closed later in write_out_rejected_hunks(). */
2277       if (target->kind_on_disk == svn_node_file)
2278         SVN_ERR(svn_io_file_close(target->file, scratch_pool));
2279
2280       SVN_ERR(svn_io_file_close(target->patched_file, scratch_pool));
2281     }
2282
2283   if (! target->skipped)
2284     {
2285       apr_finfo_t working_file;
2286       apr_finfo_t patched_file;
2287
2288       /* Get sizes of the patched temporary file and the working file.
2289        * We'll need those to figure out whether we should delete the
2290        * patched file. */
2291       SVN_ERR(svn_io_stat(&patched_file, target->patched_path,
2292                           APR_FINFO_SIZE | APR_FINFO_LINK, scratch_pool));
2293       if (target->kind_on_disk == svn_node_file)
2294         SVN_ERR(svn_io_stat(&working_file, target->local_abspath,
2295                             APR_FINFO_SIZE | APR_FINFO_LINK, scratch_pool));
2296       else
2297         working_file.size = 0;
2298
2299       if (patched_file.size == 0 && working_file.size > 0)
2300         {
2301           /* If a unidiff removes all lines from a file, that usually
2302            * means deletion, so we can confidently schedule the target
2303            * for deletion. In the rare case where the unidiff was really
2304            * meant to replace a file with an empty one, this may not
2305            * be desirable. But the deletion can easily be reverted and
2306            * creating an empty file manually is not exactly hard either. */
2307           target->deleted = (target->db_kind == svn_node_file);
2308         }
2309       else if (patched_file.size == 0 && working_file.size == 0)
2310         {
2311           /* The target was empty or non-existent to begin with
2312            * and no content was changed by patching.
2313            * Report this as skipped if it didn't exist, unless in the special
2314            * case of adding an empty file which has properties set on it or
2315            * adding an empty file with a 'git diff' */
2316           if (target->kind_on_disk == svn_node_none
2317               && ! target->has_prop_changes
2318               && ! target->added)
2319             target->skipped = TRUE;
2320         }
2321       else if (patched_file.size > 0 && working_file.size == 0)
2322         {
2323           /* The patch has created a file. */
2324           if (target->locally_deleted)
2325             target->replaced = TRUE;
2326           else if (target->db_kind == svn_node_none)
2327             target->added = TRUE;
2328         }
2329     }
2330
2331   *patch_target = target;
2332
2333   return SVN_NO_ERROR;
2334 }
2335
2336 /* Try to create missing parent directories for TARGET in the working copy
2337  * rooted at ABS_WC_PATH, and add the parents to version control.
2338  * If the parents cannot be created, mark the target as skipped.
2339  * Use client context CTX. If DRY_RUN is true, do not create missing
2340  * parents but issue notifications only.
2341  * Use SCRATCH_POOL for temporary allocations. */
2342 static svn_error_t *
2343 create_missing_parents(patch_target_t *target,
2344                        const char *abs_wc_path,
2345                        svn_client_ctx_t *ctx,
2346                        svn_boolean_t dry_run,
2347                        apr_pool_t *scratch_pool)
2348 {
2349   const char *local_abspath;
2350   apr_array_header_t *components;
2351   int present_components;
2352   int i;
2353   apr_pool_t *iterpool;
2354
2355   /* Check if we can safely create the target's parent. */
2356   local_abspath = abs_wc_path;
2357   components = svn_path_decompose(target->local_relpath, scratch_pool);
2358   present_components = 0;
2359   iterpool = svn_pool_create(scratch_pool);
2360   for (i = 0; i < components->nelts - 1; i++)
2361     {
2362       const char *component;
2363       svn_node_kind_t wc_kind, disk_kind;
2364
2365       svn_pool_clear(iterpool);
2366
2367       component = APR_ARRAY_IDX(components, i, const char *);
2368       local_abspath = svn_dirent_join(local_abspath, component, scratch_pool);
2369
2370       SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, local_abspath,
2371                                 FALSE, TRUE, iterpool));
2372
2373       SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, iterpool));
2374
2375       if (disk_kind == svn_node_file || wc_kind == svn_node_file)
2376         {
2377           /* on-disk files and missing files are obstructions */
2378           target->skipped = TRUE;
2379           break;
2380         }
2381       else if (disk_kind == svn_node_dir)
2382         {
2383           if (wc_kind == svn_node_dir)
2384             present_components++;
2385           else
2386             {
2387               target->skipped = TRUE;
2388               break;
2389             }
2390         }
2391       else if (wc_kind != svn_node_none)
2392         {
2393           /* Node is missing */
2394           target->skipped = TRUE;
2395           break;
2396         }
2397       else
2398         {
2399           /* It's not a file, it's not a dir...
2400              Let's add a dir */
2401           break;
2402         }
2403     }
2404   if (! target->skipped)
2405     {
2406       local_abspath = abs_wc_path;
2407       for (i = 0; i < present_components; i++)
2408         {
2409           const char *component;
2410           component = APR_ARRAY_IDX(components, i, const char *);
2411           local_abspath = svn_dirent_join(local_abspath,
2412                                           component, scratch_pool);
2413         }
2414
2415       if (!dry_run && present_components < components->nelts - 1)
2416         SVN_ERR(svn_io_make_dir_recursively(
2417                         svn_dirent_join(
2418                                    abs_wc_path,
2419                                    svn_relpath_dirname(target->local_relpath,
2420                                                        scratch_pool),
2421                                    scratch_pool),
2422                         scratch_pool));
2423
2424       for (i = present_components; i < components->nelts - 1; i++)
2425         {
2426           const char *component;
2427
2428           svn_pool_clear(iterpool);
2429
2430           component = APR_ARRAY_IDX(components, i, const char *);
2431           local_abspath = svn_dirent_join(local_abspath, component,
2432                                           scratch_pool);
2433           if (dry_run)
2434             {
2435               if (ctx->notify_func2)
2436                 {
2437                   /* Just do notification. */
2438                   svn_wc_notify_t *notify;
2439                   notify = svn_wc_create_notify(local_abspath,
2440                                                 svn_wc_notify_add,
2441                                                 iterpool);
2442                   notify->kind = svn_node_dir;
2443                   ctx->notify_func2(ctx->notify_baton2, notify,
2444                                     iterpool);
2445                 }
2446             }
2447           else
2448             {
2449               /* Create the missing component and add it
2450                * to version control. Allow cancellation since we
2451                * have not modified the working copy yet for this
2452                * target. */
2453
2454               if (ctx->cancel_func)
2455                 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
2456
2457               SVN_ERR(svn_wc_add_from_disk2(ctx->wc_ctx, local_abspath,
2458                                             NULL /*props*/,
2459                                             ctx->notify_func2, ctx->notify_baton2,
2460                                             iterpool));
2461             }
2462         }
2463     }
2464
2465   svn_pool_destroy(iterpool);
2466   return SVN_NO_ERROR;
2467 }
2468
2469 /* Install a patched TARGET into the working copy at ABS_WC_PATH.
2470  * Use client context CTX to retrieve WC_CTX, and possibly doing
2471  * notifications. If DRY_RUN is TRUE, don't modify the working copy.
2472  * Do temporary allocations in POOL. */
2473 static svn_error_t *
2474 install_patched_target(patch_target_t *target, const char *abs_wc_path,
2475                        svn_client_ctx_t *ctx, svn_boolean_t dry_run,
2476                        apr_pool_t *pool)
2477 {
2478   if (target->deleted)
2479     {
2480       if (! dry_run)
2481         {
2482           /* Schedule the target for deletion.  Suppress
2483            * notification, we'll do it manually in a minute
2484            * because we also need to notify during dry-run.
2485            * Also suppress cancellation, because we'd rather
2486            * notify about what we did before aborting. */
2487           SVN_ERR(svn_wc_delete4(ctx->wc_ctx, target->local_abspath,
2488                                  FALSE /* keep_local */, FALSE,
2489                                  NULL, NULL, NULL, NULL, pool));
2490         }
2491     }
2492   else
2493     {
2494       svn_node_kind_t parent_db_kind;
2495       if (target->added || target->replaced)
2496         {
2497           const char *parent_abspath;
2498
2499           parent_abspath = svn_dirent_dirname(target->local_abspath,
2500                                               pool);
2501           /* If the target's parent directory does not yet exist
2502            * we need to create it before we can copy the patched
2503            * result in place. */
2504           SVN_ERR(svn_wc_read_kind2(&parent_db_kind, ctx->wc_ctx,
2505                                     parent_abspath, FALSE, FALSE, pool));
2506
2507           /* We can't add targets under nodes scheduled for delete, so add
2508              a new directory if needed. */
2509           if (parent_db_kind == svn_node_dir
2510               || parent_db_kind == svn_node_file)
2511             {
2512               if (parent_db_kind != svn_node_dir)
2513                 target->skipped = TRUE;
2514               else
2515                 {
2516                   svn_node_kind_t disk_kind;
2517
2518                   SVN_ERR(svn_io_check_path(parent_abspath, &disk_kind, pool));
2519                   if (disk_kind != svn_node_dir)
2520                     target->skipped = TRUE;
2521                 }
2522             }
2523           else
2524             SVN_ERR(create_missing_parents(target, abs_wc_path, ctx,
2525                                            dry_run, pool));
2526
2527         }
2528       else
2529         {
2530           svn_node_kind_t wc_kind;
2531
2532           /* The target should exist */
2533           SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx,
2534                                     target->local_abspath,
2535                                     FALSE, FALSE, pool));
2536
2537           if (target->kind_on_disk == svn_node_none
2538               || wc_kind != target->kind_on_disk)
2539             {
2540               target->skipped = TRUE;
2541             }
2542         }
2543
2544       if (! dry_run && ! target->skipped)
2545         {
2546           if (target->is_special)
2547             {
2548               svn_stream_t *stream;
2549               svn_stream_t *patched_stream;
2550
2551               SVN_ERR(svn_stream_open_readonly(&patched_stream,
2552                                                target->patched_path,
2553                                                pool, pool));
2554               SVN_ERR(svn_subst_create_specialfile(&stream,
2555                                                    target->local_abspath,
2556                                                    pool, pool));
2557               SVN_ERR(svn_stream_copy3(patched_stream, stream,
2558                                        ctx->cancel_func, ctx->cancel_baton,
2559                                        pool));
2560             }
2561           else
2562             {
2563               svn_boolean_t repair_eol;
2564
2565               /* Copy the patched file on top of the target file.
2566                * Always expand keywords in the patched file, but repair EOL
2567                * only if svn:eol-style dictates a particular style. */
2568               repair_eol = (target->content->eol_style ==
2569                               svn_subst_eol_style_fixed ||
2570                             target->content->eol_style ==
2571                               svn_subst_eol_style_native);
2572
2573               SVN_ERR(svn_subst_copy_and_translate4(
2574                         target->patched_path, target->local_abspath,
2575                         target->content->eol_str, repair_eol,
2576                         target->content->keywords,
2577                         TRUE /* expand */, FALSE /* special */,
2578                         ctx->cancel_func, ctx->cancel_baton, pool));
2579             }
2580
2581           if (target->added || target->replaced)
2582             {
2583               /* The target file didn't exist previously,
2584                * so add it to version control.
2585                * Suppress notification, we'll do that later (and also
2586                * during dry-run). Don't allow cancellation because
2587                * we'd rather notify about what we did before aborting. */
2588               SVN_ERR(svn_wc_add_from_disk2(ctx->wc_ctx, target->local_abspath,
2589                                             NULL /*props*/,
2590                                             NULL, NULL, pool));
2591             }
2592
2593           /* Restore the target's executable bit if necessary. */
2594           SVN_ERR(svn_io_set_file_executable(target->local_abspath,
2595                                              target->executable,
2596                                              FALSE, pool));
2597         }
2598     }
2599
2600   return SVN_NO_ERROR;
2601 }
2602
2603 /* Write out rejected hunks, if any, to TARGET->REJECT_PATH. If DRY_RUN is
2604  * TRUE, don't modify the working copy.
2605  * Do temporary allocations in POOL.
2606  */
2607 static svn_error_t *
2608 write_out_rejected_hunks(patch_target_t *target,
2609                          svn_boolean_t dry_run,
2610                          apr_pool_t *pool)
2611 {
2612   SVN_ERR(svn_io_file_close(target->reject_file, pool));
2613
2614   if (! dry_run && (target->had_rejects || target->had_prop_rejects))
2615     {
2616       /* Write out rejected hunks, if any. */
2617       SVN_ERR(svn_io_copy_file(target->reject_path,
2618                                apr_psprintf(pool, "%s.svnpatch.rej",
2619                                target->local_abspath),
2620                                FALSE, pool));
2621       /* ### TODO mark file as conflicted. */
2622     }
2623   return SVN_NO_ERROR;
2624 }
2625
2626 /* Install the patched properties for TARGET. Use client context CTX to
2627  * retrieve WC_CTX. If DRY_RUN is TRUE, don't modify the working copy.
2628  * Do temporary allocations in SCRATCH_POOL. */
2629 static svn_error_t *
2630 install_patched_prop_targets(patch_target_t *target,
2631                              svn_client_ctx_t *ctx, svn_boolean_t dry_run,
2632                              apr_pool_t *scratch_pool)
2633 {
2634   apr_hash_index_t *hi;
2635   apr_pool_t *iterpool;
2636
2637   iterpool = svn_pool_create(scratch_pool);
2638
2639   for (hi = apr_hash_first(scratch_pool, target->prop_targets);
2640        hi;
2641        hi = apr_hash_next(hi))
2642     {
2643       prop_patch_target_t *prop_target = svn__apr_hash_index_val(hi);
2644       const svn_string_t *prop_val;
2645       svn_error_t *err;
2646
2647       svn_pool_clear(iterpool);
2648
2649       if (ctx->cancel_func)
2650         SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
2651
2652       /* For a deleted prop we only set the value to NULL. */
2653       if (prop_target->operation == svn_diff_op_deleted)
2654         {
2655           if (! dry_run)
2656             SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, target->local_abspath,
2657                                      prop_target->name, NULL, svn_depth_empty,
2658                                      TRUE /* skip_checks */,
2659                                      NULL /* changelist_filter */,
2660                                      NULL, NULL /* cancellation */,
2661                                      NULL, NULL /* notification */,
2662                                      iterpool));
2663           continue;
2664         }
2665
2666       /* If the patch target doesn't exist yet, the patch wants to add an
2667        * empty file with properties set on it. So create an empty file and
2668        * add it to version control. But if the patch was in the 'git format'
2669        * then the file has already been added.
2670        *
2671        * ### How can we tell whether the patch really wanted to create
2672        * ### an empty directory? */
2673       if (! target->has_text_changes
2674           && target->kind_on_disk == svn_node_none
2675           && ! target->added)
2676         {
2677           if (! dry_run)
2678             {
2679               SVN_ERR(svn_io_file_create(target->local_abspath, "",
2680                                          scratch_pool));
2681               SVN_ERR(svn_wc_add_from_disk2(ctx->wc_ctx, target->local_abspath,
2682                                             NULL /*props*/,
2683                                             /* suppress notification */
2684                                             NULL, NULL,
2685                                             iterpool));
2686             }
2687           target->added = TRUE;
2688         }
2689
2690       /* Attempt to set the property, and reject all hunks if this
2691          fails.  If the property had a non-empty value, but now has
2692          an empty one, we'll just delete the property altogether.  */
2693       if (prop_target->value && prop_target->value->len
2694           && prop_target->patched_value && !prop_target->patched_value->len)
2695         prop_val = NULL;
2696       else
2697         prop_val = svn_stringbuf__morph_into_string(prop_target->patched_value);
2698
2699       if (dry_run)
2700         {
2701           const svn_string_t *canon_propval;
2702
2703           err = svn_wc_canonicalize_svn_prop(&canon_propval,
2704                                              prop_target->name,
2705                                              prop_val, target->local_abspath,
2706                                              target->db_kind,
2707                                              TRUE, /* ### Skipping checks */
2708                                              NULL, NULL,
2709                                              iterpool);
2710         }
2711       else
2712         {
2713           err = svn_wc_prop_set4(ctx->wc_ctx, target->local_abspath,
2714                                  prop_target->name, prop_val, svn_depth_empty,
2715                                  TRUE /* skip_checks */,
2716                                  NULL /* changelist_filter */,
2717                                  NULL, NULL /* cancellation */,
2718                                  NULL, NULL /* notification */,
2719                                  iterpool);
2720         }
2721
2722       if (err)
2723         {
2724           /* ### The errors which svn_wc_canonicalize_svn_prop() will
2725            * ### return aren't documented. */
2726           if (err->apr_err == SVN_ERR_ILLEGAL_TARGET ||
2727               err->apr_err == SVN_ERR_NODE_UNEXPECTED_KIND ||
2728               err->apr_err == SVN_ERR_IO_UNKNOWN_EOL ||
2729               err->apr_err == SVN_ERR_BAD_MIME_TYPE ||
2730               err->apr_err == SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION)
2731             {
2732               int i;
2733
2734               svn_error_clear(err);
2735
2736               for (i = 0; i < prop_target->content->hunks->nelts; i++)
2737                 {
2738                   hunk_info_t *hunk_info;
2739
2740                   hunk_info = APR_ARRAY_IDX(prop_target->content->hunks,
2741                                             i, hunk_info_t *);
2742                   hunk_info->rejected = TRUE;
2743                   SVN_ERR(reject_hunk(target, prop_target->content,
2744                                       hunk_info->hunk, prop_target->name,
2745                                       iterpool));
2746                 }
2747             }
2748           else
2749             return svn_error_trace(err);
2750         }
2751
2752     }
2753
2754   svn_pool_destroy(iterpool);
2755
2756   return SVN_NO_ERROR;
2757 }
2758
2759 /* Baton for can_delete_callback */
2760 struct can_delete_baton_t
2761 {
2762   svn_boolean_t must_keep;
2763   const apr_array_header_t *targets_info;
2764   const char *local_abspath;
2765 };
2766
2767 /* Implements svn_wc_status_func4_t. */
2768 static svn_error_t *
2769 can_delete_callback(void *baton,
2770                     const char *abspath,
2771                     const svn_wc_status3_t *status,
2772                     apr_pool_t *pool)
2773 {
2774   struct can_delete_baton_t *cb = baton;
2775   int i;
2776
2777   switch(status->node_status)
2778     {
2779       case svn_wc_status_none:
2780       case svn_wc_status_deleted:
2781         return SVN_NO_ERROR;
2782
2783       default:
2784         if (! strcmp(cb->local_abspath, abspath))
2785           return SVN_NO_ERROR; /* Only interested in descendants */
2786
2787         for (i = 0; i < cb->targets_info->nelts; i++)
2788           {
2789             const patch_target_info_t *target_info =
2790                APR_ARRAY_IDX(cb->targets_info, i, const patch_target_info_t *);
2791
2792             if (! strcmp(target_info->local_abspath, abspath))
2793               {
2794                 if (target_info->deleted)
2795                   return SVN_NO_ERROR;
2796
2797                 break; /* Cease invocation; must keep */
2798               }
2799           }
2800
2801         cb->must_keep = TRUE;
2802
2803         return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL);
2804     }
2805 }
2806
2807 static svn_error_t *
2808 check_ancestor_delete(const char *deleted_target,
2809                       apr_array_header_t *targets_info,
2810                       const char *apply_root,
2811                       svn_boolean_t dry_run,
2812                       svn_client_ctx_t *ctx,
2813                       apr_pool_t *result_pool,
2814                       apr_pool_t *scratch_pool)
2815 {
2816   struct can_delete_baton_t cb;
2817   svn_error_t *err;
2818   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
2819
2820   const char *dir_abspath = svn_dirent_dirname(deleted_target, scratch_pool);
2821
2822   while (svn_dirent_is_child(apply_root, dir_abspath, iterpool))
2823     {
2824       svn_pool_clear(iterpool);
2825
2826       cb.local_abspath = dir_abspath;
2827       cb.must_keep = FALSE;
2828       cb.targets_info = targets_info;
2829
2830       err = svn_wc_walk_status(ctx->wc_ctx, dir_abspath, svn_depth_infinity,
2831                                TRUE, FALSE, FALSE, NULL,
2832                                can_delete_callback, &cb,
2833                                ctx->cancel_func, ctx->cancel_baton,
2834                                iterpool);
2835
2836       if (err)
2837         {
2838           if (err->apr_err != SVN_ERR_CEASE_INVOCATION)
2839             return svn_error_trace(err);
2840
2841           svn_error_clear(err);
2842         }
2843
2844       if (cb.must_keep)
2845       {
2846         break;
2847       }
2848
2849       if (! dry_run)
2850         {
2851           SVN_ERR(svn_wc_delete4(ctx->wc_ctx, dir_abspath, FALSE, FALSE,
2852                                  ctx->cancel_func, ctx->cancel_baton,
2853                                  NULL, NULL,
2854                                  scratch_pool));
2855         }
2856
2857       {
2858         patch_target_info_t *pti = apr_pcalloc(result_pool, sizeof(*pti));
2859
2860         pti->local_abspath = apr_pstrdup(result_pool, dir_abspath);
2861         pti->deleted = TRUE;
2862
2863         APR_ARRAY_PUSH(targets_info, patch_target_info_t *) = pti;
2864       }
2865
2866
2867       if (ctx->notify_func2)
2868         {
2869           svn_wc_notify_t *notify;
2870
2871           notify = svn_wc_create_notify(dir_abspath, svn_wc_notify_delete,
2872                                     iterpool);
2873           notify->kind = svn_node_dir;
2874
2875           ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
2876         }
2877
2878       /* And check if we must also delete the parent */
2879       dir_abspath = svn_dirent_dirname(dir_abspath, scratch_pool);
2880     }
2881
2882   svn_pool_destroy(iterpool);
2883
2884   return SVN_NO_ERROR;
2885 }
2886
2887 /* This function is the main entry point into the patch code. */
2888 static svn_error_t *
2889 apply_patches(/* The path to the patch file. */
2890               const char *patch_abspath,
2891               /* The abspath to the working copy the patch should be applied to. */
2892               const char *abs_wc_path,
2893               /* Indicates whether we're doing a dry run. */
2894               svn_boolean_t dry_run,
2895               /* Number of leading components to strip from patch target paths. */
2896               int strip_count,
2897               /* Whether to apply the patch in reverse. */
2898               svn_boolean_t reverse,
2899               /* Whether to ignore whitespace when matching context lines. */
2900               svn_boolean_t ignore_whitespace,
2901               /* As in svn_client_patch(). */
2902               svn_boolean_t remove_tempfiles,
2903               /* As in svn_client_patch(). */
2904               svn_client_patch_func_t patch_func,
2905               void *patch_baton,
2906               /* The client context. */
2907               svn_client_ctx_t *ctx,
2908               apr_pool_t *scratch_pool)
2909 {
2910   svn_patch_t *patch;
2911   apr_pool_t *iterpool;
2912   svn_patch_file_t *patch_file;
2913   apr_array_header_t *targets_info;
2914
2915   /* Try to open the patch file. */
2916   SVN_ERR(svn_diff_open_patch_file(&patch_file, patch_abspath, scratch_pool));
2917
2918   /* Apply patches. */
2919   targets_info = apr_array_make(scratch_pool, 0,
2920                                 sizeof(patch_target_info_t *));
2921   iterpool = svn_pool_create(scratch_pool);
2922   do
2923     {
2924       svn_pool_clear(iterpool);
2925
2926       if (ctx->cancel_func)
2927         SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
2928
2929       SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file,
2930                                         reverse, ignore_whitespace,
2931                                         iterpool, iterpool));
2932       if (patch)
2933         {
2934           patch_target_t *target;
2935
2936           SVN_ERR(apply_one_patch(&target, patch, abs_wc_path,
2937                                   ctx->wc_ctx, strip_count,
2938                                   ignore_whitespace, remove_tempfiles,
2939                                   patch_func, patch_baton,
2940                                   ctx->cancel_func, ctx->cancel_baton,
2941                                   iterpool, iterpool));
2942           if (! target->filtered)
2943             {
2944               /* Save info we'll still need when we're done patching. */
2945               patch_target_info_t *target_info =
2946                 apr_pcalloc(scratch_pool, sizeof(patch_target_info_t));
2947               target_info->local_abspath = apr_pstrdup(scratch_pool,
2948                                                        target->local_abspath);
2949               target_info->deleted = target->deleted;
2950
2951               if (! target->skipped)
2952                 {
2953                   APR_ARRAY_PUSH(targets_info,
2954                                  patch_target_info_t *) = target_info;
2955
2956                   if (target->has_text_changes
2957                       || target->added
2958                       || target->deleted)
2959                     SVN_ERR(install_patched_target(target, abs_wc_path,
2960                                                    ctx, dry_run, iterpool));
2961
2962                   if (target->has_prop_changes && (!target->deleted))
2963                     SVN_ERR(install_patched_prop_targets(target, ctx,
2964                                                          dry_run, iterpool));
2965
2966                   SVN_ERR(write_out_rejected_hunks(target, dry_run, iterpool));
2967                 }
2968               SVN_ERR(send_patch_notification(target, ctx, iterpool));
2969
2970               if (target->deleted && !target->skipped)
2971                 {
2972                   SVN_ERR(check_ancestor_delete(target_info->local_abspath,
2973                                                 targets_info, abs_wc_path,
2974                                                 dry_run, ctx,
2975                                                 scratch_pool, iterpool));
2976                 }
2977             }
2978         }
2979     }
2980   while (patch);
2981
2982   SVN_ERR(svn_diff_close_patch_file(patch_file, iterpool));
2983   svn_pool_destroy(iterpool);
2984
2985   return SVN_NO_ERROR;
2986 }
2987
2988 svn_error_t *
2989 svn_client_patch(const char *patch_abspath,
2990                  const char *wc_dir_abspath,
2991                  svn_boolean_t dry_run,
2992                  int strip_count,
2993                  svn_boolean_t reverse,
2994                  svn_boolean_t ignore_whitespace,
2995                  svn_boolean_t remove_tempfiles,
2996                  svn_client_patch_func_t patch_func,
2997                  void *patch_baton,
2998                  svn_client_ctx_t *ctx,
2999                  apr_pool_t *scratch_pool)
3000 {
3001   svn_node_kind_t kind;
3002
3003   if (strip_count < 0)
3004     return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
3005                             _("strip count must be positive"));
3006
3007   if (svn_path_is_url(wc_dir_abspath))
3008     return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3009                              _("'%s' is not a local path"),
3010                              svn_dirent_local_style(wc_dir_abspath,
3011                                                     scratch_pool));
3012
3013   SVN_ERR(svn_io_check_path(patch_abspath, &kind, scratch_pool));
3014   if (kind == svn_node_none)
3015     return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3016                              _("'%s' does not exist"),
3017                              svn_dirent_local_style(patch_abspath,
3018                                                     scratch_pool));
3019   if (kind != svn_node_file)
3020     return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3021                              _("'%s' is not a file"),
3022                              svn_dirent_local_style(patch_abspath,
3023                                                     scratch_pool));
3024
3025   SVN_ERR(svn_io_check_path(wc_dir_abspath, &kind, scratch_pool));
3026   if (kind == svn_node_none)
3027     return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3028                              _("'%s' does not exist"),
3029                              svn_dirent_local_style(wc_dir_abspath,
3030                                                     scratch_pool));
3031   if (kind != svn_node_dir)
3032     return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
3033                              _("'%s' is not a directory"),
3034                              svn_dirent_local_style(wc_dir_abspath,
3035                                                     scratch_pool));
3036
3037   SVN_WC__CALL_WITH_WRITE_LOCK(
3038     apply_patches(patch_abspath, wc_dir_abspath, dry_run, strip_count,
3039                   reverse, ignore_whitespace, remove_tempfiles,
3040                   patch_func, patch_baton, ctx, scratch_pool),
3041     ctx->wc_ctx, wc_dir_abspath, FALSE /* lock_anchor */, scratch_pool);
3042   return SVN_NO_ERROR;
3043 }